@scattered-light/loomiere 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -2
- package/bin/loomiere.mjs +112 -0
- package/build/build-tokens.mjs +485 -0
- package/exporters/README.md +45 -0
- package/exporters/ag-grid-settings.mjs +177 -0
- package/exporters/ag-grid.mjs +112 -0
- package/exporters/chakra.mjs +630 -0
- package/exporters/component-styles.mjs +207 -0
- package/exporters/dtcg.mjs +31 -0
- package/exporters/figma.mjs +100 -0
- package/exporters/index.mjs +38 -0
- package/exporters/mui.mjs +666 -0
- package/exporters/tailwind.mjs +35 -0
- package/exporters/theme.mjs +93 -0
- package/generator/brand.mjs +328 -0
- package/generator/brand.schema.json +657 -0
- package/generator/color.mjs +130 -0
- package/generator/data-colors.mjs +104 -0
- package/generator/format-brand.mjs +60 -0
- package/generator/generate.mjs +43 -0
- package/generator/pairs.mjs +236 -0
- package/generator/roles.mjs +258 -0
- package/generator/vision.mjs +167 -0
- package/lib/config.mjs +71 -0
- package/lib/contrast.mjs +134 -0
- package/lib/lab.mjs +108 -0
- package/lib/studio.mjs +127 -0
- package/lib/tool-server.mjs +96 -0
- package/package.json +51 -4
- package/templates/brand.json +104 -0
- package/tokens/README.md +219 -0
- package/tokens/component/avatar.json +10 -0
- package/tokens/component/button.json +12 -0
- package/tokens/component/drawer.json +26 -0
- package/tokens/component/navigation-menu.json +13 -0
- package/tokens/component/popover.json +13 -0
- package/tokens/component/preview-card.json +13 -0
- package/tokens/component/slider.json +13 -0
- package/tokens/component/switch.json +41 -0
- package/tokens/component/toast.json +21 -0
- package/tokens/component/toggle.json +9 -0
- package/tokens/component/toolbar.json +9 -0
- package/tokens/component/tooltip.json +9 -0
- package/tokens/foundation/layering.json +57 -0
- package/tokens/foundation/layout.json +75 -0
- package/tokens/foundation/motion.json +41 -0
- package/tokens/foundation/type.json +92 -0
- package/tokens/semantic/accessibility.json +39 -0
- package/tokens/semantic/bar.json +45 -0
- package/tokens/semantic/check.json +57 -0
- package/tokens/semantic/color.json +270 -0
- package/tokens/semantic/content.json +124 -0
- package/tokens/semantic/disclosure.json +49 -0
- package/tokens/semantic/elevation.json +72 -0
- package/tokens/semantic/field.json +77 -0
- package/tokens/semantic/focus.json +36 -0
- package/tokens/semantic/gauge.json +45 -0
- package/tokens/semantic/icon-button.json +33 -0
- package/tokens/semantic/icon.json +30 -0
- package/tokens/semantic/item.json +65 -0
- package/tokens/semantic/modal.json +86 -0
- package/tokens/semantic/motion.json +20 -0
- package/tokens/semantic/notice.json +37 -0
- package/tokens/semantic/popup.json +89 -0
- package/tokens/semantic/scheme.json +11 -0
- package/tokens/semantic/shape.json +54 -0
- package/tokens/semantic/spacing.json +110 -0
- package/tokens/semantic/table.json +73 -0
- package/tokens/semantic/typography.json +312 -0
- package/tools/brand-builder/builder.js +1119 -0
- package/tools/brand-builder/index.html +329 -0
- package/tools/pair-lab/index.html +119 -0
- package/tools/pair-lab/lab.js +249 -0
|
@@ -0,0 +1,630 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chakra UI v3 system: every brand palette as a Chakra color palette, plus recipe overrides
|
|
3
|
+
* that give Chakra's components the same look as loomiere-base-ui: Button variants are the
|
|
4
|
+
* brand's emphasis levels, inputs are the field concept, menus are popups and rows, and so
|
|
5
|
+
* on. Tokens and recipes read the theme's --sl- variables, so load tokens.css too.
|
|
6
|
+
* import { ChakraProvider } from '@chakra-ui/react';
|
|
7
|
+
* import { system } from './theme/exports/chakra.js';
|
|
8
|
+
* <ChakraProvider value={system}>…</ChakraProvider>
|
|
9
|
+
*/
|
|
10
|
+
import { STYLES } from '../generator/roles.mjs';
|
|
11
|
+
import { styleKit, toSource } from './component-styles.mjs';
|
|
12
|
+
|
|
13
|
+
/** Chakra size → control tier. */
|
|
14
|
+
const TIER = { '2xs': 'xs', xs: 'xs', sm: 'sm', md: 'md', lg: 'lg', xl: 'lg', '2xl': 'lg' };
|
|
15
|
+
/** Button variant → emphasis level, as data-variant does for the Base UI Button. */
|
|
16
|
+
const VARIANT_LEVEL = { solid: 'bold', outline: 'standard', subtle: 'subtle', surface: 'subtle', ghost: 'quiet', plain: 'quiet' };
|
|
17
|
+
/** Chakra's own color-palette keys → scope slots, so Chakra components we leave alone still fit. */
|
|
18
|
+
const CHAKRA_SLOTS = { contrast: 'on-solid', subtle: 'soft', muted: 'soft-hover', emphasized: 'soft-active', focusRing: 'focus' };
|
|
19
|
+
const STATUS = ['info', 'warning', 'success', 'error'];
|
|
20
|
+
/** Text styles a Chakra user can name as textStyle="…". */
|
|
21
|
+
const TEXT_STYLES = [
|
|
22
|
+
'minimize', 'condense', 'base', 'magnify', 'base-label', 'base-selected', 'condense-subtitle-label',
|
|
23
|
+
'condense-trigger', 'base-trigger', 'magnify-trigger', 'maximize-md', 'maximize-lg', 'maximize-xl', 'maximize-2xl', 'maximize-3xl', 'maximize-4xl',
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** Merges our recipe pieces into Chakra's own, in the generated file (see RESTYLE below). */
|
|
27
|
+
const RESTYLE = `/**
|
|
28
|
+
* Our recipe pieces merged into Chakra's own: a base style merges key by key into
|
|
29
|
+
* Chakra's (null removes a key), and a variant's style replaces Chakra's for that slot.
|
|
30
|
+
* Anything we leave out keeps Chakra's style.
|
|
31
|
+
*/
|
|
32
|
+
const merge = (a = {}, b) => {
|
|
33
|
+
const out = { ...a, ...b };
|
|
34
|
+
for (const key of Object.keys(out)) if (out[key] === null) delete out[key];
|
|
35
|
+
return out;
|
|
36
|
+
};
|
|
37
|
+
function restyle(recipe, ours) {
|
|
38
|
+
const slotted = Boolean(recipe.slots);
|
|
39
|
+
const base = !ours.base ? recipe.base : slotted ? Object.fromEntries([...new Set([...Object.keys(recipe.base ?? {}), ...Object.keys(ours.base)])].map((slot) => [slot, merge(recipe.base?.[slot], ours.base[slot] ?? {})])) : merge(recipe.base, ours.base);
|
|
40
|
+
const variants = { ...recipe.variants };
|
|
41
|
+
for (const [group, values] of Object.entries(ours.variants ?? {})) {
|
|
42
|
+
variants[group] = { ...recipe.variants?.[group] };
|
|
43
|
+
for (const [value, style] of Object.entries(values)) variants[group][value] = slotted ? { ...recipe.variants?.[group]?.[value], ...style } : style;
|
|
44
|
+
}
|
|
45
|
+
return { ...recipe, base, variants, compoundVariants: ours.compoundVariants ?? recipe.compoundVariants ?? [], defaultVariants: { ...recipe.defaultVariants, ...ours.defaultVariants } };
|
|
46
|
+
}
|
|
47
|
+
const restyleAll = (defaults, ours) => ({ ...defaults, ...Object.fromEntries(Object.entries(ours).map(([name, r]) => [name, restyle(defaults[name], r)])) });`;
|
|
48
|
+
|
|
49
|
+
export default {
|
|
50
|
+
id: 'chakra',
|
|
51
|
+
name: 'Chakra UI v3',
|
|
52
|
+
file: 'chakra.js',
|
|
53
|
+
for: 'React apps on @chakra-ui/react v3: pass the exported system to ChakraProvider, and load tokens.css.',
|
|
54
|
+
build(theme) {
|
|
55
|
+
const { scopes, palettes, roles } = theme;
|
|
56
|
+
const prefix = theme.prefix ?? 'sl';
|
|
57
|
+
const k = styleKit(prefix, roles);
|
|
58
|
+
const { v, text } = k;
|
|
59
|
+
const sizes = (names, fn) => Object.fromEntries(names.map((s) => [s, fn(TIER[s], s)]));
|
|
60
|
+
|
|
61
|
+
// Colors: each palette's steps, and each scope's slots as a Chakra color palette.
|
|
62
|
+
const value = (x) => ({ value: x });
|
|
63
|
+
const colors = Object.fromEntries(Object.entries(palettes).map(([name, steps]) => [name, Object.fromEntries(Object.entries(steps).map(([s, hex]) => [s, value(hex)]))]));
|
|
64
|
+
const scopeColors = Object.fromEntries(
|
|
65
|
+
Object.entries(scopes).map(([name, s]) => {
|
|
66
|
+
const slots = Object.keys(s.light);
|
|
67
|
+
return [
|
|
68
|
+
name,
|
|
69
|
+
{
|
|
70
|
+
...Object.fromEntries(slots.map((slot) => [slot, value(k.scope(name, slot))])),
|
|
71
|
+
...Object.fromEntries(Object.entries(CHAKRA_SLOTS).map(([key, slot]) => [key, value(k.scope(name, slot))])),
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
// Chakra's own semantic colors, pointed at ours, so the components we leave alone match too.
|
|
77
|
+
const semanticColors = {
|
|
78
|
+
...scopeColors,
|
|
79
|
+
bg: {
|
|
80
|
+
DEFAULT: value(v('surface-canvas')),
|
|
81
|
+
subtle: value(v('surface-raised')),
|
|
82
|
+
muted: value(v('surface-overlay')),
|
|
83
|
+
emphasized: value(v('fill-default')),
|
|
84
|
+
panel: value(v('popup-background')),
|
|
85
|
+
inverted: value(v('surface-inverse')),
|
|
86
|
+
...Object.fromEntries(STATUS.map((s) => [s, value(k.scope(s, 'soft'))])),
|
|
87
|
+
},
|
|
88
|
+
fg: {
|
|
89
|
+
DEFAULT: value(v('text-primary')),
|
|
90
|
+
muted: value(v('text-secondary')),
|
|
91
|
+
subtle: value(v('text-tertiary')),
|
|
92
|
+
inverted: value(v('text-on-inverse-surface')),
|
|
93
|
+
...Object.fromEntries(STATUS.map((s) => [s, value(k.scope(s, 'fg'))])),
|
|
94
|
+
},
|
|
95
|
+
border: {
|
|
96
|
+
DEFAULT: value(v('border-default')),
|
|
97
|
+
muted: value(v('border-subtle')),
|
|
98
|
+
subtle: value(v('border-subtle')),
|
|
99
|
+
emphasized: value(v('border-strong')),
|
|
100
|
+
inverted: value(v('surface-canvas')),
|
|
101
|
+
...Object.fromEntries(STATUS.map((s) => [s, value(k.scope(s, 'border'))])),
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// An emphasis level in Chakra's colorPalette, so colorPalette="…" re-colors it the way
|
|
106
|
+
// data-color re-colors a Base UI Button. Without one the level keeps its own color.
|
|
107
|
+
const palette = (slot) => (slot === 'transparent' ? 'transparent' : `colorPalette.${slot}`);
|
|
108
|
+
const paletteVar = (slot) => `var(--chakra-colors-color-palette-${slot})`;
|
|
109
|
+
const level = (name) => {
|
|
110
|
+
const { style, color } = roles.emphasis[name];
|
|
111
|
+
const s = STYLES[style];
|
|
112
|
+
const rest = { bg: palette(s.bg), color: palette(s.fg), borderColor: palette(s.border) };
|
|
113
|
+
return {
|
|
114
|
+
colorPalette: color,
|
|
115
|
+
...rest,
|
|
116
|
+
_hover: { bg: palette(s['bg-hover']), borderColor: palette(s['border-hover']) },
|
|
117
|
+
_active: { bg: palette(s['bg-active']) },
|
|
118
|
+
_focusVisible: {
|
|
119
|
+
outline: `${v('a11y-focus-outline-width')} solid ${paletteVar('focus')}`,
|
|
120
|
+
outlineOffset: v('a11y-focus-outline-offset'),
|
|
121
|
+
boxShadow: `0 0 0 ${v('focus-halo-spread')} ${paletteVar('soft-hover')}`,
|
|
122
|
+
},
|
|
123
|
+
_disabled: { ...rest, opacity: v('button-opacity-disabled'), cursor: 'not-allowed' },
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
const control = (tier, opts) => ({ ...k.control(tier, opts), textStyle: null, minW: 0, px: null, h: null });
|
|
127
|
+
|
|
128
|
+
const field = {
|
|
129
|
+
...k.field,
|
|
130
|
+
...text('base'),
|
|
131
|
+
focusVisibleRing: null,
|
|
132
|
+
focusRingColor: null,
|
|
133
|
+
_hover: k.fieldStates.hover,
|
|
134
|
+
_focus: k.fieldStates.focus,
|
|
135
|
+
_invalid: { ...k.fieldStates.error, _focus: k.fieldStates.errorFocus },
|
|
136
|
+
_disabled: { ...k.fieldStates.disabled, layerStyle: null },
|
|
137
|
+
_placeholder: k.fieldStates.placeholder,
|
|
138
|
+
};
|
|
139
|
+
const fieldSize = (tier) => ({
|
|
140
|
+
...text(tier === 'xs' || tier === 'sm' ? 'condense' : tier === 'lg' ? 'magnify' : 'base'),
|
|
141
|
+
textStyle: null,
|
|
142
|
+
'--input-height': v(`control-height-${tier}`),
|
|
143
|
+
height: v(`control-height-${tier}`),
|
|
144
|
+
px: v(`control-padding-inline${tier === 'md' ? '' : `-${tier}`}`),
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
const popup = { ...k.popupSurface, bg: null, '--menu-bg': v('popup-background') };
|
|
148
|
+
const row = {
|
|
149
|
+
...k.itemRow,
|
|
150
|
+
textStyle: null,
|
|
151
|
+
cursor: 'default',
|
|
152
|
+
_highlighted: k.itemStates.hover,
|
|
153
|
+
_disabled: { ...k.itemStates.disabled, layerStyle: null },
|
|
154
|
+
};
|
|
155
|
+
const groupLabel = { ...k.groupLabel, textStyle: null };
|
|
156
|
+
const listParts = {
|
|
157
|
+
content: { ...popup, borderRadius: v('popup-radius-field'), padding: v('popup-padding'), boxShadow: v('popup-shadow') },
|
|
158
|
+
item: { ...row, '&[data-state=checked]': k.itemStates.selected },
|
|
159
|
+
itemGroupLabel: groupLabel,
|
|
160
|
+
itemIndicator: { color: v('item-indicator-color') },
|
|
161
|
+
};
|
|
162
|
+
const clearListSizes = (names, extra = () => ({})) => sizes(names, (tier) => ({ content: {}, item: {}, itemGroupLabel: {}, ...extra(tier) }));
|
|
163
|
+
|
|
164
|
+
const statusPalette = (s) => (scopes[s] ? s : 'chrome');
|
|
165
|
+
|
|
166
|
+
const recipes = {
|
|
167
|
+
button: {
|
|
168
|
+
base: {
|
|
169
|
+
...control('md', { trigger: true }),
|
|
170
|
+
// Longhands: Chakra drops a variant's borderColor when the base has the shorthand.
|
|
171
|
+
borderWidth: v('stroke-default'),
|
|
172
|
+
borderStyle: 'solid',
|
|
173
|
+
borderColor: 'transparent',
|
|
174
|
+
borderRadius: v('button-radius'),
|
|
175
|
+
transition: v('transition-button'),
|
|
176
|
+
transitionProperty: null,
|
|
177
|
+
transitionDuration: null,
|
|
178
|
+
focusVisibleRing: null,
|
|
179
|
+
_disabled: null,
|
|
180
|
+
_icon: { flexShrink: 0, width: k.iconSize('md'), height: k.iconSize('md') },
|
|
181
|
+
},
|
|
182
|
+
variants: {
|
|
183
|
+
variant: Object.fromEntries(Object.entries(VARIANT_LEVEL).map(([variant, name]) => [variant, level(name)])),
|
|
184
|
+
size: sizes(['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'], (tier) => ({ ...control(tier, { trigger: true }), _icon: { width: k.iconSize(tier), height: k.iconSize(tier) } })),
|
|
185
|
+
},
|
|
186
|
+
},
|
|
187
|
+
input: {
|
|
188
|
+
base: { ...field, height: v('control-height-md'), minW: 0 },
|
|
189
|
+
variants: { size: sizes(['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'], fieldSize), variant: { outline: {} } },
|
|
190
|
+
},
|
|
191
|
+
textarea: {
|
|
192
|
+
base: { ...field, minHeight: '80px', paddingBlock: v('control-padding-block'), px: v('control-padding-inline') },
|
|
193
|
+
variants: { size: sizes(['xs', 'sm', 'md', 'lg', 'xl'], () => ({})), variant: { outline: {} } },
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const checkControl = (tier) => ({ boxSize: k.iconSize(tier), '& svg': { width: v(`icon-size-${{ xs: 'minimize', sm: 'minimize', md: 'condense', lg: 'base' }[tier]}`), height: v(`icon-size-${{ xs: 'minimize', sm: 'minimize', md: 'condense', lg: 'base' }[tier]}`) } });
|
|
198
|
+
const checkRow = { gap: v('gap-control'), color: v('text-primary') };
|
|
199
|
+
const label = { ...text('base'), textStyle: null, _disabled: { color: v('text-disabled'), opacity: null } };
|
|
200
|
+
const checked = '&:is([data-state=checked], [data-state=indeterminate])';
|
|
201
|
+
|
|
202
|
+
const slotRecipes = {
|
|
203
|
+
field: {
|
|
204
|
+
base: {
|
|
205
|
+
root: { gap: v('field-gap') },
|
|
206
|
+
// A disabled field fades as a whole, as a disabled Base UI Field.Root does.
|
|
207
|
+
label: { ...text('base-label'), textStyle: null, gap: v('space-1'), marginBottom: v('field-label-spacing'), color: v('text-primary'), _disabled: { opacity: v('field-opacity-disabled') } },
|
|
208
|
+
helperText: { ...text('condense'), textStyle: null, margin: `${v('field-message-spacing')} 0 0`, color: v('text-secondary'), _disabled: { opacity: v('field-opacity-disabled') } },
|
|
209
|
+
errorText: { ...text('base-label'), textStyle: null, margin: `${v('field-message-spacing')} 0 0`, color: v('status-error') },
|
|
210
|
+
requiredIndicator: { color: v('status-error') },
|
|
211
|
+
},
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
checkbox: {
|
|
215
|
+
base: {
|
|
216
|
+
root: checkRow,
|
|
217
|
+
control: {
|
|
218
|
+
...k.checkBox,
|
|
219
|
+
...checkControl('md'),
|
|
220
|
+
borderRadius: v('check-radius'),
|
|
221
|
+
p: 0,
|
|
222
|
+
focusVisibleRing: null,
|
|
223
|
+
_hover: k.checkStates.hover,
|
|
224
|
+
[checked]: k.checkStates.checked,
|
|
225
|
+
_focusVisible: k.focusRing,
|
|
226
|
+
_invalid: { borderColor: v('field-border-color-error'), colorPalette: null },
|
|
227
|
+
_disabled: { ...k.checkStates.disabled, cursor: 'not-allowed' },
|
|
228
|
+
},
|
|
229
|
+
label,
|
|
230
|
+
},
|
|
231
|
+
variants: {
|
|
232
|
+
variant: { solid: { control: {} }, outline: { control: {} }, subtle: { control: {} } },
|
|
233
|
+
size: sizes(['xs', 'sm', 'md', 'lg'], (tier) => ({ root: { gap: v(tier === 'xs' ? 'gap-control-xs' : tier === 'sm' ? 'gap-control-sm' : 'gap-control') }, label: {}, control: checkControl(tier) })),
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
radioGroup: {
|
|
237
|
+
base: {
|
|
238
|
+
root: { display: 'flex', gap: v('gap-stack-sm') },
|
|
239
|
+
item: { ...checkRow, fontWeight: null },
|
|
240
|
+
itemText: label,
|
|
241
|
+
label,
|
|
242
|
+
itemControl: {
|
|
243
|
+
...k.checkBox,
|
|
244
|
+
boxSize: k.iconSize('md'),
|
|
245
|
+
borderRadius: v('radius-pill'),
|
|
246
|
+
_focusVisible: k.focusRing,
|
|
247
|
+
_hover: k.checkStates.hover,
|
|
248
|
+
_checked: k.checkStates.checked,
|
|
249
|
+
_invalid: { borderColor: v('field-border-color-error'), colorPalette: null },
|
|
250
|
+
_disabled: { ...k.checkStates.disabled, cursor: 'not-allowed' },
|
|
251
|
+
'& .dot': { width: `round(100% * ${v('check-dot-scale')}, 1px)`, height: `round(100% * ${v('check-dot-scale')}, 1px)`, scale: null, borderRadius: v('radius-pill'), bg: v('check-mark-color') },
|
|
252
|
+
'&:not([data-state=checked]) .dot': { display: 'none' },
|
|
253
|
+
},
|
|
254
|
+
},
|
|
255
|
+
variants: {
|
|
256
|
+
variant: { solid: { itemControl: {} }, outline: { itemControl: {} }, subtle: { itemControl: {} } },
|
|
257
|
+
size: sizes(['xs', 'sm', 'md', 'lg'], (tier) => ({ item: { gap: v(tier === 'xs' ? 'gap-control-xs' : tier === 'sm' ? 'gap-control-sm' : 'gap-control') }, itemControl: { boxSize: k.iconSize(tier) } })),
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
switch: {
|
|
261
|
+
base: {
|
|
262
|
+
root: { gap: v('gap-control'), '--switch-diff': 'calc(var(--switch-width) - var(--switch-height))' },
|
|
263
|
+
label: { ...text('base-label'), fontSize: v('font-base-label-font-size'), color: v('text-primary'), _disabled: { color: v('text-disabled'), opacity: null } },
|
|
264
|
+
control: {
|
|
265
|
+
boxSizing: 'border-box',
|
|
266
|
+
alignItems: 'center',
|
|
267
|
+
padding: v('switch-track-padding'),
|
|
268
|
+
border: `${v('check-border-width')} solid ${v('check-border-color')}`,
|
|
269
|
+
borderRadius: v('radius-pill'),
|
|
270
|
+
bg: v('fill-default'),
|
|
271
|
+
transition: `background-color ${v('duration-normal')} ${v('easing-in-out')}`,
|
|
272
|
+
focusVisibleRing: null,
|
|
273
|
+
_before: {
|
|
274
|
+
...text('condense-subtitle-label'),
|
|
275
|
+
content: '"off"',
|
|
276
|
+
position: 'absolute',
|
|
277
|
+
insetBlock: 0,
|
|
278
|
+
right: v('switch-track-padding'),
|
|
279
|
+
width: 'var(--switch-diff)',
|
|
280
|
+
display: 'flex',
|
|
281
|
+
alignItems: 'center',
|
|
282
|
+
justifyContent: 'center',
|
|
283
|
+
fontSize: 'var(--switch-indicator-font-size)',
|
|
284
|
+
lineHeight: 1,
|
|
285
|
+
color: v('text-secondary'),
|
|
286
|
+
pointerEvents: 'none',
|
|
287
|
+
},
|
|
288
|
+
_hover: { bg: v('fill-strong') },
|
|
289
|
+
_checked: {
|
|
290
|
+
bg: v('check-background-checked'),
|
|
291
|
+
borderColor: v('check-border-color-checked'),
|
|
292
|
+
_hover: { bg: v('interactive-hover') },
|
|
293
|
+
_before: { content: '"on"', right: 'auto', left: v('switch-track-padding'), color: v('check-mark-color') },
|
|
294
|
+
},
|
|
295
|
+
_focusVisible: k.focusRing,
|
|
296
|
+
_disabled: { opacity: v('check-opacity-disabled'), cursor: 'not-allowed', _hover: { bg: v('fill-default') } },
|
|
297
|
+
},
|
|
298
|
+
thumb: {
|
|
299
|
+
'--thumb-size': `calc(var(--switch-height) - 2 * (${v('switch-track-padding')} + ${v('check-border-width')}))`,
|
|
300
|
+
width: 'var(--thumb-size)',
|
|
301
|
+
height: 'var(--thumb-size)',
|
|
302
|
+
position: 'relative',
|
|
303
|
+
zIndex: 1,
|
|
304
|
+
transitionDuration: v('duration-normal'),
|
|
305
|
+
bg: v('check-background'),
|
|
306
|
+
boxShadow: v('elevation-sm'),
|
|
307
|
+
},
|
|
308
|
+
},
|
|
309
|
+
variants: {
|
|
310
|
+
variant: { solid: { control: {}, thumb: {} }, raised: { control: {}, thumb: {} } },
|
|
311
|
+
size: Object.fromEntries(
|
|
312
|
+
['xs', 'sm', 'md', 'lg'].map((s) => {
|
|
313
|
+
const suffix = s === 'xs' || s === 'sm' ? '-sm' : s === 'lg' ? '-lg' : '';
|
|
314
|
+
return [s, { root: { '--switch-width': v(`switch-track-width${suffix}`), '--switch-height': v(`switch-track-height${suffix}`), '--switch-indicator-font-size': v(`switch-track-text-font-size${suffix}`) } }];
|
|
315
|
+
}),
|
|
316
|
+
),
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
slider: {
|
|
320
|
+
base: {
|
|
321
|
+
root: { ...text('base'), textStyle: null, gap: v('gauge-gap'), _disabled: { opacity: v('check-opacity-disabled') } },
|
|
322
|
+
label: { ...text('base-label'), textStyle: null, color: v('gauge-label-color') },
|
|
323
|
+
valueText: { ...text('base'), color: v('gauge-value-color') },
|
|
324
|
+
track: { bg: v('gauge-track-color'), borderRadius: v('gauge-radius'), boxShadow: 'none' },
|
|
325
|
+
range: { bg: 'colorPalette.solid', borderRadius: v('gauge-radius') },
|
|
326
|
+
thumb: {
|
|
327
|
+
boxSizing: 'border-box',
|
|
328
|
+
bg: v('field-background'),
|
|
329
|
+
border: `${v('stroke-emphasis')} solid ${paletteVar('solid')}`,
|
|
330
|
+
transition: `box-shadow ${v('duration-normal')} ${v('easing-default')}`,
|
|
331
|
+
_hover: { boxShadow: `0 0 0 ${v('focus-halo-spread')} ${paletteVar('soft-hover')}` },
|
|
332
|
+
_dragging: { boxShadow: `0 0 0 calc(${v('focus-halo-spread')} * 1.5) ${paletteVar('soft-active')}` },
|
|
333
|
+
_focusVisible: { ...k.focusRing, ring: null, ringColor: null },
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
variants: {
|
|
337
|
+
variant: { outline: { track: {}, range: {}, thumb: {} }, solid: { track: {}, range: {}, thumb: {} } },
|
|
338
|
+
size: sizes(['sm', 'md', 'lg'], (tier) => ({ root: { '--slider-thumb-size': v('slider-thumb-size'), '--slider-track-size': v(tier === 'lg' ? 'gauge-height' : 'gauge-height-sm'), '--slider-marker-size': v('stroke-emphasis') } })),
|
|
339
|
+
},
|
|
340
|
+
},
|
|
341
|
+
|
|
342
|
+
tabs: {
|
|
343
|
+
base: {
|
|
344
|
+
list: { borderColor: v('disclosure-divider-color') },
|
|
345
|
+
trigger: {
|
|
346
|
+
...text('base'),
|
|
347
|
+
height: 'auto',
|
|
348
|
+
minW: 0,
|
|
349
|
+
padding: `${v('space-2')} ${v('space-4')}`,
|
|
350
|
+
color: v('text-secondary'),
|
|
351
|
+
transition: v('transition-button'),
|
|
352
|
+
_hover: { color: v('text-primary'), bg: v('disclosure-trigger-background-hover') },
|
|
353
|
+
_focusVisible: { ...k.focusRingInset, zIndex: 1, outlineColor: null },
|
|
354
|
+
_disabled: { color: v('text-disabled'), opacity: v('disclosure-opacity-disabled'), cursor: 'not-allowed' },
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
variants: {
|
|
358
|
+
size: sizes(['sm', 'md', 'lg'], () => ({ trigger: {} })),
|
|
359
|
+
variant: {
|
|
360
|
+
line: {
|
|
361
|
+
list: { display: 'flex', borderColor: v('disclosure-divider-color'), _horizontal: { borderBottomWidth: v('stroke-default') }, _vertical: { borderEndWidth: v('stroke-default') } },
|
|
362
|
+
trigger: {
|
|
363
|
+
_selected: {
|
|
364
|
+
...text('base-selected'),
|
|
365
|
+
color: v('disclosure-selected-color'),
|
|
366
|
+
_horizontal: { layerStyle: 'indicator.bottom', '--indicator-offset-y': `calc(${v('stroke-default')} * -1)`, '--indicator-color': v('disclosure-indicator-color'), '--indicator-thickness': v('disclosure-indicator-width') },
|
|
367
|
+
_vertical: { layerStyle: 'indicator.end', '--indicator-offset-x': `calc(${v('stroke-default')} * -1)`, '--indicator-color': v('disclosure-indicator-color'), '--indicator-thickness': v('disclosure-indicator-width') },
|
|
368
|
+
},
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
accordion: {
|
|
375
|
+
base: {
|
|
376
|
+
root: { color: v('text-primary') },
|
|
377
|
+
item: { borderBottom: `${v('stroke-default')} solid ${v('disclosure-divider-color')}`, _last: { borderBottom: 'none' } },
|
|
378
|
+
itemTrigger: {
|
|
379
|
+
...text('maximize-md'),
|
|
380
|
+
justifyContent: 'space-between',
|
|
381
|
+
gap: v('space-4'),
|
|
382
|
+
padding: `${v('space-4')} ${v('space-6')}`,
|
|
383
|
+
borderRadius: 0,
|
|
384
|
+
transition: `background-color ${v('disclosure-animation-duration')} ${v('disclosure-animation-easing')}`,
|
|
385
|
+
_hover: { bg: v('disclosure-trigger-background-hover') },
|
|
386
|
+
_active: { bg: v('disclosure-trigger-background-active') },
|
|
387
|
+
_focusVisible: { ...k.focusRingInset, outlineColor: null, borderRadius: v('radius-item') },
|
|
388
|
+
_disabled: { opacity: v('disclosure-opacity-disabled'), layerStyle: null },
|
|
389
|
+
},
|
|
390
|
+
itemIndicator: { color: v('disclosure-icon-color') },
|
|
391
|
+
itemBody: { ...text('base'), padding: `0 ${v('space-6')} ${v('space-4')}`, color: v('disclosure-content-color') },
|
|
392
|
+
},
|
|
393
|
+
variants: {
|
|
394
|
+
variant: { outline: { item: {} } },
|
|
395
|
+
size: sizes(['sm', 'md', 'lg'], () => ({ itemTrigger: {} })),
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
|
|
399
|
+
menu: {
|
|
400
|
+
base: {
|
|
401
|
+
content: { ...popup, minW: v('popup-min-width'), padding: v('popup-padding'), overflow: null },
|
|
402
|
+
item: { ...row, paddingBlock: v('item-padding-block-dense'), '&[data-type]': { ps: null } },
|
|
403
|
+
itemGroupLabel: groupLabel,
|
|
404
|
+
itemCommand: { ...text('condense'), color: v('text-tertiary'), opacity: null, letterSpacing: null },
|
|
405
|
+
itemIndicator: { color: v('item-indicator-color') },
|
|
406
|
+
separator: { ...k.separator, mx: 0, my: null },
|
|
407
|
+
arrow: { '--arrow-background': v('popup-background') },
|
|
408
|
+
},
|
|
409
|
+
variants: {
|
|
410
|
+
variant: { subtle: { item: {} }, solid: { item: { _highlighted: { bg: 'colorPalette.solid', color: 'colorPalette.on-solid' } } } },
|
|
411
|
+
size: sizes(['sm', 'md'], () => ({ content: {}, item: {} })),
|
|
412
|
+
},
|
|
413
|
+
},
|
|
414
|
+
select: {
|
|
415
|
+
base: {
|
|
416
|
+
trigger: {
|
|
417
|
+
...field,
|
|
418
|
+
gap: v('gap-control'),
|
|
419
|
+
minH: null,
|
|
420
|
+
height: 'var(--select-trigger-height)',
|
|
421
|
+
cursor: 'pointer',
|
|
422
|
+
_focus: null,
|
|
423
|
+
_focusVisible: k.fieldStates.focus,
|
|
424
|
+
_placeholderShown: { color: v('field-placeholder-color') },
|
|
425
|
+
},
|
|
426
|
+
indicator: { color: v('text-tertiary') },
|
|
427
|
+
label: { ...text('base-label'), textStyle: null, color: v('text-primary') },
|
|
428
|
+
...listParts,
|
|
429
|
+
},
|
|
430
|
+
variants: {
|
|
431
|
+
variant: { outline: { trigger: {} } },
|
|
432
|
+
size: clearListSizes(['xs', 'sm', 'md', 'lg'], (tier) => ({
|
|
433
|
+
root: { '--select-trigger-height': v(`control-height-${tier}`), '--select-trigger-padding-x': v(`control-padding-inline${tier === 'md' ? '' : `-${tier}`}`) },
|
|
434
|
+
trigger: {},
|
|
435
|
+
})),
|
|
436
|
+
},
|
|
437
|
+
},
|
|
438
|
+
combobox: {
|
|
439
|
+
base: {
|
|
440
|
+
input: { ...field, focusVisibleRing: null },
|
|
441
|
+
label: { ...text('base-label'), textStyle: null, color: v('text-primary') },
|
|
442
|
+
trigger: { color: v('text-tertiary') },
|
|
443
|
+
empty: { ...text('condense'), color: v('text-tertiary'), justifyContent: 'center', paddingBlock: v('popup-empty-padding-block') },
|
|
444
|
+
...listParts,
|
|
445
|
+
},
|
|
446
|
+
variants: {
|
|
447
|
+
variant: { outline: { input: {} } },
|
|
448
|
+
size: clearListSizes(['xs', 'sm', 'md', 'lg'], (tier) => ({ input: fieldSize(tier) })),
|
|
449
|
+
},
|
|
450
|
+
},
|
|
451
|
+
popover: {
|
|
452
|
+
base: {
|
|
453
|
+
content: { ...popup, '--popover-bg': v('popup-background'), textStyle: null, ...text('base') },
|
|
454
|
+
title: { ...text('maximize-md') },
|
|
455
|
+
description: { ...text('base'), color: v('text-secondary') },
|
|
456
|
+
arrow: { '--arrow-background': v('popup-background') },
|
|
457
|
+
},
|
|
458
|
+
},
|
|
459
|
+
tooltip: {
|
|
460
|
+
base: {
|
|
461
|
+
content: {
|
|
462
|
+
...text('condense'),
|
|
463
|
+
textStyle: null,
|
|
464
|
+
maxW: v('tooltip-max-width'),
|
|
465
|
+
padding: `${v('space-1-5')} ${v('space-2-5')}`,
|
|
466
|
+
borderRadius: v('radius-chrome'),
|
|
467
|
+
bg: v('surface-inverse'),
|
|
468
|
+
color: v('text-on-inverse-surface'),
|
|
469
|
+
boxShadow: v('popup-shadow'),
|
|
470
|
+
'--tooltip-bg': v('surface-inverse'),
|
|
471
|
+
},
|
|
472
|
+
arrow: { '--arrow-background': v('surface-inverse') },
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
|
|
476
|
+
dialog: {
|
|
477
|
+
base: {
|
|
478
|
+
backdrop: { bg: v('modal-backdrop') },
|
|
479
|
+
content: { ...k.modalSurface, bg: null, background: v('modal-background'), textStyle: null, ...text('base'), width: v('modal-width'), maxH: v('modal-max-height') },
|
|
480
|
+
header: { padding: 0, gap: v('space-2') },
|
|
481
|
+
body: { padding: 0 },
|
|
482
|
+
footer: { padding: 0, gap: v('modal-actions-gap'), marginTop: v('modal-actions-spacing') },
|
|
483
|
+
title: { ...text('maximize-xl'), textStyle: null, color: v('modal-title-color') },
|
|
484
|
+
description: { ...text('base'), color: v('modal-description-color') },
|
|
485
|
+
closeTrigger: { top: v('space-3'), insetEnd: v('space-3') },
|
|
486
|
+
},
|
|
487
|
+
variants: { size: { md: { content: { maxW: v('modal-max-width') } } } },
|
|
488
|
+
},
|
|
489
|
+
drawer: {
|
|
490
|
+
base: {
|
|
491
|
+
backdrop: { bg: v('modal-backdrop') },
|
|
492
|
+
content: { bg: v('modal-background'), color: v('text-primary'), boxShadow: v('modal-shadow'), textStyle: null, ...text('base') },
|
|
493
|
+
title: { ...text('maximize-xl'), textStyle: null, color: v('modal-title-color') },
|
|
494
|
+
description: { ...text('base'), color: v('modal-description-color') },
|
|
495
|
+
footer: { gap: v('modal-actions-gap') },
|
|
496
|
+
},
|
|
497
|
+
},
|
|
498
|
+
|
|
499
|
+
toast: {
|
|
500
|
+
base: {
|
|
501
|
+
root: {
|
|
502
|
+
...k.popupSurface,
|
|
503
|
+
...text('base'),
|
|
504
|
+
padding: v('notice-padding'),
|
|
505
|
+
gap: v('notice-gap'),
|
|
506
|
+
'--toast-trigger-bg': v('surface-raised'),
|
|
507
|
+
...Object.fromEntries(
|
|
508
|
+
STATUS.filter((s) => scopes[s]).map((s) => [`&[data-type=${s}]`, { colorPalette: s, bg: 'colorPalette.soft', borderColor: 'colorPalette.border', color: 'colorPalette.on-soft' }]),
|
|
509
|
+
),
|
|
510
|
+
},
|
|
511
|
+
title: { ...text('maximize-md'), textStyle: null },
|
|
512
|
+
description: { ...text('base'), textStyle: null, marginTop: v('notice-text-gap'), color: v('notice-description-color'), '[data-type] &': { color: 'inherit' } },
|
|
513
|
+
closeTrigger: k.iconButton,
|
|
514
|
+
},
|
|
515
|
+
},
|
|
516
|
+
alert: {
|
|
517
|
+
base: {
|
|
518
|
+
root: { ...text('base'), textStyle: null, padding: v('notice-padding'), gap: v('notice-gap'), borderRadius: v('popup-radius'), borderWidth: v('popup-border-width'), borderStyle: 'solid', borderColor: 'transparent' },
|
|
519
|
+
title: { ...text('maximize-md') },
|
|
520
|
+
description: { ...text('base'), marginTop: v('notice-text-gap') },
|
|
521
|
+
indicator: { color: 'inherit' },
|
|
522
|
+
},
|
|
523
|
+
variants: {
|
|
524
|
+
status: { ...Object.fromEntries(STATUS.map((s) => [s, { root: { colorPalette: statusPalette(s) } }])), neutral: { root: { colorPalette: 'chrome' } } },
|
|
525
|
+
variant: {
|
|
526
|
+
subtle: { root: { bg: 'colorPalette.soft', borderColor: 'colorPalette.border', color: 'colorPalette.on-soft' } },
|
|
527
|
+
surface: { root: { bg: 'colorPalette.soft', borderColor: 'colorPalette.border', color: 'colorPalette.on-soft' } },
|
|
528
|
+
outline: { root: { bg: 'transparent', borderColor: 'colorPalette.border', color: 'colorPalette.fg' } },
|
|
529
|
+
solid: { root: { bg: 'colorPalette.solid', borderColor: 'colorPalette.solid', color: 'colorPalette.on-solid' } },
|
|
530
|
+
},
|
|
531
|
+
size: sizes(['sm', 'md', 'lg'], () => ({ root: {} })),
|
|
532
|
+
},
|
|
533
|
+
},
|
|
534
|
+
progress: {
|
|
535
|
+
base: {
|
|
536
|
+
root: { gap: v('gauge-gap') },
|
|
537
|
+
label: { ...text('base-label'), textStyle: null, color: v('gauge-label-color') },
|
|
538
|
+
valueText: { ...text('base'), textStyle: null, color: v('gauge-value-color') },
|
|
539
|
+
track: { bg: v('gauge-track-color'), borderRadius: v('gauge-radius') },
|
|
540
|
+
range: { bg: 'colorPalette.solid', borderRadius: v('gauge-radius'), transition: v('gauge-transition') },
|
|
541
|
+
},
|
|
542
|
+
variants: {
|
|
543
|
+
variant: { outline: { track: {}, range: {} }, subtle: { track: {}, range: {} } },
|
|
544
|
+
size: sizes(['xs', 'sm', 'md', 'lg', 'xl'], (tier, s) => ({ track: { height: v(s === 'xs' || s === 'sm' ? 'gauge-height-sm' : s === 'xl' ? 'gauge-height-lg' : 'gauge-height') } })),
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
avatar: {
|
|
548
|
+
base: {
|
|
549
|
+
root: { ...text('maximize-md'), boxSizing: 'border-box', border: `${v('stroke-emphasis')} solid ${v('surface-canvas')}`, boxShadow: v('elevation-sm'), textTransform: 'uppercase' },
|
|
550
|
+
},
|
|
551
|
+
variants: {
|
|
552
|
+
variant: Object.fromEntries(['solid', 'subtle', 'outline'].map((s) => [s, { root: { bg: 'colorPalette.solid', color: 'colorPalette.on-solid' } }])),
|
|
553
|
+
size: Object.fromEntries(['2xs', 'xs', 'sm', 'md', 'lg', 'xl', '2xl'].map((s) => [s, { root: { '--avatar-size': v(`avatar-size-${{ '2xs': 'xs', '2xl': 'xl' }[s] ?? s}`), '--avatar-font-size': null } }])),
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
table: {
|
|
557
|
+
base: {
|
|
558
|
+
root: { ...text('condense'), textStyle: null, color: v('table-color') },
|
|
559
|
+
columnHeader: { ...text('base-label'), bg: v('table-header-background'), color: v('table-header-color'), borderColor: v('table-row-border-color') },
|
|
560
|
+
cell: { borderColor: v('table-row-border-color'), paddingInline: v('table-cell-padding-inline') },
|
|
561
|
+
row: { '&[data-selected]': { bg: v('table-row-selected') } },
|
|
562
|
+
},
|
|
563
|
+
},
|
|
564
|
+
numberInput: { base: { input: { ...field } } },
|
|
565
|
+
pinInput: { base: { input: { ...field, textAlign: 'center', boxSize: v('control-height-md'), paddingInline: 0 } } },
|
|
566
|
+
};
|
|
567
|
+
|
|
568
|
+
const config = {
|
|
569
|
+
// Dark follows data-theme, the attribute tokens.css (and Base UI's stories) use.
|
|
570
|
+
conditions: { light: ':root &, .light &, [data-theme=light] &', dark: '.dark &, [data-theme=dark] &' },
|
|
571
|
+
globalCss: { html: { colorPalette: 'accent' } },
|
|
572
|
+
theme: {
|
|
573
|
+
tokens: {
|
|
574
|
+
colors,
|
|
575
|
+
fonts: { heading: value(v('font-family-display')), body: value(v('font-family-body')), mono: value(v('font-family-mono')) },
|
|
576
|
+
radii: { l1: value(v('radius-item')), l2: value(v('radius-chrome')), l3: value(v('radius-surface')) },
|
|
577
|
+
},
|
|
578
|
+
semanticTokens: { colors: semanticColors },
|
|
579
|
+
textStyles: Object.fromEntries(TEXT_STYLES.map((s) => [s, { value: text(s) }])),
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
|
|
583
|
+
const js = `/**
|
|
584
|
+
* Chakra UI v3 system for "${theme.brand}". Generated by loomiere build; do not edit.
|
|
585
|
+
* Load the theme's tokens.css as well: tokens and recipes read its --${prefix}- variables, so
|
|
586
|
+
* Chakra's components match loomiere-base-ui in light and dark (data-theme="dark" on <html>).
|
|
587
|
+
*
|
|
588
|
+
* import './tokens.css';
|
|
589
|
+
* import { ChakraProvider } from '@chakra-ui/react';
|
|
590
|
+
* import { system } from './chakra.js';
|
|
591
|
+
* <ChakraProvider value={system}>…</ChakraProvider>
|
|
592
|
+
*
|
|
593
|
+
* Every palette, plus "accent" and "chrome", works as a colorPalette. Button variants are
|
|
594
|
+
* the brand's emphasis levels (solid = bold, outline = standard, subtle = subtle,
|
|
595
|
+
* ghost = quiet); each keeps its own color unless you pass colorPalette.
|
|
596
|
+
* To add your own config on top: createSystem(config, yours).
|
|
597
|
+
*/
|
|
598
|
+
import { createSystem, defaultConfig, mergeConfigs } from '@chakra-ui/react';
|
|
599
|
+
|
|
600
|
+
${RESTYLE}
|
|
601
|
+
|
|
602
|
+
const recipes = ${toSource(recipes)};
|
|
603
|
+
|
|
604
|
+
const slotRecipes = ${toSource(slotRecipes)};
|
|
605
|
+
|
|
606
|
+
const merged = mergeConfigs(defaultConfig, ${toSource(config)});
|
|
607
|
+
|
|
608
|
+
export const config = {
|
|
609
|
+
...merged,
|
|
610
|
+
theme: {
|
|
611
|
+
...merged.theme,
|
|
612
|
+
recipes: restyleAll(merged.theme.recipes, recipes),
|
|
613
|
+
slotRecipes: restyleAll(merged.theme.slotRecipes, slotRecipes),
|
|
614
|
+
},
|
|
615
|
+
};
|
|
616
|
+
|
|
617
|
+
export const system = createSystem(config);
|
|
618
|
+
|
|
619
|
+
export default system;
|
|
620
|
+
`;
|
|
621
|
+
const dts = `/** Chakra UI v3 system for "${theme.brand}". Generated by loomiere build; do not edit. */
|
|
622
|
+
import type { SystemConfig, SystemContext } from '@chakra-ui/react';
|
|
623
|
+
|
|
624
|
+
export declare const config: SystemConfig;
|
|
625
|
+
export declare const system: SystemContext;
|
|
626
|
+
export default system;
|
|
627
|
+
`;
|
|
628
|
+
return { 'chakra.js': js, 'chakra.d.ts': dts };
|
|
629
|
+
},
|
|
630
|
+
};
|