@12-apps/ui 1.22.0 → 2.0.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 (88) 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/RESPONSIVE-BEHAVIOUR.md +37 -3
  22. package/src/components/data-display/DataViews/data-views-day-input.tsx +22 -3
  23. package/src/components/data-display/DataViews/data-views-display-panel.tsx +4 -2
  24. package/src/components/data-display/DataViews/data-views-grid-bodies.tsx +113 -62
  25. package/src/components/data-display/DataViews/data-views-grid-parts.tsx +17 -1
  26. package/src/components/data-display/DataViews/data-views-more-fields.tsx +16 -23
  27. package/src/components/data-display/DataViews/data-views-more-filters.tsx +7 -2
  28. package/src/components/data-display/DataViews/data-views-number-input.tsx +14 -4
  29. package/src/components/data-display/DataViews/data-views-overflow-costs.ts +87 -16
  30. package/src/components/data-display/DataViews/data-views-overflow.ts +104 -71
  31. package/src/components/data-display/DataViews/index.ts +4 -0
  32. package/src/components/data-display/DataViews/list-card-rails.tsx +29 -0
  33. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.tsx +8 -5
  34. package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.types.ts +2 -1
  35. package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.types.ts +2 -1
  36. package/src/components/data-display/LoadingState/LoadingState.types.ts +2 -1
  37. package/src/components/data-display/Progress/Progress.types.ts +3 -2
  38. package/src/components/data-display/Sheet/Sheet.body.tsx +2 -1
  39. package/src/components/data-display/Sheet/Sheet.styles.ts +3 -2
  40. package/src/components/data-display/Sheet/Sheet.types.ts +7 -2
  41. package/src/components/data-display/Stepper/Stepper.types.ts +2 -1
  42. package/src/components/data-display/Table/Table.styles.ts +1 -1
  43. package/src/components/data-display/Table/Table.types.ts +3 -1
  44. package/src/components/data-display/Tooltip/Tooltip.types.ts +2 -1
  45. package/src/components/feedback/Dialog/Dialog.types.ts +2 -1
  46. package/src/components/feedback/Modal/Modal.types.ts +2 -1
  47. package/src/components/form/Button/Button.types.ts +3 -2
  48. package/src/components/form/Command/Command.styles.ts +8 -7
  49. package/src/components/form/Command/Command.types.ts +3 -2
  50. package/src/components/form/CreatableSelect/CreatableSelect.types.ts +2 -1
  51. package/src/components/form/Input/Input.types.ts +2 -1
  52. package/src/components/form/InputOTP/InputOTP.types.ts +3 -2
  53. package/src/components/form/Label/Label.styles.ts +8 -5
  54. package/src/components/form/Label/Label.tsx +1 -1
  55. package/src/components/form/Label/Label.types.ts +3 -2
  56. package/src/components/form/Menubar/Menubar.styles.ts +10 -8
  57. package/src/components/form/Menubar/Menubar.tsx +2 -1
  58. package/src/components/form/Menubar/Menubar.types.ts +7 -6
  59. package/src/components/form/RadioGroup/RadioGroup.types.ts +3 -2
  60. package/src/components/form/Select/Select.tsx +13 -1
  61. package/src/components/form/Select/Select.types.ts +5 -2
  62. package/src/components/form/Slider/Slider.types.ts +3 -2
  63. package/src/components/form/Switch/Switch.types.ts +3 -2
  64. package/src/components/form/Textarea/Textarea.types.ts +3 -2
  65. package/src/components/form/Toggle/Toggle.types.ts +3 -2
  66. package/src/components/form/ToggleGroup/ToggleGroup.types.ts +3 -2
  67. package/src/components/layout/Separator/Separator.types.ts +2 -1
  68. package/src/components/layout/Spacer/Spacer.types.ts +2 -1
  69. package/src/components/navigation/Breadcrumbs/Breadcrumbs.tsx +1 -1
  70. package/src/components/navigation/Breadcrumbs/Breadcrumbs.types.ts +7 -2
  71. package/src/components/navigation/ContextMenu/ContextMenu.types.ts +3 -2
  72. package/src/components/navigation/DropdownMenu/DropdownMenu.tsx +1 -0
  73. package/src/components/navigation/DropdownMenu/DropdownMenu.types.ts +12 -2
  74. package/src/components/navigation/NavigationMenu/NavigationMenu.types.ts +7 -2
  75. package/src/components/navigation/Pagination/Pagination.tsx +3 -1
  76. package/src/components/navigation/Pagination/Pagination.types.ts +5 -3
  77. package/src/components/navigation/Tabs/Tabs.types.ts +3 -2
  78. package/src/components/typography/Blockquote/Blockquote.types.ts +2 -1
  79. package/src/components/typography/Code/Code.types.ts +2 -1
  80. package/src/components/typography/Heading/Heading.types.ts +2 -1
  81. package/src/components/typography/Paragraph/Paragraph.types.ts +3 -2
  82. package/src/components/typography/Text/Text.types.ts +3 -2
  83. package/src/components/utility/AnimatedIcon/AnimatedIcon.types.ts +2 -1
  84. package/src/components/utility/Overflow/index.ts +6 -0
  85. package/src/components/utility/Overflow/overflow.ts +125 -0
  86. package/src/components/utility/WorkflowStep/WorkflowStep.types.ts +3 -2
  87. package/src/components/utility/index.ts +1 -0
  88. 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.22.0",
