@djangocfg/ui-core 2.1.460 → 2.1.463

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 (46) hide show
  1. package/README.md +1 -1
  2. package/package.json +19 -11
  3. package/src/styles/README.md +74 -510
  4. package/src/styles/{base.css → css/base.css} +1 -1
  5. package/src/styles/css/presets/default.css +1 -0
  6. package/src/styles/css/presets/dense.css +36 -0
  7. package/src/styles/css/presets/django-cfg.css +44 -0
  8. package/src/styles/css/presets/high-contrast.css +24 -0
  9. package/src/styles/css/presets/ios.css +120 -0
  10. package/src/styles/css/presets/macos.css +138 -0
  11. package/src/styles/css/presets/soft.css +52 -0
  12. package/src/styles/css/presets/windows.css +140 -0
  13. package/src/styles/{utilities → css/utilities}/glass.css +16 -0
  14. package/src/styles/presets/index.ts +2 -15
  15. package/src/styles/presets/presets.ts +0 -3
  16. package/src/styles/presets/types.ts +4 -148
  17. package/src/theme/README.md +36 -64
  18. package/src/theme/TROUBLESHOOTING.md +4 -0
  19. package/src/styles/presets/build.ts +0 -64
  20. package/src/styles/presets/themes/default.ts +0 -6
  21. package/src/styles/presets/themes/dense.ts +0 -31
  22. package/src/styles/presets/themes/django-cfg.ts +0 -62
  23. package/src/styles/presets/themes/high-contrast.ts +0 -35
  24. package/src/styles/presets/themes/index.ts +0 -24
  25. package/src/styles/presets/themes/ios.ts +0 -114
  26. package/src/styles/presets/themes/macos.ts +0 -190
  27. package/src/styles/presets/themes/soft.ts +0 -48
  28. package/src/styles/presets/themes/types.ts +0 -6
  29. package/src/styles/presets/themes/windows.ts +0 -154
  30. /package/src/styles/{full.css → css/full.css} +0 -0
  31. /package/src/styles/{globals.css → css/globals.css} +0 -0
  32. /package/src/styles/{index.css → css/index.css} +0 -0
  33. /package/src/styles/{sources.css → css/sources.css} +0 -0
  34. /package/src/styles/{theme → css/theme}/animations.css +0 -0
  35. /package/src/styles/{theme → css/theme}/dark.css +0 -0
  36. /package/src/styles/{theme → css/theme}/light.css +0 -0
  37. /package/src/styles/{theme → css/theme}/tokens.css +0 -0
  38. /package/src/styles/{theme.css → css/theme.css} +0 -0
  39. /package/src/styles/{utilities → css/utilities}/animations.css +0 -0
  40. /package/src/styles/{utilities → css/utilities}/controls.css +0 -0
  41. /package/src/styles/{utilities → css/utilities}/display.css +0 -0
  42. /package/src/styles/{utilities → css/utilities}/divider.css +0 -0
  43. /package/src/styles/{utilities → css/utilities}/marquee.css +0 -0
  44. /package/src/styles/{utilities → css/utilities}/overlay.css +0 -0
  45. /package/src/styles/{utilities → css/utilities}/step.css +0 -0
  46. /package/src/styles/{utilities.css → css/utilities.css} +0 -0
