@djangocfg/ui-core 2.1.530 → 2.1.532
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.532",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -128,7 +128,7 @@
|
|
|
128
128
|
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
129
129
|
},
|
|
130
130
|
"peerDependencies": {
|
|
131
|
-
"@djangocfg/i18n": "^2.1.
|
|
131
|
+
"@djangocfg/i18n": "^2.1.532",
|
|
132
132
|
"consola": "^3.4.2",
|
|
133
133
|
"lucide-react": "^0.545.0",
|
|
134
134
|
"moment": "^2.30.1",
|
|
@@ -206,8 +206,8 @@
|
|
|
206
206
|
"@chenglou/pretext": "^0.0.8"
|
|
207
207
|
},
|
|
208
208
|
"devDependencies": {
|
|
209
|
-
"@djangocfg/i18n": "^2.1.
|
|
210
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
209
|
+
"@djangocfg/i18n": "^2.1.532",
|
|
210
|
+
"@djangocfg/typescript-config": "^2.1.532",
|
|
211
211
|
"@types/node": "^24.13.3",
|
|
212
212
|
"@types/react": "19.2.15",
|
|
213
213
|
"@types/react-dom": "19.2.3",
|
|
@@ -80,7 +80,11 @@ const DialogContent = React.forwardRef<
|
|
|
80
80
|
? "left-0 top-0 grid grid-rows-[minmax(0,1fr)] h-[100dvh] w-screen max-w-none gap-0 border-0 p-0 shadow-none data-[state=closed]:zoom-out-[0.98] data-[state=open]:zoom-in-[0.98]"
|
|
81
81
|
// Rounded on ALL sizes — the radius used to be `sm:`-gated, which left
|
|
82
82
|
// phone dialogs with hard square corners. Centered, soft-cornered card.
|
|
83
|
-
|
|
83
|
+
// Width by MEASUREMENT, not breakpoint: `min()` keeps the card off
|
|
84
|
+
// both edges at any viewport. A `sm:` gate reads the window, so a
|
|
85
|
+
// narrow desktop pane lost its margins and rendered as a sheet with
|
|
86
|
+
// its rounding cropped.
|
|
87
|
+
: "left-1/2 top-1/2 grid w-[min(32rem,calc(100vw-2rem))] -translate-x-1/2 -translate-y-1/2 gap-4 rounded-[var(--radius-dialog)] border p-6 shadow-lg data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95",
|
|
84
88
|
className
|
|
85
89
|
)}
|
|
86
90
|
{...props}
|
|
@@ -105,7 +109,9 @@ const DialogHeader = ({
|
|
|
105
109
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
106
110
|
<div
|
|
107
111
|
className={cn(
|
|
108
|
-
|
|
112
|
+
// Start-aligned at every width: the `sm:` gate centred the title in a
|
|
113
|
+
// narrow pane only, so one dialog read as two different components.
|
|
114
|
+
"flex flex-col space-y-1.5 text-start",
|
|
109
115
|
className
|
|
110
116
|
)}
|
|
111
117
|
{...props}
|
|
@@ -119,7 +125,10 @@ const DialogFooter = ({
|
|
|
119
125
|
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
120
126
|
<div
|
|
121
127
|
className={cn(
|
|
122
|
-
|
|
128
|
+
// One row, buttons sized to their labels. The `sm:`-gated stack read the
|
|
129
|
+
// window, so a narrow pane got two full-bleed buttons — Cancel as loud as
|
|
130
|
+
// Confirm. `flex-wrap` handles a long label by measurement instead.
|
|
131
|
+
"flex flex-wrap items-center justify-end gap-2",
|
|
123
132
|
className
|
|
124
133
|
)}
|
|
125
134
|
{...props}
|
package/src/styles/README.md
CHANGED
|
@@ -1,217 +1,156 @@
|
|
|
1
1
|
# ui-core styles
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
CSS is the single source of truth for colors, typography, radius, status
|
|
4
|
+
surfaces, charts, and sidebar tokens. There are no TypeScript color maps and no
|
|
5
|
+
runtime token generator — do not add either.
|
|
6
6
|
|
|
7
7
|
## Layout
|
|
8
8
|
|
|
9
9
|
```text
|
|
10
10
|
styles/
|
|
11
|
-
├── css/
|
|
12
|
-
│ ├── full.css
|
|
13
|
-
│ ├──
|
|
14
|
-
│ ├──
|
|
15
|
-
│ ├──
|
|
16
|
-
│ ├──
|
|
17
|
-
│
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
│ ├── utilities/ # focused utility modules
|
|
21
|
-
│ └── presets/ # authoritative static preset token sheets
|
|
22
|
-
│ ├── default.css
|
|
23
|
-
│ ├── django-cfg.css
|
|
24
|
-
│ ├── ios.css
|
|
25
|
-
│ ├── macos.css
|
|
26
|
-
│ ├── windows.css
|
|
27
|
-
│ ├── soft.css
|
|
28
|
-
│ ├── dense.css
|
|
29
|
-
│ └── high-contrast.css
|
|
30
|
-
├── palette/ # runtime readers of computed CSS variables
|
|
31
|
-
└── presets/ # TypeScript names/order only
|
|
32
|
-
├── index.ts
|
|
33
|
-
├── presets.ts
|
|
34
|
-
└── types.ts
|
|
11
|
+
├── css/
|
|
12
|
+
│ ├── full.css # THE entry: Tailwind + tokens + base + utilities
|
|
13
|
+
│ ├── base.css # resets
|
|
14
|
+
│ ├── sources.css # Tailwind @source directives
|
|
15
|
+
│ ├── theme/ # tokens.css (the @theme map), light.css, dark.css
|
|
16
|
+
│ ├── utilities/ # focused utility modules
|
|
17
|
+
│ └── presets/ # the authoritative token values, one file per preset
|
|
18
|
+
├── palette/ # runtime readers of computed CSS variables
|
|
19
|
+
└── presets/ # TypeScript names and ordering only — never values
|
|
35
20
|
```
|
|
36
21
|
|
|
37
|
-
|
|
38
|
-
|
|
22
|
+
`index.css` and `globals.css` are compatibility entries. New consumers import
|
|
23
|
+
`full.css`.
|
|
39
24
|
|
|
40
|
-
|
|
41
|
-
the public API. The actual values always live in `styles/css/presets/*.css`.
|
|
42
|
-
|
|
43
|
-
## Consumer contract
|
|
44
|
-
|
|
45
|
-
Import the golden path first, then exactly one product preset:
|
|
25
|
+
## Consumer setup
|
|
46
26
|
|
|
47
27
|
```css
|
|
48
28
|
@import "@djangocfg/ui-core/styles/full";
|
|
29
|
+
@plugin "tailwindcss-animate";
|
|
49
30
|
@import "@djangocfg/ui-core/styles/presets/macos";
|
|
31
|
+
@import "@djangocfg/ui-tools/styles";
|
|
50
32
|
```
|
|
51
33
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
`.dark` (dark) values. `ThemeProvider` only controls the `html.dark` class.
|
|
55
|
-
|
|
56
|
-
For a product-specific adjustment, add a small override after the preset:
|
|
34
|
+
Order is load-bearing: the preset must come after `full.css` so its `:root` and
|
|
35
|
+
`.dark` values win. Application overrides come last.
|
|
57
36
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
.
|
|
61
|
-
--font-size-base: 0.9375rem;
|
|
62
|
-
--font-size-sm: 0.875rem;
|
|
63
|
-
}
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Do not create a `theme-preset.ts`, append a `<style>` tag, or call a token
|
|
67
|
-
builder during application startup.
|
|
37
|
+
`full.css` already imports Tailwind, tokens, base and utilities — a second
|
|
38
|
+
`@import "tailwindcss"` or `base.css` in the consumer is a duplicate. The
|
|
39
|
+
`tailwindcss-animate` plugin is registered by the consumer, not the package.
|
|
68
40
|
|
|
69
|
-
|
|
41
|
+
A preset defines both `:root` (light) and `.dark`. `ThemeProvider` only toggles
|
|
42
|
+
the `html.dark` class.
|
|
70
43
|
|
|
71
|
-
|
|
44
|
+
For a product adjustment, override after the preset:
|
|
72
45
|
|
|
73
46
|
```css
|
|
74
|
-
|
|
75
|
-
@plugin "tailwindcss-animate";
|
|
76
|
-
@import "@djangocfg/ui-core/styles/presets/macos";
|
|
77
|
-
@import "@djangocfg/layouts/styles";
|
|
78
|
-
@import "@djangocfg/ui-tools/styles";
|
|
47
|
+
:root, .dark { --font-size-base: 0.9375rem; }
|
|
79
48
|
```
|
|
80
49
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
or `utilities.css` in the consumer. Load the preset after `full.css`, so its
|
|
84
|
-
`:root` and `.dark` variables win over defaults. Keep application overrides
|
|
85
|
-
after the preset.
|
|
86
|
-
|
|
87
|
-
The `tailwindcss-animate` plugin is registered once by the consumer. It
|
|
88
|
-
provides classes such as `animate-in`, `fade-in-0`, and `zoom-in-95`; the
|
|
89
|
-
package's component source uses those classes but does not register the
|
|
90
|
-
plugin itself.
|
|
50
|
+
Do not build tokens at startup, append a `<style>` tag, or copy values into a
|
|
51
|
+
`theme-preset.ts`.
|
|
91
52
|
|
|
92
53
|
### Tailwind source scanning
|
|
93
54
|
|
|
94
|
-
`css/sources.css`
|
|
95
|
-
root from its own location:
|
|
55
|
+
`css/sources.css` must scan the package root from its own location:
|
|
96
56
|
|
|
97
57
|
```css
|
|
98
58
|
@source "../../**/*.{ts,tsx}";
|
|
99
59
|
```
|
|
100
60
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
but should not replace the ui-core source directive.
|
|
61
|
+
Scanning `../` instead reaches only `src/styles` and silently drops every
|
|
62
|
+
utility used by components, `bg-overlay` among them. Consumers may add their own
|
|
63
|
+
`@source` lines but must not replace this one.
|
|
105
64
|
|
|
106
|
-
|
|
65
|
+
## Token format
|
|
107
66
|
|
|
108
|
-
|
|
109
|
-
`
|
|
110
|
-
from `--overlay-blur` (the default is `5px`):
|
|
67
|
+
Color variables are complete CSS colors, never bare HSL triplets. Use
|
|
68
|
+
`var(--background)` or `bg-background`; never `hsl(var(--background))`.
|
|
111
69
|
|
|
112
70
|
```css
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
backdrop-filter: blur(var(--overlay-blur));
|
|
116
|
-
}
|
|
71
|
+
:root { --background: hsl(240 17% 97%); }
|
|
72
|
+
.dark { --background: hsl(240 5% 8%); }
|
|
117
73
|
```
|
|
118
74
|
|
|
119
|
-
|
|
120
|
-
`bg-popover` makes the blur invisible. A dropdown rendered inside an ancestor
|
|
121
|
-
that already has `backdrop-filter` is also unable to blur the page behind that
|
|
122
|
-
ancestor because it becomes a backdrop root. PublicLayout desktop dropdowns
|
|
123
|
-
are therefore rendered through a body portal and positioned from the trigger.
|
|
124
|
-
Use the same pattern for new floating surfaces that must blur content outside
|
|
125
|
-
their parent stacking context.
|
|
75
|
+
Typography variables are ordinary CSS values (`--font-size-base: 0.8125rem`).
|
|
126
76
|
|
|
127
|
-
|
|
128
|
-
`backdrop-blur-[5px]` when the consuming build must emit the standard
|
|
129
|
-
`backdrop-filter` declaration. Keep the visual token (`--overlay-blur`) in
|
|
130
|
-
CSS rather than duplicating a pixel value in React.
|
|
77
|
+
### Raised and recessed surfaces
|
|
131
78
|
|
|
132
|
-
|
|
79
|
+
| Token | Reads as | Use for |
|
|
80
|
+
|---|---|---|
|
|
81
|
+
| `--surface-hover` | a step toward the reader | pointer feedback on a resting surface |
|
|
82
|
+
| `--surface-recessed` | sunk below the page | chrome framing content: a tab strip, a rail, a footer band |
|
|
133
83
|
|
|
134
|
-
|
|
84
|
+
`--surface-recessed` is a translucent black, not a mix with `--background`. A
|
|
85
|
+
percentage mix takes its step from what is already there, and a dark page has
|
|
86
|
+
almost nothing to take: 6% black into `hsl(240 5% 8%)` moved it two bytes out of
|
|
87
|
+
255, which no eye resolves. A fixed overlay steps by the same amount whatever
|
|
88
|
+
the page — and the alpha differs per theme, because the same overlay costs far
|
|
89
|
+
more contrast on a near-white ground.
|
|
135
90
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
.
|
|
141
|
-
--background: hsl(240 5% 8%);
|
|
142
|
-
}
|
|
143
|
-
```
|
|
91
|
+
**Never recess with `--muted` or `--card`.** Their direction is not guaranteed:
|
|
92
|
+
`theme/dark.css` keeps `--muted` below `--background`, while the macOS preset
|
|
93
|
+
raises every resting surface (background 8%, muted 11%, card 12%). A tab strip
|
|
94
|
+
painted `bg-muted` floated toward the reader on that preset and looked correct
|
|
95
|
+
in the base theme.
|
|
144
96
|
|
|
145
|
-
|
|
146
|
-
Use `var(--background)` or `bg-background`; do not write
|
|
147
|
-
`hsl(var(--background))`.
|
|
97
|
+
### Overlays and glass
|
|
148
98
|
|
|
149
|
-
|
|
99
|
+
`bg-overlay` takes its color from `--overlay` and its frosting from
|
|
100
|
+
`--overlay-blur` (default `5px`). A glass surface needs a translucent
|
|
101
|
+
background — an opaque `bg-card` makes the blur invisible.
|
|
150
102
|
|
|
151
|
-
|
|
152
|
-
:root
|
|
153
|
-
.
|
|
154
|
-
--font-size-base: 0.8125rem;
|
|
155
|
-
--font-size-sm: 0.75rem;
|
|
156
|
-
}
|
|
157
|
-
```
|
|
103
|
+
An element inside an ancestor that already has `backdrop-filter` cannot blur the
|
|
104
|
+
page behind that ancestor: the ancestor becomes a backdrop root. Render such
|
|
105
|
+
surfaces through a body portal, positioned from their trigger.
|
|
158
106
|
|
|
159
107
|
## Preset rules
|
|
160
108
|
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
not a neighbouring one that happens to be nearby.**
|
|
182
|
-
- Changes to a preset require checking both light and dark modes.
|
|
183
|
-
- Product density overrides belong in the consuming app's CSS, not in a copied
|
|
184
|
-
TypeScript map.
|
|
109
|
+
- Values live in `styles/css/presets/*.css`. `styles/presets/*.ts` holds names
|
|
110
|
+
and ordering only.
|
|
111
|
+
- Every value must be a complete CSS color. Keep a preset's light and dark pairs
|
|
112
|
+
in one file. A preset may omit tokens that inherit from `theme.css`.
|
|
113
|
+
- **Light mode:** `--muted` at least 2 lightness points darker than
|
|
114
|
+
`--background`, so translucent muted fills stay visible.
|
|
115
|
+
- **Both modes:** an interactive surface sits at least 4 lightness points from
|
|
116
|
+
the surface it lands on — `--accent` vs `--background`, `--sidebar-accent` vs
|
|
117
|
+
`--sidebar-background`.
|
|
118
|
+
|
|
119
|
+
Direction is not asserted, only the step size. The second pair exists because
|
|
120
|
+
the first was not enough: a sidebar hover lands on `--sidebar-background`,
|
|
121
|
+
which nothing checked, so presets shipped one-point steps in light against
|
|
122
|
+
eleven in dark, and one hover came out lighter than the rail it highlighted —
|
|
123
|
+
all while passing the `--muted` gate. **Check the surface the user sees, not a
|
|
124
|
+
neighbour that happens to be nearby.**
|
|
125
|
+
|
|
126
|
+
Because direction is free, product code must not infer it from a token's
|
|
127
|
+
value. That is what `--surface-recessed` and `--surface-hover` are for.
|
|
128
|
+
- Density overrides belong in the consuming app's CSS.
|
|
185
129
|
|
|
186
130
|
## Verification
|
|
187
131
|
|
|
188
132
|
```bash
|
|
189
|
-
pnpm check:contrast
|
|
190
|
-
pnpm check
|
|
133
|
+
pnpm check:contrast # preset gates + the macOS AA compact-text audit
|
|
134
|
+
pnpm check # tsc --noEmit
|
|
191
135
|
```
|
|
192
136
|
|
|
193
|
-
`check:contrast` merges
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
muted text against every common surface. When tuning a token, change the
|
|
198
|
-
semantic pair together rather than overriding text in a consumer component.
|
|
137
|
+
`check:contrast` merges each static preset over the base theme, enforces the
|
|
138
|
+
rules above, and requires WCAG AA 4.5:1 for semantic pairs in both modes. When
|
|
139
|
+
tuning a token, move the semantic pair together rather than overriding text in a
|
|
140
|
+
consumer component.
|
|
199
141
|
|
|
200
|
-
|
|
201
|
-
The important invariant is that changing a preset requires editing one CSS
|
|
202
|
-
file, and no React runtime code is involved in applying it.
|
|
142
|
+
Then build a consumer and look at both modes.
|
|
203
143
|
|
|
204
|
-
|
|
205
|
-
restart its dev server afterward:
|
|
144
|
+
## Cross-repository changes
|
|
206
145
|
|
|
207
146
|
```bash
|
|
208
147
|
pnpm sync:cfg:one ui-core
|
|
209
|
-
pnpm sync:cfg:one
|
|
210
|
-
pnpm sync:cfg:one i18n # required peer when ui-core is synced locally
|
|
211
|
-
rm -rf apps/web/.next
|
|
148
|
+
pnpm sync:cfg:one i18n # required peer when ui-core is synced locally
|
|
212
149
|
```
|
|
213
150
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
151
|
+
Restart the consumer's dev server afterward.
|
|
152
|
+
|
|
153
|
+
A sync is local and temporary: a clean install restores the published package,
|
|
154
|
+
so a source change survives only once a new version is published. Update the
|
|
155
|
+
dependency range with pnpm and let the lockfile record the resolution — never
|
|
156
|
+
delete `pnpm-lock.yaml` to force an update.
|
|
@@ -37,6 +37,18 @@
|
|
|
37
37
|
--muted-foreground: hsl(48 5% 59%);
|
|
38
38
|
/* Neutral interaction surface: a quiet lift above the current dark page. */
|
|
39
39
|
--surface-hover: color-mix(in oklab, var(--card) 96%, var(--foreground));
|
|
40
|
+
/* The counterpart to --surface-hover: a surface that reads as sunk BELOW the
|
|
41
|
+
* page — a tab strip under a document, a rail beside it. Every other surface
|
|
42
|
+
* token raises, so a chrome band painted with one of them floats toward the
|
|
43
|
+
* viewer instead of receding, and a preset whose --muted sits above
|
|
44
|
+
* --background has no recessed fill at all.
|
|
45
|
+
*
|
|
46
|
+
* A translucent BLACK, not a mix with --background. A percentage mix takes its
|
|
47
|
+
* step from what is already there, and a dark page has almost nothing to take:
|
|
48
|
+
* mixing 6% black into hsl(240 5% 8%) moved it two bytes out of 255, which no
|
|
49
|
+
* eye resolves. A fixed overlay steps by the same amount whatever the page,
|
|
50
|
+
* and composites over whatever surface the band happens to sit on. */
|
|
51
|
+
--surface-recessed: rgb(0 0 0 / 0.35);
|
|
40
52
|
/* Hover/active surface (rails, menus, tabs) — a quiet warm lift above the page. */
|
|
41
53
|
--accent: hsl(48 3% 24%);
|
|
42
54
|
--accent-foreground: hsl(48 33% 97%);
|
|
@@ -34,6 +34,11 @@
|
|
|
34
34
|
* It deliberately keys off --background, not white --card, so hover remains
|
|
35
35
|
* visible on translucent menus and on plain pages alike. */
|
|
36
36
|
--surface-hover: color-mix(in oklab, var(--background) 96%, var(--foreground));
|
|
37
|
+
/* The counterpart to --surface-hover: a surface that reads as sunk BELOW the
|
|
38
|
+
* page. A translucent black for the same reason as the dark theme's, but much
|
|
39
|
+
* weaker: the same overlay costs far more contrast on a near-white ground,
|
|
40
|
+
* where it goes muddy rather than quiet. */
|
|
41
|
+
--surface-recessed: rgb(0 0 0 / 0.045);
|
|
37
42
|
/* Neutral hover/active surface — quiet warm-gray lift (Claude bg-200), a
|
|
38
43
|
* clear step (≥4L) below the 97% canvas. */
|
|
39
44
|
--accent: hsl(60 11% 92%);
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
--color-muted: var(--muted);
|
|
40
40
|
--color-muted-foreground: var(--muted-foreground);
|
|
41
41
|
--color-surface-hover: var(--surface-hover);
|
|
42
|
+
--color-surface-recessed: var(--surface-recessed);
|
|
42
43
|
--color-accent: var(--accent);
|
|
43
44
|
--color-accent-foreground: var(--accent-foreground);
|
|
44
45
|
--color-destructive: var(--destructive);
|