@happyvertical/smrt-ui 0.55.16 → 0.55.18

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.
Files changed (166) hide show
  1. package/AGENTS.md +14 -0
  2. package/README.md +75 -0
  3. package/dist/components/calendar/Calendar.svelte +59 -56
  4. package/dist/components/calendar/CalendarView.svelte +80 -77
  5. package/dist/components/calendar/DayView.svelte +39 -36
  6. package/dist/components/chat/MessageBubble.svelte +26 -23
  7. package/dist/components/chat/ReactionPicker.svelte +12 -9
  8. package/dist/components/chat/TypingIndicator.svelte +12 -9
  9. package/dist/components/data/CollectionList.svelte +11 -8
  10. package/dist/components/data/CollectionToolbar.svelte +5 -2
  11. package/dist/components/data/DataTable.svelte +74 -59
  12. package/dist/components/data/ListSortSelect.svelte +8 -5
  13. package/dist/components/data/SortableHeader.svelte +10 -7
  14. package/dist/components/display/ConfidenceBadge.svelte +11 -8
  15. package/dist/components/display/CurrencyDisplay.svelte +7 -4
  16. package/dist/components/display/DateDisplay.svelte +6 -3
  17. package/dist/components/display/Icon.svelte +4 -1
  18. package/dist/components/display/StatusBadge.svelte +9 -6
  19. package/dist/components/feedback/Alert.svelte +12 -8
  20. package/dist/components/feedback/ConfirmDialog.svelte +13 -10
  21. package/dist/components/feedback/LoadingOverlay.svelte +37 -34
  22. package/dist/components/feedback/Meter.svelte +4 -1
  23. package/dist/components/feedback/Modal.svelte +26 -21
  24. package/dist/components/feedback/PhoneSheet.svelte +6 -3
  25. package/dist/components/feedback/Progress.svelte +8 -5
  26. package/dist/components/feedback/ProgressBar.svelte +13 -10
  27. package/dist/components/feedback/Spinner.svelte +4 -1
  28. package/dist/components/feedback/ToastViewport.svelte +18 -15
  29. package/dist/components/feedback/WorkingStrip.svelte +6 -3
  30. package/dist/components/forms/CameraCapture.svelte +8 -5
  31. package/dist/components/forms/Checkbox.svelte +11 -8
  32. package/dist/components/forms/Combobox.svelte +5 -2
  33. package/dist/components/forms/DatePicker.svelte +7 -2
  34. package/dist/components/forms/DictationButton.svelte +17 -14
  35. package/dist/components/forms/DictationStatus.svelte +12 -9
  36. package/dist/components/forms/ErrorSummary.svelte +4 -1
  37. package/dist/components/forms/FieldLabel.svelte +9 -6
  38. package/dist/components/forms/Fieldset.svelte +4 -1
  39. package/dist/components/forms/FilePicker.svelte +5 -2
  40. package/dist/components/forms/Form.svelte +1 -1
  41. package/dist/components/forms/FormActionBar.svelte +4 -1
  42. package/dist/components/forms/FormGroup.svelte +6 -3
  43. package/dist/components/forms/Input.svelte +22 -15
  44. package/dist/components/forms/InputGroup.svelte +6 -3
  45. package/dist/components/forms/Listbox.svelte +5 -2
  46. package/dist/components/forms/MultiSelect.svelte +4 -1
  47. package/dist/components/forms/Radio.svelte +9 -6
  48. package/dist/components/forms/RadioGroup.svelte +5 -2
  49. package/dist/components/forms/RangeSlider.svelte +5 -2
  50. package/dist/components/forms/RelationInput.svelte +7 -4
  51. package/dist/components/forms/SearchInput.svelte +7 -4
  52. package/dist/components/forms/SegmentedControl.svelte +6 -3
  53. package/dist/components/forms/Select.svelte +20 -13
  54. package/dist/components/forms/SignaturePad.svelte +8 -5
  55. package/dist/components/forms/Slider.svelte +5 -2
  56. package/dist/components/forms/StagedControlReview.svelte +15 -12
  57. package/dist/components/forms/Switch.svelte +8 -5
  58. package/dist/components/forms/TagsInput.svelte +5 -2
  59. package/dist/components/forms/Textarea.svelte +20 -13
  60. package/dist/components/forms/TimePicker.svelte +7 -2
  61. package/dist/components/forms/Toggle.svelte +14 -11
  62. package/dist/components/forms/ToggleButton.svelte +5 -2
  63. package/dist/components/layout/ActionGroup.svelte +4 -1
  64. package/dist/components/layout/Container.svelte +4 -1
  65. package/dist/components/layout/EmptyState.svelte +11 -8
  66. package/dist/components/layout/Footer.svelte +4 -1
  67. package/dist/components/layout/Grid.svelte +13 -10
  68. package/dist/components/layout/Header.svelte +4 -1
  69. package/dist/components/layout/Masthead.svelte +4 -1
  70. package/dist/components/layout/PageHeader.svelte +12 -9
  71. package/dist/components/layout/PageLayout.svelte +3 -0
  72. package/dist/components/layout/SummaryCard.svelte +9 -6
  73. package/dist/components/memberships/MembershipCard.svelte +5 -2
  74. package/dist/components/memberships/MembershipList.svelte +5 -2
  75. package/dist/components/nav/FilterChips.svelte +13 -10
  76. package/dist/components/nav/Tabs.svelte +75 -41
  77. package/dist/components/permissions/PermissionPicker.svelte +5 -2
  78. package/dist/components/roles/RoleBadge.svelte +8 -5
  79. package/dist/components/roles/RoleSelector.svelte +22 -19
  80. package/dist/components/theme/ThemeProvider.svelte +7 -4
  81. package/dist/components/ui/Accordion.svelte +4 -1
  82. package/dist/components/ui/AccordionItem.svelte +5 -2
  83. package/dist/components/ui/Avatar.svelte +10 -7
  84. package/dist/components/ui/Badge.svelte +6 -2
  85. package/dist/components/ui/Button.svelte +49 -37
  86. package/dist/components/ui/Card.svelte +18 -14
  87. package/dist/components/ui/Chip.svelte +41 -14
  88. package/dist/components/ui/Disclosure.svelte +5 -2
  89. package/dist/components/ui/Dropdown.svelte +33 -25
  90. package/dist/components/ui/IconToggle.svelte +12 -9
  91. package/dist/components/ui/IconToggle.svelte.d.ts +1 -1
  92. package/dist/components/ui/IconToggle.svelte.d.ts.map +1 -1
  93. package/dist/components/ui/IconToggleGroup.svelte +8 -5
  94. package/dist/components/ui/Pagination.svelte +11 -8
  95. package/dist/components/ui/Popover.svelte +9 -6
  96. package/dist/components/ui/Skeleton.svelte +10 -7
  97. package/dist/components/ui/Tooltip.svelte +13 -9
  98. package/dist/components/ui/Tree.svelte +12 -9
  99. package/dist/styles/layers.css +8 -0
  100. package/dist/styles/layers.d.ts +9 -0
  101. package/dist/styles/layers.d.ts.map +1 -0
  102. package/dist/styles/layers.js +8 -0
  103. package/dist/styles/tokens.css +27 -69
  104. package/dist/styles/unstyled.css +65 -0
  105. package/dist/svelte/playground/BaseControlsPreview.svelte +3 -0
  106. package/dist/svelte/playground/CalendarPreview.svelte +5 -2
  107. package/dist/svelte/playground/CapturePreview.svelte +3 -0
  108. package/dist/svelte/playground/CodeSelectorsPreview.svelte +3 -0
  109. package/dist/svelte/playground/CollectionsPreview.svelte +3 -0
  110. package/dist/svelte/playground/DataTablePreview.svelte +3 -0
  111. package/dist/svelte/playground/FeedbackPreview.svelte +3 -0
  112. package/dist/svelte/playground/InteractiveControlsPreview.svelte +3 -0
  113. package/dist/svelte/playground/StylelessPreview.svelte +110 -0
  114. package/dist/svelte/playground/StylelessPreview.svelte.d.ts +4 -0
  115. package/dist/svelte/playground/StylelessPreview.svelte.d.ts.map +1 -0
  116. package/dist/svelte/playground.d.ts.map +1 -1
  117. package/dist/svelte/playground.js +10 -0
  118. package/dist/test-support/setup.js +9 -0
  119. package/dist/theme/ThemeProvider.svelte +3 -0
  120. package/dist/themes/CUSTOM_THEME_GUIDE.md +40 -0
  121. package/dist/themes/README.md +151 -0
  122. package/dist/themes/ThemeProvider.svelte +181 -12
  123. package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
  124. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  125. package/dist/themes/components/ColorSchemeToggle.svelte +17 -14
  126. package/dist/themes/components/ThemeSwitcher.svelte +14 -11
  127. package/dist/themes/create-theme.d.ts +22 -1
  128. package/dist/themes/create-theme.d.ts.map +1 -1
  129. package/dist/themes/create-theme.js +100 -9
  130. package/dist/themes/css-generator.d.ts.map +1 -1
  131. package/dist/themes/css-generator.js +20 -20
  132. package/dist/themes/derive.d.ts +31 -0
  133. package/dist/themes/derive.d.ts.map +1 -0
  134. package/dist/themes/derive.js +183 -0
  135. package/dist/themes/glass/index.d.ts.map +1 -1
  136. package/dist/themes/glass/index.js +127 -14
  137. package/dist/themes/happyvertical/index.d.ts.map +1 -1
  138. package/dist/themes/happyvertical/index.js +101 -20
  139. package/dist/themes/index.d.ts +2 -2
  140. package/dist/themes/index.d.ts.map +1 -1
  141. package/dist/themes/index.js +1 -1
  142. package/dist/themes/material/index.d.ts.map +1 -1
  143. package/dist/themes/material/index.js +129 -28
  144. package/dist/themes/required-tokens.generated.json +201 -0
  145. package/dist/themes/shared.d.ts +35 -0
  146. package/dist/themes/shared.d.ts.map +1 -1
  147. package/dist/themes/shared.js +32 -0
  148. package/dist/themes/smrt/index.d.ts +3 -0
  149. package/dist/themes/smrt/index.d.ts.map +1 -1
  150. package/dist/themes/smrt/index.js +108 -26
  151. package/dist/themes/studio/index.d.ts.map +1 -1
  152. package/dist/themes/studio/index.js +116 -40
  153. package/dist/themes/styles/base.css +229 -1
  154. package/dist/themes/styles/glass.css +107 -20
  155. package/dist/themes/styles/happyvertical.css +111 -15
  156. package/dist/themes/styles/material.css +127 -35
  157. package/dist/themes/styles/smrt.css +126 -31
  158. package/dist/themes/styles/studio.css +132 -37
  159. package/dist/themes/theme-script.d.ts +3 -1
  160. package/dist/themes/theme-script.d.ts.map +1 -1
  161. package/dist/themes/theme-script.js +7 -2
  162. package/dist/themes/types.d.ts +118 -6
  163. package/dist/themes/types.d.ts.map +1 -1
  164. package/dist/utils/theme/typography.d.ts +21 -21
  165. package/dist/utils/theme/typography.js +21 -21
  166. package/package.json +8 -2
