feat(design-system): F003 – complete design system foundation
Design tokens: - lib/ds.ts: DS_COLORS (resultType/confidence/risk/availability/freshness/dataQuality), scoreToConfidenceLevel(), scoreToDataQualityLevel() - lib/constants.ts: added CONFIDENCE_LABELS, DATA_QUALITY_LABELS Badge system (src/components/badges/): - ResultTypeBadge, ConfidenceBadge, RiskBadge, AvailabilityBadge, FreshnessBadge, DataQualityBadge - All use DS_COLORS, MUI Chip size=small, lucide icons, German labels from constants Score components (src/components/scores/): - ConfidenceScore: numeric % with color from utils.confidenceHex - DataQualityScore: LinearProgress + qualitative label - ScoreBar: horizontal bar with label/value/weight - ScoreBreakdownMini: ScoreFactor[] mapped to ScoreBars with Tooltip explanations Card system (src/components/cards/): - DecisionCard: header+badges+score+body+actions, selected/loading/restricted states - CompactCard: single-row 48px card with leading/trailing slots - MetricCard: KPI card with label/value/delta/icon (extracted pattern from SupplyDashboard) Panel system (src/components/panels/): - InfoPanel: outlined Paper with icon+title header - DetailPanel: Accordion sections in bordered Paper - WarningPanel: MUI Alert with warning/critical severity Table components (src/components/tables/): - DataTableShell: Paper+TableContainer+Table with loading/empty/toolbar slots - TableToolbar: flex row with title/count/filters/actions - FilterBar: deletable Chips with clear-all - EmptyTableState: centered empty row spanning all cols - LoadingRows: Skeleton cell rows Form components (src/components/forms/): - TextInput: MUI TextField wrapper with onChange(string) signature - SelectField: generic SelectField<T extends string> with MUI Select - PriorityChipGroup: togglable Chip set (single/multi select) - FieldWithConfidence: label+value+ConfidenceBadge for AI-parsed fields UI additions (src/components/ui/): - CardSkeleton, PanelLoadingState, UnauthorizedState, RestrictedState Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
import { Paper, Table, TableBody, TableContainer } from '@mui/material'
|
||||
import type { SxProps, Theme } from '@mui/material'
|
||||
import type { ReactNode } from 'react'
|
||||
import { LoadingRows } from './LoadingRows'
|
||||
import { EmptyTableState } from './EmptyTableState'
|
||||
|
||||
interface DataTableShellProps {
|
||||
toolbar?: ReactNode
|
||||
children: ReactNode
|
||||
loading?: boolean
|
||||
loadingRows?: number
|
||||
loadingCols?: number
|
||||
empty?: boolean
|
||||
emptySlot?: ReactNode
|
||||
emptyColSpan?: number
|
||||
stickyHeader?: boolean
|
||||
size?: 'small' | 'medium'
|
||||
sx?: SxProps<Theme>
|
||||
}
|
||||
|
||||
export function DataTableShell({
|
||||
toolbar,
|
||||
children,
|
||||
loading = false,
|
||||
loadingRows = 5,
|
||||
loadingCols = 6,
|
||||
empty = false,
|
||||
emptySlot,
|
||||
emptyColSpan = 6,
|
||||
stickyHeader = false,
|
||||
size = 'small',
|
||||
sx,
|
||||
}: DataTableShellProps) {
|
||||
return (
|
||||
<Paper variant="outlined" sx={{ borderRadius: 1.5, overflow: 'hidden', ...sx }}>
|
||||
{toolbar}
|
||||
<TableContainer>
|
||||
<Table size={size} stickyHeader={stickyHeader}>
|
||||
{!loading && children}
|
||||
{loading && (
|
||||
<TableBody>
|
||||
<LoadingRows rows={loadingRows} cols={loadingCols} />
|
||||
</TableBody>
|
||||
)}
|
||||
{!loading && empty && (
|
||||
<TableBody>
|
||||
{emptySlot ?? <EmptyTableState colSpan={emptyColSpan} />}
|
||||
</TableBody>
|
||||
)}
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Paper>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Box, TableCell, TableRow, Typography } from '@mui/material'
|
||||
import { Inbox } from 'lucide-react'
|
||||
|
||||
interface EmptyTableStateProps {
|
||||
colSpan: number
|
||||
title?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export function EmptyTableState({ colSpan, title, description }: EmptyTableStateProps) {
|
||||
return (
|
||||
<TableRow>
|
||||
<TableCell colSpan={colSpan} sx={{ border: 'none' }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 1,
|
||||
py: 5,
|
||||
color: 'text.disabled',
|
||||
}}
|
||||
>
|
||||
<Inbox size={32} color="#cbd5e1" />
|
||||
<Typography sx={{ fontWeight: 500, fontSize: '0.875rem', color: 'text.secondary' }}>
|
||||
{title ?? 'Keine Einträge gefunden'}
|
||||
</Typography>
|
||||
{description && (
|
||||
<Typography sx={{ fontSize: '0.8125rem', color: 'text.disabled', textAlign: 'center', maxWidth: 360 }}>
|
||||
{description}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Box, Chip, Typography } from '@mui/material'
|
||||
import type { SxProps, Theme } from '@mui/material'
|
||||
|
||||
interface ActiveFilter {
|
||||
key: string
|
||||
label: string
|
||||
onRemove: () => void
|
||||
}
|
||||
|
||||
interface FilterBarProps {
|
||||
filters: ActiveFilter[]
|
||||
onClearAll?: () => void
|
||||
sx?: SxProps<Theme>
|
||||
}
|
||||
|
||||
export function FilterBar({ filters, onClearAll, sx }: FilterBarProps) {
|
||||
if (filters.length === 0) return null
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexWrap: 'wrap', alignItems: 'center', gap: 0.5, ...sx }}>
|
||||
{filters.map(f => (
|
||||
<Chip
|
||||
key={f.key}
|
||||
label={f.label}
|
||||
size="small"
|
||||
onDelete={f.onRemove}
|
||||
sx={{ fontSize: '0.75rem', height: 24 }}
|
||||
/>
|
||||
))}
|
||||
{onClearAll && filters.length > 1 && (
|
||||
<Typography
|
||||
component="button"
|
||||
onClick={onClearAll}
|
||||
sx={{
|
||||
fontSize: '0.75rem',
|
||||
color: 'text.secondary',
|
||||
cursor: 'pointer',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
p: 0,
|
||||
ml: 0.5,
|
||||
'&:hover': { color: 'primary.main', textDecoration: 'underline' },
|
||||
}}
|
||||
>
|
||||
Alle löschen
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { Skeleton, TableCell, TableRow } from '@mui/material'
|
||||
|
||||
interface LoadingRowsProps {
|
||||
rows?: number
|
||||
cols?: number
|
||||
}
|
||||
|
||||
export function LoadingRows({ rows = 5, cols = 6 }: LoadingRowsProps) {
|
||||
return (
|
||||
<>
|
||||
{Array.from({ length: rows }).map((_, ri) => (
|
||||
<TableRow key={ri}>
|
||||
{Array.from({ length: cols }).map((_, ci) => (
|
||||
<TableCell key={ci}>
|
||||
<Skeleton variant="text" height={18} width={ci === 0 ? '80%' : ci === cols - 1 ? '40%' : '65%'} />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
import { Box, Chip, Typography } from '@mui/material'
|
||||
import type { SxProps, Theme } from '@mui/material'
|
||||
import type { ReactNode } from 'react'
|
||||
|
||||
interface TableToolbarProps {
|
||||
title?: string
|
||||
count?: number
|
||||
filterSlot?: ReactNode
|
||||
actions?: ReactNode
|
||||
sx?: SxProps<Theme>
|
||||
}
|
||||
|
||||
export function TableToolbar({ title, count, filterSlot, actions, sx }: TableToolbarProps) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
px: 2,
|
||||
py: 1.25,
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
gap: 1,
|
||||
flexWrap: 'wrap',
|
||||
...sx,
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1, flexWrap: 'wrap', flex: 1 }}>
|
||||
{title && (
|
||||
<Typography sx={{ fontWeight: 600, fontSize: '0.875rem' }}>
|
||||
{title}
|
||||
</Typography>
|
||||
)}
|
||||
{count !== undefined && (
|
||||
<Chip
|
||||
label={count}
|
||||
size="small"
|
||||
sx={{ height: 20, fontSize: '0.7rem', bgcolor: 'rgba(30,58,95,0.08)', color: '#1e3a5f' }}
|
||||
/>
|
||||
)}
|
||||
{filterSlot}
|
||||
</Box>
|
||||
{actions && (
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, flexShrink: 0 }}>
|
||||
{actions}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
export { DataTableShell } from './DataTableShell'
|
||||
export { TableToolbar } from './TableToolbar'
|
||||
export { FilterBar } from './FilterBar'
|
||||
export { EmptyTableState } from './EmptyTableState'
|
||||
export { LoadingRows } from './LoadingRows'
|
||||
Reference in New Issue
Block a user