@jinaan/uikit 0.1.29 → 0.1.31
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/dist/dialog.d.ts +16 -1
- package/dist/dialog.d.ts.map +1 -1
- package/dist/index.js +665 -631
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/dialog.tsx +148 -85
- package/styles.css +18 -0
package/package.json
CHANGED
package/src/dialog.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
11
|
type ComponentPropsWithoutRef,
|
|
12
|
+
type CSSProperties,
|
|
12
13
|
type ElementRef,
|
|
13
14
|
type HTMLAttributes,
|
|
14
15
|
} from "react";
|
|
@@ -83,6 +84,7 @@ function resolveOrigin(content: HTMLElement): DialogOriginRect | null {
|
|
|
83
84
|
const DIALOG_CLOSE_MS = 250;
|
|
84
85
|
const DialogClosingContext = createContext(false);
|
|
85
86
|
const DialogRequestCloseContext = createContext<() => void>(() => {});
|
|
87
|
+
const InDialogContext = createContext(false);
|
|
86
88
|
|
|
87
89
|
/** Dialog root; owns the delayed close so the fly-out can finish. */
|
|
88
90
|
export function Dialog({
|
|
@@ -158,13 +160,15 @@ export function Dialog({
|
|
|
158
160
|
return (
|
|
159
161
|
<DialogRequestCloseContext.Provider value={() => handleOpenChange(false)}>
|
|
160
162
|
<DialogClosingContext.Provider value={closing}>
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
<InDialogContext.Provider value={true}>
|
|
164
|
+
<DialogPrimitive.Root
|
|
165
|
+
open={open}
|
|
166
|
+
onOpenChange={handleOpenChange}
|
|
167
|
+
{...props}
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</DialogPrimitive.Root>
|
|
171
|
+
</InDialogContext.Provider>
|
|
168
172
|
</DialogClosingContext.Provider>
|
|
169
173
|
</DialogRequestCloseContext.Provider>
|
|
170
174
|
);
|
|
@@ -253,77 +257,117 @@ function startDialogMotion(content: HTMLDivElement, retry = true) {
|
|
|
253
257
|
content.addEventListener("animationend", onEnd);
|
|
254
258
|
}
|
|
255
259
|
|
|
260
|
+
export interface DialogContentProps
|
|
261
|
+
extends ComponentPropsWithoutRef<typeof DialogPrimitive.Content> {
|
|
262
|
+
/** Vertical placement: centered (default) or anchored near the viewport top. */
|
|
263
|
+
placement?: "center" | "top";
|
|
264
|
+
/** Top edge offset for top placement. Defaults to 11dvh. */
|
|
265
|
+
topOffset?: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function dialogTopFallback(offset: string): string {
|
|
269
|
+
return offset.replace(/dvh\b/g, "vh");
|
|
270
|
+
}
|
|
271
|
+
|
|
256
272
|
export const DialogContent = forwardRef<
|
|
257
273
|
ElementRef<typeof DialogPrimitive.Content>,
|
|
258
|
-
|
|
259
|
-
>(
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
271
|
-
if (ref) {
|
|
272
|
-
(ref as { current: HTMLDivElement | null }).current = node;
|
|
273
|
-
}
|
|
274
|
+
DialogContentProps
|
|
275
|
+
>(
|
|
276
|
+
(
|
|
277
|
+
{
|
|
278
|
+
className,
|
|
279
|
+
children,
|
|
280
|
+
placement = "center",
|
|
281
|
+
topOffset = "11dvh",
|
|
282
|
+
style,
|
|
283
|
+
onEscapeKeyDown,
|
|
284
|
+
onPointerDownOutside,
|
|
285
|
+
...props
|
|
274
286
|
},
|
|
275
|
-
|
|
276
|
-
)
|
|
287
|
+
ref,
|
|
288
|
+
) => {
|
|
289
|
+
const closing = useContext(DialogClosingContext);
|
|
290
|
+
const top = placement === "top";
|
|
291
|
+
const placementStyle: CSSProperties | undefined = top
|
|
292
|
+
? ({
|
|
293
|
+
"--dialog-top-offset": topOffset,
|
|
294
|
+
"--dialog-top-offset-fallback": dialogTopFallback(topOffset),
|
|
295
|
+
} as CSSProperties)
|
|
296
|
+
: undefined;
|
|
297
|
+
const requestClose = useContext(DialogRequestCloseContext);
|
|
298
|
+
const contentRef = useRef<HTMLDivElement>(null);
|
|
299
|
+
const setRef = useCallback(
|
|
300
|
+
(node: HTMLDivElement | null) => {
|
|
301
|
+
contentRef.current = node;
|
|
302
|
+
if (node) startDialogMotion(node);
|
|
303
|
+
if (typeof ref === "function") {
|
|
304
|
+
ref(node);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (ref) {
|
|
308
|
+
(ref as { current: HTMLDivElement | null }).current = node;
|
|
309
|
+
}
|
|
310
|
+
},
|
|
311
|
+
[ref],
|
|
312
|
+
);
|
|
277
313
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
className={cn(
|
|
282
|
-
"fixed inset-0 z-50 overflow-hidden bg-overlay backdrop-blur-[3px] data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
283
|
-
!closing &&
|
|
284
|
-
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
285
|
-
closing && "opacity-0 transition-opacity duration-200",
|
|
286
|
-
)}
|
|
287
|
-
>
|
|
288
|
-
<span
|
|
289
|
-
aria-hidden="true"
|
|
290
|
-
className="dialog-noise pointer-events-none absolute inset-0"
|
|
291
|
-
/>
|
|
292
|
-
</DialogPrimitive.Overlay>
|
|
293
|
-
<div className="pointer-events-none fixed inset-0 z-50 flex items-center justify-center overflow-y-auto p-4">
|
|
294
|
-
<DialogPrimitive.Content
|
|
295
|
-
ref={setRef}
|
|
314
|
+
return (
|
|
315
|
+
<DialogPrimitive.Portal>
|
|
316
|
+
<DialogPrimitive.Overlay
|
|
296
317
|
className={cn(
|
|
297
|
-
"
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
"
|
|
301
|
-
className,
|
|
318
|
+
"fixed inset-0 z-50 overflow-hidden bg-overlay backdrop-blur-[3px] data-[state=open]:animate-in data-[state=open]:fade-in-0",
|
|
319
|
+
!closing &&
|
|
320
|
+
"data-[state=closed]:animate-out data-[state=closed]:fade-out-0",
|
|
321
|
+
closing && "opacity-0 transition-opacity duration-200",
|
|
302
322
|
)}
|
|
303
|
-
onEscapeKeyDown={(event) => {
|
|
304
|
-
onEscapeKeyDown?.(event);
|
|
305
|
-
if (event.defaultPrevented) return;
|
|
306
|
-
event.preventDefault();
|
|
307
|
-
requestClose();
|
|
308
|
-
}}
|
|
309
|
-
onPointerDownOutside={(event) => {
|
|
310
|
-
onPointerDownOutside?.(event);
|
|
311
|
-
if (event.defaultPrevented) return;
|
|
312
|
-
event.preventDefault();
|
|
313
|
-
requestClose();
|
|
314
|
-
}}
|
|
315
|
-
{...props}
|
|
316
323
|
>
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
324
|
+
<span
|
|
325
|
+
aria-hidden="true"
|
|
326
|
+
className="dialog-noise pointer-events-none absolute inset-0"
|
|
327
|
+
/>
|
|
328
|
+
</DialogPrimitive.Overlay>
|
|
329
|
+
<div
|
|
330
|
+
className={cn(
|
|
331
|
+
"pointer-events-none fixed inset-0 z-50 flex justify-center overflow-y-auto p-4",
|
|
332
|
+
top ? "items-start" : "items-center",
|
|
333
|
+
)}
|
|
334
|
+
>
|
|
335
|
+
<DialogPrimitive.Content
|
|
336
|
+
ref={setRef}
|
|
337
|
+
style={placementStyle ? { ...placementStyle, ...style } : style}
|
|
338
|
+
className={cn(
|
|
339
|
+
"dialog-content pointer-events-auto relative flex max-h-[min(85vh,44rem)] w-full max-w-[22.4rem] flex-col gap-0 overflow-hidden will-change-[transform,opacity]",
|
|
340
|
+
"rounded-2xl border border-(--dialog-shell-border) bg-(--dialog-shell-bg) pb-3 shadow-(--dialog-shell-shadow) sm:pb-4",
|
|
341
|
+
top && "dialog-anchor-top",
|
|
342
|
+
closing && "dialog-closing",
|
|
343
|
+
"focus:outline-none",
|
|
344
|
+
className,
|
|
345
|
+
)}
|
|
346
|
+
onEscapeKeyDown={(event) => {
|
|
347
|
+
onEscapeKeyDown?.(event);
|
|
348
|
+
if (event.defaultPrevented) return;
|
|
349
|
+
event.preventDefault();
|
|
350
|
+
requestClose();
|
|
351
|
+
}}
|
|
352
|
+
onPointerDownOutside={(event) => {
|
|
353
|
+
onPointerDownOutside?.(event);
|
|
354
|
+
if (event.defaultPrevented) return;
|
|
355
|
+
event.preventDefault();
|
|
356
|
+
requestClose();
|
|
357
|
+
}}
|
|
358
|
+
{...props}
|
|
359
|
+
>
|
|
360
|
+
{children}
|
|
361
|
+
<DialogPrimitive.Close className="absolute right-3.5 top-3.5 z-10 rounded-sm text-subtle transition-colors hover:text-fg focus-visible:outline-none">
|
|
362
|
+
<X className="size-4" />
|
|
363
|
+
<span className="sr-only">Close</span>
|
|
364
|
+
</DialogPrimitive.Close>
|
|
365
|
+
</DialogPrimitive.Content>
|
|
366
|
+
</div>
|
|
367
|
+
</DialogPrimitive.Portal>
|
|
368
|
+
);
|
|
369
|
+
},
|
|
370
|
+
);
|
|
327
371
|
DialogContent.displayName = "DialogContent";
|
|
328
372
|
|
|
329
373
|
/**
|
|
@@ -393,26 +437,45 @@ export function DialogFooter({
|
|
|
393
437
|
);
|
|
394
438
|
}
|
|
395
439
|
|
|
440
|
+
/**
|
|
441
|
+
* Dialog heading. Standalone-safe: outside a `Dialog` root it renders a plain
|
|
442
|
+
* heading with the same classes (Radix's Title requires a dialog context).
|
|
443
|
+
*/
|
|
396
444
|
export const DialogTitle = forwardRef<
|
|
397
445
|
ElementRef<typeof DialogPrimitive.Title>,
|
|
398
446
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
|
|
399
|
-
>(({ className, ...props }, ref) =>
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
447
|
+
>(({ className, ...props }, ref) => {
|
|
448
|
+
const Comp = useContext(InDialogContext)
|
|
449
|
+
? DialogPrimitive.Title
|
|
450
|
+
: "h2";
|
|
451
|
+
return (
|
|
452
|
+
<Comp
|
|
453
|
+
ref={ref}
|
|
454
|
+
className={cn("text-base font-semibold text-fg", className)}
|
|
455
|
+
{...props}
|
|
456
|
+
/>
|
|
457
|
+
);
|
|
458
|
+
});
|
|
406
459
|
DialogTitle.displayName = "DialogTitle";
|
|
407
460
|
|
|
461
|
+
/**
|
|
462
|
+
* Dialog body copy. Standalone-safe: outside a `Dialog` root it renders a
|
|
463
|
+
* plain paragraph with the same classes (Radix's Description requires a
|
|
464
|
+
* dialog context).
|
|
465
|
+
*/
|
|
408
466
|
export const DialogDescription = forwardRef<
|
|
409
467
|
ElementRef<typeof DialogPrimitive.Description>,
|
|
410
468
|
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
|
|
411
|
-
>(({ className, ...props }, ref) =>
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
469
|
+
>(({ className, ...props }, ref) => {
|
|
470
|
+
const Comp = useContext(InDialogContext)
|
|
471
|
+
? DialogPrimitive.Description
|
|
472
|
+
: "p";
|
|
473
|
+
return (
|
|
474
|
+
<Comp
|
|
475
|
+
ref={ref}
|
|
476
|
+
className={cn("text-sm text-muted", className)}
|
|
477
|
+
{...props}
|
|
478
|
+
/>
|
|
479
|
+
);
|
|
480
|
+
});
|
|
418
481
|
DialogDescription.displayName = "DialogDescription";
|
package/styles.css
CHANGED
|
@@ -402,6 +402,24 @@
|
|
|
402
402
|
}
|
|
403
403
|
}
|
|
404
404
|
|
|
405
|
+
/*
|
|
406
|
+
* Dialog top anchoring: the panel's top edge stays at the requested offset
|
|
407
|
+
* (11dvh by default) regardless of content height. Older browsers without dvh
|
|
408
|
+
* fall back to the equivalent vh value. Unlayered so it wins over the default
|
|
409
|
+
* utility max-height.
|
|
410
|
+
*/
|
|
411
|
+
.dialog-anchor-top {
|
|
412
|
+
margin-top: calc(var(--dialog-top-offset-fallback, 11vh) - 1rem);
|
|
413
|
+
max-height: calc(100vh - var(--dialog-top-offset-fallback, 11vh) - 1rem);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
@supports (height: 100dvh) {
|
|
417
|
+
.dialog-anchor-top {
|
|
418
|
+
margin-top: calc(var(--dialog-top-offset, 11dvh) - 1rem);
|
|
419
|
+
max-height: calc(100dvh - var(--dialog-top-offset, 11dvh) - 1rem);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
405
423
|
/* Dialog motion + texture: caller-origin flight, centered fallback, noise. */
|
|
406
424
|
@keyframes dialog-fly-in {
|
|
407
425
|
from {
|