@12-apps/ui 1.23.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/package.json +3 -1
  2. package/src/components/data-display/Alert/Alert.types.ts +2 -1
  3. package/src/components/data-display/Avatar/Avatar.types.ts +10 -2
  4. package/src/components/data-display/Badge/Badge.types.ts +3 -2
  5. package/src/components/data-display/Carousel/Carousel.parts.tsx +12 -10
  6. package/src/components/data-display/Carousel/Carousel.styles.ts +4 -3
  7. package/src/components/data-display/Carousel/Carousel.tsx +2 -1
  8. package/src/components/data-display/Carousel/Carousel.types.ts +7 -6
  9. package/src/components/data-display/Chart/Chart.tsx +2 -1
  10. package/src/components/data-display/Chart/Chart.types.ts +3 -2
  11. package/src/components/data-display/Chart/ChartSpec.types.ts +6 -7
  12. package/src/components/data-display/Chart/SpecChart.tsx +2 -1
  13. package/src/components/data-display/Chart/chart-internals.ts +3 -2
  14. package/src/components/data-display/Chart/chart-spec.ts +11 -1
  15. package/src/components/data-display/Chip/Chip.tsx +23 -3
  16. package/src/components/data-display/Chip/Chip.types.ts +37 -3
  17. package/src/components/data-display/Chip/chip.md +7 -3
  18. package/src/components/data-display/Chip/index.ts +1 -1
  19. package/src/components/data-display/DataViews/DataViewsGrid.tsx +10 -0
  20. package/src/components/data-display/DataViews/DataViewsTableBase.tsx +3 -0
  21. package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +113 -62
  22. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +4 -0
  23. package/src/components/data-display/DataViews/data-views-overflow.ts +23 -51
  24. package/src/components/data-display/DataViews/index.ts +4 -0
  25. package/src/components/data-display/DataViews/list-card-rails.tsx +29 -0
  26. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +8 -5
  27. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.types.ts +2 -1
  28. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.types.ts +2 -1
  29. package/src/components/data-display/LoadingState/LoadingState.types.ts +2 -1
  30. package/src/components/data-display/Progress/Progress.types.ts +3 -2
  31. package/src/components/data-display/Sheet/Sheet.body.tsx +2 -1
  32. package/src/components/data-display/Sheet/Sheet.styles.ts +3 -2
  33. package/src/components/data-display/Sheet/Sheet.types.ts +7 -2
  34. package/src/components/data-display/Stepper/Stepper.types.ts +2 -1
  35. package/src/components/data-display/Table/Table.styles.ts +1 -1
  36. package/src/components/data-display/Table/Table.types.ts +3 -1
  37. package/src/components/data-display/Tooltip/Tooltip.types.ts +2 -1
  38. package/src/components/feedback/Dialog/Dialog.types.ts +2 -1
  39. package/src/components/feedback/Modal/Modal.types.ts +2 -1
  40. package/src/components/form/Button/Button.types.ts +3 -2
  41. package/src/components/form/Calendar/CalendarMonth.tsx +6 -1
  42. package/src/components/form/Command/Command.styles.ts +8 -7
  43. package/src/components/form/Command/Command.types.ts +3 -2
  44. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +2 -1
  45. package/src/components/form/Input/Input.types.ts +2 -1
  46. package/src/components/form/InputOTP/InputOTP.types.ts +3 -2
  47. package/src/components/form/Label/Label.styles.ts +8 -5
  48. package/src/components/form/Label/Label.tsx +1 -1
  49. package/src/components/form/Label/Label.types.ts +3 -2
  50. package/src/components/form/Menubar/Menubar.styles.ts +10 -8
  51. package/src/components/form/Menubar/Menubar.tsx +2 -1
  52. package/src/components/form/Menubar/Menubar.types.ts +7 -6
  53. package/src/components/form/RadioGroup/RadioGroup.types.ts +3 -2
  54. package/src/components/form/Select/Select.tsx +13 -1
  55. package/src/components/form/Select/Select.types.ts +5 -2
  56. package/src/components/form/Slider/Slider.types.ts +3 -2
  57. package/src/components/form/Switch/Switch.types.ts +3 -2
  58. package/src/components/form/Textarea/Textarea.types.ts +3 -2
  59. package/src/components/form/Toggle/Toggle.types.ts +3 -2
  60. package/src/components/form/ToggleGroup/ToggleGroup.types.ts +3 -2
  61. package/src/components/layout/Separator/Separator.types.ts +2 -1
  62. package/src/components/layout/Spacer/Spacer.types.ts +2 -1
  63. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +1 -1
  64. package/src/components/navigation/Breadcrumbs/Breadcrumbs.types.ts +7 -2
  65. package/src/components/navigation/ContextMenu/ContextMenu.types.ts +3 -2
  66. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +1 -0
  67. package/src/components/navigation/DropdownMenu/DropdownMenu.types.ts +12 -2
  68. package/src/components/navigation/NavigationMenu/NavigationMenu.types.ts +7 -2
  69. package/src/components/navigation/Pagination/Pagination.tsx +3 -1
  70. package/src/components/navigation/Pagination/Pagination.types.ts +5 -3
  71. package/src/components/navigation/Tabs/Tabs.types.ts +3 -2
  72. package/src/components/typography/Blockquote/Blockquote.types.ts +2 -1
  73. package/src/components/typography/Code/Code.types.ts +2 -1
  74. package/src/components/typography/Heading/Heading.types.ts +2 -1
  75. package/src/components/typography/Paragraph/Paragraph.types.ts +3 -2
  76. package/src/components/typography/Text/Text.types.ts +3 -2
  77. package/src/components/utility/AnimatedIcon/AnimatedIcon.types.ts +2 -1
  78. package/src/components/utility/Overflow/index.ts +6 -0
  79. package/src/components/utility/Overflow/overflow.ts +125 -0
  80. package/src/components/utility/WorkflowStep/WorkflowStep.types.ts +3 -2
  81. package/src/components/utility/index.ts +1 -0
  82. package/src/tokens/scales.ts +153 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@12-apps/ui",
