@djangocfg/ui-nextjs 2.1.251 → 2.1.254
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 +6 -3
- package/package.json +7 -7
- package/src/components/sidebar.tsx +95 -56
- package/src/theme/ThemeToggle.tsx +0 -1
package/README.md
CHANGED
|
@@ -26,7 +26,7 @@ Peer dependencies: `next`, `next-intl`, `react`, `tailwindcss`.
|
|
|
26
26
|
|
|
27
27
|
```
|
|
28
28
|
@djangocfg/ui-nextjs
|
|
29
|
-
├── Re-exports everything from @djangocfg/ui-core (
|
|
29
|
+
├── Re-exports everything from @djangocfg/ui-core (components + hooks)
|
|
30
30
|
├── + Next.js locale-aware components (11)
|
|
31
31
|
├── + Navigation hooks (useNavigation, useRouter, usePathname)
|
|
32
32
|
├── + Browser storage hooks (2)
|
|
@@ -49,7 +49,7 @@ All components from `@djangocfg/ui-core` are re-exported.
|
|
|
49
49
|
|-----------|-------------|
|
|
50
50
|
| `NextLink` | Locale-aware link wrapper |
|
|
51
51
|
| `NextButtonLink` | Button-styled locale-aware link with variants |
|
|
52
|
-
| `Sidebar` | Locale-aware
|
|
52
|
+
| `Sidebar` | Locale-aware collapsible sidebar; `SidebarTrigger` shows an OS-aware tooltip (`⌘+B` / `Ctrl+B`); `SidebarMenuButton` accepts `tooltip` for icon-rail hints |
|
|
53
53
|
| `Breadcrumb` | Locale-aware breadcrumbs |
|
|
54
54
|
| `BreadcrumbNavigation` | High-level breadcrumb component |
|
|
55
55
|
| `NavigationMenu` | Locale-aware navigation menu |
|
|
@@ -91,12 +91,15 @@ function MyComponent() {
|
|
|
91
91
|
}
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
### From ui-core (
|
|
94
|
+
### From ui-core (re-exported)
|
|
95
|
+
|
|
96
|
+
All hooks from `@djangocfg/ui-core/hooks` are available. Common examples:
|
|
95
97
|
|
|
96
98
|
| Hook | Description |
|
|
97
99
|
|------|-------------|
|
|
98
100
|
| `useMediaQuery` | Responsive breakpoints |
|
|
99
101
|
| `useIsMobile` | Mobile detection |
|
|
102
|
+
| `useShortcutModLabel` | `⌘` vs `Ctrl` labels for shortcut tooltips |
|
|
100
103
|
| `useCopy` | Copy to clipboard |
|
|
101
104
|
| `useCountdown` | Countdown timer |
|
|
102
105
|
| `useDebounce` | Debounce values |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-nextjs",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.254",
|
|
4
4
|
"description": "Next.js UI component library with Radix UI primitives, Tailwind CSS styling, charts, and form components",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ui-components",
|
|
@@ -85,11 +85,11 @@
|
|
|
85
85
|
"check": "tsc --noEmit"
|
|
86
86
|
},
|
|
87
87
|
"peerDependencies": {
|
|
88
|
-
"@djangocfg/api": "^2.1.
|
|
89
|
-
"@djangocfg/i18n": "^2.1.
|
|
90
|
-
"@djangocfg/nextjs": "^2.1.
|
|
91
|
-
"@djangocfg/ui-core": "^2.1.
|
|
92
|
-
"@djangocfg/ui-tools": "^2.1.
|
|
88
|
+
"@djangocfg/api": "^2.1.254",
|
|
89
|
+
"@djangocfg/i18n": "^2.1.254",
|
|
90
|
+
"@djangocfg/nextjs": "^2.1.254",
|
|
91
|
+
"@djangocfg/ui-core": "^2.1.254",
|
|
92
|
+
"@djangocfg/ui-tools": "^2.1.254",
|
|
93
93
|
"@types/react": "^19.1.0",
|
|
94
94
|
"@types/react-dom": "^19.1.0",
|
|
95
95
|
"consola": "^3.4.2",
|
|
@@ -112,7 +112,7 @@
|
|
|
112
112
|
"react-chartjs-2": "^5.3.0"
|
|
113
113
|
},
|
|
114
114
|
"devDependencies": {
|
|
115
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
115
|
+
"@djangocfg/typescript-config": "^2.1.254",
|
|
116
116
|
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
117
117
|
"@radix-ui/react-slot": "^1.2.4",
|
|
118
118
|
"@types/node": "^24.7.2",
|
|
@@ -6,17 +6,27 @@ import { Link } from '../lib/navigation';
|
|
|
6
6
|
import * as React from 'react';
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
Button,
|
|
10
|
+
Drawer,
|
|
11
|
+
DrawerContent,
|
|
12
|
+
DrawerDescription,
|
|
13
|
+
DrawerHeader,
|
|
14
|
+
DrawerTitle,
|
|
15
|
+
Input,
|
|
16
|
+
Separator,
|
|
17
|
+
Skeleton,
|
|
18
|
+
Tooltip,
|
|
19
|
+
TooltipContent,
|
|
20
|
+
TooltipProvider,
|
|
21
|
+
TooltipTrigger,
|
|
11
22
|
} from '@djangocfg/ui-core/components';
|
|
12
|
-
import { useIsMobile } from '@djangocfg/ui-core/hooks';
|
|
23
|
+
import { useIsMobile, useShortcutModLabel } from '@djangocfg/ui-core/hooks';
|
|
13
24
|
import { cn } from '@djangocfg/ui-core/lib';
|
|
14
25
|
import { Slot } from '@radix-ui/react-slot';
|
|
15
26
|
|
|
16
27
|
const SIDEBAR_COOKIE_NAME = "sidebar_state"
|
|
17
28
|
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7
|
|
18
29
|
const SIDEBAR_WIDTH = "16rem"
|
|
19
|
-
const SIDEBAR_WIDTH_MOBILE = "min(80vw, 320px)"
|
|
20
30
|
const SIDEBAR_WIDTH_ICON = "3rem"
|
|
21
31
|
const SIDEBAR_KEYBOARD_SHORTCUT = "b"
|
|
22
32
|
|
|
@@ -187,27 +197,37 @@ const Sidebar = React.forwardRef<
|
|
|
187
197
|
}
|
|
188
198
|
|
|
189
199
|
if (isMobile) {
|
|
200
|
+
const drawerSide = side === "right" ? "right" : "left"
|
|
190
201
|
return (
|
|
191
|
-
<
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
{
|
|
198
|
-
"--sidebar-width": SIDEBAR_WIDTH_MOBILE,
|
|
199
|
-
backgroundColor: "hsl(var(--sidebar-background))",
|
|
200
|
-
} as React.CSSProperties
|
|
201
|
-
}
|
|
202
|
-
side={side}
|
|
202
|
+
<div ref={ref} className={cn("contents", className)} {...props}>
|
|
203
|
+
<Drawer
|
|
204
|
+
open={openMobile}
|
|
205
|
+
onOpenChange={setOpenMobile}
|
|
206
|
+
direction={drawerSide}
|
|
207
|
+
shouldScaleBackground={false}
|
|
203
208
|
>
|
|
204
|
-
<
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
209
|
+
<DrawerContent
|
|
210
|
+
direction={drawerSide}
|
|
211
|
+
data-sidebar="sidebar"
|
|
212
|
+
data-mobile="true"
|
|
213
|
+
className={cn(
|
|
214
|
+
"flex h-[100dvh] max-h-[100dvh] min-h-0 !w-[min(80vw,320px)] max-w-[min(80vw,320px)] flex-col gap-0 border-sidebar-border/60 p-0 text-sidebar-foreground shadow-xl",
|
|
215
|
+
drawerSide === "left" ? "border-r" : "border-l",
|
|
216
|
+
)}
|
|
217
|
+
style={
|
|
218
|
+
{
|
|
219
|
+
backgroundColor: "hsl(var(--sidebar-background))",
|
|
220
|
+
} as React.CSSProperties
|
|
221
|
+
}
|
|
222
|
+
>
|
|
223
|
+
<DrawerHeader className="sr-only">
|
|
224
|
+
<DrawerTitle>Sidebar</DrawerTitle>
|
|
225
|
+
<DrawerDescription>Mobile navigation sidebar.</DrawerDescription>
|
|
226
|
+
</DrawerHeader>
|
|
227
|
+
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">{children}</div>
|
|
228
|
+
</DrawerContent>
|
|
229
|
+
</Drawer>
|
|
230
|
+
</div>
|
|
211
231
|
)
|
|
212
232
|
}
|
|
213
233
|
|
|
@@ -266,10 +286,7 @@ const Sidebar = React.forwardRef<
|
|
|
266
286
|
// Adjust the padding for floating and inset variants.
|
|
267
287
|
variant === "floating" || variant === "inset"
|
|
268
288
|
? "p-2"
|
|
269
|
-
:
|
|
270
|
-
side === "left" && "border-r",
|
|
271
|
-
side === "right" && "border-l"
|
|
272
|
-
),
|
|
289
|
+
: undefined,
|
|
273
290
|
className
|
|
274
291
|
)}
|
|
275
292
|
style={{
|
|
@@ -280,11 +297,24 @@ const Sidebar = React.forwardRef<
|
|
|
280
297
|
<div
|
|
281
298
|
data-sidebar="sidebar"
|
|
282
299
|
className={cn(
|
|
283
|
-
"flex h-full w-full flex-col
|
|
284
|
-
variant === "floating"
|
|
300
|
+
"relative flex h-full w-full flex-col overflow-hidden text-sidebar-foreground",
|
|
301
|
+
variant === "floating"
|
|
302
|
+
? "rounded-lg border border-sidebar-border bg-sidebar shadow"
|
|
303
|
+
: "bg-sidebar",
|
|
285
304
|
)}
|
|
286
305
|
>
|
|
287
|
-
{
|
|
306
|
+
{/* Full-height vertical accent on the seam (gradient runs top → bottom, not along X) */}
|
|
307
|
+
{variant === "sidebar" && (
|
|
308
|
+
<div
|
|
309
|
+
aria-hidden
|
|
310
|
+
className={cn(
|
|
311
|
+
"pointer-events-none absolute inset-y-0 right-0 z-[1] w-px",
|
|
312
|
+
"bg-[linear-gradient(180deg,hsl(var(--sidebar-border)_/_0.02)_0%,hsl(var(--sidebar-border)_/_0.22)_18%,hsl(var(--sidebar-border)_/_0.4)_50%,hsl(var(--sidebar-border)_/_0.2)_82%,hsl(var(--sidebar-border)_/_0.03)_100%)]",
|
|
313
|
+
"dark:bg-[linear-gradient(180deg,hsl(var(--sidebar-border)_/_0.08)_0%,hsl(var(--sidebar-border)_/_0.34)_22%,hsl(var(--sidebar-border)_/_0.55)_50%,hsl(var(--sidebar-border)_/_0.28)_78%,hsl(var(--sidebar-border)_/_0.06)_100%)]"
|
|
314
|
+
)}
|
|
315
|
+
/>
|
|
316
|
+
)}
|
|
317
|
+
<div className="relative z-0 flex min-h-0 flex-1 flex-col">{children}</div>
|
|
288
318
|
</div>
|
|
289
319
|
</div>
|
|
290
320
|
</div>
|
|
@@ -298,23 +328,33 @@ const SidebarTrigger = React.forwardRef<
|
|
|
298
328
|
React.ComponentProps<typeof Button>
|
|
299
329
|
>(({ className, onClick, ...props }, ref) => {
|
|
300
330
|
const { toggleSidebar } = useSidebar()
|
|
331
|
+
const mod = useShortcutModLabel()
|
|
332
|
+
const isMobile = useIsMobile()
|
|
333
|
+
const hint = isMobile ? "Toggle sidebar" : `Toggle sidebar (${mod}+B)`
|
|
301
334
|
|
|
302
335
|
return (
|
|
303
|
-
<
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
336
|
+
<Tooltip delayDuration={400}>
|
|
337
|
+
<TooltipTrigger asChild>
|
|
338
|
+
<Button
|
|
339
|
+
ref={ref}
|
|
340
|
+
data-sidebar="trigger"
|
|
341
|
+
variant="ghost"
|
|
342
|
+
size="icon"
|
|
343
|
+
className={cn("h-7 w-7", className)}
|
|
344
|
+
onClick={(event) => {
|
|
345
|
+
onClick?.(event)
|
|
346
|
+
toggleSidebar()
|
|
347
|
+
}}
|
|
348
|
+
{...props}
|
|
349
|
+
>
|
|
350
|
+
<PanelLeft />
|
|
351
|
+
<span className="sr-only">{hint}</span>
|
|
352
|
+
</Button>
|
|
353
|
+
</TooltipTrigger>
|
|
354
|
+
<TooltipContent side="bottom" align="center" sideOffset={6} className="max-w-[16rem]">
|
|
355
|
+
{hint}
|
|
356
|
+
</TooltipContent>
|
|
357
|
+
</Tooltip>
|
|
318
358
|
)
|
|
319
359
|
})
|
|
320
360
|
SidebarTrigger.displayName = "SidebarTrigger"
|
|
@@ -332,7 +372,6 @@ const SidebarRail = React.forwardRef<
|
|
|
332
372
|
aria-label="Toggle Sidebar"
|
|
333
373
|
tabIndex={-1}
|
|
334
374
|
onClick={toggleSidebar}
|
|
335
|
-
title="Toggle Sidebar"
|
|
336
375
|
className={cn(
|
|
337
376
|
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex",
|
|
338
377
|
"group-data-[side=left]:cursor-w-resize group-data-[side=right]:cursor-e-resize",
|
|
@@ -494,7 +533,8 @@ const SidebarGroupLabel = React.forwardRef<
|
|
|
494
533
|
data-sidebar="group-label"
|
|
495
534
|
className={cn(
|
|
496
535
|
"flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-semibold text-muted-foreground uppercase tracking-wider outline-none ring-sidebar-ring transition-[margin,opacity] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
|
497
|
-
|
|
536
|
+
// Collapsed rail: label is visually hidden but was still hit-testing; -mt-8 overlaps the last nav row.
|
|
537
|
+
"group-data-[state=collapsed]:group-data-[collapsible=icon]:pointer-events-none group-data-[state=collapsed]:group-data-[collapsible=icon]:-mt-8 group-data-[state=collapsed]:group-data-[collapsible=icon]:opacity-0",
|
|
498
538
|
className
|
|
499
539
|
)}
|
|
500
540
|
{...props}
|
|
@@ -649,11 +689,8 @@ const SidebarMenuButton = React.forwardRef<
|
|
|
649
689
|
return buttonContent
|
|
650
690
|
}
|
|
651
691
|
|
|
652
|
-
|
|
653
|
-
tooltip
|
|
654
|
-
children: tooltip,
|
|
655
|
-
}
|
|
656
|
-
}
|
|
692
|
+
const tooltipContentProps: React.ComponentProps<typeof TooltipContent> =
|
|
693
|
+
typeof tooltip === "string" ? { children: tooltip } : { ...tooltip }
|
|
657
694
|
|
|
658
695
|
return (
|
|
659
696
|
<Tooltip>
|
|
@@ -661,8 +698,10 @@ const SidebarMenuButton = React.forwardRef<
|
|
|
661
698
|
<TooltipContent
|
|
662
699
|
side="right"
|
|
663
700
|
align="center"
|
|
701
|
+
sideOffset={8}
|
|
702
|
+
avoidCollisions={false}
|
|
664
703
|
hidden={state !== "collapsed" || isMobile}
|
|
665
|
-
{...
|
|
704
|
+
{...tooltipContentProps}
|
|
666
705
|
/>
|
|
667
706
|
</Tooltip>
|
|
668
707
|
)
|
|
@@ -709,11 +748,11 @@ const SidebarMenuBadge = React.forwardRef<
|
|
|
709
748
|
ref={ref}
|
|
710
749
|
data-sidebar="menu-badge"
|
|
711
750
|
className={cn(
|
|
712
|
-
"pointer-events-none absolute right-1 flex h-
|
|
713
|
-
"peer-hover/menu-button:text-
|
|
751
|
+
"pointer-events-none absolute right-1 flex h-4 min-w-4 select-none items-center justify-center rounded-md px-1 text-[10px] font-medium tabular-nums leading-none text-muted-foreground/75",
|
|
752
|
+
"peer-hover/menu-button:text-muted-foreground peer-data-[active=true]/menu-button:text-foreground/65",
|
|
714
753
|
"peer-data-[size=sm]/menu-button:top-1",
|
|
715
754
|
"peer-data-[size=default]/menu-button:top-1.5",
|
|
716
|
-
"peer-data-[size=lg]/menu-button:top-2
|
|
755
|
+
"peer-data-[size=lg]/menu-button:top-2",
|
|
717
756
|
"group-data-[state=collapsed]:group-data-[collapsible=icon]:hidden",
|
|
718
757
|
className
|
|
719
758
|
)}
|
|
@@ -71,7 +71,6 @@ export function ThemeToggle({ className, size = 'auto' }: ThemeToggleProps) {
|
|
|
71
71
|
size="icon"
|
|
72
72
|
onClick={toggleTheme}
|
|
73
73
|
className={cn(buttonSize, className)}
|
|
74
|
-
title={`Switch to ${resolvedTheme === 'light' ? 'dark' : 'light'} theme`}
|
|
75
74
|
>
|
|
76
75
|
{resolvedTheme === 'light' ? (
|
|
77
76
|
<Sun className={iconSize} />
|