@djangocfg/ui-core 2.1.427 → 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/feedback/banner/index.tsx +4 -1
- package/src/components/forms/setting-row/index.tsx +5 -1
- package/src/hooks/router/README.md +4 -1
- package/src/lib/env.ts +6 -1
- package/src/styles/README.md +66 -12
- 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 +8 -6
- package/src/styles/theme/light.css +8 -6
- package/src/styles/theme/tokens.css +22 -0
package/README.md
CHANGED
|
@@ -16,7 +16,10 @@ Framework-agnostic React UI library: 70+ shadcn/Radix components on Tailwind v4,
|
|
|
16
16
|
pnpm add @djangocfg/ui-core
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
Next.js apps
|
|
19
|
+
Works in any React host. Next.js apps import components from here directly and
|
|
20
|
+
add the Next router adapter from `@djangocfg/ui-core/adapters/nextjs` (see
|
|
21
|
+
**Router adapters** below); Next-specific *server* utilities (sitemap, health,
|
|
22
|
+
OG images, config) live in the separate [`@djangocfg/nextjs`](../nextjs) package.
|
|
20
23
|
|
|
21
24
|
Import styles once at the app root — use the golden path, which pins everything
|
|
22
25
|
to the right cascade layers so import order can't break layout utilities:
|
|
@@ -92,8 +95,7 @@ The router-aware components (`Sidebar`, `Link`, `SSRPagination`) read the active
|
|
|
92
95
|
|
|
93
96
|
| Adapter | Source |
|
|
94
97
|
|---|---|
|
|
95
|
-
| Next.js App Router | `@djangocfg/ui-nextjs`
|
|
96
|
-
| Vite / SPA (`react-router`) | `@djangocfg/ui-core/adapters/react-router` |
|
|
98
|
+
| Next.js App Router | `@djangocfg/ui-core/adapters/nextjs` |
|
|
97
99
|
| Plain `<a>` fallback | default (no adapter) |
|
|
98
100
|
|
|
99
101
|
## Theming (`/styles`)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.428",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -106,7 +106,7 @@
|
|
|
106
106
|
"check": "tsc --noEmit"
|
|
107
107
|
},
|
|
108
108
|
"peerDependencies": {
|
|
109
|
-
"@djangocfg/i18n": "^2.1.
|
|
109
|
+
"@djangocfg/i18n": "^2.1.428",
|
|
110
110
|
"consola": "^3.4.2",
|
|
111
111
|
"lucide-react": "^0.545.0",
|
|
112
112
|
"moment": "^2.30.1",
|
|
@@ -180,8 +180,8 @@
|
|
|
180
180
|
"@chenglou/pretext": "*"
|
|
181
181
|
},
|
|
182
182
|
"devDependencies": {
|
|
183
|
-
"@djangocfg/i18n": "^2.1.
|
|
184
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
183
|
+
"@djangocfg/i18n": "^2.1.428",
|
|
184
|
+
"@djangocfg/typescript-config": "^2.1.428",
|
|
185
185
|
"@types/node": "^25.2.3",
|
|
186
186
|
"@types/react": "^19.2.15",
|
|
187
187
|
"@types/react-dom": "^19.2.3",
|
|
@@ -311,8 +311,11 @@ const bannerVariants = cva(
|
|
|
311
311
|
"bg-success-background text-success-foreground border-success-border",
|
|
312
312
|
warning:
|
|
313
313
|
"bg-warning-background text-warning-foreground border-warning-border",
|
|
314
|
+
// `--destructive-foreground` is white (text on a filled red button), so
|
|
315
|
+
// use the red itself as the surface text — readable on the pale red
|
|
316
|
+
// banner bg, consistent with how Alert tints status text.
|
|
314
317
|
destructive:
|
|
315
|
-
"bg-destructive-background text-destructive
|
|
318
|
+
"bg-destructive-background text-destructive border-destructive-border",
|
|
316
319
|
},
|
|
317
320
|
},
|
|
318
321
|
defaultVariants: {
|
|
@@ -115,7 +115,11 @@ export const SettingRow = React.forwardRef<HTMLDivElement, SettingRowProps>(
|
|
|
115
115
|
<div className={cn('min-w-0', stacked ? 'w-full' : 'flex-1')}>
|
|
116
116
|
<div className="text-sm text-foreground">{label}</div>
|
|
117
117
|
{description && (
|
|
118
|
-
|
|
118
|
+
// `div`, not `p`: `description` is arbitrary ReactNode and consumers
|
|
119
|
+
// legitimately embed block-level nodes (e.g. a `<Badge>` status pill,
|
|
120
|
+
// which renders a `<div>`) — a `<p>` cannot contain a `<div>` and
|
|
121
|
+
// triggers an invalid-nesting hydration error.
|
|
122
|
+
<div className="mt-0.5 text-[13px] leading-relaxed text-muted-foreground">{description}</div>
|
|
119
123
|
)}
|
|
120
124
|
</div>
|
|
121
125
|
);
|
|
@@ -38,12 +38,15 @@ import { NextRouterAdapter, NextLinkProvider } from '@djangocfg/ui-core/adapters
|
|
|
38
38
|
<I18nProvider locale={locale} messages={messages}>
|
|
39
39
|
<NextRouterAdapter>
|
|
40
40
|
<NextLinkProvider>
|
|
41
|
-
|
|
41
|
+
{children}
|
|
42
42
|
</NextLinkProvider>
|
|
43
43
|
</NextRouterAdapter>
|
|
44
44
|
</I18nProvider>
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
> Using `@djangocfg/layouts`? `BaseApp` already mounts both adapters — you don't
|
|
48
|
+
> need this manual wiring.
|
|
49
|
+
|
|
47
50
|
`next` is an **optional peer dependency** — the package never imports from `next/*` from the main entry. The Next adapter lives behind the `/adapters/nextjs` sub-path entry, so non-Next consumers don't pull `next` into their bundle.
|
|
48
51
|
|
|
49
52
|
For other routers (TanStack Router, wouter, Remix, custom transports) — write a ~20-line custom adapter:
|
package/src/lib/env.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Pure environment constants — shared across all @djangocfg UI packages.
|
|
3
|
+
*
|
|
4
|
+
* Keep this to framework-agnostic env primitives only. Feature flags that carry
|
|
5
|
+
* domain meaning (e.g. DPoP auth, static-build behaviour) live in the package
|
|
6
|
+
* that owns them — `dpopEnabled` / `isStaticBuild` are exported from
|
|
7
|
+
* `@djangocfg/api` (the lowest shared package, which implements DPoP).
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
export const nodeEnv = process.env.NODE_ENV || 'development';
|
package/src/styles/README.md
CHANGED
|
@@ -84,7 +84,12 @@ Available statuses: **`warning`** · **`success`** · **`destructive`** · **`in
|
|
|
84
84
|
</div>
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
-
|
|
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:
|
|
88
93
|
|
|
89
94
|
```tsx
|
|
90
95
|
<div className="rounded-md border border-warning/30 bg-warning/10 text-warning">…</div>
|
|
@@ -99,6 +104,41 @@ Most presets don't override `*-background` / `*-border` / `*-foreground`. For pr
|
|
|
99
104
|
| `bg-sidebar` / `text-sidebar-foreground` / `border-sidebar-border` | App sidebar chrome |
|
|
100
105
|
| `bg-sidebar-accent` / `text-sidebar-accent-foreground` | Sidebar hover state |
|
|
101
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
|
+
|
|
102
142
|
## Radius tokens
|
|
103
143
|
|
|
104
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):
|
|
@@ -138,16 +178,25 @@ Some classes are authored as **plain CSS** in `utilities/*.css` (not Tailwind ut
|
|
|
138
178
|
|
|
139
179
|
## Theme presets — 8 production-ready
|
|
140
180
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
|
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 |
|
|
151
200
|
|
|
152
201
|
### Apply a preset
|
|
153
202
|
|
|
@@ -297,7 +346,12 @@ Now any of these work:
|
|
|
297
346
|
|
|
298
347
|
## Theme Showcase story
|
|
299
348
|
|
|
300
|
-
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.
|
|
301
355
|
|
|
302
356
|
Use it to validate any token change before publishing — every regression shows up on one screen.
|
|
303
357
|
|
|
@@ -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;
|
|
@@ -99,10 +99,12 @@
|
|
|
99
99
|
/* Surface gradient dark */
|
|
100
100
|
--surface-gradient: linear-gradient(to bottom, var(--background), color-mix(in oklab, var(--background) 80%, transparent));
|
|
101
101
|
|
|
102
|
-
/* Chart colors — chart-1 aligned with brand
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
--chart-
|
|
106
|
-
--chart-
|
|
107
|
-
--chart-
|
|
102
|
+
/* Chart colors — chart-1 aligned with brand.
|
|
103
|
+
* Fully-wrapped hsl() like every other token (consume via `bg-chart-N` or
|
|
104
|
+
* `var(--chart-N)`, never `hsl(var(--chart-N))`). */
|
|
105
|
+
--chart-1: hsl(189 100% 50%);
|
|
106
|
+
--chart-2: hsl(142 76% 36%);
|
|
107
|
+
--chart-3: hsl(262 83% 58%);
|
|
108
|
+
--chart-4: hsl(26 90% 57%);
|
|
109
|
+
--chart-5: hsl(346 77% 50%);
|
|
108
110
|
}
|
|
@@ -92,10 +92,12 @@
|
|
|
92
92
|
/* Surface gradient */
|
|
93
93
|
--surface-gradient: linear-gradient(to bottom, var(--background), color-mix(in oklab, var(--background) 80%, transparent));
|
|
94
94
|
|
|
95
|
-
/* Chart colors — chart-1 aligned with brand hue
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
--chart-
|
|
99
|
-
--chart-
|
|
100
|
-
--chart-
|
|
95
|
+
/* Chart colors — chart-1 aligned with brand hue.
|
|
96
|
+
* Fully-wrapped hsl() like every other token (consume via `bg-chart-N` or
|
|
97
|
+
* `var(--chart-N)`, never `hsl(var(--chart-N))`). */
|
|
98
|
+
--chart-1: hsl(192 90% 35%);
|
|
99
|
+
--chart-2: hsl(142 76% 36%);
|
|
100
|
+
--chart-3: hsl(262 83% 58%);
|
|
101
|
+
--chart-4: hsl(26 90% 57%);
|
|
102
|
+
--chart-5: hsl(346 77% 50%);
|
|
101
103
|
}
|
|
@@ -83,6 +83,28 @@
|
|
|
83
83
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
84
84
|
--color-sidebar-border: var(--sidebar-border);
|
|
85
85
|
--color-sidebar-ring: var(--sidebar-ring);
|
|
86
|
+
|
|
87
|
+
/* Charts — categorical series. `--chart-N` are fully-wrapped colors in
|
|
88
|
+
* light/dark/preset, so this is a plain reference (NOT `hsl(var(...))`).
|
|
89
|
+
* Backs the `bg-chart-1` / `text-chart-2` / `border-chart-3` utilities and
|
|
90
|
+
* their opacity modifiers (`bg-chart-1/40`). For Recharts/shadcn `color:`
|
|
91
|
+
* props pass `var(--chart-N)` directly. */
|
|
92
|
+
--color-chart-1: var(--chart-1);
|
|
93
|
+
--color-chart-2: var(--chart-2);
|
|
94
|
+
--color-chart-3: var(--chart-3);
|
|
95
|
+
--color-chart-4: var(--chart-4);
|
|
96
|
+
--color-chart-5: var(--chart-5);
|
|
97
|
+
|
|
98
|
+
/* Typography — `font-sans` / `font-mono` utilities read `--font-sans` /
|
|
99
|
+
* `--font-mono` directly (those live in base.css, retinted per preset), so
|
|
100
|
+
* no binding is needed for them. Tailwind's text scale reads `--text-*`,
|
|
101
|
+
* which we bridge to the preset-overridable `--font-size-*` tokens so
|
|
102
|
+
* `text-xs…text-xl` follow the active preset instead of Tailwind defaults. */
|
|
103
|
+
--text-xs: var(--font-size-xs);
|
|
104
|
+
--text-sm: var(--font-size-sm);
|
|
105
|
+
--text-base: var(--font-size-base);
|
|
106
|
+
--text-lg: var(--font-size-lg);
|
|
107
|
+
--text-xl: var(--font-size-xl);
|
|
86
108
|
}
|
|
87
109
|
|
|
88
110
|
@theme {
|