@@ -1,140 +1,9 @@
1
1
  /**
2
- * Theme preset types — lives in ui-core so Wails, Electron, and any
3
- * non-Next.js consumer can use presets without pulling in @djangocfg/layouts.
4
- */
5
-
6
- /**
7
- * Core semantic colors — **fully-wrapped CSS colors** (`hsl(...)`, `oklch(...)`, `#rrggbb`, …).
2
+ * Names of the static CSS presets shipped by ui-core.
8
3
  *
9
- * Required by the `@theme inline { --color-X: var(--X); }` shape so Tailwind's
10
- * opacity modifier (`bg-card/40`) can resolve through `color-mix`. Bare HSL
11
- * triplets (`240 17% 97%`) silently break every opacity modifier — they used
12
- * to work via a manual `hsl(var(--X))` wrapper in tokens.css; that wrapper is
13
- * gone after the v4 refactor. Same rule for `border` / `ring` / `chart-*` /
14
- * `sidebar-*` (every color-typed key). `radius` + typography keys stay raw.
15
- */
16
- export type ThemeCssVarColorKey =
17
- | 'background'
18
- | 'foreground'
19
- | 'card'
20
- | 'card-foreground'
21
- | 'popover'
22
- | 'popover-foreground'
23
- | 'primary'
24
- | 'primary-foreground'
25
- | 'secondary'
26
- | 'secondary-foreground'
27
- | 'muted'
28
- | 'muted-foreground'
29
- | 'accent'
30
- | 'accent-foreground'
31
- | 'destructive'
32
- | 'destructive-foreground';
33
-
34
- /**
35
- * Layout / focus tokens — `radius` is a CSS length, the rest are wrapped colors.
36
- * `divider` is the hairline-between-rows token (deliberately *lighter* than
37
- * `--card` so it stays visible on elevated surfaces — see styles README).
38
- * `overlay` is the modal scrim behind dialogs/drawers/sheets — a wrapped color
39
- * that OWNS its own opacity (`hsl(0 0% 0% / 0.55)`). Brand presets that set a
40
- * custom `background` should set `overlay` too: on a near-black canvas the base
41
- * 0.6/0.7 scrim crushes the backdrop flat and the dialog can't lift off it.
42
- */
43
- export type ThemeCssVarChromeKey = 'border' | 'input' | 'divider' | 'overlay' | 'ring' | 'radius';
44
-
45
- /**
46
- * Status surfaces — each role has a 4-token set (icon/accent, banner background,
47
- * readable foreground, border ring) used by banners/alerts. Presets with a
48
- * custom `background` should re-tint these so banners don't clash with the
49
- * canvas; presets that keep the default canvas can omit them. All are
50
- * **fully-wrapped CSS colors** (same rule as `ThemeCssVarColorKey`).
51
- *
52
- * `*-foreground` = status TEXT on the tinted `*-background` (banners).
53
- * `on-*` = readable text/icon ON the solid `*` fill (badges, pills, filled
54
- * chips) — a near-black/near-white contrast color, NOT the banner tint. The
55
- * base `on-*` (near-black) is safe for most fills; a preset only needs to set
56
- * `on-*` when it retints a fill DARK enough that near-black fails (e.g.
57
- * Fluent's dark light-mode green/blue → white ink).
58
- */
59
- export type ThemeCssVarStatusKey =
60
- | 'warning'
61
- | 'warning-background'
62
- | 'warning-foreground'
63
- | 'warning-border'
64
- | 'success'
65
- | 'success-background'
66
- | 'success-foreground'
67
- | 'success-border'
68
- | 'destructive-background'
69
- | 'destructive-border'
70
- | 'info'
71
- | 'info-background'
72
- | 'info-foreground'
73
- | 'info-border'
74
- | 'on-success'
75
- | 'on-warning'
76
- | 'on-info'
77
- | 'on-destructive';
78
-
79
- /**
80
- * Typography tokens — override system font stack and scale per preset.
81
- * Values are plain CSS (not HSL): font-family strings, rem lengths, unitless line-height, em letter-spacing.
82
- */
83
- export type ThemeCssVarTypographyKey =
84
- | 'font-sans'
85
- | 'font-mono'
86
- | 'font-size-base'
87
- | 'font-size-sm'
88
- | 'font-size-xs'
89
- | 'font-size-lg'
90
- | 'font-size-xl'
91
- | 'line-height-base'
92
- | 'letter-spacing-base';
93
-
94
- export type ThemeCssVarSidebarKey =
95
- | 'sidebar-background'
96
- | 'sidebar-foreground'
97
- | 'sidebar-primary'
98
- | 'sidebar-primary-foreground'
99
- | 'sidebar-accent'
100
- | 'sidebar-accent-foreground'
101
- | 'sidebar-border'
102
- | 'sidebar-ring';
103
-
104
- /**
105
- * Categorical chart colors. **Fully-wrapped CSS colors** (`hsl(...)`) — same
106
- * rule as every color key. Consume via the Tailwind utility (`bg-chart-1`,
107
- * backed by `--color-chart-N` in tokens.css) or `var(--chart-N)`; never wrap as
108
- * `hsl(var(--chart-N))` (that double-wraps to `hsl(hsl(...))` and is invalid).
109
- */
110
- export type ThemeCssVarChartKey = 'chart-1' | 'chart-2' | 'chart-3' | 'chart-4' | 'chart-5';
111
-
112
- /** All keys that map to `--${key}` in ui-core CSS files */
113
- export type ThemeCssVarKey =
114
- | ThemeCssVarColorKey
115
- | ThemeCssVarChromeKey
116
- | ThemeCssVarStatusKey
117
- | ThemeCssVarTypographyKey
118
- | ThemeCssVarSidebarKey
119
- | ThemeCssVarChartKey;
120
-
121
- /** Partial map of semantic variables for one color mode */
122
- export type ThemeCssVarMap = Partial<Record<ThemeCssVarKey, string>>;
123
-
124
- /**
125
- * Built-in preset IDs.
126
- *
127
- * Two families:
128
- * - Brand / OS-native: default, django-cfg, ios, macos, windows
129
- * (pixel-accurate Apple HIG / Microsoft Fluent tokens — useful for
130
- * Wails/Electron desktop and mobile web).
131
- * - Density / accessibility: soft (radius 1rem), dense (0.25rem),
132
- * high-contrast (a11y).
133
- *
134
- * The earlier code-editor vibe palette (catppuccin / dracula / github /
135
- * gruvbox / material / nord / one-dark / solarized / tokyo-night) was
136
- * removed in May 2026 — those are for IDE skins, not admin/desktop UI.
137
- * Re-add a preset only when a real product needs it.
4
+ * Preset tokens are intentionally not represented as TypeScript maps. CSS is
5
+ * the single source of truth and consumers select a preset with a stylesheet
6
+ * import, e.g. `@djangocfg/ui-core/styles/presets/macos`.
138
7
  */
