@happyvertical/smrt-ui 0.55.17 → 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 (90) hide show
  1. package/README.md +7 -0
  2. package/dist/components/calendar/CalendarView.svelte +2 -2
  3. package/dist/components/chat/ReactionPicker.svelte +2 -2
  4. package/dist/components/data/CollectionList.svelte +1 -1
  5. package/dist/components/data/CollectionToolbar.svelte +1 -1
  6. package/dist/components/data/DataTable.svelte +44 -32
  7. package/dist/components/data/SortableHeader.svelte +2 -2
  8. package/dist/components/feedback/Alert.svelte +4 -3
  9. package/dist/components/feedback/LoadingOverlay.svelte +2 -2
  10. package/dist/components/feedback/Modal.svelte +11 -9
  11. package/dist/components/feedback/PhoneSheet.svelte +2 -2
  12. package/dist/components/feedback/ToastViewport.svelte +1 -1
  13. package/dist/components/forms/CameraCapture.svelte +2 -2
  14. package/dist/components/forms/Checkbox.svelte +1 -1
  15. package/dist/components/forms/Combobox.svelte +1 -1
  16. package/dist/components/forms/FilePicker.svelte +1 -1
  17. package/dist/components/forms/Input.svelte +11 -7
  18. package/dist/components/forms/InputGroup.svelte +1 -1
  19. package/dist/components/forms/Listbox.svelte +1 -1
  20. package/dist/components/forms/Radio.svelte +1 -1
  21. package/dist/components/forms/RangeSlider.svelte +1 -1
  22. package/dist/components/forms/SearchInput.svelte +2 -2
  23. package/dist/components/forms/SegmentedControl.svelte +1 -1
  24. package/dist/components/forms/Select.svelte +11 -7
  25. package/dist/components/forms/SignaturePad.svelte +2 -2
  26. package/dist/components/forms/Slider.svelte +1 -1
  27. package/dist/components/forms/StagedControlReview.svelte +1 -1
  28. package/dist/components/forms/Switch.svelte +1 -1
  29. package/dist/components/forms/TagsInput.svelte +1 -1
  30. package/dist/components/forms/Textarea.svelte +10 -6
  31. package/dist/components/forms/Toggle.svelte +2 -2
  32. package/dist/components/forms/ToggleButton.svelte +1 -1
  33. package/dist/components/layout/PageHeader.svelte +2 -2
  34. package/dist/components/nav/Tabs.svelte +47 -17
  35. package/dist/components/permissions/PermissionPicker.svelte +1 -1
  36. package/dist/components/roles/RoleSelector.svelte +2 -2
  37. package/dist/components/ui/AccordionItem.svelte +1 -1
  38. package/dist/components/ui/Badge.svelte +2 -1
  39. package/dist/components/ui/Button.svelte +38 -29
  40. package/dist/components/ui/Card.svelte +11 -10
  41. package/dist/components/ui/Chip.svelte +31 -7
  42. package/dist/components/ui/Disclosure.svelte +1 -1
  43. package/dist/components/ui/Dropdown.svelte +20 -15
  44. package/dist/components/ui/IconToggle.svelte +2 -2
  45. package/dist/components/ui/Popover.svelte +4 -4
  46. package/dist/components/ui/Tooltip.svelte +1 -0
  47. package/dist/components/ui/Tree.svelte +2 -2
  48. package/dist/themes/CUSTOM_THEME_GUIDE.md +15 -0
  49. package/dist/themes/README.md +126 -0
  50. package/dist/themes/ThemeProvider.svelte +173 -13
  51. package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
  52. package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
  53. package/dist/themes/create-theme.d.ts +22 -1
  54. package/dist/themes/create-theme.d.ts.map +1 -1
  55. package/dist/themes/create-theme.js +93 -5
  56. package/dist/themes/css-generator.d.ts.map +1 -1
  57. package/dist/themes/css-generator.js +15 -16
  58. package/dist/themes/derive.d.ts +31 -0
  59. package/dist/themes/derive.d.ts.map +1 -0
  60. package/dist/themes/derive.js +183 -0
  61. package/dist/themes/glass/index.d.ts.map +1 -1
  62. package/dist/themes/glass/index.js +127 -14
  63. package/dist/themes/happyvertical/index.d.ts.map +1 -1
  64. package/dist/themes/happyvertical/index.js +101 -20
  65. package/dist/themes/index.d.ts +2 -2
  66. package/dist/themes/index.d.ts.map +1 -1
  67. package/dist/themes/index.js +1 -1
  68. package/dist/themes/material/index.d.ts.map +1 -1
  69. package/dist/themes/material/index.js +129 -28
  70. package/dist/themes/required-tokens.generated.json +29 -0
  71. package/dist/themes/shared.d.ts +35 -0
  72. package/dist/themes/shared.d.ts.map +1 -1
  73. package/dist/themes/shared.js +32 -0
  74. package/dist/themes/smrt/index.d.ts +3 -0
  75. package/dist/themes/smrt/index.d.ts.map +1 -1
  76. package/dist/themes/smrt/index.js +108 -26
  77. package/dist/themes/studio/index.d.ts.map +1 -1
  78. package/dist/themes/studio/index.js +116 -40
  79. package/dist/themes/styles/base.css +225 -0
  80. package/dist/themes/styles/glass.css +103 -19
  81. package/dist/themes/styles/happyvertical.css +104 -12
  82. package/dist/themes/styles/material.css +127 -35
  83. package/dist/themes/styles/smrt.css +122 -30
  84. package/dist/themes/styles/studio.css +128 -36
  85. package/dist/themes/theme-script.d.ts +3 -1
  86. package/dist/themes/theme-script.d.ts.map +1 -1
  87. package/dist/themes/theme-script.js +7 -2
  88. package/dist/themes/types.d.ts +118 -6
  89. package/dist/themes/types.d.ts.map +1 -1
  90. package/package.json +2 -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 { generateTheme, 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',
@@ -237,6 +242,27 @@ function mergeTypography(custom) {
237
242
  labelSmall: { ...defaults.labelSmall, ...custom.labelSmall },
238
243
  };
239
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
+ }
240
266
  /**
241
267
  * Create a custom theme
242
268
  *
@@ -268,7 +294,7 @@ export function createTheme(options) {
268
294
  // Get base theme if extending (lazy import to avoid circular deps)
269
295
  const baseTheme = extend ? getBaseThemeSync(extend) : null;
270
296
  // Build light colors
271
- const lightColors = {
297
+ const lightColors = withInfoFromTertiary({
272
298
  ...defaultColorStructure,
273
299
  ...baseTheme?.light,
274
300
  ...lightPartial,
@@ -280,11 +306,12 @@ export function createTheme(options) {
280
306
  `${normalizeHex(lightPartial.primary)}20`,
281
307
  onPrimaryContainer: lightPartial.onPrimaryContainer || lightPartial.primary,
282
308
  inversePrimary: lightPartial.inversePrimary || lightPartial.primary,
283
- };
309
+ }, lightPartial);
284
310
  // Build dark colors
285
311
  const darkColors = darkPartial
286
- ? { ...lightColors, ...darkPartial }
312
+ ? withInfoFromTertiary({ ...lightColors, ...darkPartial }, darkPartial)
287
313
  : generateDarkColors(lightColors);
314
+ const baseline = baselineTokens(lightColors, darkColors);
288
315
  return {
289
316
  id: id,
290
317
  name,
@@ -302,7 +329,22 @@ export function createTheme(options) {
302
329
  ...(baseTheme?.easing || materialEasing),
303
330
  ...options.easing,
304
331
  },
305
- 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
+ }),
306
348
  fontFamily: options.fontFamily || baseTheme?.fontFamily || 'system-ui, sans-serif',
307
349
  };
308
350
  }
@@ -397,3 +439,49 @@ export function createThemeFromColor(primaryColor, id, name, options) {
397
439
  });
398
440
  }
399
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;AAyMD;;;;;;;;;;;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;
@@ -114,19 +124,6 @@ function generateEasingVariables(easing, prefix) {
114
124
  }
115
125
  return vars;
116
126
  }
117
- /**
118
- * Generate CSS custom properties for glass effects
119
- */
120
- function generateGlassVariables(glass, prefix) {
121
- if (!glass)
122
- return {};
123
- return {
124
- [`${prefix}-glass-blur`]: glass.blur,
125
- [`${prefix}-glass-saturation`]: glass.saturation,
126
- [`${prefix}-glass-border-opacity`]: glass.borderOpacity,
127
- [`${prefix}-glass-background-opacity`]: glass.backgroundOpacity,
128
- };
129
- }
130
127
  /**
131
128
  * Generate helper tokens that do not vary by color scheme (issue #1431).
132
129
  *
@@ -198,8 +195,10 @@ export function generateThemeVariables(theme, isDark, options = {}) {
198
195
  ...generateEasingVariables(theme.easing, prefix),
199
196
  // Theme-independent helper tokens (mono font, named weights, z-index)
200
197
  ...generateStaticTokens(prefix, theme.fontFamilyMono),
201
- // Glass effects (if present)
202
- ...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),
203
202
  };
204
203
  }
205
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"}
@@ -5,7 +5,8 @@
5
5
  * translucency, and layered depth. Features frosted glass surfaces
6
6
  * and vibrant material effects.
7
7
  */