3
+ "version": "2.0.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. */
@@ -23,6 +23,10 @@ later; an eight-filter one degrades sooner. Both are correct.
23
23
  | small desktop | 1280 | laptop |
24
24
  | large desktop | 1600 | external monitor |
25
25
 
26
+ The tests also pin a **crowded** row at 1024, because a small desktop no longer
27
+ overflows at all: the rules about what happens when controls do NOT fit need a
28
+ width where some of them do not.
29
+
26
30
  **These are toolbar ROW widths, not viewport widths.** The `ResizeObserver`
27
31
  watches the row, and the row is narrower than the window by whatever the page
28
32
  puts around it — around 48px in the Pedidos shell. So a 430px row is roughly a
@@ -77,8 +81,26 @@ Read this as "what the Pedidos table does", not "what the code hard-codes".
77
81
 
78
82
  | | small mobile | large mobile | tablet | small desktop | large desktop |
79
83
  | --- | --- | --- | --- | --- | --- |
80
- | controls on the bar | 0 | 0 | 0 | 1–2 | 3+ |
81
- | "Mais" present | yes | yes | yes | yes | only if something overflowed |
84
+ | controls on the bar | 0 | 1 | 1 | all 6 | all 6 |
85
+ | "Mais" present | yes | yes | yes | no | no |
86
+
87
+ **The bar re-spends what its own collapse frees.** Rungs 2/4/5 turn 200 + 96 +
88
+ 216 of furniture into 44 + 0 + 140, and the split used to be decided once,
89
+ against the UNCOLLAPSED row — so a phone shed all six controls to make room for
90
+ a search box and a counter that were about to shrink, and nobody handed the
91
+ ~330px back. It showed as a 428px bar carrying a magnifier, "Mais 6", and a
92
+ 225px band of nothing. `computeSplit` now iterates to a fixed point: cheaper
93
+ furniture buys more filters, more filters push the ladder further down, a
94
+ further rung makes the furniture cheaper again, and the budget decreases
95
+ monotonically until it stops moving.
96
+
97
+ The furniture is also priced for what is ACTUALLY on the bar. "Exportar" exists
98
+ only when the host passes an `exportConfig` — several screens put their export
99
+ in the page header instead — and charging for the pair regardless billed the
100
+ filters ~108px for a button that was never rendered.
101
+
102
+ Which control survives on a phone is whichever fits the room left over, so a
103
+ large mobile keeps a cheaper pill where a tablet affords a wider one.
82
104
 
83
105
  **An applied filter is ranked first, not exempt.** Applied controls take the
84
106
  visible slots ahead of idle ones and go into "Mais" like anything else when even
@@ -133,7 +155,12 @@ Two distinct behaviours, and conflating them was a bug:
133
155
  | --- | --- | --- | --- | --- | --- |
134
156
  | Exibir / Exportar | icon | icon | label | label | label |
135
157
  | dropdown chevron | no | no | yes | yes | yes |
136
- | "N de N" counter | hidden | hidden | shown | shown | shown |
158
+ | "N de N" counter | hidden | shown | shown | shown | shown |
159
+
160
+ The counter is priced at 56, not the old 96. It was measured against
161
+ "1.234 de 5.678"; a phone reads "3 de 3" and renders at 35px, so the extra
162
+ ~60px was coming straight out of the filter budget at exactly the widths where
163
+ that budget is scarcest.
137
164
 
138
165
  A tablet keeps the labels, which is worth stating because it is easy to assume
139
166
  otherwise. With every filter already behind "Mais" the row has room to spare
@@ -176,6 +203,13 @@ leaves the board with a single populated column, which reads as data loss.
176
203
  of the five widths and asserts the tables above. It is the executable copy of
177
204
  this document; if the two disagree, the test is right and this needs updating.
178
205
 
206
+ `what fits follows the controls, not the width alone` guards the property the
207
+ rest of this document depends on: the counts above are OUTCOMES of pricing one
208
+ fixture against one row, never rules. At a single width it asserts that a table
209
+ declaring cheaper controls keeps more of them, that one long control keeps
210
+ none, and that the same table promotes another as its row grows — three answers
211
+ a breakpoint could not give.
212
+
179
213
  Two things it cannot check, because jsdom has no layout engine: actual pixel
180
214
  overflow, and how anything looks. Those are verified in a real browser against
181
215
  the running Storybook, at 1600 / 1280 / 1024 / 900 / 768 / 600 / 500 / 430 / 390