139
8
  export type ThemeStylePresetId =
140
9
  | 'default'
@@ -145,16 +14,3 @@ export type ThemeStylePresetId =
145
14
  | 'soft'
146
15
  | 'dense'
147
16
  | 'high-contrast';
148
-
149
- /**
150
- * Optional style layer: named preset + per-mode overrides.
151
- * Merged: globals (CSS imports) → preset → vars.light / vars.dark
152
- */
153
- export interface ThemeStyleConfig {
154
- /** @default 'default' */
155
- preset?: ThemeStylePresetId;
156
- vars?: {
157
- light?: ThemeCssVarMap;
158
- dark?: ThemeCssVarMap;
159
- };
160
- }
@@ -1,85 +1,57 @@
1
- # Theme — providers, switches, and forced themes
1
+ # Theme runtime
2
2
 
3
- The runtime side of the design system. `styles/` ships the **tokens** (CSS
4
- variables) and the Tailwind utilities that read them; `theme/` is the **React**
5
- layer that decides *which* token set is live (light / dark / a preset) and lets
6
- you switch or pin it.
3
+ `@djangocfg/ui-core` separates static design tokens from runtime mode
4
+ selection:
7
5
 
8
- > Read [`../styles/README.md`](../styles/README.md) first for the **token
9
- > contract** — it is load-bearing for everything here. The one rule that bites:
10
- > **base tokens are full CSS colors (`hsl(...)`), never bare triplets.**
6
+ - `@djangocfg/ui-core/styles/css/` owns all CSS tokens and presets.
7
+ - `ThemeProvider` owns the `html.dark` class and light/dark preference.
8
+ - `ThemeOverride` temporarily changes that class for a route.
9
+ - `ForceTheme` is only for a genuinely opposite-theme subtree.
11
10
 
12
- ## Exports
11
+ Select a preset in the host stylesheet, after the golden-path styles:
13
12
 
14
- | Export | What it does |
15
- |---|---|
16
- | `ThemeProvider` / `useThemeContext` | Wraps `next-themes`. Owns the `html.dark` class + the user's light/dark/system choice. Mount once at the app root (via `BaseApp` in `@djangocfg/layouts`, which mounts it for you). |
17
- | `ThemeToggle` | A light/dark toggle button. |
18
- | `ThemeSegmented` | A segmented light/dark/system control. |
19
- | `ThemeOverride` | **Pathname-aware forced theme.** Mutates the real `next-themes` value while the route matches a rule, restores the user's pick when it leaves. Globs (`*`, `**`) supported. This is the **correct** way to force a route's theme. |
20
- | `resolveForcedTheme` | Pure helper — first matching rule → forced theme (or `null`). Pair with `ForcedThemeProvider`. |
21
- | `ForcedThemeProvider` / `useForcedTheme` | Lets descendants read the currently-forced theme. |
22
- | `ForceTheme` | **Scoped, inline-var theme for a subtree.** A `<div class={theme}>` that re-declares the token vars inline. Use sparingly — see the trap below. |
13
+ ```css
14
+ @import "@djangocfg/ui-core/styles/full";
15
+ @import "@djangocfg/ui-core/styles/presets/macos";
16
+ ```
23
17
 
24
- ## Choosing: `ThemeOverride` vs `ForceTheme`
18
+ There is no runtime preset injection, token builder, or `ThemeStyleBridge`.
19
+ Navigation, hydration, lazy chunks, CDN revalidation, and system preference
20
+ changes cannot remove or replace the product palette.
25
21
 
