@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.
- package/README.md +7 -0
- package/dist/components/calendar/CalendarView.svelte +2 -2
- package/dist/components/chat/ReactionPicker.svelte +2 -2
- package/dist/components/data/CollectionList.svelte +1 -1
- package/dist/components/data/CollectionToolbar.svelte +1 -1
- package/dist/components/data/DataTable.svelte +44 -32
- package/dist/components/data/SortableHeader.svelte +2 -2
- package/dist/components/feedback/Alert.svelte +4 -3
- package/dist/components/feedback/LoadingOverlay.svelte +2 -2
- package/dist/components/feedback/Modal.svelte +11 -9
- package/dist/components/feedback/PhoneSheet.svelte +2 -2
- package/dist/components/feedback/ToastViewport.svelte +1 -1
- package/dist/components/forms/CameraCapture.svelte +2 -2
- package/dist/components/forms/Checkbox.svelte +1 -1
- package/dist/components/forms/Combobox.svelte +1 -1
- package/dist/components/forms/FilePicker.svelte +1 -1
- package/dist/components/forms/Input.svelte +11 -7
- package/dist/components/forms/InputGroup.svelte +1 -1
- package/dist/components/forms/Listbox.svelte +1 -1
- package/dist/components/forms/Radio.svelte +1 -1
- package/dist/components/forms/RangeSlider.svelte +1 -1
- package/dist/components/forms/SearchInput.svelte +2 -2
- package/dist/components/forms/SegmentedControl.svelte +1 -1
- package/dist/components/forms/Select.svelte +11 -7
- package/dist/components/forms/SignaturePad.svelte +2 -2
- package/dist/components/forms/Slider.svelte +1 -1
- package/dist/components/forms/StagedControlReview.svelte +1 -1
- package/dist/components/forms/Switch.svelte +1 -1
- package/dist/components/forms/TagsInput.svelte +1 -1
- package/dist/components/forms/Textarea.svelte +10 -6
- package/dist/components/forms/Toggle.svelte +2 -2
- package/dist/components/forms/ToggleButton.svelte +1 -1
- package/dist/components/layout/PageHeader.svelte +2 -2
- package/dist/components/nav/Tabs.svelte +47 -17
- package/dist/components/permissions/PermissionPicker.svelte +1 -1
- package/dist/components/roles/RoleSelector.svelte +2 -2
- package/dist/components/ui/AccordionItem.svelte +1 -1
- package/dist/components/ui/Badge.svelte +2 -1
- package/dist/components/ui/Button.svelte +38 -29
- package/dist/components/ui/Card.svelte +11 -10
- package/dist/components/ui/Chip.svelte +31 -7
- package/dist/components/ui/Disclosure.svelte +1 -1
- package/dist/components/ui/Dropdown.svelte +20 -15
- package/dist/components/ui/IconToggle.svelte +2 -2
- package/dist/components/ui/Popover.svelte +4 -4
- package/dist/components/ui/Tooltip.svelte +1 -0
- package/dist/components/ui/Tree.svelte +2 -2
- package/dist/themes/CUSTOM_THEME_GUIDE.md +15 -0
- package/dist/themes/README.md +126 -0
- package/dist/themes/ThemeProvider.svelte +173 -13
- package/dist/themes/ThemeProvider.svelte.d.ts +7 -2
- package/dist/themes/ThemeProvider.svelte.d.ts.map +1 -1
- package/dist/themes/create-theme.d.ts +22 -1
- package/dist/themes/create-theme.d.ts.map +1 -1
- package/dist/themes/create-theme.js +93 -5
- package/dist/themes/css-generator.d.ts.map +1 -1
- package/dist/themes/css-generator.js +15 -16
- package/dist/themes/derive.d.ts +31 -0
- package/dist/themes/derive.d.ts.map +1 -0
- package/dist/themes/derive.js +183 -0
- package/dist/themes/glass/index.d.ts.map +1 -1
- package/dist/themes/glass/index.js +127 -14
- package/dist/themes/happyvertical/index.d.ts.map +1 -1
- package/dist/themes/happyvertical/index.js +101 -20
- package/dist/themes/index.d.ts +2 -2
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +1 -1
- package/dist/themes/material/index.d.ts.map +1 -1
- package/dist/themes/material/index.js +129 -28
- package/dist/themes/required-tokens.generated.json +29 -0
- package/dist/themes/shared.d.ts +35 -0
- package/dist/themes/shared.d.ts.map +1 -1
- package/dist/themes/shared.js +32 -0
- package/dist/themes/smrt/index.d.ts +3 -0
- package/dist/themes/smrt/index.d.ts.map +1 -1
- package/dist/themes/smrt/index.js +108 -26
- package/dist/themes/studio/index.d.ts.map +1 -1
- package/dist/themes/studio/index.js +116 -40
- package/dist/themes/styles/base.css +225 -0
- package/dist/themes/styles/glass.css +103 -19
- package/dist/themes/styles/happyvertical.css +104 -12
- package/dist/themes/styles/material.css +127 -35
- package/dist/themes/styles/smrt.css +122 -30
- package/dist/themes/styles/studio.css +128 -36
- package/dist/themes/theme-script.d.ts +3 -1
- package/dist/themes/theme-script.d.ts.map +1 -1
- package/dist/themes/theme-script.js +7 -2
- package/dist/themes/types.d.ts +118 -6
- package/dist/themes/types.d.ts.map +1 -1
- package/package.json +2 -2
|
@@ -114,14 +114,14 @@ $effect(() => {
|
|
|
114
114
|
@layer smrt.reset, smrt.structure, smrt.theme;
|
|
115
115
|
@layer smrt.theme {
|
|
116
116
|
.popover { position: relative; display: inline-flex; }
|
|
117
|
-
.popover__trigger { appearance: none; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border:
|
|
118
|
-
.popover__trigger:focus-visible, .popover__panel:focus-visible { outline:
|
|
119
|
-
.popover__trigger:disabled { opacity:
|
|
117
|
+
.popover__trigger { appearance: none; padding: var(--smrt-spacing-2) var(--smrt-spacing-3); border: var(--smrt-hairline-strong); border-radius: var(--smrt-control-radius); background: var(--smrt-surface-control); color: var(--smrt-color-on-surface); font: inherit; font-weight: var(--smrt-control-weight); cursor: pointer; }
|
|
118
|
+
.popover__trigger:focus-visible, .popover__panel:focus-visible { outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color); outline-offset: var(--smrt-focus-ring-offset); }
|
|
119
|
+
.popover__trigger:disabled { opacity: var(--smrt-state-disabled); cursor: not-allowed; }
|
|
120
120
|
/* Icon trigger: a 44px round glyph in the surrounding colour. Hover and open nudge the glyph; focus keeps the standard primary ring (a soft ring in the glyph's colour fell under 3:1 on ordinary surfaces). */
|
|
121
121
|
.popover__trigger--icon { display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; inline-size: 2.75rem; block-size: 2.75rem; padding: 0; border: none; border-radius: var(--smrt-radius-full); background: transparent; color: inherit; }
|
|
122
122
|
.popover__trigger--icon:hover:not(:disabled), .popover__trigger--icon[aria-expanded='true'] { background: transparent; color: color-mix(in srgb, currentColor 80%, var(--smrt-color-on-surface)); }
|
|
123
123
|
.popover__trigger--icon:focus-visible { background: transparent; }
|
|
124
|
-
.popover__panel { position: absolute; z-index: var(--smrt-z-index-popover, 1000); box-sizing: border-box; inline-size: var(--smrt-popover-panel-width, auto); min-width: min(16rem, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); padding: var(--smrt-popover-panel-padding, var(--smrt-spacing-4)); border:
|
|
124
|
+
.popover__panel { position: absolute; z-index: var(--smrt-z-index-popover, 1000); box-sizing: border-box; inline-size: var(--smrt-popover-panel-width, auto); min-width: min(16rem, calc(100vw - 2rem)); max-width: calc(100vw - 2rem); padding: var(--smrt-popover-panel-padding, var(--smrt-spacing-4)); border: var(--smrt-hairline); border-radius: var(--smrt-radius-large); background: var(--smrt-glass-surface); backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation)); color: var(--smrt-color-on-surface); box-shadow: var(--smrt-inset-highlight), var(--smrt-elevation-3); }
|
|
125
125
|
.popover__panel--bottom-start { top: calc(100% + var(--smrt-spacing-1)); left: 0; }
|
|
126
126
|
.popover__panel--bottom-end { top: calc(100% + var(--smrt-spacing-1)); right: 0; }
|
|
127
127
|
.popover__panel--top-start { bottom: calc(100% + var(--smrt-spacing-1)); left: 0; }
|
|
@@ -88,6 +88,7 @@ function onKeydown(event: KeyboardEvent) {
|
|
|
88
88
|
color: var(--smrt-color-inverse-on-surface);
|
|
89
89
|
font-size: var(--smrt-typography-body-small-size, 0.75rem);
|
|
90
90
|
line-height: var(--smrt-typography-body-small-line-height, 1.4);
|
|
91
|
+
box-shadow: var(--smrt-elevation-3);
|
|
91
92
|
pointer-events: none;
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -181,8 +181,8 @@ function onItemKeydown(event: KeyboardEvent, index: number, item: FlatNode) {
|
|
|
181
181
|
background: var(--smrt-color-surface-container-high);
|
|
182
182
|
}
|
|
183
183
|
.tree__item:focus-visible {
|
|
184
|
-
outline:
|
|
185
|
-
outline-offset: -
|
|
184
|
+
outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color);
|
|
185
|
+
outline-offset: calc(var(--smrt-focus-ring-width) * -1);
|
|
186
186
|
}
|
|
187
187
|
.tree__item.selected {
|
|
188
188
|
background: var(--smrt-color-primary-container);
|
|
@@ -331,18 +331,33 @@ Every theme (presets and `createTheme()` output) must define these names. Genera
|
|
|
331
331
|
`--smrt-<component>-*` knobs are excluded.
|
|
332
332
|
|
|
333
333
|
- **color** (40): `--smrt-color-background`, `--smrt-color-error`, `--smrt-color-error-container`, `--smrt-color-inverse-on-surface`, `--smrt-color-inverse-surface`, `--smrt-color-on-background`, `--smrt-color-on-error`, `--smrt-color-on-error-container`, `--smrt-color-on-primary`, `--smrt-color-on-primary-container`, `--smrt-color-on-secondary`, `--smrt-color-on-secondary-container`, `--smrt-color-on-success-container`, `--smrt-color-on-surface`, `--smrt-color-on-surface-variant`, `--smrt-color-on-tertiary`, `--smrt-color-on-tertiary-container`, `--smrt-color-on-warning-container`, `--smrt-color-outline`, `--smrt-color-outline-variant`, `--smrt-color-primary`, `--smrt-color-primary-container`, `--smrt-color-scrim`, `--smrt-color-secondary`, `--smrt-color-secondary-container`, `--smrt-color-shadow`, `--smrt-color-success`, `--smrt-color-success-container`, `--smrt-color-surface`, `--smrt-color-surface-container`, `--smrt-color-surface-container-high`, `--smrt-color-surface-container-highest`, `--smrt-color-surface-container-low`, `--smrt-color-surface-container-lowest`, `--smrt-color-surface-dim`, `--smrt-color-surface-variant`, `--smrt-color-tertiary`, `--smrt-color-tertiary-container`, `--smrt-color-warning`, `--smrt-color-warning-container`
|
|
334
|
+
- **control** (4): `--smrt-control-height`, `--smrt-control-height-small`, `--smrt-control-radius`, `--smrt-control-radius-small`
|
|
334
335
|
- **duration** (6): `--smrt-duration-fast`, `--smrt-duration-medium1`, `--smrt-duration-normal`, `--smrt-duration-short2`, `--smrt-duration-short3`, `--smrt-duration-short4`
|
|
335
336
|
- **easing** (3): `--smrt-easing-emphasized`, `--smrt-easing-emphasized-decelerate`, `--smrt-easing-standard`
|
|
336
337
|
- **elevation** (5): `--smrt-elevation-1`, `--smrt-elevation-2`, `--smrt-elevation-3`, `--smrt-elevation-4`, `--smrt-elevation-5`
|
|
338
|
+
- **focus** (3): `--smrt-focus-ring-color`, `--smrt-focus-ring-offset`, `--smrt-focus-ring-width`
|
|
337
339
|
- **font** (2): `--smrt-font-family`, `--smrt-font-family-mono`
|
|
340
|
+
- **glass** (4): `--smrt-glass-blur`, `--smrt-glass-saturation`, `--smrt-glass-surface`, `--smrt-glass-surface-strong`
|
|
341
|
+
- **hairline** (4): `--smrt-hairline`, `--smrt-hairline-color`, `--smrt-hairline-strong`, `--smrt-hairline-strong-color`
|
|
342
|
+
- **inset** (1): `--smrt-inset-highlight`
|
|
338
343
|
- **radius** (11): `--smrt-radius-2xl`, `--smrt-radius-extra-large`, `--smrt-radius-extra-small`, `--smrt-radius-full`, `--smrt-radius-large`, `--smrt-radius-lg`, `--smrt-radius-md`, `--smrt-radius-medium`, `--smrt-radius-sm`, `--smrt-radius-small`, `--smrt-radius-xl`
|
|
344
|
+
- **scrollbar** (2): `--smrt-scrollbar-thumb`, `--smrt-scrollbar-track`
|
|
339
345
|
- **spacing** (19): `--smrt-spacing-0_5`, `--smrt-spacing-1`, `--smrt-spacing-10`, `--smrt-spacing-11`, `--smrt-spacing-12`, `--smrt-spacing-1_5`, `--smrt-spacing-2`, `--smrt-spacing-2xl`, `--smrt-spacing-3`, `--smrt-spacing-3xl`, `--smrt-spacing-4`, `--smrt-spacing-5`, `--smrt-spacing-6`, `--smrt-spacing-8`, `--smrt-spacing-lg`, `--smrt-spacing-md`, `--smrt-spacing-sm`, `--smrt-spacing-xl`, `--smrt-spacing-xs`
|
|
346
|
+
- **state** (5): `--smrt-state-disabled`, `--smrt-state-dragged`, `--smrt-state-hover`, `--smrt-state-pressed`, `--smrt-state-selected`
|
|
347
|
+
- **surface** (6): `--smrt-surface-control`, `--smrt-surface-ground`, `--smrt-surface-overlay`, `--smrt-surface-panel`, `--smrt-surface-raised`, `--smrt-surface-sunken`
|
|
340
348
|
- **touch** (1): `--smrt-touch-target-min`
|
|
341
349
|
- **typography** (56): `--smrt-typography-body-large-font`, `--smrt-typography-body-large-line-height`, `--smrt-typography-body-large-size`, `--smrt-typography-body-large-tracking`, `--smrt-typography-body-medium-font`, `--smrt-typography-body-medium-line-height`, `--smrt-typography-body-medium-size`, `--smrt-typography-body-medium-tracking`, `--smrt-typography-body-medium-weight`, `--smrt-typography-body-small-font`, `--smrt-typography-body-small-line-height`, `--smrt-typography-body-small-size`, `--smrt-typography-body-small-weight`, `--smrt-typography-display-medium-line-height`, `--smrt-typography-display-medium-size`, `--smrt-typography-display-small-size`, `--smrt-typography-headline-large-line-height`, `--smrt-typography-headline-large-size`, `--smrt-typography-headline-large-weight`, `--smrt-typography-headline-medium-line-height`, `--smrt-typography-headline-medium-size`, `--smrt-typography-headline-medium-weight`, `--smrt-typography-headline-small-font`, `--smrt-typography-headline-small-line-height`, `--smrt-typography-headline-small-size`, `--smrt-typography-headline-small-weight`, `--smrt-typography-label-large-font`, `--smrt-typography-label-large-size`, `--smrt-typography-label-large-tracking`, `--smrt-typography-label-large-weight`, `--smrt-typography-label-medium-font`, `--smrt-typography-label-medium-size`, `--smrt-typography-label-medium-tracking`, `--smrt-typography-label-medium-weight`, `--smrt-typography-label-small-font`, `--smrt-typography-label-small-line-height`, `--smrt-typography-label-small-size`, `--smrt-typography-label-small-tracking`, `--smrt-typography-label-small-weight`, `--smrt-typography-title-large-font`, `--smrt-typography-title-large-line-height`, `--smrt-typography-title-large-size`, `--smrt-typography-title-medium-font`, `--smrt-typography-title-medium-font-family`, `--smrt-typography-title-medium-line-height`, `--smrt-typography-title-medium-size`, `--smrt-typography-title-medium-tracking`, `--smrt-typography-title-medium-weight`, `--smrt-typography-title-small-font`, `--smrt-typography-title-small-size`, `--smrt-typography-title-small-tracking`, `--smrt-typography-title-small-weight`, `--smrt-typography-weight-bold`, `--smrt-typography-weight-medium`, `--smrt-typography-weight-normal`, `--smrt-typography-weight-semibold`
|
|
342
350
|
- **z** (7): `--smrt-z-index-dialog`, `--smrt-z-index-dropdown`, `--smrt-z-index-modal`, `--smrt-z-index-overlay`, `--smrt-z-index-sticky`, `--smrt-z-index-toast`, `--smrt-z-index-tooltip`
|
|
343
351
|
|
|
344
352
|
<!-- smrt-required-tokens:end -->
|
|
345
353
|
|
|
354
|
+
## Runtime overrides
|
|
355
|
+
|
|
356
|
+
`ThemeProvider` layers user choices over a theme without touching the theme itself:
|
|
357
|
+
`borderRadius` (control radius tokens), `primaryColor` (full derived primary set, also
|
|
358
|
+
available as `derivePrimaryOverrides(color, scheme)`), `density` and `contrast`. See the
|
|
359
|
+
ThemeProvider props table in the README for the exact behaviour.
|
|
360
|
+
|
|
346
361
|
## Best Practices
|
|
347
362
|
|
|
348
363
|
1. **Extend a base theme**: Start with `extend: 'material'` or another base to get sensible defaults
|
package/dist/themes/README.md
CHANGED
|
@@ -305,8 +305,29 @@ import '@happyvertical/smrt-ui/themes/styles/happyvertical.css';
|
|
|
305
305
|
| `borderRadius` | `'none' \| 'sm' \| 'md' \| 'lg' \| 'xl' \| 'full'` | `'md'` | Border radius scale |
|
|
306
306
|
| `overrides` | `Record<string, string>` | `{}` | Custom CSS variable overrides |
|
|
307
307
|
| `persist` | `boolean` | `true` | Persist to localStorage |
|
|
308
|
+
| `density` | `'comfortable' \| 'touch'` | - | Default control target size (`--smrt-control-target-min`); persisted |
|
|
309
|
+
| `contrast` | `'auto' \| 'standard' \| 'more'` | `'auto'` | `auto` follows `prefers-contrast: more`; persisted when explicit |
|
|
308
310
|
| `storageKey` | `string` | `'smrt-theme'` | localStorage key |
|
|
309
311
|
|
|
312
|
+
Behaviour notes (state exposed on context: `contrast`, `isChangingScheme`):
|
|
313
|
+
|
|
314
|
+
- `borderRadius` other than `md` is emitted inline as `--smrt-control-radius` /
|
|
315
|
+
`--smrt-control-radius-small` (one scale step smaller for the small radius);
|
|
316
|
+
`md` leaves the preset's own control radius in place. The generic
|
|
317
|
+
`--smrt-radius-*` scale is not changed.
|
|
318
|
+
- `primaryColor` emits the whole derived set for the resolved scheme through
|
|
319
|
+
`derivePrimaryOverrides(color, scheme)` (also exported): `color-primary`, `on-primary`
|
|
320
|
+
(best of white/black), `primary-container`, `on-primary-container`, `inverse-primary`,
|
|
321
|
+
and the focus ring, accent, caret and selection tokens.
|
|
322
|
+
- After the resolved scheme changes post-mount, `data-smrt-theme-changing` is set on
|
|
323
|
+
the root and `<html>` for one `--smrt-duration-scheme`, so styles can crossfade.
|
|
324
|
+
Never on first paint; skipped under `prefers-reduced-motion: reduce`.
|
|
325
|
+
- Contrast: `data-contrast="standard|more|forced"` on `<html>` and the root;
|
|
326
|
+
`forced-colors: active` also sets `data-forced-colors`. Contract for components in
|
|
327
|
+
forced-colors mode: do not paint over system colours, hairlines become `CanvasText`
|
|
328
|
+
and focus rings `Highlight` (component side: #3867 follow-up).
|
|
329
|
+
- `themeScript()` stamps `data-contrast` and (when persisted) `data-density` pre-paint.
|
|
330
|
+
|
|
310
331
|
## Theme Switching
|
|
311
332
|
|
|
312
333
|
Use the context to switch themes programmatically:
|
|
@@ -630,6 +651,111 @@ on any custom property outside `--smrt-*` in `packages/*/src`. The one-time rewr
|
|
|
630
651
|
was `scripts/codemod-smrt-tokens.mjs`; it is idempotent, so re-run it after a rebase
|
|
631
652
|
instead of hand-merging renamed names.
|
|
632
653
|
|
|
654
|
+
### Premium baseline token vocabulary
|
|
655
|
+
|
|
656
|
+
Every preset and every `createTheme()` result defines all of these in both schemes
|
|
657
|
+
(sections of `Theme` in `types.ts`; defaults derived in `derive.ts`; emitted by
|
|
658
|
+
`generateThemeVariables`). Themes and presets define them; **components only consume
|
|
659
|
+
them**. Do not redeclare a theme token on a component root (CSS or `style:`): the
|
|
660
|
+
required-tokens scanner (`scripts/smrt-token-scan-lib.mjs`) treats any `--smrt-*` name
|
|
661
|
+
a component defines on its own root as component-private and drops it from the required
|
|
662
|
+
list repo-wide. A per-variant override uses a component-prefixed name that falls back to
|
|
663
|
+
the theme token, for example `background: color-mix(in srgb, currentColor
|
|
664
|
+
calc(var(--smrt-button-state-hover, var(--smrt-state-hover)) * 100%), transparent)`.
|
|
665
|
+
|
|
666
|
+
| Token | Meaning | Consumed by |
|
|
667
|
+
| --- | --- | --- |
|
|
668
|
+
| `--smrt-surface-ground` | Page ground | `body`/shell background |
|
|
669
|
+
| `--smrt-surface-panel` | Cards, sidebars, panels on the ground | `background` of Card, sidebars, header, footer |
|
|
670
|
+
| `--smrt-surface-control` | Input and control fill | `background` of Input, Select, Textarea, Button (outlined/tonal) |
|
|
671
|
+
| `--smrt-surface-raised` | Menus, popovers, dialogs above panels | `background` of overlays |
|
|
672
|
+
| `--smrt-surface-sunken` | Wells and recessed regions | `background` of tables headers, code, inset areas |
|
|
673
|
+
| `--smrt-surface-overlay` | Modal scrim | `background` of Modal/Drawer backdrops |
|
|
674
|
+
| `--smrt-hairline-color` / `-strong-color` | Quiet and strong edge colours | `border-color`, `outline-color`, dividers |
|
|
675
|
+
| `--smrt-hairline` / `--smrt-hairline-strong` | `1px solid` edge shorthands | `border`, `border-top` etc. on panels (quiet) and controls (strong) |
|
|
676
|
+
| `--smrt-inset-highlight` | Top inner light line (`none` on light) | `box-shadow` (layer with an elevation) on raised dark surfaces |
|
|
677
|
+
| `--smrt-state-{hover,pressed,selected,focus,dragged,disabled}` | Unitless state-layer opacities, 0 to 1 | Mix percentage of a state overlay (`calc(var(--smrt-state-hover) * 100%)`); `disabled` as `opacity` |
|
|
678
|
+
| `--smrt-shadow-color` | `rgb(r g b)` shadow base, per scheme | `box-shadow` colour: `rgb(from var(--smrt-shadow-color) r g b / 0.2)` |
|
|
679
|
+
| `--smrt-elevation-0..5` | Existing shadow scale | `box-shadow` |
|
|
680
|
+
| `--smrt-control-radius` / `-radius-small` | Control corner radii | `border-radius` of controls |
|
|
681
|
+
| `--smrt-control-height` / `-height-small` | Control heights | `min-height`/`block-size` of controls |
|
|
682
|
+
| `--smrt-control-weight` | Control label weight | `font-weight` of buttons and inputs |
|
|
683
|
+
| `--smrt-control-border` | Control edge shorthand (`var(--smrt-hairline-strong)`) | `border` of controls |
|
|
684
|
+
| `--smrt-control-target-min` | Density-driven minimum hit size (set by ThemeProvider `density`) | `min-height`/`min-width` of interactive controls |
|
|
685
|
+
| `--smrt-control-hit-size` | Hit area of small icon controls | `inline-size`/`block-size` of icon buttons |
|
|
686
|
+
| `--smrt-focus-ring-{color,width,offset}` | Focus indicator | `outline: var(--smrt-focus-ring-width) solid var(--smrt-focus-ring-color)`, `outline-offset` on `:focus-visible` |
|
|
687
|
+
| `--smrt-accent-color` | Native control accent | `accent-color` (checkbox, radio, range, date pickers) |
|
|
688
|
+
| `--smrt-selection-{background,color}` | Text selection | `::selection` |
|
|
689
|
+
| `--smrt-caret-color` | Text caret | `caret-color` |
|
|
690
|
+
| `--smrt-scrollbar-{thumb,track}` | Scrollbar colours | `scrollbar-color: thumb track` |
|
|
691
|
+
| `--smrt-glass-blur` / `-saturation` | Backdrop filter amounts (`0px`/`100%` outside Glass) | `backdrop-filter: blur(var(--smrt-glass-blur)) saturate(var(--smrt-glass-saturation))` |
|
|
692
|
+
| `--smrt-glass-surface` / `-surface-strong` | Translucent (Glass) or opaque surface fills; opacity is part of the colour | `background` of floating chrome and overlays |
|
|
693
|
+
| `--smrt-glass-border` | Glass edge colour | `border-color` of glass surfaces |
|
|
694
|
+
| `--smrt-duration-scheme` | Scheme crossfade duration | `transition-duration` when the colour scheme changes |
|
|
695
|
+
| `--smrt-font-feature-settings` | OpenType features (`"tnum" 1, "cv11" 1` on Inter presets) | `font-feature-settings` on the root and numeric cells |
|
|
696
|
+
|
|
697
|
+
#### Semantic info colour
|
|
698
|
+
|
|
699
|
+
`ColorPalette` carries an info family beside success, warning and error:
|
|
700
|
+
`--smrt-color-info`, `--smrt-color-on-info`, `--smrt-color-info-container` and
|
|
701
|
+
`--smrt-color-on-info-container`. Presets without a distinct information hue define
|
|
702
|
+
it as their tertiary family (Material, Glass, Studio, Day Shift); **smrt** uses a
|
|
703
|
+
steel blue (`#2c6a96` light, `#6fa8d6` dark) so Information no longer shares amber
|
|
704
|
+
with Warning. `createTheme()` follows a supplied `tertiary` unless `info` is also
|
|
705
|
+
given. Components that show information status (for example `Alert` `variant="info"`)
|
|
706
|
+
should consume these, not tertiary.
|
|
707
|
+
|
|
708
|
+
#### Preset identities on the baseline
|
|
709
|
+
|
|
710
|
+
| Preset | Dark ladder (ground / panel / control / raised) | Depth | State layers (hover / pressed / selected) | Controls |
|
|
711
|
+
| --- | --- | --- | --- | --- |
|
|
712
|
+
| Material | `#121316` / `#1b1c20` / `#26282c` / `#34363a` (M3 tonal, no borders needed; control border is transparent) | contact + ambient; dark adds the inset highlight | .08 / .12 / .12 | radius md / sm, 2.75rem |
|
|
713
|
+
| Glass | `#0b0b0f` ground; panel 72% and popover 88% frosted glass, input a recessed 28% black well; blur 20px, saturation 180% | softest, widest ambient | .10 / .14 / .14 | radius lg / md, 2.75rem |
|
|
714
|
+
| Studio | `#131416` / `#131416` / `#1c1d20` / `#292a2d` (flat, crisp hairlines, panel equals ground) | elevation 1 is the hairline; tight shadows | .06 / .10 / .10 | radius 0.375rem / sm, 2.5rem |
|
|
715
|
+
| smrt | `#0b0c0e` / `#121417` / `#181b1f` / `#1d2126` (instrument dark, amber signal, steel-blue info) | elevation 1 is the hairline; calm two-layer shadows (no far-spread) | .10 / .16 / .14 | radius sm, 2.5rem |
|
|
716
|
+
| Day Shift | enamel ground, faceplate panel, control a hair lighter, raised = faceplate; hairlines are the bezel | elevation 1 is the bezel hairline; minimal | .06 / .10 / .10 | radius md (6px) / sm, 2.5rem |
|
|
717
|
+
|
|
718
|
+
Elevation 2 to 5 are two-layer shadows built with `shadow()` in `shared.ts`: a contact
|
|
719
|
+
layer (`0 1px 2px`) plus an ambient layer (`0 Npx Mpx` with negative spread), both
|
|
720
|
+
`color-mix`ed from `--smrt-shadow-color`. Dark schemes prepend
|
|
721
|
+
`var(--smrt-inset-highlight)`. Weight is 500 on every preset. Native chrome follows the
|
|
722
|
+
baseline: accent and caret are primary, selection is the primary container, and the
|
|
723
|
+
scrollbar thumb is the strong hairline over a transparent track.
|
|
724
|
+
|
|
725
|
+
`--smrt-glass-border-opacity` and `--smrt-glass-background-opacity` are deprecated: still emitted (from `GlassEffects.borderOpacity`/`backgroundOpacity`, `1` when unset) for one release and removed in the next minor; the former
|
|
726
|
+
tokens, and the ThemeProvider-local `--smrt-glass-surface`/`--smrt-glass-border`,
|
|
727
|
+
were folded into the glass surface colours above.
|
|
728
|
+
|
|
729
|
+
### Typography
|
|
730
|
+
|
|
731
|
+
`themes/styles/base.css` (import once at the app entry) maps headings to type roles
|
|
732
|
+
inside `@layer smrt.structure`, scoped under `body` and `.smrt-theme-root`, so presets'
|
|
733
|
+
display faces reach plain markup. Hosts restyle with unlayered CSS.
|
|
734
|
+
|
|
735
|
+
| Element | Role |
|
|
736
|
+
| --- | --- |
|
|
737
|
+
| `h1` | `display-small` |
|
|
738
|
+
| `h2` | `headline-large` |
|
|
739
|
+
| `h3` | `headline-medium` |
|
|
740
|
+
| `h4` | `headline-small` |
|
|
741
|
+
| `h5` | `title-large` |
|
|
742
|
+
| `h6` | `title-medium` |
|
|
743
|
+
|
|
744
|
+
Each heading takes `font-family`, `font-size`, `line-height`, `font-weight` and
|
|
745
|
+
`letter-spacing` from the role's typography tokens (for example `--smrt-typography-headline-large-size`), and a `margin-block` from
|
|
746
|
+
`--smrt-spacing-*` (first children lose the top margin). Utilities
|
|
747
|
+
`.smrt-{display,headline,title,body,label}-{large,medium,small}` apply the same five
|
|
748
|
+
properties to any element.
|
|
749
|
+
|
|
750
|
+
Tabular figures: the root sets `font-feature-settings: var(--smrt-font-feature-settings)`,
|
|
751
|
+
and `table`, `time`, `[data-smrt-component="DataTable"]`, `[data-smrt-component="Badge"]`
|
|
752
|
+
and `.smrt-tabular` set `font-variant-numeric: tabular-nums`.
|
|
753
|
+
|
|
754
|
+
Preset rhythm: display tracking tightens with size (about -0.02em at display, -0.01em at
|
|
755
|
+
headline, 0 at title), labels weigh 500 (Day Shift keeps its documented bold B612 labels),
|
|
756
|
+
body is 400, and display plus headline roles use the preset's display face where it has one
|
|
757
|
+
(smrt: Space Grotesk; Day Shift: its documented stack).
|
|
758
|
+
|
|
633
759
|
## Advanced Usage
|
|
634
760
|
|
|
635
761
|
### Build-Time Theming
|
|
@@ -3,10 +3,13 @@ import type { Snippet } from 'svelte';
|
|
|
3
3
|
import { onMount, untrack } from 'svelte';
|
|
4
4
|
import { SMRT_CASCADE_LAYERS_CSS } from '../styles/layers.js';
|
|
5
5
|
import { setThemeContext, type ThemeContext } from './context.svelte.js';
|
|
6
|
+
import { derivePrimaryOverrides } from './create-theme.js';
|
|
6
7
|
import { generateThemeVariables } from './css-generator.js';
|
|
7
8
|
import { availablePresets, getTheme, isValidPreset } from './registry.js';
|
|
8
9
|
import type {
|
|
9
10
|
ColorScheme,
|
|
11
|
+
ContrastPreference,
|
|
12
|
+
ResolvedContrast,
|
|
10
13
|
Theme,
|
|
11
14
|
ThemeConfig,
|
|
12
15
|
ThemePreset,
|
|
@@ -16,7 +19,12 @@ import { defaultThemeConfig } from './types.js';
|
|
|
16
19
|
|
|
17
20
|
interface Props {
|
|
18
21
|
/** Default control target density for descendants; individual controls may override. */
|
|
19
|
-
density?: '
|
|
22
|
+
density?: ThemeConfig['density'];
|
|
23
|
+
/**
|
|
24
|
+
* Contrast preference. `auto` (default) follows `prefers-contrast: more`;
|
|
25
|
+
* `forced-colors: active` always wins. Persisted when set explicitly.
|
|
26
|
+
*/
|
|
27
|
+
contrast?: ContrastPreference;
|
|
20
28
|
/** Theme preset to use */
|
|
21
29
|
preset?: ThemePreset;
|
|
22
30
|
/** Color scheme preference */
|
|
@@ -63,6 +71,7 @@ let {
|
|
|
63
71
|
inlineVariables,
|
|
64
72
|
children,
|
|
65
73
|
density,
|
|
74
|
+
contrast = 'auto',
|
|
66
75
|
}: Props = $props();
|
|
67
76
|
|
|
68
77
|
// Internal state — seed from the props (which already default to
|
|
@@ -78,6 +87,8 @@ let config = $state<ThemeConfig>(
|
|
|
78
87
|
colorScheme,
|
|
79
88
|
primaryColor,
|
|
80
89
|
borderRadius,
|
|
90
|
+
density,
|
|
91
|
+
contrast,
|
|
81
92
|
overrides,
|
|
82
93
|
persist,
|
|
83
94
|
storageKey,
|
|
@@ -85,8 +96,33 @@ let config = $state<ThemeConfig>(
|
|
|
85
96
|
);
|
|
86
97
|
|
|
87
98
|
let systemPrefersDark = $state(false);
|
|
99
|
+
let systemContrastMore = $state(false);
|
|
100
|
+
let systemForcedColors = $state(false);
|
|
101
|
+
let isChangingScheme = $state(false);
|
|
88
102
|
let mounted = $state(false);
|
|
89
103
|
|
|
104
|
+
/** Control radius per `borderRadius` choice: [control, small]. `md` is the preset default. */
|
|
105
|
+
const CONTROL_RADIUS: Record<
|
|
106
|
+
NonNullable<ThemeConfig['borderRadius']>,
|
|
107
|
+
[string, string]
|
|
108
|
+
> = {
|
|
109
|
+
none: ['var(--smrt-radius-none)', 'var(--smrt-radius-none)'],
|
|
110
|
+
sm: ['var(--smrt-radius-sm)', 'var(--smrt-radius-sm)'],
|
|
111
|
+
md: ['var(--smrt-radius-md)', 'var(--smrt-radius-sm)'],
|
|
112
|
+
lg: ['var(--smrt-radius-lg)', 'var(--smrt-radius-md)'],
|
|
113
|
+
xl: ['var(--smrt-radius-xl)', 'var(--smrt-radius-lg)'],
|
|
114
|
+
full: ['var(--smrt-radius-full)', 'var(--smrt-radius-full)'],
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const resolvedContrast = $derived<ResolvedContrast>(
|
|
118
|
+
systemForcedColors
|
|
119
|
+
? 'forced'
|
|
120
|
+
: config.contrast === 'more' ||
|
|
121
|
+
(config.contrast !== 'standard' && systemContrastMore)
|
|
122
|
+
? 'more'
|
|
123
|
+
: 'standard',
|
|
124
|
+
);
|
|
125
|
+
|
|
90
126
|
// Derived state
|
|
91
127
|
const currentTheme = $derived<Theme>(getTheme(config.preset));
|
|
92
128
|
|
|
@@ -123,15 +159,34 @@ const styleString = $derived.by(() => {
|
|
|
123
159
|
}
|
|
124
160
|
}
|
|
125
161
|
|
|
126
|
-
//
|
|
162
|
+
// Border radius: only a non-default choice is emitted, so `md` keeps the
|
|
163
|
+
// preset's own control radius from the static stylesheet.
|
|
164
|
+
if (config.borderRadius && config.borderRadius !== 'md') {
|
|
165
|
+
const [control, small] = CONTROL_RADIUS[config.borderRadius];
|
|
166
|
+
declarations.push(
|
|
167
|
+
`--smrt-control-radius: ${control}`,
|
|
168
|
+
`--smrt-control-radius-small: ${small}`,
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Primary colour override: the whole derived set for the resolved scheme so
|
|
173
|
+
// on-primary / container / focus / accent never stay mismatched.
|
|
127
174
|
if (config.primaryColor) {
|
|
128
|
-
|
|
129
|
-
|
|
175
|
+
try {
|
|
176
|
+
for (const [key, value] of Object.entries(
|
|
177
|
+
derivePrimaryOverrides(config.primaryColor, resolvedScheme),
|
|
178
|
+
)) {
|
|
179
|
+
declarations.push(`${key}: ${value}`);
|
|
180
|
+
}
|
|
181
|
+
} catch {
|
|
182
|
+
// Unparseable colour (e.g. a named colour): keep the single variable.
|
|
183
|
+
declarations.push(`--smrt-color-primary: ${config.primaryColor}`);
|
|
184
|
+
}
|
|
130
185
|
}
|
|
131
186
|
|
|
132
|
-
if (density !== undefined) {
|
|
187
|
+
if (config.density !== undefined) {
|
|
133
188
|
declarations.push(
|
|
134
|
-
`--smrt-control-target-min: ${density === 'touch' ? 'var(--smrt-touch-target-min)' : '0px'}`,
|
|
189
|
+
`--smrt-control-target-min: ${config.density === 'touch' ? 'var(--smrt-touch-target-min)' : '0px'}`,
|
|
135
190
|
);
|
|
136
191
|
}
|
|
137
192
|
|
|
@@ -149,6 +204,8 @@ const themeState = $derived<ThemeStateType>({
|
|
|
149
204
|
colorScheme: config.colorScheme,
|
|
150
205
|
resolvedScheme,
|
|
151
206
|
isDark,
|
|
207
|
+
contrast: resolvedContrast,
|
|
208
|
+
isChangingScheme,
|
|
152
209
|
config,
|
|
153
210
|
theme: currentTheme,
|
|
154
211
|
});
|
|
@@ -183,6 +240,10 @@ function persistConfig(): void {
|
|
|
183
240
|
preset: config.preset,
|
|
184
241
|
colorScheme: config.colorScheme,
|
|
185
242
|
borderRadius: config.borderRadius,
|
|
243
|
+
...(config.density ? { density: config.density } : {}),
|
|
244
|
+
...(config.contrast && config.contrast !== 'auto'
|
|
245
|
+
? { contrast: config.contrast }
|
|
246
|
+
: {}),
|
|
186
247
|
});
|
|
187
248
|
localStorage.setItem(config.storageKey!, data);
|
|
188
249
|
} catch {
|
|
@@ -222,6 +283,15 @@ function loadPersistedConfig(): void {
|
|
|
222
283
|
) {
|
|
223
284
|
config.borderRadius = persisted.borderRadius;
|
|
224
285
|
}
|
|
286
|
+
if (
|
|
287
|
+
persisted.density === 'comfortable' ||
|
|
288
|
+
persisted.density === 'touch'
|
|
289
|
+
) {
|
|
290
|
+
config.density = persisted.density;
|
|
291
|
+
}
|
|
292
|
+
if (persisted.contrast === 'standard' || persisted.contrast === 'more') {
|
|
293
|
+
config.contrast = persisted.contrast;
|
|
294
|
+
}
|
|
225
295
|
}
|
|
226
296
|
} catch {
|
|
227
297
|
// Ignore storage errors (e.g., corrupted data, JSON parse errors)
|
|
@@ -253,6 +323,20 @@ onMount(() => {
|
|
|
253
323
|
};
|
|
254
324
|
mediaQuery.addEventListener('change', handler);
|
|
255
325
|
|
|
326
|
+
// Contrast preferences (guarded: matchMedia stubs may omit these queries)
|
|
327
|
+
const moreQuery = window.matchMedia('(prefers-contrast: more)');
|
|
328
|
+
const forcedQuery = window.matchMedia('(forced-colors: active)');
|
|
329
|
+
systemContrastMore = !!moreQuery?.matches;
|
|
330
|
+
systemForcedColors = !!forcedQuery?.matches;
|
|
331
|
+
const moreHandler = (e: MediaQueryListEvent) => {
|
|
332
|
+
systemContrastMore = e.matches;
|
|
333
|
+
};
|
|
334
|
+
const forcedHandler = (e: MediaQueryListEvent) => {
|
|
335
|
+
systemForcedColors = e.matches;
|
|
336
|
+
};
|
|
337
|
+
moreQuery?.addEventListener?.('change', moreHandler);
|
|
338
|
+
forcedQuery?.addEventListener?.('change', forcedHandler);
|
|
339
|
+
|
|
256
340
|
// Load persisted preferences
|
|
257
341
|
loadPersistedConfig();
|
|
258
342
|
|
|
@@ -260,6 +344,9 @@ onMount(() => {
|
|
|
260
344
|
|
|
261
345
|
return () => {
|
|
262
346
|
mediaQuery.removeEventListener('change', handler);
|
|
347
|
+
moreQuery?.removeEventListener?.('change', moreHandler);
|
|
348
|
+
forcedQuery?.removeEventListener?.('change', forcedHandler);
|
|
349
|
+
if (schemeTimer) clearTimeout(schemeTimer);
|
|
263
350
|
};
|
|
264
351
|
}
|
|
265
352
|
});
|
|
@@ -278,9 +365,61 @@ $effect(() => {
|
|
|
278
365
|
|
|
279
366
|
// Color scheme for browser UI
|
|
280
367
|
html.style.colorScheme = resolvedScheme;
|
|
368
|
+
|
|
369
|
+
// Contrast / density contract (themeScript stamps the same pre-paint)
|
|
370
|
+
html.setAttribute('data-contrast', resolvedContrast);
|
|
371
|
+
html.toggleAttribute('data-forced-colors', resolvedContrast === 'forced');
|
|
372
|
+
if (config.density) html.setAttribute('data-density', config.density);
|
|
373
|
+
else html.removeAttribute('data-density');
|
|
281
374
|
}
|
|
282
375
|
});
|
|
283
376
|
|
|
377
|
+
// Scheme crossfade: flag the root for one --smrt-duration-scheme after the
|
|
378
|
+
// resolved scheme CHANGES post-mount (never on first paint, never under
|
|
379
|
+
// prefers-reduced-motion) so base.css/components can transition colours.
|
|
380
|
+
const FALLBACK_SCHEME_MS = 250;
|
|
381
|
+
let schemeTimer: ReturnType<typeof setTimeout> | undefined;
|
|
382
|
+
let lastScheme: 'light' | 'dark' | undefined;
|
|
383
|
+
|
|
384
|
+
function parseDurationMs(value: string): number | undefined {
|
|
385
|
+
const m = /^\s*(\d*\.?\d+)(ms|s)\s*$/.exec(value);
|
|
386
|
+
if (!m) return undefined;
|
|
387
|
+
return Number(m[1]) * (m[2] === 's' ? 1000 : 1);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function schemeDurationMs(): number {
|
|
391
|
+
const style = getComputedStyle(rootEl ?? document.documentElement);
|
|
392
|
+
return (
|
|
393
|
+
parseDurationMs(style.getPropertyValue('--smrt-duration-scheme')) ??
|
|
394
|
+
parseDurationMs(style.getPropertyValue('--smrt-duration-medium1')) ??
|
|
395
|
+
FALLBACK_SCHEME_MS
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
$effect(() => {
|
|
400
|
+
const scheme = resolvedScheme;
|
|
401
|
+
if (!mounted || typeof document === 'undefined') return;
|
|
402
|
+
const previous = lastScheme;
|
|
403
|
+
lastScheme = scheme;
|
|
404
|
+
if (previous === undefined || previous === scheme) return;
|
|
405
|
+
if (window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches) return;
|
|
406
|
+
const ms = untrack(schemeDurationMs);
|
|
407
|
+
isChangingScheme = true;
|
|
408
|
+
if (schemeTimer) clearTimeout(schemeTimer);
|
|
409
|
+
schemeTimer = setTimeout(() => {
|
|
410
|
+
isChangingScheme = false;
|
|
411
|
+
schemeTimer = undefined;
|
|
412
|
+
}, ms);
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
$effect(() => {
|
|
416
|
+
if (typeof document === 'undefined' || !mounted) return;
|
|
417
|
+
document.documentElement.toggleAttribute(
|
|
418
|
+
'data-smrt-theme-changing',
|
|
419
|
+
isChangingScheme,
|
|
420
|
+
);
|
|
421
|
+
});
|
|
422
|
+
|
|
284
423
|
// Sync props to state — but only on actual prop changes. config is seeded
|
|
285
424
|
// from the initial props above and onMount applies persisted preferences, so
|
|
286
425
|
// re-asserting the initial prop values here would clobber the persisted
|
|
@@ -289,6 +428,9 @@ let prevPreset = untrack(() => preset);
|
|
|
289
428
|
let prevColorScheme = untrack(() => colorScheme);
|
|
290
429
|
let prevPrimaryColor = untrack(() => primaryColor);
|
|
291
430
|
let prevBorderRadius = untrack(() => borderRadius);
|
|
431
|
+
let prevDensity = untrack(() => density);
|
|
432
|
+
let prevContrast = untrack(() => contrast);
|
|
433
|
+
let rootEl = $state<HTMLElement | undefined>();
|
|
292
434
|
|
|
293
435
|
$effect(() => {
|
|
294
436
|
if (preset !== prevPreset) {
|
|
@@ -328,6 +470,24 @@ $effect(() => {
|
|
|
328
470
|
}
|
|
329
471
|
});
|
|
330
472
|
|
|
473
|
+
$effect(() => {
|
|
474
|
+
if (density !== prevDensity) {
|
|
475
|
+
prevDensity = density;
|
|
476
|
+
untrack(() => {
|
|
477
|
+
config.density = density;
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
$effect(() => {
|
|
483
|
+
if (contrast !== prevContrast) {
|
|
484
|
+
prevContrast = contrast;
|
|
485
|
+
untrack(() => {
|
|
486
|
+
config.contrast = contrast;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
});
|
|
490
|
+
|
|
331
491
|
// Sync overrides prop to state
|
|
332
492
|
$effect(() => {
|
|
333
493
|
config.overrides = overrides;
|
|
@@ -348,13 +508,19 @@ $effect(() => {
|
|
|
348
508
|
</svelte:head>
|
|
349
509
|
|
|
350
510
|
<div
|
|
511
|
+
bind:this={rootEl}
|
|
351
512
|
class="smrt-theme-root"
|
|
352
513
|
class:dark={isDark}
|
|
353
514
|
class:no-paint={!paintSurface}
|
|
354
515
|
class:smrt-theme-glass={config.preset === 'glass'}
|
|
355
516
|
style={styleString || undefined}
|
|
356
517
|
data-theme={config.preset}
|
|
357
|
-
data-color-scheme={resolvedScheme}
|
|
518
|
+
data-color-scheme={resolvedScheme}
|
|
519
|
+
data-contrast={resolvedContrast}
|
|
520
|
+
data-forced-colors={resolvedContrast === 'forced' ? '' : undefined}
|
|
521
|
+
data-density={config.density}
|
|
522
|
+
data-smrt-theme-changing={isChangingScheme ? '' : undefined}
|
|
523
|
+
data-smrt-component="ThemeProvider"
|
|
358
524
|
>
|
|
359
525
|
{@render children()}
|
|
360
526
|
</div>
|
|
@@ -377,12 +543,6 @@ $effect(() => {
|
|
|
377
543
|
font-family: inherit;
|
|
378
544
|
}
|
|
379
545
|
|
|
380
|
-
/* Glass theme specific base styles */
|
|
381
|
-
:global(.smrt-theme-glass) {
|
|
382
|
-
--smrt-glass-surface: var(--smrt-color-surface);
|
|
383
|
-
--smrt-glass-border: var(--smrt-color-outline);
|
|
384
|
-
}
|
|
385
|
-
|
|
386
546
|
/* HappyVertical ("Day Shift") focus indicator (#2318).
|
|
387
547
|
* The brand focus ring is part of the preset, so it has to travel on BOTH
|
|
388
548
|
* delivery paths: this mirrors the identical rule in
|
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import type { Snippet } from 'svelte';
|
|
2
|
-
import type { ColorScheme, ThemeConfig, ThemePreset } from './types.js';
|
|
2
|
+
import type { ColorScheme, ContrastPreference, ThemeConfig, ThemePreset } from './types.js';
|
|
3
3
|
interface Props {
|
|
4
4
|
/** Default control target density for descendants; individual controls may override. */
|
|
5
|
-
density?: '
|
|
5
|
+
density?: ThemeConfig['density'];
|
|
6
|
+
/**
|
|
7
|
+
* Contrast preference. `auto` (default) follows `prefers-contrast: more`;
|
|
8
|
+
* `forced-colors: active` always wins. Persisted when set explicitly.
|
|
9
|
+
*/
|
|
10
|
+
contrast?: ContrastPreference;
|
|
6
11
|
/** Theme preset to use */
|
|
7
12
|
preset?: ThemePreset;
|
|
8
13
|
/** Color scheme preference */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/themes/ThemeProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"ThemeProvider.svelte.d.ts","sourceRoot":"","sources":["../../src/themes/ThemeProvider.svelte.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,QAAQ,CAAC;AAOtC,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAGlB,WAAW,EACX,WAAW,EAEZ,MAAM,YAAY,CAAC;AAIpB,UAAU,KAAK;IACb,wFAAwF;IACxF,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAC;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,0BAA0B;IAC1B,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,8BAA8B;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,oCAAoC;IACpC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,YAAY,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,CAAC;IAC3C,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0CAA0C;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kCAAkC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;;;;;;OAQG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,cAAc;IACd,QAAQ,EAAE,OAAO,CAAC;CACnB;AAqdD,QAAA,MAAM,aAAa,2CAAwC,CAAC;AAC5D,KAAK,aAAa,GAAG,UAAU,CAAC,OAAO,aAAa,CAAC,CAAC;AACtD,eAAe,aAAa,CAAC"}
|
|
@@ -22,8 +22,18 @@ export interface CreateThemeOptions {
|
|
|
22
22
|
elevation?: Partial<ElevationScale>;
|
|
23
23
|
/** Custom easing curves (optional) */
|
|
24
24
|
easing?: Partial<EasingScale>;
|
|
25
|
-
/** Glass effects (optional) */
|
|
25
|
+
/** Glass effects (optional; defaults to opaque) */
|
|
26
26
|
glass?: GlassEffects;
|
|
27
|
+
/** Optional overrides for the premium-baseline token families */
|
|
28
|
+
surfaces?: Theme['surfaces'];
|
|
29
|
+
hairlines?: Theme['hairlines'];
|
|
30
|
+
states?: Theme['states'];
|
|
31
|
+
shadowColor?: Theme['shadowColor'];
|
|
32
|
+
controls?: Theme['controls'];
|
|
33
|
+
focus?: Theme['focus'];
|
|
34
|
+
native?: Theme['native'];
|
|
35
|
+
schemeDuration?: string;
|
|
36
|
+
fontFeatureSettings?: string;
|
|
27
37
|
/** Font family */
|
|
28
38
|
fontFamily?: string;
|
|
29
39
|
/** Base theme to extend (copies all tokens as starting point) */
|
|
@@ -96,4 +106,15 @@ export declare function isThemeRegistered(id: string): Promise<boolean>;
|
|
|
96
106
|
* ```
|
|
97
107
|
*/
|
|
98
108
|
export declare function createThemeFromColor(primaryColor: string, id: string, name: string, options?: Omit<CreateThemeOptions, 'id' | 'name' | 'light'>): Theme;
|
|
109
|
+
/**
|
|
110
|
+
* Derive the primary-dependent token overrides for a user-chosen primary colour
|
|
111
|
+
* in one resolved scheme, using the same derivation as `createThemeFromColor`
|
|
112
|
+
* (light: the colour itself with a ~12.5% container; dark: the tonal primary
|
|
113
|
+
* pair from `generateTheme`). `on-primary` is the higher-contrast of white and
|
|
114
|
+
* black on the final primary so an override never leaves it mismatched. Focus,
|
|
115
|
+
* accent, caret and selection tokens follow the primary via `var()`.
|
|
116
|
+
*
|
|
117
|
+
* Returns `--smrt-*` declarations; throws on an unparseable colour.
|
|
118
|
+
*/
|
|
119
|
+
export declare function derivePrimaryOverrides(color: string, scheme: 'light' | 'dark'): Record<string, string>;
|
|
99
120
|
//# sourceMappingURL=create-theme.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-theme.d.ts","sourceRoot":"","sources":["../../src/themes/create-theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"create-theme.d.ts","sourceRoot":"","sources":["../../src/themes/create-theme.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAoBH,OAAO,KAAK,EACV,YAAY,EACZ,WAAW,EACX,cAAc,EACd,YAAY,EACZ,KAAK,EACL,WAAW,EACX,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,wCAAwC;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,mBAAmB;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,+BAA+B;IAC/B,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,IAAI,CAAC,YAAY,EAAE,SAAS,GAAG,YAAY,CAAC,CAAC;IAC5E,iFAAiF;IACjF,IAAI,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,CAAC;IAC7B,wDAAwD;IACxD,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,CAAC;IACtC,mCAAmC;IACnC,SAAS,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;IACpC,sCAAsC;IACtC,MAAM,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IAC9B,mDAAmD;IACnD,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,iEAAiE;IACjE,QAAQ,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,KAAK,CAAC,WAAW,CAAC,CAAC;IAC/B,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzB,WAAW,CAAC,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;IACnC,QAAQ,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;IACvB,MAAM,CAAC,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,kBAAkB;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iEAAiE;IACjE,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AAkRD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,KAAK,CA8E9D;AAkBD;;;GAGG;AACH,wBAAsB,oBAAoB,IAAI,OAAO,CAAC,IAAI,CAAC,CAG1D;AAgBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAEhD;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,GAAG,SAAS,CAIjE;AAED;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAEpE;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,YAAY,EAAE,MAAM,EACpB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,OAAO,CAAC,EAAE,IAAI,CAAC,kBAAkB,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAAC,GAC1D,KAAK,CAWP;AAID;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CACpC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,OAAO,GAAG,MAAM,GACvB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAkCxB"}
|