@entifix/react-controls 0.18.1 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (180) hide show
  1. package/README.md +5 -5
  2. package/dist/i18n/catalog/en.d.ts.map +1 -1
  3. package/dist/i18n/catalog/en.js +13 -1
  4. package/dist/i18n/catalog/en.js.map +1 -1
  5. package/dist/i18n/catalog/es.d.ts +12 -0
  6. package/dist/i18n/catalog/es.d.ts.map +1 -1
  7. package/dist/i18n/catalog/es.js +13 -1
  8. package/dist/i18n/catalog/es.js.map +1 -1
  9. package/dist/i18n/catalog/index.d.ts +24 -0
  10. package/dist/i18n/catalog/index.d.ts.map +1 -1
  11. package/dist/index.d.ts +7 -0
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -1
  15. package/dist/primitives.d.ts +7 -0
  16. package/dist/primitives.d.ts.map +1 -1
  17. package/dist/primitives.js +7 -0
  18. package/dist/primitives.js.map +1 -1
  19. package/dist/theme/theme-context.d.ts +23 -7
  20. package/dist/theme/theme-context.d.ts.map +1 -1
  21. package/dist/theme/theme-context.js +76 -14
  22. package/dist/theme/theme-context.js.map +1 -1
  23. package/dist/theme/theme-script.d.ts +4 -3
  24. package/dist/theme/theme-script.d.ts.map +1 -1
  25. package/dist/theme/theme-script.js +8 -3
  26. package/dist/theme/theme-script.js.map +1 -1
  27. package/dist/theme/types.d.ts +19 -3
  28. package/dist/theme/types.d.ts.map +1 -1
  29. package/dist/theme/types.js.map +1 -1
  30. package/dist/ui/atoms/alert/alert-dismiss.d.ts +9 -0
  31. package/dist/ui/atoms/alert/alert-dismiss.d.ts.map +1 -0
  32. package/dist/ui/atoms/alert/alert-dismiss.js +28 -0
  33. package/dist/ui/atoms/alert/alert-dismiss.js.map +1 -0
  34. package/dist/ui/atoms/alert/alert.d.ts +24 -0
  35. package/dist/ui/atoms/alert/alert.d.ts.map +1 -0
  36. package/dist/ui/atoms/alert/alert.js +50 -0
  37. package/dist/ui/atoms/alert/alert.js.map +1 -0
  38. package/dist/ui/atoms/alert/index.d.ts +3 -0
  39. package/dist/ui/atoms/alert/index.d.ts.map +1 -0
  40. package/dist/ui/atoms/alert/index.js +4 -0
  41. package/dist/ui/atoms/alert/index.js.map +1 -0
  42. package/dist/ui/atoms/alert/tone.d.ts +8 -0
  43. package/dist/ui/atoms/alert/tone.d.ts.map +1 -0
  44. package/dist/ui/atoms/alert/tone.js +11 -0
  45. package/dist/ui/atoms/alert/tone.js.map +1 -0
  46. package/dist/ui/atoms/button/button-recipe.js +1 -1
  47. package/dist/ui/atoms/button/button-recipe.js.map +1 -1
  48. package/dist/ui/atoms/button/index.d.ts +1 -0
  49. package/dist/ui/atoms/button/index.d.ts.map +1 -1
  50. package/dist/ui/atoms/button/index.js +1 -0
  51. package/dist/ui/atoms/button/index.js.map +1 -1
  52. package/dist/ui/atoms/button/submit-button.d.ts +13 -0
  53. package/dist/ui/atoms/button/submit-button.d.ts.map +1 -0
  54. package/dist/ui/atoms/button/submit-button.js +29 -0
  55. package/dist/ui/atoms/button/submit-button.js.map +1 -0
  56. package/dist/ui/atoms/cell-value/cell-value.d.ts +6 -1
  57. package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
  58. package/dist/ui/atoms/cell-value/cell-value.js +20 -2
  59. package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
  60. package/dist/ui/atoms/field/checkbox.d.ts.map +1 -1
  61. package/dist/ui/atoms/field/checkbox.js +21 -1
  62. package/dist/ui/atoms/field/checkbox.js.map +1 -1
  63. package/dist/ui/atoms/field/field.js +1 -1
  64. package/dist/ui/atoms/field/field.js.map +1 -1
  65. package/dist/ui/atoms/field-control/field-control.d.ts +6 -1
  66. package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
  67. package/dist/ui/atoms/field-control/field-control.js +23 -3
  68. package/dist/ui/atoms/field-control/field-control.js.map +1 -1
  69. package/dist/ui/atoms/money/index.d.ts +4 -0
  70. package/dist/ui/atoms/money/index.d.ts.map +1 -0
  71. package/dist/ui/atoms/money/index.js +5 -0
  72. package/dist/ui/atoms/money/index.js.map +1 -0
  73. package/dist/ui/atoms/money/money-input.d.ts +19 -0
  74. package/dist/ui/atoms/money/money-input.d.ts.map +1 -0
  75. package/dist/ui/atoms/money/money-input.js +75 -0
  76. package/dist/ui/atoms/money/money-input.js.map +1 -0
  77. package/dist/ui/atoms/money/money-math.d.ts +21 -0
  78. package/dist/ui/atoms/money/money-math.d.ts.map +1 -0
  79. package/dist/ui/atoms/money/money-math.js +36 -0
  80. package/dist/ui/atoms/money/money-math.js.map +1 -0
  81. package/dist/ui/atoms/money/money.d.ts +19 -0
  82. package/dist/ui/atoms/money/money.d.ts.map +1 -0
  83. package/dist/ui/atoms/money/money.js +26 -0
  84. package/dist/ui/atoms/money/money.js.map +1 -0
  85. package/dist/ui/atoms/number-input/index.d.ts +3 -0
  86. package/dist/ui/atoms/number-input/index.d.ts.map +1 -0
  87. package/dist/ui/atoms/number-input/index.js +4 -0
  88. package/dist/ui/atoms/number-input/index.js.map +1 -0
  89. package/dist/ui/atoms/number-input/number-input.d.ts +10 -0
  90. package/dist/ui/atoms/number-input/number-input.d.ts.map +1 -0
  91. package/dist/ui/atoms/number-input/number-input.js +23 -0
  92. package/dist/ui/atoms/number-input/number-input.js.map +1 -0
  93. package/dist/ui/atoms/number-input/quantity-input.d.ts +22 -0
  94. package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -0
  95. package/dist/ui/atoms/number-input/quantity-input.js +99 -0
  96. package/dist/ui/atoms/number-input/quantity-input.js.map +1 -0
  97. package/dist/ui/atoms/skeleton/skeleton.js +1 -1
  98. package/dist/ui/atoms/skeleton/skeleton.js.map +1 -1
  99. package/dist/ui/atoms/status-tag/index.d.ts +2 -0
  100. package/dist/ui/atoms/status-tag/index.d.ts.map +1 -0
  101. package/dist/ui/atoms/status-tag/index.js +3 -0
  102. package/dist/ui/atoms/status-tag/index.js.map +1 -0
  103. package/dist/ui/atoms/status-tag/status-tag.d.ts +17 -0
  104. package/dist/ui/atoms/status-tag/status-tag.d.ts.map +1 -0
  105. package/dist/ui/atoms/status-tag/status-tag.js +37 -0
  106. package/dist/ui/atoms/status-tag/status-tag.js.map +1 -0
  107. package/dist/ui/atoms/switch/switch.d.ts.map +1 -1
  108. package/dist/ui/atoms/switch/switch.js +6 -3
  109. package/dist/ui/atoms/switch/switch.js.map +1 -1
  110. package/dist/ui/molecules/breadcrumbs/breadcrumbs.d.ts +11 -2
  111. package/dist/ui/molecules/breadcrumbs/breadcrumbs.d.ts.map +1 -1
  112. package/dist/ui/molecules/breadcrumbs/breadcrumbs.js +9 -7
  113. package/dist/ui/molecules/breadcrumbs/breadcrumbs.js.map +1 -1
  114. package/dist/ui/molecules/column-settings/column-settings.js +1 -1
  115. package/dist/ui/molecules/column-settings/column-settings.js.map +1 -1
  116. package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +2 -2
  117. package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
  118. package/dist/ui/molecules/description-list/description-list.d.ts +17 -0
  119. package/dist/ui/molecules/description-list/description-list.d.ts.map +1 -0
  120. package/dist/ui/molecules/description-list/description-list.js +36 -0
  121. package/dist/ui/molecules/description-list/description-list.js.map +1 -0
  122. package/dist/ui/molecules/description-list/index.d.ts +2 -0
  123. package/dist/ui/molecules/description-list/index.d.ts.map +1 -0
  124. package/dist/ui/molecules/description-list/index.js +3 -0
  125. package/dist/ui/molecules/description-list/index.js.map +1 -0
  126. package/dist/ui/molecules/drawer/drawer.js +3 -3
  127. package/dist/ui/molecules/drawer/drawer.js.map +1 -1
  128. package/dist/ui/molecules/empty-state/empty-state.d.ts +15 -0
  129. package/dist/ui/molecules/empty-state/empty-state.d.ts.map +1 -0
  130. package/dist/ui/molecules/empty-state/empty-state.js +29 -0
  131. package/dist/ui/molecules/empty-state/empty-state.js.map +1 -0
  132. package/dist/ui/molecules/empty-state/index.d.ts +2 -0
  133. package/dist/ui/molecules/empty-state/index.d.ts.map +1 -0
  134. package/dist/ui/molecules/empty-state/index.js +3 -0
  135. package/dist/ui/molecules/empty-state/index.js.map +1 -0
  136. package/dist/ui/molecules/entity-link-input/entity-link-input.d.ts.map +1 -1
  137. package/dist/ui/molecules/entity-link-input/entity-link-input.js +7 -4
  138. package/dist/ui/molecules/entity-link-input/entity-link-input.js.map +1 -1
  139. package/dist/ui/molecules/entity-link-picker/entity-link-picker.js +2 -2
  140. package/dist/ui/molecules/entity-link-picker/entity-link-picker.js.map +1 -1
  141. package/dist/ui/molecules/media-gallery/index.d.ts +2 -0
  142. package/dist/ui/molecules/media-gallery/index.d.ts.map +1 -0
  143. package/dist/ui/molecules/media-gallery/index.js +3 -0
  144. package/dist/ui/molecules/media-gallery/index.js.map +1 -0
  145. package/dist/ui/molecules/media-gallery/media-gallery.d.ts +31 -0
  146. package/dist/ui/molecules/media-gallery/media-gallery.d.ts.map +1 -0
  147. package/dist/ui/molecules/media-gallery/media-gallery.js +64 -0
  148. package/dist/ui/molecules/media-gallery/media-gallery.js.map +1 -0
  149. package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
  150. package/dist/ui/molecules/menu/menu.js +6 -4
  151. package/dist/ui/molecules/menu/menu.js.map +1 -1
  152. package/dist/ui/molecules/nav-menu/nav-menu.js +4 -4
  153. package/dist/ui/molecules/nav-menu/nav-menu.js.map +1 -1
  154. package/dist/ui/molecules/segmented-nav/segment-classes.d.ts +3 -3
  155. package/dist/ui/molecules/segmented-nav/segment-classes.d.ts.map +1 -1
  156. package/dist/ui/molecules/segmented-nav/segment-classes.js +2 -2
  157. package/dist/ui/molecules/segmented-nav/segment-classes.js.map +1 -1
  158. package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts +14 -4
  159. package/dist/ui/molecules/segmented-nav/segmented-nav.d.ts.map +1 -1
  160. package/dist/ui/molecules/segmented-nav/segmented-nav.js +6 -5
  161. package/dist/ui/molecules/segmented-nav/segmented-nav.js.map +1 -1
  162. package/dist/ui/molecules/split-view/split-view.js +3 -3
  163. package/dist/ui/molecules/split-view/split-view.js.map +1 -1
  164. package/dist/ui/molecules/tab-strip/tab-strip.d.ts +51 -12
  165. package/dist/ui/molecules/tab-strip/tab-strip.d.ts.map +1 -1
  166. package/dist/ui/molecules/tab-strip/tab-strip.js +125 -34
  167. package/dist/ui/molecules/tab-strip/tab-strip.js.map +1 -1
  168. package/dist/ui/molecules/toast-region/toast-region.js +1 -1
  169. package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
  170. package/dist/ui/organisms/command-palette/command-palette.js +6 -6
  171. package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
  172. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
  173. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +8 -2
  174. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
  175. package/dist/ui/organisms/entity-form/entity-form.js +4 -2
  176. package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
  177. package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
  178. package/dist/ui/organisms/entity-table/entity-table.js +2 -1
  179. package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
  180. package/package.json +5 -5
