@djangocfg/ui-core 2.1.426 → 2.1.428
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/package.json +4 -4
- package/src/components/data/badge/index.tsx +1 -1
- package/src/components/data/calendar/calendar.tsx +2 -2
- package/src/components/data/stat/index.tsx +5 -5
- package/src/components/data/status/index.tsx +3 -3
- package/src/components/data/table/index.tsx +30 -11
- package/src/components/feedback/banner/index.tsx +8 -4
- package/src/components/forms/button/index.tsx +15 -5
- package/src/components/forms/button-download/index.tsx +2 -2
- package/src/components/forms/checkbox/index.tsx +1 -1
- package/src/components/forms/editable/index.tsx +19 -19
- package/src/components/forms/input/index.tsx +44 -9
- package/src/components/forms/otp/index.tsx +1 -1
- package/src/components/forms/setting-row/index.tsx +363 -0
- package/src/components/forms/switch/index.tsx +1 -1
- package/src/components/forms/tags-input/index.tsx +1 -1
- package/src/components/forms/textarea/index.tsx +3 -8
- package/src/components/index.ts +2 -0
- package/src/components/navigation/dropdown-menu/index.tsx +3 -1
- package/src/components/navigation/menu/menu-builder.tsx +7 -2
- package/src/components/navigation/stepper/index.tsx +1 -1
- package/src/components/navigation/tabs/index.tsx +3 -3
- package/src/components/overlay/dialog/index.tsx +8 -3
- package/src/components/overlay/sheet/index.tsx +1 -1
- package/src/components/overlay/tooltip/index.tsx +4 -1
- package/src/components/select/multi-select-pro-async.tsx +2 -2
- package/src/components/select/multi-select-pro.tsx +2 -2
- package/src/components/specialized/copy/index.tsx +2 -2
- package/src/components/specialized/item/index.tsx +1 -1
- package/src/hooks/router/README.md +4 -1
- package/src/lib/env.ts +6 -1
- package/src/styles/README.md +115 -22
- package/src/styles/base.css +18 -1
- package/src/styles/presets/index.ts +1 -0
- package/src/styles/presets/themes/dense.ts +11 -0
- package/src/styles/presets/themes/django-cfg.ts +43 -2
- package/src/styles/presets/themes/high-contrast.ts +25 -9
- package/src/styles/presets/themes/ios.ts +32 -0
- package/src/styles/presets/themes/macos.ts +36 -0
- package/src/styles/presets/themes/soft.ts +13 -0
- package/src/styles/presets/themes/windows.ts +34 -0
- package/src/styles/presets/types.ts +36 -2
- package/src/styles/theme/dark.css +48 -32
- package/src/styles/theme/light.css +21 -13
- package/src/styles/theme/tokens.css +23 -0
- package/src/styles/utilities/controls.css +12 -0
- package/src/styles/utilities/divider.css +23 -0
- package/src/styles/utilities.css +2 -0
- package/src/theme/ThemeSegmented.tsx +73 -0
- package/src/theme/index.ts +2 -0
- package/src/types/index.ts +0 -0
package/src/styles/README.md
CHANGED
|
@@ -9,8 +9,9 @@ styles/
|
|
|
9
9
|
├── full.css # Golden path (recommended) — Tailwind + tokens + base + utilities, cascade-layer-safe
|
|
10
10
|
├── index.css # Plain entry (no Tailwind, unlayered) — you own layer ordering
|
|
11
11
|
├── theme.css # Imports tokens.css → animations → light → dark
|
|
12
|
-
├── base.css # Resets + `*` border-color + body bg/color
|
|
13
|
-
├── utilities.css # Custom utilities
|
|
12
|
+
├── base.css # Resets + `*` border-color + body bg/color + radius scale + native focus-outline reset
|
|
13
|
+
├── utilities.css # Custom utilities entry — imports utilities/*
|
|
14
|
+
│ └── utilities/ # display · divider · controls · step · animations · glass · marquee
|
|
14
15
|
├── sources.css # @source directives for monorepo class detection
|
|
15
16
|
├── palette/ # JS-readable color access (Canvas/SVG/Mermaid)
|
|
16
17
|
└── theme/
|
|
@@ -53,17 +54,18 @@ This makes opacity modifiers (`bg-card/40`, `border-foreground/20`) resolve thro
|
|
|
53
54
|
| `bg-card` / `text-card-foreground` | `--card` | Card surface (elevated over background) |
|
|
54
55
|
| `bg-popover` / `text-popover-foreground` | `--popover` | Floating menus, tooltips, dropdowns |
|
|
55
56
|
| `bg-muted` / `text-muted-foreground` | `--muted` | Subtle surface (input rest, chips, secondary text) |
|
|
56
|
-
| `bg-accent` / `text-accent-foreground` | `--accent` | Hover surface |
|
|
57
|
-
| `bg-primary` / `text-primary-foreground` | `--primary` | Brand CTA (filled buttons, links) |
|
|
57
|
+
| `bg-accent` / `text-accent-foreground` | `--accent` | Hover + selected surface — **neutral gray** (no brand tint), quiet like macOS/Claude |
|
|
58
|
+
| `bg-primary` / `text-primary-foreground` | `--primary` | Brand CTA (filled buttons, links) — cyan |
|
|
58
59
|
| `bg-secondary` / `text-secondary-foreground` | `--secondary` | Neutral filled controls |
|
|
59
60
|
| `bg-destructive` / `text-destructive-foreground` | `--destructive` | Error / delete filled controls |
|
|
60
|
-
| `border-border` | `--border` |
|
|
61
|
-
| `
|
|
62
|
-
| `
|
|
61
|
+
| `border-border` | `--border` | Card outlines, control borders |
|
|
62
|
+
| `border-divider` / `.divider-b` | `--divider` | **Hairline between rows** — deliberately *lighter than `--card`* so it stays visible on elevated surfaces (a `--border` line vanishes on a card) |
|
|
63
|
+
| `bg-input` | `--input` | Input **fill** — a notch off the panel so fields read as real controls (not flush holes). The input *border* uses `--border`, not `--input` |
|
|
64
|
+
| `ring-ring` | `--ring` | Focus rings, selected outlines — **blue** (system-accent feel), independent of the cyan brand |
|
|
63
65
|
|
|
64
|
-
### Status surface tokens (
|
|
66
|
+
### Status surface tokens (light + dark)
|
|
65
67
|
|
|
66
|
-
Each status has 4
|
|
68
|
+
Each status has the full 4-token set in **both** themes for banners and alerts:
|
|
67
69
|
|
|
68
70
|
| Role | Class | Token |
|
|
69
71
|
|---|---|---|
|
|
@@ -72,7 +74,7 @@ Each status has 4 tokens for banners and alerts:
|
|
|
72
74
|
| Readable text | `text-warning-foreground` | `--warning-foreground` |
|
|
73
75
|
| Border ring | `border-warning-border` | `--warning-border` |
|
|
74
76
|
|
|
75
|
-
Available statuses: **`warning`** · **`success`** · **`destructive`** · **`info`**.
|
|
77
|
+
Available statuses: **`warning`** · **`success`** · **`destructive`** · **`info`**. Reference consumers: `feedback/banner`, `specialized/copy`, `forms/button-download`, `data/status`, `data/stat`.
|
|
76
78
|
|
|
77
79
|
```tsx
|
|
78
80
|
<div className="flex items-center gap-3 rounded-md border border-warning-border/40
|
|
@@ -82,7 +84,12 @@ Available statuses: **`warning`** · **`success`** · **`destructive`** · **`in
|
|
|
82
84
|
</div>
|
|
83
85
|
```
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
The **brand presets** (`macos` / `ios` / `windows` / `django-cfg`) retint the full
|
|
88
|
+
status set to their own canvas, so banners read correctly on their custom
|
|
89
|
+
backgrounds. The **modifier presets** (`soft` / `dense` / `high-contrast`) and
|
|
90
|
+
`default` inherit the base status surfaces. Either way the four-token set is
|
|
91
|
+
always defined, so `bg-warning-background` etc. are safe everywhere. For a fully
|
|
92
|
+
preset-agnostic surface you can still derive from the base color with opacity:
|
|
86
93
|
|
|
87
94
|
```tsx
|
|
88
95
|
<div className="rounded-md border border-warning/30 bg-warning/10 text-warning">…</div>
|
|
@@ -97,18 +104,99 @@ Most presets don't override `*-background` / `*-border` / `*-foreground`. For pr
|
|
|
97
104
|
| `bg-sidebar` / `text-sidebar-foreground` / `border-sidebar-border` | App sidebar chrome |
|
|
98
105
|
| `bg-sidebar-accent` / `text-sidebar-accent-foreground` | Sidebar hover state |
|
|
99
106
|
|
|
107
|
+
### Chart tokens (categorical palette)
|
|
108
|
+
|
|
109
|
+
`--chart-1 … --chart-5` are the categorical series colors (chart-1 = brand hue).
|
|
110
|
+
Like every color token they are **fully-wrapped `hsl(...)`** and bound to
|
|
111
|
+
Tailwind via `--color-chart-*` in `tokens.css`, so the utilities work with
|
|
112
|
+
opacity modifiers:
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
<div className="bg-chart-1" /> {/* solid */}
|
|
116
|
+
<div className="bg-chart-3/40" /> {/* 40% via color-mix */}
|
|
117
|
+
<span className="text-chart-2" />
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
For Recharts / SVG / Canvas, pass the variable directly — **never** wrap it:
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
<Bar fill="var(--chart-1)" /> {/* ✅ */}
|
|
124
|
+
<Bar fill="hsl(var(--chart-1))" /> {/* ❌ hsl(hsl(...)) — invalid */}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
> **JIT-scan gotcha (charts/status).** `bg-chart-${n}` / `bg-${status}-background`
|
|
128
|
+
> built from template literals are invisible to Tailwind's static scanner —
|
|
129
|
+
> only literal classes get a CSS rule. Use literal class names (or inline
|
|
130
|
+
> `style={{ background: 'var(--chart-N)' }}`) when the index is dynamic.
|
|
131
|
+
|
|
132
|
+
### Typography tokens
|
|
133
|
+
|
|
134
|
+
`--font-sans` / `--font-mono` and the size scale (`--font-size-xs … -xl`,
|
|
135
|
+
`--line-height-base`, `--letter-spacing-base`) live in `base.css` and are
|
|
136
|
+
**overridable per preset** (e.g. `macos` → SF Pro, `windows` → Segoe UI
|
|
137
|
+
Variable). `tokens.css` bridges the size scale onto Tailwind's `--text-*` tokens,
|
|
138
|
+
so `font-sans` / `font-mono` and `text-xs … text-xl` follow the active preset
|
|
139
|
+
instead of Tailwind's hardcoded defaults. `body` applies font-sans + base
|
|
140
|
+
size/line-height/tracking directly.
|
|
141
|
+
|
|
142
|
+
## Radius tokens
|
|
143
|
+
|
|
144
|
+
The radius **scale** (`--radius`, `--radius-sm`, …) is theme-independent and lives in `base.css`; presets that set a semantic `radius` regenerate the scale via `presets/build.ts`. A few named radii are fixed defaults (default preset only):
|
|
145
|
+
|
|
146
|
+
| Token | Value | Used by |
|
|
147
|
+
|---|---|---|
|
|
148
|
+
| `--radius-control` | `0.625rem` | **Interactive controls** (inputs, nav items, search, value chips) — one shared corner so they round consistently. Class: `.rounded-control` |
|
|
149
|
+
| `--radius-dialog` | `1rem` | Dialog panels. Applied at **all** sizes (was `sm:`-gated, which left phones square) |
|
|
150
|
+
| `--radius-popover` | `0.75rem` | Popovers / menus |
|
|
151
|
+
|
|
152
|
+
## Focus rings (Vercel / Linear pattern)
|
|
153
|
+
|
|
154
|
+
Inputs use a **crisp thin accent outline**, not a blurry halo: the border turns `--ring` (blue) plus a tight `ring-1 ring-ring`, `:focus-visible` only.
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
focus-visible:border-ring focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
The native browser focus outline (often white/auto, which used to pierce through on click) is reset app-wide in `base.css`:
|
|
161
|
+
|
|
162
|
+
```css
|
|
163
|
+
:where(input, textarea, select, button, a, [tabindex], [role="button"], [contenteditable]):focus { outline: none; }
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
So every interactive control relies on its own `focus-visible:ring-*` — keyboard a11y is preserved, the stray native outline is gone. The single source for input styling is `INPUT_CLASS` / `inputClass(size)` exported from `components/forms/input` and **reused by `Editable`**, so standalone inputs and inline-edits focus identically.
|
|
167
|
+
|
|
168
|
+
## Scan-independent utility classes
|
|
169
|
+
|
|
170
|
+
Some classes are authored as **plain CSS** in `utilities/*.css` (not Tailwind utilities):
|
|
171
|
+
|
|
172
|
+
| Class | File | Why plain CSS |
|
|
173
|
+
|---|---|---|
|
|
174
|
+
| `.divider-b` / `.divider-t` | `utilities/divider.css` | Hairline via `--divider` |
|
|
175
|
+
| `.rounded-control` | `utilities/controls.css` | Shared control radius |
|
|
176
|
+
|
|
177
|
+
> **JIT-scan gotcha.** Tailwind's content scan covers `ui-core/src` but **not always every consumer package** (e.g. the `layouts` package source isn't always scanned by Storybook/apps). A *new* arbitrary Tailwind class used **only** in a consumer (`border-zinc-500/25`, `border-foreground/[0.12]`, `rounded-[var(--x)]`) then produces **no CSS rule** and silently falls back to the global `* { border-color: var(--border) }` — the class is in the DOM but the computed color is wrong. **Fix pattern:** for any token-driven visual that consumers need, add a plain `.class` in `ui-core/styles/utilities/*` and `@import` it, then use that class downstream. Don't invent new Tailwind classes in the `layouts` package.
|
|
178
|
+
|
|
100
179
|
## Theme presets — 8 production-ready
|
|
101
180
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
|
181
|
+
Two families, with different coverage by design:
|
|
182
|
+
|
|
183
|
+
- **Brand / OS presets** declare a *full* token set — colors, sidebar, charts,
|
|
184
|
+
status surfaces, divider, and (macos/windows) typography — so the identity is
|
|
185
|
+
self-contained and survives layering over any base.
|
|
186
|
+
- **Modifier presets** override only the chrome they're about (radius / borders /
|
|
187
|
+
contrast) and **inherit** brand colors, charts, and status from whatever is
|
|
188
|
+
active beneath them — so they compose (`django-cfg` + `dense`, etc.).
|
|
189
|
+
|
|
190
|
+
| ID | Family | Use case |
|
|
191
|
+
|---|---|---|
|
|
192
|
+
| `default` | base | Default ui-core theme — cyan brand |
|
|
193
|
+
| `django-cfg` | brand | Brand identity — brand-washed accent/sidebar + on-brand `info` |
|
|
194
|
+
| `macos` | brand | Pixel-accurate Apple HIG (Sequoia / Tahoe 26) — SF Pro, Wails/Electron desktop |
|
|
195
|
+
| `ios` | brand | iOS app feel — 0.75rem radius, systemBlue, iOS status colors |
|
|
196
|
+
| `windows` | brand | Microsoft Fluent 2 — Segoe UI Variable, 0.375rem radius |
|
|
197
|
+
| `soft` | modifier | Larger radius (1rem) — friendly marketing surfaces |
|
|
198
|
+
| `dense` | modifier | Smaller radius (0.25rem) — data-heavy admin UIs |
|
|
199
|
+
| `high-contrast` | modifier | A11y boost — stronger borders, harder text, pure canvas |
|
|
112
200
|
|
|
113
201
|
### Apply a preset
|
|
114
202
|
|
|
@@ -258,7 +346,12 @@ Now any of these work:
|
|
|
258
346
|
|
|
259
347
|
## Theme Showcase story
|
|
260
348
|
|
|
261
|
-
The `UI Core/Theme Showcase` story in djangocfg storybook renders every base
|
|
349
|
+
The `UI Core/Theme Showcase` story in djangocfg storybook renders every base
|
|
350
|
+
token (incl. `divider`), the real status surfaces (`*-background`/`*-foreground`/
|
|
351
|
+
`*-border`), the chart palette (solid + /40 opacity), the typography scale,
|
|
352
|
+
button variants, cards, form controls, glass utilities, and an opacity
|
|
353
|
+
sanity-check on one page. Switch the `preset` control to flip across all 8
|
|
354
|
+
themes; flip light/dark from the toolbar.
|
|
262
355
|
|
|
263
356
|
Use it to validate any token change before publishing — every regression shows up on one screen.
|
|
264
357
|
|
package/src/styles/base.css
CHANGED
|
@@ -8,8 +8,15 @@
|
|
|
8
8
|
:root {
|
|
9
9
|
--radius: 0.625rem;
|
|
10
10
|
--radius-sm: 0.375rem;
|
|
11
|
+
/* Control corner — shared by interactive controls (nav items, inputs,
|
|
12
|
+
* search, value chips) so they round consistently. ~10px reads like
|
|
13
|
+
* Claude/macOS. Override per preset for a tighter/softer feel. */
|
|
14
|
+
--radius-control: 0.625rem;
|
|
11
15
|
--radius-popover: 0.75rem;
|
|
12
|
-
|
|
16
|
+
/* Dialog corner — tightened from 1.5rem to read like Claude/macOS panels
|
|
17
|
+
* (round, but not pill-soft). Default preset only; preset themes that set
|
|
18
|
+
* their own `radius` are unaffected. */
|
|
19
|
+
--radius-dialog: 1rem;
|
|
13
20
|
--radius-tooltip: 0.375rem;
|
|
14
21
|
|
|
15
22
|
/* Typography tokens — overridable per preset */
|
|
@@ -29,6 +36,16 @@
|
|
|
29
36
|
border-color: var(--border);
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
/* Suppress the native (often white/auto) focus outline app-wide — every
|
|
40
|
+
* interactive ui-core component provides its own `focus-visible:ring-*` in the
|
|
41
|
+
* blue `--ring` token. Without this, clicking certain inputs (where the browser
|
|
42
|
+
* applies `:focus` but not `:focus-visible`) shows an ugly white outline ON TOP
|
|
43
|
+
* of our ring. Keyboard accessibility is preserved via the ring utilities.
|
|
44
|
+
*/
|
|
45
|
+
:where(input, textarea, select, button, a, [tabindex], [role="button"], [contenteditable]):focus {
|
|
46
|
+
outline: none;
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
html {
|
|
33
50
|
font-weight: 400;
|
|
34
51
|
}
|
|
@@ -1,3 +1,12 @@
|
|
|
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
|
+
*/
|
|
1
10
|
import type { ThemePreset } from './types';
|
|
2
11
|
|
|
3
12
|
export const densePreset: ThemePreset = {
|
|
@@ -5,6 +14,7 @@ export const densePreset: ThemePreset = {
|
|
|
5
14
|
radius: '0.25rem',
|
|
6
15
|
border: 'hsl(0 0% 82%)',
|
|
7
16
|
input: 'hsl(0 0% 82%)',
|
|
17
|
+
divider: 'hsl(0 0% 88%)',
|
|
8
18
|
muted: 'hsl(0 0% 94%)',
|
|
9
19
|
card: 'hsl(0 0% 100%)',
|
|
10
20
|
accent: 'hsl(0 0% 93%)',
|
|
@@ -13,6 +23,7 @@ export const densePreset: ThemePreset = {
|
|
|
13
23
|
radius: '0.25rem',
|
|
14
24
|
border: 'hsl(0 0% 24%)',
|
|
15
25
|
input: 'hsl(0 0% 24%)',
|
|
26
|
+
divider: 'hsl(0 0% 18%)',
|
|
16
27
|
muted: 'hsl(0 0% 12%)',
|
|
17
28
|
card: 'hsl(0 0% 10%)',
|
|
18
29
|
accent: 'hsl(0 0% 14%)',
|
|
@@ -1,21 +1,62 @@
|
|
|
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
|
+
*/
|
|
1
15
|
import type { ThemePreset } from './types';
|
|
2
16
|
|
|
3
17
|
export const djangoCfgPreset: ThemePreset = {
|
|
4
18
|
light: {
|
|
5
19
|
primary: 'hsl(192 90% 35%)',
|
|
6
20
|
'primary-foreground': 'hsl(0 0% 100%)',
|
|
7
|
-
|
|
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%)',
|
|
8
25
|
'sidebar-primary': 'hsl(192 90% 35%)',
|
|
9
|
-
'sidebar-
|
|
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%)',
|
|
10
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%)',
|
|
11
40
|
},
|
|
12
41
|
dark: {
|
|
13
42
|
primary: 'hsl(189 100% 50%)',
|
|
14
43
|
'primary-foreground': 'hsl(0 0% 9%)',
|
|
44
|
+
accent: 'hsl(189 40% 18%)',
|
|
45
|
+
'accent-foreground': 'hsl(189 100% 78%)',
|
|
15
46
|
ring: 'hsl(189 100% 50%)',
|
|
16
47
|
'sidebar-primary': 'hsl(189 100% 50%)',
|
|
17
48
|
'sidebar-primary-foreground': 'hsl(0 0% 9%)',
|
|
49
|
+
'sidebar-accent': 'hsl(189 35% 16%)',
|
|
50
|
+
'sidebar-accent-foreground': 'hsl(189 100% 78%)',
|
|
18
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%)',
|
|
19
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%)',
|
|
20
61
|
},
|
|
21
62
|
};
|
|
@@ -1,19 +1,35 @@
|
|
|
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
|
+
*/
|
|
1
10
|
import type { ThemePreset } from './types';
|
|
2
11
|
|
|
3
12
|
export const highContrastPreset: ThemePreset = {
|
|
4
13
|
light: {
|
|
5
|
-
|
|
6
|
-
input: 'hsl(0 0% 72%)',
|
|
7
|
-
'muted-foreground': 'hsl(0 0% 32%)',
|
|
14
|
+
background: 'hsl(0 0% 100%)',
|
|
8
15
|
foreground: 'hsl(0 0% 6%)',
|
|
9
|
-
|
|
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%)',
|
|
10
23
|
},
|
|
11
24
|
dark: {
|
|
12
|
-
|
|
13
|
-
input: 'hsl(0 0% 38%)',
|
|
14
|
-
'muted-foreground': 'hsl(0 0% 78%)',
|
|
25
|
+
background: 'hsl(0 0% 4%)',
|
|
15
26
|
foreground: 'hsl(0 0% 100%)',
|
|
16
|
-
|
|
17
|
-
|
|
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%)',
|
|
18
34
|
},
|
|
19
35
|
};
|
|
@@ -21,6 +21,7 @@ export const iosPreset: ThemePreset = {
|
|
|
21
21
|
border: 'hsl(220 9% 88%)',
|
|
22
22
|
input: 'hsl(220 9% 88%)',
|
|
23
23
|
ring: 'hsl(211 100% 50%)',
|
|
24
|
+
divider: 'hsl(220 9% 84%)', // hairline, a touch lighter than border
|
|
24
25
|
radius: '0.75rem',
|
|
25
26
|
'sidebar-background': 'hsl(0 0% 99%)',
|
|
26
27
|
'sidebar-foreground': 'hsl(220 9% 12%)',
|
|
@@ -35,6 +36,21 @@ export const iosPreset: ThemePreset = {
|
|
|
35
36
|
'chart-3': 'hsl(262 83% 58%)',
|
|
36
37
|
'chart-4': 'hsl(35 100% 50%)',
|
|
37
38
|
'chart-5': 'hsl(346 77% 50%)',
|
|
39
|
+
// Status surfaces — iOS system colors
|
|
40
|
+
warning: 'hsl(35 100% 50%)',
|
|
41
|
+
'warning-background': 'hsl(35 100% 95%)',
|
|
42
|
+
'warning-foreground': 'hsl(28 80% 30%)',
|
|
43
|
+
'warning-border': 'hsl(35 90% 78%)',
|
|
44
|
+
success: 'hsl(145 63% 42%)',
|
|
45
|
+
'success-background': 'hsl(145 60% 95%)',
|
|
46
|
+
'success-foreground': 'hsl(145 60% 24%)',
|
|
47
|
+
'success-border': 'hsl(145 55% 76%)',
|
|
48
|
+
'destructive-background': 'hsl(0 100% 96%)',
|
|
49
|
+
'destructive-border': 'hsl(0 90% 80%)',
|
|
50
|
+
info: 'hsl(211 100% 50%)',
|
|
51
|
+
'info-background': 'hsl(211 100% 96%)',
|
|
52
|
+
'info-foreground': 'hsl(211 80% 30%)',
|
|
53
|
+
'info-border': 'hsl(211 90% 80%)',
|
|
38
54
|
},
|
|
39
55
|
dark: {
|
|
40
56
|
background: 'hsl(240 6% 10%)',
|
|
@@ -56,6 +72,7 @@ export const iosPreset: ThemePreset = {
|
|
|
56
72
|
border: 'hsl(240 5% 22%)',
|
|
57
73
|
input: 'hsl(240 5% 22%)',
|
|
58
74
|
ring: 'hsl(211 100% 55%)',
|
|
75
|
+
divider: 'hsl(240 5% 18%)', // hairline, softer than border
|
|
59
76
|
radius: '0.75rem',
|
|
60
77
|
'sidebar-background': 'hsl(240 6% 8%)',
|
|
61
78
|
'sidebar-foreground': 'hsl(0 0% 96%)',
|
|
@@ -70,5 +87,20 @@ export const iosPreset: ThemePreset = {
|
|
|
70
87
|
'chart-3': 'hsl(262 83% 65%)',
|
|
71
88
|
'chart-4': 'hsl(35 100% 58%)',
|
|
72
89
|
'chart-5': 'hsl(346 77% 58%)',
|
|
90
|
+
// Status surfaces (dark)
|
|
91
|
+
warning: 'hsl(35 100% 58%)',
|
|
92
|
+
'warning-background': 'hsl(35 90% 10%)',
|
|
93
|
+
'warning-foreground': 'hsl(35 100% 72%)',
|
|
94
|
+
'warning-border': 'hsl(35 80% 28%)',
|
|
95
|
+
success: 'hsl(145 65% 52%)',
|
|
96
|
+
'success-background': 'hsl(145 60% 9%)',
|
|
97
|
+
'success-foreground': 'hsl(145 60% 70%)',
|
|
98
|
+
'success-border': 'hsl(145 50% 26%)',
|
|
99
|
+
'destructive-background': 'hsl(0 80% 11%)',
|
|
100
|
+
'destructive-border': 'hsl(0 70% 30%)',
|
|
101
|
+
info: 'hsl(211 100% 55%)',
|
|
102
|
+
'info-background': 'hsl(211 90% 11%)',
|
|
103
|
+
'info-foreground': 'hsl(211 100% 74%)',
|
|
104
|
+
'info-border': 'hsl(211 80% 30%)',
|
|
73
105
|
},
|
|
74
106
|
};
|
|
@@ -56,6 +56,8 @@ export const macosPreset: ThemePreset = {
|
|
|
56
56
|
// Kept intentionally light — Apple hairline, not a thick rule
|
|
57
57
|
border: 'hsl(240 3% 78%)',
|
|
58
58
|
input: 'hsl(240 8% 93%)',
|
|
59
|
+
// Row hairline — a touch lighter than border so it reads on white cards
|
|
60
|
+
divider: 'hsl(240 4% 85%)',
|
|
59
61
|
ring: 'hsl(211 100% 50%)',
|
|
60
62
|
radius: '0.625rem',
|
|
61
63
|
// Sidebar: slighly darker than page, matches macOS sidebar material
|
|
@@ -73,6 +75,22 @@ export const macosPreset: ThemePreset = {
|
|
|
73
75
|
'chart-3': 'hsl(262 60% 56%)',
|
|
74
76
|
'chart-4': 'hsl(35 100% 48%)',
|
|
75
77
|
'chart-5': 'hsl(2 100% 59%)',
|
|
78
|
+
// Status surfaces — Apple system colors (systemOrange/Green/Red/Blue) with
|
|
79
|
+
// faint tinted banner fills that sit on the #F2F2F7 grouped canvas.
|
|
80
|
+
warning: 'hsl(35 100% 48%)', // systemOrange #FF9500
|
|
81
|
+
'warning-background': 'hsl(35 100% 95%)',
|
|
82
|
+
'warning-foreground': 'hsl(28 80% 30%)',
|
|
83
|
+
'warning-border': 'hsl(35 90% 78%)',
|
|
84
|
+
success: 'hsl(142 71% 45%)', // systemGreen #34C759
|
|
85
|
+
'success-background': 'hsl(142 60% 95%)',
|
|
86
|
+
'success-foreground': 'hsl(142 60% 24%)',
|
|
87
|
+
'success-border': 'hsl(142 55% 76%)',
|
|
88
|
+
'destructive-background': 'hsl(2 100% 96%)',
|
|
89
|
+
'destructive-border': 'hsl(2 90% 80%)',
|
|
90
|
+
info: 'hsl(211 100% 50%)', // systemBlue #007AFF
|
|
91
|
+
'info-background': 'hsl(211 100% 96%)',
|
|
92
|
+
'info-foreground': 'hsl(211 80% 30%)',
|
|
93
|
+
'info-border': 'hsl(211 90% 80%)',
|
|
76
94
|
...appleTypography,
|
|
77
95
|
},
|
|
78
96
|
dark: {
|
|
@@ -103,6 +121,8 @@ export const macosPreset: ThemePreset = {
|
|
|
103
121
|
// Separator dark: rgba(84,84,88,0.36) — ultra-thin, almost invisible
|
|
104
122
|
border: 'hsl(240 3% 22%)',
|
|
105
123
|
input: 'hsl(240 3% 19%)',
|
|
124
|
+
// Row hairline — softer than border so dark rows don't read as a hard rule
|
|
125
|
+
divider: 'hsl(240 3% 18%)',
|
|
106
126
|
ring: 'hsl(211 100% 58%)',
|
|
107
127
|
radius: '0.625rem',
|
|
108
128
|
// Sidebar: near-black floor — #0D0D0F
|
|
@@ -120,6 +140,22 @@ export const macosPreset: ThemePreset = {
|
|
|
120
140
|
'chart-3': 'hsl(262 60% 65%)',
|
|
121
141
|
'chart-4': 'hsl(35 100% 56%)',
|
|
122
142
|
'chart-5': 'hsl(3 100% 62%)',
|
|
143
|
+
// Status surfaces (dark) — Apple dark system colors, dim banner fills on the
|
|
144
|
+
// #141414 canvas with raised foregrounds for contrast.
|
|
145
|
+
warning: 'hsl(35 100% 56%)', // systemOrange dark #FF9F0A
|
|
146
|
+
'warning-background': 'hsl(35 70% 10%)',
|
|
147
|
+
'warning-foreground': 'hsl(35 95% 70%)',
|
|
148
|
+
'warning-border': 'hsl(35 50% 26%)',
|
|
149
|
+
success: 'hsl(142 60% 50%)', // systemGreen dark #30D158
|
|
150
|
+
'success-background': 'hsl(142 50% 9%)',
|
|
151
|
+
'success-foreground': 'hsl(142 65% 68%)',
|
|
152
|
+
'success-border': 'hsl(142 40% 24%)',
|
|
153
|
+
'destructive-background': 'hsl(3 70% 11%)',
|
|
154
|
+
'destructive-border': 'hsl(3 55% 30%)',
|
|
155
|
+
info: 'hsl(211 100% 58%)', // systemBlue dark #0A84FF
|
|
156
|
+
'info-background': 'hsl(211 70% 11%)',
|
|
157
|
+
'info-foreground': 'hsl(211 90% 72%)',
|
|
158
|
+
'info-border': 'hsl(211 50% 30%)',
|
|
123
159
|
...appleTypography,
|
|
124
160
|
},
|
|
125
161
|
};
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* soft — density/feel modifier (NOT a full color theme).
|
|
3
|
+
*
|
|
4
|
+
* Larger radius (1rem) + gently lifted neutral surfaces for friendly marketing
|
|
5
|
+
* UIs. It deliberately only overrides the neutral chrome (background/card/muted/
|
|
6
|
+
* accent/border/input/divider/radius + sidebar surfaces) and inherits brand
|
|
7
|
+
* colors (primary/ring/charts/status) from whatever base or brand preset is
|
|
8
|
+
* active, so `soft` composes with `django-cfg` etc. Light and dark are kept
|
|
9
|
+
* symmetric — every key set in one mode is set in the other.
|
|
10
|
+
*/
|
|
1
11
|
import type { ThemePreset } from './types';
|
|
2
12
|
|
|
3
13
|
export const softPreset: ThemePreset = {
|
|
@@ -12,7 +22,9 @@ export const softPreset: ThemePreset = {
|
|
|
12
22
|
'accent-foreground': 'hsl(240 6% 10%)',
|
|
13
23
|
border: 'hsl(240 5% 91%)',
|
|
14
24
|
input: 'hsl(240 5% 91%)',
|
|
25
|
+
divider: 'hsl(240 5% 93%)',
|
|
15
26
|
radius: '1rem',
|
|
27
|
+
'sidebar-background': 'hsl(0 0% 99%)',
|
|
16
28
|
'sidebar-accent': 'hsl(240 5% 94%)',
|
|
17
29
|
'sidebar-border': 'hsl(240 5% 91%)',
|
|
18
30
|
},
|
|
@@ -27,6 +39,7 @@ export const softPreset: ThemePreset = {
|
|
|
27
39
|
'accent-foreground': 'hsl(0 0% 96%)',
|
|
28
40
|
border: 'hsl(240 5% 20%)',
|
|
29
41
|
input: 'hsl(240 5% 20%)',
|
|
42
|
+
divider: 'hsl(240 5% 16%)',
|
|
30
43
|
radius: '1rem',
|
|
31
44
|
'sidebar-background': 'hsl(240 6% 6%)',
|
|
32
45
|
'sidebar-accent': 'hsl(240 5% 14%)',
|
|
@@ -44,6 +44,8 @@ export const windowsPreset: ThemePreset = {
|
|
|
44
44
|
// ControlStrokeColorDefault: rgba(0,0,0,0.0578)
|
|
45
45
|
border: 'hsl(0 0% 87%)',
|
|
46
46
|
input: 'hsl(0 0% 90%)',
|
|
47
|
+
// Hairline between rows — slightly lighter than border
|
|
48
|
+
divider: 'hsl(0 0% 84%)',
|
|
47
49
|
ring: 'hsl(210 100% 45%)',
|
|
48
50
|
// WinUI 3: 4px controls, 8px cards/dialogs
|
|
49
51
|
radius: '0.375rem',
|
|
@@ -60,6 +62,21 @@ export const windowsPreset: ThemePreset = {
|
|
|
60
62
|
'chart-3': 'hsl(262 83% 55%)',
|
|
61
63
|
'chart-4': 'hsl(35 100% 48%)',
|
|
62
64
|
'chart-5': 'hsl(346 77% 50%)',
|
|
65
|
+
// Status surfaces — Fluent system colors
|
|
66
|
+
warning: 'hsl(38 92% 50%)',
|
|
67
|
+
'warning-background': 'hsl(38 100% 95%)',
|
|
68
|
+
'warning-foreground': 'hsl(30 80% 28%)',
|
|
69
|
+
'warning-border': 'hsl(38 85% 78%)',
|
|
70
|
+
success: 'hsl(120 78% 27%)',
|
|
71
|
+
'success-background': 'hsl(120 55% 95%)',
|
|
72
|
+
'success-foreground': 'hsl(120 60% 28%)',
|
|
73
|
+
'success-border': 'hsl(120 45% 78%)',
|
|
74
|
+
'destructive-background': 'hsl(0 90% 96%)',
|
|
75
|
+
'destructive-border': 'hsl(0 85% 78%)',
|
|
76
|
+
info: 'hsl(210 100% 38%)',
|
|
77
|
+
'info-background': 'hsl(210 100% 95%)',
|
|
78
|
+
'info-foreground': 'hsl(210 90% 28%)',
|
|
79
|
+
'info-border': 'hsl(210 80% 78%)',
|
|
63
80
|
...fluentTypography,
|
|
64
81
|
},
|
|
65
82
|
dark: {
|
|
@@ -86,6 +103,8 @@ export const windowsPreset: ThemePreset = {
|
|
|
86
103
|
// ControlStrokeColorDefault dark: rgba(255,255,255,0.0837)
|
|
87
104
|
border: 'hsl(0 0% 28%)',
|
|
88
105
|
input: 'hsl(0 0% 24%)',
|
|
106
|
+
// Hairline between rows — softer than border
|
|
107
|
+
divider: 'hsl(0 0% 22%)',
|
|
89
108
|
ring: 'hsl(200 100% 69%)',
|
|
90
109
|
radius: '0.375rem',
|
|
91
110
|
// NavigationView pane background
|
|
@@ -102,6 +121,21 @@ export const windowsPreset: ThemePreset = {
|
|
|
102
121
|
'chart-3': 'hsl(262 83% 65%)',
|
|
103
122
|
'chart-4': 'hsl(35 100% 58%)',
|
|
104
123
|
'chart-5': 'hsl(346 77% 58%)',
|
|
124
|
+
// Status surfaces — Fluent system colors
|
|
125
|
+
warning: 'hsl(38 95% 58%)',
|
|
126
|
+
'warning-background': 'hsl(38 60% 10%)',
|
|
127
|
+
'warning-foreground': 'hsl(38 90% 72%)',
|
|
128
|
+
'warning-border': 'hsl(38 50% 28%)',
|
|
129
|
+
success: 'hsl(120 60% 55%)',
|
|
130
|
+
'success-background': 'hsl(120 40% 10%)',
|
|
131
|
+
'success-foreground': 'hsl(120 55% 72%)',
|
|
132
|
+
'success-border': 'hsl(120 35% 28%)',
|
|
133
|
+
'destructive-background': 'hsl(0 60% 11%)',
|
|
134
|
+
'destructive-border': 'hsl(0 50% 30%)',
|
|
135
|
+
info: 'hsl(200 100% 69%)',
|
|
136
|
+
'info-background': 'hsl(200 60% 11%)',
|
|
137
|
+
'info-foreground': 'hsl(200 90% 74%)',
|
|
138
|
+
'info-border': 'hsl(200 50% 30%)',
|
|
105
139
|
...fluentTypography,
|
|
106
140
|
},
|
|
107
141
|
};
|
|
@@ -31,8 +31,35 @@ export type ThemeCssVarColorKey =
|
|
|
31
31
|
| 'destructive'
|
|
32
32
|
| 'destructive-foreground';
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
|
|
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
|
+
*/
|
|
39
|
+
export type ThemeCssVarChromeKey = 'border' | 'input' | 'divider' | 'ring' | 'radius';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Status surfaces — each role has a 4-token set (icon/accent, banner background,
|
|
43
|
+
* readable foreground, border ring) used by banners/alerts. Presets with a
|
|
44
|
+
* custom `background` should re-tint these so banners don't clash with the
|
|
45
|
+
* canvas; presets that keep the default canvas can omit them. All are
|
|
46
|
+
* **fully-wrapped CSS colors** (same rule as `ThemeCssVarColorKey`).
|
|
47
|
+
*/
|
|
48
|
+
export type ThemeCssVarStatusKey =
|
|
49
|
+
| 'warning'
|
|
50
|
+
| 'warning-background'
|
|
51
|
+
| 'warning-foreground'
|
|
52
|
+
| 'warning-border'
|
|
53
|
+
| 'success'
|
|
54
|
+
| 'success-background'
|
|
55
|
+
| 'success-foreground'
|
|
56
|
+
| 'success-border'
|
|
57
|
+
| 'destructive-background'
|
|
58
|
+
| 'destructive-border'
|
|
59
|
+
| 'info'
|
|
60
|
+
| 'info-background'
|
|
61
|
+
| 'info-foreground'
|
|
62
|
+
| 'info-border';
|
|
36
63
|
|
|
37
64
|
/**
|
|
38
65
|
* Typography tokens — override system font stack and scale per preset.
|
|
@@ -59,12 +86,19 @@ export type ThemeCssVarSidebarKey =
|
|
|
59
86
|
| 'sidebar-border'
|
|
60
87
|
| 'sidebar-ring';
|
|
61
88
|
|
|
89
|
+
/**
|
|
90
|
+
* Categorical chart colors. **Fully-wrapped CSS colors** (`hsl(...)`) — same
|
|
91
|
+
* rule as every color key. Consume via the Tailwind utility (`bg-chart-1`,
|
|
92
|
+
* backed by `--color-chart-N` in tokens.css) or `var(--chart-N)`; never wrap as
|
|
93
|
+
* `hsl(var(--chart-N))` (that double-wraps to `hsl(hsl(...))` and is invalid).
|
|
94
|
+
*/
|
|
62
95
|
export type ThemeCssVarChartKey = 'chart-1' | 'chart-2' | 'chart-3' | 'chart-4' | 'chart-5';
|
|
63
96
|
|
|
64
97
|
/** All keys that map to `--${key}` in ui-core CSS files */
|
|
65
98
|
export type ThemeCssVarKey =
|
|
66
99
|
| ThemeCssVarColorKey
|
|
67
100
|
| ThemeCssVarChromeKey
|
|
101
|
+
| ThemeCssVarStatusKey
|
|
68
102
|
| ThemeCssVarTypographyKey
|
|
69
103
|
| ThemeCssVarSidebarKey
|
|
70
104
|
| ThemeCssVarChartKey;
|