@@ -3,7 +3,8 @@
3
3
  *
4
4
  * Helper functions for creating custom themes that match the SMRT theme system.
5
5
  */
6
- import { hexToRgb, rgbToHex } from '../utils/theme/color.js';
6
+ import { generateTheme, getContrastRatio, hexToRgb, rgbToHex, } from '../utils/theme/color.js';
7
+ import { baselineTokens, opaqueGlass } from './derive.js';
7
8
  import { hasCustomTheme, registerCustomTheme, themes as registeredThemes, } from './registry.js';
8
9
  import { borderRadiusScale, durationScale, materialEasing, spacingScale, } from './shared.js';
9
10
  /**
@@ -22,6 +23,10 @@ const defaultColorStructure = {
22
23
  onTertiary: '#ffffff',
23
24
  tertiaryContainer: '#ede9fe',
24
25
  onTertiaryContainer: '#3b0764',
26
+ info: '#8b5cf6',
27
+ onInfo: '#ffffff',
28
+ infoContainer: '#ede9fe',
29
+ onInfoContainer: '#3b0764',
25
30
  error: '#dc2626',
26
31
  onError: '#ffffff',
27
32
  errorContainer: '#fee2e2',
@@ -86,12 +91,15 @@ function generateDarkColors(light) {
86
91
  }
87
92
  return rgbToHex(r * factor, g * factor, b * factor);
88
93
  };
94
+ // Multiplying a very dark brand seed stays dark. Use tonal primary pairs so
95
+ // icons and labels keep contrast against their containers in dark mode.
96
+ const primary = generateTheme(light.primary, 'dark');
89
97
  return {
90
98
  ...light,
91
- primary: light.inversePrimary || adjustBrightness(light.primary, 1.2),
92
- onPrimary: adjustBrightness(light.onPrimary, 0.8),
93
- primaryContainer: adjustBrightness(light.primaryContainer || light.primary, 0.3),
94
- onPrimaryContainer: adjustBrightness(light.onPrimaryContainer || light.onPrimary, 1.2),
99
+ primary: primary.primary,
100
+ onPrimary: primary.onPrimary,
101
+ primaryContainer: primary.primaryContainer,
102
+ onPrimaryContainer: primary.onPrimaryContainer,
95
103
  secondary: adjustBrightness(light.secondary, 1.3),
96
104
  onSecondary: adjustBrightness(light.onSecondary, 0.8),
97
105
  secondaryContainer: adjustBrightness(light.secondaryContainer, 0.3),
@@ -234,6 +242,27 @@ function mergeTypography(custom) {
234
242
  labelSmall: { ...defaults.labelSmall, ...custom.labelSmall },
235
243
  };
236
244
  }
245
+ /**
246
+ * The info family follows an explicitly supplied tertiary unless the caller
247
+ * also supplied a distinct info hue; otherwise it keeps the extended preset's
248
+ * (or the default) info. Presets without a distinct info hue define it as their
249
+ * tertiary, so a custom tertiary carries the Information status with it.
250
+ */
251
+ function withInfoFromTertiary(palette, partial) {
252
+ if (partial?.tertiary === undefined)
253
+ return palette;
254
+ return {
255
+ ...palette,
256
+ info: partial.info ?? palette.tertiary,
257
+ onInfo: partial.onInfo ?? palette.onTertiary,
258
+ infoContainer: partial.infoContainer ?? palette.tertiaryContainer,
259
+ onInfoContainer: partial.onInfoContainer ?? palette.onTertiaryContainer,
260
+ };
261
+ }
262
+ /** Drop undefined entries so they cannot clobber derived defaults. */
263
+ function definedOnly(value) {
264
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== undefined));
265
+ }
237
266
  /**
238
267
  * Create a custom theme
239
268
  *
@@ -265,7 +294,7 @@ export function createTheme(options) {
265
294
  // Get base theme if extending (lazy import to avoid circular deps)
266
295
  const baseTheme = extend ? getBaseThemeSync(extend) : null;
267
296
  // Build light colors
268
- const lightColors = {
297
+ const lightColors = withInfoFromTertiary({
269
298
  ...defaultColorStructure,
270
299
  ...baseTheme?.light,
271
300
  ...lightPartial,
@@ -277,11 +306,12 @@ export function createTheme(options) {
277
306
  `${normalizeHex(lightPartial.primary)}20`,
278
307
  onPrimaryContainer: lightPartial.onPrimaryContainer || lightPartial.primary,
279
308
  inversePrimary: lightPartial.inversePrimary || lightPartial.primary,
280
- };
309
+ }, lightPartial);
281
310
  // Build dark colors
282
311
  const darkColors = darkPartial
283
- ? { ...lightColors, ...darkPartial }
312
+ ? withInfoFromTertiary({ ...lightColors, ...darkPartial }, darkPartial)
284
313
  : generateDarkColors(lightColors);
314
+ const baseline = baselineTokens(lightColors, darkColors);
285
315
  return {
286
316
  id: id,
287
317
  name,
@@ -299,7 +329,22 @@ export function createTheme(options) {
299
329
  ...(baseTheme?.easing || materialEasing),
300
330
  ...options.easing,
301
331
  },
302
- glass: options.glass,
332
+ glass: options.glass ?? baseTheme?.glass ?? { ...opaqueGlass },
333
+ ...baseline,
334
+ ...definedOnly({
335
+ surfaces: options.surfaces,
336
+ hairlines: options.hairlines,
337
+ states: options.states && { ...baseline.states, ...options.states },
338
+ shadowColor: options.shadowColor,
339
+ controls: options.controls && {
340
+ ...baseline.controls,
341
+ ...options.controls,
342
+ },
343
+ focus: options.focus && { ...baseline.focus, ...options.focus },
344
+ native: options.native,
345
+ schemeDuration: options.schemeDuration,
346
+ fontFeatureSettings: options.fontFeatureSettings,
347
+ }),
303
348
  fontFamily: options.fontFamily || baseTheme?.fontFamily || 'system-ui, sans-serif',
304
349
  };
305
350
  }
@@ -394,3 +439,49 @@ export function createThemeFromColor(primaryColor, id, name, options) {
394
439
  });
395
440
  }
396
441
  // Types are exported from index.ts
442
+ /**
443
+ * Derive the primary-dependent token overrides for a user-chosen primary colour
444
+ * in one resolved scheme, using the same derivation as `createThemeFromColor`
445
+ * (light: the colour itself with a ~12.5% container; dark: the tonal primary
446
+ * pair from `generateTheme`). `on-primary` is the higher-contrast of white and
447
+ * black on the final primary so an override never leaves it mismatched. Focus,
448
+ * accent, caret and selection tokens follow the primary via `var()`.
449
+ *
450
+ * Returns `--smrt-*` declarations; throws on an unparseable colour.
451
+ */
452
+ export function derivePrimaryOverrides(color, scheme) {
453
+ const seed = normalizeHex(color);
454
+ let primary = seed;
455
+ let onPrimary;
456
+ let container = `${seed}20`;
457
+ let onContainer = seed;
458
+ if (scheme === 'dark') {
459
+ const tonal = generateTheme(seed, 'dark');
460
+ primary = tonal.primary;
461
+ onPrimary = tonal.onPrimary;
462
+ container = tonal.primaryContainer;
463
+ onContainer = tonal.onPrimaryContainer;
464
+ }
465
+ else {
466
+ onPrimary = '#ffffff';
467
+ }
468
+ const bg = hexToRgb(primary);
469
+ const white = getContrastRatio(hexToRgb('#ffffff'), bg);
470
+ const black = getContrastRatio(hexToRgb('#000000'), bg);
471
+ const tonalOn = hexToRgb(onPrimary);
472
+ if (getContrastRatio(tonalOn, bg) < 4.5) {
473
+ onPrimary = white >= black ? '#ffffff' : '#000000';
474
+ }
475
+ return {
476
+ '--smrt-color-primary': primary,
477
+ '--smrt-color-on-primary': onPrimary,
478
+ '--smrt-color-primary-container': container,
479
+ '--smrt-color-on-primary-container': onContainer,
480
+ '--smrt-color-inverse-primary': seed,
481
+ '--smrt-focus-ring-color': 'var(--smrt-color-primary)',
482
+ '--smrt-accent-color': 'var(--smrt-color-primary)',
483
+ '--smrt-caret-color': 'var(--smrt-color-primary)',
484
+ '--smrt-selection-background': 'var(--smrt-color-primary-container)',
485
+ '--smrt-selection-color': 'var(--smrt-color-on-primary-container)',
486
+ };
487
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"css-generator.d.ts","sourceRoot":"","sources":["../../src/themes/css-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAWH,OAAO,KAAK,EAQV,KAAK,EAEN,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAwMD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA6CxB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D"}
1
+ {"version":3,"file":"css-generator.d.ts","sourceRoot":"","sources":["../../src/themes/css-generator.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,OAAO,KAAK,EAOV,KAAK,EAEN,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,4CAA4C;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAgMD;;;;;;;;;;;GAWG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CA+CxB;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CACpC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAChC,MAAM,CAIR;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,OAAO,GAAE,kBAAuB,GAC/B,MAAM,CAaR;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC,CAS5D"}
@@ -4,6 +4,7 @@
4
4
  * Converts theme definitions into CSS custom properties for runtime theming.
5
5
  * Generates CSS variables for colors, typography, spacing, elevation, etc.
6
6
  */
7
+ import { generateBaselineVariables } from './derive.js';
7
8
  import { themes } from './registry.js';
8
9
  import { borderRadiusAliases, durationAliases, fontFamilyAliases, fontWeightTokens, spacingAliases, zIndexTokens, } from './shared.js';
9
10
  /**
@@ -11,7 +12,16 @@ import { borderRadiusAliases, durationAliases, fontFamilyAliases, fontWeightToke
11
12
  */
12
13
  function generateColorVariables(colors, prefix) {
13
14
  const vars = {};
14
- for (const [key, value] of Object.entries(colors)) {
15
+ const withInfo = {
16
+ ...colors,
17
+ info: colors.info ?? colors.tertiary,
18
+ onInfo: colors.onInfo ?? colors.onTertiary,
19
+ infoContainer: colors.infoContainer ?? colors.tertiaryContainer,
20
+ onInfoContainer: colors.onInfoContainer ?? colors.onTertiaryContainer,
21
+ };
22
+ for (const [key, value] of Object.entries(withInfo)) {
23
+ if (value === undefined)
24
+ continue;
15
25
  // Convert camelCase to kebab-case: primaryContainer -> primary-container
16
26
  const cssKey = key.replace(/([A-Z])/g, '-$1').toLowerCase();
17
27
  vars[`${prefix}-color-${cssKey}`] = value;
@@ -29,14 +39,15 @@ function generateTypographyVariables(typography, prefix) {
29
39
  vars[`${prefix}-typography-${cssKey}-line-height`] = token.lineHeight;
30
40
  vars[`${prefix}-typography-${cssKey}-weight`] = String(token.weight);
31
41
  vars[`${prefix}-typography-${cssKey}-tracking`] = token.tracking;
32
- if (token.fontFamily) {
33
- vars[`${prefix}-typography-${cssKey}-font-family`] = token.fontFamily;
34
- }
42
+ // Always emit the longhand (issue #3826): a token without its own family
43
+ // inherits the theme body font, so the longhand is defined for every theme
44
+ // and components never depend on an unset custom property.
45
+ const family = token.fontFamily ?? `var(${prefix}-font-family)`;
46
+ vars[`${prefix}-typography-${cssKey}-font-family`] = family;
35
47
  // Additive `font` shorthand alias (issue #1431): components use
36
48
  // `font: var(--smrt-typography-body-medium-font)`, expecting the CSS
37
49
  // `font` shorthand order (style variant size/line-height family). Built
38
50
  // from the emitted longhands so it stays in sync.
39
- const family = token.fontFamily ?? `var(${prefix}-font-family)`;
40
51
  vars[`${prefix}-typography-${cssKey}-font`] =
41
52
  `${token.weight} ${token.size}/${token.lineHeight} ${family}`;
42
53
  }
@@ -113,19 +124,6 @@ function generateEasingVariables(easing, prefix) {
113
124
  }
114
125
  return vars;
115
126
  }
116
- /**
117
- * Generate CSS custom properties for glass effects
118
- */
119
- function generateGlassVariables(glass, prefix) {
120
- if (!glass)
121
- return {};
122
- return {
123
- [`${prefix}-glass-blur`]: glass.blur,
124
- [`${prefix}-glass-saturation`]: glass.saturation,
125
- [`${prefix}-glass-border-opacity`]: glass.borderOpacity,
126
- [`${prefix}-glass-background-opacity`]: glass.backgroundOpacity,
127
- };
128
- }
129
127
  /**
130
128
  * Generate helper tokens that do not vary by color scheme (issue #1431).
131
129
  *
@@ -197,8 +195,10 @@ export function generateThemeVariables(theme, isDark, options = {}) {
197
195
  ...generateEasingVariables(theme.easing, prefix),
198
196
  // Theme-independent helper tokens (mono font, named weights, z-index)
199
197
  ...generateStaticTokens(prefix, theme.fontFamilyMono),
200
- // Glass effects (if present)
201
- ...generateGlassVariables(theme.glass, prefix),
198
+ // Premium baseline: surface ladder, hairlines, state layers, shadow colour,
199
+ // control geometry, focus ring, native chrome, glass, scheme motion, font
200
+ // features (#3865)
201
+ ...generateBaselineVariables(theme, isDark, prefix),
202
202
  };
203
203
  }
204
204
  /**
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Premium-baseline token derivation (epic #3864, issue #3865).
3
+ *
4
+ * Resolves the surface ladder, hairlines, state layers, shadow colour, control
5
+ * geometry, focus ring, native chrome, glass and motion/typography tokens for
6
+ * one theme and scheme. Presets set explicit values on `Theme`; anything a theme
7
+ * omits (custom themes, `createTheme()`) is derived here from its palette so
8
+ * every theme always defines every token.
9
+ */
10
+ import type { ColorPalette, ControlTokens, FocusRingTokens, GlassEffects, GlassSurfaces, HairlineTokens, NativeChromeTokens, StateLayers, SurfaceLadder, Theme } from './types.js';
11
+ export declare const defaultStateLayers: StateLayers;
12
+ export declare const defaultSchemeDuration = "var(--smrt-duration-medium1)";
13
+ export declare const defaultFontFeatureSettings = "normal";
14
+ /** `rgb(r g b)` from any palette colour; black when it cannot be parsed. */
15
+ export declare function toRgbTriplet(color: string): string;
16
+ export declare function deriveSurfaces(colors: ColorPalette, isDark: boolean): SurfaceLadder;
17
+ export declare function deriveHairlines(isDark: boolean, prefix?: string): HairlineTokens;
18
+ export declare function deriveControls(prefix?: string): ControlTokens;
19
+ export declare function deriveFocus(prefix?: string): FocusRingTokens;
20
+ export declare function deriveNative(prefix?: string): NativeChromeTokens;
21
+ export declare function deriveGlassSurfaces(prefix?: string): GlassSurfaces;
22
+ /** Opaque glass default for themes that are not glass. */
23
+ export declare const opaqueGlass: GlassEffects;
24
+ /**
25
+ * Complete set of baseline sections derived from a palette pair. Presets spread
26
+ * this and override what they tune; `createTheme()` uses it directly.
27
+ */
28
+ export declare function baselineTokens(light: ColorPalette, dark: ColorPalette): Pick<Theme, 'surfaces' | 'hairlines' | 'states' | 'shadowColor' | 'controls' | 'focus' | 'native' | 'schemeDuration' | 'fontFeatureSettings'>;
29
+ /** Flat `--prefix-*` map for the premium-baseline families. */
30
+ export declare function generateBaselineVariables(theme: Theme, isDark: boolean, prefix: string): Record<string, string>;
31
+ //# sourceMappingURL=derive.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"derive.d.ts","sourceRoot":"","sources":["../../src/themes/derive.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EACV,YAAY,EACZ,aAAa,EACb,eAAe,EACf,YAAY,EACZ,aAAa,EACb,cAAc,EACd,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,KAAK,EACN,MAAM,YAAY,CAAC;AAEpB,eAAO,MAAM,kBAAkB,EAAE,WAOhC,CAAC;AAEF,eAAO,MAAM,qBAAqB,iCAAiC,CAAC;AACpE,eAAO,MAAM,0BAA0B,WAAW,CAAC;AAEnD,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQlD;AAED,wBAAgB,cAAc,CAC5B,MAAM,EAAE,YAAY,EACpB,MAAM,EAAE,OAAO,GACd,aAAa,CAaf;AAED,wBAAgB,eAAe,CAC7B,MAAM,EAAE,OAAO,EACf,MAAM,SAAW,GAChB,cAAc,CAQhB;AAED,wBAAgB,cAAc,CAAC,MAAM,SAAW,GAAG,aAAa,CAS/D;AAED,wBAAgB,WAAW,CAAC,MAAM,SAAW,GAAG,eAAe,CAM9D;AAED,wBAAgB,YAAY,CAAC,MAAM,SAAW,GAAG,kBAAkB,CASlE;AAED,wBAAgB,mBAAmB,CAAC,MAAM,SAAW,GAAG,aAAa,CAMpE;AAED,0DAA0D;AAC1D,eAAO,MAAM,WAAW,EAAE,YAAkD,CAAC;AAE7E;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,IAAI,EAAE,YAAY,GACjB,IAAI,CACL,KAAK,EACH,UAAU,GACV,WAAW,GACX,QAAQ,GACR,aAAa,GACb,UAAU,GACV,OAAO,GACP,QAAQ,GACR,gBAAgB,GAChB,qBAAqB,CACxB,CAkBA;AAED,+DAA+D;AAC/D,wBAAgB,yBAAyB,CACvC,KAAK,EAAE,KAAK,EACZ,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,MAAM,GACb,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAuExB"}
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Premium-baseline token derivation (epic #3864, issue #3865).
3
+ *
4
+ * Resolves the surface ladder, hairlines, state layers, shadow colour, control
5
+ * geometry, focus ring, native chrome, glass and motion/typography tokens for
6
+ * one theme and scheme. Presets set explicit values on `Theme`; anything a theme
7
+ * omits (custom themes, `createTheme()`) is derived here from its palette so
8
+ * every theme always defines every token.
9
+ */
10
+ import { hexToRgb } from '../utils/theme/color.js';
11
+ import { noInsetHighlight } from './shared.js';
12
+ export const defaultStateLayers = {
13
+ hover: '0.08',
14
+ pressed: '0.12',
15
+ selected: '0.12',
16
+ focus: '0.12',
17
+ dragged: '0.16',
18
+ disabled: '0.38',
19
+ };
20
+ export const defaultSchemeDuration = 'var(--smrt-duration-medium1)';
21
+ export const defaultFontFeatureSettings = 'normal';
22
+ /** `rgb(r g b)` from any palette colour; black when it cannot be parsed. */
23
+ export function toRgbTriplet(color) {
24
+ try {
25
+ const { r, g, b } = hexToRgb(color);
26
+ if ([r, g, b].some((n) => Number.isNaN(n)))
27
+ return 'rgb(0 0 0)';
28
+ return `rgb(${Math.round(r)} ${Math.round(g)} ${Math.round(b)})`;
29
+ }
30
+ catch {
31
+ return 'rgb(0 0 0)';
32
+ }
33
+ }
34
+ export function deriveSurfaces(colors, isDark) {
35
+ return {
36
+ ground: colors.background,
37
+ panel: colors.surface,
38
+ control: isDark
39
+ ? colors.surfaceContainerHigh
40
+ : colors.surfaceContainerLowest,
41
+ raised: isDark
42
+ ? colors.surfaceContainerHigh
43
+ : colors.surfaceContainerLowest,
44
+ sunken: isDark ? colors.surfaceContainerLowest : colors.surfaceContainer,
45
+ overlay: colors.scrim,
46
+ };
47
+ }
48
+ export function deriveHairlines(isDark, prefix = '--smrt') {
49
+ return {
50
+ color: `color-mix(in srgb, var(${prefix}-color-on-surface) 12%, transparent)`,
51
+ strongColor: `color-mix(in srgb, var(${prefix}-color-on-surface) 20%, transparent)`,
52
+ insetHighlight: isDark
53
+ ? 'inset 0 1px 0 rgb(255 255 255 / 0.06)'
54
+ : noInsetHighlight,
55
+ };
56
+ }
57
+ export function deriveControls(prefix = '--smrt') {
58
+ return {
59
+ radius: `var(${prefix}-radius-md)`,
60
+ radiusSmall: `var(${prefix}-radius-sm)`,
61
+ height: '2.75rem',
62
+ heightSmall: '2.25rem',
63
+ weight: '500',
64
+ border: `var(${prefix}-hairline-strong)`,
65
+ };
66
+ }
67
+ export function deriveFocus(prefix = '--smrt') {
68
+ return {
69
+ color: `var(${prefix}-color-primary)`,
70
+ width: '2px',
71
+ offset: '2px',
72
+ };
73
+ }
74
+ export function deriveNative(prefix = '--smrt') {
75
+ return {
76
+ accent: `var(${prefix}-color-primary)`,
77
+ selectionBackground: `var(${prefix}-color-primary-container)`,
78
+ selectionColor: `var(${prefix}-color-on-primary-container)`,
79
+ caret: `var(${prefix}-color-primary)`,
80
+ scrollbarThumb: `var(${prefix}-hairline-strong-color)`,
81
+ scrollbarTrack: 'transparent',
82
+ };
83
+ }
84
+ export function deriveGlassSurfaces(prefix = '--smrt') {
85
+ return {
86
+ surface: `var(${prefix}-surface-panel)`,
87
+ surfaceStrong: `var(${prefix}-surface-raised)`,
88
+ border: `var(${prefix}-hairline-color)`,
89
+ };
90
+ }
91
+ /** Opaque glass default for themes that are not glass. */
92
+ export const opaqueGlass = { blur: '0px', saturation: '100%' };
93
+ /**
94
+ * Complete set of baseline sections derived from a palette pair. Presets spread
95
+ * this and override what they tune; `createTheme()` uses it directly.
96
+ */
97
+ export function baselineTokens(light, dark) {
98
+ return {
99
+ surfaces: {
100
+ light: deriveSurfaces(light, false),
101
+ dark: deriveSurfaces(dark, true),
102
+ },
103
+ hairlines: { light: deriveHairlines(false), dark: deriveHairlines(true) },
104
+ states: { ...defaultStateLayers },
105
+ shadowColor: {
106
+ light: toRgbTriplet(light.shadow),
107
+ dark: toRgbTriplet(dark.shadow),
108
+ },
109
+ controls: deriveControls(),
110
+ focus: deriveFocus(),
111
+ native: { light: deriveNative(), dark: deriveNative() },
112
+ schemeDuration: defaultSchemeDuration,
113
+ fontFeatureSettings: defaultFontFeatureSettings,
114
+ };
115
+ }
116
+ /** Flat `--prefix-*` map for the premium-baseline families. */
117
+ export function generateBaselineVariables(theme, isDark, prefix) {
118
+ const scheme = isDark ? 'dark' : 'light';
119
+ const colors = isDark ? theme.dark : theme.light;
120
+ const surfaces = theme.surfaces?.[scheme] ?? deriveSurfaces(colors, isDark);
121
+ const hairlines = theme.hairlines?.[scheme] ?? deriveHairlines(isDark, prefix);
122
+ const states = { ...defaultStateLayers, ...theme.states };
123
+ const controls = { ...deriveControls(prefix), ...theme.controls };
124
+ const focus = { ...deriveFocus(prefix), ...theme.focus };
125
+ const native = theme.native?.[scheme] ?? deriveNative(prefix);
126
+ const glass = theme.glass ?? opaqueGlass;
127
+ const glassSurfaces = glass.surfaces?.[scheme] ?? deriveGlassSurfaces(prefix);
128
+ const shadowColor = theme.shadowColor?.[scheme] ?? toRgbTriplet(colors.shadow);
129
+ return {
130
+ // Surface ladder
131
+ [`${prefix}-surface-ground`]: surfaces.ground,
132
+ [`${prefix}-surface-panel`]: surfaces.panel,
133
+ [`${prefix}-surface-control`]: surfaces.control,
134
+ [`${prefix}-surface-raised`]: surfaces.raised,
135
+ [`${prefix}-surface-sunken`]: surfaces.sunken,
136
+ [`${prefix}-surface-overlay`]: surfaces.overlay,
137
+ // Hairlines
138
+ [`${prefix}-hairline-color`]: hairlines.color,
139
+ [`${prefix}-hairline-strong-color`]: hairlines.strongColor,
140
+ [`${prefix}-hairline`]: `1px solid var(${prefix}-hairline-color)`,
141
+ [`${prefix}-hairline-strong`]: `1px solid var(${prefix}-hairline-strong-color)`,
142
+ [`${prefix}-inset-highlight`]: hairlines.insetHighlight,
143
+ // State layers
144
+ [`${prefix}-state-hover`]: states.hover,
145
+ [`${prefix}-state-pressed`]: states.pressed,
146
+ [`${prefix}-state-selected`]: states.selected,
147
+ [`${prefix}-state-focus`]: states.focus,
148
+ [`${prefix}-state-dragged`]: states.dragged,
149
+ [`${prefix}-state-disabled`]: states.disabled,
150
+ // Depth
151
+ [`${prefix}-shadow-color`]: shadowColor,
152
+ // Controls
153
+ [`${prefix}-control-radius`]: controls.radius,
154
+ [`${prefix}-control-radius-small`]: controls.radiusSmall,
155
+ [`${prefix}-control-height`]: controls.height,
156
+ [`${prefix}-control-height-small`]: controls.heightSmall,
157
+ [`${prefix}-control-weight`]: controls.weight,
158
+ [`${prefix}-control-border`]: controls.border,
159
+ // Focus ring
160
+ [`${prefix}-focus-ring-color`]: focus.color,
161
+ [`${prefix}-focus-ring-width`]: focus.width,
162
+ [`${prefix}-focus-ring-offset`]: focus.offset,
163
+ // Native chrome
164
+ [`${prefix}-accent-color`]: native.accent,
165
+ [`${prefix}-selection-background`]: native.selectionBackground,
166
+ [`${prefix}-selection-color`]: native.selectionColor,
167
+ [`${prefix}-caret-color`]: native.caret,
168
+ [`${prefix}-scrollbar-thumb`]: native.scrollbarThumb,
169
+ [`${prefix}-scrollbar-track`]: native.scrollbarTrack,
170
+ // Glass
171
+ [`${prefix}-glass-blur`]: glass.blur,
172
+ [`${prefix}-glass-saturation`]: glass.saturation,
173
+ [`${prefix}-glass-surface`]: glassSurfaces.surface,
174
+ [`${prefix}-glass-surface-strong`]: glassSurfaces.surfaceStrong,
175
+ [`${prefix}-glass-border`]: glassSurfaces.border,
176
+ // Deprecated aliases kept for one release (see GlassEffects).
177
+ [`${prefix}-glass-border-opacity`]: glass.borderOpacity ?? '1',
178
+ [`${prefix}-glass-background-opacity`]: glass.backgroundOpacity ?? '1',
179
+ // Motion and typography
180
+ [`${prefix}-duration-scheme`]: theme.schemeDuration ?? defaultSchemeDuration,
181
+ [`${prefix}-font-feature-settings`]: theme.fontFeatureSettings ?? defaultFontFeatureSettings,
182
+ };
183
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/glass/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH,OAAO,KAAK,EAIV,KAAK,EAEN,MAAM,aAAa,CAAC;AAySrB;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,KAcxB,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/glass/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH,OAAO,KAAK,EAIV,KAAK,EAEN,MAAM,aAAa,CAAC;AA2XrB;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,KA2DxB,CAAC;AAEF,eAAe,UAAU,CAAC"}