@djangocfg/ui-core 2.1.464 → 2.1.466
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 -0
- package/package.json +46 -45
- package/src/components/navigation/dropdown-menu/index.tsx +9 -2
- package/src/components/navigation/menu/menu-builder.tsx +2 -1
- package/src/components/navigation/menu/render-items.tsx +9 -1
- package/src/components/navigation/menu/types.ts +15 -1
- package/src/styles/README.md +7 -0
- package/src/styles/css/presets/macos.css +13 -8
package/README.md
CHANGED
|
@@ -176,6 +176,11 @@ Tailwind v4 with semantic tokens, not raw color scales:
|
|
|
176
176
|
|
|
177
177
|
Tokens live in `:root` / `.dark` as fully-wrapped CSS colors; `@theme inline` exposes them as `--color-X` references, so opacity modifiers (`bg-card/40`, `border-foreground/20`) resolve via `color-mix` for **every** semantic token.
|
|
178
178
|
|
|
179
|
+
Preset text/fill pairs target WCAG AA for compact product UI. For the macOS
|
|
180
|
+
preset this is enforced by `pnpm check:contrast`; primary and destructive
|
|
181
|
+
foregrounds intentionally switch between light and dark ink where the system
|
|
182
|
+
fill's luminance requires it.
|
|
183
|
+
|
|
179
184
|
`@custom-variant dark (&:where(.dark, .dark *))` binds the `dark:` variant to the `.dark` class on `<html>` (not `prefers-color-scheme`) — every theme-switcher in this monorepo toggles that class.
|
|
180
185
|
|
|
181
186
|
Tailwind's `text-*` size utilities are bridged to the preset `--font-size-*` scale. Product-specific density belongs in a small CSS override after the static preset import — never in a runtime token builder. See `src/styles/README.md`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.466",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -124,10 +124,11 @@
|
|
|
124
124
|
],
|
|
125
125
|
"scripts": {
|
|
126
126
|
"lint": "eslint .",
|
|
127
|
-
"check": "tsc --noEmit"
|
|
127
|
+
"check": "tsc --noEmit",
|
|
128
|
+
"check:contrast": "node scripts/check-preset-contrast.mjs"
|
|
128
129
|
},
|
|
129
130
|
"peerDependencies": {
|
|
130
|
-
"@djangocfg/i18n": "^2.1.
|
|
131
|
+
"@djangocfg/i18n": "^2.1.466",
|
|
131
132
|
"consola": "^3.4.2",
|
|
132
133
|
"lucide-react": "^0.545.0",
|
|
133
134
|
"moment": "^2.30.1",
|
|
@@ -150,68 +151,68 @@
|
|
|
150
151
|
}
|
|
151
152
|
},
|
|
152
153
|
"dependencies": {
|
|
153
|
-
"@hookform/resolvers": "^5.
|
|
154
|
-
"@radix-ui/react-accordion": "^1.2.
|
|
155
|
-
"@radix-ui/react-alert-dialog": "^1.1.
|
|
156
|
-
"@radix-ui/react-aspect-ratio": "^1.1.
|
|
157
|
-
"@radix-ui/react-avatar": "^1.
|
|
158
|
-
"@radix-ui/react-checkbox": "^1.3.
|
|
159
|
-
"@radix-ui/react-collapsible": "^1.1.
|
|
160
|
-
"@radix-ui/react-context-menu": "^2.
|
|
161
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
162
|
-
"@radix-ui/react-dropdown-menu": "^2.1.
|
|
163
|
-
"@radix-ui/react-hover-card": "^1.1.
|
|
154
|
+
"@hookform/resolvers": "^5.4.0",
|
|
155
|
+
"@radix-ui/react-accordion": "^1.2.16",
|
|
156
|
+
"@radix-ui/react-alert-dialog": "^1.1.19",
|
|
157
|
+
"@radix-ui/react-aspect-ratio": "^1.1.11",
|
|
158
|
+
"@radix-ui/react-avatar": "^1.2.2",
|
|
159
|
+
"@radix-ui/react-checkbox": "^1.3.7",
|
|
160
|
+
"@radix-ui/react-collapsible": "^1.1.16",
|
|
161
|
+
"@radix-ui/react-context-menu": "^2.3.3",
|
|
162
|
+
"@radix-ui/react-dialog": "^1.1.19",
|
|
163
|
+
"@radix-ui/react-dropdown-menu": "^2.1.20",
|
|
164
|
+
"@radix-ui/react-hover-card": "^1.1.19",
|
|
164
165
|
"@radix-ui/react-icons": "^1.3.2",
|
|
165
|
-
"@radix-ui/react-label": "^2.1.
|
|
166
|
-
"@radix-ui/react-menubar": "^1.1.
|
|
167
|
-
"@radix-ui/react-navigation-menu": "^1.2.
|
|
168
|
-
"@radix-ui/react-popover": "^1.1.
|
|
169
|
-
"@radix-ui/react-progress": "^1.1.
|
|
170
|
-
"@radix-ui/react-radio-group": "^1.3
|
|
171
|
-
"@radix-ui/react-scroll-area": "^1.2.
|
|
172
|
-
"@radix-ui/react-select": "^2.
|
|
173
|
-
"@radix-ui/react-separator": "^1.1.
|
|
174
|
-
"@radix-ui/react-slider": "^1.3
|
|
175
|
-
"@radix-ui/react-slot": "^1.
|
|
176
|
-
"@radix-ui/react-switch": "^1.
|
|
177
|
-
"@radix-ui/react-tabs": "^1.1.
|
|
178
|
-
"@radix-ui/react-toast": "^1.2.
|
|
179
|
-
"@radix-ui/react-toggle": "^1.1.
|
|
180
|
-
"@radix-ui/react-toggle-group": "^1.1.
|
|
181
|
-
"@radix-ui/react-tooltip": "^1.2.
|
|
182
|
-
"@web3icons/react": "^4.
|
|
166
|
+
"@radix-ui/react-label": "^2.1.11",
|
|
167
|
+
"@radix-ui/react-menubar": "^1.1.20",
|
|
168
|
+
"@radix-ui/react-navigation-menu": "^1.2.18",
|
|
169
|
+
"@radix-ui/react-popover": "^1.1.19",
|
|
170
|
+
"@radix-ui/react-progress": "^1.1.12",
|
|
171
|
+
"@radix-ui/react-radio-group": "^1.4.3",
|
|
172
|
+
"@radix-ui/react-scroll-area": "^1.2.14",
|
|
173
|
+
"@radix-ui/react-select": "^2.3.3",
|
|
174
|
+
"@radix-ui/react-separator": "^1.1.11",
|
|
175
|
+
"@radix-ui/react-slider": "^1.4.3",
|
|
176
|
+
"@radix-ui/react-slot": "^1.3.0",
|
|
177
|
+
"@radix-ui/react-switch": "^1.3.3",
|
|
178
|
+
"@radix-ui/react-tabs": "^1.1.17",
|
|
179
|
+
"@radix-ui/react-toast": "^1.2.19",
|
|
180
|
+
"@radix-ui/react-toggle": "^1.1.14",
|
|
181
|
+
"@radix-ui/react-toggle-group": "^1.1.15",
|
|
182
|
+
"@radix-ui/react-tooltip": "^1.2.12",
|
|
183
|
+
"@web3icons/react": "^4.1.17",
|
|
183
184
|
"class-variance-authority": "^0.7.1",
|
|
184
185
|
"clsx": "^2.1.1",
|
|
185
186
|
"cmdk": "1.1.1",
|
|
186
|
-
"countries-list": "^3.
|
|
187
|
-
"country-flag-icons": "^1.6.
|
|
188
|
-
"date-fns": "^4.
|
|
187
|
+
"countries-list": "^3.4.0",
|
|
188
|
+
"country-flag-icons": "^1.6.20",
|
|
189
|
+
"date-fns": "^4.4.0",
|
|
189
190
|
"embla-carousel-react": "8.6.0",
|
|
190
191
|
"i18n-iso-countries": "^7.14.0",
|
|
191
192
|
"input-otp": "1.4.2",
|
|
192
|
-
"libphonenumber-js": "^1.
|
|
193
|
+
"libphonenumber-js": "^1.13.8",
|
|
193
194
|
"next-themes": "^0.4.6",
|
|
194
195
|
"nextjs-toploader": "^3.9.17",
|
|
195
196
|
"react-day-picker": "9.11.1",
|
|
196
|
-
"react-hotkeys-hook": "^4.6.
|
|
197
|
+
"react-hotkeys-hook": "^4.6.2",
|
|
197
198
|
"react-resizable-panels": "3.0.6",
|
|
198
199
|
"react-sticky-box": "^2.0.5",
|
|
199
200
|
"recharts": "2.15.4",
|
|
200
201
|
"sonner": "2.0.7",
|
|
201
|
-
"tailwind-merge": "^3.
|
|
202
|
+
"tailwind-merge": "^3.6.0",
|
|
202
203
|
"vaul": "1.1.2"
|
|
203
204
|
},
|
|
204
205
|
"optionalDependencies": {
|
|
205
|
-
"@chenglou/pretext": "
|
|
206
|
+
"@chenglou/pretext": "^0.0.8"
|
|
206
207
|
},
|
|
207
208
|
"devDependencies": {
|
|
208
|
-
"@djangocfg/i18n": "^2.1.
|
|
209
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
210
|
-
"@types/node": "^25.
|
|
211
|
-
"@types/react": "
|
|
212
|
-
"@types/react-dom": "
|
|
209
|
+
"@djangocfg/i18n": "^2.1.466",
|
|
210
|
+
"@djangocfg/typescript-config": "^2.1.466",
|
|
211
|
+
"@types/node": "^25.9.5",
|
|
212
|
+
"@types/react": "19.2.15",
|
|
213
|
+
"@types/react-dom": "19.2.3",
|
|
213
214
|
"lucide-react": "^0.545.0",
|
|
214
|
-
"next": "^16.2.
|
|
215
|
+
"next": "^16.2.10",
|
|
215
216
|
"typescript": "^5.9.3"
|
|
216
217
|
},
|
|
217
218
|
"publishConfig": {
|
|
@@ -48,7 +48,12 @@ const DropdownMenuSubContent = React.forwardRef<
|
|
|
48
48
|
<DropdownMenuPrimitive.SubContent
|
|
49
49
|
ref={ref}
|
|
50
50
|
className={cn(
|
|
51
|
-
|
|
51
|
+
// Height/scroll mirrors DropdownMenuContent below: a long submenu (e.g. a
|
|
52
|
+
// 40-vendor model list) must cap and scroll INSIDE, not overflow off-screen
|
|
53
|
+
// and clip. `min()` of the space Radix measured to the viewport edge and a
|
|
54
|
+
// comfortable ceiling — a menu that spans the WHOLE screen reads as a page,
|
|
55
|
+
// not a menu, so we stop well short of the viewport even when it would fit.
|
|
56
|
+
"z-[700] max-h-[min(var(--radix-dropdown-menu-content-available-height),24rem)] min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
52
57
|
className
|
|
53
58
|
)}
|
|
54
59
|
{...props}
|
|
@@ -66,7 +71,9 @@ const DropdownMenuContent = React.forwardRef<
|
|
|
66
71
|
ref={ref}
|
|
67
72
|
sideOffset={sideOffset}
|
|
68
73
|
className={cn(
|
|
69
|
-
|
|
74
|
+
// See DropdownMenuSubContent: cap at the SMALLER of the space to the
|
|
75
|
+
// viewport edge and a comfortable ceiling, then scroll inside.
|
|
76
|
+
"z-[700] max-h-[min(var(--radix-dropdown-menu-content-available-height),24rem)] min-w-32 overflow-y-auto overflow-x-hidden rounded-[var(--radius-popover)] border bg-popover backdrop-blur-xl p-1 text-popover-foreground shadow-md",
|
|
70
77
|
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
|
71
78
|
className
|
|
72
79
|
)}
|
|
@@ -33,6 +33,7 @@ export function MenuBuilder({
|
|
|
33
33
|
sideOffset,
|
|
34
34
|
alignOffset,
|
|
35
35
|
contentClassName,
|
|
36
|
+
submenuClassName,
|
|
36
37
|
showDescriptions = true,
|
|
37
38
|
}: MenuBuilderProps) {
|
|
38
39
|
return (
|
|
@@ -54,7 +55,7 @@ export function MenuBuilder({
|
|
|
54
55
|
// select-none: menu labels shouldn't be text-selectable (feels crisp).
|
|
55
56
|
className={cn('min-w-56 max-w-xs select-none', contentClassName)}
|
|
56
57
|
>
|
|
57
|
-
{renderItems(items, { showDescriptions })}
|
|
58
|
+
{renderItems(items, { showDescriptions, submenuClassName })}
|
|
58
59
|
</DropdownMenuContent>
|
|
59
60
|
</DropdownMenu>
|
|
60
61
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Fragment, type ReactNode } from 'react';
|
|
2
2
|
|
|
3
|
+
import { cn } from '../../../lib';
|
|
3
4
|
import {
|
|
4
5
|
DropdownMenuCheckboxItem,
|
|
5
6
|
DropdownMenuGroup,
|
|
@@ -18,6 +19,9 @@ import type { MenuItem } from './types';
|
|
|
18
19
|
|
|
19
20
|
interface RenderOptions {
|
|
20
21
|
showDescriptions: boolean;
|
|
22
|
+
/** Applied to every submenu panel at any depth; a `MenuSubmenuItem` may
|
|
23
|
+
* override it per-branch with its own `contentClassName`. */
|
|
24
|
+
submenuClassName?: string;
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/** Drop `hidden` rows before rendering. */
|
|
@@ -83,7 +87,11 @@ export function renderItems(
|
|
|
83
87
|
/>
|
|
84
88
|
</DropdownMenuSubTrigger>
|
|
85
89
|
<DropdownMenuPortal>
|
|
86
|
-
|
|
90
|
+
{/* Per-branch class wins over the builder-wide one. Height +
|
|
91
|
+
scroll come from the primitive — see DropdownMenuSubContent. */}
|
|
92
|
+
<DropdownMenuSubContent
|
|
93
|
+
className={cn(opts.submenuClassName, item.contentClassName)}
|
|
94
|
+
>
|
|
87
95
|
{renderItems(item.items, opts)}
|
|
88
96
|
</DropdownMenuSubContent>
|
|
89
97
|
</DropdownMenuPortal>
|
|
@@ -37,6 +37,12 @@ export interface MenuActionItem extends MenuRowBase {
|
|
|
37
37
|
export interface MenuSubmenuItem extends MenuRowBase {
|
|
38
38
|
kind: 'submenu';
|
|
39
39
|
items: MenuItem[];
|
|
40
|
+
/**
|
|
41
|
+
* Extra class for THIS submenu's panel — overrides the builder-wide
|
|
42
|
+
* `submenuClassName` for one branch (e.g. only the vendor list needs to be
|
|
43
|
+
* wider). Height/scroll are already handled by the primitive.
|
|
44
|
+
*/
|
|
45
|
+
contentClassName?: string;
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
/** A single checkbox row. */
|
|
@@ -135,8 +141,16 @@ export interface MenuBuilderProps {
|
|
|
135
141
|
align?: 'start' | 'center' | 'end';
|
|
136
142
|
sideOffset?: number;
|
|
137
143
|
alignOffset?: number;
|
|
138
|
-
/** Extra class for the content panel. */
|
|
144
|
+
/** Extra class for the ROOT content panel. */
|
|
139
145
|
contentClassName?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Extra class for EVERY submenu panel (applied at any nesting depth).
|
|
148
|
+
* Both root and sub panels already cap their height to the space Radix
|
|
149
|
+
* measured to the viewport edge and scroll inside, so this is for WIDTH and
|
|
150
|
+
* the like — e.g. `sm:max-w-md` when rows carry long ids that would
|
|
151
|
+
* otherwise be clipped by the default `max-w-xs`.
|
|
152
|
+
*/
|
|
153
|
+
submenuClassName?: string;
|
|
140
154
|
/** Render the muted description line on rows that have one. Default: true. */
|
|
141
155
|
showDescriptions?: boolean;
|
|
142
156
|
}
|
package/src/styles/README.md
CHANGED
|
@@ -164,9 +164,16 @@ Typography variables are ordinary CSS values:
|
|
|
164
164
|
## Verification
|
|
165
165
|
|
|
166
166
|
```bash
|
|
167
|
+
pnpm check:contrast
|
|
167
168
|
pnpm check
|
|
168
169
|
```
|
|
169
170
|
|
|
171
|
+
`check:contrast` validates the macOS preset's compact-text semantic pairs at
|
|
172
|
+
WCAG AA 4.5:1 in both modes. It includes primary, destructive, sidebar,
|
|
173
|
+
status-surface, and muted text against every common surface. When tuning a
|
|
174
|
+
token, change the semantic pair together rather than overriding text in a
|
|
175
|
+
consumer component.
|
|
176
|
+
|
|
170
177
|
At the consumer, build the app that imports the preset and verify both modes.
|
|
171
178
|
The important invariant is that changing a preset requires editing one CSS
|
|
172
179
|
file, and no React runtime code is involved in applying it.
|
|
@@ -6,15 +6,18 @@
|
|
|
6
6
|
--card-foreground: hsl(0 0% 7%);
|
|
7
7
|
--popover: hsl(0 0% 100%);
|
|
8
8
|
--popover-foreground: hsl(0 0% 7%);
|
|
9
|
-
|
|
9
|
+
/* Apple system-blue hue, lowered just enough for white small text (4.95:1). */
|
|
10
|
+
--primary: hsl(211 100% 44%);
|
|
10
11
|
--primary-foreground: hsl(0 0% 100%);
|
|
11
12
|
--secondary: hsl(240 12% 95%);
|
|
12
13
|
--secondary-foreground: hsl(0 0% 7%);
|
|
13
14
|
--muted: hsl(240 8% 91%);
|
|
14
|
-
|
|
15
|
+
/* Readable even when muted text sits on the muted fill itself (4.63:1). */
|
|
16
|
+
--muted-foreground: hsl(240 3% 41%);
|
|
15
17
|
--accent: hsl(211 50% 96%);
|
|
16
18
|
--accent-foreground: hsl(211 100% 36%);
|
|
17
|
-
|
|
19
|
+
/* Keep white destructive labels AA-readable in the light theme (5.69:1). */
|
|
20
|
+
--destructive: hsl(2 80% 44%);
|
|
18
21
|
--destructive-foreground: hsl(0 0% 100%);
|
|
19
22
|
--border: hsl(240 3% 78%);
|
|
20
23
|
--input: hsl(240 8% 93%);
|
|
@@ -24,7 +27,7 @@
|
|
|
24
27
|
--overlay: hsl(240 6% 10% / 0.32);
|
|
25
28
|
--sidebar-background: hsl(240 12% 94%);
|
|
26
29
|
--sidebar-foreground: hsl(0 0% 7%);
|
|
27
|
-
--sidebar-primary: hsl(211 100%
|
|
30
|
+
--sidebar-primary: hsl(211 100% 44%);
|
|
28
31
|
--sidebar-primary-foreground: hsl(0 0% 100%);
|
|
29
32
|
--sidebar-accent: hsl(211 50% 93%);
|
|
30
33
|
--sidebar-accent-foreground: hsl(211 100% 36%);
|
|
@@ -76,15 +79,17 @@
|
|
|
76
79
|
--popover: hsl(240 3% 20%);
|
|
77
80
|
--popover-foreground: hsl(0 0% 95%);
|
|
78
81
|
--primary: hsl(211 100% 58%);
|
|
79
|
-
|
|
82
|
+
/* Bright system blue needs dark ink for compact controls (5.59:1). */
|
|
83
|
+
--primary-foreground: hsl(0 0% 9%);
|
|
80
84
|
--secondary: hsl(240 3% 20%);
|
|
81
85
|
--secondary-foreground: hsl(0 0% 88%);
|
|
82
86
|
--muted: hsl(240 3% 11%);
|
|
83
|
-
|
|
87
|
+
/* Clears AA on background, card, popover, muted, and accent surfaces. */
|
|
88
|
+
--muted-foreground: hsl(240 4% 62%);
|
|
84
89
|
--accent: hsl(211 25% 19%);
|
|
85
90
|
--accent-foreground: hsl(211 100% 72%);
|
|
86
91
|
--destructive: hsl(3 100% 62%);
|
|
87
|
-
--destructive-foreground: hsl(0 0%
|
|
92
|
+
--destructive-foreground: hsl(0 0% 9%);
|
|
88
93
|
--border: hsl(240 3% 22%);
|
|
89
94
|
--input: hsl(240 3% 22%);
|
|
90
95
|
--divider: hsl(240 4% 46% / 0.18);
|
|
@@ -94,7 +99,7 @@
|
|
|
94
99
|
--sidebar-background: hsl(240 5% 5%);
|
|
95
100
|
--sidebar-foreground: hsl(0 0% 90%);
|
|
96
101
|
--sidebar-primary: hsl(211 100% 58%);
|
|
97
|
-
--sidebar-primary-foreground: hsl(0 0%
|
|
102
|
+
--sidebar-primary-foreground: hsl(0 0% 9%);
|
|
98
103
|
--sidebar-accent: hsl(211 22% 16%);
|
|
99
104
|
--sidebar-accent-foreground: hsl(211 100% 72%);
|
|
100
105
|
--sidebar-border: hsl(240 3% 22%);
|