8
- import { appleEasing, borderRadiusScale, durationScale, spacingScale, } from '../shared.js';
8
+ import { baselineTokens } from '../derive.js';
9
+ import { appleEasing, borderRadiusScale, darkInsetHighlight, durationScale, hairlineMix, noInsetHighlight, noShadow, shadow, spacingScale, } from '../shared.js';
9
10
  /**
10
11
  * Glass light color palette
11
12
  * Translucent whites with subtle color tints
@@ -26,6 +27,11 @@ const lightColors = {
26
27
  onTertiary: '#ffffff',
27
28
  tertiaryContainer: 'rgba(88, 86, 214, 0.15)',
28
29
  onTertiaryContainer: '#2a2866',
30
+ // Info - Shares the tertiary hue (no distinct info hue in this preset)
31
+ info: '#5856d6',
32
+ onInfo: '#ffffff',
33
+ infoContainer: 'rgba(88, 86, 214, 0.15)',
34
+ onInfoContainer: '#2a2866',
29
35
  // Error - System red
30
36
  error: '#ff3b30',
31
37
  onError: '#ffffff',
@@ -96,6 +102,11 @@ const darkColors = {
96
102
  onTertiary: '#ffffff',
97
103
  tertiaryContainer: 'rgba(94, 92, 230, 0.25)',
98
104
  onTertiaryContainer: '#d4d3ff',
105
+ // Info - Shares the tertiary hue (no distinct info hue in this preset)
106
+ info: '#5e5ce6',
107
+ onInfo: '#ffffff',
108
+ infoContainer: 'rgba(94, 92, 230, 0.25)',
109
+ onInfoContainer: '#d4d3ff',
99
110
  // Error
100
111
  error: '#ff453a',
101
112
  onError: '#000000',
@@ -112,7 +123,7 @@ const darkColors = {
112
123
  successContainer: 'rgba(48, 209, 88, 0.25)',
113
124
  onSuccessContainer: '#b8f5c9',
114
125
  // Surface - Translucent darks
115
- surface: 'rgba(30, 30, 30, 0.72)',
126
+ surface: 'rgba(29, 29, 38, 0.72)',
116
127
  onSurface: '#ffffff',
117
128
  surfaceVariant: 'rgba(44, 44, 46, 0.65)',
118
129
  onSurfaceVariant: '#8e8e93',
@@ -124,7 +135,7 @@ const darkColors = {
124
135
  surfaceDim: 'rgba(0, 0, 0, 0.3)',
125
136
  surfaceBright: 'rgba(58, 58, 60, 0.9)',
126
137
  // Background - Deep dark with subtle tint
127
- background: '#000000',
138
+ background: '#0b0b0f',
128
139
  onBackground: '#ffffff',
129
140
  // Outline — see the light-scheme note. Composited over the dark frosted
130
141
  // surface the original rgba(84, 84, 88, 0.65) measured 1.70:1; lightening the
@@ -139,7 +150,7 @@ const darkColors = {
139
150
  shadow: 'rgba(0, 0, 0, 0.4)',
140
151
  scrim: 'rgba(0, 0, 0, 0.6)',
141
152
  // Glass-specific
142
- glassBackdrop: 'rgba(30, 30, 30, 0.72)',
153
+ glassBackdrop: 'rgba(29, 29, 38, 0.72)',
143
154
  glassBorder: 'rgba(255, 255, 255, 0.1)',
144
155
  };
145
156
  /**
@@ -248,31 +259,88 @@ const typography = {
248
259
  labelSmall: {
249
260
  size: '0.6875rem',
250
261
  lineHeight: '1.3',
251
- weight: '600',
262
+ weight: '500',
252
263
  tracking: '-0.004em',
253
264
  fontFamily: 'var(--smrt-font-family)',
254
265
  },
255
266
  };
256
267
  /**
257
- * Glass elevation
258
- * Softer, more diffuse shadows with color tint
268
+ * Glass elevation (light) — soft, diffuse two-layer shadows: a faint contact
269
+ * line and a wide ambient layer with negative spread so panels float without a
270
+ * hard edge.
259
271
  */
