@djangocfg/ui-core 2.1.299 → 2.1.301
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
|
@@ -71,6 +71,34 @@ Default **`TooltipContent`** styling uses semantic **popover** tokens (`bg-popov
|
|
|
71
71
|
</SidePanel>
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
**`Drawer`** — modal vaul-based panel that slides in from any edge (`top` / `right` / `bottom` / `left`). Picks a size from a preset table or takes an explicit CSS length; both are applied via inline `style` so vaul's first-paint measurement matches the final layout (no inset miscalc).
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
<Drawer direction="right">
|
|
78
|
+
<DrawerTrigger asChild><Button>Open</Button></DrawerTrigger>
|
|
79
|
+
<DrawerContent direction="right" size="lg">
|
|
80
|
+
<DrawerHeader>
|
|
81
|
+
<DrawerTitle>Details</DrawerTitle>
|
|
82
|
+
</DrawerHeader>
|
|
83
|
+
…
|
|
84
|
+
</DrawerContent>
|
|
85
|
+
</Drawer>
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Size presets — `sm` `md` `lg` `xl` `full`. Maps to width for `left`/`right`, height for `top`/`bottom`:
|
|
89
|
+
|
|
90
|
+
| Size | Horizontal width | Vertical height |
|
|
91
|
+
|------|-------------------|------------------|
|
|
92
|
+
| sm | `min(100vw, 360px)` | `min(100vh, 240px)` |
|
|
93
|
+
| md | `min(100vw, 480px)` | `min(100vh, 360px)` |
|
|
94
|
+
| lg | `min(100vw, 640px)` | `min(100vh, 480px)` |
|
|
95
|
+
| xl | `min(100vw, 800px)` | `min(100vh, 640px)` |
|
|
96
|
+
| full | `100vw` | `100vh` |
|
|
97
|
+
|
|
98
|
+
If you need an exact size, pass `width` / `height` (a CSS length string or number → px). Inline-applied so the vaul measurement is correct on first paint.
|
|
99
|
+
|
|
100
|
+
**Migration note** — the default size changed (was hardcoded `280px` for left/right, `auto` for top/bottom). New default is `size="md"`. Pass `size="sm"` (~360px) for the closest old left/right behavior, or set an explicit `width` / `height`.
|
|
101
|
+
|
|
74
102
|
### Navigation (8)
|
|
75
103
|
`Tabs` `Accordion` `Collapsible` `Command` `ContextMenu` `DropdownMenu` `Menubar` `NavigationMenu`
|
|
76
104
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/ui-core",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.301",
|
|
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.301",
|
|
95
95
|
"consola": "^3.4.2",
|
|
96
96
|
"lucide-react": "^0.545.0",
|
|
97
97
|
"moment": "^2.30.1",
|
|
@@ -159,9 +159,9 @@
|
|
|
159
159
|
"vaul": "1.1.2"
|
|
160
160
|
},
|
|
161
161
|
"devDependencies": {
|
|
162
|
-
"@djangocfg/i18n": "^2.1.
|
|
162
|
+
"@djangocfg/i18n": "^2.1.301",
|
|
163
163
|
"@djangocfg/playground": "workspace:*",
|
|
164
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
164
|
+
"@djangocfg/typescript-config": "^2.1.301",
|
|
165
165
|
"@types/node": "^24.7.2",
|
|
166
166
|
"@types/react": "^19.1.0",
|
|
167
167
|
"@types/react-dom": "^19.1.0",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { defineStory, useSelect } from '@djangocfg/playground';
|
|
2
3
|
import {
|
|
3
4
|
Drawer,
|
|
4
5
|
DrawerTrigger,
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
DrawerTitle,
|
|
9
10
|
DrawerDescription,
|
|
10
11
|
DrawerClose,
|
|
12
|
+
type DrawerSize,
|
|
11
13
|
} from '.';
|
|
12
14
|
import { Button } from '../../forms/button';
|
|
13
15
|
import { Input } from '../../forms/input';
|
|
@@ -129,3 +131,145 @@ export const ActionSheet = () => (
|
|
|
129
131
|
</DrawerContent>
|
|
130
132
|
</Drawer>
|
|
131
133
|
);
|
|
134
|
+
|
|
135
|
+
const SIZES: readonly DrawerSize[] = ['sm', 'md', 'lg', 'xl', 'full'] as const;
|
|
136
|
+
|
|
137
|
+
export const Sizes = () => {
|
|
138
|
+
const [size, setSize] = React.useState<DrawerSize>('md');
|
|
139
|
+
const [open, setOpen] = React.useState(false);
|
|
140
|
+
|
|
141
|
+
return (
|
|
142
|
+
<div className="space-y-4">
|
|
143
|
+
<div className="flex flex-wrap gap-2">
|
|
144
|
+
{SIZES.map((s) => (
|
|
145
|
+
<Button
|
|
146
|
+
key={s}
|
|
147
|
+
variant={size === s ? 'default' : 'outline'}
|
|
148
|
+
onClick={() => {
|
|
149
|
+
setSize(s);
|
|
150
|
+
setOpen(true);
|
|
151
|
+
}}
|
|
152
|
+
>
|
|
153
|
+
{s}
|
|
154
|
+
</Button>
|
|
155
|
+
))}
|
|
156
|
+
</div>
|
|
157
|
+
<p className="text-sm text-muted-foreground">
|
|
158
|
+
Current size: <code>{size}</code>. Direction: right.
|
|
159
|
+
</p>
|
|
160
|
+
<Drawer open={open} onOpenChange={setOpen} direction="right">
|
|
161
|
+
<DrawerContent direction="right" size={size}>
|
|
162
|
+
<DrawerHeader>
|
|
163
|
+
<DrawerTitle>Size: {size}</DrawerTitle>
|
|
164
|
+
<DrawerDescription>
|
|
165
|
+
Width preset is applied via inline style — vaul measures it
|
|
166
|
+
correctly on first paint.
|
|
167
|
+
</DrawerDescription>
|
|
168
|
+
</DrawerHeader>
|
|
169
|
+
<div className="p-4 text-sm">Content adapts to the chosen size.</div>
|
|
170
|
+
<DrawerFooter>
|
|
171
|
+
<DrawerClose asChild>
|
|
172
|
+
<Button variant="outline">Close</Button>
|
|
173
|
+
</DrawerClose>
|
|
174
|
+
</DrawerFooter>
|
|
175
|
+
</DrawerContent>
|
|
176
|
+
</Drawer>
|
|
177
|
+
</div>
|
|
178
|
+
);
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
export const Directions = () => {
|
|
182
|
+
const [direction] = useSelect('direction', {
|
|
183
|
+
options: ['top', 'right', 'bottom', 'left'] as const,
|
|
184
|
+
defaultValue: 'right',
|
|
185
|
+
label: 'Direction',
|
|
186
|
+
description: 'Edge from which the drawer slides.',
|
|
187
|
+
});
|
|
188
|
+
const [size] = useSelect('size', {
|
|
189
|
+
options: ['sm', 'md', 'lg', 'xl', 'full'] as const,
|
|
190
|
+
defaultValue: 'md',
|
|
191
|
+
label: 'Size',
|
|
192
|
+
description: 'Width (left/right) or height (top/bottom).',
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<Drawer direction={direction}>
|
|
197
|
+
<DrawerTrigger asChild>
|
|
198
|
+
<Button variant="outline">Open from {direction}</Button>
|
|
199
|
+
</DrawerTrigger>
|
|
200
|
+
<DrawerContent direction={direction} size={size}>
|
|
201
|
+
<DrawerHeader>
|
|
202
|
+
<DrawerTitle>Direction: {direction}</DrawerTitle>
|
|
203
|
+
<DrawerDescription>
|
|
204
|
+
Size presets adapt: horizontal directions size width, vertical
|
|
205
|
+
ones size height.
|
|
206
|
+
</DrawerDescription>
|
|
207
|
+
</DrawerHeader>
|
|
208
|
+
<div className="p-4 text-sm">size = {size}</div>
|
|
209
|
+
<DrawerFooter>
|
|
210
|
+
<DrawerClose asChild>
|
|
211
|
+
<Button variant="outline">Close</Button>
|
|
212
|
+
</DrawerClose>
|
|
213
|
+
</DrawerFooter>
|
|
214
|
+
</DrawerContent>
|
|
215
|
+
</Drawer>
|
|
216
|
+
);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
export const CustomWidth = () => (
|
|
220
|
+
<Drawer direction="right">
|
|
221
|
+
<DrawerTrigger asChild>
|
|
222
|
+
<Button variant="outline">Open with width=720px</Button>
|
|
223
|
+
</DrawerTrigger>
|
|
224
|
+
<DrawerContent direction="right" width="720px">
|
|
225
|
+
<DrawerHeader>
|
|
226
|
+
<DrawerTitle>Custom width</DrawerTitle>
|
|
227
|
+
<DrawerDescription>
|
|
228
|
+
Explicit <code>width</code> overrides the <code>size</code> preset.
|
|
229
|
+
</DrawerDescription>
|
|
230
|
+
</DrawerHeader>
|
|
231
|
+
<div className="p-4 text-sm">Width is exactly 720px.</div>
|
|
232
|
+
<DrawerFooter>
|
|
233
|
+
<DrawerClose asChild>
|
|
234
|
+
<Button variant="outline">Close</Button>
|
|
235
|
+
</DrawerClose>
|
|
236
|
+
</DrawerFooter>
|
|
237
|
+
</DrawerContent>
|
|
238
|
+
</Drawer>
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
export const NarrowViewport = () => (
|
|
242
|
+
<div className="space-y-3">
|
|
243
|
+
<p className="text-sm text-muted-foreground">
|
|
244
|
+
Resize the browser narrower than the size preset (e.g. < 480px for
|
|
245
|
+
<code> md</code>) to see <code>min(100vw, …)</code> clamp the drawer
|
|
246
|
+
to the viewport. This is the regression we now guard against.
|
|
247
|
+
</p>
|
|
248
|
+
<div
|
|
249
|
+
style={{ width: 360 }}
|
|
250
|
+
className="rounded border border-dashed p-4"
|
|
251
|
+
>
|
|
252
|
+
<Drawer direction="right">
|
|
253
|
+
<DrawerTrigger asChild>
|
|
254
|
+
<Button variant="outline">Open in 360px container</Button>
|
|
255
|
+
</DrawerTrigger>
|
|
256
|
+
<DrawerContent direction="right" size="md">
|
|
257
|
+
<DrawerHeader>
|
|
258
|
+
<DrawerTitle>Narrow viewport</DrawerTitle>
|
|
259
|
+
<DrawerDescription>
|
|
260
|
+
Drawer is portaled to the body — viewport width still applies.
|
|
261
|
+
</DrawerDescription>
|
|
262
|
+
</DrawerHeader>
|
|
263
|
+
<div className="p-4 text-sm">
|
|
264
|
+
min(100vw, 480px) keeps the drawer within bounds on tiny screens.
|
|
265
|
+
</div>
|
|
266
|
+
<DrawerFooter>
|
|
267
|
+
<DrawerClose asChild>
|
|
268
|
+
<Button variant="outline">Close</Button>
|
|
269
|
+
</DrawerClose>
|
|
270
|
+
</DrawerFooter>
|
|
271
|
+
</DrawerContent>
|
|
272
|
+
</Drawer>
|
|
273
|
+
</div>
|
|
274
|
+
</div>
|
|
275
|
+
);
|
|
@@ -37,19 +37,66 @@ const DrawerOverlay = React.forwardRef<
|
|
|
37
37
|
))
|
|
38
38
|
DrawerOverlay.displayName = DrawerPrimitive.Overlay.displayName
|
|
39
39
|
|
|
40
|
+
/** Drawer size preset. Maps to width for left/right, height for top/bottom. */
|
|
41
|
+
export type DrawerSize = 'sm' | 'md' | 'lg' | 'xl' | 'full'
|
|
42
|
+
|
|
43
|
+
const horizontalSizePresets: Record<DrawerSize, string> = {
|
|
44
|
+
sm: 'min(100vw, 360px)',
|
|
45
|
+
md: 'min(100vw, 480px)',
|
|
46
|
+
lg: 'min(100vw, 640px)',
|
|
47
|
+
xl: 'min(100vw, 800px)',
|
|
48
|
+
full: '100vw',
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const verticalSizePresets: Record<DrawerSize, string> = {
|
|
52
|
+
sm: 'min(100vh, 240px)',
|
|
53
|
+
md: 'min(100vh, 360px)',
|
|
54
|
+
lg: 'min(100vh, 480px)',
|
|
55
|
+
xl: 'min(100vh, 640px)',
|
|
56
|
+
full: '100vh',
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Anchor + border + radius only — no width/height bake-in. Length is
|
|
60
|
+
// applied via inline `style` from props so vaul's first
|
|
61
|
+
// `getBoundingClientRect()` measurement matches the final layout
|
|
62
|
+
// (avoids the inset.left=mismatch bug when className-based defaults
|
|
63
|
+
// leak into the first paint).
|
|
40
64
|
const directionStyles = {
|
|
41
|
-
bottom: "inset-x-0 bottom-0 mt-24
|
|
42
|
-
top: "inset-x-0 top-0 mb-24
|
|
43
|
-
right: "inset-y-0 right-0 h-full
|
|
44
|
-
left: "inset-y-0 left-0 h-full
|
|
65
|
+
bottom: "inset-x-0 bottom-0 mt-24 rounded-t-lg border-t",
|
|
66
|
+
top: "inset-x-0 top-0 mb-24 rounded-b-lg border-b",
|
|
67
|
+
right: "inset-y-0 right-0 h-full border-l",
|
|
68
|
+
left: "inset-y-0 left-0 h-full border-r",
|
|
45
69
|
} as const;
|
|
46
70
|
|
|
71
|
+
const toCssLength = (value: string | number | undefined): string | undefined => {
|
|
72
|
+
if (value == null) return undefined;
|
|
73
|
+
return typeof value === 'number' ? `${value}px` : value;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export interface DrawerContentProps
|
|
77
|
+
extends React.ComponentPropsWithoutRef<typeof DrawerPrimitive.Content> {
|
|
78
|
+
direction?: 'bottom' | 'right' | 'left' | 'top';
|
|
79
|
+
/** Preset size (default `'md'`). Width for horizontal, height for vertical. */
|
|
80
|
+
size?: DrawerSize;
|
|
81
|
+
/** CSS length for width — applies to left/right drawers. Overrides `size`. */
|
|
82
|
+
width?: string | number;
|
|
83
|
+
/** CSS length for height — applies to top/bottom drawers. Overrides `size`. */
|
|
84
|
+
height?: string | number;
|
|
85
|
+
}
|
|
86
|
+
|
|
47
87
|
const DrawerContent = React.forwardRef<
|
|
48
88
|
React.ElementRef<typeof DrawerPrimitive.Content>,
|
|
49
|
-
|
|
50
|
-
>(({ className, children, direction = 'bottom', style, ...props }, ref) => {
|
|
89
|
+
DrawerContentProps
|
|
90
|
+
>(({ className, children, direction = 'bottom', size = 'md', width, height, style, ...props }, ref) => {
|
|
51
91
|
const isVertical = direction === 'bottom' || direction === 'top';
|
|
52
92
|
|
|
93
|
+
const resolvedWidth = !isVertical
|
|
94
|
+
? (toCssLength(width) ?? horizontalSizePresets[size])
|
|
95
|
+
: undefined;
|
|
96
|
+
const resolvedHeight = isVertical
|
|
97
|
+
? (toCssLength(height) ?? verticalSizePresets[size])
|
|
98
|
+
: undefined;
|
|
99
|
+
|
|
53
100
|
return (
|
|
54
101
|
<DrawerPortal>
|
|
55
102
|
<DrawerOverlay />
|
|
@@ -62,6 +109,8 @@ const DrawerContent = React.forwardRef<
|
|
|
62
109
|
)}
|
|
63
110
|
style={{
|
|
64
111
|
transition: 'transform 300ms cubic-bezier(0.32, 0.72, 0, 1)',
|
|
112
|
+
...(resolvedWidth ? { width: resolvedWidth } : {}),
|
|
113
|
+
...(resolvedHeight ? { height: resolvedHeight } : {}),
|
|
65
114
|
...style,
|
|
66
115
|
}}
|
|
67
116
|
{...props}
|
|
@@ -83,7 +83,8 @@ function ResponsiveSheetContent({ children, className }: ResponsiveSheetContentP
|
|
|
83
83
|
const { isMobile } = React.useContext(ResponsiveSheetContext);
|
|
84
84
|
|
|
85
85
|
if (isMobile) {
|
|
86
|
-
|
|
86
|
+
// Preserve legacy content-hugging behavior; new Drawer default is `md`.
|
|
87
|
+
return <DrawerContent className={className} height="auto">{children}</DrawerContent>;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
90
|
return <DialogContent className={className}>{children}</DialogContent>;
|