@diwauhris/ui 1.0.0
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 +75 -0
- package/lib/assets/ui.css +154 -0
- package/lib/index.cjs +183 -0
- package/lib/index.mjs +65 -0
- package/lib/index10.cjs +33 -0
- package/lib/index10.cjs.map +1 -0
- package/lib/index10.mjs +33 -0
- package/lib/index10.mjs.map +1 -0
- package/lib/index11.cjs +16 -0
- package/lib/index11.cjs.map +1 -0
- package/lib/index11.mjs +16 -0
- package/lib/index11.mjs.map +1 -0
- package/lib/index12.cjs +16 -0
- package/lib/index12.cjs.map +1 -0
- package/lib/index12.mjs +15 -0
- package/lib/index12.mjs.map +1 -0
- package/lib/index13.cjs +57 -0
- package/lib/index13.cjs.map +1 -0
- package/lib/index13.mjs +57 -0
- package/lib/index13.mjs.map +1 -0
- package/lib/index14.cjs +10 -0
- package/lib/index14.cjs.map +1 -0
- package/lib/index14.mjs +10 -0
- package/lib/index14.mjs.map +1 -0
- package/lib/index15.cjs +30 -0
- package/lib/index15.cjs.map +1 -0
- package/lib/index15.mjs +30 -0
- package/lib/index15.mjs.map +1 -0
- package/lib/index16.cjs +28 -0
- package/lib/index16.cjs.map +1 -0
- package/lib/index16.mjs +26 -0
- package/lib/index16.mjs.map +1 -0
- package/lib/index17.cjs +43 -0
- package/lib/index17.cjs.map +1 -0
- package/lib/index17.mjs +43 -0
- package/lib/index17.mjs.map +1 -0
- package/lib/index18.cjs +59 -0
- package/lib/index18.cjs.map +1 -0
- package/lib/index18.mjs +59 -0
- package/lib/index18.mjs.map +1 -0
- package/lib/index19.cjs +67 -0
- package/lib/index19.cjs.map +1 -0
- package/lib/index19.mjs +67 -0
- package/lib/index19.mjs.map +1 -0
- package/lib/index2.cjs +33 -0
- package/lib/index2.mjs +13 -0
- package/lib/index20.cjs +79 -0
- package/lib/index20.cjs.map +1 -0
- package/lib/index20.mjs +79 -0
- package/lib/index20.mjs.map +1 -0
- package/lib/index21.cjs +105 -0
- package/lib/index21.cjs.map +1 -0
- package/lib/index21.mjs +104 -0
- package/lib/index21.mjs.map +1 -0
- package/lib/index22.cjs +50 -0
- package/lib/index22.cjs.map +1 -0
- package/lib/index22.mjs +50 -0
- package/lib/index22.mjs.map +1 -0
- package/lib/index23.cjs +143 -0
- package/lib/index23.cjs.map +1 -0
- package/lib/index23.mjs +143 -0
- package/lib/index23.mjs.map +1 -0
- package/lib/index25.cjs +177 -0
- package/lib/index25.cjs.map +1 -0
- package/lib/index25.mjs +175 -0
- package/lib/index25.mjs.map +1 -0
- package/lib/index26.cjs +108 -0
- package/lib/index26.cjs.map +1 -0
- package/lib/index26.mjs +108 -0
- package/lib/index26.mjs.map +1 -0
- package/lib/index27.cjs +120 -0
- package/lib/index27.cjs.map +1 -0
- package/lib/index27.mjs +120 -0
- package/lib/index27.mjs.map +1 -0
- package/lib/index28.cjs +54 -0
- package/lib/index28.cjs.map +1 -0
- package/lib/index28.mjs +54 -0
- package/lib/index28.mjs.map +1 -0
- package/lib/index29.cjs +100 -0
- package/lib/index29.cjs.map +1 -0
- package/lib/index29.mjs +100 -0
- package/lib/index29.mjs.map +1 -0
- package/lib/index3.cjs +38 -0
- package/lib/index3.cjs.map +1 -0
- package/lib/index3.mjs +38 -0
- package/lib/index3.mjs.map +1 -0
- package/lib/index30.cjs +27 -0
- package/lib/index30.cjs.map +1 -0
- package/lib/index30.mjs +27 -0
- package/lib/index30.mjs.map +1 -0
- package/lib/index31.cjs +72 -0
- package/lib/index31.cjs.map +1 -0
- package/lib/index31.mjs +72 -0
- package/lib/index31.mjs.map +1 -0
- package/lib/index32.cjs +19 -0
- package/lib/index32.cjs.map +1 -0
- package/lib/index32.mjs +19 -0
- package/lib/index32.mjs.map +1 -0
- package/lib/index33.cjs +25 -0
- package/lib/index33.cjs.map +1 -0
- package/lib/index33.mjs +25 -0
- package/lib/index33.mjs.map +1 -0
- package/lib/index34.cjs +55 -0
- package/lib/index34.cjs.map +1 -0
- package/lib/index34.mjs +51 -0
- package/lib/index34.mjs.map +1 -0
- package/lib/index35.cjs +25 -0
- package/lib/index35.cjs.map +1 -0
- package/lib/index35.mjs +25 -0
- package/lib/index35.mjs.map +1 -0
- package/lib/index36.cjs +54 -0
- package/lib/index36.cjs.map +1 -0
- package/lib/index36.mjs +49 -0
- package/lib/index36.mjs.map +1 -0
- package/lib/index37.cjs +130 -0
- package/lib/index37.cjs.map +1 -0
- package/lib/index37.mjs +128 -0
- package/lib/index37.mjs.map +1 -0
- package/lib/index38.cjs +106 -0
- package/lib/index38.cjs.map +1 -0
- package/lib/index38.mjs +102 -0
- package/lib/index38.mjs.map +1 -0
- package/lib/index39.cjs +137 -0
- package/lib/index39.cjs.map +1 -0
- package/lib/index39.mjs +137 -0
- package/lib/index39.mjs.map +1 -0
- package/lib/index4.cjs +24 -0
- package/lib/index4.cjs.map +1 -0
- package/lib/index4.mjs +24 -0
- package/lib/index4.mjs.map +1 -0
- package/lib/index40.cjs +130 -0
- package/lib/index40.cjs.map +1 -0
- package/lib/index40.mjs +126 -0
- package/lib/index40.mjs.map +1 -0
- package/lib/index41.cjs +60 -0
- package/lib/index41.cjs.map +1 -0
- package/lib/index41.mjs +58 -0
- package/lib/index41.mjs.map +1 -0
- package/lib/index42.cjs +105 -0
- package/lib/index42.cjs.map +1 -0
- package/lib/index42.mjs +105 -0
- package/lib/index42.mjs.map +1 -0
- package/lib/index43.cjs +47 -0
- package/lib/index43.cjs.map +1 -0
- package/lib/index43.mjs +42 -0
- package/lib/index43.mjs.map +1 -0
- package/lib/index44.cjs +40 -0
- package/lib/index44.cjs.map +1 -0
- package/lib/index44.mjs +35 -0
- package/lib/index44.mjs.map +1 -0
- package/lib/index45.cjs +91 -0
- package/lib/index45.cjs.map +1 -0
- package/lib/index45.mjs +91 -0
- package/lib/index45.mjs.map +1 -0
- package/lib/index46.cjs +180 -0
- package/lib/index46.cjs.map +1 -0
- package/lib/index46.mjs +180 -0
- package/lib/index46.mjs.map +1 -0
- package/lib/index47.cjs +61 -0
- package/lib/index47.cjs.map +1 -0
- package/lib/index47.mjs +61 -0
- package/lib/index47.mjs.map +1 -0
- package/lib/index48.cjs +165 -0
- package/lib/index48.cjs.map +1 -0
- package/lib/index48.mjs +161 -0
- package/lib/index48.mjs.map +1 -0
- package/lib/index49.cjs +83 -0
- package/lib/index49.cjs.map +1 -0
- package/lib/index49.mjs +83 -0
- package/lib/index49.mjs.map +1 -0
- package/lib/index5.cjs +39 -0
- package/lib/index5.cjs.map +1 -0
- package/lib/index5.mjs +39 -0
- package/lib/index5.mjs.map +1 -0
- package/lib/index50.cjs +100 -0
- package/lib/index50.cjs.map +1 -0
- package/lib/index50.mjs +100 -0
- package/lib/index50.mjs.map +1 -0
- package/lib/index51.cjs +116 -0
- package/lib/index51.cjs.map +1 -0
- package/lib/index51.mjs +113 -0
- package/lib/index51.mjs.map +1 -0
- package/lib/index52.cjs +93 -0
- package/lib/index52.cjs.map +1 -0
- package/lib/index52.mjs +93 -0
- package/lib/index52.mjs.map +1 -0
- package/lib/index53.cjs +120 -0
- package/lib/index53.cjs.map +1 -0
- package/lib/index53.mjs +120 -0
- package/lib/index53.mjs.map +1 -0
- package/lib/index54.cjs +53 -0
- package/lib/index54.cjs.map +1 -0
- package/lib/index54.mjs +50 -0
- package/lib/index54.mjs.map +1 -0
- package/lib/index55.cjs +130 -0
- package/lib/index55.cjs.map +1 -0
- package/lib/index55.mjs +127 -0
- package/lib/index55.mjs.map +1 -0
- package/lib/index56.cjs +36 -0
- package/lib/index56.cjs.map +1 -0
- package/lib/index56.mjs +35 -0
- package/lib/index56.mjs.map +1 -0
- package/lib/index57.cjs +47 -0
- package/lib/index57.cjs.map +1 -0
- package/lib/index57.mjs +47 -0
- package/lib/index57.mjs.map +1 -0
- package/lib/index58.cjs +97 -0
- package/lib/index58.cjs.map +1 -0
- package/lib/index58.mjs +97 -0
- package/lib/index58.mjs.map +1 -0
- package/lib/index59.cjs +120 -0
- package/lib/index59.cjs.map +1 -0
- package/lib/index59.mjs +116 -0
- package/lib/index59.mjs.map +1 -0
- package/lib/index6.cjs +29 -0
- package/lib/index6.cjs.map +1 -0
- package/lib/index6.mjs +29 -0
- package/lib/index6.mjs.map +1 -0
- package/lib/index60.cjs +34 -0
- package/lib/index60.cjs.map +1 -0
- package/lib/index60.mjs +34 -0
- package/lib/index60.mjs.map +1 -0
- package/lib/index61.cjs +62 -0
- package/lib/index61.cjs.map +1 -0
- package/lib/index61.mjs +62 -0
- package/lib/index61.mjs.map +1 -0
- package/lib/index62.cjs +213 -0
- package/lib/index62.cjs.map +1 -0
- package/lib/index62.mjs +213 -0
- package/lib/index62.mjs.map +1 -0
- package/lib/index63.cjs +42 -0
- package/lib/index63.cjs.map +1 -0
- package/lib/index63.mjs +42 -0
- package/lib/index63.mjs.map +1 -0
- package/lib/index64.cjs +86 -0
- package/lib/index64.cjs.map +1 -0
- package/lib/index64.mjs +86 -0
- package/lib/index64.mjs.map +1 -0
- package/lib/index65.cjs +156 -0
- package/lib/index65.cjs.map +1 -0
- package/lib/index65.mjs +156 -0
- package/lib/index65.mjs.map +1 -0
- package/lib/index66.cjs +107 -0
- package/lib/index66.cjs.map +1 -0
- package/lib/index66.mjs +106 -0
- package/lib/index66.mjs.map +1 -0
- package/lib/index67.cjs +52 -0
- package/lib/index67.cjs.map +1 -0
- package/lib/index67.mjs +52 -0
- package/lib/index67.mjs.map +1 -0
- package/lib/index68.cjs +125 -0
- package/lib/index68.cjs.map +1 -0
- package/lib/index68.mjs +115 -0
- package/lib/index68.mjs.map +1 -0
- package/lib/index69.cjs +34 -0
- package/lib/index69.cjs.map +1 -0
- package/lib/index69.mjs +29 -0
- package/lib/index69.mjs.map +1 -0
- package/lib/index7.cjs +17 -0
- package/lib/index7.cjs.map +1 -0
- package/lib/index7.mjs +17 -0
- package/lib/index7.mjs.map +1 -0
- package/lib/index8.cjs +86 -0
- package/lib/index8.cjs.map +1 -0
- package/lib/index8.mjs +86 -0
- package/lib/index8.mjs.map +1 -0
- package/lib/index9.cjs +29 -0
- package/lib/index9.cjs.map +1 -0
- package/lib/index9.mjs +29 -0
- package/lib/index9.mjs.map +1 -0
- package/lib/types/index.d.ts +1 -0
- package/package.json +68 -0
package/lib/index38.mjs
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { cn } from "./index14.mjs";
|
|
2
|
+
import { typography } from "./index36.mjs";
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
|
+
import "react";
|
|
5
|
+
import { X } from "lucide-react";
|
|
6
|
+
import * as DialogPrimitive from "@radix-ui/react-dialog";
|
|
7
|
+
//#region src/ui-library/gallery/dialog/Dialog.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Dialog — Design System Component
|
|
10
|
+
*
|
|
11
|
+
* A focus-blocking modal dialog.
|
|
12
|
+
*
|
|
13
|
+
* Behavior is provided by @radix-ui/react-dialog, which handles:
|
|
14
|
+
* - Focus trap (Tab / Shift+Tab contained within dialog)
|
|
15
|
+
* - Focus restoration (returns to trigger on close)
|
|
16
|
+
* - Escape key dismissal (unless preventClose)
|
|
17
|
+
* - Backdrop interaction dismissal (unless preventClose)
|
|
18
|
+
* - Body scroll lock while open
|
|
19
|
+
* - Portal rendering to document.body
|
|
20
|
+
* - role="dialog" + aria-modal + aria-labelledby + aria-describedby
|
|
21
|
+
* - Modal semantics
|
|
22
|
+
*
|
|
23
|
+
* DIWA continues to own:
|
|
24
|
+
* - Public API (open, onClose, title, description, size, preventClose, children)
|
|
25
|
+
* - Backdrop visual treatment
|
|
26
|
+
* - Panel visual treatment (bg, border, radius, shadow, sizing)
|
|
27
|
+
* - Header layout and typography
|
|
28
|
+
* - Close button styling
|
|
29
|
+
* - DialogBody and DialogFooter layout
|
|
30
|
+
*
|
|
31
|
+
* Composition:
|
|
32
|
+
* <Dialog open={open} onClose={close} title="…">
|
|
33
|
+
* <DialogBody>…</DialogBody>
|
|
34
|
+
* <DialogFooter>…</DialogFooter>
|
|
35
|
+
* </Dialog>
|
|
36
|
+
*/
|
|
37
|
+
var SIZE_CLASS = {
|
|
38
|
+
sm: "max-w-sm",
|
|
39
|
+
md: "max-w-lg",
|
|
40
|
+
lg: "max-w-2xl",
|
|
41
|
+
xl: "max-w-4xl"
|
|
42
|
+
};
|
|
43
|
+
function DialogBody({ children, className = "" }) {
|
|
44
|
+
return /* @__PURE__ */ jsx("div", {
|
|
45
|
+
className: `min-h-0 overflow-y-auto overscroll-contain px-6 py-4 ${className}`,
|
|
46
|
+
children
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function DialogFooter({ children, className = "" }) {
|
|
50
|
+
return /* @__PURE__ */ jsx("div", {
|
|
51
|
+
className: `flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4 ${className}`,
|
|
52
|
+
children
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
function Dialog({ open, onClose, title, description, preventClose = false, size = "md", children }) {
|
|
56
|
+
const hasTitle = Boolean(title);
|
|
57
|
+
const hasDesc = Boolean(description);
|
|
58
|
+
return /* @__PURE__ */ jsx(DialogPrimitive.Root, {
|
|
59
|
+
open,
|
|
60
|
+
onOpenChange: (isOpen) => {
|
|
61
|
+
if (!isOpen && !preventClose) onClose();
|
|
62
|
+
},
|
|
63
|
+
children: /* @__PURE__ */ jsxs(DialogPrimitive.Portal, { children: [/* @__PURE__ */ jsx(DialogPrimitive.Overlay, { className: "fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm" }), /* @__PURE__ */ jsxs(DialogPrimitive.Content, {
|
|
64
|
+
className: cn("fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2", "flex w-full flex-col overflow-hidden", "rounded-lg bg-white shadow-xl", "max-h-[90vh] outline-none", "p-4", SIZE_CLASS[size]),
|
|
65
|
+
onEscapeKeyDown: preventClose ? (e) => e.preventDefault() : void 0,
|
|
66
|
+
onInteractOutside: preventClose ? (e) => e.preventDefault() : void 0,
|
|
67
|
+
style: { padding: 0 },
|
|
68
|
+
children: [
|
|
69
|
+
hasTitle ? /* @__PURE__ */ jsxs("div", {
|
|
70
|
+
className: "shrink-0 border-b-2 border-slate-100 px-6 py-5 pr-14",
|
|
71
|
+
children: [/* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
72
|
+
className: cn(typography.titleOverlay, "text-slate-900"),
|
|
73
|
+
children: title
|
|
74
|
+
}), hasDesc && /* @__PURE__ */ jsx(DialogPrimitive.Description, {
|
|
75
|
+
className: "mt-0.5 text-sm font-medium text-slate-500",
|
|
76
|
+
children: description
|
|
77
|
+
})]
|
|
78
|
+
}) : /* @__PURE__ */ jsx(DialogPrimitive.Title, {
|
|
79
|
+
className: "sr-only",
|
|
80
|
+
children: "Dialog"
|
|
81
|
+
}),
|
|
82
|
+
!preventClose && /* @__PURE__ */ jsx(DialogPrimitive.Close, {
|
|
83
|
+
asChild: true,
|
|
84
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
85
|
+
type: "button",
|
|
86
|
+
"aria-label": "Close dialog",
|
|
87
|
+
className: "absolute right-4 top-4 rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
88
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
89
|
+
size: 16,
|
|
90
|
+
"aria-hidden": "true"
|
|
91
|
+
})
|
|
92
|
+
})
|
|
93
|
+
}),
|
|
94
|
+
children
|
|
95
|
+
]
|
|
96
|
+
})] })
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
//#endregion
|
|
100
|
+
export { Dialog, DialogBody, DialogFooter };
|
|
101
|
+
|
|
102
|
+
//# sourceMappingURL=index38.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index38.mjs","names":[],"sources":["../src/ui-library/gallery/dialog/Dialog.tsx"],"sourcesContent":["/**\n * Dialog — Design System Component\n *\n * A focus-blocking modal dialog.\n *\n * Behavior is provided by @radix-ui/react-dialog, which handles:\n * - Focus trap (Tab / Shift+Tab contained within dialog)\n * - Focus restoration (returns to trigger on close)\n * - Escape key dismissal (unless preventClose)\n * - Backdrop interaction dismissal (unless preventClose)\n * - Body scroll lock while open\n * - Portal rendering to document.body\n * - role=\"dialog\" + aria-modal + aria-labelledby + aria-describedby\n * - Modal semantics\n *\n * DIWA continues to own:\n * - Public API (open, onClose, title, description, size, preventClose, children)\n * - Backdrop visual treatment\n * - Panel visual treatment (bg, border, radius, shadow, sizing)\n * - Header layout and typography\n * - Close button styling\n * - DialogBody and DialogFooter layout\n *\n * Composition:\n * <Dialog open={open} onClose={close} title=\"…\">\n * <DialogBody>…</DialogBody>\n * <DialogFooter>…</DialogFooter>\n * </Dialog>\n */\n\nimport * as DialogPrimitive from '@radix-ui/react-dialog';\nimport { type ReactNode } from 'react';\nimport { X } from 'lucide-react';\nimport { cn } from '../../../lib/utils';\nimport { typography } from '../../tokens/typography';\n\nexport type DialogSize = 'sm' | 'md' | 'lg' | 'xl';\n\nexport interface DialogProps {\n open: boolean;\n onClose: () => void;\n title?: string;\n description?: string;\n /** When true, Escape and backdrop click will not close the dialog */\n preventClose?: boolean;\n size?: DialogSize;\n children?: ReactNode;\n}\n\nexport interface DialogBodyProps {\n children: ReactNode;\n className?: string;\n}\n\nexport interface DialogFooterProps {\n children: ReactNode;\n className?: string;\n}\n\n// ── Style maps ────────────────────────────────────────────────────────────────\n\nconst SIZE_CLASS: Record<DialogSize, string> = {\n sm: 'max-w-sm',\n md: 'max-w-lg',\n lg: 'max-w-2xl',\n xl: 'max-w-4xl',\n};\n\n// ── Sub-components — pure DIWA layout, no Radix involvement ──────────────────\n\nexport function DialogBody({ children, className = '' }: DialogBodyProps) {\n return (\n <div className={`min-h-0 overflow-y-auto overscroll-contain px-6 py-4 ${className}`}>\n {children}\n </div>\n );\n}\n\nexport function DialogFooter({ children, className = '' }: DialogFooterProps) {\n return (\n <div className={`flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4 ${className}`}>\n {children}\n </div>\n );\n}\n\n// ── Main component ────────────────────────────────────────────────────────────\n\nexport function Dialog({\n open,\n onClose,\n title,\n description,\n preventClose = false,\n size = 'md',\n children,\n}: DialogProps) {\n const hasTitle = Boolean(title);\n const hasDesc = Boolean(description);\n\n return (\n // Radix Dialog.Root drives open/close state.\n // onOpenChange is called when Radix wants to close (Escape, outside click).\n // We route it through DIWA's onClose, respecting preventClose.\n <DialogPrimitive.Root\n open={open}\n onOpenChange={(isOpen) => {\n // Radix calls onOpenChange(false) when closing.\n // Ignore opening attempts from Radix (we control open externally).\n if (!isOpen && !preventClose) onClose();\n }}\n >\n <DialogPrimitive.Portal>\n {/* Backdrop — DIWA visual treatment */}\n <DialogPrimitive.Overlay\n className=\"fixed inset-0 z-50 bg-brand-navy/50 backdrop-blur-sm\"\n />\n\n {/* Panel — DIWA visual treatment */}\n <DialogPrimitive.Content\n className={cn(\n 'fixed left-1/2 top-1/2 z-50 -translate-x-1/2 -translate-y-1/2',\n 'flex w-full flex-col overflow-hidden',\n 'rounded-lg bg-white shadow-xl',\n 'max-h-[90vh] outline-none',\n 'p-4',\n SIZE_CLASS[size],\n )}\n // Suppress Radix's own Escape/outside-click when preventClose is set.\n // Radix calls these before triggering onOpenChange, so preventing here\n // ensures neither the overlay nor keyboard can dismiss the dialog.\n onEscapeKeyDown={preventClose ? (e) => e.preventDefault() : undefined}\n onInteractOutside={preventClose ? (e) => e.preventDefault() : undefined}\n // Remove padding added by className — override with our layout below\n style={{ padding: 0 }}\n >\n {/* Radix requires Dialog.Title for accessible naming.\n Render it visually when title prop is provided.\n When absent, render sr-only so Radix's ARIA wiring works\n without adding visible text the consumer didn't request. */}\n {hasTitle ? (\n // Header — visible when title or description are provided\n <div className=\"shrink-0 border-b-2 border-slate-100 px-6 py-5 pr-14\">\n <DialogPrimitive.Title className={cn(typography.titleOverlay, 'text-slate-900')}>\n {title}\n </DialogPrimitive.Title>\n {hasDesc && (\n <DialogPrimitive.Description className=\"mt-0.5 text-sm font-medium text-slate-500\">\n {description}\n </DialogPrimitive.Description>\n )}\n </div>\n ) : (\n // No visible title — render sr-only for Radix ARIA requirement\n <DialogPrimitive.Title className=\"sr-only\">\n Dialog\n </DialogPrimitive.Title>\n )}\n\n {/* Close button — uses Radix Dialog.Close so it participates\n in the Radix open/close flow. asChild avoids an extra DOM node. */}\n {!preventClose && (\n <DialogPrimitive.Close asChild>\n <button\n type=\"button\"\n aria-label=\"Close dialog\"\n className=\"absolute right-4 top-4 rounded-lg p-1.5 text-slate-400 transition hover:bg-slate-100 hover:text-slate-600 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <X size={16} aria-hidden=\"true\" />\n </button>\n </DialogPrimitive.Close>\n )}\n\n {/* Content — children contain DialogBody + DialogFooter */}\n {children}\n </DialogPrimitive.Content>\n </DialogPrimitive.Portal>\n </DialogPrimitive.Root>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6DA,IAAM,aAAyC;CAC7C,IAAI;CACJ,IAAI;CACJ,IAAI;CACJ,IAAI;AACN;AAIA,SAAgB,WAAW,EAAE,UAAU,YAAY,MAAuB;CACxE,OACE,oBAAC,OAAD;EAAK,WAAW,wDAAwD;EACrE;CACE,CAAA;AAET;AAEA,SAAgB,aAAa,EAAE,UAAU,YAAY,MAAyB;CAC5E,OACE,oBAAC,OAAD;EAAK,WAAW,2EAA2E;EACxF;CACE,CAAA;AAET;AAIA,SAAgB,OAAO,EACrB,MACA,SACA,OACA,aACA,eAAe,OACf,OAAO,MACP,YACc;CACd,MAAM,WAAW,QAAQ,KAAK;CAC9B,MAAM,UAAW,QAAQ,WAAW;CAEpC,OAIE,oBAAC,gBAAgB,MAAjB;EACQ;EACN,eAAe,WAAW;GAGxB,IAAI,CAAC,UAAU,CAAC,cAAc,QAAQ;EACxC;EAEA,UAAA,qBAAC,gBAAgB,QAAjB,EAAA,UAAA,CAEE,oBAAC,gBAAgB,SAAjB,EACE,WAAU,uDACX,CAAA,GAGD,qBAAC,gBAAgB,SAAjB;GACE,WAAW,GACT,iEACA,wCACA,iCACA,6BACA,OACA,WAAW,KACb;GAIA,iBAAiB,gBAAgB,MAAM,EAAE,eAAe,IAAI,KAAA;GAC5D,mBAAmB,gBAAgB,MAAM,EAAE,eAAe,IAAI,KAAA;GAE9D,OAAO,EAAE,SAAS,EAAE;GAftB,UAAA;IAqBG,WAEC,qBAAC,OAAD;KAAK,WAAU;KAAf,UAAA,CACE,oBAAC,gBAAgB,OAAjB;MAAuB,WAAW,GAAG,WAAW,cAAc,gBAAgB;MAC3E,UAAA;KACoB,CAAA,GACtB,WACC,oBAAC,gBAAgB,aAAjB;MAA6B,WAAU;MACpC,UAAA;KAC0B,CAAA,CAE5B;IAGL,CAAA,IAAA,oBAAC,gBAAgB,OAAjB;KAAuB,WAAU;KAAU,UAAA;IAEpB,CAAA;IAKxB,CAAC,gBACA,oBAAC,gBAAgB,OAAjB;KAAuB,SAAA;KACrB,UAAA,oBAAC,UAAD;MACE,MAAK;MACL,cAAW;MACX,WAAU;MAEV,UAAA,oBAAC,GAAD;OAAG,MAAM;OAAI,eAAY;MAAQ,CAAA;KAC3B,CAAA;IACa,CAAA;IAIxB;GACsB;EACH,CAAA,CAAA,EAAA,CAAA;CACJ,CAAA;AAE1B"}
|
package/lib/index39.cjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
|
+
let react = require("react");
|
|
3
|
+
let lucide_react = require("lucide-react");
|
|
4
|
+
let react_dom = require("react-dom");
|
|
5
|
+
//#region src/ui-library/gallery/drawer/Drawer.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Drawer — Design System Component
|
|
8
|
+
*
|
|
9
|
+
* A slide-in panel anchored to the screen edge. Supports right (default)
|
|
10
|
+
* and bottom placements.
|
|
11
|
+
*
|
|
12
|
+
* Adaptation note: framer-motion animation replaced with CSS transitions
|
|
13
|
+
* (translate + opacity) for the standalone gallery which does not include
|
|
14
|
+
* framer-motion as a dependency.
|
|
15
|
+
*/
|
|
16
|
+
var FOCUSABLE = "a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])";
|
|
17
|
+
var PLACEMENT_PANEL_CLASS = {
|
|
18
|
+
right: "fixed top-0 right-0 bottom-0 flex flex-col",
|
|
19
|
+
bottom: "fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl"
|
|
20
|
+
};
|
|
21
|
+
function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children }) {
|
|
22
|
+
const panelRef = (0, react.useRef)(null);
|
|
23
|
+
const titleId = (0, react.useId)();
|
|
24
|
+
const [rendered, setRendered] = (0, react.useState)(open);
|
|
25
|
+
const [visible, setVisible] = (0, react.useState)(false);
|
|
26
|
+
(0, react.useEffect)(() => {
|
|
27
|
+
if (open) {
|
|
28
|
+
setRendered(true);
|
|
29
|
+
requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));
|
|
30
|
+
} else {
|
|
31
|
+
setVisible(false);
|
|
32
|
+
const timer = window.setTimeout(() => setRendered(false), 300);
|
|
33
|
+
return () => window.clearTimeout(timer);
|
|
34
|
+
}
|
|
35
|
+
}, [open]);
|
|
36
|
+
(0, react.useEffect)(() => {
|
|
37
|
+
if (!open) return;
|
|
38
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
39
|
+
const prevOverflow = document.body.style.overflow;
|
|
40
|
+
document.body.style.overflow = "hidden";
|
|
41
|
+
const timer = window.setTimeout(() => {
|
|
42
|
+
const panel = panelRef.current;
|
|
43
|
+
((panel ? Array.from(panel.querySelectorAll(FOCUSABLE)) : [])[0] ?? panel)?.focus();
|
|
44
|
+
}, 0);
|
|
45
|
+
return () => {
|
|
46
|
+
window.clearTimeout(timer);
|
|
47
|
+
document.body.style.overflow = prevOverflow;
|
|
48
|
+
previousFocus?.focus();
|
|
49
|
+
};
|
|
50
|
+
}, [open]);
|
|
51
|
+
(0, react.useEffect)(() => {
|
|
52
|
+
if (!open) return;
|
|
53
|
+
const handleKeyDown = (e) => {
|
|
54
|
+
if (e.key === "Escape") {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
onClose();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (e.key !== "Tab") return;
|
|
60
|
+
const panel = panelRef.current;
|
|
61
|
+
if (!panel) return;
|
|
62
|
+
const focusable = Array.from(panel.querySelectorAll(FOCUSABLE));
|
|
63
|
+
if (!focusable.length) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
panel.focus();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const first = focusable[0];
|
|
69
|
+
const last = focusable[focusable.length - 1];
|
|
70
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
last.focus();
|
|
73
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
first.focus();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
79
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
80
|
+
}, [open, onClose]);
|
|
81
|
+
if (!rendered) return null;
|
|
82
|
+
const panelClass = [
|
|
83
|
+
PLACEMENT_PANEL_CLASS[placement],
|
|
84
|
+
placement === "right" ? width : maxHeight,
|
|
85
|
+
"bg-white shadow-xl focus:outline-none",
|
|
86
|
+
placement === "bottom" ? "overflow-y-auto" : "overflow-hidden",
|
|
87
|
+
"transition-transform duration-300 ease-out",
|
|
88
|
+
placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full"
|
|
89
|
+
].filter(Boolean).join(" ");
|
|
90
|
+
return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
91
|
+
className: "fixed inset-0 z-50",
|
|
92
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
93
|
+
className: `fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? "opacity-100" : "opacity-0"}`,
|
|
94
|
+
onClick: onClose,
|
|
95
|
+
"aria-hidden": "true"
|
|
96
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
97
|
+
ref: panelRef,
|
|
98
|
+
role: "dialog",
|
|
99
|
+
"aria-modal": "true",
|
|
100
|
+
"aria-labelledby": titleId,
|
|
101
|
+
"aria-describedby": description ? `${titleId}-desc` : void 0,
|
|
102
|
+
tabIndex: -1,
|
|
103
|
+
className: panelClass,
|
|
104
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
105
|
+
className: "flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0",
|
|
106
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
107
|
+
className: "min-w-0",
|
|
108
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
109
|
+
id: titleId,
|
|
110
|
+
className: "font-heading text-base font-bold text-slate-900 truncate",
|
|
111
|
+
children: title
|
|
112
|
+
}), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
113
|
+
id: `${titleId}-desc`,
|
|
114
|
+
className: "text-xs text-slate-500 mt-0.5",
|
|
115
|
+
children: description
|
|
116
|
+
})]
|
|
117
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
118
|
+
type: "button",
|
|
119
|
+
onClick: onClose,
|
|
120
|
+
"aria-label": closeLabel,
|
|
121
|
+
className: "ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
122
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, {
|
|
123
|
+
size: 16,
|
|
124
|
+
"aria-hidden": "true"
|
|
125
|
+
})
|
|
126
|
+
})]
|
|
127
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
128
|
+
className: "flex-1 overflow-y-auto px-5 py-4",
|
|
129
|
+
children
|
|
130
|
+
})]
|
|
131
|
+
})]
|
|
132
|
+
}), document.body);
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
exports.Drawer = Drawer;
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=index39.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index39.cjs","names":[],"sources":["../src/ui-library/gallery/drawer/Drawer.tsx"],"sourcesContent":["/**\n * Drawer — Design System Component\n *\n * A slide-in panel anchored to the screen edge. Supports right (default)\n * and bottom placements.\n *\n * Adaptation note: framer-motion animation replaced with CSS transitions\n * (translate + opacity) for the standalone gallery which does not include\n * framer-motion as a dependency.\n */\n\nimport { useEffect, useId, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { X } from 'lucide-react';\n\nexport type DrawerPlacement = 'right' | 'bottom';\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n title: ReactNode;\n description?: string;\n placement?: DrawerPlacement;\n width?: string;\n maxHeight?: string;\n closeLabel?: string;\n children: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nconst PLACEMENT_PANEL_CLASS: Record<DrawerPlacement, string> = {\n right: 'fixed top-0 right-0 bottom-0 flex flex-col',\n bottom: 'fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl',\n};\n\nexport function Drawer({\n open,\n onClose,\n title,\n description,\n placement = 'right',\n width = 'w-80',\n maxHeight = 'max-h-[85vh]',\n closeLabel = 'Close drawer',\n children,\n}: DrawerProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // rendered tracks whether the portal is mounted at all\n const [rendered, setRendered] = useState(open);\n // visible tracks the CSS transition state\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n if (open) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setRendered(true);\n // Allow the element to mount before starting the transition\n requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));\n } else {\n setVisible(false);\n // Remove from DOM after transition completes\n const timer = window.setTimeout(() => setRendered(false), 300);\n return () => window.clearTimeout(timer);\n }\n }, [open]);\n\n // Effect 1: scroll lock + initial focus + focus restoration\n useEffect(() => {\n if (!open) return;\n const previousFocus = document.activeElement instanceof HTMLElement\n ? document.activeElement : null;\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n\n const timer = window.setTimeout(() => {\n const panel = panelRef.current;\n const focusable = panel ? Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)) : [];\n (focusable[0] ?? panel)?.focus();\n }, 0);\n\n return () => {\n window.clearTimeout(timer);\n document.body.style.overflow = prevOverflow;\n previousFocus?.focus();\n };\n }, [open]);\n\n // Effect 2: keyboard — Escape + focus trap\n useEffect(() => {\n if (!open) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') { e.preventDefault(); onClose(); return; }\n if (e.key !== 'Tab') return;\n const panel = panelRef.current;\n if (!panel) return;\n const focusable = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE));\n if (!focusable.length) { e.preventDefault(); panel.focus(); return; }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }\n else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [open, onClose]);\n\n if (!rendered) return null;\n\n const panelClass = [\n PLACEMENT_PANEL_CLASS[placement],\n placement === 'right' ? width : maxHeight,\n 'bg-white shadow-xl focus:outline-none',\n placement === 'bottom' ? 'overflow-y-auto' : 'overflow-hidden',\n 'transition-transform duration-300 ease-out',\n placement === 'right'\n ? (visible ? 'translate-x-0' : 'translate-x-full')\n : (visible ? 'translate-y-0' : 'translate-y-full'),\n ].filter(Boolean).join(' ');\n\n return createPortal(\n <div className=\"fixed inset-0 z-50\">\n {/* Backdrop */}\n <div\n className={`fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? 'opacity-100' : 'opacity-0'}`}\n onClick={onClose}\n aria-hidden=\"true\"\n />\n\n {/* Panel */}\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={description ? `${titleId}-desc` : undefined}\n tabIndex={-1}\n className={panelClass}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0\">\n <div className=\"min-w-0\">\n <h2 id={titleId} className=\"font-heading text-base font-bold text-slate-900 truncate\">\n {title}\n </h2>\n {description && (\n <p id={`${titleId}-desc`} className=\"text-xs text-slate-500 mt-0.5\">\n {description}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={closeLabel}\n className=\"ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <X size={16} aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Scrollable body */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {children}\n </div>\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"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"}
|
package/lib/index39.mjs
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useId, useRef, useState } from "react";
|
|
3
|
+
import { X } from "lucide-react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
//#region src/ui-library/gallery/drawer/Drawer.tsx
|
|
6
|
+
/**
|
|
7
|
+
* Drawer — Design System Component
|
|
8
|
+
*
|
|
9
|
+
* A slide-in panel anchored to the screen edge. Supports right (default)
|
|
10
|
+
* and bottom placements.
|
|
11
|
+
*
|
|
12
|
+
* Adaptation note: framer-motion animation replaced with CSS transitions
|
|
13
|
+
* (translate + opacity) for the standalone gallery which does not include
|
|
14
|
+
* framer-motion as a dependency.
|
|
15
|
+
*/
|
|
16
|
+
var FOCUSABLE = "a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])";
|
|
17
|
+
var PLACEMENT_PANEL_CLASS = {
|
|
18
|
+
right: "fixed top-0 right-0 bottom-0 flex flex-col",
|
|
19
|
+
bottom: "fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl"
|
|
20
|
+
};
|
|
21
|
+
function Drawer({ open, onClose, title, description, placement = "right", width = "w-80", maxHeight = "max-h-[85vh]", closeLabel = "Close drawer", children }) {
|
|
22
|
+
const panelRef = useRef(null);
|
|
23
|
+
const titleId = useId();
|
|
24
|
+
const [rendered, setRendered] = useState(open);
|
|
25
|
+
const [visible, setVisible] = useState(false);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
if (open) {
|
|
28
|
+
setRendered(true);
|
|
29
|
+
requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));
|
|
30
|
+
} else {
|
|
31
|
+
setVisible(false);
|
|
32
|
+
const timer = window.setTimeout(() => setRendered(false), 300);
|
|
33
|
+
return () => window.clearTimeout(timer);
|
|
34
|
+
}
|
|
35
|
+
}, [open]);
|
|
36
|
+
useEffect(() => {
|
|
37
|
+
if (!open) return;
|
|
38
|
+
const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
39
|
+
const prevOverflow = document.body.style.overflow;
|
|
40
|
+
document.body.style.overflow = "hidden";
|
|
41
|
+
const timer = window.setTimeout(() => {
|
|
42
|
+
const panel = panelRef.current;
|
|
43
|
+
((panel ? Array.from(panel.querySelectorAll(FOCUSABLE)) : [])[0] ?? panel)?.focus();
|
|
44
|
+
}, 0);
|
|
45
|
+
return () => {
|
|
46
|
+
window.clearTimeout(timer);
|
|
47
|
+
document.body.style.overflow = prevOverflow;
|
|
48
|
+
previousFocus?.focus();
|
|
49
|
+
};
|
|
50
|
+
}, [open]);
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
if (!open) return;
|
|
53
|
+
const handleKeyDown = (e) => {
|
|
54
|
+
if (e.key === "Escape") {
|
|
55
|
+
e.preventDefault();
|
|
56
|
+
onClose();
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (e.key !== "Tab") return;
|
|
60
|
+
const panel = panelRef.current;
|
|
61
|
+
if (!panel) return;
|
|
62
|
+
const focusable = Array.from(panel.querySelectorAll(FOCUSABLE));
|
|
63
|
+
if (!focusable.length) {
|
|
64
|
+
e.preventDefault();
|
|
65
|
+
panel.focus();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const first = focusable[0];
|
|
69
|
+
const last = focusable[focusable.length - 1];
|
|
70
|
+
if (e.shiftKey && document.activeElement === first) {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
last.focus();
|
|
73
|
+
} else if (!e.shiftKey && document.activeElement === last) {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
first.focus();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
79
|
+
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
80
|
+
}, [open, onClose]);
|
|
81
|
+
if (!rendered) return null;
|
|
82
|
+
const panelClass = [
|
|
83
|
+
PLACEMENT_PANEL_CLASS[placement],
|
|
84
|
+
placement === "right" ? width : maxHeight,
|
|
85
|
+
"bg-white shadow-xl focus:outline-none",
|
|
86
|
+
placement === "bottom" ? "overflow-y-auto" : "overflow-hidden",
|
|
87
|
+
"transition-transform duration-300 ease-out",
|
|
88
|
+
placement === "right" ? visible ? "translate-x-0" : "translate-x-full" : visible ? "translate-y-0" : "translate-y-full"
|
|
89
|
+
].filter(Boolean).join(" ");
|
|
90
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
91
|
+
className: "fixed inset-0 z-50",
|
|
92
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
93
|
+
className: `fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? "opacity-100" : "opacity-0"}`,
|
|
94
|
+
onClick: onClose,
|
|
95
|
+
"aria-hidden": "true"
|
|
96
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
97
|
+
ref: panelRef,
|
|
98
|
+
role: "dialog",
|
|
99
|
+
"aria-modal": "true",
|
|
100
|
+
"aria-labelledby": titleId,
|
|
101
|
+
"aria-describedby": description ? `${titleId}-desc` : void 0,
|
|
102
|
+
tabIndex: -1,
|
|
103
|
+
className: panelClass,
|
|
104
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
105
|
+
className: "flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0",
|
|
106
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
107
|
+
className: "min-w-0",
|
|
108
|
+
children: [/* @__PURE__ */ jsx("h2", {
|
|
109
|
+
id: titleId,
|
|
110
|
+
className: "font-heading text-base font-bold text-slate-900 truncate",
|
|
111
|
+
children: title
|
|
112
|
+
}), description && /* @__PURE__ */ jsx("p", {
|
|
113
|
+
id: `${titleId}-desc`,
|
|
114
|
+
className: "text-xs text-slate-500 mt-0.5",
|
|
115
|
+
children: description
|
|
116
|
+
})]
|
|
117
|
+
}), /* @__PURE__ */ jsx("button", {
|
|
118
|
+
type: "button",
|
|
119
|
+
onClick: onClose,
|
|
120
|
+
"aria-label": closeLabel,
|
|
121
|
+
className: "ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
122
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
123
|
+
size: 16,
|
|
124
|
+
"aria-hidden": "true"
|
|
125
|
+
})
|
|
126
|
+
})]
|
|
127
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
128
|
+
className: "flex-1 overflow-y-auto px-5 py-4",
|
|
129
|
+
children
|
|
130
|
+
})]
|
|
131
|
+
})]
|
|
132
|
+
}), document.body);
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
export { Drawer };
|
|
136
|
+
|
|
137
|
+
//# sourceMappingURL=index39.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index39.mjs","names":[],"sources":["../src/ui-library/gallery/drawer/Drawer.tsx"],"sourcesContent":["/**\n * Drawer — Design System Component\n *\n * A slide-in panel anchored to the screen edge. Supports right (default)\n * and bottom placements.\n *\n * Adaptation note: framer-motion animation replaced with CSS transitions\n * (translate + opacity) for the standalone gallery which does not include\n * framer-motion as a dependency.\n */\n\nimport { useEffect, useId, useRef, useState, type ReactNode } from 'react';\nimport { createPortal } from 'react-dom';\nimport { X } from 'lucide-react';\n\nexport type DrawerPlacement = 'right' | 'bottom';\n\nexport interface DrawerProps {\n open: boolean;\n onClose: () => void;\n title: ReactNode;\n description?: string;\n placement?: DrawerPlacement;\n width?: string;\n maxHeight?: string;\n closeLabel?: string;\n children: ReactNode;\n}\n\nconst FOCUSABLE =\n 'a[href],button:not([disabled]),textarea:not([disabled]),input:not([disabled]),select:not([disabled]),[tabindex]:not([tabindex=\"-1\"])';\n\nconst PLACEMENT_PANEL_CLASS: Record<DrawerPlacement, string> = {\n right: 'fixed top-0 right-0 bottom-0 flex flex-col',\n bottom: 'fixed bottom-0 left-0 right-0 flex flex-col rounded-t-xl',\n};\n\nexport function Drawer({\n open,\n onClose,\n title,\n description,\n placement = 'right',\n width = 'w-80',\n maxHeight = 'max-h-[85vh]',\n closeLabel = 'Close drawer',\n children,\n}: DrawerProps) {\n const panelRef = useRef<HTMLDivElement>(null);\n const titleId = useId();\n // rendered tracks whether the portal is mounted at all\n const [rendered, setRendered] = useState(open);\n // visible tracks the CSS transition state\n const [visible, setVisible] = useState(false);\n\n useEffect(() => {\n if (open) {\n // eslint-disable-next-line react-hooks/set-state-in-effect\n setRendered(true);\n // Allow the element to mount before starting the transition\n requestAnimationFrame(() => requestAnimationFrame(() => setVisible(true)));\n } else {\n setVisible(false);\n // Remove from DOM after transition completes\n const timer = window.setTimeout(() => setRendered(false), 300);\n return () => window.clearTimeout(timer);\n }\n }, [open]);\n\n // Effect 1: scroll lock + initial focus + focus restoration\n useEffect(() => {\n if (!open) return;\n const previousFocus = document.activeElement instanceof HTMLElement\n ? document.activeElement : null;\n const prevOverflow = document.body.style.overflow;\n document.body.style.overflow = 'hidden';\n\n const timer = window.setTimeout(() => {\n const panel = panelRef.current;\n const focusable = panel ? Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE)) : [];\n (focusable[0] ?? panel)?.focus();\n }, 0);\n\n return () => {\n window.clearTimeout(timer);\n document.body.style.overflow = prevOverflow;\n previousFocus?.focus();\n };\n }, [open]);\n\n // Effect 2: keyboard — Escape + focus trap\n useEffect(() => {\n if (!open) return;\n const handleKeyDown = (e: KeyboardEvent) => {\n if (e.key === 'Escape') { e.preventDefault(); onClose(); return; }\n if (e.key !== 'Tab') return;\n const panel = panelRef.current;\n if (!panel) return;\n const focusable = Array.from(panel.querySelectorAll<HTMLElement>(FOCUSABLE));\n if (!focusable.length) { e.preventDefault(); panel.focus(); return; }\n const first = focusable[0];\n const last = focusable[focusable.length - 1];\n if (e.shiftKey && document.activeElement === first) { e.preventDefault(); last.focus(); }\n else if (!e.shiftKey && document.activeElement === last) { e.preventDefault(); first.focus(); }\n };\n document.addEventListener('keydown', handleKeyDown);\n return () => document.removeEventListener('keydown', handleKeyDown);\n }, [open, onClose]);\n\n if (!rendered) return null;\n\n const panelClass = [\n PLACEMENT_PANEL_CLASS[placement],\n placement === 'right' ? width : maxHeight,\n 'bg-white shadow-xl focus:outline-none',\n placement === 'bottom' ? 'overflow-y-auto' : 'overflow-hidden',\n 'transition-transform duration-300 ease-out',\n placement === 'right'\n ? (visible ? 'translate-x-0' : 'translate-x-full')\n : (visible ? 'translate-y-0' : 'translate-y-full'),\n ].filter(Boolean).join(' ');\n\n return createPortal(\n <div className=\"fixed inset-0 z-50\">\n {/* Backdrop */}\n <div\n className={`fixed inset-0 bg-brand-navy/50 backdrop-blur-sm transition-opacity duration-200 ${visible ? 'opacity-100' : 'opacity-0'}`}\n onClick={onClose}\n aria-hidden=\"true\"\n />\n\n {/* Panel */}\n <div\n ref={panelRef}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby={titleId}\n aria-describedby={description ? `${titleId}-desc` : undefined}\n tabIndex={-1}\n className={panelClass}\n >\n {/* Header */}\n <div className=\"flex items-center justify-between px-5 py-4 border-b-2 border-slate-100 shrink-0\">\n <div className=\"min-w-0\">\n <h2 id={titleId} className=\"font-heading text-base font-bold text-slate-900 truncate\">\n {title}\n </h2>\n {description && (\n <p id={`${titleId}-desc`} className=\"text-xs text-slate-500 mt-0.5\">\n {description}\n </p>\n )}\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label={closeLabel}\n className=\"ml-3 shrink-0 flex h-8 w-8 items-center justify-center rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-700 transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30\"\n >\n <X size={16} aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Scrollable body */}\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {children}\n </div>\n </div>\n </div>,\n document.body,\n );\n}\n"],"mappings":"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"}
|
package/lib/index4.cjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
2
|
+
//#region src/ui-library/gallery/icon-button/IconButton.tsx
|
|
3
|
+
var BASE = "inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none";
|
|
4
|
+
var VARIANT_STYLES = {
|
|
5
|
+
default: "hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20",
|
|
6
|
+
edit: "hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20",
|
|
7
|
+
danger: "hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100"
|
|
8
|
+
};
|
|
9
|
+
function IconButton({ icon, variant = "default", className = "", ...props }) {
|
|
10
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11
|
+
type: "button",
|
|
12
|
+
className: [
|
|
13
|
+
BASE,
|
|
14
|
+
VARIANT_STYLES[variant],
|
|
15
|
+
className
|
|
16
|
+
].filter(Boolean).join(" "),
|
|
17
|
+
...props,
|
|
18
|
+
children: icon
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
exports.IconButton = IconButton;
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=index4.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index4.cjs","names":[],"sources":["../src/ui-library/gallery/icon-button/IconButton.tsx"],"sourcesContent":["/**\n * IconButton — Design System Component\n *\n * A square icon-only button with three visual variants.\n *\n * Accessibility:\n * - aria-label is REQUIRED — icon buttons have no visible text.\n * - Mark the icon inside aria-hidden=\"true\" to avoid double-announcement.\n * - Focus ring is focus-visible only (keyboard, not mouse click).\n * - disabled attribute prevents interaction and announces via AT.\n * - title provides a browser tooltip as a secondary affordance but is NOT\n * a substitute for aria-label.\n *\n * Usage:\n * <IconButton icon={<Pencil size={16} aria-hidden=\"true\" />} aria-label=\"Edit Engineering\" variant=\"edit\" />\n * <IconButton icon={<Trash2 size={16} aria-hidden=\"true\" />} aria-label=\"Delete record\" variant=\"danger\" />\n * <IconButton icon={<Settings size={16} aria-hidden=\"true\" />} aria-label=\"Open settings\" />\n */\n\nimport type { ReactNode } from 'react';\n\nexport type IconButtonVariant = 'default' | 'edit' | 'danger';\n\nexport interface IconButtonProps extends React.ComponentProps<'button'> {\n /** Icon element. Use aria-hidden=\"true\" on the icon to avoid double-announcement. */\n icon: ReactNode;\n /** Visual variant. Default: \"default\" (slate hover) */\n variant?: IconButtonVariant;\n}\n\n// ── Style maps ─────────────────────────────────────────────────────────────\n\nconst BASE =\n 'inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out' +\n ' active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1' +\n ' disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none';\n\nconst VARIANT_STYLES: Record<IconButtonVariant, string> = {\n default: 'hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20',\n edit: 'hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20',\n danger: 'hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100',\n};\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport function IconButton({\n icon,\n variant = 'default',\n className = '',\n ...props\n}: IconButtonProps) {\n return (\n <button\n type=\"button\"\n className={[BASE, VARIANT_STYLES[variant], className].filter(Boolean).join(' ')}\n {...props}\n >\n {icon}\n </button>\n );\n}\n"],"mappings":";;AAgCA,IAAM,OACJ;AAIF,IAAM,iBAAoD;CACxD,SAAS;CACT,MAAS;CACT,QAAS;AACX;AAIA,SAAgB,WAAW,EACzB,MACA,UAAU,WACV,YAAY,IACZ,GAAG,SACe;CAClB,OACE,iBAAA,GAAA,kBAAA,IAAA,CAAC,UAAD;EACE,MAAK;EACL,WAAW;GAAC;GAAM,eAAe;GAAU;EAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAC9E,GAAI;EAEH,UAAA;CACK,CAAA;AAEZ"}
|
package/lib/index4.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
//#region src/ui-library/gallery/icon-button/IconButton.tsx
|
|
3
|
+
var BASE = "inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1 disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none";
|
|
4
|
+
var VARIANT_STYLES = {
|
|
5
|
+
default: "hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20",
|
|
6
|
+
edit: "hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20",
|
|
7
|
+
danger: "hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100"
|
|
8
|
+
};
|
|
9
|
+
function IconButton({ icon, variant = "default", className = "", ...props }) {
|
|
10
|
+
return /* @__PURE__ */ jsx("button", {
|
|
11
|
+
type: "button",
|
|
12
|
+
className: [
|
|
13
|
+
BASE,
|
|
14
|
+
VARIANT_STYLES[variant],
|
|
15
|
+
className
|
|
16
|
+
].filter(Boolean).join(" "),
|
|
17
|
+
...props,
|
|
18
|
+
children: icon
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { IconButton };
|
|
23
|
+
|
|
24
|
+
//# sourceMappingURL=index4.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index4.mjs","names":[],"sources":["../src/ui-library/gallery/icon-button/IconButton.tsx"],"sourcesContent":["/**\n * IconButton — Design System Component\n *\n * A square icon-only button with three visual variants.\n *\n * Accessibility:\n * - aria-label is REQUIRED — icon buttons have no visible text.\n * - Mark the icon inside aria-hidden=\"true\" to avoid double-announcement.\n * - Focus ring is focus-visible only (keyboard, not mouse click).\n * - disabled attribute prevents interaction and announces via AT.\n * - title provides a browser tooltip as a secondary affordance but is NOT\n * a substitute for aria-label.\n *\n * Usage:\n * <IconButton icon={<Pencil size={16} aria-hidden=\"true\" />} aria-label=\"Edit Engineering\" variant=\"edit\" />\n * <IconButton icon={<Trash2 size={16} aria-hidden=\"true\" />} aria-label=\"Delete record\" variant=\"danger\" />\n * <IconButton icon={<Settings size={16} aria-hidden=\"true\" />} aria-label=\"Open settings\" />\n */\n\nimport type { ReactNode } from 'react';\n\nexport type IconButtonVariant = 'default' | 'edit' | 'danger';\n\nexport interface IconButtonProps extends React.ComponentProps<'button'> {\n /** Icon element. Use aria-hidden=\"true\" on the icon to avoid double-announcement. */\n icon: ReactNode;\n /** Visual variant. Default: \"default\" (slate hover) */\n variant?: IconButtonVariant;\n}\n\n// ── Style maps ─────────────────────────────────────────────────────────────\n\nconst BASE =\n 'inline-flex items-center justify-center rounded-lg p-2 text-slate-400 transition-all duration-150 ease-out' +\n ' active:scale-[.98] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1' +\n ' disabled:cursor-not-allowed disabled:opacity-40 disabled:active:scale-100 select-none';\n\nconst VARIANT_STYLES: Record<IconButtonVariant, string> = {\n default: 'hover:bg-slate-100 hover:text-slate-700 focus-visible:ring-brand-blue/20',\n edit: 'hover:bg-blue-50 hover:text-brand-blue focus-visible:ring-brand-blue/20',\n danger: 'hover:bg-rose-50 hover:text-rose-600 focus-visible:ring-rose-100',\n};\n\n// ── Component ──────────────────────────────────────────────────────────────\n\nexport function IconButton({\n icon,\n variant = 'default',\n className = '',\n ...props\n}: IconButtonProps) {\n return (\n <button\n type=\"button\"\n className={[BASE, VARIANT_STYLES[variant], className].filter(Boolean).join(' ')}\n {...props}\n >\n {icon}\n </button>\n );\n}\n"],"mappings":";;AAgCA,IAAM,OACJ;AAIF,IAAM,iBAAoD;CACxD,SAAS;CACT,MAAS;CACT,QAAS;AACX;AAIA,SAAgB,WAAW,EACzB,MACA,UAAU,WACV,YAAY,IACZ,GAAG,SACe;CAClB,OACE,oBAAC,UAAD;EACE,MAAK;EACL,WAAW;GAAC;GAAM,eAAe;GAAU;EAAS,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK,GAAG;EAC9E,GAAI;EAEH,UAAA;CACK,CAAA;AAEZ"}
|
package/lib/index40.cjs
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const require_runtime = require("./index2.cjs");
|
|
2
|
+
const require_utils = require("./index14.cjs");
|
|
3
|
+
let react_jsx_runtime = require("react/jsx-runtime");
|
|
4
|
+
let react = require("react");
|
|
5
|
+
let _radix_ui_react_popover = require("@radix-ui/react-popover");
|
|
6
|
+
_radix_ui_react_popover = require_runtime.__toESM(_radix_ui_react_popover, 1);
|
|
7
|
+
//#region src/ui-library/gallery/popover/Popover.tsx
|
|
8
|
+
/**
|
|
9
|
+
* Popover — Design System Component
|
|
10
|
+
*
|
|
11
|
+
* An anchored floating panel that opens on trigger interaction.
|
|
12
|
+
*
|
|
13
|
+
* Behavior is provided by @radix-ui/react-popover, which handles:
|
|
14
|
+
* - Open/close state (both controlled and uncontrolled)
|
|
15
|
+
* - Trigger click interaction
|
|
16
|
+
* - Escape key closes the popover + focus returns to trigger
|
|
17
|
+
* - Outside-click (pointer-down) closes the popover
|
|
18
|
+
* - Focus moves into the content on open
|
|
19
|
+
* - Portal rendering to document.body
|
|
20
|
+
* - Viewport collision detection and automatic side-flip
|
|
21
|
+
* - aria-expanded, aria-haspopup, aria-controls wiring
|
|
22
|
+
* - role="dialog" on the content panel
|
|
23
|
+
*
|
|
24
|
+
* DIWA continues to own:
|
|
25
|
+
* - Public API (open, onOpenChange, defaultOpen, placement, title, maxWidth, className, children)
|
|
26
|
+
* - Trigger visual treatment (button styling)
|
|
27
|
+
* - Content panel visual treatment (bg, border, radius, shadow, padding)
|
|
28
|
+
* - Placement vocabulary (DIWA names mapped internally to Radix side+align)
|
|
29
|
+
* - Typography and color tokens
|
|
30
|
+
* - Component anatomy and composition model
|
|
31
|
+
*
|
|
32
|
+
* Composition:
|
|
33
|
+
* <Popover>
|
|
34
|
+
* <PopoverTrigger>Open</PopoverTrigger>
|
|
35
|
+
* <PopoverContent title="Settings">…</PopoverContent>
|
|
36
|
+
* </Popover>
|
|
37
|
+
*
|
|
38
|
+
* Placement: 'bottom-start' | 'bottom-end' | 'bottom' |
|
|
39
|
+
* 'top-start' | 'top-end' | 'top'
|
|
40
|
+
*
|
|
41
|
+
* Difference from Dialog/Modal:
|
|
42
|
+
* - Popover is anchored to a trigger and positioned near it.
|
|
43
|
+
* - Dialog/Modal is centered in the viewport and blocks the page.
|
|
44
|
+
* - Tooltip shows text-only hints on hover/focus without interaction.
|
|
45
|
+
*/
|
|
46
|
+
function toRadixPlacement(placement) {
|
|
47
|
+
switch (placement) {
|
|
48
|
+
case "bottom-start": return {
|
|
49
|
+
side: "bottom",
|
|
50
|
+
align: "start"
|
|
51
|
+
};
|
|
52
|
+
case "bottom": return {
|
|
53
|
+
side: "bottom",
|
|
54
|
+
align: "center"
|
|
55
|
+
};
|
|
56
|
+
case "bottom-end": return {
|
|
57
|
+
side: "bottom",
|
|
58
|
+
align: "end"
|
|
59
|
+
};
|
|
60
|
+
case "top-start": return {
|
|
61
|
+
side: "top",
|
|
62
|
+
align: "start"
|
|
63
|
+
};
|
|
64
|
+
case "top": return {
|
|
65
|
+
side: "top",
|
|
66
|
+
align: "center"
|
|
67
|
+
};
|
|
68
|
+
case "top-end": return {
|
|
69
|
+
side: "top",
|
|
70
|
+
align: "end"
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function Popover({ open, onOpenChange, defaultOpen = false, placement = "bottom-start", children }) {
|
|
75
|
+
const { side, align } = toRadixPlacement(placement);
|
|
76
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Root, {
|
|
77
|
+
open,
|
|
78
|
+
onOpenChange,
|
|
79
|
+
defaultOpen,
|
|
80
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverPlacementContext.Provider, {
|
|
81
|
+
value: {
|
|
82
|
+
side,
|
|
83
|
+
align
|
|
84
|
+
},
|
|
85
|
+
children
|
|
86
|
+
})
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
var PopoverPlacementContext = (0, react.createContext)({
|
|
90
|
+
side: "bottom",
|
|
91
|
+
align: "start"
|
|
92
|
+
});
|
|
93
|
+
function usePlacement() {
|
|
94
|
+
return (0, react.useContext)(PopoverPlacementContext);
|
|
95
|
+
}
|
|
96
|
+
function PopoverTrigger({ children }) {
|
|
97
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Trigger, {
|
|
98
|
+
asChild: true,
|
|
99
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
100
|
+
type: "button",
|
|
101
|
+
className: "inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2 text-sm font-semibold text-slate-700 shadow-sm transition hover:border-slate-300 hover:bg-slate-50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-brand-blue/30",
|
|
102
|
+
children
|
|
103
|
+
})
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
function PopoverContent({ title, children, maxWidth = "max-w-xs", className = "" }) {
|
|
107
|
+
const { side, align } = usePlacement();
|
|
108
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_radix_ui_react_popover.Content, {
|
|
109
|
+
side,
|
|
110
|
+
align,
|
|
111
|
+
sideOffset: 8,
|
|
112
|
+
className: require_utils.cn("z-50 w-max rounded-xl border border-slate-200 bg-white shadow-lg", "outline-none", maxWidth, className),
|
|
113
|
+
children: [title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
114
|
+
className: "border-b border-slate-100 px-4 py-3",
|
|
115
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", {
|
|
116
|
+
className: "font-heading text-sm font-bold text-slate-900",
|
|
117
|
+
children: title
|
|
118
|
+
})
|
|
119
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
120
|
+
className: "px-4 py-3",
|
|
121
|
+
children
|
|
122
|
+
})]
|
|
123
|
+
}) });
|
|
124
|
+
}
|
|
125
|
+
//#endregion
|
|
126
|
+
exports.Popover = Popover;
|
|
127
|
+
exports.PopoverContent = PopoverContent;
|
|
128
|
+
exports.PopoverTrigger = PopoverTrigger;
|
|
129
|
+
|
|
130
|
+
//# sourceMappingURL=index40.cjs.map
|