260
272
  const elevation = {
261
- 0: 'none',
262
- 1: '0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.02)',
263
- 2: '0 3px 6px rgba(0, 0, 0, 0.06), 0 2px 4px rgba(0, 0, 0, 0.03)',
264
- 3: '0 6px 12px rgba(0, 0, 0, 0.08), 0 4px 8px rgba(0, 0, 0, 0.04)',
265
- 4: '0 12px 24px rgba(0, 0, 0, 0.1), 0 8px 16px rgba(0, 0, 0, 0.05)',
266
- 5: '0 24px 48px rgba(0, 0, 0, 0.12), 0 16px 32px rgba(0, 0, 0, 0.06)',
273
+ 0: noShadow,
274
+ 1: shadow([
275
+ [1, 2, 0, 5],
276
+ [2, 6, -2, 5],
277
+ ]),
278
+ 2: shadow([
279
+ [1, 3, 0, 6],
280
+ [6, 14, -4, 8],
281
+ ]),
282
+ 3: shadow([
283
+ [2, 4, 0, 7],
284
+ [12, 24, -8, 10],
285
+ ]),
286
+ 4: shadow([
287
+ [2, 6, 0, 8],
288
+ [20, 40, -12, 12],
289
+ ]),
290
+ 5: shadow([
291
+ [4, 8, 0, 8],
292
+ [32, 64, -16, 16],
293
+ ]),
294
+ };
295
+ /**
296
+ * Glass elevation (dark) — translucency and the inset top highlight carry the
297
+ * depth; the shadows only support it.
298
+ */
299
+ const darkElevation = {
300
+ 0: noShadow,
301
+ 1: shadow([
302
+ [1, 2, 0, 20],
303
+ [2, 6, -2, 14],
304
+ ]),
305
+ 2: shadow([
306
+ [1, 3, 0, 24],
307
+ [6, 14, -4, 18],
308
+ ], 'var(--smrt-inset-highlight)'),
309
+ 3: shadow([
310
+ [2, 4, 0, 26],
311
+ [12, 24, -8, 22],
312
+ ], 'var(--smrt-inset-highlight)'),
313
+ 4: shadow([
314
+ [2, 6, 0, 28],
315
+ [20, 40, -12, 26],
316
+ ], 'var(--smrt-inset-highlight)'),
317
+ 5: shadow([
318
+ [4, 8, 0, 30],
319
+ [32, 64, -16, 30],
320
+ ], 'var(--smrt-inset-highlight)'),
267
321
  };
