This commit is contained in:
seb
2026-08-09 01:22:49 +02:00
parent 4f0d73e0ea
commit f0e7a523da
22 changed files with 516 additions and 3 deletions

View File

@@ -0,0 +1,300 @@
import { Component } from 'react';
import Alert from '@mui/material/Alert';
import Box from '@mui/material/Box';
import Chip from '@mui/material/Chip';
import Typography from '@mui/material/Typography';
import AddIcon from '@mui/icons-material/Add';
import FolderIcon from '@mui/icons-material/FolderOutlined';
import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined';
import ToggleOnIcon from '@mui/icons-material/ToggleOn';
import { EditablePanelTile, PanelTile, PanelTileGrid } from '../../panels';
import RecordList from '../../lists/RecordList';
import { Tile, tileBaseSx } from '../../tiles';
import ElevatedPill, {
ActionElevatedPill,
ToggleElevatedPill,
} from '../../ElevatedPill';
import SingleLineTextEdit from '../../SingleLineTextEdit';
const MOCK_ROWS = [
{
id: 1,
number: 'L-1001',
date: '2026-08-01',
party: 'Müller GmbH',
status: 'fertig',
},
{
id: 2,
number: 'L-1002',
date: '2026-08-03',
party: 'Schmidt & Co',
status: 'offen',
},
{
id: 3,
number: 'L-1003',
date: '2026-08-05',
party: 'Weber OHG',
status: 'offen',
},
];
const COLUMNS = [
{ key: 'number', label: 'Nummer' },
{ key: 'date', label: 'Lieferdatum' },
{ key: 'party', label: 'Lieferant' },
{
key: 'status',
label: 'Status',
render: (row) => (
<Chip
size="small"
label={row.status}
color={row.status === 'fertig' ? 'success' : 'default'}
/>
),
},
];
class MockFolderTile extends Tile {
renderIcon() {
return <FolderIcon color="action" sx={{ fontSize: 40 }} />;
}
}
class MockAddTile extends Tile {
renderIcon() {
return <AddIcon color="primary" sx={{ fontSize: 40 }} />;
}
}
/**
* Fixed, deterministic gallery of every GUI variation — one `data-shot`
* section per reference screenshot. Served under /screenshots.html and
* captured by the Playwright screenshot tests (tests/screenshot.spec.js).
*
* Deliberately static: no timers, no routing, no user state. Only stable
* props, so references stay comparable across runs. Interactive states
* (EditablePanelTile editing/dirty) are driven from the test, not here.
*/
function Section({ id, label, children }) {
return (
<Box
component="section"
data-shot={id}
sx={{ background: '#f4f4f5', p: 2, borderRadius: 1, mb: 3 }}
>
<Typography
variant="overline"
color="text.secondary"
sx={{ display: 'block', mb: 1.5 }}
>
{label}
</Typography>
{children}
</Box>
);
}
class ScreenshotHarness extends Component {
render() {
return (
<Box sx={{ width: 1000, mx: 'auto', py: 3, px: 1 }}>
<Section id="pills" label="ElevatedPill / Toggle / Action">
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<ElevatedPill label="Statisch" />
<ElevatedPill label="Aktiv" active />
</Box>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<ToggleElevatedPill label="Aus" checked={false} onToggle={() => {}} />
<ToggleElevatedPill label="An" checked onToggle={() => {}} />
</Box>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<ActionElevatedPill
label="Inaktiv"
icon={<ToggleOnIcon sx={{ fontSize: 18 }} />}
onClick={() => {}}
/>
<ActionElevatedPill
label="Aktiv"
active
icon={<ToggleOnIcon sx={{ fontSize: 18 }} />}
onClick={() => {}}
/>
</Box>
</Box>
</Section>
<Section id="textfields" label="SingleLineTextEdit">
<Box
sx={{
display: 'flex',
flexDirection: 'column',
gap: 2,
maxWidth: 480,
}}
>
<SingleLineTextEdit label="Name" value="ACME GmbH" onChange={() => {}} />
<SingleLineTextEdit
label="Name"
value=""
onChange={() => {}}
helperText="Mit Hilfetext"
/>
<SingleLineTextEdit
label="Nur lesen"
value="Schreibgeschützt"
readOnly
onChange={() => {}}
/>
<SingleLineTextEdit
label="Deaktiviert"
value="Ausgegraut"
disabled
onChange={() => {}}
/>
<SingleLineTextEdit
label="Klein"
value="Small"
size="small"
onChange={() => {}}
/>
</Box>
</Section>
<Section id="tiles" label="Tile / tileBaseSx">
<Box
sx={{
display: 'grid',
gridTemplateColumns: 'repeat(4, 140px)',
gap: 2,
}}
>
<MockFolderTile label="Ordner" secondary="3 Artikel" />
<MockAddTile label="Neu" secondary="anlegen" />
<MockFolderTile label="Deaktiviert" secondary="nicht klickbar" disabled />
<Box sx={{ ...tileBaseSx, bgcolor: 'primary.main', color: 'white' }}>
<Typography variant="body2" fontWeight={600}>
tileBaseSx direkt
</Typography>
</Box>
</Box>
</Section>
<Section id="panels-basic" label="PanelTile">
<PanelTileGrid>
<PanelTile wide={2} title="Firma">
<Typography variant="body1">ACME GmbH</Typography>
</PanelTile>
<PanelTile wide={1} title="Farbe" color="#fef3c7">
<Typography variant="body2">Mit Hintergrundfarbe</Typography>
</PanelTile>
<PanelTile
wide={1}
title="Mit Aktion"
action={{ label: 'Speichern', icon: SaveOutlinedIcon, onClick: () => {} }}
>
<Typography variant="body2">Idle Aktion</Typography>
</PanelTile>
</PanelTileGrid>
</Section>
<Section id="panels-busy" label="PanelTile – Aktion busy">
<PanelTileGrid>
<PanelTile
wide={2}
title="Speichern läuft"
action={{
label: 'Speichern',
icon: SaveOutlinedIcon,
busy: true,
onClick: () => {},
}}
>
<Typography variant="body1">Wird gespeichert …</Typography>
</PanelTile>
</PanelTileGrid>
</Section>
<Section id="editable" label="EditablePanelTile">
<PanelTileGrid>
<EditablePanelTile
wide={2}
title="Firma"
value="ACME GmbH"
onSave={async () => {}}
onError={() => {}}
display={<Typography variant="body1">ACME GmbH</Typography>}
edit={({ draft, onChange, inputRef }) => (
<SingleLineTextEdit
label="Name"
value={draft}
onChange={onChange}
inputRef={inputRef}
/>
)}
/>
</PanelTileGrid>
</Section>
<Section id="lists-ready" label="RecordList – ready">
<RecordList
status="ready"
rows={MOCK_ROWS}
columns={COLUMNS}
createLabel="Neue Lieferung"
onCreate={() => {}}
onEdit={() => {}}
onDelete={() => {}}
emptyMessage="Noch keine Lieferungen."
color="#fed7aa"
/>
</Section>
<Section id="lists-empty" label="RecordList – leer">
<RecordList
status="ready"
rows={[]}
columns={COLUMNS}
createLabel="Neue Lieferung"
onCreate={() => {}}
onEdit={() => {}}
onDelete={() => {}}
emptyMessage="Noch keine Lieferungen."
color="#fed7aa"
/>
</Section>
<Section id="lists-loading" label="RecordList – laden">
<RecordList status="loading" rows={[]} columns={COLUMNS} />
</Section>
<Section id="lists-failed" label="RecordList – Fehler">
<RecordList
status="failed"
rows={[]}
columns={COLUMNS}
error={
<Alert severity="error">
Backend-Fehler: Verbindung fehlgeschlagen.
</Alert>
}
/>
</Section>
<Section id="lists-no-actions" label="RecordList – ohne Aktionen">
<RecordList
status="ready"
rows={MOCK_ROWS}
columns={COLUMNS}
color="#e2e8f0"
/>
</Section>
</Box>
);
}
}
export { ScreenshotHarness };

View File

@@ -0,0 +1,17 @@
import { createRoot } from 'react-dom/client';
import CssBaseline from '@mui/material/CssBaseline';
import { ThemeProvider, createTheme } from '@mui/material/styles';
import { ScreenshotHarness } from './screenshots/ScreenshotHarness';
// Standalone root for the screenshot harness (/screenshots.html). Mirrors the
// dev gallery's theme so reference screenshots match real-app rendering.
const theme = createTheme({
shape: { borderRadius: 4 },
});
createRoot(document.getElementById('root')).render(
<ThemeProvider theme={theme}>
<CssBaseline />
<ScreenshotHarness />
</ThemeProvider>,
);