3
- "version": "1.23.0",
3
+ "version": "2.1.0",
4
4
  "license": "MIT",
5
5
  "packageManager": "pnpm@9.0.0",
6
6
  "type": "module",
@@ -20,6 +20,7 @@
20
20
  "./social-login-button": "./src/social-login-button/index.ts",
21
21
  "./user-avatar": "./src/user-avatar/index.ts",
22
22
  "./utils": "./src/utils/index.ts",
23
+ "./tokens": "./src/tokens/scales.ts",
23
24
  "./mui/Box": "./src/mui/Box.ts",
24
25
  "./mui/Link": "./src/mui/Link.ts",
25
26
  "./mui/Stack": "./src/mui/Stack.ts",
@@ -134,6 +135,7 @@
134
135
  "./utility/AspectRatio": "./src/components/utility/AspectRatio/index.ts",
135
136
  "./utility/InfiniteScroll": "./src/components/utility/InfiniteScroll/index.ts",
136
137
  "./utility/InstallPrompt": "./src/components/utility/InstallPrompt/index.ts",
138
+ "./utility/Overflow": "./src/components/utility/Overflow/index.ts",
137
139
  "./utility/Portal": "./src/components/utility/Portal/index.ts",
138
140
  "./utility/Transition": "./src/components/utility/Transition/index.ts",
139
141
  "./utility/VirtualList": "./src/components/utility/VirtualList/index.ts",
@@ -1,3 +1,4 @@
1
+ import type { ColorValue } from '../../../tokens/scales';
1
2
  import type { AlertProps as MuiAlertProps } from '@mui/material';
2
3
  import type React from 'react';
3
4
 
@@ -10,7 +11,7 @@ export interface AlertProps extends Omit<MuiAlertProps, 'variant' | 'color' | 'r
10
11
  /**
11
12
  * The color of the alert (when not using variant-specific colors)
12
13
  */
13
- color?: 'primary' | 'secondary' | 'success' | 'warning' | 'danger' | 'neutral';
14
+ color?: Exclude<ColorValue, 'info'>;
14
15
 
15
16
  /**
16
17
  * Whether the alert should have a glow effect
@@ -1,7 +1,15 @@
1
+ import type { ColorValue, SizeValue } from '../../../tokens/scales';
1
2
  import type { AvatarProps as MuiAvatarProps } from '@mui/material';
2
3
  import type React from 'react';
3
4
 
4
- export type AvatarSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl';
5
+ /**
6
+ * The house scale, EXTENDED by one stop rather than replacing it.
7
+ *
8
+ * `xxl` is real — an avatar is the one component with a portrait size above the
9
+ * scale's top — so it is added to `SizeValue` instead of the union being
10
+ * rewritten by hand. Spelling all six out again is how the copy drifts.
11
+ */
12
+ export type AvatarSize = SizeValue | 'xxl';
5
13
  export type AvatarVariant = 'circle' | 'square' | 'rounded' | 'status';