@@ -2,30 +2,73 @@
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
3
  import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
4
4
  const ThemeContext = /*#__PURE__*/ createContext(null);
5
+ const DENSITIES = [
6
+ 'comfortable',
7
+ 'compact'
8
+ ];
9
+ const isDensity = (value)=>DENSITIES.includes(value);
10
+ /** Storage that may be absent or refuse (a private window, blocked site data). */ function read(key) {
11
+ try {
12
+ return window.localStorage.getItem(key);
13
+ } catch (unused) {
14
+ return null;
15
+ }
16
+ }
17
+ function write(key, value) {
18
+ try {
19
+ window.localStorage.setItem(key, value);
20
+ } catch (unused) {
21
+ // Nothing to remember it in; the choice still applies to this page.
22
+ }
23
+ }
5
24
  /**
6
- * Applies the active palette by writing `data-theme` on <html> and persisting
7
- * the choice. Static palette VALUES live in CSS (@entifix/style presets or
8
- * an app-local themes.css); this flips the selector. Dynamic palettes passed via
9
- * `palettes` are injected at runtime under the same `data-theme` mechanism.
10
- */ export function ThemeProvider({ children, themes, defaultTheme, storageKey = 'entifix-theme', palettes }) {
25
+ * Applies the active palette and density by writing `data-theme` and
26
+ * `data-density` on <html>, and remembers what the user chooses. Static palette
27
+ * VALUES live in CSS (@entifix/style presets or an app-local themes.css); this
28
+ * flips the selector. Dynamic palettes passed via `palettes` are injected at
29
+ * runtime under the same `data-theme` mechanism.
30
+ *
31
+ * ⚠️ **Only a choice is written to storage, never a default.** The provider
32
+ * used to persist whatever theme it held from an effect, and under StrictMode
33
+ * the effect ran before the stored value had been read back: the default
34
+ * overwrote the stored theme, and a reload in development always came back on
35
+ * the default. Writing in `setTheme` and `setDensity` alone means mounting,
36
+ * however many times, can never lose what was chosen.
37
+ */ export function ThemeProvider({ children, themes, defaultTheme, defaultDensity, storageKey = 'entifix-theme', palettes }) {
11
38
  var _ref;
12
39
  var _themes_;
13
40
  const initial = (_ref = defaultTheme != null ? defaultTheme : (_themes_ = themes[0]) == null ? void 0 : _themes_.id) != null ? _ref : '';
14
41
  const [theme, setThemeState] = useState(initial);
42
+ const densityKey = `${storageKey}-density`;
43
+ // `undefined` until a density is stored, chosen or defaulted: the document's
44
+ // own `data-density` stands until then.
45
+ const [density, setDensityState] = useState(defaultDensity);
15
46
  const isKnown = useCallback((value)=>typeof value === 'string' && themes.some((t)=>t.id === value), [
16
47
  themes
17
48
  ]);
18
49
  // Hydrate from a previously stored preference on mount. This intentionally
19
50
  // runs in an effect (not a lazy initializer): localStorage is unavailable
20
51
  // during SSR, so the server renders `initial` and the client corrects after
21
- // mount — reading it in render would cause a hydration mismatch.
52
+ // mount — reading it in render would cause a hydration mismatch. It only
53
+ // reads, so running twice changes nothing.
22
54
  useEffect(()=>{
23
- const stored = window.localStorage.getItem(storageKey);
55
+ const stored = read(storageKey);
24
56
  // eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot SSR-safe hydration, see comment above
25
57
  if (isKnown(stored)) setThemeState(stored);
58
+ // A stored density wins; otherwise the default, otherwise whatever the
59
+ // document was served with, so the context never reports a density the
60
+ // page is not in.
61
+ const storedDensity = read(densityKey);
62
+ const served = document.documentElement.dataset.density;
63
+ if (isDensity(storedDensity)) setDensityState(storedDensity);
64
+ else if (defaultDensity === undefined && isDensity(served)) {
65
+ setDensityState(served);
66
+ }
26
67
  }, [
27
68
  storageKey,
28
- isKnown
69
+ densityKey,
70
+ isKnown,
71
+ defaultDensity
29
72
  ]);
30
73
  // Inject runtime palettes as a single <style> appended to <head>. Being last
31
74
  // in the cascade, these win over any static preset defining the same tokens.
@@ -47,23 +90,42 @@ const ThemeContext = /*#__PURE__*/ createContext(null);
47
90
  palettes,
48
91
  storageKey
49
92
  ]);
50
- // Reflect the active theme onto the document + persist it.
93
+ // Reflect the active theme and density onto the document. Storage is not
94
+ // touched here — see the ⚠️ above.
51
95
  useEffect(()=>{
52
96
  document.documentElement.dataset.theme = theme;
53
- window.localStorage.setItem(storageKey, theme);
54
97
  }, [
55
- theme,
98
+ theme
99
+ ]);
100
+ useEffect(()=>{
101
+ if (density !== undefined) document.documentElement.dataset.density = density;
102
+ }, [
103
+ density
104
+ ]);
105
+ const setTheme = useCallback((next)=>{
106
+ setThemeState(next);
107
+ write(storageKey, next);
108
+ }, [
56
109
  storageKey
57
110
  ]);
58
- const setTheme = useCallback((next)=>setThemeState(next), []);
111
+ const setDensity = useCallback((next)=>{
112
+ setDensityState(next);
113
+ write(densityKey, next);
114
+ }, [
115
+ densityKey
116
+ ]);
59
117
  const value = useMemo(()=>({
60
118
  theme,
61
119
  setTheme,
62
- themes
120
+ themes,
121
+ density: density != null ? density : 'comfortable',
122
+ setDensity
63
123
  }), [
64
124
  theme,
65
125
  setTheme,
66
- themes
126
+ themes,
127
+ density,
128
+ setDensity
67
129
  ]);
