@scattered-light/loomiere 0.2.0 → 0.4.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 +6 -1
- package/bin/loomiere.mjs +24 -0
- package/build/build-tokens.mjs +93 -49
- package/exporters/README.md +1 -1
- package/exporters/ag-grid-settings.mjs +7 -7
- package/exporters/ag-grid.mjs +6 -6
- package/exporters/chakra.mjs +43 -43
- package/exporters/component-styles.mjs +4 -4
- package/exporters/design-system.mjs +74 -10
- package/exporters/figma.mjs +774 -68
- package/exporters/mui.mjs +40 -40
- package/exporters/shadcn.mjs +5 -13
- package/exporters/tailwind.mjs +14 -5
- package/figma/README.md +135 -0
- package/figma/build-plugin.mjs +25 -0
- package/figma/engine.js +730 -0
- package/figma/plugin/code.js +746 -0
- package/figma/plugin/manifest.json +14 -0
- package/figma/plugin/ui.html +237 -0
- package/figma/plugin-main.js +14 -0
- package/generator/brand.mjs +16 -14
- package/generator/brand.schema.json +60 -0
- package/generator/generate.mjs +2 -2
- package/generator/pairs.mjs +6 -1
- package/generator/roles.mjs +55 -15
- package/generator/vision.mjs +7 -2
- package/lib/config.mjs +2 -0
- package/lib/contrast.mjs +8 -3
- package/lib/figma.mjs +338 -0
- package/lib/studio.mjs +4 -0
- package/lib/tool-server.mjs +7 -4
- package/package.json +5 -1
- package/tokens/README.md +10 -5
- package/tokens/component/avatar.json +41 -6
- package/tokens/component/popover.json +3 -3
- package/tokens/semantic/color.json +1 -1
- package/tokens/semantic/elevation.json +8 -43
- package/tokens/semantic/typography.json +61 -94
- package/tools/brand-builder/builder.js +33 -4
package/exporters/chakra.mjs
CHANGED
|
@@ -19,8 +19,8 @@ const CHAKRA_SLOTS = { contrast: 'on-solid', subtle: 'soft', muted: 'soft-hover'
|
|
|
19
19
|
const STATUS = ['info', 'warning', 'success', 'error'];
|
|
20
20
|
/** Text styles a Chakra user can name as textStyle="…". */
|
|
21
21
|
const TEXT_STYLES = [
|
|
22
|
-
'minimize', 'condense', 'base', 'magnify', 'base
|
|
23
|
-
'condense
|
|
22
|
+
'body-minimize', 'body-condense', 'body-base', 'body-magnify', 'label-base', 'selected-base', 'eyebrow-condense',
|
|
23
|
+
'trigger-condense', 'trigger-base', 'trigger-magnify', 'title-condense', 'title-base', 'title-magnify', 'title-amplify', 'title-maximize',
|
|
24
24
|
];
|
|
25
25
|
|
|
26
26
|
/** Merges our recipe pieces into Chakra's own, in the generated file (see RESTYLE below). */
|
|
@@ -127,7 +127,7 @@ export default {
|
|
|
127
127
|
|
|
128
128
|
const field = {
|
|
129
129
|
...k.field,
|
|
130
|
-
...text('base'),
|
|
130
|
+
...text('body-base'),
|
|
131
131
|
focusVisibleRing: null,
|
|
132
132
|
focusRingColor: null,
|
|
133
133
|
_hover: k.fieldStates.hover,
|
|
@@ -137,7 +137,7 @@ export default {
|
|
|
137
137
|
_placeholder: k.fieldStates.placeholder,
|
|
138
138
|
};
|
|
139
139
|
const fieldSize = (tier) => ({
|
|
140
|
-
...text(tier === 'xs' || tier === 'sm' ? 'condense' : tier === 'lg' ? 'magnify' : 'base'),
|
|
140
|
+
...text(tier === 'xs' || tier === 'sm' ? 'body-condense' : tier === 'lg' ? 'body-magnify' : 'body-base'),
|
|
141
141
|
textStyle: null,
|
|
142
142
|
'--input-height': v(`control-height-${tier}`),
|
|
143
143
|
height: v(`control-height-${tier}`),
|
|
@@ -196,7 +196,7 @@ export default {
|
|
|
196
196
|
|
|
197
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
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 } };
|
|
199
|
+
const label = { ...text('body-base'), textStyle: null, _disabled: { color: v('text-disabled'), opacity: null } };
|
|
200
200
|
const checked = '&:is([data-state=checked], [data-state=indeterminate])';
|
|
201
201
|
|
|
202
202
|
const slotRecipes = {
|
|
@@ -204,9 +204,9 @@ export default {
|
|
|
204
204
|
base: {
|
|
205
205
|
root: { gap: v('field-gap') },
|
|
206
206
|
// A disabled field fades as a whole, as a disabled Base UI Field.Root does.
|
|
207
|
-
label: { ...text('base
|
|
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
|
|
207
|
+
label: { ...text('label-base'), textStyle: null, gap: v('space-1'), marginBottom: v('field-label-spacing'), color: v('text-primary'), _disabled: { opacity: v('field-opacity-disabled') } },
|
|
208
|
+
helperText: { ...text('body-condense'), textStyle: null, margin: `${v('field-message-spacing')} 0 0`, color: v('text-secondary'), _disabled: { opacity: v('field-opacity-disabled') } },
|
|
209
|
+
errorText: { ...text('label-base'), textStyle: null, margin: `${v('field-message-spacing')} 0 0`, color: v('status-error') },
|
|
210
210
|
requiredIndicator: { color: v('status-error') },
|
|
211
211
|
},
|
|
212
212
|
},
|
|
@@ -260,7 +260,7 @@ export default {
|
|
|
260
260
|
switch: {
|
|
261
261
|
base: {
|
|
262
262
|
root: { gap: v('gap-control'), '--switch-diff': 'calc(var(--switch-width) - var(--switch-height))' },
|
|
263
|
-
label: { ...text('base
|
|
263
|
+
label: { ...text('label-base'), fontSize: v('font-label-base-font-size'), color: v('text-primary'), _disabled: { color: v('text-disabled'), opacity: null } },
|
|
264
264
|
control: {
|
|
265
265
|
boxSizing: 'border-box',
|
|
266
266
|
alignItems: 'center',
|
|
@@ -271,7 +271,7 @@ export default {
|
|
|
271
271
|
transition: `background-color ${v('duration-normal')} ${v('easing-in-out')}`,
|
|
272
272
|
focusVisibleRing: null,
|
|
273
273
|
_before: {
|
|
274
|
-
...text('condense
|
|
274
|
+
...text('eyebrow-condense'),
|
|
275
275
|
content: '"off"',
|
|
276
276
|
position: 'absolute',
|
|
277
277
|
insetBlock: 0,
|
|
@@ -318,9 +318,9 @@ export default {
|
|
|
318
318
|
},
|
|
319
319
|
slider: {
|
|
320
320
|
base: {
|
|
321
|
-
root: { ...text('base'), textStyle: null, gap: v('gauge-gap'), _disabled: { opacity: v('check-opacity-disabled') } },
|
|
322
|
-
label: { ...text('base
|
|
323
|
-
valueText: { ...text('base'), color: v('gauge-value-color') },
|
|
321
|
+
root: { ...text('body-base'), textStyle: null, gap: v('gauge-gap'), _disabled: { opacity: v('check-opacity-disabled') } },
|
|
322
|
+
label: { ...text('label-base'), textStyle: null, color: v('gauge-label-color') },
|
|
323
|
+
valueText: { ...text('body-base'), color: v('gauge-value-color') },
|
|
324
324
|
track: { bg: v('gauge-track-color'), borderRadius: v('gauge-radius'), boxShadow: 'none' },
|
|
325
325
|
range: { bg: 'colorPalette.solid', borderRadius: v('gauge-radius') },
|
|
326
326
|
thumb: {
|
|
@@ -343,7 +343,7 @@ export default {
|
|
|
343
343
|
base: {
|
|
344
344
|
list: { borderColor: v('disclosure-divider-color') },
|
|
345
345
|
trigger: {
|
|
346
|
-
...text('base'),
|
|
346
|
+
...text('body-base'),
|
|
347
347
|
height: 'auto',
|
|
348
348
|
minW: 0,
|
|
349
349
|
padding: `${v('space-2')} ${v('space-4')}`,
|
|
@@ -361,7 +361,7 @@ export default {
|
|
|
361
361
|
list: { display: 'flex', borderColor: v('disclosure-divider-color'), _horizontal: { borderBottomWidth: v('stroke-default') }, _vertical: { borderEndWidth: v('stroke-default') } },
|
|
362
362
|
trigger: {
|
|
363
363
|
_selected: {
|
|
364
|
-
...text('base
|
|
364
|
+
...text('selected-base'),
|
|
365
365
|
color: v('disclosure-selected-color'),
|
|
366
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
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') },
|
|
@@ -376,7 +376,7 @@ export default {
|
|
|
376
376
|
root: { color: v('text-primary') },
|
|
377
377
|
item: { borderBottom: `${v('stroke-default')} solid ${v('disclosure-divider-color')}`, _last: { borderBottom: 'none' } },
|
|
378
378
|
itemTrigger: {
|
|
379
|
-
...text('
|
|
379
|
+
...text('title-base'),
|
|
380
380
|
justifyContent: 'space-between',
|
|
381
381
|
gap: v('space-4'),
|
|
382
382
|
padding: `${v('space-4')} ${v('space-6')}`,
|
|
@@ -388,7 +388,7 @@ export default {
|
|
|
388
388
|
_disabled: { opacity: v('disclosure-opacity-disabled'), layerStyle: null },
|
|
389
389
|
},
|
|
390
390
|
itemIndicator: { color: v('disclosure-icon-color') },
|
|
391
|
-
itemBody: { ...text('base'), padding: `0 ${v('space-6')} ${v('space-4')}`, color: v('disclosure-content-color') },
|
|
391
|
+
itemBody: { ...text('body-base'), padding: `0 ${v('space-6')} ${v('space-4')}`, color: v('disclosure-content-color') },
|
|
392
392
|
},
|
|
393
393
|
variants: {
|
|
394
394
|
variant: { outline: { item: {} } },
|
|
@@ -401,7 +401,7 @@ export default {
|
|
|
401
401
|
content: { ...popup, minW: v('popup-min-width'), padding: v('popup-padding'), overflow: null },
|
|
402
402
|
item: { ...row, paddingBlock: v('item-padding-block-dense'), '&[data-type]': { ps: null } },
|
|
403
403
|
itemGroupLabel: groupLabel,
|
|
404
|
-
itemCommand: { ...text('condense'), color: v('text-tertiary'), opacity: null, letterSpacing: null },
|
|
404
|
+
itemCommand: { ...text('body-condense'), color: v('text-tertiary'), opacity: null, letterSpacing: null },
|
|
405
405
|
itemIndicator: { color: v('item-indicator-color') },
|
|
406
406
|
separator: { ...k.separator, mx: 0, my: null },
|
|
407
407
|
arrow: { '--arrow-background': v('popup-background') },
|
|
@@ -424,7 +424,7 @@ export default {
|
|
|
424
424
|
_placeholderShown: { color: v('field-placeholder-color') },
|
|
425
425
|
},
|
|
426
426
|
indicator: { color: v('text-tertiary') },
|
|
427
|
-
label: { ...text('base
|
|
427
|
+
label: { ...text('label-base'), textStyle: null, color: v('text-primary') },
|
|
428
428
|
...listParts,
|
|
429
429
|
},
|
|
430
430
|
variants: {
|
|
@@ -438,9 +438,9 @@ export default {
|
|
|
438
438
|
combobox: {
|
|
439
439
|
base: {
|
|
440
440
|
input: { ...field, focusVisibleRing: null },
|
|
441
|
-
label: { ...text('base
|
|
441
|
+
label: { ...text('label-base'), textStyle: null, color: v('text-primary') },
|
|
442
442
|
trigger: { color: v('text-tertiary') },
|
|
443
|
-
empty: { ...text('condense'), color: v('text-tertiary'), justifyContent: 'center', paddingBlock: v('popup-empty-padding-block') },
|
|
443
|
+
empty: { ...text('body-condense'), color: v('text-tertiary'), justifyContent: 'center', paddingBlock: v('popup-empty-padding-block') },
|
|
444
444
|
...listParts,
|
|
445
445
|
},
|
|
446
446
|
variants: {
|
|
@@ -450,16 +450,16 @@ export default {
|
|
|
450
450
|
},
|
|
451
451
|
popover: {
|
|
452
452
|
base: {
|
|
453
|
-
content: { ...popup, '--popover-bg': v('popup-background'), textStyle: null, ...text('base') },
|
|
454
|
-
title: { ...text('
|
|
455
|
-
description: { ...text('base'), color: v('text-secondary') },
|
|
453
|
+
content: { ...popup, '--popover-bg': v('popup-background'), textStyle: null, ...text('body-base') },
|
|
454
|
+
title: { ...text('title-base') },
|
|
455
|
+
description: { ...text('body-base'), color: v('text-secondary') },
|
|
456
456
|
arrow: { '--arrow-background': v('popup-background') },
|
|
457
457
|
},
|
|
458
458
|
},
|
|
459
459
|
tooltip: {
|
|
460
460
|
base: {
|
|
461
461
|
content: {
|
|
462
|
-
...text('condense'),
|
|
462
|
+
...text('body-condense'),
|
|
463
463
|
textStyle: null,
|
|
464
464
|
maxW: v('tooltip-max-width'),
|
|
465
465
|
padding: `${v('space-1-5')} ${v('space-2-5')}`,
|
|
@@ -476,12 +476,12 @@ export default {
|
|
|
476
476
|
dialog: {
|
|
477
477
|
base: {
|
|
478
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') },
|
|
479
|
+
content: { ...k.modalSurface, bg: null, background: v('modal-background'), textStyle: null, ...text('body-base'), width: v('modal-width'), maxH: v('modal-max-height') },
|
|
480
480
|
header: { padding: 0, gap: v('space-2') },
|
|
481
481
|
body: { padding: 0 },
|
|
482
482
|
footer: { padding: 0, gap: v('modal-actions-gap'), marginTop: v('modal-actions-spacing') },
|
|
483
|
-
title: { ...text('
|
|
484
|
-
description: { ...text('base'), color: v('modal-description-color') },
|
|
483
|
+
title: { ...text('title-amplify'), textStyle: null, color: v('modal-title-color') },
|
|
484
|
+
description: { ...text('body-base'), color: v('modal-description-color') },
|
|
485
485
|
closeTrigger: { top: v('space-3'), insetEnd: v('space-3') },
|
|
486
486
|
},
|
|
487
487
|
variants: { size: { md: { content: { maxW: v('modal-max-width') } } } },
|
|
@@ -489,9 +489,9 @@ export default {
|
|
|
489
489
|
drawer: {
|
|
490
490
|
base: {
|
|
491
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('
|
|
494
|
-
description: { ...text('base'), color: v('modal-description-color') },
|
|
492
|
+
content: { bg: v('modal-background'), color: v('text-primary'), boxShadow: v('modal-shadow'), textStyle: null, ...text('body-base') },
|
|
493
|
+
title: { ...text('title-amplify'), textStyle: null, color: v('modal-title-color') },
|
|
494
|
+
description: { ...text('body-base'), color: v('modal-description-color') },
|
|
495
495
|
footer: { gap: v('modal-actions-gap') },
|
|
496
496
|
},
|
|
497
497
|
},
|
|
@@ -500,7 +500,7 @@ export default {
|
|
|
500
500
|
base: {
|
|
501
501
|
root: {
|
|
502
502
|
...k.popupSurface,
|
|
503
|
-
...text('base'),
|
|
503
|
+
...text('body-base'),
|
|
504
504
|
padding: v('notice-padding'),
|
|
505
505
|
gap: v('notice-gap'),
|
|
506
506
|
'--toast-trigger-bg': v('surface-raised'),
|
|
@@ -508,16 +508,16 @@ export default {
|
|
|
508
508
|
STATUS.filter((s) => scopes[s]).map((s) => [`&[data-type=${s}]`, { colorPalette: s, bg: 'colorPalette.soft', borderColor: 'colorPalette.border', color: 'colorPalette.on-soft' }]),
|
|
509
509
|
),
|
|
510
510
|
},
|
|
511
|
-
title: { ...text('
|
|
512
|
-
description: { ...text('base'), textStyle: null, marginTop: v('notice-text-gap'), color: v('notice-description-color'), '[data-type] &': { color: 'inherit' } },
|
|
511
|
+
title: { ...text('title-base'), textStyle: null },
|
|
512
|
+
description: { ...text('body-base'), textStyle: null, marginTop: v('notice-text-gap'), color: v('notice-description-color'), '[data-type] &': { color: 'inherit' } },
|
|
513
513
|
closeTrigger: k.iconButton,
|
|
514
514
|
},
|
|
515
515
|
},
|
|
516
516
|
alert: {
|
|
517
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('
|
|
520
|
-
description: { ...text('base'), marginTop: v('notice-text-gap') },
|
|
518
|
+
root: { ...text('body-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('title-base') },
|
|
520
|
+
description: { ...text('body-base'), marginTop: v('notice-text-gap') },
|
|
521
521
|
indicator: { color: 'inherit' },
|
|
522
522
|
},
|
|
523
523
|
variants: {
|
|
@@ -534,8 +534,8 @@ export default {
|
|
|
534
534
|
progress: {
|
|
535
535
|
base: {
|
|
536
536
|
root: { gap: v('gauge-gap') },
|
|
537
|
-
label: { ...text('base
|
|
538
|
-
valueText: { ...text('base'), textStyle: null, color: v('gauge-value-color') },
|
|
537
|
+
label: { ...text('label-base'), textStyle: null, color: v('gauge-label-color') },
|
|
538
|
+
valueText: { ...text('body-base'), textStyle: null, color: v('gauge-value-color') },
|
|
539
539
|
track: { bg: v('gauge-track-color'), borderRadius: v('gauge-radius') },
|
|
540
540
|
range: { bg: 'colorPalette.solid', borderRadius: v('gauge-radius'), transition: v('gauge-transition') },
|
|
541
541
|
},
|
|
@@ -546,7 +546,7 @@ export default {
|
|
|
546
546
|
},
|
|
547
547
|
avatar: {
|
|
548
548
|
base: {
|
|
549
|
-
root: { ...text('
|
|
549
|
+
root: { ...text('title-base'), boxSizing: 'border-box', border: `${v('stroke-emphasis')} solid ${v('surface-canvas')}`, boxShadow: v('elevation-sm'), textTransform: 'uppercase' },
|
|
550
550
|
},
|
|
551
551
|
variants: {
|
|
552
552
|
variant: Object.fromEntries(['solid', 'subtle', 'outline'].map((s) => [s, { root: { bg: 'colorPalette.solid', color: 'colorPalette.on-solid' } }])),
|
|
@@ -555,8 +555,8 @@ export default {
|
|
|
555
555
|
},
|
|
556
556
|
table: {
|
|
557
557
|
base: {
|
|
558
|
-
root: { ...text('condense'), textStyle: null, color: v('table-color') },
|
|
559
|
-
columnHeader: { ...text('base
|
|
558
|
+
root: { ...text('body-condense'), textStyle: null, color: v('table-color') },
|
|
559
|
+
columnHeader: { ...text('label-base'), bg: v('table-header-background'), color: v('table-header-color'), borderColor: v('table-row-border-color') },
|
|
560
560
|
cell: { borderColor: v('table-row-border-color'), paddingInline: v('table-cell-padding-inline') },
|
|
561
561
|
row: { '&[data-selected]': { bg: v('table-row-selected') } },
|
|
562
562
|
},
|
|
@@ -567,7 +567,7 @@ export default {
|
|
|
567
567
|
|
|
568
568
|
const config = {
|
|
569
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] &' },
|
|
570
|
+
conditions: { light: ':root &, .light &, [data-theme=light] &', dark: '.dark &, [data-theme=dark] &, [data-theme=dark-high-contrast] &' },
|
|
571
571
|
globalCss: { html: { colorPalette: 'accent' } },
|
|
572
572
|
theme: {
|
|
573
573
|
tokens: {
|
|
@@ -69,7 +69,7 @@ export function styleKit(prefix, roles) {
|
|
|
69
69
|
const control = (size, { trigger = false } = {}) => {
|
|
70
70
|
const s = SIZES[size];
|
|
71
71
|
return {
|
|
72
|
-
...text(trigger ?
|
|
72
|
+
...text(trigger ? `trigger-${s.text}` : `body-${s.text}`),
|
|
73
73
|
height: v(`control-height${s.suffix || '-md'}`),
|
|
74
74
|
paddingInline: v(`control-padding-inline${s.suffix}`),
|
|
75
75
|
gap: v(size === 'xs' ? 'gap-control-xs' : size === 'sm' ? 'gap-control-sm' : 'gap-control'),
|
|
@@ -107,7 +107,7 @@ export function styleKit(prefix, roles) {
|
|
|
107
107
|
};
|
|
108
108
|
|
|
109
109
|
const itemRow = {
|
|
110
|
-
...text('base'),
|
|
110
|
+
...text('body-base'),
|
|
111
111
|
display: 'flex',
|
|
112
112
|
alignItems: 'center',
|
|
113
113
|
gap: v('item-gap'),
|
|
@@ -120,11 +120,11 @@ export function styleKit(prefix, roles) {
|
|
|
120
120
|
};
|
|
121
121
|
const itemStates = {
|
|
122
122
|
hover: { background: v('item-background-hover'), color: v('item-color-hover') },
|
|
123
|
-
selected: { ...text('base
|
|
123
|
+
selected: { ...text('selected-base'), background: v('item-background-selected'), color: v('item-color-selected') },
|
|
124
124
|
disabled: { color: v('item-color-disabled'), opacity: v('item-opacity-disabled') },
|
|
125
125
|
};
|
|
126
126
|
const groupLabel = {
|
|
127
|
-
...text('condense
|
|
127
|
+
...text('eyebrow-condense'),
|
|
128
128
|
paddingBlock: `${v('popup-group-label-padding-top')} ${v('popup-group-label-padding-bottom')}`,
|
|
129
129
|
paddingInline: v('item-padding-inline'),
|
|
130
130
|
color: v('text-secondary'),
|
|
@@ -97,8 +97,8 @@ export default {
|
|
|
97
97
|
// ------------------------------------------------------------------------------- type
|
|
98
98
|
const styleNames = [...new Set(theme.tokens.filter((t) => /^font\.[^.]+\.font$/.test(t.path)).map((t) => t.path.split('.')[1]))];
|
|
99
99
|
const familyKey = (style) => byPath.get(`font.${style}.font-family`)?.references?.match(/^\{font-family\.([^}]+)\}$/)?.[1] ?? 'body';
|
|
100
|
-
const tierOf = (style) =>
|
|
101
|
-
const TIERS = ['minimize', 'condense', 'base', 'magnify', '
|
|
100
|
+
const tierOf = (style) => style.split('-').pop();
|
|
101
|
+
const TIERS = ['minimize', 'condense', 'base', 'magnify', 'amplify', 'maximize'];
|
|
102
102
|
const tiers = [...new Set(styleNames.map(tierOf))].sort((a, b) => (TIERS.indexOf(a) + 1 || 99) - (TIERS.indexOf(b) + 1 || 99));
|
|
103
103
|
const styleOf = (style) => {
|
|
104
104
|
const v = (prop) => byPath.get(`font.${style}.${prop}`)?.value;
|
|
@@ -256,6 +256,12 @@ function brandBook({ theme, brand, byPath, cssName, usageOf, tokens, tiers, styl
|
|
|
256
256
|
'',
|
|
257
257
|
`\`data-color="<palette>"\` on any element repaints everything inside it in that palette (\`primary\`, \`secondary\`, \`error\`, \`accent\`, \`chrome\`...): a destructive button is \`<Button data-emphasis="bold" data-color="error">\`.`,
|
|
258
258
|
'',
|
|
259
|
+
'### Statuses',
|
|
260
|
+
'',
|
|
261
|
+
`\`data-status="info|success|warning|error"\` on any element gives every emphasis level inside it that status's version, so a subtle warning banner is \`<div data-status="warning" data-emphasis="subtle">\`. A level is the level's style in the status's palette unless the brand picked a pair for it:`,
|
|
262
|
+
'',
|
|
263
|
+
...Object.entries(roles.status ?? {}).flatMap(([status, picks]) => Object.entries(picks).map(([level, pair]) => `- ${level} in ${status}: the \`${pair}\` pair`)),
|
|
264
|
+
...(Object.keys(roles.status ?? {}).length ? [''] : ['(This brand picks none, so every level is automatic.)', '']),
|
|
259
265
|
);
|
|
260
266
|
const pairs = Object.entries(brand?.color?.pairs ?? {});
|
|
261
267
|
if (pairs.length) {
|
|
@@ -284,16 +290,16 @@ function brandBook({ theme, brand, byPath, cssName, usageOf, tokens, tiers, styl
|
|
|
284
290
|
condense: 'compact UI: small controls, table cells, secondary labels.',
|
|
285
291
|
base: 'the default UI and body text.',
|
|
286
292
|
magnify: 'emphasized body text: lead paragraphs, larger controls.',
|
|
287
|
-
|
|
288
|
-
|
|
293
|
+
amplify: 'titles of dialogs, drawers and page sections.',
|
|
294
|
+
maximize: 'the page head.',
|
|
289
295
|
};
|
|
290
296
|
for (const tier of tiers) {
|
|
291
|
-
const variants = styleNames.filter((s) => s.
|
|
292
|
-
out.push(`- **${tier}**: ${tierWhy[tier] ?? ''}${variants.length ? `
|
|
297
|
+
const variants = styleNames.filter((s) => s.endsWith(`-${tier}`)).map((s) => s.slice(0, -tier.length - 1));
|
|
298
|
+
out.push(`- **${tier}**: ${tierWhy[tier] ?? ''}${variants.length ? ` Roles: ${variants.map((x) => `\`${x}\``).join(', ')}.` : ''}`);
|
|
293
299
|
}
|
|
294
300
|
out.push(
|
|
295
301
|
'',
|
|
296
|
-
'
|
|
302
|
+
'Names are role first, then tier: `body-` for running text, `trigger-` for buttons and triggers, `selected-` for the current tab, toggle or nav item, `link-` for links, `subtitle-` for subheads, `label-` for form labels, `eyebrow-` for small uppercase labels, `title-` for titles in the display family, `code-` for code in the mono family. Sizes below the base are fixed; the base and up grow with the viewport.',
|
|
297
303
|
'',
|
|
298
304
|
'## Space, shape and depth',
|
|
299
305
|
'',
|
|
@@ -309,17 +315,75 @@ function brandBook({ theme, brand, byPath, cssName, usageOf, tokens, tiers, styl
|
|
|
309
315
|
'',
|
|
310
316
|
'## Modes',
|
|
311
317
|
'',
|
|
312
|
-
`
|
|
318
|
+
`Themes: \`data-theme="light|dark|light-high-contrast|dark-high-contrast"\` on the root, or the system setting (including \`prefers-contrast: more\`) when none is set. Color vision themes are themes of their own, each a light theme designed from this brand: \`data-theme="deuteranopia|protanopia|tritanopia|achromatopsia"\`. Never hand-pick dark colors; the tokens already flip.`,
|
|
313
319
|
'',
|
|
314
320
|
);
|
|
315
321
|
if (componentNames.length) {
|
|
322
|
+
const has = (n) => componentNames.includes(n);
|
|
323
|
+
const pick = (list) => list.filter(([n]) => has(n)).map(([n, use]) => `| \`${n}\` | ${use} |`);
|
|
316
324
|
out.push(
|
|
317
325
|
'## Components',
|
|
318
326
|
'',
|
|
319
|
-
`The components are [Base UI](https://base-ui.com) parts styled by class name: ${componentNames.map((n) => `\`${n}\``).join(', ')}. Each component's README lists the classes its parts take and the data attributes they answer to (\`data-size="sm|lg"\`, \`data-emphasis\`, \`data-color\`)
|
|
327
|
+
`The components are [Base UI](https://base-ui.com) parts styled by class name: ${componentNames.map((n) => `\`${n}\``).join(', ')}. Each component's README lists the classes its parts take and the data attributes they answer to (\`data-size="sm|lg"\`, \`data-emphasis\`, \`data-color\`).`,
|
|
320
328
|
'',
|
|
321
329
|
'Icons are Material Symbols Outlined by ligature name (`<span class="material-symbols-outlined">search</span>`, or `Loomiere.MaterialSymbol name="search"`), sized by the `.icon-<tier>` utilities to match the text beside them.',
|
|
322
330
|
'',
|
|
331
|
+
'### Using the components in a page or design',
|
|
332
|
+
'',
|
|
333
|
+
'Load three things, in this order:',
|
|
334
|
+
'',
|
|
335
|
+
'1. `components/bundle.css`: the whole theme in one sheet. It holds every token (light, dark, high contrast and color vision modes), the text style, icon and focus utilities, the brand pairs, and every component\'s styles, and it imports the brand fonts and Material Symbols from Google Fonts itself. Nothing else is needed for styling; `tokens.json` is the same tokens as data for the Design System page.',
|
|
336
|
+
'2. React 18 and ReactDOM 18 as the `window.React` and `window.ReactDOM` globals. A design canvas or the preview frame already has them.',
|
|
337
|
+
'3. `components/bundle.js`: one classic script that sets `window.Loomiere`.',
|
|
338
|
+
'',
|
|
339
|
+
'`window.Loomiere` holds two kinds of component:',
|
|
340
|
+
'',
|
|
341
|
+
`- **Wrappers** put their own classes on, so use them bare: ${['Button', 'Input', 'Field', 'Fieldset', 'Form', 'NavigationMenu'].filter(has).map((n) => `\`Loomiere.${n}\``).join(', ')}. \`Loomiere.Button\` also takes \`emphasis\` (\`bold|standard|subtle|quiet\`) and \`palette\` props, and a Button holding only an icon draws itself square (it sets \`data-icon-only\`). A plain \`.Button\`, such as a Popover or Toolbar trigger, is drawn square when it has an \`aria-label\` and a single icon inside, or when you set \`data-icon-only\` on it.`,
|
|
342
|
+
'- **Everything else is the bare Base UI part** under its usual name (`Loomiere.Popover.Popup`, `Loomiere.ToggleGroup`). It draws nothing until you give each part its class, which is the component and part names joined: `<Loomiere.Popover.Popup className="PopoverPopup">`, `<Loomiere.Toggle className="Toggle">`. The component READMEs list every class.',
|
|
343
|
+
'',
|
|
344
|
+
'`Loomiere.Base` holds the bare Base UI parts for the wrappers too (`Loomiere.Base.Button`), for when you need the part without its classes.',
|
|
345
|
+
'',
|
|
346
|
+
'### Choosing a component',
|
|
347
|
+
'',
|
|
348
|
+
'Pick by what the person is doing, not by look:',
|
|
349
|
+
'',
|
|
350
|
+
'| Component | Use it to |',
|
|
351
|
+
'|---|---|',
|
|
352
|
+
...pick([
|
|
353
|
+
['Button', 'Run one action. Set its emphasis by how much the action matters. For a small, frequent action shown as an icon alone (calendar arrows, row actions), use a quiet neutral icon-only Button: `<Loomiere.Button emphasis="quiet" palette="neutral" data-size="xs" aria-label="Next month">` with one icon inside.'],
|
|
354
|
+
['Switch', 'Turn one setting on or off, taking effect at once.'],
|
|
355
|
+
['Checkbox', 'Turn one option on or off inside a form that is submitted later.'],
|
|
356
|
+
['Toggle', 'A tool that stays pressed, such as Bold in a toolbar.'],
|
|
357
|
+
['ToggleGroup', 'Switch between 2 to 5 short modes or views that apply at once (Single date / Date range, List / Grid).'],
|
|
358
|
+
['Tabs', 'Switch between panels of content in the same place.'],
|
|
359
|
+
['Radio', 'Choose one of 2 to 6 options in a form, where each option can carry a label and description.'],
|
|
360
|
+
['CheckboxGroup', 'Choose any number of a few visible options.'],
|
|
361
|
+
['Select', 'Choose one of a fixed list too long to show (about 6 to 15 items).'],
|
|
362
|
+
['Combobox', 'Choose one or more from a long list by typing to filter it.'],
|
|
363
|
+
['Autocomplete', 'Type free text, with suggestions to speed it up.'],
|
|
364
|
+
['NumberField', 'Enter an exact number, with steppers.'],
|
|
365
|
+
['Slider', 'Set a value in a range where roughly is good enough.'],
|
|
366
|
+
['OTPField', 'Enter a one-time code.'],
|
|
367
|
+
['Menu', 'Offer commands from a button. Menus run actions; they don\'t hold a value.'],
|
|
368
|
+
['ContextMenu', 'Offer commands on right-click or long press.'],
|
|
369
|
+
['Menubar', 'An app\'s top row of menus.'],
|
|
370
|
+
['Toolbar', 'A row of buttons, toggles and fields that act on one thing.'],
|
|
371
|
+
['NavigationMenu', 'Site navigation, with panels of links.'],
|
|
372
|
+
['Tooltip', 'Name an icon-only control. Never put anything you can click inside one.'],
|
|
373
|
+
['PreviewCard', 'Preview what a link leads to on hover.'],
|
|
374
|
+
['Popover', 'Show interactive content beside its trigger: a picker, a short form, filters.'],
|
|
375
|
+
['Dialog', 'A task that needs full attention before the page goes on.'],
|
|
376
|
+
['AlertDialog', 'Confirm a destructive or hard-to-undo action.'],
|
|
377
|
+
['Drawer', 'A side task or detail panel that keeps the page in view.'],
|
|
378
|
+
['Accordion', 'Several sections where only some need to be open.'],
|
|
379
|
+
['Collapsible', 'One section to show or hide.'],
|
|
380
|
+
['Toast', 'Report the result of an action, briefly.'],
|
|
381
|
+
['Progress', 'Show how far a task has got.'],
|
|
382
|
+
['Meter', 'Show a measured amount within known limits, such as storage used.'],
|
|
383
|
+
]),
|
|
384
|
+
'',
|
|
385
|
+
`A list of choices that stays on screen inside a panel, one of them selected (quick picks, saved filters, presets), has no Base UI part. Make each row a \`<button aria-pressed>\` drawn from the item concept: \`--${theme.prefix}-item-padding-block\`, \`-padding-inline\`, \`-radius\` and \`-gap\`, \`--${theme.prefix}-item-background-hover\` and \`-color-hover\` on hover, \`--${theme.prefix}-item-background-selected\` and \`-color-selected\` with the \`-selected\` text style when chosen. These are the same rows menus and selects use. When the choice is a form value, use Radio instead.`,
|
|
386
|
+
'',
|
|
323
387
|
);
|
|
324
388
|
}
|
|
325
389
|
return `${out.join('\n')}\n`;
|
|
@@ -384,7 +448,7 @@ function cover({ theme, brand, title }) {
|
|
|
384
448
|
function agentsGuide({ theme, readme, componentNames }) {
|
|
385
449
|
return `# ${cap(theme.brand)} theme: guide for agents
|
|
386
450
|
|
|
387
|
-
This folder is a Loomiere theme. Load \`tokens.css\` (every token as a \`--${theme.prefix}-\` variable, light and dark) and \`utilities.css\` (text styles, focus ring, brand pairs). Framework adapters sit in \`exports/\`: \`tailwind.css\` (Tailwind v4: text styles by tier such as \`text-base\`, concept classes such as \`bg-field\` and \`text-link\`, no bare sizes like \`text-sm\`), \`shadcn.css\` (shadcn/ui), \`mui.js\`, \`chakra.js\`, AG Grid themes, \`tokens.dtcg.json\`. \`tokens.json\` lists every token with its path, value, modes and the reason it exists; read a token's \`description\` before using it.
|
|
451
|
+
This folder is a Loomiere theme. Load \`tokens.css\` (every token as a \`--${theme.prefix}-\` variable, light and dark) and \`utilities.css\` (text styles, focus ring, brand pairs). Framework adapters sit in \`exports/\`: \`tailwind.css\` (Tailwind v4: text styles by tier such as \`text-body-base\`, concept classes such as \`bg-field\` and \`text-link\`, no bare sizes like \`text-sm\`), \`shadcn.css\` (shadcn/ui), \`mui.js\`, \`chakra.js\`, AG Grid themes, \`tokens.dtcg.json\`. \`tokens.json\` lists every token with its path, value, modes and the reason it exists; read a token's \`description\` before using it.
|
|
388
452
|
${componentNames ? `\nComponents: \`@scattered-light/loomiere-base-ui\` (Base UI parts styled by class name; import \`@scattered-light/loomiere-base-ui/styles.css\` after the theme). \`exports/design-system/components/<Name>/README.md\` lists each component's classes and an example.\n` : ''}
|
|
389
453
|
Change the brand only in the brand file and rebuild (\`loomiere build\`); never edit generated files.
|
|
390
454
|
|