@djangocfg/ui-core 2.1.320 → 2.1.322
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 +56 -473
- package/package.json +4 -4
- package/src/components/index.ts +51 -0
- package/src/components/navigation/breadcrumb/breadcrumb-navigation.tsx +127 -0
- package/src/components/navigation/breadcrumb/breadcrumb.tsx +133 -0
- package/src/components/navigation/breadcrumb/index.ts +15 -0
- package/src/components/navigation/pagination/index.ts +18 -0
- package/src/components/navigation/pagination/pagination-static.tsx +249 -0
- package/src/components/navigation/pagination/pagination.tsx +139 -0
- package/src/components/navigation/pagination/ssr-pagination.tsx +176 -0
- package/src/components/navigation/sidebar/index.ts +26 -0
- package/src/components/navigation/sidebar/sidebar.tsx +895 -0
- package/src/components/select/README.md +2 -0
- package/src/components/select/select.tsx +39 -3
- package/src/lib/configurator-schema.ts +105 -0
- package/src/lib/index.ts +7 -0
package/README.md
CHANGED
|
@@ -6,13 +6,9 @@
|
|
|
6
6
|
|
|
7
7
|
# @djangocfg/ui-core
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Framework-agnostic React UI library: 70+ components on Radix + Tailwind v4, plus a router-adapter system that lets the same `<Link>` / `<Sidebar>` / `<SSRPagination>` work under Next.js, Vite, Electron, Wails, or plain React.
|
|
10
10
|
|
|
11
|
-
**
|
|
12
|
-
|
|
13
|
-
**Part of [DjangoCFG](https://djangocfg.com)** — modern Django framework for production-ready SaaS applications.
|
|
14
|
-
|
|
15
|
-
**[Live Demo & Props](https://djangocfg.com/demo/)**
|
|
11
|
+
**Part of [DjangoCFG](https://djangocfg.com).** **[Live demo](https://djangocfg.com/demo/)**.
|
|
16
12
|
|
|
17
13
|
## Install
|
|
18
14
|
|
|
@@ -20,515 +16,102 @@ Pure React UI library with 65+ components built on Radix UI + Tailwind CSS v4.
|
|
|
20
16
|
pnpm add @djangocfg/ui-core
|
|
21
17
|
```
|
|
22
18
|
|
|
23
|
-
##
|
|
24
|
-
|
|
25
|
-
| Package | Use Case |
|
|
26
|
-
|---------|----------|
|
|
27
|
-
| `@djangocfg/ui-core` | Electron, Vite, CRA, any React app |
|
|
28
|
-
| `@djangocfg/ui-tools` | Heavy tools with lazy loading |
|
|
29
|
-
| `@djangocfg/ui-nextjs` | Next.js apps (extends ui-core) |
|
|
30
|
-
|
|
31
|
-
## Components (65+)
|
|
32
|
-
|
|
33
|
-
Components are organized by category in `components/`. All exports are available from the root:
|
|
34
|
-
```tsx
|
|
35
|
-
import { Button, Dialog, Table } from '@djangocfg/ui-core';
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
### Forms (18)
|
|
39
|
-
`Button` `ButtonLink` `ButtonGroup` `Input` `Textarea` `Checkbox` `RadioGroup` `Switch` `Slider` `Label` `Form` `Field` `InputOTP` `PhoneInput` `InputGroup` `DownloadButton` `OTPInput` `Textarea`
|
|
40
|
-
|
|
41
|
-
### Select (8)
|
|
42
|
-
`Select` `Combobox` `MultiSelect` `MultiSelectPro` `MultiSelectProAsync` `CountrySelect` `LanguageSelect`
|
|
43
|
-
|
|
44
|
-
> **Select Components** — All select components now support **icons** and **badges**:
|
|
45
|
-
> - `Select` — Radix primitives with `icon` on trigger/item, `badge` on item
|
|
46
|
-
> - `Combobox` — Searchable single-select with icon + badge in trigger and dropdown
|
|
47
|
-
> - `MultiSelectPro` — Multi-select with colored badges, icons, animations
|
|
48
|
-
>
|
|
49
|
-
> See `components/select/README.md` for full documentation.
|
|
50
|
-
|
|
51
|
-
### Layout (8)
|
|
52
|
-
`Card` `Separator` `Skeleton` `AspectRatio` `Sticky` `ScrollArea` `Resizable` `Section`
|
|
53
|
-
|
|
54
|
-
### Overlay (9)
|
|
55
|
-
`Dialog` `AlertDialog` `Sheet` `Drawer` `Popover` `HoverCard` `Tooltip` `ResponsiveSheet` `SidePanel`
|
|
56
|
-
|
|
57
|
-
**`Dialog`** — `DialogContent` adds two props on top of Radix:
|
|
58
|
-
|
|
59
|
-
- `fullscreen` — drops card chrome and stretches to `100dvw × 100dvh`.
|
|
60
|
-
- `closeButton` — `undefined` (default `X`), `false` (none), or a `ReactNode` (wrap in `DialogClose`).
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
<DialogContent
|
|
64
|
-
fullscreen
|
|
65
|
-
closeButton={
|
|
66
|
-
<DialogClose className="absolute right-4 top-4 inline-flex h-10 w-10 cursor-pointer items-center justify-center rounded-full border bg-background/90 hover:bg-accent">
|
|
67
|
-
<X className="h-4 w-4" />
|
|
68
|
-
</DialogClose>
|
|
69
|
-
}
|
|
70
|
-
>
|
|
71
|
-
…your fullscreen layout…
|
|
72
|
-
</DialogContent>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
**`SidePanel`** — non-modal side drawer (right by default, or left). Surrounding UI stays interactive; Esc-to-close + swipe-to-close via vaul. Prefer `Sheet` for modal side surfaces.
|
|
19
|
+
## Imports
|
|
76
20
|
|
|
77
21
|
```tsx
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
<SidePanel.Title>Details</SidePanel.Title>
|
|
82
|
-
<SidePanel.Close className="ml-auto" />
|
|
83
|
-
</SidePanel.Header>
|
|
84
|
-
<SidePanel.Body>…</SidePanel.Body>
|
|
85
|
-
<SidePanel.Footer>…</SidePanel.Footer>
|
|
86
|
-
</SidePanel.Content>
|
|
87
|
-
</SidePanel>
|
|
22
|
+
import { Button, Card, Sidebar, SSRPagination } from '@djangocfg/ui-core/components';
|
|
23
|
+
import { useIsMobile, useNavigate, useQueryParams } from '@djangocfg/ui-core/hooks';
|
|
24
|
+
import { cn } from '@djangocfg/ui-core/lib';
|
|
88
25
|
```
|
|
89
26
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
<Drawer direction="right">
|
|
94
|
-
<DrawerTrigger asChild><Button>Open</Button></DrawerTrigger>
|
|
95
|
-
<DrawerContent direction="right" size="lg">
|
|
96
|
-
<DrawerHeader>
|
|
97
|
-
<DrawerTitle>Details</DrawerTitle>
|
|
98
|
-
</DrawerHeader>
|
|
99
|
-
…
|
|
100
|
-
</DrawerContent>
|
|
101
|
-
</Drawer>
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
### Navigation (8)
|
|
105
|
-
`Tabs` `Accordion` `Collapsible` `Command` `ContextMenu` `DropdownMenu` `Menubar` `NavigationMenu`
|
|
106
|
-
|
|
107
|
-
`ContextMenuItem`, `DropdownMenuItem`, and `MenubarItem` accept `variant?: 'default' | 'destructive'`. The `destructive` variant turns the row red on focus — use it for delete/remove actions:
|
|
108
|
-
|
|
109
|
-
```tsx
|
|
110
|
-
<DropdownMenuItem variant="destructive" onClick={remove}>
|
|
111
|
-
<Trash className="mr-2 size-4" /> Delete
|
|
112
|
-
</DropdownMenuItem>
|
|
113
|
-
```
|
|
27
|
+
## Components
|
|
114
28
|
|
|
115
|
-
|
|
116
|
-
`Table` `Badge` `Avatar` `Progress` `Calendar` `Carousel` `Chart` `Toggle` `ToggleGroup` `DatePicker`
|
|
29
|
+
Organized in `components/` by category — everything re-exported from the root barrel.
|
|
117
30
|
|
|
118
|
-
|
|
119
|
-
|
|
31
|
+
| Category | Examples |
|
|
32
|
+
|---|---|
|
|
33
|
+
| **Forms** | `Button`, `ButtonLink`, `ButtonGroup`, `Input`, `Textarea`, `Checkbox`, `RadioGroup`, `Switch`, `Slider`, `Label`, `Form`, `Field`, `InputOTP`, `PhoneInput`, `InputGroup`, `DownloadButton` |
|
|
34
|
+
| **Select** | `Select`, `Combobox`, `MultiSelect`, `MultiSelectPro`, `MultiSelectProAsync`, `CountrySelect`, `LanguageSelect` (all support icons + badges; `Select` accepts empty-string values) |
|
|
35
|
+
| **Overlay** | `Dialog`, `AlertDialog`, `Sheet`, `Drawer`, `Popover`, `Tooltip`, `HoverCard`, `ResponsiveSheet`, `SidePanel` |
|
|
36
|
+
| **Navigation** | `Link`, `Breadcrumb`, `BreadcrumbNavigation`, `Pagination`, `StaticPagination`, `SSRPagination`, `Sidebar` (full shadcn primitives), `Tabs`, `Accordion`, `Collapsible`, `Command`, `DropdownMenu`, `ContextMenu`, `Menubar`, `NavigationMenu` |
|
|
37
|
+
| **Layout** | `Card`, `Section`, `Sticky`, `ScrollArea`, `Resizable`, `Separator`, `Skeleton`, `AspectRatio` |
|
|
38
|
+
| **Data** | `Table`, `Badge`, `Avatar`, `Progress`, `Carousel`, `Calendar`, `DatePicker`, `DateRangePicker`, `Toggle`, `ToggleGroup`, `Chart*` |
|
|
39
|
+
| **Feedback** | `Alert`, `Spinner`, `Empty`, `Preloader`, `Toaster` (Sonner) |
|
|
40
|
+
| **Specialized** | `Kbd`, `CopyButton`, `CopyField`, `TokenIcon`, `Item`, `Portal`, `ImageWithFallback`, `Flag`, `LanguageFlag` |
|
|
41
|
+
| **Effects** | `GlowBackground` |
|
|
120
42
|
|
|
121
|
-
|
|
122
|
-
`Kbd` `TokenIcon` `Item` `Portal` `ImageWithFallback` `CopyButton` `CopyField` `Flag` `LanguageFlag`
|
|
43
|
+
> Pagination, breadcrumb and sidebar live here (not in ui-nextjs). `SSRPagination` reads URL state through `useLocation` + `useQueryParams`, so it works under any router adapter.
|
|
123
44
|
|
|
124
|
-
|
|
45
|
+
## Hooks
|
|
125
46
|
|
|
126
47
|
```tsx
|
|
127
|
-
|
|
128
|
-
|
|
48
|
+
import { useIsMobile, useMediaQuery, useShortcutModLabel } from '@djangocfg/ui-core/hooks';
|
|
49
|
+
import { useNavigate, useLocation, useQueryParams, useRouter, useIsActive } from '@djangocfg/ui-core/hooks/router';
|
|
129
50
|
```
|
|
130
51
|
|
|
131
|
-
Helper: `getLanguageCountryCode(locale)` returns the resolved alpha-2 code (or `null`). The static `LANGUAGE_TO_COUNTRY` map is exported for callers that need their own lookup.
|
|
132
|
-
|
|
133
|
-
## Hooks (30+)
|
|
134
|
-
|
|
135
|
-
Hooks are organized by domain inside the package (`src/hooks/<group>/`).
|
|
136
|
-
Public import path is the single barrel:
|
|
137
|
-
|
|
138
|
-
```tsx
|
|
139
|
-
import { useIsMobile, useScroll, useNavigate } from '@djangocfg/ui-core/hooks';
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
### `dom/` — DOM & viewport
|
|
143
|
-
|
|
144
|
-
| Hook | Description |
|
|
145
|
-
|------|-------------|
|
|
146
|
-
| `useScroll(target?)` | Reactive `{ x, y, direction, isScrolling }` for window or any scrollable element. `useSyncExternalStore` + module-level shared store + rAF throttle + passive listener. |
|
|
147
|
-
| `useScrollPosition(target?)` / `useScrollDirection(target?)` / `useIsScrolling(target?)` | Single-field variants — re-render only when their slice changes. |
|
|
148
|
-
| `useBodyScrollLock(locked)` | Lock body scroll while `locked=true`; counter-based (multi-consumer safe), iOS-safe via `position: fixed` fallback. |
|
|
149
|
-
| `useCopy` | Copy to clipboard. |
|
|
150
|
-
| `useImageLoader` | Image loading state. |
|
|
151
|
-
|
|
152
|
-
### `media/` — viewport size
|
|
153
|
-
|
|
154
|
-
| Hook | Description |
|
|
155
|
-
|------|-------------|
|
|
156
|
-
| `useMediaQuery(query)` | Raw media query — pass any CSS query string. Exports `BREAKPOINTS` constants (Tailwind v4 defaults). |
|
|
157
|
-
| `useIsPhone()` | `< 640px` — phones only. |
|
|
158
|
-
| `useIsMobile()` | `< 768px` — phones + small tablets. |
|
|
159
|
-
| `useIsTabletOrBelow()` | `< 1024px` — phones + tablets. |
|
|
160
|
-
|
|
161
|
-
### `state/` — state primitives
|
|
162
|
-
|
|
163
|
-
| Hook | Description |
|
|
164
|
-
|------|-------------|
|
|
165
|
-
| `useDebounce` | Debounce values. |
|
|
166
|
-
| `useDebouncedCallback` | Debounced callbacks. |
|
|
167
|
-
| `useLocalStorage` / `useSessionStorage` | Type-safe wrappers with TTL. |
|
|
168
|
-
| `useStoredValue` | Unified API over local/session storage. |
|
|
169
|
-
|
|
170
|
-
### `device/` — environment detection
|
|
171
|
-
|
|
172
|
-
| Hook | Description |
|
|
173
|
-
|------|-------------|
|
|
174
|
-
| `useBrowserDetect` | Browser detection (Chrome, Safari, in-app browsers, etc.). |
|
|
175
|
-
| `useDeviceDetect` | Device detection (mobile, tablet, desktop, OS, etc.). |
|
|
176
|
-
| `useShortcutModLabel()` | Returns `⌘` or `Ctrl` for shortcut hints (Apple vs Windows/Linux); pairs with `metaKey \|\| ctrlKey` handlers. |
|
|
177
|
-
|
|
178
|
-
### Other groups
|
|
179
|
-
|
|
180
52
|
| Group | Hooks |
|
|
181
|
-
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
187
|
-
|
|
|
188
|
-
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
import { useMediaQuery, useIsPhone, useIsMobile, BREAKPOINTS } from '@djangocfg/ui-core/hooks'
|
|
192
|
-
|
|
193
|
-
// semantic
|
|
194
|
-
const isPhone = useIsPhone() // < 640px
|
|
195
|
-
const isMobile = useIsMobile() // < 768px
|
|
196
|
-
|
|
197
|
-
// custom with constants
|
|
198
|
-
const isNarrow = useMediaQuery(`(max-width: ${BREAKPOINTS.sm - 1}px)`)
|
|
199
|
-
const isDark = useMediaQuery('(prefers-color-scheme: dark)')
|
|
53
|
+
|---|---|
|
|
54
|
+
| **Router** (adapter-driven) | `useNavigate`, `useLocation`, `useQueryParams`, `useQueryState` (typed), `useRouter`, `useUrlBuilder`, `useSmartLink`, `useIsActive`, `useBackOrFallback` + parsers (`parseAsInteger`, `parseAsBoolean`, …) |
|
|
55
|
+
| **Media** | `useIsMobile`, `useIsPhone`, `useIsTabletOrBelow`, `useMediaQuery` |
|
|
56
|
+
| **Device** | `useDeviceDetect`, `useBrowserDetect`, `useShortcutModLabel` |
|
|
57
|
+
| **State** | `useDebounce`, `useDebouncedCallback`, `useCountdown`, `useImageLoader`, `useMounted` |
|
|
58
|
+
| **DOM** | `useEventListener` |
|
|
59
|
+
| **Theme** | `useThemeColor`, `useThemePalette` (palette-aware hex colors for Canvas/SVG) |
|
|
60
|
+
| **Hotkey** | `useHotkey`, `HotkeysProvider`, `useHotkeysContext` |
|
|
61
|
+
| **Feedback** | `useToast`, `toast` (Sonner) |
|
|
62
|
+
| **Debug** | `useDebugTools` |
|
|
200
63
|
|
|
201
|
-
|
|
202
|
-
const { direction, isScrolling } = useScroll()
|
|
203
|
-
const hideNav = direction === 'down' && isScrolling
|
|
204
|
-
```
|
|
64
|
+
## Router adapters
|
|
205
65
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
Framework-agnostic navigation primitives — work on plain History API by default, plug into any router via the adapter pattern. Full docs in [`src/hooks/router/README.md`](./src/hooks/router/README.md).
|
|
209
|
-
|
|
210
|
-
| Hook | Purpose |
|
|
211
|
-
|------|---------|
|
|
212
|
-
| `useLocation()` | Reactive `{ pathname, search, hash, href }`. |
|
|
213
|
-
| `useLocationProperty(get, getSsr)` | Subscribe to ONE field — skip re-renders on unrelated changes. |
|
|
214
|
-
| `useNavigate()` | `navigate`, `navigateExternal`, `push`, `replace`, `back`, `forward`. |
|
|
215
|
-
| `useQueryParams()` | Record-style read/write of `?key=value` URL state. |
|
|
216
|
-
| `useQueryState(key, parser)` | Typed `useState`-style hook bound to one URL key (with `clearOnDefault`). |
|
|
217
|
-
| `useBackOrFallback()` | Smart Back that falls back to a route when there's no in-app history. |
|
|
218
|
-
| `useUrlBuilder()` | Pure URL assembly: `build`, `withCurrentParams`. |
|
|
219
|
-
| `useSmartLink(href)` | Make any element a link (cmd-click, middle-click, Enter, Space). |
|
|
220
|
-
| `useIsActive(href)` | Boolean for nav-item highlighting. |
|
|
221
|
-
| `useRouter()` | Convenience facade composing everything. |
|
|
222
|
-
| `RouterAdapterProvider` | Swap the navigation backend. |
|
|
223
|
-
| `parseAsString` / `parseAsInteger` / `parseAsFloat` / `parseAsBoolean` / `parseAsIsoDate` / `parseAsStringEnum` / `parseAsArrayOf` / `parseAsJson` | Parsers for `useQueryState`. Each has `.withDefault(value)`. |
|
|
66
|
+
`<Link>`, `<Sidebar>`, `<SSRPagination>` and friends are wired through a small adapter system, so the same component renders correctly under any router.
|
|
224
67
|
|
|
225
68
|
```tsx
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
parseAsInteger,
|
|
230
|
-
} from '@djangocfg/ui-core/hooks';
|
|
231
|
-
|
|
232
|
-
const { navigate } = useNavigate();
|
|
233
|
-
const [page, setPage] = useQueryState('page', parseAsInteger.withDefault(1));
|
|
69
|
+
// Next.js host (e.g. apps/demo)
|
|
70
|
+
import { LinkProvider } from '@djangocfg/ui-core/components';
|
|
71
|
+
import NextLink from 'next/link';
|
|
234
72
|
|
|
235
|
-
|
|
236
|
-
setPage((p) => p + 1); // ?page=2 — `page=1` is dropped (clearOnDefault)
|
|
73
|
+
<LinkProvider value={NextLink}>{children}</LinkProvider>
|
|
237
74
|
```
|
|
238
75
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
In Next apps, mount both adapters once near the root so navigation flows through `next/navigation` (server components + prefetch keep working) and `<Link>` delegates to `next/link`:
|
|
76
|
+
When no adapter is mounted, `<Link>` falls back to a plain `<a>` and routes clicks through the History API. `@djangocfg/layouts/BaseApp` mounts the Next adapters by default.
|
|
242
77
|
|
|
243
|
-
|
|
244
|
-
import { NextRouterAdapter, NextLinkProvider } from '@djangocfg/ui-core/adapters/nextjs';
|
|
245
|
-
|
|
246
|
-
<NextRouterAdapter>
|
|
247
|
-
<NextLinkProvider>
|
|
248
|
-
<App />
|
|
249
|
-
</NextLinkProvider>
|
|
250
|
-
</NextRouterAdapter>
|
|
251
|
-
```
|
|
78
|
+
## Schema-driven configurators
|
|
252
79
|
|
|
253
|
-
`
|
|
254
|
-
|
|
255
|
-
## Theme Palette Hooks
|
|
256
|
-
|
|
257
|
-
Hooks for accessing theme colors from CSS variables (useful for Canvas, SVG, charts, diagrams, etc.):
|
|
258
|
-
|
|
259
|
-
| Hook / Util | Description |
|
|
260
|
-
|-------------|-------------|
|
|
261
|
-
| `useThemePalette()` | Full hex color palette from CSS variables |
|
|
262
|
-
| `useThemeColor(var, opacity?)` | Single color by CSS var name — lighter alternative |
|
|
263
|
-
| `useStylePresets()` | Pre-built `{ fill, stroke, color }` configs for diagrams |
|
|
264
|
-
| `useBoxColors()` | Semi-transparent RGBA colors for boxes/containers |
|
|
265
|
-
| `alpha(hex, opacity)` | Convert hex color to `rgba()` string |
|
|
80
|
+
`@djangocfg/ui-core/lib` exports a portable JSON Schema 7 subset (`CustomJsonSchema7`, `CustomJsonUiSchema7`, `CustomJsonUiGroup`, `CustomJsonUiDisabledWhenRule`) for packages that ship configurator schemas without taking a runtime dependency on RJSF.
|
|
266
81
|
|
|
267
82
|
```tsx
|
|
268
|
-
import {
|
|
269
|
-
useThemePalette,
|
|
270
|
-
useThemeColor,
|
|
271
|
-
useStylePresets,
|
|
272
|
-
useBoxColors,
|
|
273
|
-
alpha,
|
|
274
|
-
} from '@djangocfg/ui-core/styles/palette';
|
|
275
|
-
|
|
276
|
-
function MyCanvas() {
|
|
277
|
-
const palette = useThemePalette();
|
|
278
|
-
|
|
279
|
-
// Use in Canvas / inline styles
|
|
280
|
-
ctx.fillStyle = palette.primary; // '#a855f7' (theme-aware)
|
|
281
|
-
ctx.fillStyle = alpha(palette.primary, 0.3); // 'rgba(168, 85, 247, 0.3)'
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
function MyWaveform() {
|
|
285
|
-
// Lighter: only subscribes to 'primary', not the entire palette
|
|
286
|
-
const primary = useThemeColor('primary'); // '#a855f7'
|
|
287
|
-
const primaryFaded = useThemeColor('primary', 0.3); // 'rgba(168, 85, 247, 0.3)'
|
|
288
|
-
const errorBackground = useThemeColor('destructive', 0.1);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
function MyChart() {
|
|
292
|
-
const presets = useStylePresets();
|
|
293
|
-
// presets.primary = { fill: '#a855f7', stroke: '#a855f7', color: '#fff' }
|
|
294
|
-
// presets.success = { fill: '#22c55e', stroke: '#22c55e', color: '#fff' }
|
|
295
|
-
// presets.danger = { fill: '#ef4444', stroke: '#ef4444', color: '#fff' }
|
|
296
|
-
// presets.warning = { fill: '#f59e0b', stroke: '#f59e0b', color: '#fff' }
|
|
297
|
-
// presets.info = { fill: '#3b82f6', stroke: '#3b82f6', color: '#fff' }
|
|
298
|
-
|
|
299
|
-
const boxes = useBoxColors();
|
|
300
|
-
// boxes.primary = 'rgba(168, 85, 247, 0.15)' (0.3 in dark mode)
|
|
301
|
-
// boxes.success = 'rgba(34, 197, 94, 0.15)'
|
|
302
|
-
// etc.
|
|
303
|
-
|
|
304
|
-
return <Chart colors={[presets.success.fill, presets.warning.fill]} />;
|
|
305
|
-
}
|
|
83
|
+
import type { CustomJsonSchema7, CustomJsonUiSchema7 } from '@djangocfg/ui-core/lib';
|
|
306
84
|
```
|
|
307
85
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
```tsx
|
|
311
|
-
import { hslToHex, hslToRgbString, hslToRgba } from '@djangocfg/ui-core/styles/palette';
|
|
312
|
-
|
|
313
|
-
hslToHex('217 91% 60%'); // '#3b82f6'
|
|
314
|
-
hslToRgbString('217 91% 60%'); // 'rgb(59, 130, 246)'
|
|
315
|
-
hslToRgba('217 91% 60%', 0.5); // 'rgba(59, 130, 246, 0.5)'
|
|
316
|
-
```
|
|
86
|
+
`@djangocfg/ui-tools/json-form` accepts these directly (it's a union with `RJSFSchema`).
|
|
317
87
|
|
|
318
|
-
## Dialog
|
|
319
|
-
|
|
320
|
-
Zustand-powered dialog service replacing native `window.alert`, `window.confirm`, `window.prompt` with shadcn dialogs. Also provides `window.dialog.auth()` for triggering authentication dialogs.
|
|
88
|
+
## Dialog service
|
|
321
89
|
|
|
322
90
|
```tsx
|
|
323
|
-
import { DialogProvider, useDialog } from '@djangocfg/ui-core/lib/dialog-service';
|
|
324
|
-
|
|
325
|
-
// Wrap your app with DialogProvider (already included in BaseApp)
|
|
326
|
-
function App() {
|
|
327
|
-
return (
|
|
328
|
-
<DialogProvider>
|
|
329
|
-
<YourApp />
|
|
330
|
-
</DialogProvider>
|
|
331
|
-
);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Use via React hook
|
|
335
|
-
function Component() {
|
|
336
|
-
const { alert, confirm, prompt, auth } = useDialog();
|
|
337
|
-
|
|
338
|
-
const handleDelete = async () => {
|
|
339
|
-
const confirmed = await confirm({
|
|
340
|
-
title: 'Delete item?',
|
|
341
|
-
message: 'This action cannot be undone.',
|
|
342
|
-
variant: 'destructive',
|
|
343
|
-
});
|
|
344
|
-
if (confirmed) {
|
|
345
|
-
// Delete...
|
|
346
|
-
}
|
|
347
|
-
};
|
|
348
|
-
|
|
349
|
-
const handleProtected = async () => {
|
|
350
|
-
const didAuth = await auth({ message: 'Please sign in to continue' });
|
|
351
|
-
if (didAuth) {
|
|
352
|
-
// User navigated to auth
|
|
353
|
-
}
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Or use globally from anywhere (vanilla JS, libraries, etc.)
|
|
358
|
-
window.dialog.alert({ message: 'Hello!' });
|
|
359
|
-
const ok = await window.dialog.confirm({ message: 'Are you sure?' });
|
|
360
|
-
const name = await window.dialog.prompt({ message: 'Enter your name:' });
|
|
361
|
-
const didAuth = await window.dialog.auth({ message: 'Session expired' });
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
## Usage
|
|
91
|
+
import { DialogProvider, useDialog, dialog } from '@djangocfg/ui-core/lib/dialog-service';
|
|
365
92
|
|
|
366
|
-
|
|
367
|
-
import { Button, Card, Input } from '@djangocfg/ui-core';
|
|
368
|
-
import { toast } from '@djangocfg/ui-core/hooks';
|
|
369
|
-
|
|
370
|
-
function Example() {
|
|
371
|
-
return (
|
|
372
|
-
<Card>
|
|
373
|
-
<Input placeholder="Email" />
|
|
374
|
-
<Button onClick={() => toast.success('Saved!')}>
|
|
375
|
-
Submit
|
|
376
|
-
</Button>
|
|
377
|
-
</Card>
|
|
378
|
-
);
|
|
379
|
-
}
|
|
93
|
+
dialog.confirm({ title: 'Delete?', description: 'This is permanent.' });
|
|
380
94
|
```
|
|
381
95
|
|
|
382
|
-
##
|
|
96
|
+
## Logger
|
|
383
97
|
|
|
384
98
|
```tsx
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
function App() {
|
|
390
|
-
const isMobile = useMediaQuery('(max-width: 768px)');
|
|
391
|
-
|
|
392
|
-
return (
|
|
393
|
-
<Dialog>
|
|
394
|
-
<Button>Open Dialog</Button>
|
|
395
|
-
</Dialog>
|
|
396
|
-
);
|
|
397
|
-
}
|
|
99
|
+
import { createLogger } from '@djangocfg/ui-core/lib';
|
|
100
|
+
const log = createLogger('MyComponent');
|
|
101
|
+
log.info('user logged in', { userId: 123 });
|
|
398
102
|
```
|
|
399
103
|
|
|
400
|
-
##
|
|
401
|
-
|
|
402
|
-
In your app's `globals.css`, import the package styles and add `@source` directives for every workspace package that ships Tailwind classes. Tailwind v4 does **not** scan `node_modules` automatically.
|
|
104
|
+
## Styles
|
|
403
105
|
|
|
404
106
|
```css
|
|
405
|
-
|
|
406
|
-
@import "@djangocfg/ui-nextjs/styles"; /* ui-core + ui-nextjs tokens & theme */
|
|
407
|
-
@import "@djangocfg/layouts/styles"; /* layout tokens */
|
|
408
|
-
@import "@djangocfg/ui-tools/styles"; /* heavy tool components */
|
|
409
|
-
@import "@djangocfg/debuger/styles"; /* debug panel (if used) */
|
|
410
|
-
@import "tailwindcss";
|
|
411
|
-
```
|
|
412
|
-
|
|
413
|
-
Each package that ships Tailwind classes exposes a `./styles` entry containing a single `@source` directive — no manual path configuration needed.
|
|
414
|
-
|
|
415
|
-
For non-Next.js (Electron, Vite):
|
|
416
|
-
|
|
417
|
-
```tsx
|
|
418
|
-
import '@djangocfg/ui-core/styles/globals';
|
|
419
|
-
```
|
|
420
|
-
|
|
421
|
-
## Exports
|
|
422
|
-
|
|
423
|
-
| Path | Content |
|
|
424
|
-
|------|---------|
|
|
425
|
-
| `@djangocfg/ui-core` | All components & hooks |
|
|
426
|
-
| `@djangocfg/ui-core/components` | Components only |
|
|
427
|
-
| `@djangocfg/ui-core/hooks` | Hooks only (incl. router hooks) |
|
|
428
|
-
| `@djangocfg/ui-core/adapters/nextjs` | `<NextRouterAdapter>` + `<NextLinkProvider>` for Next.js apps (optional peer: `next`) |
|
|
429
|
-
| `@djangocfg/ui-core/lib` | Utilities (cn, etc.) |
|
|
430
|
-
| `@djangocfg/ui-core/lib/dialog-service` | Dialog service |
|
|
431
|
-
| `@djangocfg/ui-core/utils` | Runtime utilities (emitRuntimeError) |
|
|
432
|
-
| `@djangocfg/ui-core/styles` | CSS |
|
|
433
|
-
| `@djangocfg/ui-core/styles/palette` | Theme palette hooks & utilities |
|
|
434
|
-
|
|
435
|
-
## Persisted UI State
|
|
436
|
-
|
|
437
|
-
Centralized localStorage-backed store for component preferences. One zustand+persist store under `djangocfg.ui.state`, scoped by category. Single migration surface, single «reset all» button.
|
|
438
|
-
|
|
439
|
-
### Per-component hooks
|
|
440
|
-
|
|
441
|
-
```tsx
|
|
442
|
-
// Drawer size
|
|
443
|
-
const drawer = useDrawerSize('settings', { min: 320, max: 900 });
|
|
444
|
-
<DrawerContent resizable resizedSize={drawer.size} onSizeChange={drawer.setSize} />
|
|
445
|
-
|
|
446
|
-
// Tabs
|
|
447
|
-
const { tab, setTab } = useTabsState('settings-page', 'general', {
|
|
448
|
-
allowed: ['general', 'appearance', 'advanced'],
|
|
449
|
-
});
|
|
450
|
-
<Tabs value={tab} onValueChange={setTab}>...</Tabs>
|
|
451
|
-
|
|
452
|
-
// Accordion
|
|
453
|
-
const { value, setValue } = useAccordionMultipleState('sidebar', ['general']);
|
|
454
|
-
<Accordion type="multiple" value={value} onValueChange={setValue}>...</Accordion>
|
|
107
|
+
@import '@djangocfg/ui-core/styles/globals.css';
|
|
455
108
|
```
|
|
456
109
|
|
|
457
|
-
Need persist for something not on this list (table prefs, sidebar state, etc.)? Use the generic `useUIPersistedState` directly — it's the same API every per-component hook is built on top of.
|
|
458
|
-
|
|
459
|
-
### Generic API
|
|
460
|
-
|
|
461
|
-
```tsx
|
|
462
|
-
import {
|
|
463
|
-
useUIPersistedState,
|
|
464
|
-
useUIPersistedStateThrottled,
|
|
465
|
-
useUIPersistStore,
|
|
466
|
-
} from '@djangocfg/ui-core';
|
|
467
|
-
|
|
468
|
-
// Build your own per-component hook
|
|
469
|
-
const { value, setValue, reset, hydrated } = useUIPersistedState(
|
|
470
|
-
'my-scope', 'instance-key', defaultValue,
|
|
471
|
-
{ sanitize: (raw) => /* clamp / validate / migrate */ raw },
|
|
472
|
-
);
|
|
473
|
-
|
|
474
|
-
// Throttled writes for high-frequency updates (live splitter, etc.)
|
|
475
|
-
useUIPersistedStateThrottled('splitter', 'main', 50, 200 /* ms */);
|
|
476
|
-
```
|
|
477
|
-
|
|
478
|
-
### Inspection / DevTools
|
|
479
|
-
|
|
480
|
-
```tsx
|
|
481
|
-
useUIPersistStore.getState().getAll(); // snapshot of all scopes
|
|
482
|
-
useUIPersistStore.getState().listScopes(); // ['drawer-size:width', 'tabs', ...]
|
|
483
|
-
useUIPersistStore.getState().clearScope('tabs');
|
|
484
|
-
useUIPersistStore.getState().clearAll(); // reset all UI preferences
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
## Runtime Error Emitter
|
|
488
|
-
|
|
489
|
-
Emit runtime errors as events (caught by ErrorTrackingProvider in layouts):
|
|
490
|
-
|
|
491
|
-
```tsx
|
|
492
|
-
import { emitRuntimeError } from '@djangocfg/ui-core/utils';
|
|
493
|
-
|
|
494
|
-
try {
|
|
495
|
-
doSomething();
|
|
496
|
-
} catch (error) {
|
|
497
|
-
emitRuntimeError('MyComponent', 'Operation failed', error, { extra: 'context' });
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
## Links
|
|
502
|
-
|
|
503
|
-
`<Link>` and `<ButtonLink>` ship in `ui-core` itself — no Next.js required.
|
|
504
|
-
Default behavior renders `<a>` and routes clicks through `useNavigate`.
|
|
505
|
-
|
|
506
|
-
In Next.js apps, mount `NextLinkProvider` (from `@djangocfg/ui-core/adapters/nextjs`)
|
|
507
|
-
near the root and the same components delegate to `next/link` automatically.
|
|
508
|
-
`@djangocfg/layouts/BaseApp` does this for you.
|
|
509
|
-
|
|
510
|
-
```tsx
|
|
511
|
-
import { Link, ButtonLink } from '@djangocfg/ui-core/components';
|
|
512
|
-
|
|
513
|
-
<Link href="/about">About</Link>
|
|
514
|
-
<ButtonLink href="/docs" variant="outline">Docs</ButtonLink>
|
|
515
|
-
```
|
|
516
|
-
|
|
517
|
-
## What's NOT included (use ui-nextjs)
|
|
518
|
-
|
|
519
|
-
These features require Next.js or browser storage APIs:
|
|
520
|
-
|
|
521
|
-
- `Sidebar` — `'use client'` heavy, lives in ui-nextjs
|
|
522
|
-
- `Breadcrumb`, `BreadcrumbNavigation` — same
|
|
523
|
-
- `Pagination`, `SSRPagination` — same
|
|
524
|
-
- `DownloadButton` — uses localStorage
|
|
525
|
-
- `useTheme` — uses next-themes
|
|
526
|
-
|
|
527
110
|
## Requirements
|
|
528
111
|
|
|
529
|
-
- React
|
|
530
|
-
- Tailwind CSS
|
|
112
|
+
- React ≥ 19
|
|
113
|
+
- Tailwind CSS ≥ 4
|
|
531
114
|
|
|
532
115
|
---
|
|
533
116
|
|
|
534
|
-
**[
|
|
117
|
+
**[djangocfg.com](https://djangocfg.com)**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.322",
|
|
4
4
|
"description": "Pure React UI component library without Next.js dependencies - for Electron, Vite, CRA apps",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"playground": "playground dev"
|
|
92
92
|
},
|
|
93
93
|
"peerDependencies": {
|
|
94
|
-
"@djangocfg/i18n": "^2.1.
|
|
94
|
+
"@djangocfg/i18n": "^2.1.322",
|
|
95
95
|
"consola": "^3.4.2",
|
|
96
96
|
"lucide-react": "^0.545.0",
|
|
97
97
|
"moment": "^2.30.1",
|
|
@@ -160,9 +160,9 @@
|
|
|
160
160
|
"vaul": "1.1.2"
|
|
161
161
|
},
|
|
162
162
|
"devDependencies": {
|
|
163
|
-
"@djangocfg/i18n": "^2.1.
|
|
163
|
+
"@djangocfg/i18n": "^2.1.322",
|
|
164
164
|
"@djangocfg/playground": "workspace:*",
|
|
165
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
165
|
+
"@djangocfg/typescript-config": "^2.1.322",
|
|
166
166
|
"@types/node": "^24.7.2",
|
|
167
167
|
"@types/react": "^19.1.0",
|
|
168
168
|
"@types/react-dom": "^19.1.0",
|
package/src/components/index.ts
CHANGED
|
@@ -66,6 +66,57 @@ export { Collapsible, CollapsibleContent, CollapsibleTrigger } from './navigatio
|
|
|
66
66
|
export { Command, CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut } from './navigation/command';
|
|
67
67
|
export { Link, LinkProvider, LinkComponentContext, useLinkComponent } from './navigation/link';
|
|
68
68
|
export type { LinkProps, LinkComponent, LinkComponentProps, LinkProviderProps } from './navigation/link';
|
|
69
|
+
export {
|
|
70
|
+
Breadcrumb,
|
|
71
|
+
BreadcrumbEllipsis,
|
|
72
|
+
BreadcrumbItem,
|
|
73
|
+
BreadcrumbLink,
|
|
74
|
+
BreadcrumbList,
|
|
75
|
+
BreadcrumbPage,
|
|
76
|
+
BreadcrumbSeparator,
|
|
77
|
+
BreadcrumbNavigation,
|
|
78
|
+
} from './navigation/breadcrumb';
|
|
79
|
+
export type { BreadcrumbNavigationItem, BreadcrumbNavigationProps } from './navigation/breadcrumb';
|
|
80
|
+
export {
|
|
81
|
+
Pagination,
|
|
82
|
+
PaginationContent,
|
|
83
|
+
PaginationEllipsis,
|
|
84
|
+
PaginationItem,
|
|
85
|
+
PaginationLink,
|
|
86
|
+
PaginationNext,
|
|
87
|
+
PaginationPrevious,
|
|
88
|
+
StaticPagination,
|
|
89
|
+
SSRPagination,
|
|
90
|
+
useDRFPagination,
|
|
91
|
+
useDRFPaginationInfo,
|
|
92
|
+
} from './navigation/pagination';
|
|
93
|
+
export type { DRFPaginatedResponse } from './navigation/pagination';
|
|
94
|
+
export {
|
|
95
|
+
Sidebar,
|
|
96
|
+
SidebarContent,
|
|
97
|
+
SidebarFooter,
|
|
98
|
+
SidebarGroup,
|
|
99
|
+
SidebarGroupAction,
|
|
100
|
+
SidebarGroupContent,
|
|
101
|
+
SidebarGroupLabel,
|
|
102
|
+
SidebarHeader,
|
|
103
|
+
SidebarInput,
|
|
104
|
+
SidebarInset,
|
|
105
|
+
SidebarMenu,
|
|
106
|
+
SidebarMenuAction,
|
|
107
|
+
SidebarMenuBadge,
|
|
108
|
+
SidebarMenuButton,
|
|
109
|
+
SidebarMenuItem,
|
|
110
|
+
SidebarMenuSkeleton,
|
|
111
|
+
SidebarMenuSub,
|
|
112
|
+
SidebarMenuSubButton,
|
|
113
|
+
SidebarMenuSubItem,
|
|
114
|
+
SidebarProvider,
|
|
115
|
+
SidebarRail,
|
|
116
|
+
SidebarSeparator,
|
|
117
|
+
SidebarTrigger,
|
|
118
|
+
useSidebar,
|
|
119
|
+
} from './navigation/sidebar';
|
|
69
120
|
|
|
70
121
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
71
122
|
// Layout
|