diff --git a/README.md b/README.md index 31a0521..46f5780 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,19 @@ To test the full PWA precache (production build + preview, no HMR), use Dashback must be running and your Google account must be authorized on the backend. +## Shared GUI library (`dashgui`) + +Reusable custom GUI class components live in the **`dashgui`** package +(`../dashgui`), installed here as a `file:` dependency (npm workspace symlink) +so source imports are reflected instantly. + +- `dashgui/panels` — `PanelTile`, `PanelTileGrid`, `EditablePanelTile`, layout helpers +- `dashgui/tiles` — base `Tile` (extended by `src/components/tiles/*`) +- `dashgui/SingleLineTextEdit`, `dashgui/ElevatedPill` + +See `../dashgui/README.md` for details and the dev/mock-page harness used for +future test cases. + ## Auth flow 1. On boot the app calls `GET /api/auth/me` with `credentials: 'include'`. diff --git a/package-lock.json b/package-lock.json index 4335f79..ae86a0a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@mui/material": "^9.2.0", "@reduxjs/toolkit": "^2.12.0", "@shopify/draggable": "^1.2.1", + "dashgui": "file:../dashgui", "notistack": "^3.0.2", "react": "^19.2.8", "react-dom": "^19.2.8", @@ -31,6 +32,27 @@ "vite-plugin-pwa": "^1.3.0" } }, + "../dashgui": { + "version": "0.1.0", + "devDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^9.2.0", + "@mui/material": "^9.2.0", + "@vitejs/plugin-react": "6.0.2", + "react": "^19.2.8", + "react-dom": "^19.2.8", + "vite": "^8.2.0" + }, + "peerDependencies": { + "@emotion/react": "^11.14.0", + "@emotion/styled": "^11.14.1", + "@mui/icons-material": "^9.0.0", + "@mui/material": "^9.0.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + }, "node_modules/@apideck/better-ajv-errors": { "version": "0.3.7", "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.7.tgz", @@ -3575,6 +3597,10 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/dashgui": { + "resolved": "../dashgui", + "link": true + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", diff --git a/package.json b/package.json index de50bae..ab5798c 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "@reduxjs/toolkit": "^2.12.0", "@shopify/draggable": "^1.2.1", "notistack": "^3.0.2", + "dashgui": "file:../dashgui", "react": "^19.2.8", "react-dom": "^19.2.8", "react-redux": "^9.3.0", diff --git a/src/components/CompanySettingsDialog.jsx b/src/components/CompanySettingsDialog.jsx index 7640969..2b17fd8 100644 --- a/src/components/CompanySettingsDialog.jsx +++ b/src/components/CompanySettingsDialog.jsx @@ -24,7 +24,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import PointOfSaleIcon from '@mui/icons-material/PointOfSale'; import { enqueueSnackbar } from 'notistack'; import BackendError from './BackendError'; -import SingleLineTextEdit from './SingleLineTextEdit'; +import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import { StorageManagerContext } from '../api/storageManagerContext'; import { requireDefined, requireObject } from '../utils/apiShape'; import { ISO639_OPTIONS, parseTaxRate } from '../utils/companyForm'; diff --git a/src/components/ElevatedPill.jsx b/src/components/ElevatedPill.jsx deleted file mode 100644 index f5c0314..0000000 --- a/src/components/ElevatedPill.jsx +++ /dev/null @@ -1,81 +0,0 @@ -import { Component } from 'react'; -import Box from '@mui/material/Box'; -import Switch from '@mui/material/Switch'; -import Typography from '@mui/material/Typography'; -import { alpha } from '@mui/material/styles'; - -/** Base pill with elevation, shared styling, and an overridable trailing element. */ -class ElevatedPill extends Component { - renderTrailing() { - return null; - } - - render() { - const { label, active = false, onClick } = this.props; - return ( - ({ - display: 'inline-flex', - alignItems: 'center', - gap: 0.5, - px: 1.5, - py: 0.25, - borderRadius: 999, - background: active - ? `linear-gradient(180deg, ${alpha(theme.palette.primary.main, 0.28)}, ${alpha( - theme.palette.primary.main, - 0.22, - )})` - : `linear-gradient(180deg, ${alpha(theme.palette.primary.main, 0.16)}, ${alpha( - theme.palette.primary.main, - 0.10, - )})`, - color: 'text.primary', - cursor: 'pointer', - userSelect: 'none', - boxShadow: theme.shadows[1], - transition: 'background 150ms ease, box-shadow 150ms ease', - })} - > - - {label} - - {this.renderTrailing()} - - ); - } -} - -export class ToggleElevatedPill extends ElevatedPill { - renderTrailing() { - const { checked, onToggle } = this.props; - return ( - onToggle(event.target.checked)} - sx={(theme) => ({ - '& .MuiSwitch-switchBase': { - color: checked ? 'success.main' : 'grey.300', - }, - '& .Mui-checked': { - color: 'success.main', - }, - '& .Mui-checked + .MuiSwitch-track': { - backgroundColor: alpha(theme.palette.success.main, 0.35), - }, - })} - /> - ); - } -} - -export class ActionElevatedPill extends ElevatedPill { - renderTrailing() { - return this.props.icon; - } -} - -export default ElevatedPill; diff --git a/src/components/NameDialog.jsx b/src/components/NameDialog.jsx index f3c5e7d..48d8c05 100644 --- a/src/components/NameDialog.jsx +++ b/src/components/NameDialog.jsx @@ -7,7 +7,7 @@ import DialogContent from '@mui/material/DialogContent'; import DialogTitle from '@mui/material/DialogTitle'; import Alert from '@mui/material/Alert'; import BackendError from './BackendError'; -import SingleLineTextEdit from './SingleLineTextEdit'; +import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import { backendErrorFrom, isBackendFailure } from '../utils/backendError'; /** diff --git a/src/components/SectionLayout.jsx b/src/components/SectionLayout.jsx index 5696b49..5acfaf4 100644 --- a/src/components/SectionLayout.jsx +++ b/src/components/SectionLayout.jsx @@ -13,7 +13,7 @@ import PageLayout from './PageLayout'; import { NAV_ITEMS, SUB_NAV, resolveActiveSub } from '../nav'; import { isKategorienPath } from '../pages/artikel/categoryRoute'; import { selectRenameMode, setRenameMode } from '../store/categorySlice'; -import { ActionElevatedPill, ToggleElevatedPill } from './ElevatedPill'; +import { ActionElevatedPill, ToggleElevatedPill } from 'dashgui/ElevatedPill'; import { article, category } from '../api'; function triggerDownload(blob, filename) { diff --git a/src/components/SingleLineTextEdit.jsx b/src/components/SingleLineTextEdit.jsx deleted file mode 100644 index 50bc35d..0000000 --- a/src/components/SingleLineTextEdit.jsx +++ /dev/null @@ -1,62 +0,0 @@ -import { Component } from 'react'; -import TextField from '@mui/material/TextField'; - -/** - * Centralized single-line name field — same outlined look everywhere. - * Props: value, onChange(nextValue), plus optional TextField props. - */ -class SingleLineTextEdit extends Component { - render() { - const { - value, - onChange, - inputRef, - label = 'Name', - disabled = false, - readOnly = false, - autoFocus = false, - autoComplete = 'off', - size, - fullWidth = true, - required = false, - sx, - slotProps, - ...rest - } = this.props; - - return ( - { - if (!readOnly) onChange(event.target.value); - }} - autoFocus={autoFocus} - autoComplete={autoComplete} - slotProps={{ - ...slotProps, - input: { - readOnly, - ...slotProps?.input, - }, - htmlInput: { - spellCheck: false, - readOnly, - tabIndex: readOnly ? -1 : undefined, - ...slotProps?.htmlInput, - }, - }} - sx={sx} - {...rest} - /> - ); - } -} - -export default SingleLineTextEdit; diff --git a/src/components/article/ArticlePhotoTile.jsx b/src/components/article/ArticlePhotoTile.jsx index 6c114e6..016a501 100644 --- a/src/components/article/ArticlePhotoTile.jsx +++ b/src/components/article/ArticlePhotoTile.jsx @@ -2,7 +2,7 @@ import { PureComponent, createRef } from 'react'; import ContentPasteIcon from '@mui/icons-material/ContentPaste'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import { StorageManagerContext } from '../../api/storageManagerContext'; -import { PanelTile } from '../panels'; +import { PanelTile } from 'dashgui/panels'; import { requireObject } from '../../utils/apiShape'; import { notifyError } from '../../utils/notify'; import { prepareArticlePhoto } from '../../utils/prepareArticlePhoto'; diff --git a/src/components/article/ArticleTileTab.jsx b/src/components/article/ArticleTileTab.jsx index 49d0c33..ec4530b 100644 --- a/src/components/article/ArticleTileTab.jsx +++ b/src/components/article/ArticleTileTab.jsx @@ -24,8 +24,8 @@ import { } from '../../pages/artikel/articleRoute'; import { router } from '../../appRouter'; import { matchPath } from 'react-router-dom'; -import { PanelTile } from '../panels'; -import { PANEL_TILE_BORDER_RADIUS, responsiveColumnSpan } from '../panels/panelTileLayout'; +import { PanelTile } from 'dashgui/panels'; +import { PANEL_TILE_BORDER_RADIUS, responsiveColumnSpan } from 'dashgui/panels/panelTileLayout'; import ArticleAttributesTab from './tabs/ArticleAttributesTab'; import ArticleCompoundTab from './tabs/ArticleCompoundTab'; import ArticleHistoryTab from './tabs/ArticleHistoryTab'; diff --git a/src/components/article/ArticleView.jsx b/src/components/article/ArticleView.jsx index 88fbbbf..fb4bbca 100644 --- a/src/components/article/ArticleView.jsx +++ b/src/components/article/ArticleView.jsx @@ -1,5 +1,5 @@ import { PureComponent } from 'react'; -import { PanelTileGrid } from '../panels'; +import { PanelTileGrid } from 'dashgui/panels'; import { StorageManagerContext } from '../../api/storageManagerContext'; import withNavigate from '../withNavigate'; import { diff --git a/src/components/article/boxes/ArticleViewBrand.jsx b/src/components/article/boxes/ArticleViewBrand.jsx index a462f5d..56cbced 100644 --- a/src/components/article/boxes/ArticleViewBrand.jsx +++ b/src/components/article/boxes/ArticleViewBrand.jsx @@ -3,7 +3,7 @@ import Autocomplete from '@mui/material/Autocomplete'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; -import { EditablePanelTile } from '../../panels'; +import { EditablePanelTile } from 'dashgui/panels'; import { StorageManagerContext } from '../../../api/storageManagerContext'; import { requireDefined, requireObject } from '../../../utils/apiShape'; import { notifyError } from '../../../utils/notify'; @@ -150,6 +150,7 @@ export default class ArticleViewBrand extends PureComponent { color="#e9d5ff" value={value} onSave={this.handleSave} + onError={notifyError} isDirty={brandDraftDirty} canSave={brandDraftValid} display={{value.name || null}} diff --git a/src/components/article/boxes/ArticleViewEANs.jsx b/src/components/article/boxes/ArticleViewEANs.jsx index cc8febf..e0b3aa1 100644 --- a/src/components/article/boxes/ArticleViewEANs.jsx +++ b/src/components/article/boxes/ArticleViewEANs.jsx @@ -3,10 +3,11 @@ import Autocomplete from '@mui/material/Autocomplete'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import Box from '@mui/material/Box'; -import { EditablePanelTile } from '../../panels'; +import { EditablePanelTile } from 'dashgui/panels'; import { StorageManagerContext } from '../../../api/storageManagerContext'; import { requireObject } from '../../../utils/apiShape'; import { articleEans } from '../../../utils/articleDetail'; +import { notifyError } from '../../../utils/notify'; class PanelValue extends Component { render() { const { label, children } = this.props; @@ -115,6 +116,7 @@ export default class ArticleViewEANs extends PureComponent { contentAlignItems="flex-start" value={value} onSave={this.handleSave} + onError={notifyError} isDirty={eansDraftDirty} canSave={eansDraftValid} display={} diff --git a/src/components/article/boxes/ArticleViewLists.jsx b/src/components/article/boxes/ArticleViewLists.jsx index 17ce63e..8f86b3c 100644 --- a/src/components/article/boxes/ArticleViewLists.jsx +++ b/src/components/article/boxes/ArticleViewLists.jsx @@ -2,7 +2,7 @@ import { Component, PureComponent } from 'react'; import { Link as RouterLink } from 'react-router-dom'; import Link from '@mui/material/Link'; import Typography from '@mui/material/Typography'; -import { PanelTile } from '../../panels'; +import { PanelTile } from 'dashgui/panels'; import { artikelDetailPath } from '../../../pages/artikel/articleRoute'; class CountLine extends Component { diff --git a/src/components/article/boxes/ArticleViewName.jsx b/src/components/article/boxes/ArticleViewName.jsx index 0e1c16b..7d5a5d5 100644 --- a/src/components/article/boxes/ArticleViewName.jsx +++ b/src/components/article/boxes/ArticleViewName.jsx @@ -1,11 +1,12 @@ import { Component, PureComponent } from 'react'; -import { EditablePanelTile } from '../../panels'; -import SingleLineTextEdit from '../../SingleLineTextEdit'; +import { EditablePanelTile } from 'dashgui/panels'; +import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; import { StorageManagerContext } from '../../../api/storageManagerContext'; import { getCategoryBreadcrumbs } from '../../../store/categorySlice'; import { Box, Chip, IconButton } from '@mui/material'; import AddIcon from '@mui/icons-material/Add'; import CategoryPickerDialog from '../CategoryPickerDialog'; +import { notifyError } from '../../../utils/notify'; const articleNameTextSx = { fontWeight: 700, @@ -288,6 +289,7 @@ export default class ArticleViewName extends PureComponent { color="#ffffff" value={value} onSave={this.handleSave} + onError={notifyError} isDirty={() => draft != null && draftDirty(draft, value)} canSave={() => draft != null && draftValid(draft)} display={ diff --git a/src/components/article/boxes/ArticleViewPrice.jsx b/src/components/article/boxes/ArticleViewPrice.jsx index f74a93d..ca353bd 100644 --- a/src/components/article/boxes/ArticleViewPrice.jsx +++ b/src/components/article/boxes/ArticleViewPrice.jsx @@ -3,7 +3,7 @@ import Box from '@mui/material/Box'; import InputAdornment from '@mui/material/InputAdornment'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; -import { EditablePanelTile } from '../../panels'; +import { EditablePanelTile } from 'dashgui/panels'; import ArticlePriceTiersDialog from '../ArticlePriceTiersDialog'; import ArticleUnitEditorDialog from '../ArticleUnitEditorDialog'; import { StorageManagerContext } from '../../../api/storageManagerContext'; @@ -510,6 +510,7 @@ export default class ArticleViewPrice extends PureComponent { contentAlignItems="flex-start" value={priceValue} onSave={this.handleSave} + onError={notifyError} isDirty={priceDraftDirty} canSave={priceDraftValid} overlay={({ editing, draft, onChange }) => ( diff --git a/src/components/article/boxes/ArticleViewStock.jsx b/src/components/article/boxes/ArticleViewStock.jsx index d6f98d4..1fcee43 100644 --- a/src/components/article/boxes/ArticleViewStock.jsx +++ b/src/components/article/boxes/ArticleViewStock.jsx @@ -1,7 +1,7 @@ import { Component, PureComponent } from 'react'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import { PanelTile } from '../../panels'; +import { PanelTile } from 'dashgui/panels'; import { formatStock } from '../../../pages/artikel/searchFormat'; class PanelValue extends Component { diff --git a/src/components/manifest/ManifestForm.jsx b/src/components/manifest/ManifestForm.jsx index 8d6de61..915aaea 100644 --- a/src/components/manifest/ManifestForm.jsx +++ b/src/components/manifest/ManifestForm.jsx @@ -4,7 +4,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import Typography from '@mui/material/Typography'; import { enqueueSnackbar } from 'notistack'; import BackendError from '../BackendError'; -import { PanelTile, PanelTileGrid } from '../panels'; +import { PanelTile, PanelTileGrid } from 'dashgui/panels'; import { StorageManagerContext } from '../../api/storageManagerContext'; import { requireArray, requireDefined, requireObject } from '../../utils/apiShape'; import { backendErrorFrom } from '../../utils/backendError'; diff --git a/src/components/manifest/ManifestHeader.jsx b/src/components/manifest/ManifestHeader.jsx index 1e23d21..3ac6138 100644 --- a/src/components/manifest/ManifestHeader.jsx +++ b/src/components/manifest/ManifestHeader.jsx @@ -2,8 +2,8 @@ import { PureComponent } from 'react'; import Autocomplete from '@mui/material/Autocomplete'; import Stack from '@mui/material/Stack'; import TextField from '@mui/material/TextField'; -import SingleLineTextEdit from '../SingleLineTextEdit'; -import { PanelTile } from '../panels'; +import SingleLineTextEdit from 'dashgui/SingleLineTextEdit'; +import { PanelTile } from 'dashgui/panels'; import { manifestConfig, partyDisplayName } from './manifestConfig'; class ManifestHeader extends PureComponent { diff --git a/src/components/manifest/ManifestLines.jsx b/src/components/manifest/ManifestLines.jsx index 6ac588e..a3e9b98 100644 --- a/src/components/manifest/ManifestLines.jsx +++ b/src/components/manifest/ManifestLines.jsx @@ -9,7 +9,7 @@ import TableContainer from '@mui/material/TableContainer'; import TableHead from '@mui/material/TableHead'; import TableRow from '@mui/material/TableRow'; import AddIcon from '@mui/icons-material/Add'; -import { PanelTile } from '../panels'; +import { PanelTile } from 'dashgui/panels'; import { LINE_COLUMNS } from './manifestConfig'; import ManifestFormLine from './ManifestFormLine'; diff --git a/src/components/manifest/ManifestList.jsx b/src/components/manifest/ManifestList.jsx index bd8ad41..8ab3e12 100644 --- a/src/components/manifest/ManifestList.jsx +++ b/src/components/manifest/ManifestList.jsx @@ -15,7 +15,7 @@ import DeleteOutlinedIcon from '@mui/icons-material/DeleteOutlined'; import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; import { enqueueSnackbar } from 'notistack'; import BackendError from '../BackendError'; -import { PanelTile, PanelTileGrid } from '../panels'; +import { PanelTile, PanelTileGrid } from 'dashgui/panels'; import { StorageManagerContext } from '../../api/storageManagerContext'; import { backendErrorFrom } from '../../utils/backendError'; import { notifyError } from '../../utils/notify'; diff --git a/src/components/panels/EditablePanelTile.jsx b/src/components/panels/EditablePanelTile.jsx deleted file mode 100644 index 2e7f0e0..0000000 --- a/src/components/panels/EditablePanelTile.jsx +++ /dev/null @@ -1,278 +0,0 @@ -import { Component, createRef } from 'react'; -import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined'; -import EditOutlinedIcon from '@mui/icons-material/EditOutlined'; -import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; -import Alert from '@mui/material/Alert'; -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 { injectMuiTheme } from './injectMuiTheme'; -import { notifyError } from '../../utils/notify'; -import { panelTilePaperSx, TILE_ACTION_PADDING, tileActionIconSx, tileActionSx } from './panelTileLayout'; - -function valuesEqual(a, b) { - return JSON.stringify(a) === JSON.stringify(b); -} - -/** - * Panel tile with centralized inline-edit chrome. - * Pass `onSave` to enable edit UI; omit it for read-only tiles (no edit icon). - * - * Content: - * display — React node shown when not editing - * edit — React node, or ( { draft, onChange, inputRef } ) => node, while editing - * overlay — optional ( { editing, draft, onChange } ) => node - */ -class EditablePanelTileBase extends Component { - constructor(props) { - super(props); - this.inputRef = createRef(); - this.state = { - editing: false, - draft: null, - saving: false, - error: null, - }; - } - - componentDidUpdate(_prevProps, prevState) { - if (!prevState.editing && this.state.editing) { - this.inputRef.current?.focus(); - } - } - - getSavedValue() { - return this.props.value; - } - - isDirty() { - const { draft, editing } = this.state; - if (!editing || draft == null) return false; - if (this.props.isDirty) { - return this.props.isDirty(draft, this.getSavedValue()); - } - return !valuesEqual(draft, this.getSavedValue()); - } - - canSave() { - const { draft, saving } = this.state; - if (saving || !this.isDirty() || draft == null) return false; - if (this.props.canSave) { - return this.props.canSave(draft, this.getSavedValue()); - } - return true; - } - - handleStartEdit = (event) => { - event.stopPropagation(); - this.setState({ - editing: true, - draft: structuredClone(this.getSavedValue()), - error: null, - }); - }; - - handleCancel = (event) => { - event?.stopPropagation(); - this.setState({ - editing: false, - draft: null, - error: null, - }); - }; - - handleDraftChange = (nextDraft) => { - this.setState({ draft: nextDraft, error: null }); - }; - - handleEditKeyDown = (event) => { - if (event.key === 'Escape') { - event.preventDefault(); - event.stopPropagation(); - this.handleCancel(); - return; - } - - if (event.key === 'Enter' && !event.shiftKey) { - event.preventDefault(); - event.stopPropagation(); - if (this.canSave()) { - void this.handleSave(event); - } - } - }; - - handleSave = async (event) => { - event?.stopPropagation(); - if (!this.canSave()) return; - - const { onSave } = this.props; - const { draft } = this.state; - - try { - await onSave(draft); - this.setState({ editing: false, draft: null, saving: false, error: null }); - } catch (err) { - const normalized = notifyError(err); - this.setState({ error: normalized.message }); - } - }; - - renderTitle() { - const { title, onSave } = this.props; - if (!title) return null; - return ( - - {title} - - ); - } - - renderActions() { - const { onSave } = this.props; - if (!onSave) return null; - - const { editing, saving } = this.state; - const iconSx = tileActionIconSx; - - if (!editing) { - return ( - <> - - - - - - - - ); - } - - return ( - <> - - {saving ? ( - - ) : ( - - )} - - - - - - ); - } - - renderBody() { - const { display, edit } = this.props; - const { editing, draft } = this.state; - - if (editing && draft != null) { - if (typeof edit === 'function') { - return edit({ - draft, - onChange: this.handleDraftChange, - inputRef: this.inputRef, - }); - } - return edit; - } - return display; - } - - renderOverlayNode() { - const { overlay } = this.props; - const { editing, draft } = this.state; - if (typeof overlay !== 'function') return null; - return overlay({ - editing, - draft, - onChange: this.handleDraftChange, - }); - } - - render() { - const { - onSave, - theme, - wide = 1, - sx, - minHeight = 120, - color, - contentAlignItems = 'center', - } = this.props; - const { editing, error } = this.state; - const pending = this.isDirty(); - const actionPadding = onSave ? TILE_ACTION_PADDING : 0; - - return ( - - {this.renderActions()} - {this.renderOverlayNode()} - {this.renderTitle()} - - {this.renderBody()} - - {error ? ( - - {error} - - ) : null} - - ); - } -} - -export const EditablePanelTile = injectMuiTheme(EditablePanelTileBase); diff --git a/src/components/panels/PanelTile.jsx b/src/components/panels/PanelTile.jsx deleted file mode 100644 index ebe54e0..0000000 --- a/src/components/panels/PanelTile.jsx +++ /dev/null @@ -1,123 +0,0 @@ -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, - TILE_ACTION_PADDING, - panelTilePaperSx, - tileActionIconSx, - tileActionSx, -} from './panelTileLayout'; - -class PanelTileBase extends Component { - renderTitle() { - const { title, action } = this.props; - if (!title) return null; - return ( - - {title} - - ); - } - - renderAction() { - const { action } = this.props; - if (!action) return null; - - const { label, icon: Icon, onClick, busy } = action; - - return ( - <> - - - - - {busy ? ( - - ) : ( - - )} - - - ); - } - - render() { - const { - theme, - wide = 1, - children, - sx, - minHeight = 120, - color, - action, - elevation = 4, - } = this.props; - const actionPadding = action ? TILE_ACTION_PADDING : 0; - - return ( - - {this.renderAction()} - {this.renderTitle()} - {children} - - ); - } -} - -export const PanelTile = injectMuiTheme(PanelTileBase); - -export function PanelTileGrid({ children, sx }) { - return ( - - {children} - - ); -} - -export { GRID_COLUMNS }; diff --git a/src/components/panels/index.js b/src/components/panels/index.js deleted file mode 100644 index d26b4a4..0000000 --- a/src/components/panels/index.js +++ /dev/null @@ -1,3 +0,0 @@ -export { PanelTile, PanelTileGrid, GRID_COLUMNS } from './PanelTile'; -export { EditablePanelTile } from './EditablePanelTile'; -export { PENDING_EDIT_BORDER } from './panelTileLayout'; diff --git a/src/components/panels/injectMuiTheme.jsx b/src/components/panels/injectMuiTheme.jsx deleted file mode 100644 index 6ad40fa..0000000 --- a/src/components/panels/injectMuiTheme.jsx +++ /dev/null @@ -1,13 +0,0 @@ -import { useTheme } from '@mui/material/styles'; - -/** Injects MUI theme into class components (replaces removed withTheme HOC). */ -export function injectMuiTheme(Wrapped) { - function ComponentWithTheme(props) { - const theme = useTheme(); - return ; - } - - const name = Wrapped.displayName || Wrapped.name || 'Component'; - ComponentWithTheme.displayName = `injectMuiTheme(${name})`; - return ComponentWithTheme; -} diff --git a/src/components/panels/panelTileLayout.js b/src/components/panels/panelTileLayout.js deleted file mode 100644 index 980e5e2..0000000 --- a/src/components/panels/panelTileLayout.js +++ /dev/null @@ -1,74 +0,0 @@ -const GRID_COLUMNS = 4; - -export const PENDING_EDIT_BORDER = '#e11d48'; -export const EDITING_TILE_BORDER = '#94a3b8'; - -function clampWide(wide) { - return Math.min(Math.max(wide, 1), 3); -} - -export function responsiveColumnSpan(wide) { - const span = clampWide(wide); - return { - xs: '1 / -1', - sm: span >= 2 ? 'span 2' : 'span 1', - md: `span ${span}`, - }; -} - -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 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, - color, - theme, -}) { - const elevationShadow = theme?.shadows?.[4]; - const ringColor = pending - ? PENDING_EDIT_BORDER - : editing - ? EDITING_TILE_BORDER - : null; - - return { - gridColumn: responsiveColumnSpan(wide), - position: 'relative', - p: 2, - minHeight, - display: 'flex', - flexDirection: 'column', - border: 'none', - borderRadius: PANEL_TILE_BORDER_RADIUS, - ...(color ? { bgcolor: color } : {}), - ...(ringColor - ? { - boxShadow: tileRingBoxShadow(ringColor, elevationShadow), - } - : {}), - ...sx, - }; -} - -export { GRID_COLUMNS }; diff --git a/src/components/tiles/ActionTile.jsx b/src/components/tiles/ActionTile.jsx index e2bd20d..549908e 100644 --- a/src/components/tiles/ActionTile.jsx +++ b/src/components/tiles/ActionTile.jsx @@ -1,6 +1,6 @@ import AddIcon from '@mui/icons-material/Add'; import Typography from '@mui/material/Typography'; -import { Tile } from './Tile'; +import { Tile } from 'dashgui/tiles'; export class ActionTile extends Tile { renderIcon() { diff --git a/src/components/tiles/ArticleTile.jsx b/src/components/tiles/ArticleTile.jsx index 1e08c69..094145f 100644 --- a/src/components/tiles/ArticleTile.jsx +++ b/src/components/tiles/ArticleTile.jsx @@ -2,7 +2,7 @@ import Inventory2OutlinedIcon from '@mui/icons-material/Inventory2Outlined'; import Box from '@mui/material/Box'; import ButtonBase from '@mui/material/ButtonBase'; import Typography from '@mui/material/Typography'; -import { Tile, tileBaseSx } from './Tile'; +import { Tile, tileBaseSx } from 'dashgui/tiles'; import { formatPrice } from '../../utils/price'; import { articleSku } from '../../pages/artikel/articleRoute'; import ArticlePhoto from '../article/ArticlePhoto'; diff --git a/src/components/tiles/BackTile.jsx b/src/components/tiles/BackTile.jsx index 9689973..b31a2c0 100644 --- a/src/components/tiles/BackTile.jsx +++ b/src/components/tiles/BackTile.jsx @@ -1,7 +1,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack'; import Box from '@mui/material/Box'; import Typography from '@mui/material/Typography'; -import { Tile, tileBaseSx } from './Tile'; +import { Tile, tileBaseSx } from 'dashgui/tiles'; /** * BackTile renders as a
(Box) instead of