@apify/ui-library 1.141.2 → 1.145.2-featverifieddeveloperbadge-bd59c6.13
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/README.md +90 -19
- package/dist/src/components/box/box.d.ts +15 -14
- package/dist/src/components/box/box.d.ts.map +1 -1
- package/dist/src/components/box/box.js +10 -33
- package/dist/src/components/box/box.js.map +1 -1
- package/dist/src/components/breadcrumb/breadcrumb.d.ts.map +1 -1
- package/dist/src/components/breadcrumb/breadcrumb.js +1 -0
- package/dist/src/components/breadcrumb/breadcrumb.js.map +1 -1
- package/dist/src/components/card/card.d.ts.map +1 -1
- package/dist/src/components/card/card.js +1 -0
- package/dist/src/components/card/card.js.map +1 -1
- package/dist/src/components/chip/chip.d.ts.map +1 -1
- package/dist/src/components/chip/chip.js +2 -0
- package/dist/src/components/chip/chip.js.map +1 -1
- package/dist/src/components/code/code_block/code_block.styled.d.ts.map +1 -1
- package/dist/src/components/code/code_block/code_block.styled.js +1 -2
- package/dist/src/components/code/code_block/code_block.styled.js.map +1 -1
- package/dist/src/components/code/one_light_theme.d.ts.map +1 -1
- package/dist/src/components/code/one_light_theme.js +1 -0
- package/dist/src/components/code/one_light_theme.js.map +1 -1
- package/dist/src/components/dropdown/dropdown.context.d.ts +6 -0
- package/dist/src/components/dropdown/dropdown.context.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.context.js +10 -0
- package/dist/src/components/dropdown/dropdown.context.js.map +1 -0
- package/dist/src/components/dropdown/dropdown.d.ts +48 -0
- package/dist/src/components/dropdown/dropdown.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.js +65 -0
- package/dist/src/components/dropdown/dropdown.js.map +1 -0
- package/dist/src/components/dropdown/dropdown.styled.d.ts +35 -0
- package/dist/src/components/dropdown/dropdown.styled.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.styled.js +241 -0
- package/dist/src/components/dropdown/dropdown.styled.js.map +1 -0
- package/dist/src/components/dropdown/dropdown.types.d.ts +17 -0
- package/dist/src/components/dropdown/dropdown.types.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown.types.js +2 -0
- package/dist/src/components/dropdown/dropdown.types.js.map +1 -0
- package/dist/src/components/dropdown/dropdown_button.d.ts +10 -0
- package/dist/src/components/dropdown/dropdown_button.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown_button.js +16 -0
- package/dist/src/components/dropdown/dropdown_button.js.map +1 -0
- package/dist/src/components/dropdown/dropdown_root.d.ts +11 -0
- package/dist/src/components/dropdown/dropdown_root.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown_root.js +19 -0
- package/dist/src/components/dropdown/dropdown_root.js.map +1 -0
- package/dist/src/components/dropdown/dropdown_shell.d.ts +15 -0
- package/dist/src/components/dropdown/dropdown_shell.d.ts.map +1 -0
- package/dist/src/components/dropdown/dropdown_shell.js +69 -0
- package/dist/src/components/dropdown/dropdown_shell.js.map +1 -0
- package/dist/src/components/dropdown/index.d.ts +7 -0
- package/dist/src/components/dropdown/index.d.ts.map +1 -0
- package/dist/src/components/dropdown/index.js +5 -0
- package/dist/src/components/dropdown/index.js.map +1 -0
- package/dist/src/components/index.d.ts +1 -0
- package/dist/src/components/index.d.ts.map +1 -1
- package/dist/src/components/index.js +1 -0
- package/dist/src/components/index.js.map +1 -1
- package/dist/src/components/message/message.d.ts +2 -0
- package/dist/src/components/message/message.d.ts.map +1 -1
- package/dist/src/components/message/message.js +2 -0
- package/dist/src/components/message/message.js.map +1 -1
- package/dist/src/components/pagination/pagination.d.ts.map +1 -1
- package/dist/src/components/pagination/pagination.js +1 -0
- package/dist/src/components/pagination/pagination.js.map +1 -1
- package/dist/src/components/tabs/tab.js +1 -1
- package/dist/src/components/tabs/tab.js.map +1 -1
- package/dist/src/utils/css_utils.d.ts +17 -0
- package/dist/src/utils/css_utils.d.ts.map +1 -1
- package/dist/src/utils/css_utils.js +54 -0
- package/dist/src/utils/css_utils.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +10 -5
- package/src/components/box/box.stories.tsx +18 -0
- package/src/components/box/box.tsx +31 -62
- package/src/components/breadcrumb/breadcrumb.tsx +1 -0
- package/src/components/card/card.tsx +1 -0
- package/src/components/chip/chip.tsx +2 -0
- package/src/components/code/code_block/code_block.styled.tsx +1 -2
- package/src/components/code/one_light_theme.ts +1 -0
- package/src/components/dropdown/dropdown.context.tsx +14 -0
- package/src/components/dropdown/dropdown.stories.tsx +343 -0
- package/src/components/dropdown/dropdown.styled.ts +265 -0
- package/src/components/dropdown/dropdown.tsx +259 -0
- package/src/components/dropdown/dropdown.types.ts +18 -0
- package/src/components/dropdown/dropdown_button.tsx +45 -0
- package/src/components/dropdown/dropdown_root.tsx +41 -0
- package/src/components/dropdown/dropdown_shell.tsx +139 -0
- package/src/components/dropdown/index.ts +6 -0
- package/src/components/index.ts +1 -0
- package/src/components/message/message.stories.jsx +1 -0
- package/src/components/message/message.tsx +2 -0
- package/src/components/pagination/pagination.tsx +1 -0
- package/src/components/tabs/tab.tsx +1 -1
- package/src/design_system/colors/build_color_tokens.js +14 -0
- package/src/design_system/colors/colors.stories.tsx +395 -0
- package/src/utils/css_utils.ts +79 -0
- package/style/colors/tokens.dark.css +149 -0
- package/style/colors/tokens.light.css +149 -0
- package/src/design_system/colors/Colors.mdx +0 -50
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import {
|
|
2
|
+
autoUpdate,
|
|
3
|
+
flip,
|
|
4
|
+
offset as offsetMiddleware,
|
|
5
|
+
type Placement,
|
|
6
|
+
shift,
|
|
7
|
+
size,
|
|
8
|
+
useFloating,
|
|
9
|
+
} from '@floating-ui/react';
|
|
10
|
+
import * as DropdownMenu from '@radix-ui/react-dropdown-menu';
|
|
11
|
+
import { type ReactNode, type RefObject, useCallback, useLayoutEffect } from 'react';
|
|
12
|
+
|
|
13
|
+
import { theme } from '../../design_system/theme.js';
|
|
14
|
+
import { DropdownContext } from './dropdown.context.js';
|
|
15
|
+
import { ScrollableContent, StyledContent } from './dropdown.styled.js';
|
|
16
|
+
import type { BaseDropdownContentProps } from './dropdown.types.js';
|
|
17
|
+
|
|
18
|
+
export type DropdownShellProps = BaseDropdownContentProps & {
|
|
19
|
+
children: ReactNode;
|
|
20
|
+
isOpen: boolean;
|
|
21
|
+
onIsOpenChange: (open: boolean) => void;
|
|
22
|
+
triggerRef: RefObject<HTMLElement | null>;
|
|
23
|
+
strategy?: 'absolute' | 'fixed';
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* DropdownShell positions the dropdown relative to a custom trigger element via Floating UI.
|
|
28
|
+
* Use it when you have a custom trigger (not a Button) and need explicit ref-based positioning.
|
|
29
|
+
*/
|
|
30
|
+
export const DropdownShell = ({
|
|
31
|
+
isOpen,
|
|
32
|
+
onIsOpenChange,
|
|
33
|
+
children,
|
|
34
|
+
closeOnSelect = true,
|
|
35
|
+
contentProps,
|
|
36
|
+
width,
|
|
37
|
+
height,
|
|
38
|
+
triggerRef,
|
|
39
|
+
strategy = 'absolute',
|
|
40
|
+
zIndex,
|
|
41
|
+
}: DropdownShellProps) => {
|
|
42
|
+
const handleSelectAndClose = useCallback(
|
|
43
|
+
(event?: Event) => {
|
|
44
|
+
if (closeOnSelect) onIsOpenChange(false);
|
|
45
|
+
if (event) event.preventDefault();
|
|
46
|
+
},
|
|
47
|
+
[closeOnSelect, onIsOpenChange],
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const side = contentProps?.side || 'bottom';
|
|
51
|
+
const align = contentProps?.align || 'start';
|
|
52
|
+
const sideOffset = contentProps?.sideOffset || 0;
|
|
53
|
+
const alignOffset = contentProps?.alignOffset || 0;
|
|
54
|
+
|
|
55
|
+
const placement: Placement = align === 'center' ? (side as Placement) : (`${side}-${align}` as Placement);
|
|
56
|
+
|
|
57
|
+
const floatingMiddleware = [
|
|
58
|
+
shift(),
|
|
59
|
+
flip(),
|
|
60
|
+
size({
|
|
61
|
+
apply({ availableHeight, elements }) {
|
|
62
|
+
Object.assign(elements.floating.style, {
|
|
63
|
+
maxHeight: `calc(${availableHeight}px - ${theme.space.space8})`,
|
|
64
|
+
});
|
|
65
|
+
},
|
|
66
|
+
}),
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
if (sideOffset || alignOffset) {
|
|
70
|
+
floatingMiddleware.push(offsetMiddleware({ mainAxis: sideOffset, crossAxis: alignOffset }));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const {
|
|
74
|
+
x,
|
|
75
|
+
y,
|
|
76
|
+
refs: { setReference, setFloating },
|
|
77
|
+
strategy: floatingStrategy,
|
|
78
|
+
} = useFloating({
|
|
79
|
+
placement,
|
|
80
|
+
strategy,
|
|
81
|
+
middleware: floatingMiddleware,
|
|
82
|
+
whileElementsMounted: autoUpdate,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
useLayoutEffect(() => {
|
|
86
|
+
if (triggerRef?.current) {
|
|
87
|
+
setReference(triggerRef.current);
|
|
88
|
+
}
|
|
89
|
+
}, [triggerRef, setReference]);
|
|
90
|
+
|
|
91
|
+
const handlePointerDownOutside = useCallback(
|
|
92
|
+
(event: any) => {
|
|
93
|
+
const isInsideTrigger = triggerRef?.current?.contains(event.detail.originalEvent.target as Node);
|
|
94
|
+
if (!isInsideTrigger) {
|
|
95
|
+
onIsOpenChange(false);
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
[triggerRef, onIsOpenChange],
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const shouldShowDropdown = isOpen && x !== null && y !== null;
|
|
102
|
+
|
|
103
|
+
const setFloatingRef = useCallback(
|
|
104
|
+
(element: HTMLElement | null) => {
|
|
105
|
+
setFloating(element);
|
|
106
|
+
},
|
|
107
|
+
[setFloating],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (!shouldShowDropdown) return null;
|
|
111
|
+
|
|
112
|
+
const fullscreenContainer = document.fullscreenElement as HTMLElement | null;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<DropdownContext.Provider value={{ handleSelectAndClose }}>
|
|
116
|
+
<DropdownMenu.Root open={true} modal={false}>
|
|
117
|
+
<DropdownMenu.Portal container={fullscreenContainer ?? undefined}>
|
|
118
|
+
<StyledContent
|
|
119
|
+
ref={setFloatingRef}
|
|
120
|
+
$width={width}
|
|
121
|
+
$height={height}
|
|
122
|
+
$zIndex={zIndex}
|
|
123
|
+
data-radix-dropdown-content
|
|
124
|
+
onPointerDownOutside={handlePointerDownOutside}
|
|
125
|
+
style={{
|
|
126
|
+
position: floatingStrategy,
|
|
127
|
+
top: y ?? '',
|
|
128
|
+
left: x ?? '',
|
|
129
|
+
zIndex: zIndex ?? 100,
|
|
130
|
+
}}
|
|
131
|
+
{...contentProps}
|
|
132
|
+
>
|
|
133
|
+
<ScrollableContent>{children}</ScrollableContent>
|
|
134
|
+
</StyledContent>
|
|
135
|
+
</DropdownMenu.Portal>
|
|
136
|
+
</DropdownMenu.Root>
|
|
137
|
+
</DropdownContext.Provider>
|
|
138
|
+
);
|
|
139
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Dropdown } from './dropdown.js';
|
|
2
|
+
export { DropdownButton } from './dropdown_button.js';
|
|
3
|
+
export { DropdownShell } from './dropdown_shell.js';
|
|
4
|
+
export type { DropdownShellProps } from './dropdown_shell.js';
|
|
5
|
+
export { DROPDOWN_ELEMENT_CLASSES } from './dropdown.styled.js';
|
|
6
|
+
export type { BaseDropdownItemProps, BaseDropdownContentProps } from './dropdown.types.js';
|
package/src/components/index.ts
CHANGED
|
@@ -158,6 +158,8 @@ type MessageProps = BoxProps & {
|
|
|
158
158
|
|
|
159
159
|
/**
|
|
160
160
|
* Component used to display larger messages that are part of the content of the application.
|
|
161
|
+
* @deprecated Use `Alert` instead once it is available.
|
|
162
|
+
* @see https://www.notion.so/apify/Design-system-roadmap-2026-326f39950a2280d4b81bec00ebc9cf44
|
|
161
163
|
*/
|
|
162
164
|
export const Message: React.FC<MessageProps> = ({
|
|
163
165
|
className,
|
|
@@ -15,6 +15,7 @@ const PaginationButtonBase = styled(Button).attrs({ variant: 'tertiary' })`
|
|
|
15
15
|
padding: 0;
|
|
16
16
|
height: 28px;
|
|
17
17
|
min-width: 28px;
|
|
18
|
+
/* TODO: (typography-partial) font-weight override without paired font-size/line-height — revisit to confirm intended typography token. */
|
|
18
19
|
font-weight: 400 !important; /* default for button is medium, force regular to all page selection buttons */
|
|
19
20
|
|
|
20
21
|
&:disabled {
|
|
@@ -199,11 +199,11 @@ export const Tab = ({
|
|
|
199
199
|
const href = typeof to === 'string' ? to : createPath(to);
|
|
200
200
|
return (
|
|
201
201
|
<TabWrapper
|
|
202
|
+
data-test="tab"
|
|
202
203
|
{...props}
|
|
203
204
|
id={id}
|
|
204
205
|
to={to}
|
|
205
206
|
role="tab"
|
|
206
|
-
data-test="tab"
|
|
207
207
|
data-test-url={href}
|
|
208
208
|
className={clsx(className, { active, disabled })}
|
|
209
209
|
onClick={onSelect ? (event) => onSelect({ id, href, event }) : undefined}
|
|
@@ -45,6 +45,15 @@ async function buildTheme(theme) {
|
|
|
45
45
|
},
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
// Format to generate a plain CSS file with tokens inside a :root block
|
|
49
|
+
StyleDictionary.registerFormat({
|
|
50
|
+
name: 'css/custom-properties',
|
|
51
|
+
format: ({ dictionary }) => {
|
|
52
|
+
const lines = dictionary.allTokens.map((token) => ` --${token.name}: ${token.$value};`);
|
|
53
|
+
return `${DO_NOT_EXPORT_COMMENT}\n:root {\n${lines.join('\n')}\n}\n`;
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
|
|
48
57
|
// Format to generate typescript file that exports CSS variables as a string
|
|
49
58
|
// Example: export const tokens = `--color-neutral-text: #000; --color-primary: #fff;`
|
|
50
59
|
StyleDictionary.registerFormat({
|
|
@@ -165,6 +174,11 @@ async function buildTheme(theme) {
|
|
|
165
174
|
mapName: 'tokens',
|
|
166
175
|
filter: 'only-palette',
|
|
167
176
|
},
|
|
177
|
+
{
|
|
178
|
+
destination: `tokens.${theme}.css`,
|
|
179
|
+
format: 'css/custom-properties',
|
|
180
|
+
filter: 'only-colors',
|
|
181
|
+
},
|
|
168
182
|
],
|
|
169
183
|
},
|
|
170
184
|
},
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-vite';
|
|
2
|
+
import type { CSSProperties, ReactNode } from 'react';
|
|
3
|
+
|
|
4
|
+
import { theme } from '../theme.js';
|
|
5
|
+
import { cssColorsVariablesDark, cssColorsVariablesLight, darkTheme, lightTheme } from './index.js';
|
|
6
|
+
|
|
7
|
+
type ColorToken = {
|
|
8
|
+
name: string;
|
|
9
|
+
value: string;
|
|
10
|
+
variable: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
type PaletteColor = {
|
|
14
|
+
name: string;
|
|
15
|
+
shade: string;
|
|
16
|
+
value: string;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type SemanticColorSection = {
|
|
20
|
+
title: string;
|
|
21
|
+
prefix: string;
|
|
22
|
+
description: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const basePaletteFamilies = ['blue', 'green', 'yellow', 'red'];
|
|
26
|
+
const neutralPaletteFamilies = ['neutral'];
|
|
27
|
+
const decorativePaletteFamilies = [
|
|
28
|
+
'coral',
|
|
29
|
+
'lavender',
|
|
30
|
+
'bamboo',
|
|
31
|
+
'rose',
|
|
32
|
+
'buttercup',
|
|
33
|
+
'paprika',
|
|
34
|
+
'teal',
|
|
35
|
+
'indigo',
|
|
36
|
+
'slate',
|
|
37
|
+
];
|
|
38
|
+
const brandPaletteFamilies = ['magenta', 'mondo', 'lime', 'meteorite', 'logo'];
|
|
39
|
+
const semanticColorSections: SemanticColorSection[] = [
|
|
40
|
+
{
|
|
41
|
+
title: 'Neutral',
|
|
42
|
+
prefix: 'neutral',
|
|
43
|
+
description: 'Foundational semantic colors for text, icons, surfaces, borders, and controls.',
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: 'Primary',
|
|
47
|
+
prefix: 'primary',
|
|
48
|
+
description: 'Primary brand actions, selected states, interactive text, and blue-tinted surfaces.',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: 'PrimaryBlack',
|
|
52
|
+
prefix: 'primaryBlack',
|
|
53
|
+
description: 'High-emphasis black/white primary action tokens.',
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
title: 'Success',
|
|
57
|
+
prefix: 'success',
|
|
58
|
+
description: 'Positive status colors for success states, chips, borders, and actions.',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
title: 'Warning',
|
|
62
|
+
prefix: 'warning',
|
|
63
|
+
description: 'Cautionary status colors for warning states, chips, borders, and fields.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
title: 'Danger',
|
|
67
|
+
prefix: 'danger',
|
|
68
|
+
description: 'Critical status colors for destructive actions, errors, chips, and borders.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
title: 'Special',
|
|
72
|
+
prefix: 'special',
|
|
73
|
+
description: 'Special-purpose semantic colors for plans and highlights.',
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
const styles = {
|
|
78
|
+
page: { display: 'flex', flexDirection: 'column', gap: 40, padding: 24, color: theme.color.neutral.text },
|
|
79
|
+
section: { display: 'flex', flexDirection: 'column', gap: 20 },
|
|
80
|
+
paletteGroup: {
|
|
81
|
+
display: 'flex',
|
|
82
|
+
flexDirection: 'column',
|
|
83
|
+
gap: 20,
|
|
84
|
+
padding: 20,
|
|
85
|
+
border: `1px solid ${theme.color.neutral.border}`,
|
|
86
|
+
borderRadius: 10,
|
|
87
|
+
background: lightTheme.neutral0,
|
|
88
|
+
color: lightTheme.neutral850,
|
|
89
|
+
},
|
|
90
|
+
paletteGroupHeader: { display: 'flex', flexDirection: 'column', gap: 4 },
|
|
91
|
+
paletteGroupTitle: { margin: 0, fontSize: 20, fontWeight: 700, lineHeight: 1.2 },
|
|
92
|
+
paletteGroupDescription: { margin: 0, color: lightTheme.neutral500, fontSize: 12, lineHeight: 1.4 },
|
|
93
|
+
sectionTitle: {
|
|
94
|
+
margin: 0,
|
|
95
|
+
paddingBottom: 10,
|
|
96
|
+
borderBottom: `1px solid ${theme.color.neutral.border}`,
|
|
97
|
+
fontSize: 28,
|
|
98
|
+
fontWeight: 700,
|
|
99
|
+
lineHeight: 1.2,
|
|
100
|
+
},
|
|
101
|
+
themeBlock: { display: 'flex', flexDirection: 'column', gap: 20 },
|
|
102
|
+
themeBlockDark: { padding: 24, borderRadius: 8, background: darkTheme.neutral900, color: darkTheme.neutral50 },
|
|
103
|
+
themeTitle: { margin: 0, fontSize: 22, fontWeight: 700, lineHeight: 1.2 },
|
|
104
|
+
neutralThemeGrid: { display: 'grid', gridTemplateColumns: 'repeat(2, minmax(0, 1fr))', gap: 32 },
|
|
105
|
+
paletteGrid: { display: 'grid', gridTemplateColumns: 'repeat(4, minmax(160px, 1fr))', gap: '28px 40px' },
|
|
106
|
+
paletteColumn: { display: 'flex', flexDirection: 'column', gap: 10, minWidth: 0 },
|
|
107
|
+
paletteColumnTitle: { margin: 0, fontSize: 18, fontWeight: 700, lineHeight: 1.2 },
|
|
108
|
+
paletteRow: { display: 'grid', gridTemplateColumns: '92px minmax(0, 1fr)', gap: 10, alignItems: 'center' },
|
|
109
|
+
paletteSwatch: { height: 44, borderRadius: 8 },
|
|
110
|
+
paletteMeta: { display: 'flex', flexDirection: 'column', minWidth: 0, fontSize: 12, lineHeight: 1.25 },
|
|
111
|
+
paletteShade: { fontSize: 16, fontWeight: 700 },
|
|
112
|
+
semanticGrid: { display: 'grid', gridTemplateColumns: '1fr 1fr' },
|
|
113
|
+
semanticPanel: {
|
|
114
|
+
display: 'flex',
|
|
115
|
+
flexDirection: 'column',
|
|
116
|
+
gap: 10,
|
|
117
|
+
padding: 24,
|
|
118
|
+
background: lightTheme.neutral0,
|
|
119
|
+
color: lightTheme.neutral850,
|
|
120
|
+
},
|
|
121
|
+
semanticPanelDark: { borderRadius: 8, background: darkTheme.neutral900, color: darkTheme.neutral50 },
|
|
122
|
+
semanticTokenRow: { display: 'grid', gridTemplateColumns: '64px minmax(0, 1fr)', gap: 10, alignItems: 'center' },
|
|
123
|
+
semanticTokenSwatch: { height: 28, borderRadius: 6 },
|
|
124
|
+
tokenMeta: {
|
|
125
|
+
display: 'flex',
|
|
126
|
+
flexDirection: 'column',
|
|
127
|
+
gap: 2,
|
|
128
|
+
marginTop: 6,
|
|
129
|
+
minWidth: 0,
|
|
130
|
+
fontSize: 11,
|
|
131
|
+
lineHeight: 1.25,
|
|
132
|
+
},
|
|
133
|
+
paletteName: { overflow: 'hidden', fontWeight: 700, textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
|
|
134
|
+
colorValue: {
|
|
135
|
+
overflow: 'hidden',
|
|
136
|
+
color: lightTheme.neutral500,
|
|
137
|
+
fontFamily: 'monospace',
|
|
138
|
+
textOverflow: 'ellipsis',
|
|
139
|
+
whiteSpace: 'nowrap',
|
|
140
|
+
},
|
|
141
|
+
} satisfies Record<string, CSSProperties>;
|
|
142
|
+
|
|
143
|
+
const parseColorTokens = (tokens: string): ColorToken[] =>
|
|
144
|
+
tokens
|
|
145
|
+
.trim()
|
|
146
|
+
.split('\n')
|
|
147
|
+
.map((line) => line.trim().match(/^(--color-([\w-]+)):\s*(.+);$/))
|
|
148
|
+
.filter((match): match is RegExpMatchArray => Boolean(match))
|
|
149
|
+
.map(([, variable, path, value]) => ({
|
|
150
|
+
name: path.replace(/-([a-z0-9])/g, (_, char: string) => char.toUpperCase()),
|
|
151
|
+
value,
|
|
152
|
+
variable,
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
const lightColors = parseColorTokens(cssColorsVariablesLight);
|
|
156
|
+
const darkColors = parseColorTokens(cssColorsVariablesDark);
|
|
157
|
+
|
|
158
|
+
const formatLabel = (name: string) =>
|
|
159
|
+
name
|
|
160
|
+
.replace(/([a-z])([A-Z0-9])/g, '$1 $2')
|
|
161
|
+
.replace(/^./, (char) => char.toUpperCase())
|
|
162
|
+
.toLowerCase()
|
|
163
|
+
.replace(/^./, (char) => char.toUpperCase());
|
|
164
|
+
|
|
165
|
+
const isColorInPrefix = (name: string, prefix: string) =>
|
|
166
|
+
name === prefix || name.startsWith(`${prefix}${name[prefix.length]?.toUpperCase() ?? ''}`);
|
|
167
|
+
|
|
168
|
+
const getColorDisplayName = (name: string, prefix: string) => {
|
|
169
|
+
const displayName = name.slice(prefix.length);
|
|
170
|
+
|
|
171
|
+
return formatLabel(displayName ? `${displayName[0].toLowerCase()}${displayName.slice(1)}` : name);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const getPaletteNameByValue = (value: string, palette: Record<string, string>) => {
|
|
175
|
+
const paletteColor = Object.entries(palette).find(
|
|
176
|
+
([, paletteValue]) => paletteValue.toLowerCase() === value.toLowerCase(),
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return paletteColor ? formatLabel(paletteColor[0]) : 'Color';
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
const getColorSectionItems = (colors: ColorToken[], prefix: string) =>
|
|
183
|
+
colors
|
|
184
|
+
.filter(({ name }) => isColorInPrefix(name, prefix))
|
|
185
|
+
.sort((colorA, colorB) => colorA.name.localeCompare(colorB.name));
|
|
186
|
+
|
|
187
|
+
const getPaletteFamily = (name: string) => name.match(/^[a-z]+/)?.[0] ?? name;
|
|
188
|
+
|
|
189
|
+
const getPaletteShade = (name: string) => {
|
|
190
|
+
const shade = name.slice(getPaletteFamily(name).length);
|
|
191
|
+
|
|
192
|
+
return shade || name;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const getPaletteGroups = (palette: Record<string, string>) =>
|
|
196
|
+
Object.entries(palette).reduce<Record<string, PaletteColor[]>>((groups, [name, value]) => {
|
|
197
|
+
const family = getPaletteFamily(name);
|
|
198
|
+
const familyColors = [...(groups[family] ?? []), { name, shade: getPaletteShade(name), value }].sort(
|
|
199
|
+
(colorA, colorB) => colorA.name.localeCompare(colorB.name, undefined, { numeric: true }),
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
return { ...groups, [family]: familyColors };
|
|
203
|
+
}, {});
|
|
204
|
+
|
|
205
|
+
const getPaletteGroupsByFamily = (palette: Record<string, string>, families: string[]) => {
|
|
206
|
+
const groups = getPaletteGroups(palette);
|
|
207
|
+
|
|
208
|
+
return families.flatMap((family) => (groups[family] ? [[family, groups[family]] as const] : []));
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const Section = ({ title, children }: { title: string; children?: ReactNode }) => (
|
|
212
|
+
<section style={styles.section}>
|
|
213
|
+
<h2 style={styles.sectionTitle}>{title}</h2>
|
|
214
|
+
{children}
|
|
215
|
+
</section>
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const PaletteColumn = ({ family, colors }: { family: string; colors: PaletteColor[] }) => (
|
|
219
|
+
<div style={styles.paletteColumn}>
|
|
220
|
+
<h3 style={styles.paletteColumnTitle}>{formatLabel(family)}</h3>
|
|
221
|
+
{colors.map(({ name, shade, value }) => (
|
|
222
|
+
<div key={name} style={styles.paletteRow}>
|
|
223
|
+
<div style={{ ...styles.paletteSwatch, backgroundColor: value }} />
|
|
224
|
+
<div style={styles.paletteMeta}>
|
|
225
|
+
<span style={styles.paletteShade}>{shade}</span>
|
|
226
|
+
<span style={styles.colorValue} title={value}>
|
|
227
|
+
{value}
|
|
228
|
+
</span>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
))}
|
|
232
|
+
</div>
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
const PaletteFamilyGrid = ({ families, palette }: { families: string[]; palette: Record<string, string> }) => (
|
|
236
|
+
<div style={styles.paletteGrid}>
|
|
237
|
+
{getPaletteGroupsByFamily(palette, families).map(([family, colors]) => (
|
|
238
|
+
<PaletteColumn key={family} family={family} colors={colors} />
|
|
239
|
+
))}
|
|
240
|
+
</div>
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const PaletteTheme = ({
|
|
244
|
+
families,
|
|
245
|
+
isDark = false,
|
|
246
|
+
palette,
|
|
247
|
+
title,
|
|
248
|
+
}: {
|
|
249
|
+
families: string[];
|
|
250
|
+
isDark?: boolean;
|
|
251
|
+
palette: Record<string, string>;
|
|
252
|
+
title: string;
|
|
253
|
+
}) => (
|
|
254
|
+
<div style={isDark ? { ...styles.themeBlock, ...styles.themeBlockDark } : styles.themeBlock}>
|
|
255
|
+
<h3 style={styles.themeTitle}>{title}</h3>
|
|
256
|
+
<PaletteFamilyGrid families={families} palette={palette} />
|
|
257
|
+
</div>
|
|
258
|
+
);
|
|
259
|
+
|
|
260
|
+
const PaletteGroup = ({
|
|
261
|
+
children,
|
|
262
|
+
description,
|
|
263
|
+
title,
|
|
264
|
+
}: {
|
|
265
|
+
children?: ReactNode;
|
|
266
|
+
description?: string;
|
|
267
|
+
title: string;
|
|
268
|
+
}) => (
|
|
269
|
+
<div style={styles.paletteGroup}>
|
|
270
|
+
<div style={styles.paletteGroupHeader}>
|
|
271
|
+
<h3 style={styles.paletteGroupTitle}>{title}</h3>
|
|
272
|
+
{description ? <p style={styles.paletteGroupDescription}>{description}</p> : null}
|
|
273
|
+
</div>
|
|
274
|
+
{children}
|
|
275
|
+
</div>
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const ColorTokenRow = ({
|
|
279
|
+
color,
|
|
280
|
+
palette,
|
|
281
|
+
name,
|
|
282
|
+
}: {
|
|
283
|
+
color: ColorToken;
|
|
284
|
+
palette: Record<string, string>;
|
|
285
|
+
name: string;
|
|
286
|
+
}) => {
|
|
287
|
+
const paletteName = getPaletteNameByValue(color.value, palette);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div style={styles.semanticTokenRow}>
|
|
291
|
+
<div style={{ ...styles.semanticTokenSwatch, backgroundColor: color.value }} />
|
|
292
|
+
<div style={styles.tokenMeta}>
|
|
293
|
+
<span style={styles.paletteShade}>{name}</span>
|
|
294
|
+
<span style={styles.paletteName} title={paletteName}>
|
|
295
|
+
{paletteName}
|
|
296
|
+
</span>
|
|
297
|
+
<span style={styles.colorValue} title={color.value}>
|
|
298
|
+
{color.value}
|
|
299
|
+
</span>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
);
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const PaletteSection = () => (
|
|
306
|
+
<Section title="Palette">
|
|
307
|
+
<PaletteGroup
|
|
308
|
+
title="Base palette"
|
|
309
|
+
description="Base palette scales for product UI. Use semantic colors for implementation."
|
|
310
|
+
>
|
|
311
|
+
<PaletteTheme families={basePaletteFamilies} palette={lightTheme} title="Light" />
|
|
312
|
+
<PaletteTheme families={basePaletteFamilies} isDark palette={darkTheme} title="Dark" />
|
|
313
|
+
</PaletteGroup>
|
|
314
|
+
<PaletteGroup title="Neutrals" description="Neutral scales for text, surfaces, borders, and structure.">
|
|
315
|
+
<div style={styles.neutralThemeGrid}>
|
|
316
|
+
<PaletteTheme families={neutralPaletteFamilies} palette={lightTheme} title="Light" />
|
|
317
|
+
<PaletteTheme families={neutralPaletteFamilies} isDark palette={darkTheme} title="Dark" />
|
|
318
|
+
</div>
|
|
319
|
+
</PaletteGroup>
|
|
320
|
+
<PaletteGroup
|
|
321
|
+
title="Decorative"
|
|
322
|
+
description="Accent colors for charts and illustrations. Do not use for semantic states."
|
|
323
|
+
>
|
|
324
|
+
<PaletteTheme families={decorativePaletteFamilies} palette={lightTheme} title="Light" />
|
|
325
|
+
<PaletteTheme families={decorativePaletteFamilies} isDark palette={darkTheme} title="Dark" />
|
|
326
|
+
</PaletteGroup>
|
|
327
|
+
<PaletteGroup
|
|
328
|
+
title="Brand"
|
|
329
|
+
description="Brand accent colors for charts and illustrations. Do not use for semantic states."
|
|
330
|
+
>
|
|
331
|
+
<PaletteFamilyGrid families={brandPaletteFamilies} palette={lightTheme} />
|
|
332
|
+
</PaletteGroup>
|
|
333
|
+
</Section>
|
|
334
|
+
);
|
|
335
|
+
|
|
336
|
+
const SemanticTokenPanel = ({
|
|
337
|
+
colors,
|
|
338
|
+
isDark = false,
|
|
339
|
+
palette,
|
|
340
|
+
prefix,
|
|
341
|
+
title,
|
|
342
|
+
}: {
|
|
343
|
+
colors: ColorToken[];
|
|
344
|
+
isDark?: boolean;
|
|
345
|
+
palette: Record<string, string>;
|
|
346
|
+
prefix: string;
|
|
347
|
+
title: string;
|
|
348
|
+
}) => (
|
|
349
|
+
<div style={isDark ? { ...styles.semanticPanel, ...styles.semanticPanelDark } : styles.semanticPanel}>
|
|
350
|
+
<h3 style={styles.paletteColumnTitle}>{title}</h3>
|
|
351
|
+
{getColorSectionItems(colors, prefix).map((color) => (
|
|
352
|
+
<ColorTokenRow
|
|
353
|
+
key={color.variable}
|
|
354
|
+
color={color}
|
|
355
|
+
name={getColorDisplayName(color.name, prefix)}
|
|
356
|
+
palette={palette}
|
|
357
|
+
/>
|
|
358
|
+
))}
|
|
359
|
+
</div>
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
const SemanticTokenGroup = ({ description, prefix, title }: SemanticColorSection) => (
|
|
363
|
+
<PaletteGroup description={description} title={title}>
|
|
364
|
+
<div style={styles.semanticGrid}>
|
|
365
|
+
<SemanticTokenPanel colors={lightColors} palette={lightTheme} prefix={prefix} title="Light" />
|
|
366
|
+
<SemanticTokenPanel colors={darkColors} isDark palette={darkTheme} prefix={prefix} title="Dark" />
|
|
367
|
+
</div>
|
|
368
|
+
</PaletteGroup>
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
const SemanticSection = () => (
|
|
372
|
+
<Section title="Semantic">
|
|
373
|
+
{semanticColorSections.map((section) => (
|
|
374
|
+
<SemanticTokenGroup key={section.prefix} {...section} />
|
|
375
|
+
))}
|
|
376
|
+
</Section>
|
|
377
|
+
);
|
|
378
|
+
|
|
379
|
+
const ColorsShowcase = () => (
|
|
380
|
+
<div style={styles.page}>
|
|
381
|
+
<PaletteSection />
|
|
382
|
+
<SemanticSection />
|
|
383
|
+
</div>
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
export default {
|
|
387
|
+
title: 'Design Tokens/Colors',
|
|
388
|
+
component: ColorsShowcase,
|
|
389
|
+
} as Meta<typeof ColorsShowcase>;
|
|
390
|
+
|
|
391
|
+
type Story = StoryObj<typeof ColorsShowcase>;
|
|
392
|
+
|
|
393
|
+
export const Default: Story = {
|
|
394
|
+
render: () => <ColorsShowcase />,
|
|
395
|
+
};
|