68
130
  return /*#__PURE__*/ _jsx(ThemeContext.Provider, {
69
131
  value: value,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/theme-context.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n type PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nimport type { ThemeContextValue, ThemeOption, ThemePalette } from './types.js';\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps extends PropsWithChildren {\n /** The themes this app exposes. First entry is the default unless\n * `defaultTheme` is given. Drives `data-theme` + the switcher. */\n themes: ThemeOption[];\n /** Theme id used for the first paint / when nothing is stored. */\n defaultTheme?: string;\n /** localStorage key — namespace it per app so apps don't clobber each other. */\n storageKey?: string;\n /**\n * Runtime palettes for brands not shipped as CSS presets (multi-tenant /\n * white-label). Keyed by theme id; injected as `[data-theme='id']{…}` vars\n * appended after the app stylesheet, so they override static presets.\n */\n palettes?: Record<string, ThemePalette>;\n}\n\n/**\n * Applies the active palette by writing `data-theme` on <html> and persisting\n * the choice. Static palette VALUES live in CSS (@entifix/style presets or\n * an app-local themes.css); this flips the selector. Dynamic palettes passed via\n * `palettes` are injected at runtime under the same `data-theme` mechanism.\n */\nexport function ThemeProvider({\n children,\n themes,\n defaultTheme,\n storageKey = 'entifix-theme',\n palettes,\n}: ThemeProviderProps) {\n const initial = defaultTheme ?? themes[0]?.id ?? '';\n const [theme, setThemeState] = useState<string>(initial);\n\n const isKnown = useCallback(\n (value: unknown): value is string =>\n typeof value === 'string' && themes.some(t => t.id === value),\n [themes],\n );\n\n // Hydrate from a previously stored preference on mount. This intentionally\n // runs in an effect (not a lazy initializer): localStorage is unavailable\n // during SSR, so the server renders `initial` and the client corrects after\n // mount — reading it in render would cause a hydration mismatch.\n useEffect(() => {\n const stored = window.localStorage.getItem(storageKey);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot SSR-safe hydration, see comment above\n if (isKnown(stored)) setThemeState(stored);\n }, [storageKey, isKnown]);\n\n // Inject runtime palettes as a single <style> appended to <head>. Being last\n // in the cascade, these win over any static preset defining the same tokens.\n useEffect(() => {\n if (!palettes || Object.keys(palettes).length === 0) return;\n const styleId = `${storageKey}-runtime-palettes`;\n const css = Object.entries(palettes)\n .map(([id, palette]) => {\n const decls = Object.entries(palette)\n .map(([token, value]) => `--color-${token}: ${value};`)\n .join(' ');\n return `[data-theme='${id}'] { ${decls} }`;\n })\n .join('\\n');\n\n let el = document.getElementById(styleId) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = styleId;\n document.head.appendChild(el);\n }\n el.textContent = css;\n }, [palettes, storageKey]);\n\n // Reflect the active theme onto the document + persist it.\n useEffect(() => {\n document.documentElement.dataset.theme = theme;\n window.localStorage.setItem(storageKey, theme);\n }, [theme, storageKey]);\n\n const setTheme = useCallback((next: string) => setThemeState(next), []);\n\n const value = useMemo<ThemeContextValue>(\n () => ({ theme, setTheme, themes }),\n [theme, setTheme, themes],\n );\n\n return (\n <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n );\n}\n\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');\n return ctx;\n}\n"],"names":["createContext","useCallback","useContext","useEffect","useMemo","useState","ThemeContext","ThemeProvider","children","themes","defaultTheme","storageKey","palettes","initial","id","theme","setThemeState","isKnown","value","some","t","stored","window","localStorage","getItem","Object","keys","length","styleId","css","entries","map","palette","decls","token","join","el","document","getElementById","createElement","head","appendChild","textContent","documentElement","dataset","setItem","setTheme","next","Provider","useTheme","ctx","Error"],"mappings":"AAAA;;AAEA,SACEA,aAAa,EAEbC,WAAW,EACXC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,QAAQ,QACH,QAAQ;AAIf,MAAMC,6BAAeN,cAAwC;AAkB7D;;;;;CAKC,GACD,OAAO,SAASO,cAAc,EAC5BC,QAAQ,EACRC,MAAM,EACNC,YAAY,EACZC,aAAa,eAAe,EAC5BC,QAAQ,EACW;QACHF;QAAgBD;IAAhC,MAAMI,WAAUH,OAAAA,uBAAAA,gBAAgBD,WAAAA,MAAM,CAAC,EAAE,qBAATA,SAAWK,EAAE,YAA7BJ,OAAiC;IACjD,MAAM,CAACK,OAAOC,cAAc,GAAGX,SAAiBQ;IAEhD,MAAMI,UAAUhB,YACd,CAACiB,QACC,OAAOA,UAAU,YAAYT,OAAOU,IAAI,CAACC,CAAAA,IAAKA,EAAEN,EAAE,KAAKI,QACzD;QAACT;KAAO;IAGV,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,iEAAiE;IACjEN,UAAU;QACR,MAAMkB,SAASC,OAAOC,YAAY,CAACC,OAAO,CAACb;QAC3C,6GAA6G;QAC7G,IAAIM,QAAQI,SAASL,cAAcK;IACrC,GAAG;QAACV;QAAYM;KAAQ;IAExB,6EAA6E;IAC7E,6EAA6E;IAC7Ed,UAAU;QACR,IAAI,CAACS,YAAYa,OAAOC,IAAI,CAACd,UAAUe,MAAM,KAAK,GAAG;QACrD,MAAMC,UAAU,GAAGjB,WAAW,iBAAiB,CAAC;QAChD,MAAMkB,MAAMJ,OAAOK,OAAO,CAAClB,UACxBmB,GAAG,CAAC,CAAC,CAACjB,IAAIkB,QAAQ;YACjB,MAAMC,QAAQR,OAAOK,OAAO,CAACE,SAC1BD,GAAG,CAAC,CAAC,CAACG,OAAOhB,MAAM,GAAK,CAAC,QAAQ,EAAEgB,MAAM,EAAE,EAAEhB,MAAM,CAAC,CAAC,EACrDiB,IAAI,CAAC;YACR,OAAO,CAAC,aAAa,EAAErB,GAAG,KAAK,EAAEmB,MAAM,EAAE,CAAC;QAC5C,GACCE,IAAI,CAAC;QAER,IAAIC,KAAKC,SAASC,cAAc,CAACV;QACjC,IAAI,CAACQ,IAAI;YACPA,KAAKC,SAASE,aAAa,CAAC;YAC5BH,GAAGtB,EAAE,GAAGc;YACRS,SAASG,IAAI,CAACC,WAAW,CAACL;QAC5B;QACAA,GAAGM,WAAW,GAAGb;IACnB,GAAG;QAACjB;QAAUD;KAAW;IAEzB,2DAA2D;IAC3DR,UAAU;QACRkC,SAASM,eAAe,CAACC,OAAO,CAAC7B,KAAK,GAAGA;QACzCO,OAAOC,YAAY,CAACsB,OAAO,CAAClC,YAAYI;IAC1C,GAAG;QAACA;QAAOJ;KAAW;IAEtB,MAAMmC,WAAW7C,YAAY,CAAC8C,OAAiB/B,cAAc+B,OAAO,EAAE;IAEtE,MAAM7B,QAAQd,QACZ,IAAO,CAAA;YAAEW;YAAO+B;YAAUrC;QAAO,CAAA,GACjC;QAACM;QAAO+B;QAAUrC;KAAO;IAG3B,qBACE,KAACH,aAAa0C,QAAQ;QAAC9B,OAAOA;kBAAQV;;AAE1C;AAEA,OAAO,SAASyC;IACd,MAAMC,MAAMhD,WAAWI;IACvB,IAAI,CAAC4C,KAAK,MAAM,IAAIC,MAAM;IAC1B,OAAOD;AACT"}
1
+ {"version":3,"sources":["../../src/theme/theme-context.tsx"],"sourcesContent":["'use client';\n\nimport {\n createContext,\n type PropsWithChildren,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n} from 'react';\n\nimport type {\n Density,\n ThemeContextValue,\n ThemeOption,\n ThemePalette,\n} from './types.js';\n\nconst ThemeContext = createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps extends PropsWithChildren {\n /** The themes this app exposes. First entry is the default unless\n * `defaultTheme` is given. Drives `data-theme` + the switcher. */\n themes: ThemeOption[];\n /** Theme id used for the first paint / when nothing is stored. */\n defaultTheme?: string;\n /**\n * The density when nothing is stored. Omitted, the provider leaves\n * `data-density` as the document has it until a density is chosen.\n */\n defaultDensity?: Density;\n /**\n * localStorage key — namespace it per app so apps don't clobber each other.\n * The density is stored beside it, under `${storageKey}-density`.\n */\n storageKey?: string;\n /**\n * Runtime palettes for brands not shipped as CSS presets (multi-tenant /\n * white-label). Keyed by theme id; injected as `[data-theme='id']{…}` vars\n * appended after the app stylesheet, so they override static presets.\n */\n palettes?: Record<string, ThemePalette>;\n}\n\nconst DENSITIES: readonly Density[] = ['comfortable', 'compact'];\n\nconst isDensity = (value: unknown): value is Density =>\n DENSITIES.includes(value as Density);\n\n/** Storage that may be absent or refuse (a private window, blocked site data). */\nfunction read(key: string): string | null {\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction write(key: string, value: string) {\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // Nothing to remember it in; the choice still applies to this page.\n }\n}\n\n/**\n * Applies the active palette and density by writing `data-theme` and\n * `data-density` on <html>, and remembers what the user chooses. Static palette\n * VALUES live in CSS (@entifix/style presets or an app-local themes.css); this\n * flips the selector. Dynamic palettes passed via `palettes` are injected at\n * runtime under the same `data-theme` mechanism.\n *\n * ⚠️ **Only a choice is written to storage, never a default.** The provider\n * used to persist whatever theme it held from an effect, and under StrictMode\n * the effect ran before the stored value had been read back: the default\n * overwrote the stored theme, and a reload in development always came back on\n * the default. Writing in `setTheme` and `setDensity` alone means mounting,\n * however many times, can never lose what was chosen.\n */\nexport function ThemeProvider({\n children,\n themes,\n defaultTheme,\n defaultDensity,\n storageKey = 'entifix-theme',\n palettes,\n}: ThemeProviderProps) {\n const initial = defaultTheme ?? themes[0]?.id ?? '';\n const [theme, setThemeState] = useState<string>(initial);\n const densityKey = `${storageKey}-density`;\n // `undefined` until a density is stored, chosen or defaulted: the document's\n // own `data-density` stands until then.\n const [density, setDensityState] = useState<Density | undefined>(\n defaultDensity,\n );\n\n const isKnown = useCallback(\n (value: unknown): value is string =>\n typeof value === 'string' && themes.some(t => t.id === value),\n [themes],\n );\n\n // Hydrate from a previously stored preference on mount. This intentionally\n // runs in an effect (not a lazy initializer): localStorage is unavailable\n // during SSR, so the server renders `initial` and the client corrects after\n // mount — reading it in render would cause a hydration mismatch. It only\n // reads, so running twice changes nothing.\n useEffect(() => {\n const stored = read(storageKey);\n // eslint-disable-next-line react-hooks/set-state-in-effect -- one-shot SSR-safe hydration, see comment above\n if (isKnown(stored)) setThemeState(stored);\n // A stored density wins; otherwise the default, otherwise whatever the\n // document was served with, so the context never reports a density the\n // page is not in.\n const storedDensity = read(densityKey);\n const served = document.documentElement.dataset.density;\n if (isDensity(storedDensity)) setDensityState(storedDensity);\n else if (defaultDensity === undefined && isDensity(served)) {\n setDensityState(served);\n }\n }, [storageKey, densityKey, isKnown, defaultDensity]);\n\n // Inject runtime palettes as a single <style> appended to <head>. Being last\n // in the cascade, these win over any static preset defining the same tokens.\n useEffect(() => {\n if (!palettes || Object.keys(palettes).length === 0) return;\n const styleId = `${storageKey}-runtime-palettes`;\n const css = Object.entries(palettes)\n .map(([id, palette]) => {\n const decls = Object.entries(palette)\n .map(([token, value]) => `--color-${token}: ${value};`)\n .join(' ');\n return `[data-theme='${id}'] { ${decls} }`;\n })\n .join('\\n');\n\n let el = document.getElementById(styleId) as HTMLStyleElement | null;\n if (!el) {\n el = document.createElement('style');\n el.id = styleId;\n document.head.appendChild(el);\n }\n el.textContent = css;\n }, [palettes, storageKey]);\n\n // Reflect the active theme and density onto the document. Storage is not\n // touched here — see the ⚠️ above.\n useEffect(() => {\n document.documentElement.dataset.theme = theme;\n }, [theme]);\n useEffect(() => {\n if (density !== undefined)\n document.documentElement.dataset.density = density;\n }, [density]);\n\n const setTheme = useCallback(\n (next: string) => {\n setThemeState(next);\n write(storageKey, next);\n },\n [storageKey],\n );\n const setDensity = useCallback(\n (next: Density) => {\n setDensityState(next);\n write(densityKey, next);\n },\n [densityKey],\n );\n\n const value = useMemo<ThemeContextValue>(\n () => ({\n theme,\n setTheme,\n themes,\n density: density ?? 'comfortable',\n setDensity,\n }),\n [theme, setTheme, themes, density, setDensity],\n );\n\n return (\n <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>\n );\n}\n\nexport function useTheme(): ThemeContextValue {\n const ctx = useContext(ThemeContext);\n if (!ctx) throw new Error('useTheme must be used within a ThemeProvider');\n return ctx;\n}\n"],"names":["createContext","useCallback","useContext","useEffect","useMemo","useState","ThemeContext","DENSITIES","isDensity","value","includes","read","key","window","localStorage","getItem","write","setItem","ThemeProvider","children","themes","defaultTheme","defaultDensity","storageKey","palettes","initial","id","theme","setThemeState","densityKey","density","setDensityState","isKnown","some","t","stored","storedDensity","served","document","documentElement","dataset","undefined","Object","keys","length","styleId","css","entries","map","palette","decls","token","join","el","getElementById","createElement","head","appendChild","textContent","setTheme","next","setDensity","Provider","useTheme","ctx","Error"],"mappings":"AAAA;;AAEA,SACEA,aAAa,EAEbC,WAAW,EACXC,UAAU,EACVC,SAAS,EACTC,OAAO,EACPC,QAAQ,QACH,QAAQ;AASf,MAAMC,6BAAeN,cAAwC;AA0B7D,MAAMO,YAAgC;IAAC;IAAe;CAAU;AAEhE,MAAMC,YAAY,CAACC,QACjBF,UAAUG,QAAQ,CAACD;AAErB,gFAAgF,GAChF,SAASE,KAAKC,GAAW;IACvB,IAAI;QACF,OAAOC,OAAOC,YAAY,CAACC,OAAO,CAACH;IACrC,EAAE,eAAM;QACN,OAAO;IACT;AACF;AAEA,SAASI,MAAMJ,GAAW,EAAEH,KAAa;IACvC,IAAI;QACFI,OAAOC,YAAY,CAACG,OAAO,CAACL,KAAKH;IACnC,EAAE,eAAM;IACN,oEAAoE;IACtE;AACF;AAEA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASS,cAAc,EAC5BC,QAAQ,EACRC,MAAM,EACNC,YAAY,EACZC,cAAc,EACdC,aAAa,eAAe,EAC5BC,QAAQ,EACW;QACHH;QAAgBD;IAAhC,MAAMK,WAAUJ,OAAAA,uBAAAA,gBAAgBD,WAAAA,MAAM,CAAC,EAAE,qBAATA,SAAWM,EAAE,YAA7BL,OAAiC;IACjD,MAAM,CAACM,OAAOC,cAAc,GAAGvB,SAAiBoB;IAChD,MAAMI,aAAa,GAAGN,WAAW,QAAQ,CAAC;IAC1C,6EAA6E;IAC7E,wCAAwC;IACxC,MAAM,CAACO,SAASC,gBAAgB,GAAG1B,SACjCiB;IAGF,MAAMU,UAAU/B,YACd,CAACQ,QACC,OAAOA,UAAU,YAAYW,OAAOa,IAAI,CAACC,CAAAA,IAAKA,EAAER,EAAE,KAAKjB,QACzD;QAACW;KAAO;IAGV,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,2CAA2C;IAC3CjB,UAAU;QACR,MAAMgC,SAASxB,KAAKY;QACpB,6GAA6G;QAC7G,IAAIS,QAAQG,SAASP,cAAcO;QACnC,uEAAuE;QACvE,uEAAuE;QACvE,kBAAkB;QAClB,MAAMC,gBAAgBzB,KAAKkB;QAC3B,MAAMQ,SAASC,SAASC,eAAe,CAACC,OAAO,CAACV,OAAO;QACvD,IAAItB,UAAU4B,gBAAgBL,gBAAgBK;aACzC,IAAId,mBAAmBmB,aAAajC,UAAU6B,SAAS;YAC1DN,gBAAgBM;QAClB;IACF,GAAG;QAACd;QAAYM;QAAYG;QAASV;KAAe;IAEpD,6EAA6E;IAC7E,6EAA6E;IAC7EnB,UAAU;QACR,IAAI,CAACqB,YAAYkB,OAAOC,IAAI,CAACnB,UAAUoB,MAAM,KAAK,GAAG;QACrD,MAAMC,UAAU,GAAGtB,WAAW,iBAAiB,CAAC;QAChD,MAAMuB,MAAMJ,OAAOK,OAAO,CAACvB,UACxBwB,GAAG,CAAC,CAAC,CAACtB,IAAIuB,QAAQ;YACjB,MAAMC,QAAQR,OAAOK,OAAO,CAACE,SAC1BD,GAAG,CAAC,CAAC,CAACG,OAAO1C,MAAM,GAAK,CAAC,QAAQ,EAAE0C,MAAM,EAAE,EAAE1C,MAAM,CAAC,CAAC,EACrD2C,IAAI,CAAC;YACR,OAAO,CAAC,aAAa,EAAE1B,GAAG,KAAK,EAAEwB,MAAM,EAAE,CAAC;QAC5C,GACCE,IAAI,CAAC;QAER,IAAIC,KAAKf,SAASgB,cAAc,CAACT;QACjC,IAAI,CAACQ,IAAI;YACPA,KAAKf,SAASiB,aAAa,CAAC;YAC5BF,GAAG3B,EAAE,GAAGmB;YACRP,SAASkB,IAAI,CAACC,WAAW,CAACJ;QAC5B;QACAA,GAAGK,WAAW,GAAGZ;IACnB,GAAG;QAACtB;QAAUD;KAAW;IAEzB,yEAAyE;IACzE,mCAAmC;IACnCpB,UAAU;QACRmC,SAASC,eAAe,CAACC,OAAO,CAACb,KAAK,GAAGA;IAC3C,GAAG;QAACA;KAAM;IACVxB,UAAU;QACR,IAAI2B,YAAYW,WACdH,SAASC,eAAe,CAACC,OAAO,CAACV,OAAO,GAAGA;IAC/C,GAAG;QAACA;KAAQ;IAEZ,MAAM6B,WAAW1D,YACf,CAAC2D;QACChC,cAAcgC;QACd5C,MAAMO,YAAYqC;IACpB,GACA;QAACrC;KAAW;IAEd,MAAMsC,aAAa5D,YACjB,CAAC2D;QACC7B,gBAAgB6B;QAChB5C,MAAMa,YAAY+B;IACpB,GACA;QAAC/B;KAAW;IAGd,MAAMpB,QAAQL,QACZ,IAAO,CAAA;YACLuB;YACAgC;YACAvC;YACAU,OAAO,EAAEA,kBAAAA,UAAW;YACpB+B;QACF,CAAA,GACA;QAAClC;QAAOgC;QAAUvC;QAAQU;QAAS+B;KAAW;IAGhD,qBACE,KAACvD,aAAawD,QAAQ;QAACrD,OAAOA;kBAAQU;;AAE1C;AAEA,OAAO,SAAS4C;IACd,MAAMC,MAAM9D,WAAWI;IACvB,IAAI,CAAC0D,KAAK,MAAM,IAAIC,MAAM;IAC1B,OAAOD;AACT"}
@@ -15,9 +15,10 @@ export interface ThemeScriptProps {
15
15
  * in <head>: it runs ahead of any stylesheet applying and long before React
16
16
  * hydrates.
17
17
  *
18
- * A stored choice wins; otherwise `defaultTheme`, which is then stored too:
19
- * `ThemeProvider` persists whatever theme it mounts with, and without a stored
20
- * value to read it would mount with its own default instead. A page whose
18
+ * A stored choice wins; otherwise `defaultTheme`, which is then stored too, so
19
+ * the script and `ThemeProvider` agree on what the page was first painted in.
20
+ * A stored density is applied the same way; with none stored the document's
21
+ * own `data-density` stands. A page whose
21
22
  * storage cannot be read is left unthemed, as it would be without the script.
22
23
  *
23
24
  * Render it from a server component; it ships no client code.
@@ -1 +1 @@
1
- {"version":3,"file":"theme-script.d.ts","sourceRoot":"","sources":["../../src/theme/theme-script.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,UAA4B,EAC5B,KAAK,GACN,EAAE,gBAAgB,+BAgBlB"}
1
+ {"version":3,"file":"theme-script.d.ts","sourceRoot":"","sources":["../../src/theme/theme-script.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,MAAM,EAAE,SAAS,WAAW,EAAE,CAAC;IAC/B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uDAAuD;IACvD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,WAAW,CAAC,EAC1B,MAAM,EACN,YAAY,EACZ,UAA4B,EAC5B,KAAK,GACN,EAAE,gBAAgB,+BAoBlB"}
@@ -5,9 +5,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
5
5
  * in <head>: it runs ahead of any stylesheet applying and long before React
6
6
  * hydrates.
7
7
  *
8
- * A stored choice wins; otherwise `defaultTheme`, which is then stored too:
9
- * `ThemeProvider` persists whatever theme it mounts with, and without a stored
10
- * value to read it would mount with its own default instead. A page whose
8
+ * A stored choice wins; otherwise `defaultTheme`, which is then stored too, so
9
+ * the script and `ThemeProvider` agree on what the page was first painted in.
10
+ * A stored density is applied the same way; with none stored the document's
11
+ * own `data-density` stands. A page whose
11
12
  * storage cannot be read is left unthemed, as it would be without the script.
12
13
  *
13
14
  * Render it from a server component; it ships no client code.
@@ -25,6 +26,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
25
26
  window.localStorage.setItem(key, theme);
26
27
  }
27
28
  document.documentElement.dataset.theme = theme;
29
+ var density = window.localStorage.getItem(key + '-density');
30
+ if (density === 'comfortable' || density === 'compact') {
31
+ document.documentElement.dataset.density = density;
32
+ }
28
33
  } catch (error) {}
29
34
  })();`;
30
35
  return /*#__PURE__*/ _jsx("script", {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/theme-script.tsx"],"sourcesContent":["import type { ThemeOption } from './types.js';\n\nexport interface ThemeScriptProps {\n /** The same registry `ThemeProvider` is given. */\n themes: readonly ThemeOption[];\n /** Painted when nothing valid is stored; the first theme otherwise. */\n defaultTheme?: string;\n /** The key `ThemeProvider` stores the choice under. */\n storageKey?: string;\n /** For a Content-Security-Policy that allows inline scripts by nonce. */\n nonce?: string;\n}\n\n/**\n * Sets `data-theme` on <html> before the first paint, so a server-rendered or\n * statically exported page never flashes the wrong palette. Render it inline\n * in <head>: it runs ahead of any stylesheet applying and long before React\n * hydrates.\n *\n * A stored choice wins; otherwise `defaultTheme`, which is then stored too:\n * `ThemeProvider` persists whatever theme it mounts with, and without a stored\n * value to read it would mount with its own default instead. A page whose\n * storage cannot be read is left unthemed, as it would be without the script.\n *\n * Render it from a server component; it ships no client code.\n */\nexport function ThemeScript({\n themes,\n defaultTheme,\n storageKey = 'entifix-theme',\n nonce,\n}: ThemeScriptProps) {\n const ids = themes.map(theme => theme.id);\n const fallback = defaultTheme ?? ids[0] ?? '';\n const script = `(function () {\n try {\n var themes = ${JSON.stringify(ids)};\n var key = ${JSON.stringify(storageKey)};\n var theme = window.localStorage.getItem(key);\n if (themes.indexOf(theme) === -1) {\n theme = ${JSON.stringify(fallback)};\n window.localStorage.setItem(key, theme);\n }\n document.documentElement.dataset.theme = theme;\n } catch (error) {}\n})();`;\n return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: script }} />;\n}\n"],"names":["ThemeScript","themes","defaultTheme","storageKey","nonce","ids","map","theme","id","fallback","script","JSON","stringify","dangerouslySetInnerHTML","__html"],"mappings":";AAaA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASA,YAAY,EAC1BC,MAAM,EACNC,YAAY,EACZC,aAAa,eAAe,EAC5BC,KAAK,EACY;QAEAF;IADjB,MAAMG,MAAMJ,OAAOK,GAAG,CAACC,CAAAA,QAASA,MAAMC,EAAE;IACxC,MAAMC,YAAWP,OAAAA,uBAAAA,eAAgBG,GAAG,CAAC,EAAE,YAAtBH,OAA0B;IAC3C,MAAMQ,SAAS,CAAC;;iBAED,EAAEC,KAAKC,SAAS,CAACP,KAAK;cACzB,EAAEM,KAAKC,SAAS,CAACT,YAAY;;;cAG7B,EAAEQ,KAAKC,SAAS,CAACH,UAAU;;;;;KAKpC,CAAC;IACJ,qBAAO,KAACC;QAAON,OAAOA;QAAOS,yBAAyB;YAAEC,QAAQJ;QAAO;;AACzE"}
1
+ {"version":3,"sources":["../../src/theme/theme-script.tsx"],"sourcesContent":["import type { ThemeOption } from './types.js';\n\nexport interface ThemeScriptProps {\n /** The same registry `ThemeProvider` is given. */\n themes: readonly ThemeOption[];\n /** Painted when nothing valid is stored; the first theme otherwise. */\n defaultTheme?: string;\n /** The key `ThemeProvider` stores the choice under. */\n storageKey?: string;\n /** For a Content-Security-Policy that allows inline scripts by nonce. */\n nonce?: string;\n}\n\n/**\n * Sets `data-theme` on <html> before the first paint, so a server-rendered or\n * statically exported page never flashes the wrong palette. Render it inline\n * in <head>: it runs ahead of any stylesheet applying and long before React\n * hydrates.\n *\n * A stored choice wins; otherwise `defaultTheme`, which is then stored too, so\n * the script and `ThemeProvider` agree on what the page was first painted in.\n * A stored density is applied the same way; with none stored the document's\n * own `data-density` stands. A page whose\n * storage cannot be read is left unthemed, as it would be without the script.\n *\n * Render it from a server component; it ships no client code.\n */\nexport function ThemeScript({\n themes,\n defaultTheme,\n storageKey = 'entifix-theme',\n nonce,\n}: ThemeScriptProps) {\n const ids = themes.map(theme => theme.id);\n const fallback = defaultTheme ?? ids[0] ?? '';\n const script = `(function () {\n try {\n var themes = ${JSON.stringify(ids)};\n var key = ${JSON.stringify(storageKey)};\n var theme = window.localStorage.getItem(key);\n if (themes.indexOf(theme) === -1) {\n theme = ${JSON.stringify(fallback)};\n window.localStorage.setItem(key, theme);\n }\n document.documentElement.dataset.theme = theme;\n var density = window.localStorage.getItem(key + '-density');\n if (density === 'comfortable' || density === 'compact') {\n document.documentElement.dataset.density = density;\n }\n } catch (error) {}\n})();`;\n return <script nonce={nonce} dangerouslySetInnerHTML={{ __html: script }} />;\n}\n"],"names":["ThemeScript","themes","defaultTheme","storageKey","nonce","ids","map","theme","id","fallback","script","JSON","stringify","dangerouslySetInnerHTML","__html"],"mappings":";AAaA;;;;;;;;;;;;;CAaC,GACD,OAAO,SAASA,YAAY,EAC1BC,MAAM,EACNC,YAAY,EACZC,aAAa,eAAe,EAC5BC,KAAK,EACY;QAEAF;IADjB,MAAMG,MAAMJ,OAAOK,GAAG,CAACC,CAAAA,QAASA,MAAMC,EAAE;IACxC,MAAMC,YAAWP,OAAAA,uBAAAA,eAAgBG,GAAG,CAAC,EAAE,YAAtBH,OAA0B;IAC3C,MAAMQ,SAAS,CAAC;;iBAED,EAAEC,KAAKC,SAAS,CAACP,KAAK;cACzB,EAAEM,KAAKC,SAAS,CAACT,YAAY;;;cAG7B,EAAEQ,KAAKC,SAAS,CAACH,UAAU;;;;;;;;;KASpC,CAAC;IACJ,qBAAO,KAACC;QAAON,OAAOA;QAAOS,yBAAyB;YAAEC,QAAQJ;QAAO;;AACzE"}
@@ -8,15 +8,31 @@ export interface ThemeOption {
8
8
  }
9
9
  /** The semantic color tokens an app may supply values for. Matches the
10
10
  * `--color-*` contract declared in @entifix/style/tokens.css. */
11
- export type ThemeColorToken = 'surface' | 'surface-elevated' | 'content' | 'content-muted' | 'primary' | 'primary-content' | 'border' | 'accent' | 'danger' | 'danger-subtle' | 'danger-content';
11
+ export type ThemeColorToken = 'surface' | 'surface-elevated' | 'content' | 'content-muted' | 'primary' | 'primary-content' | 'border' | 'border-control' | 'selected' | 'accent' | 'focus-ring' | 'danger' | 'danger-subtle' | 'danger-content' | 'success' | 'success-subtle' | 'success-content' | 'warning' | 'warning-subtle' | 'warning-content' | 'info' | 'info-subtle' | 'info-content' | 'scrim';
12
+ /**
13
+ * The tokens a runtime palette must state. Each one is held to a contrast pair
14
+ * against the palette's own surfaces (ADR 0025), so the neutral default cannot
15
+ * stand in for it: a ring or a control boundary that clears 3:1 on grey may be
16
+ * invisible on a brand's surface. Omitting one is a type error, not a silent
17
+ * fallback.
18
+ */
19
+ export type ThemePaletteRequiredToken = 'focus-ring' | 'border-control' | 'selected';
12
20
  /** A runtime palette: token → CSS color. Injected as `[data-theme]` vars for
13
- * brands not known at build time (multi-tenant / white-label). */
14
- export type ThemePalette = Partial<Record<ThemeColorToken, string>>;
21
+ * brands not known at build time (multi-tenant / white-label). Hold one to
22
+ * the contract with `assertPaletteContract` from `@entifix/style/testing`. */
23
+ export type ThemePalette = Partial<Record<ThemeColorToken, string>> & Record<ThemePaletteRequiredToken, string>;
24
+ /** How much room the UI takes: `fixed-scale.css` reads it as `data-density`. */
25
+ export type Density = 'comfortable' | 'compact';
15
26
  export interface ThemeContextValue {
16
27
  /** Active theme id (written to `data-theme`). */
17
28
  theme: string;
29
+ /** Chooses a theme, and remembers the choice. */
18
30
  setTheme: (id: string) => void;
19
31
  /** The registry this provider was configured with. */
20
32
  themes: ThemeOption[];
33
+ /** Active density (written to `data-density`). */
34
+ density: Density;
35
+ /** Chooses a density, and remembers the choice. */
36
+ setDensity: (density: Density) => void;
21
37
  }
22
38
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED;kEACkE;AAClE,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,kBAAkB,GAClB,SAAS,GACT,eAAe,GACf,SAAS,GACT,iBAAiB,GACjB,QAAQ,GACR,QAAQ,GACR,QAAQ,GACR,eAAe,GACf,gBAAgB,CAAC;AAErB;mEACmE;AACnE,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;AAEpE,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,sDAAsD;IACtD,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/theme/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;CACf;AAED;kEACkE;AAClE,MAAM,MAAM,eAAe,GACvB,SAAS,GACT,kBAAkB,GAClB,SAAS,GACT,eAAe,GACf,SAAS,GACT,iBAAiB,GACjB,QAAQ,GACR,gBAAgB,GAChB,UAAU,GACV,QAAQ,GACR,YAAY,GACZ,QAAQ,GACR,eAAe,GACf,gBAAgB,GAChB,SAAS,GACT,gBAAgB,GAChB,iBAAiB,GACjB,SAAS,GACT,gBAAgB,GAChB,iBAAiB,GACjB,MAAM,GACN,aAAa,GACb,cAAc,GACd,OAAO,CAAC;AAEZ;;;;;;GAMG;AACH,MAAM,MAAM,yBAAyB,GACnC,YAAY,GAAG,gBAAgB,GAAG,UAAU,CAAC;AAE/C;;+EAE+E;AAC/E,MAAM,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,GACjE,MAAM,CAAC,yBAAyB,EAAE,MAAM,CAAC,CAAC;AAE5C,gFAAgF;AAChF,MAAM,MAAM,OAAO,GAAG,aAAa,GAAG,SAAS,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,iDAAiD;IACjD,KAAK,EAAE,MAAM,CAAC;IACd,iDAAiD;IACjD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,sDAAsD;IACtD,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,kDAAkD;IAClD,OAAO,EAAE,OAAO,CAAC;IACjB,mDAAmD;IACnD,UAAU,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;CACxC"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/theme/types.ts"],"sourcesContent":["/**\n * A selectable theme. `id` is written to `data-theme` on <html> and matched\n * against the CSS palette blocks; `label` is what the switcher renders.\n */\nexport interface ThemeOption {\n id: string;\n label: string;\n}\n\n/** The semantic color tokens an app may supply values for. Matches the\n * `--color-*` contract declared in @entifix/style/tokens.css. */\nexport type ThemeColorToken =\n | 'surface'\n | 'surface-elevated'\n | 'content'\n | 'content-muted'\n | 'primary'\n | 'primary-content'\n | 'border'\n | 'accent'\n | 'danger'\n | 'danger-subtle'\n | 'danger-content';\n\n/** A runtime palette: token → CSS color. Injected as `[data-theme]` vars for\n * brands not known at build time (multi-tenant / white-label). */\nexport type ThemePalette = Partial<Record<ThemeColorToken, string>>;\n\nexport interface ThemeContextValue {\n /** Active theme id (written to `data-theme`). */\n theme: string;\n setTheme: (id: string) => void;\n /** The registry this provider was configured with. */\n themes: ThemeOption[];\n}\n"],"names":[],"mappings":"AAAA;;;CAGC,GAyBD,WAMC"}
1
+ {"version":3,"sources":["../../src/theme/types.ts"],"sourcesContent":["/**\n * A selectable theme. `id` is written to `data-theme` on <html> and matched\n * against the CSS palette blocks; `label` is what the switcher renders.\n */\nexport interface ThemeOption {\n id: string;\n label: string;\n}\n\n/** The semantic color tokens an app may supply values for. Matches the\n * `--color-*` contract declared in @entifix/style/tokens.css. */\nexport type ThemeColorToken =\n | 'surface'\n | 'surface-elevated'\n | 'content'\n | 'content-muted'\n | 'primary'\n | 'primary-content'\n | 'border'\n | 'border-control'\n | 'selected'\n | 'accent'\n | 'focus-ring'\n | 'danger'\n | 'danger-subtle'\n | 'danger-content'\n | 'success'\n | 'success-subtle'\n | 'success-content'\n | 'warning'\n | 'warning-subtle'\n | 'warning-content'\n | 'info'\n | 'info-subtle'\n | 'info-content'\n | 'scrim';\n\n/**\n * The tokens a runtime palette must state. Each one is held to a contrast pair\n * against the palette's own surfaces (ADR 0025), so the neutral default cannot\n * stand in for it: a ring or a control boundary that clears 3:1 on grey may be\n * invisible on a brand's surface. Omitting one is a type error, not a silent\n * fallback.\n */\nexport type ThemePaletteRequiredToken =\n 'focus-ring' | 'border-control' | 'selected';\n\n/** A runtime palette: token → CSS color. Injected as `[data-theme]` vars for\n * brands not known at build time (multi-tenant / white-label). Hold one to\n * the contract with `assertPaletteContract` from `@entifix/style/testing`. */\nexport type ThemePalette = Partial<Record<ThemeColorToken, string>> &\n Record<ThemePaletteRequiredToken, string>;\n\n/** How much room the UI takes: `fixed-scale.css` reads it as `data-density`. */\nexport type Density = 'comfortable' | 'compact';\n\nexport interface ThemeContextValue {\n /** Active theme id (written to `data-theme`). */\n theme: string;\n /** Chooses a theme, and remembers the choice. */\n setTheme: (id: string) => void;\n /** The registry this provider was configured with. */\n themes: ThemeOption[];\n /** Active density (written to `data-density`). */\n density: Density;\n /** Chooses a density, and remembers the choice. */\n setDensity: (density: Density) => void;\n}\n"],"names":[],"mappings":"AAAA;;;CAGC,GAqDD,WAWC"}
@@ -0,0 +1,9 @@
1
+ import type { ComponentPropsWithoutRef } from 'react';
2
+ export type AlertDismissProps = Omit<ComponentPropsWithoutRef<'button'>, 'children' | 'type'>;
3
+ /**
4
+ * The × that dismisses an {@link Alert}, passed as its `action`. A client
5
+ * component of its own so `Alert` stays server-safe: dismissing is the one part
6
+ * of a message that needs a handler. Its name comes from the controls catalog.
7
+ */
8
+ export declare function AlertDismiss({ className, ...props }: AlertDismissProps): import("react").JSX.Element;
9
+ //# sourceMappingURL=alert-dismiss.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert-dismiss.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/alert/alert-dismiss.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,OAAO,CAAC;AAKtD,MAAM,MAAM,iBAAiB,GAAG,IAAI,CAClC,wBAAwB,CAAC,QAAQ,CAAC,EAClC,UAAU,GAAG,MAAM,CACpB,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAE,EAAE,iBAAiB,+BAetE"}
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+ import { _ as _extends } from "@swc/helpers/_/_extends";
3
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
4
+ import { jsx as _jsx } from "react/jsx-runtime";
5
+ import { useT } from '../../../i18n/index.js';
6
+ import { cn } from '../../utils/cn.js';
7
+ /**
8
+ * The × that dismisses an {@link Alert}, passed as its `action`. A client
9
+ * component of its own so `Alert` stays server-safe: dismissing is the one part
10
+ * of a message that needs a handler. Its name comes from the controls catalog.
11
+ */ export function AlertDismiss(_0) {
12
+ let { className } = _0, props = _object_without_properties_loose(_0, [
13
+ "className"
14
+ ]);
15
+ const t = useT();
16
+ return /*#__PURE__*/ _jsx("button", _extends({
17
+ type: "button",
18
+ "aria-label": t('alert.dismiss'),
19
+ className: cn('flex min-h-(--target-min) min-w-(--target-min) items-center justify-center rounded-md focus-ring hover:bg-surface-elevated/60', className)
20
+ }, props, {
21
+ children: /*#__PURE__*/ _jsx("span", {
22
+ "aria-hidden": "true",
23
+ children: "×"
24
+ })
25
+ }));
26
+ }
27
+
28
+ //# sourceMappingURL=alert-dismiss.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/atoms/alert/alert-dismiss.tsx"],"sourcesContent":["'use client';\n\nimport type { ComponentPropsWithoutRef } from 'react';\n\nimport { useT } from '../../../i18n/index.js';\nimport { cn } from '../../utils/cn.js';\n\nexport type AlertDismissProps = Omit<\n ComponentPropsWithoutRef<'button'>,\n 'children' | 'type'\n>;\n\n/**\n * The × that dismisses an {@link Alert}, passed as its `action`. A client\n * component of its own so `Alert` stays server-safe: dismissing is the one part\n * of a message that needs a handler. Its name comes from the controls catalog.\n */\nexport function AlertDismiss({ className, ...props }: AlertDismissProps) {\n const t = useT();\n return (\n <button\n type=\"button\"\n aria-label={t('alert.dismiss')}\n className={cn(\n 'flex min-h-(--target-min) min-w-(--target-min) items-center justify-center rounded-md focus-ring hover:bg-surface-elevated/60',\n className,\n )}\n {...props}\n >\n <span aria-hidden=\"true\">×</span>\n </button>\n );\n}\n"],"names":["useT","cn","AlertDismiss","className","props","t","button","type","aria-label","span","aria-hidden"],"mappings":"AAAA;;;;AAIA,SAASA,IAAI,QAAQ,yBAAyB;AAC9C,SAASC,EAAE,QAAQ,oBAAoB;AAOvC;;;;CAIC,GACD,OAAO,SAASC,aAAa,EAA0C;QAA1C,EAAEC,SAAS,EAA+B,OAA1BC;;;IAC3C,MAAMC,IAAIL;IACV,qBACE,KAACM;QACCC,MAAK;QACLC,cAAYH,EAAE;QACdF,WAAWF,GACT,iIACAE;OAEEC;kBAEJ,cAAA,KAACK;YAAKC,eAAY;sBAAO;;;AAG/B"}
@@ -0,0 +1,24 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ import { type Tone } from './tone.js';
3
+ export type { Tone as AlertTone } from './tone.js';
4
+ export interface AlertProps extends Omit<ComponentPropsWithoutRef<'div'>, 'title' | 'role'> {
5
+ tone: Tone;
6
+ /** A short heading in bold before the message. */
7
+ title?: ReactNode;
8
+ /** What to do about it: a link, a retry button, an {@link AlertDismiss}. */
9
+ action?: ReactNode;
10
+ }
11
+ /**
12
+ * An inline message: something went wrong, needs attention, worked, or is
13
+ * worth knowing. It stays until the page changes; nothing dismisses it unless
14
+ * the caller passes an {@link AlertDismiss} as its `action`.
15
+ *
16
+ * `danger` is announced at once (`role="alert"`, assertive); every other tone
17
+ * waits its turn (`role="status"`, polite). Never both, and never neither: an
18
+ * inline message a screen reader is not told about is one only sighted users
19
+ * receive. Server-safe — no hooks, no client boundary.
20
+ */
21
+ export declare function Alert({ tone, title, action, className, children, ...props }: AlertProps): import("react").JSX.Element;
22
+ /** The same message under the name some hosts look for. */
23
+ export declare const InlineMessage: typeof Alert;
24
+ //# sourceMappingURL=alert.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alert.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/alert/alert.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,KAAK,IAAI,EAAgB,MAAM,WAAW,CAAC;AAEpD,YAAY,EAAE,IAAI,IAAI,SAAS,EAAE,MAAM,WAAW,CAAC;AAEnD,MAAM,WAAW,UAAW,SAAQ,IAAI,CACtC,wBAAwB,CAAC,KAAK,CAAC,EAC/B,OAAO,GAAG,MAAM,CACjB;IACC,IAAI,EAAE,IAAI,CAAC;IACX,kDAAkD;IAClD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,4EAA4E;IAC5E,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAgB,KAAK,CAAC,EACpB,IAAI,EACJ,KAAK,EACL,MAAM,EACN,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,UAAU,+BAmBZ;AAED,2DAA2D;AAC3D,eAAO,MAAM,aAAa,cAAQ,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
+ import { cn } from '../../utils/cn.js';
5
+ import { TONE_SURFACE } from './tone.js';
6
+ /**
7
+ * An inline message: something went wrong, needs attention, worked, or is
8
+ * worth knowing. It stays until the page changes; nothing dismisses it unless
9
+ * the caller passes an {@link AlertDismiss} as its `action`.
10
+ *
11
+ * `danger` is announced at once (`role="alert"`, assertive); every other tone
12
+ * waits its turn (`role="status"`, polite). Never both, and never neither: an
13
+ * inline message a screen reader is not told about is one only sighted users
14
+ * receive. Server-safe — no hooks, no client boundary.
15
+ */ export function Alert(_0) {
16
+ let { tone, title, action, className, children } = _0, props = _object_without_properties_loose(_0, [
17
+ "tone",
18
+ "title",
19
+ "action",
20
+ "className",
21
+ "children"
22
+ ]);
23
+ return /*#__PURE__*/ _jsxs("div", _extends({
24
+ role: tone === 'danger' ? 'alert' : 'status',
25
+ "data-tone": tone,
26
+ className: cn('flex items-start gap-s rounded-lg border-l-4 px-s py-xs text-step-sm', TONE_SURFACE[tone], className)
27
+ }, props, {
28
+ children: [
29
+ /*#__PURE__*/ _jsxs("div", {
30
+ className: "min-w-0 flex flex-1 flex-col gap-3xs",
31
+ children: [
32
+ title !== undefined && /*#__PURE__*/ _jsx("p", {
33
+ className: "m-0 font-semibold",
34
+ children: title
35
+ }),
36
+ /*#__PURE__*/ _jsx("div", {
37
+ children: children
38
+ })
39
+ ]
40
+ }),
41
+ action !== undefined && /*#__PURE__*/ _jsx("div", {
42
+ className: "shrink-0",
43
+ children: action
44
+ })
45
+ ]
46
+ }));
47
+ }
48
+ /** The same message under the name some hosts look for. */ export const InlineMessage = Alert;
49
+
50
+ //# sourceMappingURL=alert.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/atoms/alert/alert.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\nimport { type Tone, TONE_SURFACE } from './tone.js';\n\nexport type { Tone as AlertTone } from './tone.js';\n\nexport interface AlertProps extends Omit<\n ComponentPropsWithoutRef<'div'>,\n 'title' | 'role'\n> {\n tone: Tone;\n /** A short heading in bold before the message. */\n title?: ReactNode;\n /** What to do about it: a link, a retry button, an {@link AlertDismiss}. */\n action?: ReactNode;\n}\n\n/**\n * An inline message: something went wrong, needs attention, worked, or is\n * worth knowing. It stays until the page changes; nothing dismisses it unless\n * the caller passes an {@link AlertDismiss} as its `action`.\n *\n * `danger` is announced at once (`role=\"alert\"`, assertive); every other tone\n * waits its turn (`role=\"status\"`, polite). Never both, and never neither: an\n * inline message a screen reader is not told about is one only sighted users\n * receive. Server-safe — no hooks, no client boundary.\n */\nexport function Alert({\n tone,\n title,\n action,\n className,\n children,\n ...props\n}: AlertProps) {\n return (\n <div\n role={tone === 'danger' ? 'alert' : 'status'}\n data-tone={tone}\n className={cn(\n 'flex items-start gap-s rounded-lg border-l-4 px-s py-xs text-step-sm',\n TONE_SURFACE[tone],\n className,\n )}\n {...props}\n >\n <div className=\"min-w-0 flex flex-1 flex-col gap-3xs\">\n {title !== undefined && <p className=\"m-0 font-semibold\">{title}</p>}\n <div>{children}</div>\n </div>\n {action !== undefined && <div className=\"shrink-0\">{action}</div>}\n </div>\n );\n}\n\n/** The same message under the name some hosts look for. */\nexport const InlineMessage = Alert;\n"],"names":["cn","TONE_SURFACE","Alert","tone","title","action","className","children","props","div","role","data-tone","undefined","p","InlineMessage"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AACvC,SAAoBC,YAAY,QAAQ,YAAY;AAepD;;;;;;;;;CASC,GACD,OAAO,SAASC,MAAM,EAOT;QAPS,EACpBC,IAAI,EACJC,KAAK,EACLC,MAAM,EACNC,SAAS,EACTC,QAAQ,EAEG,OADRC;;;;;;;IAEH,qBACE,MAACC;QACCC,MAAMP,SAAS,WAAW,UAAU;QACpCQ,aAAWR;QACXG,WAAWN,GACT,wEACAC,YAAY,CAACE,KAAK,EAClBG;OAEEE;;0BAEJ,MAACC;gBAAIH,WAAU;;oBACZF,UAAUQ,2BAAa,KAACC;wBAAEP,WAAU;kCAAqBF;;kCAC1D,KAACK;kCAAKF;;;;YAEPF,WAAWO,2BAAa,KAACH;gBAAIH,WAAU;0BAAYD;;;;AAG1D;AAEA,yDAAyD,GACzD,OAAO,MAAMS,gBAAgBZ,MAAM"}
@@ -0,0 +1,3 @@
1
+ export * from './alert.js';
2
+ export * from './alert-dismiss.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/alert/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC"}
@@ -0,0 +1,4 @@
1
+ export * from './alert.js';
2
+ export * from './alert-dismiss.js';
3
+
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/atoms/alert/index.ts"],"sourcesContent":["export * from './alert.js';\nexport * from './alert-dismiss.js';\n"],"names":[],"mappings":"AAAA,cAAc,aAAa;AAC3B,cAAc,qBAAqB"}
@@ -0,0 +1,8 @@
1
+ /** The four message tones of the token contract (ADR 0025). */
2
+ export type Tone = 'danger' | 'warning' | 'success' | 'info';
3
+ /**
4
+ * Each tone's text on its own `-subtle` surface: a pair the contract holds to
5
+ * 4.5:1 in every palette, so nothing here needs a contrast of its own.
6
+ */
7
+ export declare const TONE_SURFACE: Record<Tone, string>;
8
+ //# sourceMappingURL=tone.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tone.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/alert/tone.ts"],"names":[],"mappings":"AAAA,+DAA+D;AAC/D,MAAM,MAAM,IAAI,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;AAE7D;;;GAGG;AACH,eAAO,MAAM,YAAY,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,CAK7C,CAAC"}
@@ -0,0 +1,11 @@
1
+ /** The four message tones of the token contract (ADR 0025). */ /**
2
+ * Each tone's text on its own `-subtle` surface: a pair the contract holds to
3
+ * 4.5:1 in every palette, so nothing here needs a contrast of its own.
4
+ */ export const TONE_SURFACE = {
5
+ danger: 'border-danger bg-danger-subtle text-danger',
6
+ warning: 'border-warning bg-warning-subtle text-warning',
7
+ success: 'border-success bg-success-subtle text-success',
8
+ info: 'border-info bg-info-subtle text-info'
9
+ };
10
+
11
+ //# sourceMappingURL=tone.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/atoms/alert/tone.ts"],"sourcesContent":["/** The four message tones of the token contract (ADR 0025). */\nexport type Tone = 'danger' | 'warning' | 'success' | 'info';\n\n/**\n * Each tone's text on its own `-subtle` surface: a pair the contract holds to\n * 4.5:1 in every palette, so nothing here needs a contrast of its own.\n */\nexport const TONE_SURFACE: Record<Tone, string> = {\n danger: 'border-danger bg-danger-subtle text-danger',\n warning: 'border-warning bg-warning-subtle text-warning',\n success: 'border-success bg-success-subtle text-success',\n info: 'border-info bg-info-subtle text-info',\n};\n"],"names":["TONE_SURFACE","danger","warning","success","info"],"mappings":"AAAA,6DAA6D,GAG7D;;;CAGC,GACD,OAAO,MAAMA,eAAqC;IAChDC,QAAQ;IACRC,SAAS;IACTC,SAAS;IACTC,MAAM;AACR,EAAE"}
@@ -9,7 +9,7 @@ import { cva } from 'class-variance-authority';
9
9
  * constraint, so a link-shaped variant (`ButtonLink`) can wear the same clothes
10
10
  * and still render entirely on the server.
11
11
  */ export const button = cva([
12
- 'inline-flex items-center justify-center rounded-lg font-medium',
12
+ 'inline-flex min-h-(--target-min) min-w-(--target-min) items-center justify-center rounded-lg font-medium',
13
13
  'transition duration-200 ease-smooth active:scale-[0.97]',
14
14
  'focus-ring',
15
15
  'disabled:cursor-not-allowed disabled:opacity-50 disabled:shadow-none disabled:active:scale-100'
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/atoms/button/button-recipe.ts"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\n\n/**\n * The button's visual recipe, kept in its own module so it can be shared with\n * components that must **not** be client ones.\n *\n * `Button` is `'use client'` for a single reason — it renders HeadlessUI's\n * `Button` — and a server page that puts one inside every card in a grid pays a\n * client boundary per card. `cva` and the token classes have no such\n * constraint, so a link-shaped variant (`ButtonLink`) can wear the same clothes\n * and still render entirely on the server.\n */\nexport const button = cva(\n [\n 'inline-flex items-center justify-center rounded-lg font-medium',\n 'transition duration-200 ease-smooth active:scale-[0.97]',\n 'focus-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50 disabled:shadow-none disabled:active:scale-100',\n ],\n {\n variants: {\n variant: {\n primary:\n 'bg-primary text-primary-content shadow-raised hover:opacity-90 hover:shadow-card',\n secondary:\n 'border border-border bg-surface-elevated text-content shadow-edge hover:border-primary hover:shadow-raised',\n ghost: 'bg-transparent text-content hover:bg-surface-elevated',\n // For an act that destroys something — ending every session a user\n // holds, deleting a record. The `--color-danger*` tokens already\n // existed and were being reached for as raw classes at call sites; this\n // only gives them a name, so a destructive action looks the same\n // wherever it appears rather than depending on who wrote the screen.\n destructive:\n 'bg-danger text-danger-content shadow-raised hover:opacity-90 hover:shadow-card',\n },\n // Padding uses the fluid spacing tokens so buttons scale with the viewport.\n size: {\n sm: 'px-xs py-3xs text-step-0',\n md: 'px-s py-2xs text-step-0',\n lg: 'px-m py-xs text-step-1',\n },\n },\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n);\n\nexport type ButtonVariant = NonNullable<VariantProps<typeof button>['variant']>;\nexport type ButtonSize = NonNullable<VariantProps<typeof button>['size']>;\nexport type ButtonVariantProps = VariantProps<typeof button>;\n"],"names":["cva","button","variants","variant","primary","secondary","ghost","destructive","size","sm","md","lg","defaultVariants"],"mappings":"AAAA,SAASA,GAAG,QAA2B,2BAA2B;AAElE;;;;;;;;;CASC,GACD,OAAO,MAAMC,SAASD,IACpB;IACE;IACA;IACA;IACA;CACD,EACD;IACEE,UAAU;QACRC,SAAS;YACPC,SACE;YACFC,WACE;YACFC,OAAO;YACP,mEAAmE;YACnE,iEAAiE;YACjE,wEAAwE;YACxE,iEAAiE;YACjE,qEAAqE;YACrEC,aACE;QACJ;QACA,4EAA4E;QAC5EC,MAAM;YACJC,IAAI;YACJC,IAAI;YACJC,IAAI;QACN;IACF;IACAC,iBAAiB;QACfT,SAAS;QACTK,MAAM;IACR;AACF,GACA"}
1
+ {"version":3,"sources":["../../../../src/ui/atoms/button/button-recipe.ts"],"sourcesContent":["import { cva, type VariantProps } from 'class-variance-authority';\n\n/**\n * The button's visual recipe, kept in its own module so it can be shared with\n * components that must **not** be client ones.\n *\n * `Button` is `'use client'` for a single reason — it renders HeadlessUI's\n * `Button` — and a server page that puts one inside every card in a grid pays a\n * client boundary per card. `cva` and the token classes have no such\n * constraint, so a link-shaped variant (`ButtonLink`) can wear the same clothes\n * and still render entirely on the server.\n */\nexport const button = cva(\n [\n 'inline-flex min-h-(--target-min) min-w-(--target-min) items-center justify-center rounded-lg font-medium',\n 'transition duration-200 ease-smooth active:scale-[0.97]',\n 'focus-ring',\n 'disabled:cursor-not-allowed disabled:opacity-50 disabled:shadow-none disabled:active:scale-100',\n ],\n {\n variants: {\n variant: {\n primary:\n 'bg-primary text-primary-content shadow-raised hover:opacity-90 hover:shadow-card',\n secondary:\n 'border border-border bg-surface-elevated text-content shadow-edge hover:border-primary hover:shadow-raised',\n ghost: 'bg-transparent text-content hover:bg-surface-elevated',\n // For an act that destroys something — ending every session a user\n // holds, deleting a record. The `--color-danger*` tokens already\n // existed and were being reached for as raw classes at call sites; this\n // only gives them a name, so a destructive action looks the same\n // wherever it appears rather than depending on who wrote the screen.\n destructive:\n 'bg-danger text-danger-content shadow-raised hover:opacity-90 hover:shadow-card',\n },\n // Padding uses the fluid spacing tokens so buttons scale with the viewport.\n size: {\n sm: 'px-xs py-3xs text-step-0',\n md: 'px-s py-2xs text-step-0',\n lg: 'px-m py-xs text-step-1',\n },\n },\n defaultVariants: {\n variant: 'primary',\n size: 'md',\n },\n },\n);\n\nexport type ButtonVariant = NonNullable<VariantProps<typeof button>['variant']>;\nexport type ButtonSize = NonNullable<VariantProps<typeof button>['size']>;\nexport type ButtonVariantProps = VariantProps<typeof button>;\n"],"names":["cva","button","variants","variant","primary","secondary","ghost","destructive","size","sm","md","lg","defaultVariants"],"mappings":"AAAA,SAASA,GAAG,QAA2B,2BAA2B;AAElE;;;;;;;;;CASC,GACD,OAAO,MAAMC,SAASD,IACpB;IACE;IACA;IACA;IACA;CACD,EACD;IACEE,UAAU;QACRC,SAAS;YACPC,SACE;YACFC,WACE;YACFC,OAAO;YACP,mEAAmE;YACnE,iEAAiE;YACjE,wEAAwE;YACxE,iEAAiE;YACjE,qEAAqE;YACrEC,aACE;QACJ;QACA,4EAA4E;QAC5EC,MAAM;YACJC,IAAI;YACJC,IAAI;YACJC,IAAI;QACN;IACF;IACAC,iBAAiB;QACfT,SAAS;QACTK,MAAM;IACR;AACF,GACA"}
@@ -1,4 +1,5 @@
1
1
  export * from './button.js';
2
2
  export * from './button-link.js';
3
3
  export * from './button-recipe.js';
4
+ export * from './submit-button.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/button/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/button/index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,kBAAkB,CAAC;AACjC,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
@@ -1,5 +1,6 @@
1
1
  export * from './button.js';
2
2
  export * from './button-link.js';
3
3
  export * from './button-recipe.js';
4
+ export * from './submit-button.js';
4
5
 
5
6
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/atoms/button/index.ts"],"sourcesContent":["export * from './button.js';\nexport * from './button-link.js';\nexport * from './button-recipe.js';\n"],"names":[],"mappings":"AAAA,cAAc,cAAc;AAC5B,cAAc,mBAAmB;AACjC,cAAc,qBAAqB"}
1
+ {"version":3,"sources":["../../../../src/ui/atoms/button/index.ts"],"sourcesContent":["export * from './button.js';\nexport * from './button-link.js';\nexport * from './button-recipe.js';\nexport * from './submit-button.js';\n"],"names":[],"mappings":"AAAA,cAAc,cAAc;AAC5B,cAAc,mBAAmB;AACjC,cAAc,qBAAqB;AACnC,cAAc,qBAAqB"}
@@ -0,0 +1,13 @@
1
+ import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
+ import { type ButtonVariantProps } from './button-recipe.js';
3
+ export interface SubmitButtonProps extends Omit<ComponentPropsWithoutRef<'button'>, 'type' | 'onClick'>, ButtonVariantProps {
4
+ children: ReactNode;
5
+ }
6
+ /**
7
+ * A form's submit button wearing {@link Button}'s recipe — and a **server**
8
+ * component, like `ButtonLink`. A server-rendered form posts with scripting
9
+ * off, so its button needs no client code and no handler; it takes none, on
10
+ * purpose, since accepting one would quietly demand a client parent.
11
+ */
12
+ export declare function SubmitButton({ variant, size, className, children, ...props }: SubmitButtonProps): import("react").JSX.Element;
13
+ //# sourceMappingURL=submit-button.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"submit-button.d.ts","sourceRoot":"","sources":["../../../../src/ui/atoms/button/submit-button.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAU,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAErE,MAAM,WAAW,iBACf,SACE,IAAI,CAAC,wBAAwB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC,EAC5D,kBAAkB;IACpB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAC3B,OAAO,EACP,IAAI,EACJ,SAAS,EACT,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,iBAAiB,+BAUnB"}
@@ -0,0 +1,29 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
+ import { jsx as _jsx } from "react/jsx-runtime";
4
+ import { cn } from '../../utils/cn.js';
5
+ import { button } from './button-recipe.js';
6
+ /**
7
+ * A form's submit button wearing {@link Button}'s recipe — and a **server**
8
+ * component, like `ButtonLink`. A server-rendered form posts with scripting
9
+ * off, so its button needs no client code and no handler; it takes none, on
10
+ * purpose, since accepting one would quietly demand a client parent.
11
+ */ export function SubmitButton(_0) {
12
+ let { variant, size, className, children } = _0, props = _object_without_properties_loose(_0, [
13
+ "variant",
14
+ "size",
15
+ "className",
16
+ "children"
17
+ ]);
18
+ return /*#__PURE__*/ _jsx("button", _extends({
19
+ type: "submit",
20
+ className: cn(button({
21
+ variant,
22
+ size
23
+ }), className)
24
+ }, props, {
25
+ children: children
26
+ }));
27
+ }
28
+
29
+ //# sourceMappingURL=submit-button.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/atoms/button/submit-button.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\nimport { button, type ButtonVariantProps } from './button-recipe.js';\n\nexport interface SubmitButtonProps\n extends\n Omit<ComponentPropsWithoutRef<'button'>, 'type' | 'onClick'>,\n ButtonVariantProps {\n children: ReactNode;\n}\n\n/**\n * A form's submit button wearing {@link Button}'s recipe — and a **server**\n * component, like `ButtonLink`. A server-rendered form posts with scripting\n * off, so its button needs no client code and no handler; it takes none, on\n * purpose, since accepting one would quietly demand a client parent.\n */\nexport function SubmitButton({\n variant,\n size,\n className,\n children,\n ...props\n}: SubmitButtonProps) {\n return (\n <button\n type=\"submit\"\n className={cn(button({ variant, size }), className)}\n {...props}\n >\n {children}\n </button>\n );\n}\n"],"names":["cn","button","SubmitButton","variant","size","className","children","props","type"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AACvC,SAASC,MAAM,QAAiC,qBAAqB;AASrE;;;;;CAKC,GACD,OAAO,SAASC,aAAa,EAMT;QANS,EAC3BC,OAAO,EACPC,IAAI,EACJC,SAAS,EACTC,QAAQ,EAEU,OADfC;;;;;;IAEH,qBACE,KAACN;QACCO,MAAK;QACLH,WAAWL,GAAGC,OAAO;YAAEE;YAASC;QAAK,IAAIC;OACrCE;kBAEHD;;AAGP"}