26
- They look similar; they are not interchangeable.
22
+ ## Exports
23
+
24
+ | Export | Responsibility |
25
+ |---|---|
26
+ | `ThemeProvider` / `useThemeContext` | `next-themes` wrapper; owns `.dark` and light/dark/system preference. |
27
+ | `ThemeToggle` | Light/dark toggle. |
28
+ | `ThemeSegmented` | Light/dark/system control. |
29
+ | `ThemeOverride` | Route-aware light/dark override using the real provider. |
30
+ | `resolveForcedTheme` | Pure route-rule matcher. |
31
+ | `ForcedThemeProvider` / `useForcedTheme` | Context for a currently forced mode. |
32
+ | `ForceTheme` | Last-resort scoped subtree override with valid full-color tokens. |
27
33
 
28
- | | `ThemeOverride` (preferred) | `ForceTheme` (last resort) |
29
- |---|---|---|
30
- | How | mutates the real `next-themes` value → toggles `html.dark` | wraps children in `<div class={theme} style={inline vars}>` |
31
- | Scope | the **whole app** while the route matches | just that **subtree** |
32
- | Tokens | the real preset/`theme.css` tokens cascade — **always valid** | re-declares a hardcoded token map inline |
33
- | Active preset | **respected** (macos/ios/django-cfg/…) | **ignored** — ships its own generic palette |
34
- | Risk | none | bare-triplet trap (below); also overrides your brand accent |
34
+ ## Choosing a runtime mode
35
35
 
36
- **Default to `ThemeOverride`** for "this route is always dark" (the common case —
37
- a marketing landing, a docs section). It's how the cmdop site forces its homepage
38
- dark:
36
+ Use `ThemeOverride` when a complete route must be dark or light:
39
37
 
40
38
  ```tsx
41
- // in the app shell (RootAppLayout)
42
39
  <BaseApp theme={{ defaultTheme: 'dark' }}>
43
- <ThemeOverride pathname={pathnameWithoutLocale} rules={[{ path: '/', theme: 'dark' }]} />
40
+ <ThemeOverride pathname={pathname} rules={[{ path: '/', theme: 'dark' }]} />
44
41
  {children}
45
42
  </BaseApp>
46
43
  ```
47
44
 
48
- Reach for `ForceTheme` **only** when you need a single subtree in the opposite
49
- theme of the rest of the page (e.g. a dark preview card on a light page) and you
50
- cannot drive it through the router.
45
+ Use `ForceTheme` only when one subtree must differ from the surrounding page.
46
+ It is not a preset selector and does not replace the host CSS import.
51
47
 
52
- ## ⚠️ The `ForceTheme` bare-triplet trap
48
+ ## Token rule
53
49
 
54
- `ForceTheme` re-declares tokens inline. Under the **Tailwind v4 token contract**,
55
- base tokens are **full colors** and the utilities read them raw:
50
+ Every color variable must be a complete CSS color:
56
51
 
57
52
  ```css
58
- .bg-muted { background-color: var(--muted); }
59
- .border-border { border-color: var(--border); }
53
+ --muted: hsl(240 3% 11%); /* valid */
60
54
  ```
61
55
 
62
- So a token set to a **bare HSL triplet** is a broken color:
63
-
64
- ```css
65
- --muted: 0 0% 10%; /* ❌ var(--muted) → "0 0% 10%" → not a color → declaration dropped */
66
- --muted: hsl(0 0% 10%);/* ✅ valid color */
67
- ```
68
-
69
- The failure is **silent and confusing**: inside the `ForceTheme` subtree
70
- `bg-muted` renders **transparent** and `border-border` renders the inherited
71
- fallback (≈ white), while `border-divider` (a token `ForceTheme` doesn't
72
- re-declare) still works — so it looks like "random borders are white and some
73
- fills vanished." It is NOT a Tailwind content-scan problem; the utilities exist,
74
- their *input* is invalid. Full write-up + how to diagnose:
75
- [`TROUBLESHOOTING.md`](TROUBLESHOOTING.md).
76
-
77
- `ForceTheme`'s token map is wrapped in `hsl(...)` as of this version, so the trap
78
- is closed for the values it ships. But the lesson stands for **any** inline token
79
- override you write: wrap in `hsl(...)`, or prefer `ThemeOverride` so the real
80
- (already-valid) preset tokens cascade and you never hand-maintain a palette.
81
-
82
- ## Maintenance rule
83
-
84
- After changing a component or the token map here, update this README and bump the
85
- package patch version (consumers pin npm versions; this file is the changelog).
56
+ Never use a bare HSL triplet such as `240 3% 11%`; Tailwind consumes semantic
57
+ variables directly and the browser will drop invalid color declarations.
@@ -2,6 +2,10 @@
2
2
 