6
14
  export type AvatarStatus = 'online' | 'offline' | 'away' | 'busy';
7
15
 
@@ -49,7 +57,7 @@ export interface AvatarProps extends Omit<MuiAvatarProps, 'variant'> {
49
57
  /**
50
58
  * Color of the avatar background when no image is provided
51
59
  */
52
- color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'neutral';
60
+ color?: Exclude<ColorValue, 'info'>;
53
61
 
54
62
  /**
55
63
  * Whether the avatar is in a loading state
@@ -1,3 +1,4 @@
1
+ import type { ColorValue, SizeValue } from '../../../tokens/scales';
1
2
  import type { BadgeProps as MuiBadgeProps } from '@mui/material';
2
3
  import type React from 'react';
3
4
 
@@ -13,7 +14,7 @@ export type BadgeVariant =
13
14
  | 'success'
14
15
  | 'warning';
15
16
 
16
- export type BadgeSize = 'xs' | 'sm' | 'md' | 'lg';
17
+ export type BadgeSize = Extract<SizeValue, 'xs' | 'sm' | 'md' | 'lg'>;
17
18
 
18
19
  export interface BadgeProps extends Omit<MuiBadgeProps, 'variant' | 'color' | 'content'> {
19
20
  /**
@@ -29,7 +30,7 @@ export interface BadgeProps extends Omit<MuiBadgeProps, 'variant' | 'color' | 'c
29
30
  /**
30
31
  * The color of the badge
31
32
  */
32
- color?: 'primary' | 'secondary' | 'success' | 'warning' | 'error' | 'neutral' | 'info';
33
+ color?: ColorValue;
33
34
 
34
35
  /**
35
36
  * Whether the badge should have a glow effect
@@ -7,6 +7,8 @@ import React from 'react';
7
7
 
8
8
  import { withDefaults } from '../../../utils/withDefaults';
9
9
 
10
+ import { accentFor, muiSize } from '../../../tokens/scales';
11
+
10
12
  import type {
11
13
  CarouselArrowsProps,
12
14
  CarouselIndicatorsProps,
@@ -69,8 +71,8 @@ const LineIndicator: React.FC<IndicatorProps> = ({ index, isActive, color, onSel
69
71
  width: isActive ? 30 : 20,
70
72
  height: 3,
71
73
  backgroundColor: isActive
72
- ? theme.palette[color].main
73
- : alpha(theme.palette[color].main, 0.3),
74
+ ? accentFor(theme, color).main
75
+ : alpha(accentFor(theme, color).main, 0.3),
74
76
  borderRadius: 1.5,
75
77
  transition: theme.transitions.create(['width', 'background-color']),
76
78
  cursor: 'pointer',
@@ -96,9 +98,9 @@ const NumberIndicator: React.FC<IndicatorProps> = ({ index, isActive, color, onS
96
98
  alignItems: 'center',
97
99
  justifyContent: 'center',
98
100
  borderRadius: '50%',
99
- backgroundColor: isActive ? theme.palette[color].main : 'transparent',
100
- color: isActive ? theme.palette[color].contrastText : theme.palette[color].main,
101
- border: `1px solid ${theme.palette[color].main}`,
101
+ backgroundColor: isActive ? accentFor(theme, color).main : 'transparent',
102
+ color: isActive ? accentFor(theme, color).contrastText : accentFor(theme, color).main,
103
+ border: `1px solid ${accentFor(theme, color).main}`,
102
104
  fontSize: '0.75rem',
103
105
  fontWeight: isActive ? 'bold' : 'normal',
104
106
  cursor: 'pointer',
@@ -122,7 +124,7 @@ const DotIndicator: React.FC<IndicatorProps> = ({ index, isActive, color, onSele
122
124
  onClick={onSelect}
123
125
  sx={{
124
126
  p: 0.5,
125
- color: isActive ? theme.palette[color].main : alpha(theme.palette[color].main, 0.3),
127
+ color: isActive ? accentFor(theme, color).main : alpha(accentFor(theme, color).main, 0.3),
126
128
  transform: isActive ? 'scale(1.2)' : 'scale(1)',
127
129
  transition: theme.transitions.create(['transform', 'color']),
128
130
  }}
@@ -185,7 +187,7 @@ const ARROWS_DEFAULTS = {
185
187
  disablePrev: false,
186
188
  disableNext: false,
187
189
  color: 'primary',
188
- size: 'medium',
190
+ size: 'md',
189
191
  } satisfies Partial<CarouselArrowsProps>;
190
192
 
191
193
  /**
@@ -198,7 +200,7 @@ const arrowSx = (theme: Theme, color: Color, isOverlay: boolean): CSSObject => (
198
200
  transform: 'translateY(-50%)',
199
201
  zIndex: 3,
200
202
  backgroundColor: alpha(theme.palette.background.paper, 0.8),
201
- color: theme.palette[color].main,
203
+ color: accentFor(theme, color).main,
202
204
  '&:disabled': { opacity: 0.3 },
203
205
  ...(isOverlay
204
206
  ? {
@@ -223,7 +225,7 @@ export const CarouselArrows: React.FC<CarouselArrowsProps> = (props) => {
223
225
  <IconButton
224
226
  onClick={onPrev}
225
227
  disabled={disabled || disablePrev}
226
- size={size}
228
+ size={muiSize(size)}
227
229
  className={className}
228
230
  aria-label="Previous slide"
229
231
  data-testid="carousel-prev-button"
@@ -236,7 +238,7 @@ export const CarouselArrows: React.FC<CarouselArrowsProps> = (props) => {
236
238
  onClick={onNext}
237
239
  disabled={disabled || disableNext}
238
240
  aria-label="Next slide"
239
- size={size}
241
+ size={muiSize(size)}
240
242
  className={className}
241
243
  data-testid="carousel-next-button"
242
244
  sx={{ ...sx, right: offset, ...style }}
@@ -2,6 +2,7 @@ import { alpha } from '@mui/material';
2
2
  import type { CSSObject, Theme } from '@mui/material';
3
3
 
4
4
  import type { CarouselProps } from './Carousel.types';
5
+ import { accentFor } from '../../../tokens/scales';
5
6
 
6
7
  type Variant = NonNullable<CarouselProps['variant']>;
7
8
  type Size = NonNullable<CarouselProps['size']>;
@@ -39,12 +40,12 @@ const SURFACES: Record<Variant, (input: ContainerStyleInput) => CSSObject> = {
39
40
  glass: ({ theme, color, glass }) => ({
40
41
  backgroundColor: alpha(theme.palette.background.paper, glass ? 0.1 : 0.9),
41
42
  backdropFilter: 'blur(20px)',
42
- border: `1px solid ${alpha(theme.palette[color].main, 0.2)}`,
43
+ border: `1px solid ${alpha(accentFor(theme, color).main, 0.2)}`,
43
44
  }),
44
45
 
45
46
  gradient: ({ theme, color, gradient }) => ({
46
47
  background: gradient
47
- ? `linear-gradient(135deg, ${alpha(theme.palette[color].light, 0.1)}, ${alpha(theme.palette[color].dark, 0.1)})`
48
+ ? `linear-gradient(135deg, ${alpha(accentFor(theme, color).light, 0.1)}, ${alpha(accentFor(theme, color).dark, 0.1)})`
48
49
  : 'transparent',
49
50
  }),
50
51
 
@@ -59,7 +60,7 @@ const SURFACES: Record<Variant, (input: ContainerStyleInput) => CSSObject> = {
59
60
 
60
61
  export const containerStyles = (input: ContainerStyleInput): CSSObject => {
61
62
  const { theme, variant, size, color, height, width, glow, pulse } = input;
62
- const accent = theme.palette[color].main;
63
+ const accent = accentFor(theme, color).main;
63
64
 
64
65
  return {
65
66
  position: 'relative' as const,
@@ -8,6 +8,7 @@ import { CarouselChrome } from './Carousel.chrome';
8
8
  import { CarouselTrack } from './Carousel.slide';
9
9
  import { containerStyles } from './Carousel.styles';
10
10
  import type { CarouselItem, CarouselProps } from './Carousel.types';
11
+ import { muiColor } from '../../../tokens/scales';
11
12
 
12
13
  export { CarouselArrows, CarouselIndicators, CarouselThumbnails } from './Carousel.parts';
13
14
 
@@ -80,7 +81,7 @@ export const Carousel: React.FC<CarouselProps> = (props) => {
80
81
  if (loading) {
81
82
  return (
82
83
  <Box sx={{ ...frameSx, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
83
- <CircularProgress color={color} />
84
+ <CircularProgress color={muiColor(color, 'inherit')} />
84
85
  </Box>
85
86
  );
86
87
  }
@@ -1,3 +1,4 @@
1
+ import type { ColorValue, SizeValue } from '../../../tokens/scales';
1
2
  import type { CSSProperties, ReactNode } from 'react';
2
3
 
3
4
  export interface CarouselItem {
@@ -12,8 +13,8 @@ export interface CarouselItem {
12
13
  export interface CarouselProps {
13
14
  items: CarouselItem[];
14
15
  variant?: 'default' | 'glass' | 'gradient' | 'elevated' | 'minimal' | 'cards';
15
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
16
- color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
16
+ size?: SizeValue;
17
+ color?: Exclude<ColorValue, 'neutral'>;
17
18
  autoPlay?: boolean;
18
19
  autoPlayInterval?: number;
19
20
  loop?: boolean;
@@ -50,7 +51,7 @@ export interface CarouselIndicatorsProps {
50
51
  activeIndex: number;
51
52
  onSelect: (index: number) => void;
52
53
  position?: 'top' | 'bottom' | 'left' | 'right';
53
- color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
54
+ color?: Exclude<ColorValue, 'neutral'>;
54
55
  variant?: 'dots' | 'lines' | 'numbers';
55
56
  className?: string;
56
57
  style?: CSSProperties;
@@ -63,8 +64,8 @@ export interface CarouselArrowsProps {
63
64
  disabled?: boolean;
64
65
  disablePrev?: boolean;
65
66
  disableNext?: boolean;
66
- color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
67
- size?: 'small' | 'medium' | 'large';
67
+ color?: Exclude<ColorValue, 'neutral'>;
68
+ size?: Extract<SizeValue, 'sm' | 'md' | 'lg'>;
68
69
  className?: string;
69
70
  style?: CSSProperties;
70
71
  }
@@ -73,7 +74,7 @@ export interface CarouselThumbnailsProps {
73
74
  items: CarouselItem[];
74
75
  activeIndex: number;
75
76
  onSelect: (index: number) => void;
76
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
77
+ size?: SizeValue;
77
78
  className?: string;
78
79
  style?: CSSProperties;
79
80
  }
@@ -5,6 +5,7 @@ import type { ChartProps } from './Chart.types';
5
5
  import { ChartContainer } from './ChartComposables';
6
6
  import { getSizeStyles, getVariantStyles } from './chart-internals';
7
7
  import { renderChartByType } from './chart-renderers';
8
+ import { muiColor } from '../../../tokens/scales';
8
9
 
9
10
  /**
10
11
  * Prop-driven Recharts wrapper (line/bar/area/pie/radar/scatter/composed)
@@ -70,7 +71,7 @@ export const Chart: React.FC<ChartProps> = (props) => {
70
71
  data-testid={`${dataTestId}-loading`}
71
72
  sx={{ ...styles, ...sizeStyles, display: 'flex', justifyContent: 'center', alignItems: 'center' }}
72
73
  >
73
- <CircularProgress color={props.color ?? 'primary'} data-testid={`${dataTestId}-loading-spinner`} />
74
+ <CircularProgress color={muiColor(props.color ?? 'primary', 'inherit')} data-testid={`${dataTestId}-loading-spinner`} />
74
75
  </Paper>
75
76
  );
76
77
  }
@@ -1,3 +1,4 @@
1
+ import type { ColorValue, SizeValue } from '../../../tokens/scales';
1
2
  import type { CSSProperties } from 'react';
2
3
 
3
4
  export type ChartType = 'line' | 'bar' | 'area' | 'pie' | 'radar' | 'scatter' | 'composed';
@@ -26,8 +27,8 @@ export interface ChartProps {
26
27
  series?: ChartSeries[];
27
28
  type?: ChartType;
28
29
  variant?: 'default' | 'glass' | 'gradient' | 'elevated' | 'minimal' | 'neon';
29
- size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl';
30
- color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
30
+ size?: SizeValue;
31
+ color?: Exclude<ColorValue, 'neutral'>;
31
32
  height?: number;
32
33
  width?: number | string;
33
34
  glow?: boolean;
@@ -1,3 +1,4 @@
1
+ import type { ColorValue } from '../../../tokens/scales';
1
2
  /**
2
3
  * JSON-serializable chart specification — the declarative surface of the raw
3
4
  * chart library. A `ChartSpec` plus plain data rows fully describes a rendered
@@ -13,13 +14,11 @@ export type ChartSpecType = 'line' | 'bar' | 'area' | 'pie' | 'donut';
13
14
  * Semantic color token resolved against the MUI palette at render time.
14
15
  * Specs must not carry raw color values.
15
16
  */
16
- export type ChartSemanticColor =
17
- | 'primary'
18
- | 'secondary'
19
- | 'success'
20
- | 'warning'
21
- | 'error'
22
- | 'info';
17
+ /**
18
+ * The house vocabulary minus `neutral`: a data series is always an accent, and
19
+ * an unaccented series would be indistinguishable from the grid it sits on.
20
+ */
21
+ export type ChartSemanticColor = Exclude<ColorValue, 'neutral'>;
23
22
 
24
23
  /**
25
24
  * Number formatting applied to tooltip values and the y axis.
@@ -5,6 +5,7 @@ import { Chart } from './Chart';
5
5
  import type { ChartDataPoint } from './Chart.types';
6
6
  import type { ChartSemanticColor, ChartSpec } from './ChartSpec.types';
7
7
  import { CHART_SEMANTIC_COLORS, specToChartProps } from './chart-spec';
8
+ import { accentFor } from '../../../tokens/scales';
8
9
 
9
10
  export interface SpecChartProps {
10
11
  /** Validated spec — parse untrusted JSON with `parseChartSpec` first. */
@@ -36,7 +37,7 @@ export const SpecChart: React.FC<SpecChartProps> = ({
36
37
  const scheme: readonly ChartSemanticColor[] =
37
38
  spec.colorScheme && spec.colorScheme.length > 0 ? spec.colorScheme : CHART_SEMANTIC_COLORS;
38
39
  const resolveColor = (token: ChartSemanticColor | undefined, index: number): string =>
39
- theme.palette[token ?? scheme[index % scheme.length] ?? 'primary'].main;
40
+ accentFor(theme, token ?? scheme[index % scheme.length] ?? 'primary').main;
40
41
  return specToChartProps(spec, data, { resolveColor });
41
42
  }, [spec, data, theme]);
42
43
 
@@ -2,6 +2,7 @@ import { alpha, type Theme } from '@mui/material';
2
2
  import type { CSSProperties } from 'react';
3
3
 
4
4
  import type { ChartProps, ChartSeries } from './Chart.types';
5
+ import { accentFor} from '../../../tokens/scales';
5
6
 
6
7
  /**
7
8
  * Non-JSX internals of the prop-driven Chart: size/variant styling, palette
@@ -65,7 +66,7 @@ interface VariantOptions {
65
66
  }
66
67
 
67
68
  function effectStyles(theme: Theme, options: VariantOptions): SxStyles {
68
- const accent = theme.palette[options.color].main;
69
+ const accent = accentFor(theme, options.color).main;
69
70
  const glow = options.glow ? { boxShadow: `0 0 30px ${alpha(accent, 0.4)}` } : {};
70
71
  const pulse = options.pulse
71
72
  ? {
@@ -91,7 +92,7 @@ function variantSurface(theme: Theme, options: VariantOptions): SxStyles {
91
92
  case 'gradient':
92
93
  return {
93
94
  background: options.gradient
94
- ? `linear-gradient(135deg, ${alpha(theme.palette[options.color].light, 0.05)}, ${alpha(theme.palette[options.color].dark, 0.05)})`
95
+ ? `linear-gradient(135deg, ${alpha(accentFor(theme, options.color).light, 0.05)}, ${alpha(accentFor(theme, options.color).dark, 0.05)})`
95
96
  : theme.palette.background.paper,
96
97
  };
97
98
  case 'elevated':
@@ -7,12 +7,22 @@ import {
7
7
  type ChartSpecRenderContext,
8
8
  } from './chart-spec-registry';
9
9
 
10
+ /**
11
+ * BREAKING FOR STORED SPECS, not just for types.
12
+ *
13
+ * A chart spec is DATA — it is authored as JSON and can be persisted — and this
14
+ * list is validated at runtime, so a saved spec carrying `"error"` now throws
15
+ * `ChartSpecError` instead of silently drawing. That is the right failure (the
16
+ * alternative is a series quietly losing its colour), but unlike the rest of
17
+ * this migration a type-check will not find the affected specs: anything stored
18
+ * outside the repo has to be rewritten `error` → `danger`.
19
+ */
10
20
  export const CHART_SEMANTIC_COLORS: readonly ChartSemanticColor[] = [
11
21
  'primary',
12
22
  'secondary',
13
23
  'success',
14
24
  'warning',
15
- 'error',
25
+ 'danger',
16
26
  'info',
17
27
  ];
18
28
 
@@ -13,6 +13,10 @@ import {
13
13
  } from './Chip.helpers';
14
14
  import type { ChipProps } from './Chip.types';
15
15
 
16
+ /** MUI draws two chip sizes; ours are the house abbreviations for them. */
17
+ const muiSizeFor = (size: NonNullable<ChipProps['size']>): 'small' | 'medium' =>
18
+ size === 'sm' ? 'small' : 'medium';
19
+
16
20
  type MuiChipColor =
17
21
  | 'primary'
18
22
  | 'secondary'
@@ -22,10 +26,26 @@ type MuiChipColor =
22
26
  | 'warning'
23
27
  | 'default';
24
28
 
29
+ /**
30
+ * `danger` and `neutral` are ours; the rest are MUI's own names already.
31
+ *
32
+ * The same boundary translation `Button` performs, and it must stay a
33
+ * translation rather than a cast: a cast is what let `danger` through untouched
34
+ * and had MUI silently fall back to grey.
35
+ *
36
+ * `neutral` maps to `default`, not to `inherit` as it does on a button. A chip's
37
+ * unaccented state is a real palette entry with its own fill; `inherit` would
38
+ * take the surrounding text colour and paint a chip that reads as disabled.
39
+ */
40
+ const muiColorFor = (color: NonNullable<ChipProps['color']>): MuiChipColor => {
41
+ if (color === 'danger') return 'error';
42
+ return color === 'neutral' ? 'default' : color;
43
+ };
44
+
25
45
  export const Chip = forwardRef<HTMLDivElement, ChipProps>(({
26
46
  label,
27
47
  variant = 'filled',
28
- size = 'medium',
48
+ size = 'md',
29
49
  color = 'primary',
30
50
  avatarSrc,
31
51
  avatar,
@@ -48,8 +68,8 @@ export const Chip = forwardRef<HTMLDivElement, ChipProps>(({
48
68
  ref={ref}
49
69
  label={<span data-testid={testId('label')}>{label}</span>}
50
70
  variant={variant}
51
- size={size}
52
- color={color as MuiChipColor | undefined}
71
+ size={muiSizeFor(size)}
72
+ color={muiColorFor(color)}
53
73
  avatar={avatarFor(avatar, avatarSrc)}
54
74
  icon={iconWithTestId(icon, testId('icon'))}
55
75
  onDelete={deletable ? onDelete : undefined}
@@ -1,7 +1,41 @@
1
+ import type { ColorValue, SizeValue } from '../../../tokens/scales';
1
2
  import type { ReactNode } from 'react';
2
3
 
3
4
  export type ChipVariant = 'filled' | 'outlined';
4
- export type ChipSize = 'small' | 'medium';
5
+
6
+ /**
7
+ * The house size scale, abbreviated like every other component's.
8
+ *
9
+ * `Avatar`, `Badge`, `Progress`, `Input`, `Modal` and `Dialog` all use `sm`/`md`;
10
+ * this was `'small' | 'medium'`, MUI's spelling, and the sole outlier in the
11
+ * package. That was not merely inconsistent — callers wrote `size="sm"` because
12
+ * every neighbouring component taught them to, it type-checked against the old
13
+ * `ChipSize` nowhere and reached MUI as an unknown value, and the chip silently
14
+ * rendered at the default `medium`. Twelve of them are doing that today.
15
+ *
16
+ * Only two entries, because a MUI chip only draws two. Inventing `lg` here would
17
+ * put a size in the type that nothing can render.
18
+ */
19
+ export type ChipSize = Extract<SizeValue, 'sm' | 'md'>;
20
+
21
+ /**
22
+ * The colours a chip can draw — THE HOUSE VOCABULARY, identical to `ButtonProps`.
23
+ *
24
+ * `danger` and `neutral` are ours; the rest are MUI's own names already. That is
25
+ * the same sentence `Button.tsx` carries, and deliberately so: `Button`, `Alert`,
26
+ * `Text`, `Heading`, `Paragraph` and `Blockquote` all speak this vocabulary, so a
27
+ * chip is the odd one out if it does not.
28
+ *
29
+ * This prop was `string`, and the widening was not free. `danger` — the house
30
+ * word for the destructive meaning, and what every neighbouring component
31
+ * accepts — reached MUI as an unknown value and fell back to the default grey.
32
+ * The chip that most needed emphasis rendered without any, and nothing said so.
33
+ *
34
+ * The fix is NOT to reject `danger`. Rejecting it would punish a caller for
35
+ * using the vocabulary the rest of the system taught them. The fix is for the
36
+ * chip to speak it and translate at the MUI boundary, exactly as `Button` does.
37
+ */
38
+ export type ChipColor = ColorValue;
5
39
 
6
40
  export interface ChipProps {
7
41
  /** Text content displayed in the chip */
@@ -13,8 +47,8 @@ export interface ChipProps {
13
47
  /** Size of the chip */
14
48
  size?: ChipSize;
15
49
 
16
- /** Theme color token */
17
- color?: string;
50
+ /** Theme color token. See {@link ChipColor} for why this is a closed set. */
51
+ color?: ChipColor;
18
52
 
19
53
  /** Source URL for avatar image */
20
54
  avatarSrc?: string;
@@ -6,8 +6,12 @@
6
6
  interface ChipProps {
7
7
  label: string;
8
8
  variant?: 'filled' | 'outlined';
9
- size?: 'sm' | 'md' | 'lg';
10
- color?: ColorValue; // theme token
9
+ size?: SizeValue narrowed to 'sm' | 'md';
10
+ /**
11
+ * The house vocabulary, identical to Button's. `danger` and `neutral` are
12
+ * ours and are translated at the MUI boundary. See `ColorValue`.
13
+ */
14
+ color?: ColorValue;
11
15
  avatarSrc?: string; // or avatar node via `avatar`
12
16
  avatar?: React.ReactNode;
13
17
  icon?: React.ReactNode; // leading icon
@@ -296,7 +300,7 @@ test('tag removal in list', async () => {
296
300
  test('status chip with color coding', () => {
297
301
  const statuses = [
298
302
  { label: 'Success', color: 'success', testId: 'status-success' },
299
- { label: 'Error', color: 'error', testId: 'status-error' },
303
+ { label: 'Error', color: 'danger', testId: 'status-error' },
300
304
  { label: 'Warning', color: 'warning', testId: 'status-warning' },
301
305
  ];
302
306
 
@@ -1,2 +1,2 @@
1
1
  export { Chip } from './Chip';
2
- export type { ChipProps, ChipSize, ChipVariant } from './Chip.types';
2
+ export type { ChipColor, ChipProps, ChipSize, ChipVariant } from './Chip.types';
@@ -5,6 +5,7 @@ import { useEffect, useRef } from "react";
5
5
  import { type SortFieldDefinition } from "../../layout/ContentToolbar";
6
6
 
7
7
  import { GridShell } from "./data-views-grid-parts";
8
+ import type { ListGroupConfig } from "./list-card-rails";
8
9
  import type { DisplayPanelView } from "./data-views-display-panel";
9
10
  import type { BoardConfig } from "./DataViewsBoard";
10
11
  import type { DataViewExport } from "./data-views-export";
@@ -123,6 +124,15 @@ interface DataViewsGridProps<T extends Record<string, unknown>> {
123
124
  * offered, exactly as `renderCard` gates the cards.
124
125
  */
125
126
  renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;
127
+ /**
128
+ * The Lista's SHARED COLUMNS — declared once for the list, obeyed by every row.
129
+ *
130
+ * This is what makes a list line up by construction rather than by convention.
131
+ * Omit it and each row still resolves its own tracks from its own config,
132
+ * which agrees with its neighbours right up until one row carries an unusually
133
+ * wide value.
134
+ */
135
+ listGroup?: ListGroupConfig<T>;
126
136
  /**
127
137
  * The page-level partition rendered as an exclusive tab strip above the
128
138
  * toolbar, with server-supplied counts. Requires `server`: a scope is applied
@@ -4,6 +4,7 @@ import type { SortFieldDefinition } from "../../layout/ContentToolbar";
4
4
  import { useEffect, useMemo, useRef, useState } from "react";
5
5
 
6
6
  import { DataViewsGrid } from "./DataViewsGrid";
7
+ import type { ListGroupConfig } from "./list-card-rails";
7
8
  import type { DisplayPanelView } from "./data-views-display-panel";
8
9
  import { viewStateKey } from "./data-views-dirty";
9
10
  import { DeleteViewDialog } from "./DeleteViewDialog";
@@ -83,6 +84,8 @@ export interface DataViewsTableBaseProps<T extends Record<string, unknown>> {
83
84
  board?: BoardConfig<T>;
84
85
  /** Opt-in "Lista" layout — one full-width, entity-rendered row per record (FUT-733). */
85
86
  renderListRow?: (row: T, selection: DataViewCardSelection) => React.ReactNode;
87
+ /** The Lista's shared columns, so its rows line up by construction not convention. */
88
+ listGroup?: ListGroupConfig<T>;
86
89
  /** The page-level partition rendered as tabs under the toolbar (server mode only). */
87
90
  scopes?: ScopeConfig[];
88
91
  /** The row field the scopes partition by — used only to reject a pill over the same field. */