@djangocfg/ui-core 2.1.443 → 2.1.445

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 CHANGED
@@ -91,12 +91,78 @@ Imports stay flat — group folders are organisational.
91
91
 
92
92
  ## Router adapters
93
93
 
94
- The router-aware components (`Sidebar`, `Link`, `SSRPagination`) read the active router via `RouterAdapterProvider`. Ship the adapter that matches your host:
94
+ The router-aware primitives (`Sidebar`, `Link`, `SSRPagination`, and the
95
+ `useRouter` / `useNavigate` / `useLocation` hooks) navigate through a **pluggable
96
+ adapter**, so the same components work under any host. ui-core itself is
97
+ router-agnostic and has **zero router dependency** — pick the adapter for your
98
+ host and mount it once near the root.
95
99
 
96
- | Adapter | Source |
97
- |---|---|
98
- | Next.js App Router | `@djangocfg/ui-core/adapters/nextjs` |
99
- | Plain `<a>` fallback | default (no adapter) |
100
+ ### Which adapter for which host
101
+
102
+ | Host | Mount this | Extra dependency you install |
103
+ |---|---|---|
104
+ | **Next.js** (App Router) | `NextRouterAdapter` from `@djangocfg/ui-core/adapters/nextjs` | `next` (you already have it) |
105
+ | **Vite / React-Router SPA** | `ReactRouterProvider` from `@djangocfg/ui-core/adapters/react-router` | `react-router` **≥7** — add to your app's `dependencies` |
106
+ | **Wails / Electron / plain React / Storybook** | **nothing** — the default History-API adapter is used automatically | none (zero deps) |
107
+
108
+ `next` and `react-router` are **optional peers**: the base package never imports
109
+ them. Only the matching sub-path entry does, so the dep is resolved *only* when
110
+ you import that adapter. Never add both — import the one sub-path for your host.
111
+
112
+ ### Where to mount it
113
+
114
+ Both provider adapters wrap a subtree; every ui-core router call inside it flows
115
+ through your app's router. Mount **once**, near the root:
116
+
117
+ ```tsx
118
+ // Next.js — inside the client provider stack (below your i18n provider)
119
+ import { NextRouterAdapter } from '@djangocfg/ui-core/adapters/nextjs';
120
+ <NextRouterAdapter><AppLayout>{children}</AppLayout></NextRouterAdapter>
121
+
122
+ // Vite / React-Router — INSIDE the router context (an element RouterProvider
123
+ // renders, e.g. a layout route wrapping <Outlet/>), NOT around <RouterProvider>.
124
+ import { ReactRouterProvider } from '@djangocfg/ui-core/adapters/react-router';
125
+ function Shell() {
126
+ return <ReactRouterProvider><Layout><Outlet /></Layout></ReactRouterProvider>;
127
+ }
128
+ ```
129
+
130
+ > **React-Router gotcha:** mounting `ReactRouterProvider` *around*
131
+ > `<RouterProvider>` throws — that subtree has no router context, so
132
+ > `useNavigate` fails. Always mount it *inside*.
133
+
134
+ ### What happens if you skip it
135
+
136
+ Without an adapter, hooks fall back to the **default History-API adapter**
137
+ (`window.history.pushState` + `window.location`). This is intentional and works
138
+ everywhere with zero deps — but in a *framework* host it silently loses the
139
+ framework's routing behavior:
140
+
141
+ - **Next.js without `NextRouterAdapter`:** no RSC refetch on navigation, no route
142
+ loader, no prefetch. Links still change the URL, but the App Router doesn't
143
+ react. → always mount it in Next apps.
144
+ - **React-Router without `ReactRouterProvider`:** programmatic `useNavigate` /
145
+ `<Link>` mutate `window.history` directly and bypass the `<Outlet/>` swap, so
146
+ navigation desyncs from the data router. → always mount it in RR SPAs.
147
+
148
+ For a plain SPA (Wails/Electron/CRA) the History-API default *is* the right
149
+ backend — mount nothing.
150
+
151
+ ### Link adapters (optional, Next.js)
152
+
153
+ By default `<Link>` from `@djangocfg/ui-core/components` renders a plain `<a>`.
154
+ To route Next's prefetch / RSC handling through it, also mount `NextLinkProvider`
155
+ alongside `NextRouterAdapter`. For locale-prefixed hrefs with `next-intl`, pass
156
+ `createNextIntlLinkAdapter(IntlLink)` as `AppLayout`'s `linkAdapter` — both live
157
+ in `@djangocfg/ui-core/adapters/nextjs`.
158
+
159
+ > **Deeper dive:** full adapter reference, the `useRouter` / `useNavigate` /
160
+ > `useLocation` / `useQueryState` hook surface, and a ~20-line recipe for
161
+ > **custom** routers (TanStack Router, wouter, Remix, custom transports) live in
162
+ > [`src/hooks/router/README.md`](./src/hooks/router/README.md).
163
+
164
+ > Using `@djangocfg/layouts`? `BaseApp` already mounts the Next adapters for you
165
+ > — you don't wire them by hand.
100
166
 
