@astryxdesign/core 0.4.1 → 0.4.2-canary.356d2f9
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/CHANGELOG.md +55 -0
- package/dist/Avatar/Avatar.d.ts +4 -1
- package/dist/Avatar/Avatar.d.ts.map +1 -1
- package/dist/Avatar/Avatar.js +19 -23
- package/dist/AvatarGroup/AvatarGroupOverflow.d.ts.map +1 -1
- package/dist/AvatarGroup/AvatarGroupOverflow.js +7 -2
- package/dist/Button/Button.d.ts.map +1 -1
- package/dist/Button/Button.js +9 -7
- package/dist/Chat/ChatMessage.d.ts +7 -0
- package/dist/Chat/ChatMessage.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.d.ts +13 -1
- package/dist/Chat/ChatMessageBubble.d.ts.map +1 -1
- package/dist/Chat/ChatMessageBubble.js +19 -1
- package/dist/CheckboxInput/CheckboxInput.d.ts.map +1 -1
- package/dist/CheckboxInput/CheckboxInput.js +5 -0
- package/dist/CommandPalette/CommandPaletteFooter.d.ts.map +1 -1
- package/dist/CommandPalette/CommandPaletteFooter.js +5 -3
- package/dist/DateTimeInput/DateTimeInput.d.ts.map +1 -1
- package/dist/DateTimeInput/DateTimeInput.js +2 -2
- package/dist/DropdownMenu/DropdownMenuSubMenu.d.ts.map +1 -1
- package/dist/DropdownMenu/DropdownMenuSubMenu.js +19 -15
- package/dist/HoverCard/HoverCard.d.ts +1 -1
- package/dist/HoverCard/HoverCard.d.ts.map +1 -1
- package/dist/HoverCard/HoverCard.js +5 -13
- package/dist/HoverCard/useHoverCard.d.ts.map +1 -1
- package/dist/HoverCard/useHoverCard.js +6 -5
- package/dist/InputGroup/groupStyles.d.ts.map +1 -1
- package/dist/InputGroup/groupStyles.js +7 -2
- package/dist/Layer/layerHost.d.ts +24 -0
- package/dist/Layer/layerHost.d.ts.map +1 -0
- package/dist/Layer/layerHost.js +79 -0
- package/dist/Layer/useLayer.d.ts +14 -6
- package/dist/Layer/useLayer.d.ts.map +1 -1
- package/dist/Layer/useLayer.js +158 -30
- package/dist/NavItem/navItemStyles.stylex.d.ts +17 -5
- package/dist/NavItem/navItemStyles.stylex.d.ts.map +1 -1
- package/dist/NavItem/navItemStyles.stylex.js +11 -5
- package/dist/RadioList/RadioListItem.d.ts.map +1 -1
- package/dist/RadioList/RadioListItem.js +5 -0
- package/dist/SideNav/SideNav.d.ts +7 -9
- package/dist/SideNav/SideNav.d.ts.map +1 -1
- package/dist/SideNav/SideNav.js +32 -5
- package/dist/SideNav/SideNavCollapseButton.d.ts +25 -9
- package/dist/SideNav/SideNavCollapseButton.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseButton.js +39 -15
- package/dist/SideNav/SideNavCollapseContext.d.ts +21 -0
- package/dist/SideNav/SideNavCollapseContext.d.ts.map +1 -1
- package/dist/SideNav/SideNavCollapseContext.js +16 -2
- package/dist/SideNav/SideNavHeading.d.ts.map +1 -1
- package/dist/SideNav/SideNavHeading.js +89 -32
- package/dist/SideNav/SideNavItem.d.ts +7 -2
- package/dist/SideNav/SideNavItem.d.ts.map +1 -1
- package/dist/SideNav/SideNavItem.js +119 -75
- package/dist/SideNav/SideNavSection.d.ts.map +1 -1
- package/dist/SideNav/SideNavSection.js +7 -16
- package/dist/SideNav/index.d.ts +1 -1
- package/dist/SideNav/index.d.ts.map +1 -1
- package/dist/Slider/Slider.d.ts.map +1 -1
- package/dist/Slider/Slider.js +56 -15
- package/dist/Switch/Switch.d.ts.map +1 -1
- package/dist/Switch/Switch.js +5 -0
- package/dist/Thumbnail/Thumbnail.d.ts.map +1 -1
- package/dist/Thumbnail/Thumbnail.js +5 -0
- package/dist/TopNav/TopNavHeading.d.ts.map +1 -1
- package/dist/TopNav/TopNavHeading.js +14 -6
- package/dist/TopNav/TopNavMegaMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMegaMenu.js +25 -93
- package/dist/TopNav/TopNavMegaMenuItem.js +1 -1
- package/dist/TopNav/TopNavMenu.d.ts.map +1 -1
- package/dist/TopNav/TopNavMenu.js +10 -5
- package/dist/astryx.css +40 -8
- package/dist/astryx.umd.js +50 -50
- package/dist/astryx.umd.js.map +4 -4
- package/dist/hooks/containerReveal.stylex.d.ts +71 -4
- package/dist/hooks/containerReveal.stylex.d.ts.map +1 -1
- package/dist/hooks/containerReveal.stylex.js +57 -5
- package/dist/hooks/index.d.ts +2 -2
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +1 -1
- package/dist/hooks/useContainerReveal.d.ts +60 -3
- package/dist/hooks/useContainerReveal.d.ts.map +1 -1
- package/dist/hooks/useContainerReveal.js +27 -5
- package/dist/hooks/useFocusTrap.d.ts.map +1 -1
- package/dist/hooks/useFocusTrap.js +16 -2
- package/dist/hooks/useMenuHover.d.ts +50 -5
- package/dist/hooks/useMenuHover.d.ts.map +1 -1
- package/dist/hooks/useMenuHover.js +171 -51
- package/dist/theme/defineTheme.d.ts +16 -5
- package/dist/theme/defineTheme.d.ts.map +1 -1
- package/dist/theme/defineTheme.js +44 -48
- package/dist/theme/derivedVarRegistry.d.ts.map +1 -1
- package/dist/theme/derivedVarRegistry.js +7 -0
- package/dist/theme/expandColorScale.d.ts.map +1 -1
- package/dist/theme/expandColorScale.js +1 -0
- package/dist/theme/expandMotionScale.d.ts +1 -0
- package/dist/theme/expandMotionScale.d.ts.map +1 -1
- package/dist/theme/expandMotionScale.js +1 -0
- package/dist/theme/expandRadiusScale.d.ts +1 -0
- package/dist/theme/expandRadiusScale.d.ts.map +1 -1
- package/dist/theme/expandRadiusScale.js +1 -0
- package/dist/theme/expandTypeScale.d.ts +1 -0
- package/dist/theme/expandTypeScale.d.ts.map +1 -1
- package/dist/theme/expandTypeScale.js +1 -0
- package/dist/theme/mergeComponents.d.ts +20 -0
- package/dist/theme/mergeComponents.d.ts.map +1 -0
- package/dist/theme/mergeComponents.js +56 -0
- package/dist/theme/onMediaTokens.d.ts +6 -1
- package/dist/theme/onMediaTokens.d.ts.map +1 -1
- package/dist/theme/onMediaTokens.js +11 -3
- package/dist/theme/tokens.stylex.d.ts +3 -1
- package/dist/theme/tokens.stylex.d.ts.map +1 -1
- package/dist/theme/tokens.stylex.js +3 -1
- package/locales/en.json +84 -0
- package/locales/pseudo.json +63 -0
- package/package.json +3 -3
- package/src/Avatar/Avatar.doc.mjs +5 -2
- package/src/Avatar/Avatar.test.tsx +51 -0
- package/src/Avatar/Avatar.tsx +30 -20
- package/src/AvatarGroup/AvatarGroup.doc.mjs +1 -1
- package/src/AvatarGroup/AvatarGroup.test.tsx +14 -0
- package/src/AvatarGroup/AvatarGroupOverflow.tsx +3 -2
- package/src/Button/Button.tsx +5 -3
- package/src/ButtonGroup/ButtonGroup.test.tsx +7 -4
- package/src/Card/Card.doc.mjs +2 -0
- package/src/Chat/Chat.doc.mjs +4 -1
- package/src/Chat/ChatMessage.doc.mjs +3 -3
- package/src/Chat/ChatMessage.tsx +7 -0
- package/src/Chat/ChatMessageBubble.doc.mjs +7 -0
- package/src/Chat/ChatMessageBubble.test.tsx +95 -6
- package/src/Chat/ChatMessageBubble.tsx +25 -0
- package/src/CheckboxInput/CheckboxInput.tsx +20 -0
- package/src/CodeBlock/CodeBlock.doc.mjs +3 -0
- package/src/CommandPalette/CommandPaletteFooter.tsx +6 -3
- package/src/DateTimeInput/DateTimeInput.tsx +4 -2
- package/src/DropdownMenu/DropdownMenuSubMenu.test.tsx +72 -1
- package/src/DropdownMenu/DropdownMenuSubMenu.tsx +24 -20
- package/src/HoverCard/HoverCard.doc.mjs +3 -3
- package/src/HoverCard/HoverCard.test.tsx +283 -55
- package/src/HoverCard/HoverCard.tsx +5 -13
- package/src/HoverCard/useHoverCard.tsx +8 -11
- package/src/InputGroup/groupStyles.ts +7 -8
- package/src/Item/Item.doc.mjs +4 -0
- package/src/Layer/layerHost.test.ts +99 -0
- package/src/Layer/layerHost.ts +141 -0
- package/src/Layer/useLayer.doc.mjs +14 -4
- package/src/Layer/useLayer.test.tsx +332 -7
- package/src/Layer/useLayer.tsx +235 -36
- package/src/MobileNav/MobileNav.doc.mjs +7 -0
- package/src/NavItem/navItemStyles.stylex.ts +31 -5
- package/src/RadioList/RadioListItem.tsx +20 -0
- package/src/SideNav/SideNav.doc.mjs +13 -4
- package/src/SideNav/SideNav.test.tsx +858 -2
- package/src/SideNav/SideNav.tsx +37 -16
- package/src/SideNav/SideNavCollapseButton.doc.mjs +28 -6
- package/src/SideNav/SideNavCollapseButton.tsx +67 -15
- package/src/SideNav/SideNavCollapseContext.ts +29 -9
- package/src/SideNav/SideNavHeading.tsx +53 -25
- package/src/SideNav/SideNavItem.doc.mjs +13 -0
- package/src/SideNav/SideNavItem.tsx +97 -84
- package/src/SideNav/SideNavSection.tsx +6 -20
- package/src/SideNav/index.ts +2 -0
- package/src/Slider/Slider.test.tsx +69 -33
- package/src/Slider/Slider.tsx +65 -13
- package/src/Switch/Switch.tsx +20 -0
- package/src/TabList/TabList.doc.mjs +3 -0
- package/src/Text/Text.doc.mjs +1 -1
- package/src/Thumbnail/Thumbnail.doc.mjs +3 -0
- package/src/Thumbnail/Thumbnail.tsx +10 -0
- package/src/Timestamp/Timestamp.test.tsx +40 -22
- package/src/Toolbar/Toolbar.test.tsx +14 -14
- package/src/TopNav/TopNav.test.tsx +86 -1
- package/src/TopNav/TopNavHeading.tsx +21 -14
- package/src/TopNav/TopNavMegaMenu.test.tsx +43 -0
- package/src/TopNav/TopNavMegaMenu.tsx +29 -105
- package/src/TopNav/TopNavMegaMenuItem.tsx +1 -1
- package/src/TopNav/TopNavMenu.test.tsx +41 -0
- package/src/TopNav/TopNavMenu.tsx +14 -4
- package/src/TreeList/TreeList.doc.mjs +1 -0
- package/src/hooks/containerReveal.stylex.ts +140 -9
- package/src/hooks/index.ts +6 -1
- package/src/hooks/useContainerReveal.doc.mjs +11 -5
- package/src/hooks/useContainerReveal.test.tsx +70 -3
- package/src/hooks/useContainerReveal.ts +86 -7
- package/src/hooks/useFocusTrap.test.tsx +16 -0
- package/src/hooks/useFocusTrap.ts +20 -2
- package/src/hooks/useMenuHover.test.tsx +364 -0
- package/src/hooks/useMenuHover.ts +243 -47
- package/src/theme/defineTheme.test.ts +127 -0
- package/src/theme/defineTheme.ts +57 -51
- package/src/theme/derivedVarRegistry.test.ts +78 -12
- package/src/theme/derivedVarRegistry.ts +4 -0
- package/src/theme/expandColorScale.ts +1 -0
- package/src/theme/expandMotionScale.ts +1 -0
- package/src/theme/expandRadiusScale.ts +1 -0
- package/src/theme/expandTypeScale.ts +1 -0
- package/src/theme/mergeComponents.ts +59 -0
- package/src/theme/onMediaTokens.ts +9 -2
- package/src/theme/themingTargets.test.ts +152 -26
- package/src/theme/tokens.stylex.ts +3 -1
- package/src/theme/tokens.test.ts +12 -0
- package/src/theme/useTheme.test.tsx +18 -0
package/src/theme/defineTheme.ts
CHANGED
|
@@ -28,6 +28,12 @@
|
|
|
28
28
|
* <App />
|
|
29
29
|
* </Theme>
|
|
30
30
|
* ```
|
|
31
|
+
*
|
|
32
|
+
* SYNC: `DefineThemeInput` is the theme surface. Adding, removing, or renaming
|
|
33
|
+
* a field means updating:
|
|
34
|
+
* - /packages/cli/assets/theme.template.ts (documents every field; the
|
|
35
|
+
* drift guard is scripts/check-theme-template.test.mjs)
|
|
36
|
+
* - /packages/cli/assets/docs/theme.doc.mjs (`astryx docs theme`)
|
|
31
37
|
*/
|
|
32
38
|
|
|
33
39
|
import type {IconRegistry} from '../Icon/globalIconRegistry';
|
|
@@ -42,6 +48,7 @@ import {
|
|
|
42
48
|
colorDefaults,
|
|
43
49
|
spacingDefaults,
|
|
44
50
|
sizeDefaults,
|
|
51
|
+
borderDefaults,
|
|
45
52
|
focusDefaults,
|
|
46
53
|
radiusDefaults,
|
|
47
54
|
shadowDefaults,
|
|
@@ -65,6 +72,7 @@ import type {DomainTokenName} from './domainTokens';
|
|
|
65
72
|
import {domainTokenDefaults} from './domainTokens';
|
|
66
73
|
import type {SyntaxThemeDefinition} from './syntax';
|
|
67
74
|
import {registerTheme} from './themeRegistry';
|
|
75
|
+
import {deepMergeComponents} from './mergeComponents';
|
|
68
76
|
|
|
69
77
|
// =============================================================================
|
|
70
78
|
// Types
|
|
@@ -75,6 +83,7 @@ export type CoreTokenName =
|
|
|
75
83
|
| keyof typeof colorDefaults
|
|
76
84
|
| keyof typeof spacingDefaults
|
|
77
85
|
| keyof typeof sizeDefaults
|
|
86
|
+
| keyof typeof borderDefaults
|
|
78
87
|
| keyof typeof focusDefaults
|
|
79
88
|
| keyof typeof radiusDefaults
|
|
80
89
|
| keyof typeof shadowDefaults
|
|
@@ -157,9 +166,14 @@ export interface DefineThemeInput {
|
|
|
157
166
|
name: string;
|
|
158
167
|
|
|
159
168
|
/**
|
|
160
|
-
* Base theme to extend. When provided, the new theme starts with
|
|
161
|
-
* base
|
|
162
|
-
*
|
|
169
|
+
* Base theme to extend. When provided, the new theme starts with everything
|
|
170
|
+
* the base resolved to — tokens, component overrides, icons, indicators, and
|
|
171
|
+
* its `onDark`/`onLight` surfaces — then applies this input on top. The base
|
|
172
|
+
* theme's values have lowest precedence.
|
|
173
|
+
*
|
|
174
|
+
* The result is flat: an extended theme carries its inheritance in its own
|
|
175
|
+
* resolved output, so `astryx theme build` emits one self-contained
|
|
176
|
+
* stylesheet and the base's CSS does not need to be loaded alongside it.
|
|
163
177
|
*
|
|
164
178
|
* Use this to create variant themes that customize only a few aspects
|
|
165
179
|
* (e.g. icons, accent color) without re-specifying the full theme.
|
|
@@ -379,6 +393,7 @@ export const tokenDefaults: Record<string, string> = {
|
|
|
379
393
|
...colorDefaults,
|
|
380
394
|
...spacingDefaults,
|
|
381
395
|
...sizeDefaults,
|
|
396
|
+
...borderDefaults,
|
|
382
397
|
...focusDefaults,
|
|
383
398
|
...radiusDefaults,
|
|
384
399
|
...shadowDefaults,
|
|
@@ -407,49 +422,6 @@ function resolveTokenValue(value: TokenValue): string {
|
|
|
407
422
|
return value;
|
|
408
423
|
}
|
|
409
424
|
|
|
410
|
-
/**
|
|
411
|
-
* Deep-merge two component style maps.
|
|
412
|
-
* Properties in `overrides` take precedence over `base`.
|
|
413
|
-
* This allows typeScale-generated rules to be overridden by explicit components.
|
|
414
|
-
*/
|
|
415
|
-
function deepMergeComponents(
|
|
416
|
-
base?: ComponentStyleMap,
|
|
417
|
-
overrides?: ComponentStyleMap,
|
|
418
|
-
): ComponentStyleMap | undefined {
|
|
419
|
-
if (!base && !overrides) {
|
|
420
|
-
return undefined;
|
|
421
|
-
}
|
|
422
|
-
if (!base) {
|
|
423
|
-
return overrides;
|
|
424
|
-
}
|
|
425
|
-
if (!overrides) {
|
|
426
|
-
return base;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const result: ComponentStyleMap = {};
|
|
430
|
-
|
|
431
|
-
// Start with all base entries
|
|
432
|
-
for (const [component, rules] of Object.entries(base)) {
|
|
433
|
-
result[component] = {...rules};
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Merge overrides on top
|
|
437
|
-
for (const [component, rules] of Object.entries(overrides)) {
|
|
438
|
-
if (!result[component]) {
|
|
439
|
-
result[component] = {...rules};
|
|
440
|
-
} else {
|
|
441
|
-
for (const [key, styles] of Object.entries(rules)) {
|
|
442
|
-
result[component][key] = {
|
|
443
|
-
...result[component][key],
|
|
444
|
-
...styles,
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
return result;
|
|
451
|
-
}
|
|
452
|
-
|
|
453
425
|
/**
|
|
454
426
|
* Resolve a FontWeight name to a var() reference.
|
|
455
427
|
* Named weights map to var(--font-weight-*); raw values pass through.
|
|
@@ -482,6 +454,24 @@ function buildFontFamily(
|
|
|
482
454
|
return quoted;
|
|
483
455
|
}
|
|
484
456
|
|
|
457
|
+
/**
|
|
458
|
+
* Describe a rejected `extends` value for the error message — enough to tell a
|
|
459
|
+
* missed import (`undefined`) from a module namespace or a plain object.
|
|
460
|
+
*/
|
|
461
|
+
function describeBadBase(value: unknown): string {
|
|
462
|
+
if (value === undefined) {
|
|
463
|
+
return 'undefined';
|
|
464
|
+
}
|
|
465
|
+
if (value === null) {
|
|
466
|
+
return 'null';
|
|
467
|
+
}
|
|
468
|
+
if (typeof value !== 'object') {
|
|
469
|
+
return typeof value;
|
|
470
|
+
}
|
|
471
|
+
const keys = Object.keys(value);
|
|
472
|
+
return `an object with keys [${keys.slice(0, 4).join(', ')}${keys.length > 4 ? ', …' : ''}]`;
|
|
473
|
+
}
|
|
474
|
+
|
|
485
475
|
/**
|
|
486
476
|
* Create an Astryx theme.
|
|
487
477
|
*
|
|
@@ -495,7 +485,19 @@ function buildFontFamily(
|
|
|
495
485
|
export function defineTheme(input: DefineThemeInput): DefinedTheme {
|
|
496
486
|
const tokens: Record<string, string> = {};
|
|
497
487
|
|
|
498
|
-
// 0. Pre-seed from base theme when `extends` is provided (lowest precedence)
|
|
488
|
+
// 0. Pre-seed from base theme when `extends` is provided (lowest precedence).
|
|
489
|
+
// A base that is not a theme is refused rather than ignored: `extends` used
|
|
490
|
+
// to inherit nothing when its value was undefined, which is what a named
|
|
491
|
+
// import silently resolving to the wrong module hands over, and the theme
|
|
492
|
+
// then built into a plausible-looking stylesheet missing everything it was
|
|
493
|
+
// supposed to inherit.
|
|
494
|
+
if ('extends' in input && !isDefinedTheme(input.extends)) {
|
|
495
|
+
throw new Error(
|
|
496
|
+
`defineTheme("${input.name}"): \`extends\` must be a theme from defineTheme(), got ${describeBadBase(input.extends)}. ` +
|
|
497
|
+
`Check that the import naming your base theme resolves to its source and exports that name — ` +
|
|
498
|
+
`a generated \`<theme>.js\` artifact sitting next to the source exports \`<name>Theme\`, not the source's own export.`,
|
|
499
|
+
);
|
|
500
|
+
}
|
|
499
501
|
const base = input.extends;
|
|
500
502
|
if (base) {
|
|
501
503
|
for (const [key, value] of Object.entries(base.tokens)) {
|
|
@@ -631,9 +633,10 @@ export function defineTheme(input: DefineThemeInput): DefinedTheme {
|
|
|
631
633
|
components = deepMergeComponents(base.components, components);
|
|
632
634
|
}
|
|
633
635
|
|
|
634
|
-
// 4. Resolve on-media token overrides (
|
|
635
|
-
|
|
636
|
-
const
|
|
636
|
+
// 4. Resolve on-media token overrides (base's resolved surface, then
|
|
637
|
+
// defaults, then this theme's own overrides)
|
|
638
|
+
const __onDark = resolveOnMedia('dark', input.onDark, base?.__onDark);
|
|
639
|
+
const __onLight = resolveOnMedia('light', input.onLight, base?.__onLight);
|
|
637
640
|
|
|
638
641
|
// 5. Merge icons — input icons override base icons
|
|
639
642
|
const icons =
|
|
@@ -654,7 +657,10 @@ export function defineTheme(input: DefineThemeInput): DefinedTheme {
|
|
|
654
657
|
components,
|
|
655
658
|
icons,
|
|
656
659
|
indicators,
|
|
657
|
-
__inputTokens:
|
|
660
|
+
__inputTokens:
|
|
661
|
+
base?.__inputTokens || input.tokens
|
|
662
|
+
? {...base?.__inputTokens, ...input.tokens}
|
|
663
|
+
: undefined,
|
|
658
664
|
__onDark,
|
|
659
665
|
__onLight,
|
|
660
666
|
};
|
|
@@ -10,6 +10,21 @@
|
|
|
10
10
|
* 2. Doc check: verifies each var is documented in the doc file's vars[]
|
|
11
11
|
* 3. Registry check: verifies themeable vars have derived[] entries that
|
|
12
12
|
* match the registry
|
|
13
|
+
*
|
|
14
|
+
* Layers 1 and 3 each used to carry a hole that let real vars through:
|
|
15
|
+
*
|
|
16
|
+
* - The source scan skipped every `--_*` name outright, on the theory that a
|
|
17
|
+
* private var is "internal, not themeable". It is internal, but it is still
|
|
18
|
+
* documented — `theming.vars[]` takes `private: true` for exactly this
|
|
19
|
+
* (`--_card-radius`, `--_dropdown-menu-padding`), and the derived-var
|
|
20
|
+
* pipeline is what theme authors reach it through. Skipping the prefix meant
|
|
21
|
+
* 10 private vars across 12 (component, var) sites were declared in source
|
|
22
|
+
* and documented nowhere.
|
|
23
|
+
* - Layer 3 narrowed its verdict to vars matching `/radius|padding/`, so any
|
|
24
|
+
* var whose name did not happen to contain those two words was exempt from
|
|
25
|
+
* ever needing a derived[] mapping. That is now an explicit allowlist
|
|
26
|
+
* (VARS_WITHOUT_DERIVED_MAPPING) instead of a name heuristic: a new var must
|
|
27
|
+
* be given a derived[] entry or be added to the list on purpose.
|
|
13
28
|
*/
|
|
14
29
|
|
|
15
30
|
import {describe, it, expect} from 'vitest';
|
|
@@ -75,7 +90,13 @@ const STRUCTURAL_VARS = new Set([
|
|
|
75
90
|
/**
|
|
76
91
|
* Extract component-specific CSS custom property names from a source file.
|
|
77
92
|
* Matches patterns like '--_card-radius': or '--_chat-composer-padding':
|
|
78
|
-
* Excludes structural/runtime vars and standard token vars (--color-*,
|
|
93
|
+
* Excludes structural/runtime vars and standard token vars (--color-*,
|
|
94
|
+
* --spacing-*, etc.).
|
|
95
|
+
*
|
|
96
|
+
* Private (`--_*`) vars are INCLUDED. They are internal in the sense that a
|
|
97
|
+
* theme author does not set them directly, but they are still part of the
|
|
98
|
+
* documented theming surface (`theming.vars[]` with `private: true`) and are
|
|
99
|
+
* how derived[] entries connect a standard CSS property to a component.
|
|
79
100
|
*/
|
|
80
101
|
function extractComponentVars(filePath: string): string[] {
|
|
81
102
|
const content = readFileSync(filePath, 'utf-8');
|
|
@@ -101,10 +122,6 @@ function extractComponentVars(filePath: string): string[] {
|
|
|
101
122
|
if (/^--(container-|layout-|edge-|component-)/.test(varName)) {
|
|
102
123
|
continue;
|
|
103
124
|
}
|
|
104
|
-
// Skip private vars (--_ prefix = internal, not themeable)
|
|
105
|
-
if (varName.startsWith('--_')) {
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
125
|
vars.add(varName);
|
|
109
126
|
}
|
|
110
127
|
return [...vars];
|
|
@@ -190,6 +207,7 @@ const DIR_TO_REGISTRY_KEY: Record<string, string> = {
|
|
|
190
207
|
Button: 'button',
|
|
191
208
|
Card: 'card',
|
|
192
209
|
Chat: 'chat',
|
|
210
|
+
ContextMenu: 'context-menu',
|
|
193
211
|
Dialog: 'dialog',
|
|
194
212
|
DropdownMenu: 'dropdown-menu',
|
|
195
213
|
Field: 'field',
|
|
@@ -213,8 +231,55 @@ const CROSS_COMPONENT_VARS: Record<string, string[]> = {
|
|
|
213
231
|
Carousel: ['--_button-radius'],
|
|
214
232
|
Thumbnail: ['--_button-radius'],
|
|
215
233
|
Chat: ['--_button-radius'],
|
|
234
|
+
// AvatarGroupOverflow sets the overlap for the Avatars it lays out; Avatar
|
|
235
|
+
// owns and documents it (and sets it itself when it is the group root).
|
|
236
|
+
AvatarGroup: ['--_avatar-group-overlap'],
|
|
237
|
+
// BreadcrumbItem tunes the DropdownMenu it opens.
|
|
238
|
+
Breadcrumbs: ['--_dropdown-menu-radius', '--_dropdown-menu-padding'],
|
|
239
|
+
// SelectableCard draws its selection ring through the Card shadow slot.
|
|
240
|
+
SelectableCard: ['--_card-ring'],
|
|
241
|
+
// Toolbar offsets the TabList indicator it hosts.
|
|
242
|
+
Toolbar: ['--_tab-indicator-bottom'],
|
|
243
|
+
// The destructive item variant recolors the Item it renders; Item owns,
|
|
244
|
+
// documents and reads both slots.
|
|
245
|
+
DropdownMenu: ['--_item-label-color', '--_item-description-color'],
|
|
216
246
|
};
|
|
217
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Documented vars that intentionally have NO derived[] entry — a theme author
|
|
250
|
+
* cannot reach them by writing a standard CSS property, only by targeting the
|
|
251
|
+
* component's own theming surface.
|
|
252
|
+
*
|
|
253
|
+
* This replaces a `/radius|padding/` name test that exempted every var whose
|
|
254
|
+
* name did not contain those words. Each entry is a deliberate classification,
|
|
255
|
+
* so a NEW var has to be argued into the list rather than slipping past on its
|
|
256
|
+
* name. The list should shrink over time, not grow.
|
|
257
|
+
*/
|
|
258
|
+
const VARS_WITHOUT_DERIVED_MAPPING = new Set([
|
|
259
|
+
// No standard CSS property maps onto these — they are component behaviors.
|
|
260
|
+
'--button-focus-offset',
|
|
261
|
+
'--button-icon-only-aspect',
|
|
262
|
+
'--_avatar-group-overlap',
|
|
263
|
+
'--_codeblock-gutter-width',
|
|
264
|
+
'--_tab-indicator-bottom',
|
|
265
|
+
// Hit-area outset on a ::after overlay — `inset` on a pseudo-element is not
|
|
266
|
+
// a property a theme author sets on the component.
|
|
267
|
+
'--_thumbnail-hit-inset',
|
|
268
|
+
// Indentation and row-spacing metrics: --tree-list-indent is the authorable
|
|
269
|
+
// step, --_tree-indent the per-row distance TreeListItem computes from it.
|
|
270
|
+
// --tree-list-row-gap is applied as half a padding-block on each row wrapper,
|
|
271
|
+
// not as gap on the list, so no standard property on the tree-list target
|
|
272
|
+
// maps onto it; a theme sets the var directly.
|
|
273
|
+
'--tree-list-indent',
|
|
274
|
+
'--_tree-indent',
|
|
275
|
+
'--tree-list-row-gap',
|
|
276
|
+
// Composed into a single box-shadow list on the card, so neither maps 1:1
|
|
277
|
+
// onto boxShadow — setting one through a derived entry would clobber the
|
|
278
|
+
// other.
|
|
279
|
+
'--_card-elevation',
|
|
280
|
+
'--_card-ring',
|
|
281
|
+
]);
|
|
282
|
+
|
|
218
283
|
// ---------------------------------------------------------------------------
|
|
219
284
|
// Tests
|
|
220
285
|
// ---------------------------------------------------------------------------
|
|
@@ -262,19 +327,20 @@ describe('component CSS vars are documented and themeable', () => {
|
|
|
262
327
|
return true;
|
|
263
328
|
});
|
|
264
329
|
|
|
265
|
-
//
|
|
266
|
-
// (
|
|
267
|
-
//
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
/radius|padding/.test(v),
|
|
330
|
+
// Everything that is not explicitly classified as unmappable must have
|
|
331
|
+
// a derived[] entry. (This was a `/radius|padding/` name test, which
|
|
332
|
+
// exempted any var whose name lacked those words.)
|
|
333
|
+
const themeableVars = missingDerived.filter(
|
|
334
|
+
v => !VARS_WITHOUT_DERIVED_MAPPING.has(v),
|
|
271
335
|
);
|
|
272
336
|
|
|
273
337
|
expect(
|
|
274
338
|
themeableVars,
|
|
275
339
|
`${dir} has vars that should be themeable via derived[]: ${themeableVars.join(', ')}. ` +
|
|
276
340
|
`Add derived[] entries in ${dir}.doc.mjs mapping standard CSS ` +
|
|
277
|
-
`properties (borderRadius, padding) to these internal vars
|
|
341
|
+
`properties (borderRadius, padding) to these internal vars — or, if ` +
|
|
342
|
+
`no standard property maps onto them, add them to ` +
|
|
343
|
+
`VARS_WITHOUT_DERIVED_MAPPING with the reason.`,
|
|
278
344
|
).toEqual([]);
|
|
279
345
|
});
|
|
280
346
|
}
|
|
@@ -57,6 +57,10 @@ export const derivedVarRegistry: Record<string, DerivedVarEntry[]> = {
|
|
|
57
57
|
{property: 'borderRadius', vars: ['--_dialog-radius']},
|
|
58
58
|
{property: 'padding', expand: 'container'},
|
|
59
59
|
],
|
|
60
|
+
'context-menu': [
|
|
61
|
+
{property: 'borderRadius', vars: ['--_dropdown-menu-radius']},
|
|
62
|
+
{property: 'padding', vars: ['--_dropdown-menu-padding']},
|
|
63
|
+
],
|
|
60
64
|
'dropdown-menu': [
|
|
61
65
|
{property: 'borderRadius', vars: ['--_dropdown-menu-radius']},
|
|
62
66
|
{property: 'padding', vars: ['--_dropdown-menu-padding']},
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
* SYNC: When modified, update:
|
|
19
19
|
* - /packages/core/src/theme/expandMotionScale.test.ts
|
|
20
20
|
* - /packages/core/src/theme/defineTheme.ts
|
|
21
|
+
* - /packages/cli/assets/theme.template.ts (the annotated field reference)
|
|
21
22
|
*/
|
|
22
23
|
|
|
23
24
|
// =============================================================================
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
* SYNC: When modified, update:
|
|
23
23
|
* - /packages/core/src/theme/expandRadiusScale.test.ts
|
|
24
24
|
* - /packages/core/src/theme/defineTheme.ts
|
|
25
|
+
* - /packages/cli/assets/theme.template.ts (the annotated field reference)
|
|
25
26
|
*/
|
|
26
27
|
|
|
27
28
|
// =============================================================================
|
|
@@ -44,6 +44,7 @@
|
|
|
44
44
|
* SYNC: When modified, update:
|
|
45
45
|
* - /packages/core/src/theme/expandTypeScale.test.ts
|
|
46
46
|
* - /packages/core/src/theme/defineTheme.ts
|
|
47
|
+
* - /packages/cli/assets/theme.template.ts (the annotated field reference)
|
|
47
48
|
*/
|
|
48
49
|
|
|
49
50
|
// =============================================================================
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file Component style-map merging
|
|
5
|
+
*
|
|
6
|
+
* One merge rule for component overrides, shared by every layer that composes
|
|
7
|
+
* them: `extends` inheritance, generated type-scale rules, and the on-media
|
|
8
|
+
* (`onDark`/`onLight`) surfaces. Merging is per style key, so a child that
|
|
9
|
+
* restates one property of `button.base` keeps the rest of the base's.
|
|
10
|
+
*
|
|
11
|
+
* @input two ComponentStyleMaps — the base and the overrides that win
|
|
12
|
+
* @output a merged ComponentStyleMap
|
|
13
|
+
* @position packages/core/src/theme/mergeComponents.ts
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type {ComponentStyleMap} from './defineTheme';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Deep-merge component style maps: `overrides` wins per style key, and every
|
|
20
|
+
* component and key the base declared that the overrides do not mention is
|
|
21
|
+
* carried through untouched.
|
|
22
|
+
*/
|
|
23
|
+
export function deepMergeComponents(
|
|
24
|
+
base?: ComponentStyleMap,
|
|
25
|
+
overrides?: ComponentStyleMap,
|
|
26
|
+
): ComponentStyleMap | undefined {
|
|
27
|
+
if (!base && !overrides) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (!base) {
|
|
31
|
+
return overrides;
|
|
32
|
+
}
|
|
33
|
+
if (!overrides) {
|
|
34
|
+
return base;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const result: ComponentStyleMap = {};
|
|
38
|
+
|
|
39
|
+
// Start with all base entries
|
|
40
|
+
for (const [component, rules] of Object.entries(base)) {
|
|
41
|
+
result[component] = {...rules};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Merge overrides on top
|
|
45
|
+
for (const [component, rules] of Object.entries(overrides)) {
|
|
46
|
+
if (!result[component]) {
|
|
47
|
+
result[component] = {...rules};
|
|
48
|
+
} else {
|
|
49
|
+
for (const [key, styles] of Object.entries(rules)) {
|
|
50
|
+
result[component][key] = {
|
|
51
|
+
...result[component][key],
|
|
52
|
+
...styles,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
|
|
23
23
|
import type {TokenValue, ComponentStyleMap} from './defineTheme';
|
|
24
|
+
import {deepMergeComponents} from './mergeComponents';
|
|
24
25
|
|
|
25
26
|
/**
|
|
26
27
|
* On-media theme overrides — same shape as the main theme but scoped
|
|
@@ -89,15 +90,21 @@ function resolveValue(value: TokenValue): string {
|
|
|
89
90
|
/**
|
|
90
91
|
* Resolve on-media overrides: merge user tokens with defaults,
|
|
91
92
|
* pass through component overrides.
|
|
93
|
+
*
|
|
94
|
+
* `base` is the already-resolved surface of a theme being extended. It sits
|
|
95
|
+
* between the defaults and this theme's own input, so a child theme inherits
|
|
96
|
+
* the surface customizations of the theme it extends instead of silently
|
|
97
|
+
* reverting them to the defaults.
|
|
92
98
|
*/
|
|
93
99
|
export function resolveOnMedia(
|
|
94
100
|
surface: 'dark' | 'light',
|
|
95
101
|
input?: OnMediaOverrides,
|
|
102
|
+
base?: ResolvedOnMedia,
|
|
96
103
|
): ResolvedOnMedia {
|
|
97
104
|
const defaults =
|
|
98
105
|
surface === 'dark' ? defaultOnDarkTokens : defaultOnLightTokens;
|
|
99
106
|
|
|
100
|
-
const tokens = {...defaults};
|
|
107
|
+
const tokens = {...defaults, ...base?.tokens};
|
|
101
108
|
|
|
102
109
|
if (input?.tokens) {
|
|
103
110
|
for (const [key, value] of Object.entries(input.tokens)) {
|
|
@@ -109,6 +116,6 @@ export function resolveOnMedia(
|
|
|
109
116
|
|
|
110
117
|
return {
|
|
111
118
|
tokens,
|
|
112
|
-
components: input?.components,
|
|
119
|
+
components: deepMergeComponents(base?.components, input?.components),
|
|
113
120
|
};
|
|
114
121
|
}
|