3
3
  Symptom-first. Each entry: what you see → why → the fix.
4
4
 
5
+ The product palette is static CSS imported from `styles/presets/*`. The React
6
+ theme layer only changes `.dark`; if the palette is missing, inspect the CSS
7
+ import/export path before debugging React state or localStorage.
8
+
5
9
  ---
6
10
 
7
11
  ## "Random white borders + transparent fills on part of the page"
@@ -1,64 +0,0 @@
1
- import type { ThemeCssVarMap, ThemeStyleConfig, ThemeStylePresetId } from './types';
2
- import { THEME_STYLE_PRESETS } from './presets';
3
-
4
- function mergeLayer(base: ThemeCssVarMap | undefined, over: ThemeCssVarMap | undefined): ThemeCssVarMap {
5
- return { ...base, ...over };
6
- }
7
-
8
- /**
9
- * Tailwind v4 `rounded-*` utilities are backed by `--radius-*` scale variables.
10
- * When semantic `radius` is present, emit the derived scale too.
11
- */
12
- function withTailwindRadiusScale(vars: ThemeCssVarMap): Array<[string, string]> {
13
- const entries = Object.entries(vars);
14
- const radius = vars.radius;
15
- if (!radius) return entries;
16
-
17
- const r = String(radius).trim();
18
- if (!r) return entries;
19
-
20
- const scale: Array<[string, string]> = [
21
- ['radius-xs', `calc(${r} - 6px)`],
22
- ['radius-sm', `calc(${r} - 4px)`],
23
- ['radius-md', `calc(${r} - 2px)`],
24
- ['radius-lg', r],
25
- ['radius-xl', `calc(${r} + 4px)`],
26
- ['radius-2xl', `calc(${r} + 8px)`],
27
- ['radius-3xl', `calc(${r} + 12px)`],
28
- ['radius-4xl', `calc(${r} + 16px)`],
29
- ];
30
-
31
- return [...entries, ...scale];
32
- }
33
-
34
- /**
35
- * Build a stylesheet fragment: preset → vars.light / vars.dark.
36
- * Inject this after globals (later rule wins).
37
- */
38
- export function buildThemeStyleSheet(style?: ThemeStyleConfig): string {
39
- if (!style) return '';
40
-
41
- const presetId: ThemeStylePresetId = style.preset ?? 'default';
42
- const preset = THEME_STYLE_PRESETS[presetId] ?? THEME_STYLE_PRESETS.default;
43
-
44
- const light = mergeLayer(preset.light, style.vars?.light);
45
- const dark = mergeLayer(preset.dark, style.vars?.dark);
46
-
47
- const blocks: string[] = [];
48
-
49
- if (Object.keys(light).length > 0) {
50
- const body = withTailwindRadiusScale(light)
51
- .map(([k, v]) => ` --${k}: ${v};`)
52
- .join('\n');
53
- blocks.push(`:root {\n${body}\n}`);
54
- }
55
-
56
- if (Object.keys(dark).length > 0) {
57
- const body = withTailwindRadiusScale(dark)
58
- .map(([k, v]) => ` --${k}: ${v};`)
59
- .join('\n');
60
- blocks.push(`.dark {\n${body}\n}`);
61
- }
62
-
63
- return blocks.join('\n\n');
64
- }
@@ -1,6 +0,0 @@
1
- import type { ThemePreset } from './types';
2
-
3
- export const defaultPreset: ThemePreset = {
4
- light: {},
5
- dark: {},
6
- };
@@ -1,31 +0,0 @@
1
- /**
2
- * dense — density modifier (NOT a full color theme).
3
- *
4
- * Tight radius (0.25rem) + slightly stronger neutral chrome for data-heavy
5
- * admin tables/grids where rows need crisp separation. Overrides only neutral
6
- * surfaces (radius/border/input/divider/muted/card/accent) and inherits brand
7
- * colors from the active base/brand preset, so it composes with `django-cfg`.
8
- * Light and dark are symmetric.
9
- */
10
- import type { ThemePreset } from './types';
11
-
12
- export const densePreset: ThemePreset = {
13
- light: {
14
- radius: '0.25rem',
15
- border: 'hsl(0 0% 82%)',
16
- input: 'hsl(0 0% 82%)',
17
- divider: 'hsl(0 0% 88%)',
18
- muted: 'hsl(0 0% 94%)',
19
- card: 'hsl(0 0% 100%)',
20
- accent: 'hsl(0 0% 93%)',
21
- },
22
- dark: {
23
- radius: '0.25rem',
24
- border: 'hsl(0 0% 24%)',
25
- input: 'hsl(0 0% 24%)',
26
- divider: 'hsl(0 0% 46% / 0.18)', // translucent hairline — dissolves into the page
27
- muted: 'hsl(0 0% 12%)',
28
- card: 'hsl(0 0% 10%)',
29
- accent: 'hsl(0 0% 14%)',
30
- },
31
- };
@@ -1,62 +0,0 @@
1
- /**
2
- * django-cfg brand preset.
3
- *
4
- * The default ui-core theme already uses the django-cfg cyan family, so this
5
- * preset is the *explicit, self-contained* brand declaration: it re-states the
6
- * full brand-bearing token set (primary, ring, accent wash, sidebar, charts,
7
- * info status) so the identity survives even when layered over a non-default
8
- * base, and tints the neutral hover/accent surfaces with a faint brand wash
9
- * instead of the base's pure grey.
10
- *
11
- * Brand cyan: #0989aa (light, `192 90% 35%`) / #00d9ff (dark, `189 100% 50%`).
12
- * Only color tokens are set — radius/typography inherit base so the brand can
13
- * be combined with `soft`/`dense` density modifiers downstream.
14
- */
15
- import type { ThemePreset } from './types';
16
-
17
- export const djangoCfgPreset: ThemePreset = {
18
- light: {
19
- primary: 'hsl(192 90% 35%)',
20
- 'primary-foreground': 'hsl(0 0% 100%)',
21
- // Faint brand wash on hover/selected surfaces (vs base's neutral grey)
22
- accent: 'hsl(192 60% 94%)',
23
- 'accent-foreground': 'hsl(192 90% 22%)',
24
- ring: 'hsl(192 90% 40%)',
25
- 'sidebar-primary': 'hsl(192 90% 35%)',
26
- 'sidebar-primary-foreground': 'hsl(0 0% 100%)',
27
- 'sidebar-accent': 'hsl(192 55% 93%)',
28
- 'sidebar-accent-foreground': 'hsl(192 90% 22%)',
29
- 'sidebar-ring': 'hsl(192 90% 40%)',
30
- // Info status routed to the brand hue so banners read as on-brand
31
- info: 'hsl(192 90% 35%)',
32
- 'info-background': 'hsl(192 100% 96%)',
33
- 'info-foreground': 'hsl(192 90% 22%)',
34
- 'info-border': 'hsl(192 80% 78%)',
35
- 'chart-1': 'hsl(192 90% 35%)',
36
- 'chart-2': 'hsl(142 76% 36%)',
37
- 'chart-3': 'hsl(262 83% 58%)',
38
- 'chart-4': 'hsl(26 90% 57%)',
39
- 'chart-5': 'hsl(346 77% 50%)',
40
- },
41
- dark: {
42
- primary: 'hsl(189 100% 50%)',
43
- 'primary-foreground': 'hsl(0 0% 9%)',
44
- accent: 'hsl(189 40% 18%)',
45
- 'accent-foreground': 'hsl(189 100% 78%)',
46
- ring: 'hsl(189 100% 50%)',
47
- 'sidebar-primary': 'hsl(189 100% 50%)',
48
- 'sidebar-primary-foreground': 'hsl(0 0% 9%)',
49
- 'sidebar-accent': 'hsl(189 35% 16%)',
50
- 'sidebar-accent-foreground': 'hsl(189 100% 78%)',
51
- 'sidebar-ring': 'hsl(189 100% 50%)',
52
- info: 'hsl(189 100% 55%)',
53
- 'info-background': 'hsl(189 70% 10%)',
54
- 'info-foreground': 'hsl(189 90% 75%)',
55
- 'info-border': 'hsl(189 50% 28%)',
56
- 'chart-1': 'hsl(189 100% 50%)',
57
- 'chart-2': 'hsl(142 76% 50%)',
58
- 'chart-3': 'hsl(262 83% 65%)',
59
- 'chart-4': 'hsl(26 90% 60%)',
60
- 'chart-5': 'hsl(346 77% 58%)',
61
- },
62
- };
@@ -1,35 +0,0 @@
1
- /**
2
- * high-contrast — accessibility modifier (NOT a full color theme).
3
- *
4
- * Pushes border/divider/text contrast to meet stronger WCAG ratios: harder
5
- * borders, darker (light) / brighter (dark) text, a stronger focus ring, and a
6
- * pure white / near-black canvas so foreground separation is maximal. Brand
7
- * colors (primary/charts/status) are inherited so the a11y boost stacks on top
8
- * of any base or brand preset. Light and dark are symmetric.
9
- */
10
- import type { ThemePreset } from './types';
11
-
12
- export const highContrastPreset: ThemePreset = {
13
- light: {
14
- background: 'hsl(0 0% 100%)',
15
- foreground: 'hsl(0 0% 6%)',
16
- card: 'hsl(0 0% 100%)',
17
- 'card-foreground': 'hsl(0 0% 6%)',
18
- border: 'hsl(0 0% 62%)',
19
- input: 'hsl(0 0% 62%)',
20
- divider: 'hsl(0 0% 68%)',
21
- 'muted-foreground': 'hsl(0 0% 28%)',
22
- ring: 'hsl(0 0% 12%)',
23
- },
24
- dark: {
25
- background: 'hsl(0 0% 4%)',
26
- foreground: 'hsl(0 0% 100%)',
27
- card: 'hsl(0 0% 8%)',
28
- 'card-foreground': 'hsl(0 0% 100%)',
29
- border: 'hsl(0 0% 48%)',
30
- input: 'hsl(0 0% 48%)',
31
- divider: 'hsl(0 0% 42%)',
32
- 'muted-foreground': 'hsl(0 0% 82%)',
33
- ring: 'hsl(0 0% 92%)',
34
- },
35
- };
@@ -1,24 +0,0 @@
1
- import type { ThemeStylePresetId } from '../types';
2
- import type { ThemePreset } from './types';
3
-
4
- import { defaultPreset } from './default';
5
- import { djangoCfgPreset } from './django-cfg';
6
- import { iosPreset } from './ios';
7
- import { macosPreset } from './macos';
8
- import { windowsPreset } from './windows';
9
- import { softPreset } from './soft';
10
- import { densePreset } from './dense';
11
- import { highContrastPreset } from './high-contrast';
12
-
13
- export type { ThemePreset };
14
-
15
- export const THEME_PRESETS: Record<ThemeStylePresetId, ThemePreset> = {
16
- 'default': defaultPreset,
17
- 'django-cfg': djangoCfgPreset,
18
- 'ios': iosPreset,
19
- 'macos': macosPreset,
20
- 'windows': windowsPreset,
21
- 'soft': softPreset,
22
- 'dense': densePreset,
23
- 'high-contrast': highContrastPreset,
24
- };
@@ -1,114 +0,0 @@
1
- import type { ThemePreset } from './types';
2
-
3
- export const iosPreset: ThemePreset = {
4
- light: {
5
- background: 'hsl(0 0% 98%)',
6
- foreground: 'hsl(220 9% 12%)',
7
- card: 'hsl(0 0% 100%)',
8
- 'card-foreground': 'hsl(220 9% 12%)',
9
- popover: 'hsl(0 0% 100%)',
10
- 'popover-foreground': 'hsl(220 9% 12%)',
11
- primary: 'hsl(211 100% 50%)',
12
- 'primary-foreground': 'hsl(0 0% 100%)',
13
- secondary: 'hsl(220 9% 92%)',
14
- 'secondary-foreground': 'hsl(220 9% 12%)',
15
- muted: 'hsl(220 9% 94%)',
16
- 'muted-foreground': 'hsl(220 9% 40%)',
17
- accent: 'hsl(220 9% 94%)',
18
- 'accent-foreground': 'hsl(220 9% 12%)',
19
- destructive: 'hsl(0 100% 58%)',
20
- 'destructive-foreground': 'hsl(0 0% 100%)',
21
- border: 'hsl(220 9% 85%)', // nudged darker so outlines read on white cards
22
- input: 'hsl(220 9% 88%)',
23
- ring: 'hsl(211 100% 50%)',
24
- divider: 'hsl(220 9% 88%)', // hairline, a touch lighter than border
25
- radius: '0.75rem',
26
- // Modal scrim — light dim, iOS sheets keep the backdrop visible.
27
- overlay: 'hsl(220 9% 10% / 0.32)',
28
- 'sidebar-background': 'hsl(0 0% 99%)',
29
- 'sidebar-foreground': 'hsl(220 9% 12%)',
30
- 'sidebar-primary': 'hsl(211 100% 50%)',
31
- 'sidebar-primary-foreground': 'hsl(0 0% 100%)',
32
- 'sidebar-accent': 'hsl(220 9% 94%)',
33
- 'sidebar-accent-foreground': 'hsl(220 9% 12%)',
34
- 'sidebar-border': 'hsl(220 9% 88%)',
35
- 'sidebar-ring': 'hsl(211 100% 50%)',
36
- 'chart-1': 'hsl(211 100% 50%)',
37
- 'chart-2': 'hsl(145 63% 42%)',
38
- 'chart-3': 'hsl(262 83% 58%)',
39
- 'chart-4': 'hsl(35 100% 50%)',
40
- 'chart-5': 'hsl(346 77% 50%)',
41
- // Status surfaces — iOS system colors
42
- warning: 'hsl(35 100% 50%)',
43
- 'warning-background': 'hsl(35 100% 95%)',
44
- 'warning-foreground': 'hsl(28 80% 30%)',
45
- 'warning-border': 'hsl(35 90% 78%)',
46
- success: 'hsl(145 63% 42%)',
47
- 'success-background': 'hsl(145 60% 95%)',
48
- 'success-foreground': 'hsl(145 60% 24%)',
49
- 'success-border': 'hsl(145 55% 76%)',
50
- 'destructive-background': 'hsl(0 100% 96%)',
51
- 'destructive-border': 'hsl(0 90% 80%)',
52
- info: 'hsl(211 100% 50%)',
53
- 'info-background': 'hsl(211 100% 96%)',
54
- 'info-foreground': 'hsl(211 80% 30%)',
55
- 'info-border': 'hsl(211 90% 80%)',
56
- },
57
- dark: {
58
- background: 'hsl(240 6% 10%)',
59
- foreground: 'hsl(0 0% 96%)',
60
- // Lifted 14→16 for a clearer float off the dark page (see macos rationale).
61
- card: 'hsl(240 6% 16%)',
62
- 'card-foreground': 'hsl(0 0% 96%)',
63
- // Kept a distinct step above card.
64
- popover: 'hsl(240 6% 20%)',
65
- 'popover-foreground': 'hsl(0 0% 96%)',
66
- primary: 'hsl(211 100% 55%)',
67
- 'primary-foreground': 'hsl(0 0% 100%)',
68
- secondary: 'hsl(240 5% 26%)',
69
- 'secondary-foreground': 'hsl(0 0% 96%)',
70
- // Recessed surface — below the page (bg 10%) and below card (16%), so
71
- // bg-muted rails/chips read as sunk, not level with the panel.
72
- muted: 'hsl(240 5% 13%)',
73
- 'muted-foreground': 'hsl(240 5% 64%)',
74
- accent: 'hsl(240 5% 18%)',
75
- 'accent-foreground': 'hsl(0 0% 96%)',
76
- destructive: 'hsl(0 100% 67%)',
77
- 'destructive-foreground': 'hsl(0 0% 100%)',
78
- border: 'hsl(240 5% 27%)', // raised so outlines read on the dark page
79
- input: 'hsl(240 5% 22%)',
80
- ring: 'hsl(211 100% 55%)',
81
- divider: 'hsl(240 5% 48% / 0.18)', // translucent hairline — dissolves into the page
82
- radius: '0.75rem',
83
- // Modal scrim — lighter than base 0.7 so the dark page still reads behind.
84
- overlay: 'hsl(0 0% 0% / 0.55)',
85
- 'sidebar-background': 'hsl(240 6% 8%)',
86
- 'sidebar-foreground': 'hsl(0 0% 96%)',
87
- 'sidebar-primary': 'hsl(211 100% 55%)',
88
- 'sidebar-primary-foreground': 'hsl(0 0% 100%)',
89
- 'sidebar-accent': 'hsl(240 5% 16%)',
90
- 'sidebar-accent-foreground': 'hsl(0 0% 96%)',
91
- 'sidebar-border': 'hsl(240 5% 22%)',
92
- 'sidebar-ring': 'hsl(211 100% 55%)',
93
- 'chart-1': 'hsl(211 100% 55%)',
94
- 'chart-2': 'hsl(145 65% 52%)',
95
- 'chart-3': 'hsl(262 83% 65%)',
96
- 'chart-4': 'hsl(35 100% 58%)',
97
- 'chart-5': 'hsl(346 77% 58%)',
98
- // Status surfaces (dark)
99
- warning: 'hsl(35 100% 58%)',
100
- 'warning-background': 'hsl(35 90% 10%)',
101
- 'warning-foreground': 'hsl(35 100% 72%)',
102
- 'warning-border': 'hsl(35 80% 28%)',
103
- success: 'hsl(145 65% 52%)',
104
- 'success-background': 'hsl(145 60% 9%)',
105
- 'success-foreground': 'hsl(145 60% 70%)',
106
- 'success-border': 'hsl(145 50% 26%)',
107
- 'destructive-background': 'hsl(0 80% 11%)',
108
- 'destructive-border': 'hsl(0 70% 30%)',
109
- info: 'hsl(211 100% 55%)',
110
- 'info-background': 'hsl(211 90% 11%)',
111
- 'info-foreground': 'hsl(211 100% 74%)',
112
- 'info-border': 'hsl(211 80% 30%)',
113
- },
114
- };