101
167
  ## Theming (`/styles`)
102
168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/ui-core",
3
- "version": "2.1.443",
3
+ "version": "2.1.445",
4
4
  "description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
5
5
  "keywords": [
6
6
  "ui-components",
@@ -116,7 +116,7 @@
116
116
  "check": "tsc --noEmit"
117
117
  },
118
118
  "peerDependencies": {
119
- "@djangocfg/i18n": "^2.1.443",
119
+ "@djangocfg/i18n": "^2.1.445",
120
120
  "consola": "^3.4.2",
121
121
  "lucide-react": "^0.545.0",
122
122
  "moment": "^2.30.1",
@@ -194,8 +194,8 @@
194
194
  "@chenglou/pretext": "*"
195
195
  },
196
196
  "devDependencies": {
197
- "@djangocfg/i18n": "^2.1.443",
198
- "@djangocfg/typescript-config": "^2.1.443",
197
+ "@djangocfg/i18n": "^2.1.445",
198
+ "@djangocfg/typescript-config": "^2.1.445",
199
199
  "@types/node": "^25.2.3",
200
200
  "@types/react": "^19.2.15",
201
201
  "@types/react-dom": "^19.2.3",
@@ -136,7 +136,9 @@ const ResizableHandle = ({
136
136
  return (
137
137
  <div
138
138
  className={cn(
139
- "bg-border w-px",
139
+ // Separator between panels → `bg-divider` (soft hairline), not the
140
+ // opaque `bg-border` outline weight, so a resize seam stays quiet.
141
+ "bg-divider w-px",
140
142
  "data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full",
141
143
  className
142
144
  )}
@@ -147,7 +149,7 @@ const ResizableHandle = ({
147
149
  return (
148
150
  <ResizablePrimitive.PanelResizeHandle
149
151
  className={cn(
150
- "relative flex items-center justify-center bg-border",
152
+ "relative flex items-center justify-center bg-divider",
151
153
  "w-px h-full",
152
154
  "hover:bg-primary/20 active:bg-primary/30",
153
155
  "transition-colors",
@@ -68,8 +68,8 @@ This makes opacity modifiers (`bg-card/40`, `border-foreground/20`) resolve thro
68
68
  | `bg-primary` / `text-primary-foreground` | `--primary` | Brand CTA (filled buttons, links) — cyan |
69
69
  | `bg-secondary` / `text-secondary-foreground` | `--secondary` | Neutral filled controls |
70
70
  | `bg-destructive` / `text-destructive-foreground` | `--destructive` | Error / delete filled controls |
71
- | `border-border` | `--border` | Card outlines, control borders |
72
- | `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) |
71
+ | `border-border` | `--border` | **Outlines only** — card frames, control/input edges. *Not* for separators (see Presets § Border vs divider) |
72
+ | `border-divider` / `.divider-b` / `bg-divider` | `--divider` | **All separators** — columns, header `border-b`, list rows, resize handles. A soft hairline (translucent on dark themes) so the chrome never reads as a heavy grid |
73
73
  | `bg-overlay` | `--overlay` | Modal scrim / backdrop behind dialogs, drawers, sheets — black scrim in both themes, the token owns the opacity |
74
74
  | `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` |
75
75
  | `ring-ring` | `--ring` | Focus rings, selected outlines — **blue** (system-accent feel), independent of the cyan brand |
@@ -299,6 +299,35 @@ Two families, with different coverage by design:
299
299
  | `dense` | modifier | Smaller radius (0.25rem) — data-heavy admin UIs |
300
300
  | `high-contrast` | modifier | A11y boost — stronger borders, harder text, pure canvas |
301
301
 
302
+ #### Border vs divider — outline weight vs translucent hairline
303
+
304
+ `--border` and `--divider` are **two different roles**, not two shades of one:
305
+
306
+ - **`--border`** — an OUTLINE: opaque, control-weight. Card frames, input
307
+ edges, panel outlines. Use `border-border` / `border border-border`.
308
+ - **`--divider`** — a SEPARATOR: a soft hairline between things. Shell columns,
309
+ header `border-b` rules, list rows, resize handles. Use `border-divider` /
310
+ `.divider-b` / `bg-divider`, or `divide-*` on a `divide-y` stack.
311
+
312
+ On the **dark** themes the separator token is now a **translucent** hairline —
313
+ `hsl(H S 46-48% / 0.18)` — mirroring Apple's own `rgba(84,84,88,0.36)` approach:
314
+ a light grey at low alpha that *dissolves into whatever sits behind it* instead
315
+ of a fixed opaque line. This is deliberate. An opaque separator (even a dim one)
316
+ read as a hard grid on a near-black canvas — every column edge and header rule
317
+ looked heavy. A translucent hairline self-adapts: heavier where it crosses an
318
+ elevated card, lighter on the page. Every dark preset carries it (`macos`,
319
+ `ios`, `windows`, `dense`, `soft`, and the base `dark.css` behind
320
+ `default`/`django-cfg`); **`high-contrast` keeps an opaque divider on purpose**
321
+ (a11y wants the harder line). Light themes keep an opaque divider too — a
322
+ translucent line on white gains nothing.
323
+
324
+ The alpha was tuned by eye in a live cmdop-web session (0.18 is quiet-but-present;
325
+ lower starts to vanish on the page).
326
+
327
+ **If a dark surface looks too contrasty**, the culprit is almost always a
328
+ column/row/header separator wrongly drawn with `--border` (the outline weight)
329
+ instead of `--divider` — move it to `border-divider` / `var(--divider)`.
330
+
302
331
  ### Apply a preset
303
332
 
304
333
  ```tsx
@@ -23,7 +23,7 @@ export const densePreset: ThemePreset = {
23
23
  radius: '0.25rem',
24
24
  border: 'hsl(0 0% 24%)',
25
25
  input: 'hsl(0 0% 24%)',
26
- divider: 'hsl(0 0% 18%)',
26
+ divider: 'hsl(0 0% 46% / 0.18)', // translucent hairline — dissolves into the page
27
27
  muted: 'hsl(0 0% 12%)',
28
28
  card: 'hsl(0 0% 10%)',
29
29
  accent: 'hsl(0 0% 14%)',
@@ -18,11 +18,13 @@ export const iosPreset: ThemePreset = {
18
18
  'accent-foreground': 'hsl(220 9% 12%)',
19
19
  destructive: 'hsl(0 100% 58%)',
20
20
  'destructive-foreground': 'hsl(0 0% 100%)',
21
- border: 'hsl(220 9% 88%)',
21
+ border: 'hsl(220 9% 85%)', // nudged darker so outlines read on white cards
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
+ divider: 'hsl(220 9% 88%)', // hairline, a touch lighter than border
25
25
  radius: '0.75rem',
26
+ // Modal scrim — light dim, iOS sheets keep the backdrop visible.
27
+ overlay: 'hsl(220 9% 10% / 0.32)',
26
28
  'sidebar-background': 'hsl(0 0% 99%)',
27
29
  'sidebar-foreground': 'hsl(220 9% 12%)',
28
30
  'sidebar-primary': 'hsl(211 100% 50%)',
@@ -55,25 +57,31 @@ export const iosPreset: ThemePreset = {
55
57
  dark: {
56
58
  background: 'hsl(240 6% 10%)',
57
59
  foreground: 'hsl(0 0% 96%)',
58
- card: 'hsl(240 6% 14%)',
60
+ // Lifted 14→16 for a clearer float off the dark page (see macos rationale).
61
+ card: 'hsl(240 6% 16%)',
59
62
  'card-foreground': 'hsl(0 0% 96%)',
60
- popover: 'hsl(240 6% 16%)',
63
+ // Kept a distinct step above card.
64
+ popover: 'hsl(240 6% 20%)',
61
65
  'popover-foreground': 'hsl(0 0% 96%)',
62
66
  primary: 'hsl(211 100% 55%)',
63
67
  'primary-foreground': 'hsl(0 0% 100%)',
64
68
  secondary: 'hsl(240 5% 26%)',
65
69
  'secondary-foreground': 'hsl(0 0% 96%)',
66
- muted: 'hsl(240 5% 16%)',
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%)',
67
73
  'muted-foreground': 'hsl(240 5% 64%)',
68
74
  accent: 'hsl(240 5% 18%)',
69
75
  'accent-foreground': 'hsl(0 0% 96%)',
70
76
  destructive: 'hsl(0 100% 67%)',
71
77
  'destructive-foreground': 'hsl(0 0% 100%)',
72
- border: 'hsl(240 5% 22%)',
78
+ border: 'hsl(240 5% 27%)', // raised so outlines read on the dark page
73
79
  input: 'hsl(240 5% 22%)',
74
80
  ring: 'hsl(211 100% 55%)',
75
- divider: 'hsl(240 5% 18%)', // hairline, softer than border
81
+ divider: 'hsl(240 5% 48% / 0.18)', // translucent hairline dissolves into the page
76
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)',
77
85
  'sidebar-background': 'hsl(240 6% 8%)',
78
86
  'sidebar-foreground': 'hsl(0 0% 96%)',
79
87
  'sidebar-primary': 'hsl(211 100% 55%)',
@@ -52,14 +52,19 @@ export const macosPreset: ThemePreset = {
52
52
  // systemRed #FF3B30
53
53
  destructive: 'hsl(2 100% 59%)',
54
54
  'destructive-foreground': 'hsl(0 0% 100%)',
55
- // Apple opaque separator: rgba(60,60,67,0.29) on white ≈ #C6C6C8 → HSL 240 3% 78%
56
- // Kept intentionally light Apple hairline, not a thick rule
55
+ // Apple opaque separator: rgba(60,60,67,0.29) on white ≈ #C6C6C8 → HSL 240 3% 78%.
56
+ // Kept at the Apple hairline value (78%). An earlier build nudged this darker
57
+ // (78→74) to force outlines onto white cards, but that over-contrasted every
58
+ // panel/header `border-b` on the grouped canvas — back to the HIG hairline.
57
59
  border: 'hsl(240 3% 78%)',
58
60
  input: 'hsl(240 8% 93%)',
59
61
  // Row hairline — a touch lighter than border so it reads on white cards
60
- divider: 'hsl(240 4% 85%)',
62
+ divider: 'hsl(240 4% 83%)',
61
63
  ring: 'hsl(211 100% 50%)',
62
64
  radius: '0.625rem',
65
+ // Modal scrim — lighter than the base 0.6 so the light grouped canvas still
66
+ // reads behind the dialog (Apple sheets dim, they don't black out).
67
+ overlay: 'hsl(240 6% 10% / 0.32)',
63
68
  // Sidebar: slighly darker than page, matches macOS sidebar material
64
69
  'sidebar-background': 'hsl(240 12% 94%)',
65
70
  'sidebar-foreground': 'hsl(0 0% 7%)',
@@ -97,11 +102,13 @@ export const macosPreset: ThemePreset = {
97
102
  // Deeper than stock #1C1C1E — matches Sequoia dark finder/mail chrome (#141414)
98
103
  background: 'hsl(240 5% 8%)',
99
104
  foreground: 'hsl(0 0% 95%)',
100
- // secondarySystemBackground #1E1E20 — cards slightly elevated
101
- card: 'hsl(240 3% 12%)',
105
+ // secondarySystemBackground #1E1E20 — cards lifted above the #141414 page.
106
+ // Bumped 12→15 so panels (dialog body, settings cards) visibly float off the
107
+ // near-black canvas; on L≈8 a +4 lift is too subtle for the eye (Weber).
108
+ card: 'hsl(240 3% 15%)',
102
109
  'card-foreground': 'hsl(0 0% 95%)',
103
- // #2C2C2E — popovers, menus, dropdowns
104
- popover: 'hsl(240 3% 17%)',
110
+ // #2C2C2E — popovers, menus, dropdowns; kept a clear step above card.
111
+ popover: 'hsl(240 3% 20%)',
105
112
  'popover-foreground': 'hsl(0 0% 95%)',
106
113
  // systemBlue dark #0A84FF
107
114
  primary: 'hsl(211 100% 58%)',
@@ -109,8 +116,12 @@ export const macosPreset: ThemePreset = {
109
116
  // Elevated neutral surface — slightly lighter than card
110
117
  secondary: 'hsl(240 3% 20%)',
111
118
  'secondary-foreground': 'hsl(0 0% 88%)',
112
- // tertiarySystemBackground #3A3A3Cchips, tags, muted fills
113
- muted: 'hsl(240 3% 23%)',
119
+ // Recessed surfacea notch BELOW the page (bg 8%), like default's muted.
120
+ // `--muted` is the "sunk" surface (input rest, nav rails, chips): it must
121
+ // read as recessed vs `--card` (15%), not raised. Apple's #3A3A3C
122
+ // tertiary fill (23%) sat *above* card and inverted the depth — the
123
+ // SettingsDialog nav rail (bg-muted) then looked lighter than its panel.
124
+ muted: 'hsl(240 3% 11%)',
114
125
  'muted-foreground': 'hsl(240 4% 58%)',
115
126
  // Blue-tinted hover (Apple active state in dark)
116
127
  accent: 'hsl(211 25% 19%)',
@@ -118,13 +129,28 @@ export const macosPreset: ThemePreset = {
118
129
  // systemRed dark #FF453A
119
130
  destructive: 'hsl(3 100% 62%)',
120
131
  'destructive-foreground': 'hsl(0 0% 100%)',
121
- // Separator dark: rgba(84,84,88,0.36) ultra-thin, almost invisible
132
+ // Control/panel OUTLINEinput & card frames, not separators. Opaque 22%
133
+ // reads cleanly as a control edge on card (L15) surfaces. Structural
134
+ // separators (shell columns, headers, list rows) do NOT use this — they use
135
+ // `--divider`, which is a softer translucent hairline (see below), so the
136
+ // outline weight here never turns the chrome into a hard grid.
122
137
  border: 'hsl(240 3% 22%)',
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%)',
138
+ input: 'hsl(240 3% 22%)',
139
+ // Separator hairline — TRANSLUCENT, mirroring Apple's rgba(84,84,88,0.36):
140
+ // a light grey at low alpha that dissolves into whatever sits behind it
141
+ // instead of a fixed opaque line. This is the token for ALL structural
142
+ // separators — shell columns (rail↔list↔chat), header `border-b` rules, and
143
+ // list-row hairlines — deliberately softer than the opaque `--border`
144
+ // outline so the chrome never reads as a hard grid on the near-black canvas.
145
+ // 46% L @ 0.18 alpha picked live in cmdop-web: a quiet, self-adapting
146
+ // hairline (heavier over an elevated card, lighter on the page).
147
+ divider: 'hsl(240 4% 46% / 0.18)',
126
148
  ring: 'hsl(211 100% 58%)',
127
149
  radius: '0.625rem',
150
+ // Modal scrim — a touch lighter than the base 0.7. The page is already near
151
+ // black (L≈8); 0.55 dims it enough to separate the dialog without crushing
152
+ // the backdrop into a flat black slab the panel can't lift off of.
153
+ overlay: 'hsl(0 0% 0% / 0.55)',
128
154
  // Sidebar: near-black floor — #0D0D0F
129
155
  'sidebar-background': 'hsl(240 5% 5%)',
130
156
  'sidebar-foreground': 'hsl(0 0% 90%)',
@@ -132,7 +158,10 @@ export const macosPreset: ThemePreset = {
132
158
  'sidebar-primary-foreground': 'hsl(0 0% 100%)',
133
159
  'sidebar-accent': 'hsl(211 22% 16%)',
134
160
  'sidebar-accent-foreground': 'hsl(211 100% 72%)',
135
- 'sidebar-border': 'hsl(240 3% 15%)',
161
+ // 22 — the sidebar rail floor is #0D0D0F (L5), darker than the page (L8), so
162
+ // its edge needs a touch more lift than the page-calibrated `--border` (18)
163
+ // to read at all. Still a hairline, not the hard rule an outline weight draws.
164
+ 'sidebar-border': 'hsl(240 3% 22%)',
136
165
  'sidebar-ring': 'hsl(211 100% 58%)',
137
166
  // Chart: brighter variants for contrast on dark
138
167
  'chart-1': 'hsl(211 100% 58%)',
@@ -39,7 +39,7 @@ export const softPreset: ThemePreset = {
39
39
  'accent-foreground': 'hsl(0 0% 96%)',
40
40
  border: 'hsl(240 5% 20%)',
41
41
  input: 'hsl(240 5% 20%)',
42
- divider: 'hsl(240 5% 16%)',
42
+ divider: 'hsl(240 5% 48% / 0.18)', // translucent hairline — dissolves into the page
43
43
  radius: '1rem',
44
44
  'sidebar-background': 'hsl(240 6% 6%)',
45
45
  'sidebar-accent': 'hsl(240 5% 14%)',
@@ -47,6 +47,8 @@ export const windowsPreset: ThemePreset = {
47
47
  // Hairline between rows — slightly lighter than border
48
48
  divider: 'hsl(0 0% 84%)',
49
49
  ring: 'hsl(210 100% 45%)',
50
+ // Fluent SmokeFillColorDefault — content dialogs dim the layer beneath.
51
+ overlay: 'hsl(0 0% 0% / 0.4)',
50
52
  // WinUI 3: 4px controls, 8px cards/dialogs
51
53
  radius: '0.375rem',
52
54
  'sidebar-background': 'hsl(0 0% 92%)',
@@ -101,7 +103,9 @@ export const windowsPreset: ThemePreset = {
101
103
  'primary-foreground': 'hsl(0 0% 9%)',
102
104
  secondary: 'hsl(0 0% 22%)',
103
105
  'secondary-foreground': 'hsl(0 0% 90%)',
104
- muted: 'hsl(0 0% 18%)',
106
+ // Recessed surface — below the page (bg 13%) and below card (17%), so
107
+ // bg-muted rails/chips read as sunk, not raised above the panel.
108
+ muted: 'hsl(0 0% 15%)',
105
109
  'muted-foreground': 'hsl(0 0% 62%)',
106
110
  accent: 'hsl(210 25% 20%)',
107
111
  'accent-foreground': 'hsl(200 100% 75%)',
@@ -111,8 +115,10 @@ export const windowsPreset: ThemePreset = {
111
115
  border: 'hsl(0 0% 28%)',
112
116
  input: 'hsl(0 0% 24%)',
113
117
  // Hairline between rows — softer than border
114
- divider: 'hsl(0 0% 22%)',
118
+ divider: 'hsl(0 0% 46% / 0.18)', // translucent hairline — dissolves into the page
115
119
  ring: 'hsl(200 100% 69%)',
120
+ // Fluent SmokeFillColorDefault dark — dims the dark page behind the dialog.
121
+ overlay: 'hsl(0 0% 0% / 0.55)',
116
122
  radius: '0.375rem',
117
123
  // NavigationView pane background
118
124
  'sidebar-background': 'hsl(0 0% 10%)',
@@ -35,8 +35,12 @@ export type ThemeCssVarColorKey =
35
35
  * Layout / focus tokens — `radius` is a CSS length, the rest are wrapped colors.
36
36
  * `divider` is the hairline-between-rows token (deliberately *lighter* than
37
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.
38
42
  */
39
- export type ThemeCssVarChromeKey = 'border' | 'input' | 'divider' | 'ring' | 'radius';
43
+ export type ThemeCssVarChromeKey = 'border' | 'input' | 'divider' | 'overlay' | 'ring' | 'radius';
40
44
 
41
45
  /**
42
46
  * Status surfaces — each role has a 4-token set (icon/accent, banner background,
@@ -44,9 +44,11 @@
44
44
  --border: hsl(48 3% 28%);
45
45
  /* Input surface — a notch above card so fields read as raised controls. */
46
46
  --input: hsl(48 2.5% 24%);
47
- /* Divider — hairline; slightly softer than --border so rows don't read as a
48
- * hard rule (Claude menus/rows). */
49
- --divider: hsl(48 3% 24%);
47
+ /* Divider — TRANSLUCENT hairline (Apple-style): a light warm-gray at low alpha
48
+ * that dissolves into whatever sits behind it, so structural separators
49
+ * (columns, headers, rows) stay quiet on the dark page instead of reading as a
50
+ * hard opaque rule. Backs default + django-cfg (they don't re-declare it). */
51
+ --divider: hsl(48 4% 46% / 0.18);
50
52
  /* Overlay — modal scrim / backdrop behind dialogs, drawers, sheets. Black in
51
53
  * both themes; slightly darker here so it still reads on the dark page. */
52
54
  --overlay: hsl(0 0% 0% / 0.7);