This commit is contained in:
seb
2026-07-31 20:10:23 +02:00
parent 7cd053ada6
commit d5d30fb286
9 changed files with 349 additions and 37 deletions

View File

@@ -11,14 +11,12 @@ import Typography from '@mui/material/Typography';
import { injectMuiTheme } from './injectMuiTheme';
import { enqueueSnackbar } from 'notistack';
import { backendErrorFrom } from '../../utils/backendError';
import { panelTilePaperSx } from './panelTileLayout';
import { panelTilePaperSx, TILE_ACTION_PADDING, tileActionIconSx, tileActionSx } from './panelTileLayout';
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).
@@ -140,12 +138,7 @@ class EditablePanelTileBase extends Component {
if (!onSave) return null;
const { editing, saving } = this.state;
const actionSx = {
position: 'absolute',
top: 4,
color: 'text.secondary',
};
const iconSx = { fontSize: 28 };
const iconSx = tileActionIconSx;
if (!editing) {
return (
@@ -155,7 +148,7 @@ class EditablePanelTileBase extends Component {
aria-hidden
tabIndex={-1}
sx={{
...actionSx,
...tileActionSx,
right: 48,
visibility: 'hidden',
pointerEvents: 'none',
@@ -166,7 +159,7 @@ class EditablePanelTileBase extends Component {
<IconButton
onClick={this.handleStartEdit}
aria-label="Bearbeiten"
sx={{ ...actionSx, right: 4 }}
sx={{ ...tileActionSx, right: 4 }}
>
<EditOutlinedIcon sx={iconSx} />
</IconButton>
@@ -181,7 +174,7 @@ class EditablePanelTileBase extends Component {
disabled={!this.canSave()}
aria-label="Speichern"
color={this.canSave() ? 'primary' : 'default'}
sx={{ ...actionSx, right: 48 }}
sx={{ ...tileActionSx, right: 48 }}
>
{saving ? (
<CircularProgress size={22} color="inherit" />
@@ -193,7 +186,7 @@ class EditablePanelTileBase extends Component {
onClick={this.handleCancel}
disabled={saving}
aria-label="Abbrechen"
sx={{ ...actionSx, right: 4 }}
sx={{ ...tileActionSx, right: 4 }}
>
<CloseOutlinedIcon sx={iconSx} />
</IconButton>
@@ -207,7 +200,7 @@ class EditablePanelTileBase extends Component {
const { editing, draft, error } = this.state;
const savedValue = this.getSavedValue();
const pending = this.isDirty();
const actionPadding = onSave ? ACTION_PADDING : 0;
const actionPadding = onSave ? TILE_ACTION_PADDING : 0;
return (
<Paper

View File

@@ -1,27 +1,75 @@
import { Component } from 'react';
import { Component, createRef } from 'react';
import Box from '@mui/material/Box';
import CircularProgress from '@mui/material/CircularProgress';
import IconButton from '@mui/material/IconButton';
import Paper from '@mui/material/Paper';
import Typography from '@mui/material/Typography';
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
import { injectMuiTheme } from './injectMuiTheme';
import { GRID_COLUMNS, panelTilePaperSx } from './panelTileLayout';
import {
GRID_COLUMNS,
TILE_ACTION_PADDING,
panelTilePaperSx,
tileActionIconSx,
tileActionSx,
} from './panelTileLayout';
class PanelTileBase extends Component {
renderTitle() {
const { title } = this.props;
const { title, action } = this.props;
if (!title) return null;
return (
<Typography
variant="overline"
color="text.secondary"
sx={{ display: 'block', mb: 1 }}
sx={{ display: 'block', mb: 1, pr: action ? 9 : 0 }}
>
{title}
</Typography>
);
}
renderAction() {
const { action } = this.props;
if (!action) return null;
const { label, icon: Icon, onClick, busy } = action;
return (
<>
<IconButton
disabled
aria-hidden
tabIndex={-1}
sx={{
...tileActionSx,
right: 48,
visibility: 'hidden',
pointerEvents: 'none',
}}
>
<SaveOutlinedIcon sx={tileActionIconSx} />
</IconButton>
<IconButton
type="button"
onClick={onClick}
aria-label={label}
disabled={busy}
sx={{ ...tileActionSx, right: 4 }}
>
{busy ? (
<CircularProgress size={22} color="inherit" />
) : (
<Icon sx={tileActionIconSx} />
)}
</IconButton>
</>
);
}
render() {
const { theme, wide = 1, children, sx, minHeight = 120, tileKey } = this.props;
const { theme, wide = 1, children, sx, minHeight = 120, tileKey, action } = this.props;
const actionPadding = action ? TILE_ACTION_PADDING : 0;
return (
<Paper
@@ -34,8 +82,9 @@ class PanelTileBase extends Component {
theme,
})}
>
{this.renderAction()}
{this.renderTitle()}
<Box sx={{ flex: 1, minWidth: 0 }}>{children}</Box>
<Box sx={{ flex: 1, minWidth: 0, pr: actionPadding }}>{children}</Box>
</Paper>
);
}

View File

@@ -17,6 +17,17 @@ export function responsiveColumnSpan(wide) {
}
export const PENDING_EDIT_RING_PX = 4;
export const TILE_ACTION_PADDING = 12;
export const tileActionSx = {
position: 'absolute',
top: 4,
color: 'text.secondary',
};
export const tileActionIconSx = {
fontSize: 28,
};
export function panelTileBackground(theme, tileKey) {
if (!tileKey || !theme?.panelTiles) return undefined;