This commit is contained in:
seb
2026-07-31 19:38:52 +02:00
parent d08cf7f2df
commit 7cd053ada6
9 changed files with 221 additions and 117 deletions

View File

@@ -17,6 +17,8 @@ function valuesEqual(a, b) {
return JSON.stringify(a) === JSON.stringify(b);
}
const ACTION_PADDING = 12;
/**
* Panel tile with centralized inline-edit chrome.
* Pass `onSave` to enable edit UI; omit it for read-only tiles (no edit icon).
@@ -120,14 +122,13 @@ class EditablePanelTileBase extends Component {
};
renderTitle() {
const { title } = this.props;
const { editing } = this.state;
const { title, onSave } = this.props;
if (!title) return null;
return (
<Typography
variant="overline"
color="text.secondary"
sx={{ display: 'block', mb: 1, pr: editing ? 9 : 5 }}
sx={{ display: 'block', mb: 1, pr: onSave ? 9 : 0 }}
>
{title}
</Typography>
@@ -148,32 +149,39 @@ class EditablePanelTileBase extends Component {
if (!editing) {
return (
<IconButton
onClick={this.handleStartEdit}
aria-label="Bearbeiten"
sx={{ ...actionSx, right: 4 }}
>
<EditOutlinedIcon sx={iconSx} />
</IconButton>
<>
<IconButton
disabled
aria-hidden
tabIndex={-1}
sx={{
...actionSx,
right: 48,
visibility: 'hidden',
pointerEvents: 'none',
}}
>
<SaveOutlinedIcon sx={iconSx} />
</IconButton>
<IconButton
onClick={this.handleStartEdit}
aria-label="Bearbeiten"
sx={{ ...actionSx, right: 4 }}
>
<EditOutlinedIcon sx={iconSx} />
</IconButton>
</>
);
}
return (
<>
<IconButton
onClick={this.handleCancel}
disabled={saving}
aria-label="Abbrechen"
sx={{ ...actionSx, right: 48 }}
>
<CloseOutlinedIcon sx={iconSx} />
</IconButton>
<IconButton
onClick={this.handleSave}
disabled={!this.canSave()}
aria-label="Speichern"
color={this.canSave() ? 'primary' : 'default'}
sx={{ ...actionSx, right: 4 }}
sx={{ ...actionSx, right: 48 }}
>
{saving ? (
<CircularProgress size={22} color="inherit" />
@@ -181,23 +189,25 @@ class EditablePanelTileBase extends Component {
<SaveOutlinedIcon sx={iconSx} />
)}
</IconButton>
<IconButton
onClick={this.handleCancel}
disabled={saving}
aria-label="Abbrechen"
sx={{ ...actionSx, right: 4 }}
>
<CloseOutlinedIcon sx={iconSx} />
</IconButton>
</>
);
}
render() {
const {
theme,
wide = 1,
sx,
minHeight = 120,
tileKey,
renderDisplay,
renderEdit,
} = this.props;
const { onSave, theme, wide = 1, sx, minHeight = 120, tileKey, renderDisplay, renderEdit } =
this.props;
const { editing, draft, error } = this.state;
const savedValue = this.getSavedValue();
const pending = this.isDirty();
const actionPadding = onSave ? ACTION_PADDING : 0;
return (
<Paper
@@ -206,6 +216,7 @@ class EditablePanelTileBase extends Component {
wide,
sx,
minHeight,
editing,
pending,
tileKey,
theme,
@@ -214,7 +225,13 @@ class EditablePanelTileBase extends Component {
{this.renderActions()}
{this.renderTitle()}
<Box
sx={{ flex: 1, minWidth: 0, pr: editing ? 1 : 0 }}
sx={{
flex: 1,
minWidth: 0,
pr: actionPadding,
display: 'flex',
alignItems: 'center',
}}
onKeyDown={editing ? this.handleEditKeyDown : undefined}
>
{editing && draft != null

View File

@@ -1,6 +1,7 @@
const GRID_COLUMNS = 4;
export const PENDING_EDIT_BORDER = '#ec4899';
export const PENDING_EDIT_BORDER = '#e11d48';
export const EDITING_TILE_BORDER = '#94a3b8';
function clampWide(wide) {
return Math.min(Math.max(wide, 1), 3);
@@ -24,10 +25,16 @@ export function panelTileBackground(theme, tileKey) {
export const PANEL_TILE_BORDER_RADIUS = 4;
function tileRingBoxShadow(ringColor, elevationShadow) {
const inset = `inset 0 0 0 ${PENDING_EDIT_RING_PX}px ${ringColor}`;
return elevationShadow ? `${inset}, ${elevationShadow}` : inset;
}
export function panelTilePaperSx({
wide,
sx,
minHeight = 120,
editing = false,
pending = false,
tileKey,
theme,
@@ -35,6 +42,11 @@ export function panelTilePaperSx({
const tileBg = panelTileBackground(theme, tileKey);
const elevationShadow = theme?.shadows?.[4];
const ringColor = pending
? PENDING_EDIT_BORDER
: editing
? EDITING_TILE_BORDER
: null;
return {
gridColumn: responsiveColumnSpan(wide),
@@ -46,9 +58,9 @@ export function panelTilePaperSx({
border: 'none',
borderRadius: PANEL_TILE_BORDER_RADIUS,
...(tileBg ? { bgcolor: tileBg } : {}),
...(pending && elevationShadow
...(ringColor
? {
boxShadow: `inset 0 0 0 ${PENDING_EDIT_RING_PX}px ${PENDING_EDIT_BORDER}, ${elevationShadow}`,
boxShadow: tileRingBoxShadow(ringColor, elevationShadow),
}
: {}),
...sx,