@12-apps/ui 2.1.0 → 3.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.
- package/package.json +1 -1
- package/src/components/data-display/Alert/Alert.types.ts +1 -1
- package/src/components/data-display/Avatar/Avatar.types.ts +1 -1
- package/src/components/data-display/Avatar/Avatar.view.tsx +5 -1
- package/src/components/data-display/Badge/Badge.styles.ts +11 -2
- package/src/components/data-display/Badge/Badge.types.ts +1 -1
- package/src/components/data-display/Carousel/Carousel.types.ts +4 -4
- package/src/components/data-display/Chart/Chart.types.ts +1 -1
- package/src/components/data-display/Chart/ChartSpec.types.ts +1 -1
- package/src/components/data-display/Chip/Chip.tsx +1 -1
- package/src/components/data-display/Chip/Chip.types.ts +6 -3
- package/src/components/data-display/DateTimeDisplay/DateTimeDisplay.types.ts +1 -1
- package/src/components/data-display/InteractiveTooltip/InteractiveTooltip.types.ts +1 -1
- package/src/components/data-display/LoadingState/LoadingState.helpers.ts +4 -0
- package/src/components/data-display/LoadingState/LoadingState.types.ts +1 -1
- package/src/components/data-display/Progress/Progress.styles.ts +3 -0
- package/src/components/data-display/Progress/Progress.types.ts +2 -2
- package/src/components/data-display/Sheet/Sheet.types.ts +1 -1
- package/src/components/data-display/Stepper/Stepper.tsx +18 -6
- package/src/components/data-display/Stepper/Stepper.types.ts +2 -2
- package/src/components/data-display/Tooltip/Tooltip.types.ts +1 -1
- package/src/components/form/Button/Button.styles.ts +1 -0
- package/src/components/form/Command/Command.types.ts +1 -1
- package/src/components/form/CreatableSelect/CreatableSelect.tsx +5 -2
- package/src/components/form/CreatableSelect/CreatableSelect.types.ts +1 -1
- package/src/components/form/Input/Input.styles.ts +4 -0
- package/src/components/form/Input/Input.types.ts +1 -1
- package/src/components/form/InputOTP/InputOTP.styles.ts +1 -0
- package/src/components/form/InputOTP/InputOTP.types.ts +1 -1
- package/src/components/form/RadioGroup/RadioGroup.styles.ts +1 -0
- package/src/components/form/RadioGroup/RadioGroup.types.ts +1 -1
- package/src/components/form/Select/Select.types.ts +1 -1
- package/src/components/form/Slider/Slider.styles.ts +1 -0
- package/src/components/form/Slider/Slider.types.ts +1 -1
- package/src/components/form/Switch/Switch.styles.ts +1 -0
- package/src/components/form/Switch/Switch.types.ts +1 -1
- package/src/components/form/Textarea/Textarea.styles.ts +1 -0
- package/src/components/form/Textarea/Textarea.types.ts +1 -1
- package/src/components/form/Toggle/Toggle.styles.ts +1 -0
- package/src/components/form/Toggle/Toggle.types.ts +1 -1
- package/src/components/form/ToggleGroup/ToggleGroup.tsx +1 -0
- package/src/components/form/ToggleGroup/ToggleGroup.types.ts +1 -1
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.styles.tsx +6 -3
- package/src/components/navigation/Breadcrumbs/Breadcrumbs.types.ts +1 -1
- package/src/components/navigation/ContextMenu/ContextMenu.types.ts +1 -1
- package/src/components/navigation/DropdownMenu/DropdownMenu.types.ts +1 -1
- package/src/components/navigation/NavigationMenu/NavigationMenu.types.ts +1 -1
- package/src/components/navigation/Pagination/Pagination.types.ts +1 -1
- package/src/components/navigation/Tabs/Tabs.types.ts +1 -1
- package/src/components/typography/Blockquote/Blockquote.tsx +1 -0
- package/src/components/typography/Blockquote/Blockquote.types.ts +1 -1
- package/src/components/typography/Code/Code.types.ts +1 -1
- package/src/components/typography/Heading/Heading.styles.ts +23 -7
- package/src/components/typography/Heading/Heading.types.ts +1 -1
- package/src/components/typography/Paragraph/Paragraph.tsx +1 -0
- package/src/components/typography/Paragraph/Paragraph.types.ts +1 -1
- package/src/components/typography/Text/Text.tsx +11 -5
- package/src/components/typography/Text/Text.types.ts +1 -1
- package/src/components/utility/AnimatedIcon/AnimatedIcon.styles.ts +1 -0
- package/src/components/utility/AnimatedIcon/AnimatedIcon.types.ts +1 -1
- package/src/components/utility/WorkflowStep/WorkflowStep.types.ts +1 -1
- package/src/tokens/scales.ts +58 -7
package/package.json
CHANGED
|
@@ -11,7 +11,7 @@ export interface AlertProps extends Omit<MuiAlertProps, 'variant' | 'color' | 'r
|
|
|
11
11
|
/**
|
|
12
12
|
* The color of the alert (when not using variant-specific colors)
|
|
13
13
|
*/
|
|
14
|
-
color?:
|
|
14
|
+
color?: ColorValue;
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Whether the alert should have a glow effect
|
|
@@ -57,7 +57,7 @@ export interface AvatarProps extends Omit<MuiAvatarProps, 'variant'> {
|
|
|
57
57
|
/**
|
|
58
58
|
* Color of the avatar background when no image is provided
|
|
59
59
|
*/
|
|
60
|
-
color?:
|
|
60
|
+
color?: ColorValue;
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* Whether the avatar is in a loading state
|
|
@@ -64,7 +64,11 @@ const getColorFromTheme = (theme: Theme, color: string): PaletteLike => {
|
|
|
64
64
|
secondary: theme.palette.secondary,
|
|
65
65
|
success: theme.palette.success,
|
|
66
66
|
warning: theme.palette.warning,
|
|
67
|
-
|
|
67
|
+
info: theme.palette.info,
|
|
68
|
+
// `danger`, not `error`. This map was keyed on MUI's word while the prop
|
|
69
|
+
// took the house one, so `danger` matched nothing and fell through to
|
|
70
|
+
// primary — a red avatar rendering blue, silently.
|
|
71
|
+
danger: theme.palette.error,
|
|
68
72
|
neutral: { main: theme.palette.grey[700], contrastText: '#fff' },
|
|
69
73
|
};
|
|
70
74
|
|
|
@@ -24,8 +24,10 @@ const getColorFromTheme = (theme: Theme, color: string) => {
|
|
|
24
24
|
secondary: theme.palette.secondary,
|
|
25
25
|
success: theme.palette.success,
|
|
26
26
|
warning: theme.palette.warning,
|
|
27
|
-
error: theme.palette.error,
|
|
28
27
|
info: theme.palette.info,
|
|
28
|
+
// `danger`, the house word the prop actually takes. Keyed on MUI's `error`
|
|
29
|
+
// it matched nothing and fell through to the default.
|
|
30
|
+
danger: theme.palette.error,
|
|
29
31
|
neutral: {
|
|
30
32
|
main: theme.palette.grey[600],
|
|
31
33
|
light: theme.palette.grey[400],
|
|
@@ -74,7 +76,14 @@ export const getSizeStyles = (size: BadgeSize) => {
|
|
|
74
76
|
fontSize: '0.875rem',
|
|
75
77
|
padding: '0 8px',
|
|
76
78
|
dotSize: 12,
|
|
77
|
-
iconSize: '1rem' }
|
|
79
|
+
iconSize: '1rem' },
|
|
80
|
+
xl: {
|
|
81
|
+
minWidth: 28,
|
|
82
|
+
height: 28,
|
|
83
|
+
fontSize: '1rem',
|
|
84
|
+
padding: '0 10px',
|
|
85
|
+
dotSize: 14,
|
|
86
|
+
iconSize: '1.125rem' } };
|
|
78
87
|
|
|
79
88
|
return sizeMap[size] || sizeMap.md;
|
|
80
89
|
};
|
|
@@ -14,7 +14,7 @@ export type BadgeVariant =
|
|
|
14
14
|
| 'success'
|
|
15
15
|
| 'warning';
|
|
16
16
|
|
|
17
|
-
export type BadgeSize =
|
|
17
|
+
export type BadgeSize = SizeValue;
|
|
18
18
|
|
|
19
19
|
export interface BadgeProps extends Omit<MuiBadgeProps, 'variant' | 'color' | 'content'> {
|
|
20
20
|
/**
|
|
@@ -14,7 +14,7 @@ export interface CarouselProps {
|
|
|
14
14
|
items: CarouselItem[];
|
|
15
15
|
variant?: 'default' | 'glass' | 'gradient' | 'elevated' | 'minimal' | 'cards';
|
|
16
16
|
size?: SizeValue;
|
|
17
|
-
color?:
|
|
17
|
+
color?: ColorValue;
|
|
18
18
|
autoPlay?: boolean;
|
|
19
19
|
autoPlayInterval?: number;
|
|
20
20
|
loop?: boolean;
|
|
@@ -51,7 +51,7 @@ export interface CarouselIndicatorsProps {
|
|
|
51
51
|
activeIndex: number;
|
|
52
52
|
onSelect: (index: number) => void;
|
|
53
53
|
position?: 'top' | 'bottom' | 'left' | 'right';
|
|
54
|
-
color?:
|
|
54
|
+
color?: ColorValue;
|
|
55
55
|
variant?: 'dots' | 'lines' | 'numbers';
|
|
56
56
|
className?: string;
|
|
57
57
|
style?: CSSProperties;
|
|
@@ -64,8 +64,8 @@ export interface CarouselArrowsProps {
|
|
|
64
64
|
disabled?: boolean;
|
|
65
65
|
disablePrev?: boolean;
|
|
66
66
|
disableNext?: boolean;
|
|
67
|
-
color?:
|
|
68
|
-
size?:
|
|
67
|
+
color?: ColorValue;
|
|
68
|
+
size?: SizeValue;
|
|
69
69
|
className?: string;
|
|
70
70
|
style?: CSSProperties;
|
|
71
71
|
}
|
|
@@ -28,7 +28,7 @@ export interface ChartProps {
|
|
|
28
28
|
type?: ChartType;
|
|
29
29
|
variant?: 'default' | 'glass' | 'gradient' | 'elevated' | 'minimal' | 'neon';
|
|
30
30
|
size?: SizeValue;
|
|
31
|
-
color?:
|
|
31
|
+
color?: ColorValue;
|
|
32
32
|
height?: number;
|
|
33
33
|
width?: number | string;
|
|
34
34
|
glow?: boolean;
|
|
@@ -18,7 +18,7 @@ export type ChartSpecType = 'line' | 'bar' | 'area' | 'pie' | 'donut';
|
|
|
18
18
|
* The house vocabulary minus `neutral`: a data series is always an accent, and
|
|
19
19
|
* an unaccented series would be indistinguishable from the grid it sits on.
|
|
20
20
|
*/
|
|
21
|
-
export type ChartSemanticColor =
|
|
21
|
+
export type ChartSemanticColor = ColorValue;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Number formatting applied to tooltip values and the y axis.
|
|
@@ -15,7 +15,7 @@ import type { ChipProps } from './Chip.types';
|
|
|
15
15
|
|
|
16
16
|
/** MUI draws two chip sizes; ours are the house abbreviations for them. */
|
|
17
17
|
const muiSizeFor = (size: NonNullable<ChipProps['size']>): 'small' | 'medium' =>
|
|
18
|
-
size === 'sm' ? 'small' : 'medium';
|
|
18
|
+
size === 'xs' || size === 'sm' ? 'small' : 'medium';
|
|
19
19
|
|
|
20
20
|
type MuiChipColor =
|
|
21
21
|
| 'primary'
|
|
@@ -13,10 +13,13 @@ export type ChipVariant = 'filled' | 'outlined';
|
|
|
13
13
|
* `ChipSize` nowhere and reached MUI as an unknown value, and the chip silently
|
|
14
14
|
* rendered at the default `medium`. Twelve of them are doing that today.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* All five stops, and `Chip.tsx` draws them: MUI gives a chip two heights, so
|
|
17
|
+
* `xs`/`sm` map to small and `md` and up to medium. Offering fewer than five
|
|
18
|
+
* here is what made callers write `size="sm"` against a type that did not have
|
|
19
|
+
* it — the scale is the vocabulary, and a component that speaks a dialect of it
|
|
20
|
+
* is the problem, not the solution.
|
|
18
21
|
*/
|
|
19
|
-
export type ChipSize =
|
|
22
|
+
export type ChipSize = SizeValue;
|
|
20
23
|
|
|
21
24
|
/**
|
|
22
25
|
* The colours a chip can draw — THE HOUSE VOCABULARY, identical to `ButtonProps`.
|
|
@@ -4,15 +4,19 @@ export const SIZE_MAP: Record<
|
|
|
4
4
|
LoadingStateSize,
|
|
5
5
|
{ spinner: number; text: 'body2' | 'body1' | 'h6' }
|
|
6
6
|
> = {
|
|
7
|
+
xs: { spinner: 16, text: 'body2' },
|
|
7
8
|
sm: { spinner: 24, text: 'body2' },
|
|
8
9
|
md: { spinner: 40, text: 'body1' },
|
|
9
10
|
lg: { spinner: 56, text: 'h6' },
|
|
11
|
+
xl: { spinner: 64, text: 'h6' },
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
export const SKELETON_ROW_HEIGHT: Record<LoadingStateSize, number> = {
|
|
15
|
+
xs: 14,
|
|
13
16
|
sm: 20,
|
|
14
17
|
md: 28,
|
|
15
18
|
lg: 36,
|
|
19
|
+
xl: 44,
|
|
16
20
|
};
|
|
17
21
|
|
|
18
22
|
type LoadingStateDefaultedKeys = 'variant' | 'size' | 'skeletonRows';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SizeValue } from '../../../tokens/scales';
|
|
2
2
|
export type LoadingStateVariant = 'spinner' | 'skeleton';
|
|
3
|
-
export type LoadingStateSize =
|
|
3
|
+
export type LoadingStateSize = SizeValue;
|
|
4
4
|
|
|
5
5
|
export interface LoadingStateProps {
|
|
6
6
|
/**
|
|
@@ -34,6 +34,7 @@ export const getColorFromTheme = (theme: Theme, color: string): PaletteColor =>
|
|
|
34
34
|
success: theme.palette.success,
|
|
35
35
|
warning: theme.palette.warning,
|
|
36
36
|
error: theme.palette.error,
|
|
37
|
+
info: theme.palette.info,
|
|
37
38
|
danger: theme.palette.error, // Add danger alias
|
|
38
39
|
};
|
|
39
40
|
|
|
@@ -42,9 +43,11 @@ export const getColorFromTheme = (theme: Theme, color: string): PaletteColor =>
|
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
const SIZE_MAP: Record<ProgressSize, { height: number; circularSize: number; fontSize: string }> = {
|
|
46
|
+
xs: { height: 2, circularSize: 24, fontSize: '0.625rem' },
|
|
45
47
|
sm: { height: 4, circularSize: 32, fontSize: '0.75rem' },
|
|
46
48
|
md: { height: 6, circularSize: 40, fontSize: '0.875rem' },
|
|
47
49
|
lg: { height: 8, circularSize: 48, fontSize: '1rem' },
|
|
50
|
+
xl: { height: 10, circularSize: 56, fontSize: '1.125rem' },
|
|
48
51
|
};
|
|
49
52
|
|
|
50
53
|
export const getSizeStyles = (size?: ProgressSize) => SIZE_MAP[size as ProgressSize] || SIZE_MAP.md;
|
|
@@ -2,7 +2,7 @@ import type { ColorValue, SizeValue } from '../../../tokens/scales';
|
|
|
2
2
|
import type { LinearProgressProps } from '@mui/material';
|
|
3
3
|
|
|
4
4
|
export type ProgressVariant = 'linear' | 'circular' | 'segmented' | 'gradient' | 'glass';
|
|
5
|
-
export type ProgressSize =
|
|
5
|
+
export type ProgressSize = SizeValue;
|
|
6
6
|
|
|
7
7
|
export interface ProgressProps extends Omit<LinearProgressProps, 'variant' | 'color'> {
|
|
8
8
|
/**
|
|
@@ -18,7 +18,7 @@ export interface ProgressProps extends Omit<LinearProgressProps, 'variant' | 'co
|
|
|
18
18
|
/**
|
|
19
19
|
* The color of the progress
|
|
20
20
|
*/
|
|
21
|
-
color?:
|
|
21
|
+
color?: ColorValue;
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Whether the progress should have a glow effect
|
|
@@ -8,6 +8,18 @@ import {
|
|
|
8
8
|
import React from 'react';
|
|
9
9
|
|
|
10
10
|
import type { Step, StepItemProps, StepperProps } from './Stepper.types';
|
|
11
|
+
import type { SizeValue } from '../../../tokens/scales';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The five house stops, drawn rather than collapsed.
|
|
15
|
+
*
|
|
16
|
+
* These were two-way ternaries on `sm`, which is what a two-value size prop
|
|
17
|
+
* allows; with the full scale they have to be real values or the three new
|
|
18
|
+
* stops would render identically to `md` and the prop would be a lie.
|
|
19
|
+
*/
|
|
20
|
+
const STEP_MIN_WIDTH: Record<SizeValue, number> = { xs: 44, sm: 56, md: 80, lg: 96, xl: 112 };
|
|
21
|
+
const STEP_ICON_SIZE: Record<SizeValue, number> = { xs: 14, sm: 18, md: 24, lg: 30, xl: 36 };
|
|
22
|
+
|
|
11
23
|
|
|
12
24
|
const StepperRoot = styled(Box, {
|
|
13
25
|
shouldForwardProp: (prop) => prop !== 'orientation',
|
|
@@ -37,12 +49,12 @@ const StepItem = styled(Box, {
|
|
|
37
49
|
}));
|
|
38
50
|
|
|
39
51
|
/** Circle diameter per size. */
|
|
40
|
-
const CIRCLE_SIZE = { sm: 32, md: 48
|
|
52
|
+
const CIRCLE_SIZE: Record<SizeValue, number> = { xs: 24, sm: 32, md: 48, lg: 60, xl: 72 };
|
|
41
53
|
|
|
42
54
|
const StepButton = styled(Button, {
|
|
43
55
|
shouldForwardProp: (prop) =>
|
|
44
56
|
!['isActive', 'isCompleted', 'clickable', 'stepSize'].includes(prop as string),
|
|
45
|
-
})<{ isActive: boolean; isCompleted: boolean; clickable: boolean; stepSize:
|
|
57
|
+
})<{ isActive: boolean; isCompleted: boolean; clickable: boolean; stepSize: SizeValue }>(
|
|
46
58
|
({ theme, isActive, isCompleted, clickable, stepSize }) => ({
|
|
47
59
|
minWidth: CIRCLE_SIZE[stepSize],
|
|
48
60
|
width: CIRCLE_SIZE[stepSize],
|
|
@@ -80,7 +92,7 @@ const StepButton = styled(Button, {
|
|
|
80
92
|
const StepConnector = styled(Box, {
|
|
81
93
|
shouldForwardProp: (prop) =>
|
|
82
94
|
!['orientation', 'isCompleted', 'size'].includes(prop as string),
|
|
83
|
-
})<{ orientation: 'horizontal' | 'vertical'; isCompleted: boolean; size:
|
|
95
|
+
})<{ orientation: 'horizontal' | 'vertical'; isCompleted: boolean; size: SizeValue }>(
|
|
84
96
|
({ theme, orientation, isCompleted, size }) => ({
|
|
85
97
|
backgroundColor: isCompleted ? theme.palette.primary.main : theme.palette.grey[300],
|
|
86
98
|
transition: theme.transitions.create('background-color', {
|
|
@@ -115,7 +127,7 @@ const StepLabel = styled(Box, {
|
|
|
115
127
|
marginTop: orientation === 'horizontal' ? 8 : 0,
|
|
116
128
|
}));
|
|
117
129
|
|
|
118
|
-
type StepSize =
|
|
130
|
+
type StepSize = SizeValue;
|
|
119
131
|
type LabelVariant = 'caption' | 'body2';
|
|
120
132
|
|
|
121
133
|
/** Space-joined state classes for a step (active / completed / optional). */
|
|
@@ -132,7 +144,7 @@ function stepClasses(isActive: boolean, isCompleted: boolean, optional?: boolean
|
|
|
132
144
|
/** The circle content-column width per orientation/size. */
|
|
133
145
|
function contentSx(orientation: 'horizontal' | 'vertical', size: StepSize) {
|
|
134
146
|
return orientation === 'horizontal'
|
|
135
|
-
? { minWidth: size
|
|
147
|
+
? { minWidth: STEP_MIN_WIDTH[size], maxWidth: 150, flex: '0 1 auto' }
|
|
136
148
|
: { flexShrink: 0 };
|
|
137
149
|
}
|
|
138
150
|
|
|
@@ -162,7 +174,7 @@ const StepCircle: React.FC<
|
|
|
162
174
|
data-testid={`stepper-step-icon-${index}`}
|
|
163
175
|
>
|
|
164
176
|
{isCompleted ? (
|
|
165
|
-
<CheckCircle sx={{ fontSize: size
|
|
177
|
+
<CheckCircle sx={{ fontSize: STEP_ICON_SIZE[size] }} />
|
|
166
178
|
) : (
|
|
167
179
|
<Typography variant={labelVariant} fontWeight="bold">
|
|
168
180
|
{index + 1}
|
|
@@ -41,7 +41,7 @@ export interface StepperProps {
|
|
|
41
41
|
* takes far less vertical space — useful above the fold on mobile checkouts.
|
|
42
42
|
* @default 'md'
|
|
43
43
|
*/
|
|
44
|
-
size?:
|
|
44
|
+
size?: SizeValue;
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
47
|
* Navigation variant controlling step access
|
|
@@ -89,7 +89,7 @@ export interface StepItemProps {
|
|
|
89
89
|
isCompleted: boolean;
|
|
90
90
|
isLast: boolean;
|
|
91
91
|
orientation: 'horizontal' | 'vertical';
|
|
92
|
-
size:
|
|
92
|
+
size: SizeValue;
|
|
93
93
|
variant: 'linear' | 'non-linear';
|
|
94
94
|
clickable: boolean;
|
|
95
95
|
onStepClick?: (id: string) => void;
|
|
@@ -46,6 +46,7 @@ export const getColorFromTheme = (theme: Theme, color: string): ColorPalette =>
|
|
|
46
46
|
secondary: theme.palette.secondary as ColorPalette,
|
|
47
47
|
success: theme.palette.success as ColorPalette,
|
|
48
48
|
warning: theme.palette.warning as ColorPalette,
|
|
49
|
+
info: theme.palette.info as ColorPalette,
|
|
49
50
|
danger: theme.palette.error as ColorPalette,
|
|
50
51
|
};
|
|
51
52
|
|
|
@@ -23,7 +23,7 @@ export interface CommandProps {
|
|
|
23
23
|
onSelect?: (item: CommandItem) => void;
|
|
24
24
|
variant?: 'default' | 'glass' | 'gradient' | 'minimal' | 'elevated';
|
|
25
25
|
size?: SizeValue;
|
|
26
|
-
color?:
|
|
26
|
+
color?: ColorValue;
|
|
27
27
|
glow?: boolean;
|
|
28
28
|
pulse?: boolean;
|
|
29
29
|
loading?: boolean;
|
|
@@ -9,6 +9,7 @@ import { useId, useMemo } from 'react';
|
|
|
9
9
|
|
|
10
10
|
import { FormControl, FormLabel, FormMessage } from '../Form';
|
|
11
11
|
import type { CreatableSelectOption, CreatableSelectProps } from './CreatableSelect.types';
|
|
12
|
+
import type { SizeValue } from '../../../tokens/scales';
|
|
12
13
|
|
|
13
14
|
/** Internal option shape: a real option, or the synthetic "create new" row. */
|
|
14
15
|
interface InternalOption extends CreatableSelectOption {
|
|
@@ -25,7 +26,9 @@ const filterOptions = createFilterOptions<InternalOption>();
|
|
|
25
26
|
const INDENT_STEP = 16;
|
|
26
27
|
|
|
27
28
|
/** `sm`/`md` → MUI's own scale, the same mapping {@link Input} uses. */
|
|
28
|
-
|
|
29
|
+
// All five house stops onto MUI's two. `xs`/`sm` draw small; `md` and up draw
|
|
30
|
+
// medium, because an autocomplete field has no third or fourth height to give.
|
|
31
|
+
const MUI_SIZE = { xs: 'small', sm: 'small', md: 'medium', lg: 'medium', xl: 'medium' } as const;
|
|
29
32
|
|
|
30
33
|
/** Match the outlined border treatment of `Input` so siblings look identical. */
|
|
31
34
|
const fieldSx = {
|
|
@@ -92,7 +95,7 @@ function renderField(
|
|
|
92
95
|
placeholder?: string;
|
|
93
96
|
error?: string;
|
|
94
97
|
loading: boolean;
|
|
95
|
-
size:
|
|
98
|
+
size: SizeValue;
|
|
96
99
|
dataTestId: string;
|
|
97
100
|
},
|
|
98
101
|
): React.JSX.Element {
|
|
@@ -45,7 +45,7 @@ export interface CreatableSelectProps {
|
|
|
45
45
|
* Control height, matching {@link Input}'s scale so the two line up when they
|
|
46
46
|
* share a grid row (default: `md`).
|
|
47
47
|
*/
|
|
48
|
-
size?:
|
|
48
|
+
size?: SizeValue;
|
|
49
49
|
/** Base test id; the input is `<dataTestId>-input`. */
|
|
50
50
|
dataTestId?: string;
|
|
51
51
|
}
|
|
@@ -34,10 +34,14 @@ export const muiVariantFor = (variant: InputVariant): 'outlined' | 'filled' | 's
|
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
36
|
|
|
37
|
+
// MUI gives a text field two heights, so the five house stops are drawn as two
|
|
38
|
+
// plus padding: `xs` tightens below small, `lg`/`xl` open up above medium.
|
|
37
39
|
export const SIZE_MAP = {
|
|
40
|
+
xs: { size: 'small' as const, sx: { '& .MuiInputBase-input': { padding: '6px 10px' } } },
|
|
38
41
|
sm: { size: 'small' as const },
|
|
39
42
|
md: { size: 'medium' as const },
|
|
40
43
|
lg: { size: 'medium' as const, sx: { '& .MuiInputBase-input': { padding: '16px 14px' } } },
|
|
44
|
+
xl: { size: 'medium' as const, sx: { '& .MuiInputBase-input': { padding: '20px 16px' } } },
|
|
41
45
|
};
|
|
42
46
|
|
|
43
47
|
export const glowStyles = (theme: Theme): CSSObject => ({
|
|
@@ -4,7 +4,7 @@ import type { InputHTMLAttributes, ReactNode } from 'react';
|
|
|
4
4
|
import type React from 'react';
|
|
5
5
|
|
|
6
6
|
export type InputVariant = 'outlined' | 'filled' | 'glass' | 'underline' | 'gradient';
|
|
7
|
-
export type InputSize =
|
|
7
|
+
export type InputSize = SizeValue;
|
|
8
8
|
|
|
9
9
|
export interface InputProps extends Omit<InputHTMLAttributes<HTMLInputElement>, 'size' | 'color'> {
|
|
10
10
|
variant?: InputVariant;
|
|
@@ -38,7 +38,7 @@ export interface SelectProps extends Omit<MuiSelectProps, 'variant' | 'size'> {
|
|
|
38
38
|
* Size of the select component
|
|
39
39
|
* @default 'medium'
|
|
40
40
|
*/
|
|
41
|
-
size?:
|
|
41
|
+
size?: SizeValue;
|
|
42
42
|
/**
|
|
43
43
|
* Placeholder text when no option is selected
|
|
44
44
|
*/
|
|
@@ -207,6 +207,9 @@ export const breadcrumbLinkStyles = ({
|
|
|
207
207
|
padding: theme.spacing(0.375, 0.5) } });
|
|
208
208
|
|
|
209
209
|
export const sizeIconMap = {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
210
|
+
xs: 'small',
|
|
211
|
+
sm: 'small',
|
|
212
|
+
md: 'small',
|
|
213
|
+
lg: 'medium',
|
|
214
|
+
xl: 'medium',
|
|
215
|
+
} as const;
|
|
@@ -106,7 +106,7 @@ export interface BreadcrumbsProps extends Omit<MuiBreadcrumbsProps, 'separator'>
|
|
|
106
106
|
* Size of the breadcrumbs
|
|
107
107
|
* @default 'md'
|
|
108
108
|
*/
|
|
109
|
-
size?:
|
|
109
|
+
size?: SizeValue;
|
|
110
110
|
|
|
111
111
|
/**
|
|
112
112
|
* Color scheme for separators and accents
|
|
@@ -1,24 +1,32 @@
|
|
|
1
1
|
import type { CSSObject, Theme } from '@mui/material';
|
|
2
2
|
|
|
3
3
|
import type { HeadingProps } from './Heading.types';
|
|
4
|
+
import type { ColorValue } from '../../../tokens/scales';
|
|
4
5
|
|
|
5
6
|
type Level = NonNullable<HeadingProps['level']>;
|
|
6
7
|
type Weight = NonNullable<HeadingProps['weight']>;
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
// Exhaustive over ColorValue, and deliberately without a fallback. The map
|
|
10
|
+
// happened to hold all seven already, but it was `Record<string, string>` with
|
|
11
|
+
// `|| text.primary` behind it — so nothing kept it that way, and the eighth
|
|
12
|
+
// colour would have rendered as ordinary body text with no complaint. That is
|
|
13
|
+
// the failure this whole vocabulary exists to make impossible; `Text` was
|
|
14
|
+
// tightened the same way and this one was missed.
|
|
15
|
+
const getColorFromTheme = (theme: Theme, color: ColorValue): string => {
|
|
9
16
|
if (color === 'neutral') {
|
|
10
17
|
return theme.palette.text.primary;
|
|
11
18
|
}
|
|
12
19
|
|
|
13
|
-
const colorMap: Record<
|
|
20
|
+
const colorMap: Record<Exclude<ColorValue, 'neutral'>, string> = {
|
|
14
21
|
primary: theme.palette.primary.main,
|
|
15
22
|
secondary: theme.palette.secondary.main,
|
|
16
23
|
success: theme.palette.success.main,
|
|
17
24
|
warning: theme.palette.warning.main,
|
|
25
|
+
info: theme.palette.info.main,
|
|
18
26
|
danger: theme.palette.error.main,
|
|
19
27
|
};
|
|
20
28
|
|
|
21
|
-
return colorMap[color]
|
|
29
|
+
return colorMap[color];
|
|
22
30
|
};
|
|
23
31
|
|
|
24
32
|
const WEIGHTS: Record<Weight, number> = {
|
|
@@ -50,22 +58,30 @@ const PRIMARY_STOPS = (theme: Theme): [string, string] => [
|
|
|
50
58
|
theme.palette.secondary.main,
|
|
51
59
|
];
|
|
52
60
|
|
|
53
|
-
|
|
61
|
+
// Keyed over the whole vocabulary, with no `?? PRIMARY_STOPS` behind it. Two
|
|
62
|
+
// stops were missing — `info` and `neutral` — so a gradient heading in either
|
|
63
|
+
// colour painted the PRIMARY gradient and looked deliberate while ignoring the
|
|
64
|
+
// prop entirely. The fallback is what hid it, so the fallback is gone.
|
|
65
|
+
const GRADIENT_STOPS: Record<ColorValue, (theme: Theme) => [string, string]> = {
|
|
54
66
|
primary: PRIMARY_STOPS,
|
|
55
67
|
secondary: (theme) => [theme.palette.secondary.main, theme.palette.primary.main],
|
|
56
68
|
success: (theme) => [theme.palette.success.light, theme.palette.success.dark],
|
|
57
69
|
warning: (theme) => [theme.palette.warning.light, theme.palette.warning.dark],
|
|
70
|
+
info: (theme) => [theme.palette.info.light, theme.palette.info.dark],
|
|
58
71
|
danger: (theme) => [theme.palette.error.light, theme.palette.error.dark],
|
|
72
|
+
// The grey ramp has no light/dark pair, and every stop on it is
|
|
73
|
+
// `string | undefined` under `noUncheckedIndexedAccess`.
|
|
74
|
+
neutral: (theme) => [theme.palette.grey[500] ?? '#9e9e9e', theme.palette.grey[900] ?? '#212121'],
|
|
59
75
|
};
|
|
60
76
|
|
|
61
|
-
const gradientFor = (theme: Theme, color:
|
|
62
|
-
const [from, to] =
|
|
77
|
+
const gradientFor = (theme: Theme, color: ColorValue) => {
|
|
78
|
+
const [from, to] = GRADIENT_STOPS[color](theme);
|
|
63
79
|
return `linear-gradient(135deg, ${from} 0%, ${to} 100%)`;
|
|
64
80
|
};
|
|
65
81
|
|
|
66
82
|
export interface HeadingFlags {
|
|
67
83
|
customLevel?: string;
|
|
68
|
-
customColor?:
|
|
84
|
+
customColor?: ColorValue;
|
|
69
85
|
customWeight?: string;
|
|
70
86
|
gradient?: boolean;
|
|
71
87
|
}
|
|
@@ -5,7 +5,7 @@ export type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'display';
|
|
|
5
5
|
|
|
6
6
|
export interface HeadingProps extends React.HTMLAttributes<globalThis.HTMLHeadingElement> {
|
|
7
7
|
level?: HeadingLevel;
|
|
8
|
-
color?:
|
|
8
|
+
color?: ColorValue;
|
|
9
9
|
weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
|
|
10
10
|
gradient?: boolean;
|
|
11
11
|
children: React.ReactNode;
|
|
@@ -15,6 +15,7 @@ const getColorFromTheme = (theme: Theme, color: string) => {
|
|
|
15
15
|
secondary: theme.palette.secondary.main,
|
|
16
16
|
success: theme.palette.success.main,
|
|
17
17
|
warning: theme.palette.warning.main,
|
|
18
|
+
info: theme.palette.info.main,
|
|
18
19
|
danger: theme.palette.error.main,
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -5,7 +5,7 @@ export type ParagraphVariant = 'default' | 'lead' | 'muted' | 'small';
|
|
|
5
5
|
|
|
6
6
|
export interface ParagraphProps extends React.HTMLAttributes<globalThis.HTMLParagraphElement> {
|
|
7
7
|
variant?: ParagraphVariant;
|
|
8
|
-
color?:
|
|
8
|
+
color?: ColorValue;
|
|
9
9
|
size?: SizeValue;
|
|
10
10
|
children: React.ReactNode;
|
|
11
11
|
}
|
|
@@ -6,8 +6,9 @@ import { styled } from '@mui/material/styles';
|
|
|
6
6
|
import React from 'react';
|
|
7
7
|
|
|
8
8
|
import type { TextProps } from './Text.types';
|
|
9
|
+
import type { ColorValue } from '../../../tokens/scales';
|
|
9
10
|
|
|
10
|
-
const getColorFromTheme = (theme: Theme, color:
|
|
11
|
+
const getColorFromTheme = (theme: Theme, color: ColorValue): string => {
|
|
11
12
|
if (color === 'neutral') {
|
|
12
13
|
return theme.palette.text.primary;
|
|
13
14
|
}
|
|
@@ -16,14 +17,19 @@ const getColorFromTheme = (theme: Theme, color: string) => {
|
|
|
16
17
|
return theme.palette.text.secondary;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
// Exhaustive over ColorValue on purpose. It was a partial `Record<string, …>`
|
|
21
|
+
// with a silent `|| text.primary` fallback, so `info` — accepted by the type
|
|
22
|
+
// — rendered as ordinary body text. Typing the key means a colour added to
|
|
23
|
+
// the vocabulary cannot reach here without a home.
|
|
24
|
+
const colorMap: Record<Exclude<ColorValue, 'neutral' | 'secondary'>, string> = {
|
|
20
25
|
primary: theme.palette.primary.main,
|
|
21
26
|
success: theme.palette.success.main,
|
|
22
27
|
warning: theme.palette.warning.main,
|
|
28
|
+
info: theme.palette.info.main,
|
|
23
29
|
danger: theme.palette.error.main,
|
|
24
30
|
};
|
|
25
31
|
|
|
26
|
-
return colorMap[color]
|
|
32
|
+
return colorMap[color];
|
|
27
33
|
};
|
|
28
34
|
|
|
29
35
|
const SIZE_MAP: Record<string, { fontSize: string; lineHeight: number }> = {
|
|
@@ -45,7 +51,7 @@ const WEIGHT_MAP: Record<string, number> = {
|
|
|
45
51
|
interface TextStyleArgs {
|
|
46
52
|
theme: Theme;
|
|
47
53
|
customVariant?: string;
|
|
48
|
-
customColor?:
|
|
54
|
+
customColor?: ColorValue;
|
|
49
55
|
customSize?: string;
|
|
50
56
|
customWeight?: string;
|
|
51
57
|
italic?: boolean;
|
|
@@ -119,7 +125,7 @@ const StyledText = styled(Typography, {
|
|
|
119
125
|
].includes(prop as string),
|
|
120
126
|
})<{
|
|
121
127
|
customVariant?: string;
|
|
122
|
-
customColor?:
|
|
128
|
+
customColor?: ColorValue;
|
|
123
129
|
customSize?: string;
|
|
124
130
|
customWeight?: string;
|
|
125
131
|
italic?: boolean;
|
|
@@ -5,7 +5,7 @@ export type TextVariant = 'body' | 'heading' | 'caption' | 'code';
|
|
|
5
5
|
|
|
6
6
|
export interface TextProps extends React.HTMLAttributes<HTMLSpanElement> {
|
|
7
7
|
variant?: TextVariant;
|
|
8
|
-
color?:
|
|
8
|
+
color?: ColorValue;
|
|
9
9
|
size?: SizeValue;
|
|
10
10
|
weight?: 'light' | 'normal' | 'medium' | 'semibold' | 'bold';
|
|
11
11
|
as?: keyof React.JSX.IntrinsicElements;
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
import type { AnimationSize, AnimationVariant } from './AnimatedIcon.types';
|
|
25
25
|
|
|
26
26
|
export const sizeConfigs: Record<AnimationSize, { size: number; fontSize: number }> = {
|
|
27
|
+
xs: { size: 12, fontSize: 10 },
|
|
27
28
|
sm: { size: 24, fontSize: 20 },
|
|
28
29
|
md: { size: 32, fontSize: 28 },
|
|
29
30
|
lg: { size: 48, fontSize: 44 },
|
|
@@ -20,7 +20,7 @@ export type AnimationVariant =
|
|
|
20
20
|
| 'neonFlicker'
|
|
21
21
|
| 'breathe';
|
|
22
22
|
|
|
23
|
-
export type AnimationSize =
|
|
23
|
+
export type AnimationSize = SizeValue;
|
|
24
24
|
|
|
25
25
|
export type ShadowStyle = 'soft' | 'hard' | 'elevated' | 'none';
|
|
26
26
|
|
|
@@ -19,7 +19,7 @@ export type WorkflowStepOrientation = 'horizontal' | 'vertical';
|
|
|
19
19
|
/**
|
|
20
20
|
* Color theme for the workflow step component
|
|
21
21
|
*/
|
|
22
|
-
export type WorkflowStepColor =
|
|
22
|
+
export type WorkflowStepColor = ColorValue;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* Size variant for step indicators
|
package/src/tokens/scales.ts
CHANGED
|
@@ -18,13 +18,13 @@ import type { Theme } from '@mui/material/styles';
|
|
|
18
18
|
*
|
|
19
19
|
* So the vocabulary is declared once, here, and components derive from it.
|
|
20
20
|
*
|
|
21
|
-
* DERIVE, DON'T COPY
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
21
|
+
* DERIVE, DON'T COPY — AND TAKE THE WHOLE VOCABULARY. Every size prop is
|
|
22
|
+
* `SizeValue` and every colour prop is `ColorValue`, with no `Extract` and no
|
|
23
|
+
* `Exclude`. Handing a component a slice was tried and undone: it is the same
|
|
24
|
+
* fragmentation wearing the canonical type's name, and it leaves a caller having
|
|
25
|
+
* to remember which components accept `xs` and which accept `info`. Where the
|
|
26
|
+
* underlying widget draws fewer steps than five, that is a rendering detail and
|
|
27
|
+
* belongs at the MUI boundary below — not in the prop's type.
|
|
28
28
|
*
|
|
29
29
|
* A component whose size or colour is not this concept at all keeps its own type
|
|
30
30
|
* — `Scrollbar`'s `thin|medium|thick` is a track width, `Sheet`'s `full` is a
|
|
@@ -37,9 +37,17 @@ import type { Theme } from '@mui/material/styles';
|
|
|
37
37
|
*
|
|
38
38
|
* Abbreviated, because that is what 26 of the 30 components with a real size
|
|
39
39
|
* already used and what every caller has been taught to write.
|
|
40
|
+
*
|
|
41
|
+
* Declared as the array and the type together so the two cannot disagree. The
|
|
42
|
+
* runtime list is what Storybook `argTypes.options` and the demo grids iterate;
|
|
43
|
+
* spelled by hand in each story it drifts, and it had — twelve colour lists
|
|
44
|
+
* omitted `info`, so the control that documents the vocabulary offered a
|
|
45
|
+
* component less than the component accepted.
|
|
40
46
|
*/
|
|
41
47
|
export type SizeValue = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
42
48
|
|
|
49
|
+
export const SIZE_VALUES = ['xs', 'sm', 'md', 'lg', 'xl'] as const satisfies readonly SizeValue[];
|
|
50
|
+
|
|
43
51
|
/**
|
|
44
52
|
* The house colour vocabulary.
|
|
45
53
|
*
|
|
@@ -53,6 +61,8 @@ export type SizeValue = 'xs' | 'sm' | 'md' | 'lg' | 'xl';
|
|
|
53
61
|
* component calls its unaccented state). The translation must be a real mapping
|
|
54
62
|
* and never a cast: a cast is precisely what let `danger` through untouched and
|
|
55
63
|
* had MUI fall back to grey.
|
|
64
|
+
*
|
|
65
|
+
* Array and type declared together, for the reason given on {@link SIZE_VALUES}.
|
|
56
66
|
*/
|
|
57
67
|
export type ColorValue =
|
|
58
68
|
| 'primary'
|
|
@@ -63,6 +73,47 @@ export type ColorValue =
|
|
|
63
73
|
| 'danger'
|
|
64
74
|
| 'neutral';
|
|
65
75
|
|
|
76
|
+
export const COLOR_VALUES = [
|
|
77
|
+
'primary',
|
|
78
|
+
'secondary',
|
|
79
|
+
'success',
|
|
80
|
+
'warning',
|
|
81
|
+
'info',
|
|
82
|
+
'danger',
|
|
83
|
+
'neutral',
|
|
84
|
+
] as const satisfies readonly ColorValue[];
|
|
85
|
+
|
|
86
|
+
/* ── Array and union, pinned to each other ────────────────────────────────── */
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* The arrays above are written out rather than derived, because deriving the
|
|
90
|
+
* union from the array (`(typeof COLOR_VALUES)[number]`) costs the alias its
|
|
91
|
+
* name: TypeScript resolves the indexed access eagerly, and every error in the
|
|
92
|
+
* package stops saying `not assignable to type 'ColorValue'` and starts dumping
|
|
93
|
+
* seven string literals. In a package whose entire purpose is teaching this
|
|
94
|
+
* vocabulary, the name in the error message is worth keeping.
|
|
95
|
+
*
|
|
96
|
+
* Writing both means they could disagree, so both directions are checked here.
|
|
97
|
+
* `satisfies` above catches an entry that is not a member. These catch the other
|
|
98
|
+
* half — a member missing from the array — which `satisfies` cannot see and
|
|
99
|
+
* which is the direction that actually bit: twelve story control lists offered
|
|
100
|
+
* six colours for a vocabulary of seven, so `info` was undocumented everywhere.
|
|
101
|
+
*
|
|
102
|
+
* A gap makes the assignment below fail, and the error names the missing member.
|
|
103
|
+
*/
|
|
104
|
+
type Missing<Union extends string, Listed extends string> = Exclude<Union, Listed>;
|
|
105
|
+
|
|
106
|
+
const _allSizesListed: Missing<SizeValue, (typeof SIZE_VALUES)[number]> extends never
|
|
107
|
+
? true
|
|
108
|
+
: never = true;
|
|
109
|
+
|
|
110
|
+
const _allColorsListed: Missing<ColorValue, (typeof COLOR_VALUES)[number]> extends never
|
|
111
|
+
? true
|
|
112
|
+
: never = true;
|
|
113
|
+
|
|
114
|
+
void _allSizesListed;
|
|
115
|
+
void _allColorsListed;
|
|
116
|
+
|
|
66
117
|
/* ── The MUI boundary ─────────────────────────────────────────────────────── */
|
|
67
118
|
|
|
68
119
|
/** What MUI calls a size. Three entries against our five. */
|