268
322
  /**
269
323
  * Glass effect configuration
270
324
  */
271
325
  const glassEffects = {
272
326
  blur: '20px',
273
- saturation: '180%',
327
+ // Deprecated aliases, still emitted for consumers of the old opacity tokens.
274
328
  borderOpacity: '0.3',
275
329
  backgroundOpacity: '0.72',
330
+ saturation: '180%',
331
+ // Translucent frosted surfaces; the opacity is part of each colour.
332
+ surfaces: {
333
+ light: {
334
+ surface: 'color-mix(in srgb, #ffffff 72%, transparent)',
335
+ surfaceStrong: 'color-mix(in srgb, #ffffff 88%, transparent)',
336
+ border: 'var(--smrt-hairline-color)',
337
+ },
338
+ dark: {
339
+ surface: 'color-mix(in srgb, #1d1d26 72%, transparent)',
340
+ surfaceStrong: 'color-mix(in srgb, #1d1d26 88%, transparent)',
341
+ border: 'color-mix(in srgb, #ffffff 10%, transparent)',
342
+ },
343
+ },
276
344
  };
277
345
  /**
278
346
  * Glass theme definition
@@ -284,10 +352,55 @@ export const glassTheme = {
284
352
  dark: darkColors,
285
353
  typography,
286
354
  elevation,
355
+ darkElevation,
287
356
  spacing: spacingScale,
288
357
  borderRadius: borderRadiusScale,
289
358
  duration: durationScale,
290
359
  easing: appleEasing,
360
+ ...baselineTokens(lightColors, darkColors),
361
+ shadowColor: { light: 'rgb(0 0 0)', dark: 'rgb(0 0 0)' },
362
+ // The ladder is expressed through translucency over the ground. Panels are
363
+ // frosted glass (72%), inputs are recessed glass (a tinted well that reads as
364
+ // pressed into the panel), and popovers are thicker glass (88%).
365
+ surfaces: {
366
+ light: {
367
+ ground: lightColors.background,
368
+ panel: 'color-mix(in srgb, #ffffff 72%, transparent)',
369
+ control: 'color-mix(in srgb, #767680 12%, transparent)',
370
+ raised: 'color-mix(in srgb, #ffffff 88%, transparent)',
371
+ sunken: 'color-mix(in srgb, #767680 8%, transparent)',
372
+ overlay: lightColors.scrim,
373
+ },
374
+ dark: {
375
+ ground: darkColors.background,
376
+ panel: 'color-mix(in srgb, #1d1d26 72%, transparent)',
377
+ control: 'color-mix(in srgb, #000000 28%, transparent)',
378
+ raised: 'color-mix(in srgb, #2a2a34 88%, transparent)',
379
+ sunken: 'color-mix(in srgb, #000000 38%, transparent)',
380
+ overlay: darkColors.scrim,
381
+ },
382
+ },
383
+ hairlines: {
384
+ light: hairlineMix(10, 26, noInsetHighlight),
385
+ dark: hairlineMix(12, 28, darkInsetHighlight(0.1)),
386
+ },
387
+ // Slightly stronger than Material: translucent fills need a firmer response.
388
+ states: {
389
+ hover: '0.10',
390
+ pressed: '0.14',
391
+ selected: '0.14',
392
+ focus: '0.14',
393
+ dragged: '0.18',
394
+ disabled: '0.38',
395
+ },
396
+ // Pill-ish controls: radius lg.
397
+ controls: {
398
+ radius: 'var(--smrt-radius-lg)',
399
+ radiusSmall: 'var(--smrt-radius-md)',
400
+ height: '2.75rem',
401
+ heightSmall: '2.25rem',
402
+ weight: '500',
403
+ },
291
404
  glass: glassEffects,
292
405
  fontFamily: '-apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", sans-serif',
293
406
  };
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/happyvertical/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAGH,OAAO,KAAK,EAMV,KAAK,EAEN,MAAM,aAAa,CAAC;AAoWrB;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAchC,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/themes/happyvertical/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAUH,OAAO,KAAK,EAMV,KAAK,EAEN,MAAM,aAAa,CAAC;AA2YrB;;GAEG;AACH,eAAO,MAAM,kBAAkB,EAAE,KAkEhC,CAAC;AAEF,eAAe,kBAAkB,CAAC"}