@togo-framework/ui 0.1.9 → 0.1.11

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/dist/shadcn.js CHANGED
@@ -29,7 +29,6 @@ import {
29
29
  BreadcrumbList,
30
30
  BreadcrumbPage,
31
31
  BreadcrumbSeparator,
32
- Button,
33
32
  Calendar,
34
33
  Card,
35
34
  CardContent,
@@ -98,21 +97,6 @@ import {
98
97
  DrawerPortal,
99
98
  DrawerTitle,
100
99
  DrawerTrigger,
101
- DropdownMenu,
102
- DropdownMenuCheckboxItem,
103
- DropdownMenuContent,
104
- DropdownMenuGroup,
105
- DropdownMenuItem,
106
- DropdownMenuLabel,
107
- DropdownMenuPortal,
108
- DropdownMenuRadioGroup,
109
- DropdownMenuRadioItem,
110
- DropdownMenuSeparator,
111
- DropdownMenuShortcut,
112
- DropdownMenuSub,
113
- DropdownMenuSubContent,
114
- DropdownMenuSubTrigger,
115
- DropdownMenuTrigger,
116
100
  Form,
117
101
  FormControl,
118
102
  FormDescription,
@@ -242,14 +226,32 @@ import {
242
226
  TooltipProvider,
243
227
  TooltipTrigger,
244
228
  badgeVariants,
245
- buttonVariants,
246
229
  navigationMenuTriggerStyle,
247
230
  toast,
248
231
  toggleVariants,
249
232
  useFormField,
250
233
  useOptionalSidebar,
251
234
  useSidebar
252
- } from "./chunk-XXP2ZYPY.js";
235
+ } from "./chunk-YYV7ECKZ.js";
236
+ import {
237
+ Button,
238
+ DropdownMenu,
239
+ DropdownMenuCheckboxItem,
240
+ DropdownMenuContent,
241
+ DropdownMenuGroup,
242
+ DropdownMenuItem,
243
+ DropdownMenuLabel,
244
+ DropdownMenuPortal,
245
+ DropdownMenuRadioGroup,
246
+ DropdownMenuRadioItem,
247
+ DropdownMenuSeparator,
248
+ DropdownMenuShortcut,
249
+ DropdownMenuSub,
250
+ DropdownMenuSubContent,
251
+ DropdownMenuSubTrigger,
252
+ DropdownMenuTrigger,
253
+ buttonVariants
254
+ } from "./chunk-NRF3KNQX.js";
253
255
  export {
254
256
  Accordion,
255
257
  AccordionContent,
@@ -133,6 +133,8 @@ interface ThemeDef {
133
133
  label: string;
134
134
  /** Whether this theme toggles the `.dark` class (keeps `dark:` utilities in sync). */
135
135
  base: ThemeBase;
136
+ /** Optional accent hex for swatch display (not used by ThemeProvider itself). */
137
+ accent?: string;
136
138
  }
137
139
  declare const themes: ThemeDef[];
138
140
  declare function themeBase(id: string): ThemeBase;
@@ -181,4 +183,26 @@ declare namespace ThemeProvider {
181
183
  }
182
184
  declare function useTheme(): ThemeContextValue;
183
185
 
184
- export { type BrandContextValue, type BrandTokens, BrandingProvider, type BrandingProviderProps, type Dir, SENTRA_BRAND, STORAGE_KEY, type ThemeBase, type ThemeContextValue, type ThemeDef, type ThemeOverrides, ThemeProvider, type ThemeProviderProps, type TogoTheme, applyBrand, hexToHSL, isHSL, isValidColor, nudgeL, themeBase, themeInitScript, themes, toHSLSafe, useBrand, useTheme };
186
+ /**
187
+ * ThemePicker — a compact dropdown that lists every registered theme with a colour
188
+ * swatch and checks the currently active one. Wires directly to useTheme(); no
189
+ * external props needed unless you want to pass a custom theme list.
190
+ *
191
+ * Design: semantic tokens only, RTL-safe, accessible (role="menuitemradio").
192
+ */
193
+
194
+ interface ThemePickerProps {
195
+ /** Override the list of themes shown (defaults to the kit's built-in presets). */
196
+ themes?: ThemeDef[];
197
+ /** Button size */
198
+ size?: "sm" | "default";
199
+ className?: string;
200
+ /** Label shown in the dropdown header (defaults to no header). */
201
+ label?: string;
202
+ }
203
+ declare function ThemePicker({ themes, size, className, label }: ThemePickerProps): React.JSX.Element;
204
+ declare namespace ThemePicker {
205
+ var displayName: string;
206
+ }
207
+
208
+ export { type BrandContextValue, type BrandTokens, BrandingProvider, type BrandingProviderProps, type Dir, SENTRA_BRAND, STORAGE_KEY, type ThemeBase, type ThemeContextValue, type ThemeDef, type ThemeOverrides, ThemePicker, type ThemePickerProps, ThemeProvider, type ThemeProviderProps, type TogoTheme, applyBrand, hexToHSL, isHSL, isValidColor, nudgeL, themeBase, themeInitScript, themes, toHSLSafe, useBrand, useTheme };
@@ -3,6 +3,7 @@ import {
3
3
  BrandingProvider,
4
4
  SENTRA_BRAND,
5
5
  STORAGE_KEY,
6
+ ThemePicker,
6
7
  ThemeProvider,
7
8
  applyBrand,
8
9
  hexToHSL,
@@ -15,11 +16,13 @@ import {
15
16
  toHSLSafe,
16
17
  useBrand,
17
18
  useTheme
18
- } from "../chunk-KD4MPGYQ.js";
19
+ } from "../chunk-7B6OKTGY.js";
20
+ import "../chunk-NRF3KNQX.js";
19
21
  export {
20
22
  BrandingProvider,
21
23
  SENTRA_BRAND,
22
24
  STORAGE_KEY,
25
+ ThemePicker,
23
26
  ThemeProvider,
24
27
  applyBrand,
25
28
  hexToHSL,
@@ -43,3 +43,105 @@
43
43
  --togo-color-warn: var(--togo-warn-500);
44
44
  --togo-color-danger: var(--togo-danger-500);
45
45
  }
46
+
47
+ /* ── PURPLE (dark accent) ── */
48
+ [data-theme="purple"] {
49
+ --togo-color-bg: #16101E;
50
+ --togo-color-surface: #1D1528;
51
+ --togo-color-surface-alt: #241B30;
52
+ --togo-color-border: #342549;
53
+ --togo-color-text: #EDE9F7;
54
+ --togo-color-text-muted: #9D8FC4;
55
+ --togo-color-accent: #9B6DF5;
56
+ --togo-color-accent-strong: #B38EFF;
57
+ --togo-color-on-accent: #FFFFFF;
58
+ --togo-color-focus-ring: #9B6DF5;
59
+ --togo-color-success: var(--togo-success-500);
60
+ --togo-color-warn: var(--togo-warn-500);
61
+ --togo-color-danger: var(--togo-danger-500);
62
+ }
63
+
64
+ /* ── ROSE (dark accent) ── */
65
+ [data-theme="rose"] {
66
+ --togo-color-bg: #1E1015;
67
+ --togo-color-surface: #271620;
68
+ --togo-color-surface-alt: #2F1C28;
69
+ --togo-color-border: #4A2236;
70
+ --togo-color-text: #F7E9EC;
71
+ --togo-color-text-muted: #C48FA0;
72
+ --togo-color-accent: #F5427B;
73
+ --togo-color-accent-strong: #FF6A9B;
74
+ --togo-color-on-accent: #FFFFFF;
75
+ --togo-color-focus-ring: #F5427B;
76
+ --togo-color-success: var(--togo-success-500);
77
+ --togo-color-warn: var(--togo-warn-500);
78
+ --togo-color-danger: #E05565;
79
+ }
80
+
81
+ /* ── EMERALD (dark accent) ── */
82
+ [data-theme="emerald"] {
83
+ --togo-color-bg: #0E1A17;
84
+ --togo-color-surface: #142119;
85
+ --togo-color-surface-alt: #1A2B23;
86
+ --togo-color-border: #243D30;
87
+ --togo-color-text: #E8F4F0;
88
+ --togo-color-text-muted: #8AB5A2;
89
+ --togo-color-accent: #10B981;
90
+ --togo-color-accent-strong: #34D399;
91
+ --togo-color-on-accent: #0E1A17;
92
+ --togo-color-focus-ring: #10B981;
93
+ --togo-color-success: #10B981;
94
+ --togo-color-warn: var(--togo-warn-500);
95
+ --togo-color-danger: var(--togo-danger-500);
96
+ }
97
+
98
+ /* ── PURPLE LIGHT ── */
99
+ [data-theme="purple-light"] {
100
+ --togo-color-bg: #FAF9FF;
101
+ --togo-color-surface: #FFFFFF;
102
+ --togo-color-surface-alt: #F3F0FE;
103
+ --togo-color-border: #DDD6FE;
104
+ --togo-color-text: #2E1A5C;
105
+ --togo-color-text-muted: #6D5FA0;
106
+ --togo-color-accent: #7C3AED;
107
+ --togo-color-accent-strong: #6D28D9;
108
+ --togo-color-on-accent: #FFFFFF;
109
+ --togo-color-focus-ring: #7C3AED;
110
+ --togo-color-success: var(--togo-success-500);
111
+ --togo-color-warn: var(--togo-warn-500);
112
+ --togo-color-danger: var(--togo-danger-500);
113
+ }
114
+
115
+ /* ── ROSE LIGHT ── */
116
+ [data-theme="rose-light"] {
117
+ --togo-color-bg: #FFF8F9;
118
+ --togo-color-surface: #FFFFFF;
119
+ --togo-color-surface-alt: #FEF0F3;
120
+ --togo-color-border: #FECDD3;
121
+ --togo-color-text: #4C0519;
122
+ --togo-color-text-muted: #9F4060;
123
+ --togo-color-accent: #E11D48;
124
+ --togo-color-accent-strong: #BE123C;
125
+ --togo-color-on-accent: #FFFFFF;
126
+ --togo-color-focus-ring: #E11D48;
127
+ --togo-color-success: var(--togo-success-500);
128
+ --togo-color-warn: var(--togo-warn-500);
129
+ --togo-color-danger: #E11D48;
130
+ }
131
+
132
+ /* ── EMERALD LIGHT ── */
133
+ [data-theme="emerald-light"] {
134
+ --togo-color-bg: #F0FDF4;
135
+ --togo-color-surface: #FFFFFF;
136
+ --togo-color-surface-alt: #DCFCE7;
137
+ --togo-color-border: #BBF7D0;
138
+ --togo-color-text: #052E16;
139
+ --togo-color-text-muted: #166534;
140
+ --togo-color-accent: #059669;
141
+ --togo-color-accent-strong: #047857;
142
+ --togo-color-on-accent: #FFFFFF;
143
+ --togo-color-focus-ring: #059669;
144
+ --togo-color-success: #059669;
145
+ --togo-color-warn: var(--togo-warn-500);
146
+ --togo-color-danger: var(--togo-danger-500);
147
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@togo-framework/ui",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "togo UI kit — the prism-style admin + auth component library (layout, data, charts, primitives). Framework-agnostic, RTL-ready, dark-first.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/theme/brand.ts","../src/theme/BrandingProvider.tsx","../src/theme/ThemeProvider.tsx","../src/theme/themes.ts"],"sourcesContent":["'use client'\n\n/**\n * brand.ts — Sentra dynamic theming primitives.\n *\n * Pure utilities: no React, no app context, no bridge dependency.\n * A product imports these and wires its own org-settings fetch,\n * then calls applyBrand() to override the CSS var baseline at runtime.\n *\n * CSS variables written (same set as app/src/components/BrandingProvider.tsx):\n * --primary HSL \"H S% L%\" — buttons, rings, active states\n * --brand-primary HSL \"H S% L%\" — alias kept for Situation Room glow\n * --brand-primary-glow HSL with +7 lightness — glow variant\n * --ring same as --primary\n * --sidebar-primary same as --primary\n * --sidebar-ring same as --primary\n * --ai-glow same as --primary\n * --accent-muted accent HSL with -13 lightness\n * --logo-url CSS url(\"…\") string for background-image consumers\n */\n\n// ── Colour helpers ─────────────────────────────────────────────────────────\n\n/** Returns true for the HSL string form \"H S% L%\". */\nexport function isHSL(value: string): boolean {\n return /^\\d+(\\.\\d+)?\\s+\\d+(\\.\\d+)?%\\s+\\d+(\\.\\d+)?%$/.test(value.trim());\n}\n\n/** Returns true for \"#RRGGBB\" or \"#RGB\". */\nexport function isValidColor(value: string): boolean {\n const t = value.trim();\n return (\n isHSL(t) || /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(t)\n );\n}\n\n/**\n * Converts \"#RRGGBB\" or \"#RGB\" to the HSL string form \"H S% L%\".\n * Returns an empty string when the input is not a valid hex color.\n */\nexport function hexToHSL(hex: string): string {\n let h = hex.replace(\"#\", \"\").trim();\n if (h.length === 3) h = h.split(\"\").map((c) => c + c).join(\"\");\n if (h.length !== 6) return \"\";\n\n const r = parseInt(h.substring(0, 2), 16) / 255;\n const g = parseInt(h.substring(2, 4), 16) / 255;\n const b = parseInt(h.substring(4, 6), 16) / 255;\n\n const max = Math.max(r, g, b);\n const min = Math.min(r, g, b);\n const l = (max + min) / 2;\n\n if (max === min) return `0 0% ${Math.round(l * 100)}%`;\n\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let hue = 0;\n if (max === r) hue = ((g - b) / d + (g < b ? 6 : 0)) / 6;\n else if (max === g) hue = ((b - r) / d + 2) / 6;\n else hue = ((r - g) / d + 4) / 6;\n\n return `${Math.round(hue * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;\n}\n\n/**\n * Normalises a hex or HSL value to the \"H S% L%\" form.\n * Returns null when the value is unrecognised — callers should skip setting\n * the var rather than writing a broken value.\n */\nexport function toHSLSafe(color: string): string | null {\n if (!color) return null;\n const t = color.trim();\n if (isHSL(t)) return t;\n const result = hexToHSL(t);\n return result !== \"\" ? result : null;\n}\n\n/**\n * Nudges the lightness of an \"H S% L%\" string by `delta` percentage points,\n * clamped to [0, 100].\n */\nexport function nudgeL(hsl: string, delta: number): string {\n const parts = hsl.trim().split(/\\s+/);\n if (parts.length < 3) return hsl;\n const l = parseFloat(parts[2] ?? \"0\");\n const newL = Math.max(0, Math.min(100, l + delta));\n return `${parts[0]} ${parts[1]} ${Math.round(newL)}%`;\n}\n\n// ── Sentra platform defaults ───────────────────────────────────────────────\n\n/**\n * SENTRA_BRAND — the platform's own brand tokens.\n *\n * These are the values used when no tenant branding is configured.\n * BrandingProvider applies them on first render so the first paint is always\n * branded; a tenant override writes over them once org-settings resolves.\n */\nexport const SENTRA_BRAND = {\n /** Brand primary: Sentra crimson (--primary 345 75% 33%). */\n primaryHex: \"#931535\",\n /** Brand accent: Tailwind violet-500. */\n accentHex: \"#daab4e\",\n /** Logo path — products serve this from their own /public directory. */\n logoUrl: \"/sentra-logo-full.png\",\n} as const;\n\n// ── Pre-computed HSL values for the defaults ──────────────────────────────\n\nconst SENTRA_PRIMARY_HSL = toHSLSafe(SENTRA_BRAND.primaryHex)!; // \"160 84% 39%\"\nconst SENTRA_ACCENT_HSL = toHSLSafe(SENTRA_BRAND.accentHex)!; // \"263 70% 66%\"\n\n// ── Brand application ──────────────────────────────────────────────────────\n\nexport interface BrandTokens {\n /** Hex string (\"#RRGGBB\") or HSL string (\"H S% L%\") for the primary color. */\n primaryHex?: string;\n /** Hex string (\"#RRGGBB\") or HSL string (\"H S% L%\") for the accent color. */\n accentHex?: string;\n /** Absolute URL or relative path to the org logo image. */\n logoUrl?: string;\n}\n\n/**\n * applyBrand — writes brand CSS custom properties directly onto `root`\n * (typically `document.documentElement`).\n *\n * Call this inside a useEffect whenever org-settings data changes.\n * When a field is absent or invalid, the corresponding Sentra default fills\n * the gap — the product always has a styled baseline.\n *\n * @param root The element to set properties on (usually document.documentElement).\n * @param tokens The brand tokens from org-settings; all fields are optional.\n */\nexport function applyBrand(root: HTMLElement, tokens: BrandTokens = {}): void {\n // ── Primary ──────────────────────────────────────────────────────────────\n const primaryHSL =\n (tokens.primaryHex ? toHSLSafe(tokens.primaryHex) : null) ?? SENTRA_PRIMARY_HSL;\n\n root.style.setProperty(\"--primary\", primaryHSL);\n root.style.setProperty(\"--brand-primary\", primaryHSL);\n root.style.setProperty(\"--brand-primary-glow\", nudgeL(primaryHSL, 7));\n root.style.setProperty(\"--ring\", primaryHSL);\n root.style.setProperty(\"--sidebar-primary\", primaryHSL);\n root.style.setProperty(\"--sidebar-ring\", primaryHSL);\n root.style.setProperty(\"--ai-glow\", primaryHSL);\n\n // ── Accent ───────────────────────────────────────────────────────────────\n const accentHSL =\n (tokens.accentHex ? toHSLSafe(tokens.accentHex) : null) ?? SENTRA_ACCENT_HSL;\n\n root.style.setProperty(\"--accent-muted\", nudgeL(accentHSL, -13));\n\n // ── Logo ─────────────────────────────────────────────────────────────────\n // Only set --logo-url when the tenant actually has a logo. Do NOT fall back to\n // the Sentra default PNG (operator 2026-06-05: empty DB logo was rendering\n // /sentra-logo-full.png everywhere instead of the platform icon). When there's\n // no logo, set --logo-url: none so background-image consumers show nothing and\n // icon-based marks (AuthCard crest, AdminLayout brand mark) take over.\n const rawLogo = tokens.logoUrl;\n if (rawLogo && rawLogo.trim() !== \"\") {\n root.style.setProperty(\"--logo-url\", `url(\"${rawLogo}\")`);\n } else {\n root.style.setProperty(\"--logo-url\", \"none\");\n }\n}","'use client'\n\n/**\n * BrandingProvider.tsx — React wrapper for dynamic Sentra branding.\n *\n * Design contract (prop-driven, no app context):\n * A product fetches its own org-settings and passes the values as props.\n * BrandingProvider calls applyBrand() in a useEffect whenever the values\n * change, writing the CSS custom properties onto document.documentElement.\n *\n * The tokens.css vars (--primary, --brand-primary, etc.) are the static\n * fallback baseline. applyBrand() overrides them at runtime with the org's\n * palette, making the UI theme dynamic per tenant.\n *\n * Placement: wrap the root of your product's component tree AFTER your\n * ThemeProvider (light/dark), so the dark-class is already on <html> when\n * the effect fires.\n *\n * Usage:\n * import { BrandingProvider } from '@prism/ui'\n *\n * // In your product's root layout / providers component:\n * const { data: org } = useOrgSettings() // your own fetch\n *\n * return (\n * <BrandingProvider\n * primaryHex={org?.primaryColor}\n * accentHex={org?.accentColor}\n * logoUrl={org?.logoUrl}\n * >\n * {children}\n * </BrandingProvider>\n * )\n *\n * SSR note: applyBrand() only runs inside useEffect (client-side). On the\n * server the CSS var defaults from tokens.css are used — no flash for the\n * initial paint because the default palette is set in the stylesheet.\n */\nimport { createContext, useContext, useEffect, type ReactNode } from \"react\";\nimport { applyBrand, SENTRA_BRAND, type BrandTokens } from \"./brand\";\n\n// ── BrandContext (optional — lets deeply-nested components read the current tokens) ──\n\ninterface BrandContextValue extends Omit<BrandTokens, 'logoUrl'> {\n /** The resolved primary hex/HSL that is currently active (may be the Sentra default). */\n primaryHex: string;\n /** The resolved accent hex/HSL that is currently active (may be the Sentra default). */\n accentHex: string;\n /**\n * The RAW logo URL the tenant set, or null when none is configured.\n * Intentionally NOT defaulted to the Sentra logo: consumers (AdminLayout,\n * AuthCard) use null to decide \"no logo → render the icon mark instead\".\n * (operator 2026-06-05: everything was showing /sentra-logo-full.png — the\n * default — instead of the platform icon when no DB logo exists.)\n * The `--logo-url` CSS var (set by applyBrand) still falls back to the Sentra\n * default for pure background-image consumers; this context value does not.\n */\n logoUrl: string | null;\n /** Lucide icon name (e.g. 'ShieldCheck') for the platform mark. Null if unset. */\n iconName: string | null;\n /** Resolved product/platform name (for the sidebar title). Empty if unset. */\n productName: string;\n}\n\nconst BrandContext = createContext<BrandContextValue>({\n primaryHex: SENTRA_BRAND.primaryHex,\n accentHex: SENTRA_BRAND.accentHex,\n logoUrl: null,\n iconName: null,\n productName: \"\",\n});\n\n/**\n * useBrand — reads the currently-active brand tokens from context.\n *\n * Returns the Sentra defaults when no BrandingProvider is in the tree.\n * Only use this when a component genuinely needs to read the color values\n * (e.g. to pass them to a canvas API). For standard CSS theming the CSS\n * vars (hsl(var(--primary)) etc.) are always preferred.\n */\nexport function useBrand(): BrandContextValue {\n return useContext(BrandContext);\n}\n\n// ── BrandingProvider ───────────────────────────────────────────────────────\n\ninterface BrandingProviderProps extends BrandTokens {\n /** Lucide icon name for the platform mark (from Fort branding.icon). */\n iconName?: string | null;\n /** Product/platform display name (from Fort branding.product_name_*). */\n productName?: string;\n children: ReactNode;\n}\n\n/**\n * BrandingProvider — hot-applies org branding CSS vars and exposes the\n * active tokens via BrandContext.\n *\n * Renders no additional DOM nodes — it is a pure side-effect + context provider.\n *\n * Props (all optional — Sentra defaults fill any missing value):\n * primaryHex Hex \"#RRGGBB\" or HSL \"H S% L%\" for the brand primary color.\n * accentHex Hex \"#RRGGBB\" or HSL \"H S% L%\" for the brand accent color.\n * logoUrl Absolute URL or relative path to the org logo.\n */\nconst BrandingProvider = ({\n primaryHex,\n accentHex,\n logoUrl,\n iconName,\n productName,\n children,\n}: BrandingProviderProps) => {\n // Apply CSS vars on mount and whenever the brand tokens change.\n useEffect(() => {\n if (typeof document === \"undefined\") return; // SSR guard\n applyBrand(document.documentElement, { primaryHex, accentHex, logoUrl });\n }, [primaryHex, accentHex, logoUrl]);\n\n const contextValue: BrandContextValue = {\n primaryHex: primaryHex ?? SENTRA_BRAND.primaryHex,\n accentHex: accentHex ?? SENTRA_BRAND.accentHex,\n // RAW logo: null when the tenant has none (empty/whitespace counts as none),\n // so consumers fall back to the icon mark instead of the Sentra default PNG.\n logoUrl: logoUrl && logoUrl.trim() !== '' ? logoUrl : null,\n iconName: iconName ?? null,\n productName: productName ?? \"\",\n };\n\n return (\n <BrandContext.Provider value={contextValue}>\n {children}\n </BrandContext.Provider>\n );\n};\n\nBrandingProvider.displayName = \"BrandingProvider\";\n\nexport { BrandingProvider };\nexport type { BrandingProviderProps, BrandContextValue };","\"use client\";\n\nimport * as React from \"react\";\nimport { themes as defaultThemes, themeBase, STORAGE_KEY, type ThemeDef } from \"./themes\";\n\nexport type TogoTheme = \"dark\" | \"light\" | (string & {});\nexport type Dir = \"ltr\" | \"rtl\";\n\n/** Per-app brand overrides: any `--togo-*` (or bridged) custom property → value. */\nexport type ThemeOverrides = Record<string, string>;\n\nexport interface ThemeContextValue {\n theme: TogoTheme;\n setTheme: (t: TogoTheme) => void;\n themes: ThemeDef[];\n dir: Dir;\n setDir: (d: Dir) => void;\n}\n\nconst ThemeContext = React.createContext<ThemeContextValue | null>(null);\n\nexport interface ThemeProviderProps {\n theme?: TogoTheme;\n /** Per-app brand overrides applied as inline custom properties on the scope element. */\n overrides?: ThemeOverrides;\n dir?: Dir;\n /** \"html\" (default) themes <html>; \"self\" themes a wrapper div (so themes can coexist on one page). */\n scope?: \"html\" | \"self\";\n themes?: ThemeDef[];\n /** Persist theme/dir choice to localStorage and read it on first mount. */\n persist?: boolean;\n className?: string;\n children: React.ReactNode;\n}\n\nfunction applyToElement(el: HTMLElement, theme: string, dir: Dir, overrides?: ThemeOverrides) {\n el.setAttribute(\"data-theme\", theme);\n el.classList.toggle(\"dark\", themeBase(theme) === \"dark\");\n el.setAttribute(\"dir\", dir);\n if (overrides) for (const [k, v] of Object.entries(overrides)) el.style.setProperty(k, v);\n}\n\n/**\n * ThemeProvider — runtime theming. Sets `data-theme` + toggles `.dark` + `dir` on its\n * scope (the <html> element, or a wrapper when scope=\"self\"), applies per-app `overrides`\n * as inline custom properties, and exposes `useTheme()`. SSR-safe (no DOM access at module\n * top level; all mutations run in effects). For no-flash, also inline `themeInitScript`.\n */\nexport function ThemeProvider({\n theme: themeProp,\n overrides,\n dir: dirProp,\n scope = \"html\",\n themes = defaultThemes,\n persist = true,\n className,\n children,\n}: ThemeProviderProps) {\n const [theme, setThemeState] = React.useState<TogoTheme>(themeProp ?? \"dark\");\n const [dir, setDirState] = React.useState<Dir>(dirProp ?? \"ltr\");\n const selfRef = React.useRef<HTMLDivElement>(null);\n\n // First mount: hydrate from localStorage / prefers-color-scheme (only when uncontrolled).\n React.useEffect(() => {\n if (themeProp != null || !persist || typeof window === \"undefined\") return;\n const stored = window.localStorage.getItem(STORAGE_KEY);\n if (stored) setThemeState(stored);\n else if (window.matchMedia?.(\"(prefers-color-scheme: light)\").matches) setThemeState(\"light\");\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, []);\n\n // Keep state in sync when controlled.\n React.useEffect(() => { if (themeProp != null) setThemeState(themeProp); }, [themeProp]);\n React.useEffect(() => { if (dirProp != null) setDirState(dirProp); }, [dirProp]);\n\n const setTheme = React.useCallback((t: TogoTheme) => {\n setThemeState(t);\n if (persist && typeof window !== \"undefined\") window.localStorage.setItem(STORAGE_KEY, String(t));\n }, [persist]);\n\n const setDir = React.useCallback((d: Dir) => setDirState(d), []);\n\n // Apply to the chosen scope.\n React.useEffect(() => {\n if (typeof document === \"undefined\") return;\n const el = scope === \"self\" ? selfRef.current : document.documentElement;\n if (el) applyToElement(el, String(theme), dir, overrides);\n }, [theme, dir, overrides, scope]);\n\n const value = React.useMemo<ThemeContextValue>(() => ({ theme, setTheme, themes, dir, setDir }), [theme, setTheme, themes, dir, setDir]);\n\n return (\n <ThemeContext.Provider value={value}>\n {scope === \"self\" ? (\n <div\n ref={selfRef}\n data-theme={theme}\n dir={dir}\n className={[\"tg-root\", themeBase(String(theme)) === \"dark\" ? \"dark\" : \"\", \"bg-background text-foreground\", className].filter(Boolean).join(\" \")}\n style={overrides as React.CSSProperties}\n >\n {children}\n </div>\n ) : (\n children\n )}\n </ThemeContext.Provider>\n );\n}\nThemeProvider.displayName = \"ThemeProvider\";\n\nexport function useTheme(): ThemeContextValue {\n const ctx = React.useContext(ThemeContext);\n if (!ctx) throw new Error(\"useTheme must be used within a <ThemeProvider>\");\n return ctx;\n}\n","// ToGO theme registry. A theme is a `data-theme` value + a dark/light base (so the\n// existing `.dark`-driven utilities stay in sync). Add a tenant/brand theme by adding\n// one block to tokens.semantic.css and one entry here — no component changes.\n\nexport type ThemeBase = \"dark\" | \"light\";\n\nexport interface ThemeDef {\n id: string;\n label: string;\n /** Whether this theme toggles the `.dark` class (keeps `dark:` utilities in sync). */\n base: ThemeBase;\n}\n\nexport const themes: ThemeDef[] = [\n { id: \"dark\", label: \"Dark\", base: \"dark\" },\n { id: \"light\", label: \"Light\", base: \"light\" },\n];\n\nexport function themeBase(id: string): ThemeBase {\n return themes.find((t) => t.id === id)?.base ?? \"dark\";\n}\n\nexport const STORAGE_KEY = \"togo-theme\";\n\n/**\n * Inline this string in a <script> in <head> to set the theme before first paint\n * (no flash of the wrong theme). The framework injects it server-side.\n *\n * <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />\n */\nexport const themeInitScript = `(function(){try{\nvar k=${JSON.stringify(STORAGE_KEY)};\nvar t=localStorage.getItem(k);\nif(!t){t=window.matchMedia&&window.matchMedia('(prefers-color-scheme: light)').matches?'light':'dark';}\nvar d=document.documentElement;\nd.setAttribute('data-theme',t);\nd.classList.toggle('dark', t!=='light');\n}catch(e){}})();`;\n"],"mappings":"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","names":["jsx","themes"]}