@djangocfg/ui-core 2.1.319 → 2.1.321

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
@@ -6,13 +6,9 @@
6
6
 
7
7
  # @djangocfg/ui-core
8
8
 
9
- Pure React UI library with 65+ components built on Radix UI + Tailwind CSS v4.
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
- **No Next.js dependencies** — works with Electron, Vite, CRA, and any React environment.
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
- ## Why ui-core?
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
- <SidePanel open={open} onOpenChange={setOpen} side="right">
79
- <SidePanel.Content width="440px">
80
- <SidePanel.Header>
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
- **`Drawer`** — modal vaul panel sliding from any edge (`top` / `right` / `bottom` / `left`). Sizes: `sm` `md` `lg` `xl` `full`, default `md`; or pass an explicit `width` / `height`. Pass `resizable` to drag the inner edge — auto-disabled on mobile (< 768px) unless `resizableOnDesktopOnly={false}`. Tune with `minSize` / `maxSize` (px). Resize is controlled via `resizedSize` + `onSizeChange` (uncontrolled if omitted). For built-in `localStorage` persistence use the `useDrawerSize(key, { axis, min, max })` hook — it returns `{ size, setSize, reset }` you wire into the same props. Backed by the centralized `useUIPersistedState` / `useUIPersistStore` (see below).
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
- ### Data (10)
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
- ### Feedback (5)
119
- `Alert` `Toaster` (Sonner) `Spinner` `Empty` `Preloader`
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
- ### Specialized (9)
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
- **`Flag`** / **`LanguageFlag`** — SVG country flags (3:2 aspect) backed by `country-flag-icons`. `Flag` takes an ISO 3166-1 alpha-2 `countryCode` and covers every ISO country (~270 entries); `LanguageFlag` resolves a locale (`'en'`, `'pt-BR'`, `'zh_CN'`) to a country and renders the flag. Both render `null` for unknown codes. Used by `LocaleSwitcher`, `PhoneInput`, and `CountrySelect` — no emoji fallbacks anywhere.
45
+ ## Hooks
125
46
 
126
47
  ```tsx
127
- <Flag countryCode="JP" rounded className="h-3 w-4" />
128
- <LanguageFlag code="pt-BR" rounded className="h-3 w-4" />
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
- | `feedback/` | `useToast`, `toast` (Sonner). |
183
- | `theme/` | `useResolvedTheme` current resolved theme (light/dark/system). |
184
- | `time/` | `useCountdown`, `useCountdownFromSeconds`. |
185
- | `events/` | `useEventListener`, `events` (PubSub bus). |
186
- | `hotkey/` | `useHotkey`, `HotkeysProvider` (react-hotkeys-hook). |
187
- | `debug/` | `useDebugTools`. |
188
- | `router/` | See [Router Hooks](#router-hooks) below — its own subsection. |
189
-
190
- ```tsx
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
- // scroll snapshot — direction-aware navbar
202
- const { direction, isScrolling } = useScroll()
203
- const hideNav = direction === 'down' && isScrolling
204
- ```
64
+ ## Router adapters
205
65
 
206
- ## Router Hooks
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
- import {
227
- useNavigate,
228
- useQueryState,
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
- navigate('/products');
236
- setPage((p) => p + 1); // ?page=2 — `page=1` is dropped (clearOnDefault)
73
+ <LinkProvider value={NextLink}>{children}</LinkProvider>
237
74
  ```
238
75
 
239
- ### Next.js adapter
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
- ```tsx
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
- `next` is an **optional peer dependency** Wails / Electron / Vite consumers don't pull it in. `@djangocfg/layouts/BaseApp` mounts both adapters automatically.
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
- ### Color Utilities (HSL conversion)
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 Service
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
- ```tsx
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
- ## Electron Usage
96
+ ## Logger
383
97
 
384
98
  ```tsx
385
- // In Electron renderer process
386
- import { Button, Dialog, useMediaQuery } from '@djangocfg/ui-core';
387
- import '@djangocfg/ui-core/styles/globals';
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
- ## Styling (Next.js / Tailwind v4)
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
- /* globals.css */
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 >= 18 or >= 19
530
- - Tailwind CSS >= 4
112
+ - React 19
113
+ - Tailwind CSS 4
531
114
 
532
115
  ---
533
116
 
534
- **[Full documentation & examples](https://djangocfg.com/demo/)**
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.319",
3
+ "version": "2.1.321",
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.319",
94
+ "@djangocfg/i18n": "^2.1.321",
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.319",
163
+ "@djangocfg/i18n": "^2.1.321",
164
164
  "@djangocfg/playground": "workspace:*",
165
- "@djangocfg/typescript-config": "^2.1.319",
165
+ "@djangocfg/typescript-config": "^2.1.321",
166
166
  "@types/node": "^24.7.2",
167
167
  "@types/react": "^19.1.0",
168
168
  "@types/react-dom": "^19.1.0",
@@ -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