@jbpark/ui-kit 3.4.0 → 3.6.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/dist/components/atoms/float-button/back-top.mjs +2 -7
- package/dist/components/atoms/float-button/back-top.mjs.map +1 -1
- package/dist/components/atoms/input/search.d.mts.map +1 -1
- package/dist/components/atoms/input/search.mjs +3 -6
- package/dist/components/atoms/input/search.mjs.map +1 -1
- package/dist/components/atoms/popover.d.mts.map +1 -1
- package/dist/components/atoms/popover.mjs +71 -19
- package/dist/components/atoms/popover.mjs.map +1 -1
- package/dist/components/atoms/skeleton/button.d.mts +1 -1
- package/dist/components/atoms/skeleton/button.d.mts.map +1 -1
- package/dist/components/atoms/skeleton/button.mjs +5 -2
- package/dist/components/atoms/skeleton/button.mjs.map +1 -1
- package/dist/components/atoms/skeleton/node.d.mts +1 -1
- package/dist/components/atoms/skeleton/node.d.mts.map +1 -1
- package/dist/components/atoms/skeleton/node.mjs +5 -2
- package/dist/components/atoms/skeleton/node.mjs.map +1 -1
- package/dist/components/molecules/marquees/item.d.mts.map +1 -1
- package/dist/components/molecules/marquees/item.mjs +6 -11
- package/dist/components/molecules/marquees/item.mjs.map +1 -1
- package/dist/components/molecules/upload.d.mts.map +1 -1
- package/dist/components/molecules/upload.mjs +19 -27
- package/dist/components/molecules/upload.mjs.map +1 -1
- package/dist/components/organisms/drawer.d.mts.map +1 -1
- package/dist/components/organisms/drawer.mjs +37 -13
- package/dist/components/organisms/drawer.mjs.map +1 -1
- package/dist/components/organisms/list/list.mjs +4 -9
- package/dist/components/organisms/list/list.mjs.map +1 -1
- package/dist/components/organisms/modal.d.mts +1 -0
- package/dist/components/organisms/modal.d.mts.map +1 -1
- package/dist/components/organisms/modal.mjs +13 -8
- package/dist/components/organisms/modal.mjs.map +1 -1
- package/dist/components/organisms/swiper/swiper.d.mts.map +1 -1
- package/dist/components/organisms/swiper/swiper.mjs +5 -1
- package/dist/components/organisms/swiper/swiper.mjs.map +1 -1
- package/dist/components/organisms/toast.d.mts.map +1 -1
- package/dist/components/organisms/toast.mjs +12 -8
- package/dist/components/organisms/toast.mjs.map +1 -1
- package/dist/style.css +138 -0
- package/package.json +2 -3
|
@@ -9,14 +9,30 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
9
9
|
|
|
10
10
|
//#region src/components/organisms/drawer.tsx
|
|
11
11
|
const { DrawerContent, Drawer: DrawerCore, DrawerDescription, DrawerFooter, DrawerHeader, DrawerTitle } = drawer_exports;
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const maskLessStates = /* @__PURE__ */ new WeakMap();
|
|
13
|
+
const getMaskLessState = (doc) => {
|
|
14
|
+
let state = maskLessStates.get(doc);
|
|
15
|
+
if (!state) {
|
|
16
|
+
state = {
|
|
17
|
+
count: 0,
|
|
18
|
+
originalPointerEvents: null
|
|
19
|
+
};
|
|
20
|
+
maskLessStates.set(doc, state);
|
|
21
|
+
}
|
|
22
|
+
return state;
|
|
23
|
+
};
|
|
14
24
|
const SIZES = {
|
|
15
25
|
small: "30%",
|
|
16
26
|
medium: "50%",
|
|
17
27
|
large: "75%",
|
|
18
28
|
full: "90%"
|
|
19
29
|
};
|
|
30
|
+
const ROUNDED_CLASSES = {
|
|
31
|
+
top: "rounded-b-[30px]!",
|
|
32
|
+
bottom: "rounded-t-[30px]!",
|
|
33
|
+
left: "rounded-r-[30px]!",
|
|
34
|
+
right: "rounded-l-[30px]!"
|
|
35
|
+
};
|
|
20
36
|
const getSizeStyles = (direction, size) => {
|
|
21
37
|
const value = SIZES[size] || size;
|
|
22
38
|
return direction === "top" || direction === "bottom" ? { height: value } : { width: value };
|
|
@@ -24,20 +40,28 @@ const getSizeStyles = (direction, size) => {
|
|
|
24
40
|
const Drawer = ({ open, title, extra, footer, closable = true, closeIcon, direction = "bottom", size = "medium", maskClosable = true, handlebar = true, draggable = false, rounded = false, mask = true, className, classNames, style, children, container, onClose, ...props }) => {
|
|
25
41
|
useEffect(() => {
|
|
26
42
|
if (!open || mask) return;
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const
|
|
30
|
-
|
|
43
|
+
const targetDocument = container?.ownerDocument ?? document;
|
|
44
|
+
const targetWindow = targetDocument.defaultView ?? window;
|
|
45
|
+
const body = targetDocument.body;
|
|
46
|
+
const state = getMaskLessState(targetDocument);
|
|
47
|
+
if (state.count === 0) state.originalPointerEvents = body.style.pointerEvents;
|
|
48
|
+
state.count += 1;
|
|
49
|
+
const raf = targetWindow.requestAnimationFrame(() => {
|
|
50
|
+
body.style.pointerEvents = "auto";
|
|
31
51
|
});
|
|
32
52
|
return () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
|
|
37
|
-
|
|
53
|
+
targetWindow.cancelAnimationFrame(raf);
|
|
54
|
+
state.count -= 1;
|
|
55
|
+
if (state.count === 0) {
|
|
56
|
+
body.style.pointerEvents = state.originalPointerEvents ?? "";
|
|
57
|
+
state.originalPointerEvents = null;
|
|
38
58
|
}
|
|
39
59
|
};
|
|
40
|
-
}, [
|
|
60
|
+
}, [
|
|
61
|
+
open,
|
|
62
|
+
mask,
|
|
63
|
+
container
|
|
64
|
+
]);
|
|
41
65
|
return /* @__PURE__ */ jsx(DrawerCore, {
|
|
42
66
|
open,
|
|
43
67
|
direction,
|
|
@@ -48,7 +72,7 @@ const Drawer = ({ open, title, extra, footer, closable = true, closeIcon, direct
|
|
|
48
72
|
},
|
|
49
73
|
...props,
|
|
50
74
|
children: /* @__PURE__ */ jsxs(DrawerContent, {
|
|
51
|
-
className: cn("border-none outline-none", rounded ?
|
|
75
|
+
className: cn("border-none outline-none", rounded ? ROUNDED_CLASSES[direction] : "rounded-none!", classNames?.content, className),
|
|
52
76
|
handlebar,
|
|
53
77
|
mask,
|
|
54
78
|
onPointerDownOutside: (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drawer.mjs","names":["drawer"],"sources":["../../../src/components/organisms/drawer.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\n\nimport { X } from 'lucide-react';\n\nimport { drawer } from '@repo/ui/core';\nimport { cn, renderConditional } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\nconst {\n DrawerContent,\n Drawer: DrawerCore,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} = drawer;\n\n// `
|
|
1
|
+
{"version":3,"file":"drawer.mjs","names":["drawer"],"sources":["../../../src/components/organisms/drawer.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\n\nimport { X } from 'lucide-react';\n\nimport { drawer } from '@repo/ui/core';\nimport { cn, renderConditional } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\nconst {\n DrawerContent,\n Drawer: DrawerCore,\n DrawerDescription,\n DrawerFooter,\n DrawerHeader,\n DrawerTitle,\n} = drawer;\n\n// `body.style.pointerEvents` is global state (per document), so several\n// mask-less overlays opening at once would otherwise race: each instance\n// would capture whatever the previous one had already written and the\n// last cleanup would restore a stale value. Reference counting makes only\n// the first instance capture the original value and only the last one\n// restore it. Keyed by document (not a single module-level counter) so\n// drawers portaled into different documents — e.g. live-editor's iframe\n// Renderer — don't share state with the host document.\ntype MaskLessState = { count: number; originalPointerEvents: string | null };\nconst maskLessStates = new WeakMap<Document, MaskLessState>();\n\nconst getMaskLessState = (doc: Document): MaskLessState => {\n let state = maskLessStates.get(doc);\n\n if (!state) {\n state = { count: 0, originalPointerEvents: null };\n maskLessStates.set(doc, state);\n }\n\n return state;\n};\n\nexport interface Props {\n open: boolean;\n children?: React.ReactNode;\n title?: React.ReactNode;\n extra?: React.ReactNode;\n footer?: React.ReactNode;\n closable?: boolean;\n closeIcon?: React.ReactNode;\n direction?: 'top' | 'bottom' | 'left' | 'right';\n size?: 'small' | 'medium' | 'large' | 'full' | string;\n maskClosable?: boolean;\n handlebar?: boolean;\n draggable?: boolean;\n rounded?: boolean;\n mask?: boolean;\n className?: string;\n style?: React.CSSProperties;\n classNames?: {\n mask?: string;\n content?: string;\n handlebar?: string;\n header?: string;\n title?: string;\n close?: string;\n extra?: string;\n body?: string;\n footer?: string;\n };\n container?: HTMLElement;\n onClose: () => void;\n}\n\nconst SIZES: Record<string, string> = {\n small: '30%',\n medium: '50%',\n large: '75%',\n full: '90%',\n};\n\n// Rounds the edge facing into the viewport — the opposite edge from\n// whichever side the drawer is anchored to.\nconst ROUNDED_CLASSES: Record<'top' | 'bottom' | 'left' | 'right', string> = {\n top: 'rounded-b-[30px]!',\n bottom: 'rounded-t-[30px]!',\n left: 'rounded-r-[30px]!',\n right: 'rounded-l-[30px]!',\n};\n\nconst getSizeStyles = (\n direction: 'top' | 'bottom' | 'left' | 'right',\n size: string,\n): React.CSSProperties => {\n const value = SIZES[size] || size;\n\n return direction === 'top' || direction === 'bottom'\n ? { height: value }\n : { width: value };\n};\n\nconst Drawer = ({\n open,\n title,\n extra,\n footer,\n closable = true,\n closeIcon,\n direction = 'bottom',\n size = 'medium',\n maskClosable = true,\n handlebar = true,\n draggable = false,\n rounded = false,\n mask = true,\n className,\n classNames,\n style,\n children,\n container,\n onClose,\n ...props\n}: Props) => {\n useEffect(() => {\n if (!open || mask) {\n return;\n }\n\n const targetDocument = container?.ownerDocument ?? document;\n const targetWindow = targetDocument.defaultView ?? window;\n const body = targetDocument.body;\n const state = getMaskLessState(targetDocument);\n\n if (state.count === 0) {\n state.originalPointerEvents = body.style.pointerEvents;\n }\n state.count += 1;\n\n const raf = targetWindow.requestAnimationFrame(() => {\n body.style.pointerEvents = 'auto';\n });\n\n return () => {\n targetWindow.cancelAnimationFrame(raf);\n state.count -= 1;\n\n if (state.count === 0) {\n body.style.pointerEvents = state.originalPointerEvents ?? '';\n state.originalPointerEvents = null;\n }\n };\n }, [open, mask, container]);\n\n return (\n <DrawerCore\n open={open}\n direction={direction}\n handleOnly={!draggable}\n container={container}\n onOpenChange={open => {\n if (!open) {\n onClose();\n }\n }}\n {...props}\n >\n <DrawerContent\n className={cn(\n 'border-none outline-none',\n rounded ? ROUNDED_CLASSES[direction] : 'rounded-none!',\n classNames?.content,\n className,\n //\n )}\n handlebar={handlebar}\n mask={mask}\n onPointerDownOutside={event => {\n if (!maskClosable) {\n event.preventDefault();\n }\n }}\n classNames={{\n mask: classNames?.mask || '',\n handlebar: classNames?.handlebar || '',\n }}\n style={{\n ...getSizeStyles(direction, size),\n ...style,\n }}\n >\n <DrawerHeader\n className={cn(\n classNames?.header,\n !title && 'p-0',\n //\n )}\n >\n <div className=\"flex items-start gap-2\">\n {closable && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label=\"닫기\"\n icon={closeIcon || <X />}\n onClick={onClose}\n className={cn(classNames?.close)}\n />\n )}\n <div className=\"flex flex-1 items-start justify-between gap-2\">\n <DrawerTitle className={cn(classNames?.title)}>\n {title}\n </DrawerTitle>\n {renderConditional(extra, v => (\n <div className={cn('shrink-0', classNames?.extra)}>{v}</div>\n ))}\n </div>\n </div>\n <DrawerDescription className=\"hidden\" />\n </DrawerHeader>\n <div\n className={cn(\n 'overflow-auto p-5',\n classNames?.body,\n //\n )}\n >\n {children}\n </div>\n {renderConditional(footer, v => (\n <DrawerFooter className={cn(classNames?.footer)}>{v}</DrawerFooter>\n ))}\n </DrawerContent>\n </DrawerCore>\n );\n};\n\nexport default Drawer;\n"],"mappings":";;;;;;;;;;AAWA,MAAM,EACJ,eACA,QAAQ,YACR,mBACA,cACA,cACA,gBACEA;AAWJ,MAAM,iCAAiB,IAAI,SAAkC;AAE7D,MAAM,oBAAoB,QAAiC;CACzD,IAAI,QAAQ,eAAe,IAAI,IAAI;AAEnC,KAAI,CAAC,OAAO;AACV,UAAQ;GAAE,OAAO;GAAG,uBAAuB;GAAM;AACjD,iBAAe,IAAI,KAAK,MAAM;;AAGhC,QAAO;;AAmCT,MAAM,QAAgC;CACpC,OAAO;CACP,QAAQ;CACR,OAAO;CACP,MAAM;CACP;AAID,MAAM,kBAAuE;CAC3E,KAAK;CACL,QAAQ;CACR,MAAM;CACN,OAAO;CACR;AAED,MAAM,iBACJ,WACA,SACwB;CACxB,MAAM,QAAQ,MAAM,SAAS;AAE7B,QAAO,cAAc,SAAS,cAAc,WACxC,EAAE,QAAQ,OAAO,GACjB,EAAE,OAAO,OAAO;;AAGtB,MAAM,UAAU,EACd,MACA,OACA,OACA,QACA,WAAW,MACX,WACA,YAAY,UACZ,OAAO,UACP,eAAe,MACf,YAAY,MACZ,YAAY,OACZ,UAAU,OACV,OAAO,MACP,WACA,YACA,OACA,UACA,WACA,SACA,GAAG,YACQ;AACX,iBAAgB;AACd,MAAI,CAAC,QAAQ,KACX;EAGF,MAAM,iBAAiB,WAAW,iBAAiB;EACnD,MAAM,eAAe,eAAe,eAAe;EACnD,MAAM,OAAO,eAAe;EAC5B,MAAM,QAAQ,iBAAiB,eAAe;AAE9C,MAAI,MAAM,UAAU,EAClB,OAAM,wBAAwB,KAAK,MAAM;AAE3C,QAAM,SAAS;EAEf,MAAM,MAAM,aAAa,4BAA4B;AACnD,QAAK,MAAM,gBAAgB;IAC3B;AAEF,eAAa;AACX,gBAAa,qBAAqB,IAAI;AACtC,SAAM,SAAS;AAEf,OAAI,MAAM,UAAU,GAAG;AACrB,SAAK,MAAM,gBAAgB,MAAM,yBAAyB;AAC1D,UAAM,wBAAwB;;;IAGjC;EAAC;EAAM;EAAM;EAAU,CAAC;AAE3B,QACE,oBAAC;EACO;EACK;EACX,YAAY,CAAC;EACF;EACX,eAAc,SAAQ;AACpB,OAAI,CAAC,KACH,UAAS;;EAGb,GAAI;YAEJ,qBAAC;GACC,WAAW,GACT,4BACA,UAAU,gBAAgB,aAAa,iBACvC,YAAY,SACZ,UAED;GACU;GACL;GACN,uBAAsB,UAAS;AAC7B,QAAI,CAAC,aACH,OAAM,gBAAgB;;GAG1B,YAAY;IACV,MAAM,YAAY,QAAQ;IAC1B,WAAW,YAAY,aAAa;IACrC;GACD,OAAO;IACL,GAAG,cAAc,WAAW,KAAK;IACjC,GAAG;IACJ;;IAED,qBAAC;KACC,WAAW,GACT,YAAY,QACZ,CAAC,SAAS,MAEX;gBAED,qBAAC;MAAI,WAAU;iBACZ,YACC,oBAAC;OACC,MAAK;OACL,OAAM;OACN,MAAK;OACL,cAAW;OACX,MAAM,aAAa,oBAAC,MAAI;OACxB,SAAS;OACT,WAAW,GAAG,YAAY,MAAM;QAChC,EAEJ,qBAAC;OAAI,WAAU;kBACb,oBAAC;QAAY,WAAW,GAAG,YAAY,MAAM;kBAC1C;SACW,EACb,kBAAkB,QAAO,MACxB,oBAAC;QAAI,WAAW,GAAG,YAAY,YAAY,MAAM;kBAAG;SAAQ,CAC5D;QACE;OACF,EACN,oBAAC,qBAAkB,WAAU,WAAW;MAC3B;IACf,oBAAC;KACC,WAAW,GACT,qBACA,YAAY,KAEb;KAEA;MACG;IACL,kBAAkB,SAAQ,MACzB,oBAAC;KAAa,WAAW,GAAG,YAAY,OAAO;eAAG;MAAiB,CACnE;;IACY;GACL"}
|
|
@@ -9,7 +9,7 @@ import { useIntersectionObserver } from "@jbpark/use-hooks";
|
|
|
9
9
|
|
|
10
10
|
//#region src/components/organisms/list/list.tsx
|
|
11
11
|
const List = ({ loading, loaderProps, loader, header, title, titleLevel = 2, empty, scroll, data, className, classNames, renderItem = () => null, itemKey, ...props }) => {
|
|
12
|
-
const [loaderRef,
|
|
12
|
+
const [loaderRef, { isIntersecting }] = useIntersectionObserver({
|
|
13
13
|
threshold: 0,
|
|
14
14
|
root: null,
|
|
15
15
|
rootMargin: "0px",
|
|
@@ -22,11 +22,11 @@ const List = ({ loading, loaderProps, loader, header, title, titleLevel = 2, emp
|
|
|
22
22
|
fetchingRef.current = false;
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
|
-
if (
|
|
25
|
+
if (isIntersecting && scroll.hasMore && !fetchingRef.current) {
|
|
26
26
|
fetchingRef.current = true;
|
|
27
27
|
scroll.next();
|
|
28
28
|
}
|
|
29
|
-
}, [
|
|
29
|
+
}, [isIntersecting, scroll]);
|
|
30
30
|
if (loading) return loader || /* @__PURE__ */ jsxs("div", {
|
|
31
31
|
className: cn(className),
|
|
32
32
|
...props,
|
|
@@ -49,11 +49,6 @@ const List = ({ loading, loaderProps, loader, header, title, titleLevel = 2, emp
|
|
|
49
49
|
})
|
|
50
50
|
]
|
|
51
51
|
});
|
|
52
|
-
if (!data?.length && empty) return /* @__PURE__ */ jsx("div", {
|
|
53
|
-
className: cn(className),
|
|
54
|
-
...props,
|
|
55
|
-
children: empty
|
|
56
|
-
});
|
|
57
52
|
return /* @__PURE__ */ jsxs("div", {
|
|
58
53
|
className: cn(className),
|
|
59
54
|
...props,
|
|
@@ -71,7 +66,7 @@ const List = ({ loading, loaderProps, loader, header, title, titleLevel = 2, emp
|
|
|
71
66
|
role: "list",
|
|
72
67
|
className: cn("space-y-2", classNames?.body),
|
|
73
68
|
children: [
|
|
74
|
-
data?.map((item, i) =>
|
|
69
|
+
!data?.length && empty ? empty : data?.map((item, i) => /* @__PURE__ */ jsx(React.Fragment, { children: renderItem(item, i) }, itemKey ? itemKey(item, i) : i)),
|
|
75
70
|
scroll?.loading && (scroll?.loader ?? /* @__PURE__ */ jsx(Skeleton.Node, {
|
|
76
71
|
count: 10,
|
|
77
72
|
gap: 10,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list.mjs","names":[],"sources":["../../../../src/components/organisms/list/list.tsx"],"sourcesContent":["'use client';\n\nimport React, { useEffect, useRef } from 'react';\n\nimport { useIntersectionObserver } from '@jbpark/use-hooks';\n\nimport { cn, renderConditional } from '@repo/ui/utils';\n\nimport Skeleton from '../../atoms/skeleton';\nimport { Title } from '../../atoms/typography';\n\ninterface Props<T> extends Omit<\n React.ComponentPropsWithoutRef<'div'>,\n 'title'\n> {\n loading?: boolean;\n loaderProps?: React.ComponentProps<typeof Skeleton>;\n loader?: React.ReactNode;\n title?: React.ReactNode;\n titleLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n header?: React.ReactNode;\n empty?: React.ReactNode;\n classNames?: {\n title?: string;\n header?: string;\n body?: string;\n };\n scroll?: {\n hasMore: boolean;\n loading: boolean;\n loader?: React.ReactNode;\n options?: IntersectionObserverInit;\n /**\n * Called when more items should be fetched. `List` tracks in-flight\n * requests via `loading` — the caller MUST flip `loading` back to\n * `false` when the fetch settles, including on failure/rejection.\n * If `loading` never becomes `false` again after a failed fetch,\n * `List` will stop calling `next()` for subsequent intersections.\n */\n next: () => void;\n };\n data?: T[];\n renderItem?: (item: T, index: number) => React.ReactNode;\n itemKey?: (item: T, index: number) => React.Key;\n}\n\nconst List = <T,>({\n loading,\n loaderProps,\n loader,\n header,\n title,\n titleLevel = 2,\n empty,\n scroll,\n data,\n className,\n classNames,\n renderItem = () => null,\n itemKey,\n ...props\n}: Props<T>) => {\n const [loaderRef,
|
|
1
|
+
{"version":3,"file":"list.mjs","names":[],"sources":["../../../../src/components/organisms/list/list.tsx"],"sourcesContent":["'use client';\n\nimport React, { useEffect, useRef } from 'react';\n\nimport { useIntersectionObserver } from '@jbpark/use-hooks';\n\nimport { cn, renderConditional } from '@repo/ui/utils';\n\nimport Skeleton from '../../atoms/skeleton';\nimport { Title } from '../../atoms/typography';\n\ninterface Props<T> extends Omit<\n React.ComponentPropsWithoutRef<'div'>,\n 'title'\n> {\n loading?: boolean;\n loaderProps?: React.ComponentProps<typeof Skeleton>;\n loader?: React.ReactNode;\n title?: React.ReactNode;\n titleLevel?: 1 | 2 | 3 | 4 | 5 | 6;\n header?: React.ReactNode;\n empty?: React.ReactNode;\n classNames?: {\n title?: string;\n header?: string;\n body?: string;\n };\n scroll?: {\n hasMore: boolean;\n loading: boolean;\n loader?: React.ReactNode;\n options?: IntersectionObserverInit;\n /**\n * Called when more items should be fetched. `List` tracks in-flight\n * requests via `loading` — the caller MUST flip `loading` back to\n * `false` when the fetch settles, including on failure/rejection.\n * If `loading` never becomes `false` again after a failed fetch,\n * `List` will stop calling `next()` for subsequent intersections.\n */\n next: () => void;\n };\n data?: T[];\n renderItem?: (item: T, index: number) => React.ReactNode;\n itemKey?: (item: T, index: number) => React.Key;\n}\n\nconst List = <T,>({\n loading,\n loaderProps,\n loader,\n header,\n title,\n titleLevel = 2,\n empty,\n scroll,\n data,\n className,\n classNames,\n renderItem = () => null,\n itemKey,\n ...props\n}: Props<T>) => {\n const [loaderRef, { isIntersecting }] = useIntersectionObserver({\n threshold: 0,\n root: null,\n rootMargin: '0px',\n ...scroll?.options,\n });\n\n // `scroll` is typically a new object identity every render, so this\n // effect re-runs often. Guard with a ref (updated synchronously, unlike\n // the `loading` prop which only reflects the caller's next state update)\n // to avoid firing `next()` again for the same in-flight request before\n // the caller's `loading` has had a chance to become true.\n const fetchingRef = useRef(false);\n\n useEffect(() => {\n if (!scroll) {\n return;\n }\n\n if (scroll.loading) {\n fetchingRef.current = false;\n return;\n }\n\n if (isIntersecting && scroll.hasMore && !fetchingRef.current) {\n fetchingRef.current = true;\n scroll.next();\n }\n }, [isIntersecting, scroll]);\n\n if (loading) {\n return (\n loader || (\n <div className={cn(className)} {...props}>\n <div className={cn(classNames?.title)}>{title && <Skeleton />}</div>\n <div className={cn(classNames?.header)}>\n {header && <Skeleton size=\"small\" />}\n </div>\n <div className={cn('space-y-2', classNames?.body)}>\n <Skeleton.Node count={10} gap={10} {...loaderProps} />\n </div>\n </div>\n )\n );\n }\n\n return (\n <div\n className={cn(\n className,\n //\n )}\n {...props}\n >\n {renderConditional(title, v => (\n <Title\n level={titleLevel}\n className={cn(\n 'px-1.5 py-4',\n 'text-black-90 text-lg leading-5.75 font-bold',\n //\n )}\n >\n {v}\n </Title>\n ))}\n <div className={cn(classNames?.header)}>{header}</div>\n <div role=\"list\" className={cn('space-y-2', classNames?.body)}>\n {!data?.length && empty\n ? empty\n : data?.map((item, i) => (\n <React.Fragment key={itemKey ? itemKey(item, i) : i}>\n {renderItem(item, i)}\n </React.Fragment>\n ))}\n {scroll?.loading &&\n (scroll?.loader ?? (\n <Skeleton.Node count={10} gap={10} {...loaderProps} />\n ))}\n {scroll && <div ref={loaderRef} />}\n </div>\n </div>\n );\n};\n\nexport default List;\n"],"mappings":";;;;;;;;;;AA8CA,MAAM,QAAY,EAChB,SACA,aACA,QACA,QACA,OACA,aAAa,GACb,OACA,QACA,MACA,WACA,YACA,mBAAmB,MACnB,SACA,GAAG,YACW;CACd,MAAM,CAAC,WAAW,EAAE,oBAAoB,wBAAwB;EAC9D,WAAW;EACX,MAAM;EACN,YAAY;EACZ,GAAG,QAAQ;EACZ,CAAC;CAOF,MAAM,cAAc,OAAO,MAAM;AAEjC,iBAAgB;AACd,MAAI,CAAC,OACH;AAGF,MAAI,OAAO,SAAS;AAClB,eAAY,UAAU;AACtB;;AAGF,MAAI,kBAAkB,OAAO,WAAW,CAAC,YAAY,SAAS;AAC5D,eAAY,UAAU;AACtB,UAAO,MAAM;;IAEd,CAAC,gBAAgB,OAAO,CAAC;AAE5B,KAAI,QACF,QACE,UACE,qBAAC;EAAI,WAAW,GAAG,UAAU;EAAE,GAAI;;GACjC,oBAAC;IAAI,WAAW,GAAG,YAAY,MAAM;cAAG,SAAS,oBAAC,aAAW;KAAO;GACpE,oBAAC;IAAI,WAAW,GAAG,YAAY,OAAO;cACnC,UAAU,oBAAC,YAAS,MAAK,UAAU;KAChC;GACN,oBAAC;IAAI,WAAW,GAAG,aAAa,YAAY,KAAK;cAC/C,oBAAC,SAAS;KAAK,OAAO;KAAI,KAAK;KAAI,GAAI;MAAe;KAClD;;GACF;AAKZ,QACE,qBAAC;EACC,WAAW,GACT,UAED;EACD,GAAI;;GAEH,kBAAkB,QAAO,MACxB,oBAAC;IACC,OAAO;IACP,WAAW,GACT,eACA,+CAED;cAEA;KACK,CACR;GACF,oBAAC;IAAI,WAAW,GAAG,YAAY,OAAO;cAAG;KAAa;GACtD,qBAAC;IAAI,MAAK;IAAO,WAAW,GAAG,aAAa,YAAY,KAAK;;KAC1D,CAAC,MAAM,UAAU,QACd,QACA,MAAM,KAAK,MAAM,MACf,oBAAC,MAAM,sBACJ,WAAW,MAAM,EAAE,IADD,UAAU,QAAQ,MAAM,EAAE,GAAG,EAEjC,CACjB;KACL,QAAQ,YACN,QAAQ,UACP,oBAAC,SAAS;MAAK,OAAO;MAAI,KAAK;MAAI,GAAI;OAAe;KAEzD,UAAU,oBAAC,SAAI,KAAK,YAAa;;KAC9B;;GACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal.d.mts","names":[],"sources":["../../../src/components/organisms/modal.tsx"],"mappings":";;;;UA6BU,KAAA;EACR,IAAA;EACA,YAAA;EACA,QAAA;IAGM,SAAA,GAAY,KAAA,CAAM,SAAA;IAClB,QAAA;EAAA;EAEN,SAAA,GAAY,KAAA,CAAM,SAAA;EAClB,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,OAAA,GAAU,KAAA,CAAM,SAAA;EAChB,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,UAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA;IACA,MAAA;EAAA;EAEF,KAAA,GAAQ,KAAA,CAAM,aAAA;EACd,MAAA;EACA,UAAA;EACA,SAAA,GAAY,WAAA;EACZ,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,IAAA;EACA,QAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,IAAA;EACA,EAAA;EACA,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,SAAA,GAAY,WAAA;AAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"modal.d.mts","names":[],"sources":["../../../src/components/organisms/modal.tsx"],"mappings":";;;;UA6BU,KAAA;EACR,IAAA;EACA,YAAA;EACA,QAAA;IAGM,SAAA,GAAY,KAAA,CAAM,SAAA;IAClB,QAAA;EAAA;EAEN,SAAA,GAAY,KAAA,CAAM,SAAA;EAClB,KAAA,GAAQ,KAAA,CAAM,SAAA;EACd,OAAA,GAAU,KAAA,CAAM,SAAA;EAChB,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,UAAA;IACE,IAAA;IACA,MAAA;IACA,IAAA;IACA,MAAA;EAAA;EAEF,KAAA,GAAQ,KAAA,CAAM,aAAA;EACd,MAAA;EACA,UAAA;EACA,SAAA,GAAY,WAAA;EACZ,QAAA,GAAW,KAAA,CAAM,SAAA;EACjB,IAAA;EACA,QAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,IAAA;EACA,EAAA;EACA,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,SAAA,GAAY,WAAA;AAAA;AAAA,cAmDR,KAAA;EAAA;;;;;;;;;;;;;;;;;;KAkBH,KAAA,GAAK,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cAgQa,WAAA;iBACG,WAAA;eAEF,WAAA;iBACE,WAAA;iBAEA,WAAA;AAAA"}
|
|
@@ -14,6 +14,7 @@ import { v4 } from "uuid";
|
|
|
14
14
|
const { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } = dialog_exports;
|
|
15
15
|
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
16
16
|
const containerStates = /* @__PURE__ */ new Map();
|
|
17
|
+
const modalRootElements = /* @__PURE__ */ new WeakMap();
|
|
17
18
|
const getContainerState = (container) => {
|
|
18
19
|
let state = containerStates.get(container);
|
|
19
20
|
if (!state) {
|
|
@@ -28,19 +29,17 @@ const getContainerState = (container) => {
|
|
|
28
29
|
const createModalRoot = (container) => {
|
|
29
30
|
if (!isBrowser) return null;
|
|
30
31
|
const targetContainer = container || document.body;
|
|
31
|
-
let rootEl =
|
|
32
|
+
let rootEl = modalRootElements.get(targetContainer);
|
|
32
33
|
if (!rootEl) {
|
|
33
34
|
rootEl = document.createElement("div");
|
|
34
|
-
rootEl.setAttribute("id", "modal-root");
|
|
35
|
-
rootEl.setAttribute("role", "dialog");
|
|
36
|
-
rootEl.setAttribute("aria-modal", "true");
|
|
37
35
|
rootEl.style.zIndex = "10000";
|
|
38
36
|
rootEl.style.position = "absolute";
|
|
39
37
|
targetContainer.appendChild(rootEl);
|
|
38
|
+
modalRootElements.set(targetContainer, rootEl);
|
|
40
39
|
}
|
|
41
40
|
return rootEl;
|
|
42
41
|
};
|
|
43
|
-
const Modal = ({ open = false, maskClosable = false, closable = false, closeIcon, className, classNames, style, title, footer, container, children, okText = "OK", cancelText = "Cancel", onOk, onCancel, ...props }) => {
|
|
42
|
+
const Modal = ({ open = false, maskClosable = false, closable = false, closeIcon, className, classNames, style, title, content, footer, container, children, okText = "OK", cancelText = "Cancel", onOk, onCancel, ...props }) => {
|
|
44
43
|
if (typeof window === "undefined") return null;
|
|
45
44
|
return /* @__PURE__ */ jsx(Dialog, {
|
|
46
45
|
open,
|
|
@@ -68,7 +67,7 @@ const Modal = ({ open = false, maskClosable = false, closable = false, closeIcon
|
|
|
68
67
|
}),
|
|
69
68
|
/* @__PURE__ */ jsx("div", {
|
|
70
69
|
className: cn("break-all", classNames?.body),
|
|
71
|
-
children
|
|
70
|
+
children: children ?? content
|
|
72
71
|
}),
|
|
73
72
|
footer !== null && /* @__PURE__ */ jsx(DialogFooter, {
|
|
74
73
|
className: cn("flex-row justify-end gap-x-2", classNames?.footer),
|
|
@@ -133,7 +132,7 @@ const StaticModal = ({ type, title, content, okText = "OK", cancelText = "Cancel
|
|
|
133
132
|
children: [icon || type && STATIC_ICONS[type], title]
|
|
134
133
|
}),
|
|
135
134
|
container,
|
|
136
|
-
onCancel: () => closeModal(
|
|
135
|
+
onCancel: () => closeModal(onCancel),
|
|
137
136
|
...props,
|
|
138
137
|
children: content
|
|
139
138
|
}), createModalRoot(container));
|
|
@@ -178,7 +177,11 @@ Modal.destroy = (id) => {
|
|
|
178
177
|
state.stack = [];
|
|
179
178
|
state.update?.();
|
|
180
179
|
});
|
|
181
|
-
modalRoots.forEach((root) =>
|
|
180
|
+
modalRoots.forEach((root, container) => {
|
|
181
|
+
root.unmount();
|
|
182
|
+
modalRootElements.get(container)?.remove();
|
|
183
|
+
modalRootElements.delete(container);
|
|
184
|
+
});
|
|
182
185
|
modalRoots.clear();
|
|
183
186
|
containerStates.clear();
|
|
184
187
|
return;
|
|
@@ -191,6 +194,8 @@ Modal.destroy = (id) => {
|
|
|
191
194
|
modalRoots.get(container)?.unmount();
|
|
192
195
|
modalRoots.delete(container);
|
|
193
196
|
containerStates.delete(container);
|
|
197
|
+
modalRootElements.get(container)?.remove();
|
|
198
|
+
modalRootElements.delete(container);
|
|
194
199
|
}
|
|
195
200
|
});
|
|
196
201
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"modal.mjs","names":["dialog","uuid"],"sources":["../../../src/components/organisms/modal.tsx"],"sourcesContent":["'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Root, createRoot } from 'react-dom/client';\n\nimport {\n Check,\n CircleQuestionMark,\n Info,\n OctagonAlert,\n OctagonX,\n} from 'lucide-react';\nimport { v4 as uuid } from 'uuid';\n\nimport { dialog } from '@repo/ui/core';\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\nconst {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} = dialog;\n\ninterface Props {\n open?: boolean;\n maskClosable?: boolean;\n closable?:\n | boolean\n | {\n closeIcon?: React.ReactNode;\n disabled?: boolean;\n };\n closeIcon?: React.ReactNode;\n title?: React.ReactNode;\n content?: React.ReactNode;\n footer?: React.ReactNode;\n className?: string;\n classNames?: {\n mask?: string;\n header?: string;\n body?: string;\n footer?: string;\n };\n style?: React.CSSProperties;\n okText?: string;\n cancelText?: string;\n container?: HTMLElement;\n children?: React.ReactNode;\n onOk?: () => void;\n onCancel?: () => void;\n}\n\ninterface StaticProps extends Props {\n type?: 'info' | 'success' | 'error' | 'warning' | 'confirm';\n id?: string;\n icon?: React.ReactNode;\n container?: HTMLElement;\n}\n\nconst isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\ntype ContainerState = {\n stack: StaticProps[];\n update: (() => void) | null;\n};\n\nconst containerStates = new Map<HTMLElement, ContainerState>();\n\nconst getContainerState = (container: HTMLElement): ContainerState => {\n let state = containerStates.get(container);\n\n if (!state) {\n state = { stack: [], update: null };\n containerStates.set(container, state);\n }\n\n return state;\n};\n\nconst createModalRoot = (container?: HTMLElement) => {\n if (!isBrowser) {\n return null;\n }\n\n const targetContainer = container || document.body;\n let rootEl = targetContainer.querySelector(\n '#modal-root',\n ) as HTMLElement | null;\n\n if (!rootEl) {\n rootEl = document.createElement('div');\n rootEl.setAttribute('id', 'modal-root');\n rootEl.setAttribute('role', 'dialog');\n rootEl.setAttribute('aria-modal', 'true');\n rootEl.style.zIndex = '10000';\n rootEl.style.position = 'absolute';\n targetContainer.appendChild(rootEl);\n }\n\n return rootEl;\n};\n\nconst Modal = ({\n open = false,\n maskClosable = false,\n closable = false,\n closeIcon,\n className,\n classNames,\n style,\n title,\n footer,\n container,\n children,\n okText = 'OK',\n cancelText = 'Cancel',\n onOk,\n onCancel,\n ...props\n}: Props) => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(open: boolean) => {\n if (!open) {\n onCancel?.();\n }\n }}\n {...props}\n >\n <DialogContent\n className={cn(\n 'rounded-lg p-4 max-sm:max-w-[80%]',\n 'border-none outline-none',\n className,\n )}\n classNames={{\n ...classNames,\n mask: cn('bg-black/60', classNames?.mask),\n }}\n style={style}\n closable={closable}\n closeIcon={closeIcon}\n container={container}\n onPointerDownOutside={event => {\n if (!maskClosable) {\n event.preventDefault();\n }\n }}\n >\n {/**\n * @todo [Dialog & AlertDialog] fix: can't get id correctly in shadow dom\n * @see https://github.com/radix-ui/primitives/pull/3384\n **/}\n <DialogHeader\n className={cn(\n classNames?.header,\n !title && 'hidden',\n //\n )}\n >\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription className=\"hidden\" />\n </DialogHeader>\n <div className={cn('break-all', classNames?.body)}>{children}</div>\n {footer !== null && (\n <DialogFooter\n className={cn('flex-row justify-end gap-x-2', classNames?.footer)}\n >\n {footer || (\n <>\n <Button onClick={onOk}>{okText}</Button>\n <Button variant=\"outlined\" onClick={onCancel}>\n {cancelText}\n </Button>\n </>\n )}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n );\n};\n\nconst STATIC_ICONS = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n confirm: <CircleQuestionMark />,\n};\n\nconst StaticModal = ({\n type,\n title,\n content,\n okText = 'OK',\n cancelText = 'Cancel',\n id,\n container,\n icon,\n onOk,\n onCancel,\n ...props\n}: StaticProps) => {\n const [open, setOpen] = useState(true);\n\n const closeModal = (callback?: () => void) => {\n callback?.();\n setOpen(false);\n setTimeout(() => {\n Modal.destroy(id);\n }, 200);\n };\n\n const footer =\n type === 'confirm' ? (\n <div className=\"grid w-full grid-cols-5 gap-x-2\">\n <Button\n variant=\"outlined\"\n className=\"col-span-2\"\n onClick={() => closeModal(onCancel)}\n >\n {cancelText}\n </Button>\n <Button className=\"col-span-3\" onClick={() => closeModal(onOk)}>\n {okText}\n </Button>\n </div>\n ) : (\n <Button\n className={cn(\n 'grow',\n //\n )}\n onClick={() => closeModal(onOk)}\n >\n {okText}\n </Button>\n );\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <Modal\n open={open}\n footer={footer}\n className={cn(\n 'z-10000',\n 'box-content w-100',\n 'rounded-3xl p-4',\n 'gap-y-0',\n )}\n classNames={{\n mask: 'z-10000 bg-black/[.6]',\n body: cn(\n 'text-black-70 whitespace-pre-wrap text-center',\n content && 'mt-3',\n ),\n footer: 'mt-9',\n }}\n title={\n <p\n className={cn(\n 'text-center text-lg leading-normal whitespace-pre-wrap',\n 'flex items-center justify-center gap-x-2',\n //\n )}\n >\n {icon || (type && STATIC_ICONS[type])}\n {title}\n </p>\n }\n container={container}\n onCancel={() => closeModal(onOk)}\n {...props}\n >\n {content}\n </Modal>,\n createModalRoot(container)!,\n );\n};\n\nconst ModalStackRenderer = ({ container }: { container: HTMLElement }) => {\n const [, forceUpdate] = useState({});\n\n useEffect(() => {\n const state = getContainerState(container);\n state.update = () => forceUpdate({});\n return () => {\n state.update = null;\n };\n }, [container]);\n\n const { stack } = getContainerState(container);\n\n return (\n <>\n {stack.map(({ id, ...props }) => (\n <StaticModal key={id} id={id} {...props} />\n ))}\n </>\n );\n};\n\nconst modalRoots = new Map<HTMLElement, Root>();\n\nconst renderModal = (props: StaticProps) => {\n if (!isBrowser) {\n return;\n }\n\n const targetContainer = props.container || document.body;\n const rootElement = createModalRoot(targetContainer)!;\n\n if (!modalRoots.has(targetContainer)) {\n const root = createRoot(rootElement);\n modalRoots.set(targetContainer, root);\n root.render(<ModalStackRenderer container={targetContainer} />);\n }\n\n const state = getContainerState(targetContainer);\n const id = props.id || uuid();\n state.stack.push({ ...props, id });\n state.update?.();\n\n return id;\n};\n\nModal.destroy = (id?: string) => {\n if (!id) {\n containerStates.forEach(state => {\n state.stack = [];\n state.update?.();\n });\n modalRoots.forEach(root => root.unmount());\n modalRoots.clear();\n containerStates.clear();\n return;\n }\n\n containerStates.forEach((state, container) => {\n if (!state.stack.some(modal => modal.id === id)) {\n return;\n }\n\n state.stack = state.stack.filter(modal => modal.id !== id);\n state.update?.();\n\n if (!state.stack.length) {\n modalRoots.get(container)?.unmount();\n modalRoots.delete(container);\n containerStates.delete(container);\n }\n });\n};\n\nModal.destroyAll = () => {\n Modal.destroy();\n};\n\nModal.info = (props: StaticProps) => renderModal({ type: 'info', ...props });\nModal.success = (props: StaticProps) =>\n renderModal({ type: 'success', ...props });\nModal.error = (props: StaticProps) => renderModal({ type: 'error', ...props });\nModal.warning = (props: StaticProps) =>\n renderModal({ type: 'warning', ...props });\nModal.confirm = (props: StaticProps) =>\n renderModal({ type: 'confirm', ...props });\n\nexport default Modal;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"modal.mjs","names":["dialog","uuid"],"sources":["../../../src/components/organisms/modal.tsx"],"sourcesContent":["'use client';\n\nimport React, { useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Root, createRoot } from 'react-dom/client';\n\nimport {\n Check,\n CircleQuestionMark,\n Info,\n OctagonAlert,\n OctagonX,\n} from 'lucide-react';\nimport { v4 as uuid } from 'uuid';\n\nimport { dialog } from '@repo/ui/core';\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\nconst {\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} = dialog;\n\ninterface Props {\n open?: boolean;\n maskClosable?: boolean;\n closable?:\n | boolean\n | {\n closeIcon?: React.ReactNode;\n disabled?: boolean;\n };\n closeIcon?: React.ReactNode;\n title?: React.ReactNode;\n content?: React.ReactNode;\n footer?: React.ReactNode;\n className?: string;\n classNames?: {\n mask?: string;\n header?: string;\n body?: string;\n footer?: string;\n };\n style?: React.CSSProperties;\n okText?: string;\n cancelText?: string;\n container?: HTMLElement;\n children?: React.ReactNode;\n onOk?: () => void;\n onCancel?: () => void;\n}\n\ninterface StaticProps extends Props {\n type?: 'info' | 'success' | 'error' | 'warning' | 'confirm';\n id?: string;\n icon?: React.ReactNode;\n container?: HTMLElement;\n}\n\nconst isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\ntype ContainerState = {\n stack: StaticProps[];\n update: (() => void) | null;\n};\n\nconst containerStates = new Map<HTMLElement, ContainerState>();\nconst modalRootElements = new WeakMap<HTMLElement, HTMLElement>();\n\nconst getContainerState = (container: HTMLElement): ContainerState => {\n let state = containerStates.get(container);\n\n if (!state) {\n state = { stack: [], update: null };\n containerStates.set(container, state);\n }\n\n return state;\n};\n\n// No `role`/`aria-modal` here — this element is only a mount point for the\n// imperative stack, not the dialog itself. Radix already renders its own\n// `role=\"dialog\"`/`aria-modal=\"true\"` on the actual `DialogContent` via\n// portal once a modal is open. Setting it here as well would (a) leave\n// `aria-modal=\"true\"` on the page permanently, even with zero modals open,\n// hiding the rest of the document from screen readers, and (b) duplicate\n// Radix's own dialog role once one is open.\nconst createModalRoot = (container?: HTMLElement) => {\n if (!isBrowser) {\n return null;\n }\n\n const targetContainer = container || document.body;\n let rootEl = modalRootElements.get(targetContainer);\n\n if (!rootEl) {\n rootEl = document.createElement('div');\n rootEl.style.zIndex = '10000';\n rootEl.style.position = 'absolute';\n targetContainer.appendChild(rootEl);\n modalRootElements.set(targetContainer, rootEl);\n }\n\n return rootEl;\n};\n\nconst Modal = ({\n open = false,\n maskClosable = false,\n closable = false,\n closeIcon,\n className,\n classNames,\n style,\n title,\n content,\n footer,\n container,\n children,\n okText = 'OK',\n cancelText = 'Cancel',\n onOk,\n onCancel,\n ...props\n}: Props) => {\n if (typeof window === 'undefined') {\n return null;\n }\n\n return (\n <Dialog\n open={open}\n onOpenChange={(open: boolean) => {\n if (!open) {\n onCancel?.();\n }\n }}\n {...props}\n >\n <DialogContent\n className={cn(\n 'rounded-lg p-4 max-sm:max-w-[80%]',\n 'border-none outline-none',\n className,\n )}\n classNames={{\n ...classNames,\n mask: cn('bg-black/60', classNames?.mask),\n }}\n style={style}\n closable={closable}\n closeIcon={closeIcon}\n container={container}\n onPointerDownOutside={event => {\n if (!maskClosable) {\n event.preventDefault();\n }\n }}\n >\n {/**\n * @todo [Dialog & AlertDialog] fix: can't get id correctly in shadow dom\n * @see https://github.com/radix-ui/primitives/pull/3384\n **/}\n <DialogHeader\n className={cn(\n classNames?.header,\n !title && 'hidden',\n //\n )}\n >\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription className=\"hidden\" />\n </DialogHeader>\n <div className={cn('break-all', classNames?.body)}>\n {children ?? content}\n </div>\n {footer !== null && (\n <DialogFooter\n className={cn('flex-row justify-end gap-x-2', classNames?.footer)}\n >\n {footer || (\n <>\n <Button onClick={onOk}>{okText}</Button>\n <Button variant=\"outlined\" onClick={onCancel}>\n {cancelText}\n </Button>\n </>\n )}\n </DialogFooter>\n )}\n </DialogContent>\n </Dialog>\n );\n};\n\nconst STATIC_ICONS = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n confirm: <CircleQuestionMark />,\n};\n\nconst StaticModal = ({\n type,\n title,\n content,\n okText = 'OK',\n cancelText = 'Cancel',\n id,\n container,\n icon,\n onOk,\n onCancel,\n ...props\n}: StaticProps) => {\n const [open, setOpen] = useState(true);\n\n const closeModal = (callback?: () => void) => {\n callback?.();\n setOpen(false);\n setTimeout(() => {\n Modal.destroy(id);\n }, 200);\n };\n\n const footer =\n type === 'confirm' ? (\n <div className=\"grid w-full grid-cols-5 gap-x-2\">\n <Button\n variant=\"outlined\"\n className=\"col-span-2\"\n onClick={() => closeModal(onCancel)}\n >\n {cancelText}\n </Button>\n <Button className=\"col-span-3\" onClick={() => closeModal(onOk)}>\n {okText}\n </Button>\n </div>\n ) : (\n <Button\n className={cn(\n 'grow',\n //\n )}\n onClick={() => closeModal(onOk)}\n >\n {okText}\n </Button>\n );\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <Modal\n open={open}\n footer={footer}\n className={cn(\n 'z-10000',\n 'box-content w-100',\n 'rounded-3xl p-4',\n 'gap-y-0',\n )}\n classNames={{\n mask: 'z-10000 bg-black/[.6]',\n body: cn(\n 'text-black-70 whitespace-pre-wrap text-center',\n content && 'mt-3',\n ),\n footer: 'mt-9',\n }}\n title={\n <p\n className={cn(\n 'text-center text-lg leading-normal whitespace-pre-wrap',\n 'flex items-center justify-center gap-x-2',\n //\n )}\n >\n {icon || (type && STATIC_ICONS[type])}\n {title}\n </p>\n }\n container={container}\n onCancel={() => closeModal(onCancel)}\n {...props}\n >\n {content}\n </Modal>,\n createModalRoot(container)!,\n );\n};\n\nconst ModalStackRenderer = ({ container }: { container: HTMLElement }) => {\n const [, forceUpdate] = useState({});\n\n useEffect(() => {\n const state = getContainerState(container);\n state.update = () => forceUpdate({});\n return () => {\n state.update = null;\n };\n }, [container]);\n\n const { stack } = getContainerState(container);\n\n return (\n <>\n {stack.map(({ id, ...props }) => (\n <StaticModal key={id} id={id} {...props} />\n ))}\n </>\n );\n};\n\nconst modalRoots = new Map<HTMLElement, Root>();\n\nconst renderModal = (props: StaticProps) => {\n if (!isBrowser) {\n return;\n }\n\n const targetContainer = props.container || document.body;\n const rootElement = createModalRoot(targetContainer)!;\n\n if (!modalRoots.has(targetContainer)) {\n const root = createRoot(rootElement);\n modalRoots.set(targetContainer, root);\n root.render(<ModalStackRenderer container={targetContainer} />);\n }\n\n const state = getContainerState(targetContainer);\n const id = props.id || uuid();\n state.stack.push({ ...props, id });\n state.update?.();\n\n return id;\n};\n\nModal.destroy = (id?: string) => {\n if (!id) {\n containerStates.forEach(state => {\n state.stack = [];\n state.update?.();\n });\n modalRoots.forEach((root, container) => {\n root.unmount();\n modalRootElements.get(container)?.remove();\n modalRootElements.delete(container);\n });\n modalRoots.clear();\n containerStates.clear();\n return;\n }\n\n containerStates.forEach((state, container) => {\n if (!state.stack.some(modal => modal.id === id)) {\n return;\n }\n\n state.stack = state.stack.filter(modal => modal.id !== id);\n state.update?.();\n\n if (!state.stack.length) {\n modalRoots.get(container)?.unmount();\n modalRoots.delete(container);\n containerStates.delete(container);\n modalRootElements.get(container)?.remove();\n modalRootElements.delete(container);\n }\n });\n};\n\nModal.destroyAll = () => {\n Modal.destroy();\n};\n\nModal.info = (props: StaticProps) => renderModal({ type: 'info', ...props });\nModal.success = (props: StaticProps) =>\n renderModal({ type: 'success', ...props });\nModal.error = (props: StaticProps) => renderModal({ type: 'error', ...props });\nModal.warning = (props: StaticProps) =>\n renderModal({ type: 'warning', ...props });\nModal.confirm = (props: StaticProps) =>\n renderModal({ type: 'confirm', ...props });\n\nexport default Modal;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swiper.d.mts","names":[],"sources":["../../../../src/components/organisms/swiper/swiper.tsx"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"swiper.d.mts","names":[],"sources":["../../../../src/components/organisms/swiper/swiper.tsx"],"mappings":";;;;;;UA+BU,KAAA,YAAiB,WAAA;EACzB,OAAA;EACA,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,gBAAA;EACA,OAAA,GAAU,aAAA;EAiBwB;;;;;;;;;;;;;;EAFlC,OAAA,GAAU,YAAA;EACV,IAAA,EAAM,CAAA;EACN,UAAA,CAAW,IAAA,EAAM,CAAA,EAAG,GAAA,WAAc,KAAA,CAAM,SAAA;AAAA;AAAA,cAGpC,QAAA;EAAc,OAAA;EAAA,MAAA;EAAA,OAAA;EAAA,OAAA;EAAA,IAAA;EAAA,KAAA;EAAA,UAAA;EAAA,gBAAA;EAAA,GAAA;AAAA,GAUjB,KAAA,CAAM,CAAA,MAAE,kBAAA,CAAA,GAAA,CAAA,OAAA"}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { cn } from "../../../utils.mjs";
|
|
4
4
|
import Spin from "../../atoms/spin.mjs";
|
|
5
|
+
import { cloneElement, isValidElement } from "react";
|
|
5
6
|
import { jsx } from "react/jsx-runtime";
|
|
6
7
|
import { Swiper } from "swiper/react";
|
|
7
8
|
import "swiper/css";
|
|
@@ -41,7 +42,10 @@ const Swiper$1 = ({ loading, loader, options = {}, modules = [], data = [], styl
|
|
|
41
42
|
...style
|
|
42
43
|
},
|
|
43
44
|
...props,
|
|
44
|
-
children: data.map((item, i) =>
|
|
45
|
+
children: data.map((item, i) => {
|
|
46
|
+
const node = renderItem(item, i);
|
|
47
|
+
return isValidElement(node) && node.key == null ? cloneElement(node, { key: i }) : node;
|
|
48
|
+
})
|
|
45
49
|
});
|
|
46
50
|
};
|
|
47
51
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"swiper.mjs","names":["Swiper","SwiperCore"],"sources":["../../../../src/components/organisms/swiper/swiper.tsx"],"sourcesContent":["'use client';\n\nimport { Swiper as SwiperCore, SwiperProps } from 'swiper/react';\nimport { SwiperModule, SwiperOptions } from 'swiper/types';\n\nimport { cn } from '@repo/ui/utils';\n\nimport 'swiper/css';\n\nimport { Spin } from '../../atoms';\n\n/**\n * Baseline Swiper options. `navigation` / `autoplay` are inert unless the\n * matching modules are passed through the `modules` prop — see the README next\n * to this file.\n */\nexport const initialOptions: SwiperOptions = {\n loop: false,\n spaceBetween: 8,\n slidesPerView: 'auto',\n navigation: false,\n autoplay: {\n delay: 2500,\n disableOnInteraction: false,\n },\n};\n\nconst initialStyle = { width: '100%', maxWidth: '100vw', overflow: 'hidden' };\n\ninterface Props<T> extends SwiperProps {\n loading?: boolean;\n loader?: React.ReactNode;\n loadingClassName?: string;\n options?: SwiperOptions;\n /**\n * Swiper feature modules to activate. Nothing is bundled by default — import\n * the modules you need from `swiper/modules` together with their stylesheets\n * (`swiper/css/<module>`) and pass them here.\n *\n * @example\n * import { Autoplay, Navigation } from 'swiper/modules';\n * import 'swiper/css/autoplay';\n * import 'swiper/css/navigation';\n *\n * <Swiper modules={[Autoplay, Navigation]} ... />\n *\n * @default []\n */\n modules?: SwiperModule[];\n data: T[];\n renderItem(item: T, key: number): React.ReactNode;\n}\n\nconst Swiper = <T,>({\n loading,\n loader,\n options = {},\n modules = [],\n data = [],\n style,\n renderItem,\n loadingClassName,\n ...props\n}: Props<T>) => {\n if (loading) {\n return (\n <div className={cn('h-32', loadingClassName)}>\n {loader || <Spin spinning />}\n </div>\n );\n }\n\n return (\n <SwiperCore\n modules={modules}\n {...initialOptions}\n {...options}\n style={{ ...initialStyle, ...style }}\n {...props}\n >\n {data.map((item: T, i) => renderItem(item, i))}\n </SwiperCore>\n );\n};\n\nexport default Swiper;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"swiper.mjs","names":["Swiper","SwiperCore"],"sources":["../../../../src/components/organisms/swiper/swiper.tsx"],"sourcesContent":["'use client';\n\nimport { cloneElement, isValidElement } from 'react';\n\nimport { Swiper as SwiperCore, SwiperProps } from 'swiper/react';\nimport { SwiperModule, SwiperOptions } from 'swiper/types';\n\nimport { cn } from '@repo/ui/utils';\n\nimport 'swiper/css';\n\nimport { Spin } from '../../atoms';\n\n/**\n * Baseline Swiper options. `navigation` / `autoplay` are inert unless the\n * matching modules are passed through the `modules` prop — see the README next\n * to this file.\n */\nexport const initialOptions: SwiperOptions = {\n loop: false,\n spaceBetween: 8,\n slidesPerView: 'auto',\n navigation: false,\n autoplay: {\n delay: 2500,\n disableOnInteraction: false,\n },\n};\n\nconst initialStyle = { width: '100%', maxWidth: '100vw', overflow: 'hidden' };\n\ninterface Props<T> extends SwiperProps {\n loading?: boolean;\n loader?: React.ReactNode;\n loadingClassName?: string;\n options?: SwiperOptions;\n /**\n * Swiper feature modules to activate. Nothing is bundled by default — import\n * the modules you need from `swiper/modules` together with their stylesheets\n * (`swiper/css/<module>`) and pass them here.\n *\n * @example\n * import { Autoplay, Navigation } from 'swiper/modules';\n * import 'swiper/css/autoplay';\n * import 'swiper/css/navigation';\n *\n * <Swiper modules={[Autoplay, Navigation]} ... />\n *\n * @default []\n */\n modules?: SwiperModule[];\n data: T[];\n renderItem(item: T, key: number): React.ReactNode;\n}\n\nconst Swiper = <T,>({\n loading,\n loader,\n options = {},\n modules = [],\n data = [],\n style,\n renderItem,\n loadingClassName,\n ...props\n}: Props<T>) => {\n if (loading) {\n return (\n <div className={cn('h-32', loadingClassName)}>\n {loader || <Spin spinning />}\n </div>\n );\n }\n\n return (\n <SwiperCore\n modules={modules}\n {...initialOptions}\n {...options}\n style={{ ...initialStyle, ...style }}\n {...props}\n >\n {data.map((item: T, i) => {\n const node = renderItem(item, i);\n // Fall back to an index key only when the consumer's renderItem\n // didn't already set one — cloning (rather than wrapping in a\n // Fragment) keeps the returned element a direct child of\n // SwiperCore, which swiper/react's slide detection relies on.\n return isValidElement(node) && node.key == null\n ? cloneElement(node, { key: i })\n : node;\n })}\n </SwiperCore>\n );\n};\n\nexport default Swiper;\n"],"mappings":";;;;;;;;;;;;;;;AAkBA,MAAa,iBAAgC;CAC3C,MAAM;CACN,cAAc;CACd,eAAe;CACf,YAAY;CACZ,UAAU;EACR,OAAO;EACP,sBAAsB;EACvB;CACF;AAED,MAAM,eAAe;CAAE,OAAO;CAAQ,UAAU;CAAS,UAAU;CAAU;AA0B7E,MAAMA,YAAc,EAClB,SACA,QACA,UAAU,EAAE,EACZ,UAAU,EAAE,EACZ,OAAO,EAAE,EACT,OACA,YACA,kBACA,GAAG,YACW;AACd,KAAI,QACF,QACE,oBAAC;EAAI,WAAW,GAAG,QAAQ,iBAAiB;YACzC,UAAU,oBAAC,QAAK,iBAAW;GACxB;AAIV,QACE,oBAACC;EACU;EACT,GAAI;EACJ,GAAI;EACJ,OAAO;GAAE,GAAG;GAAc,GAAG;GAAO;EACpC,GAAI;YAEH,KAAK,KAAK,MAAS,MAAM;GACxB,MAAM,OAAO,WAAW,MAAM,EAAE;AAKhC,UAAO,eAAe,KAAK,IAAI,KAAK,OAAO,OACvC,aAAa,MAAM,EAAE,KAAK,GAAG,CAAC,GAC9B;IACJ;GACS"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.d.mts","names":[],"sources":["../../../src/components/organisms/toast.tsx"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"toast.d.mts","names":[],"sources":["../../../src/components/organisms/toast.tsx"],"mappings":";;;KAcK,SAAA;AAAA,UAEY,KAAA;EACf,IAAA,GAAO,SAAA;EACP,KAAA,EAAO,KAAA,CAAM,SAAA;EACb,WAAA,GAAc,KAAA,CAAM,SAAA;EACpB,IAAA,GAAO,KAAA,CAAM,SAAA;EACb,QAAA;EACA,MAAA,GAAS,KAAA,CAAM,SAAA;EACf,SAAA;EACA,OAAA;AAAA;AAAA,UAGQ,WAAA,SAAoB,KAAA;EAC5B,EAAA;EACA,QAAA;EACA,SAAA,GAAY,WAAA;AAAA;AAAA,cA4DR,KAAA;EAAA;;;;;;;;;KASH,KAAA,GAAK,kBAAA,CAAA,GAAA,CAAA,OAAA;;;cAiKa,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;iBAEtB,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;eAE3B,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;iBAEvB,KAAA,CAAM,SAAA,EAAS,KAAA,GAAU,cAAA;AAAA;AAAA,KAR5C,cAAA,GAAiB,IAAA,CAAK,WAAA"}
|
|
@@ -5,6 +5,7 @@ import Button from "../../Button.mjs";
|
|
|
5
5
|
import { Check, Info, OctagonAlert, OctagonX, X } from "lucide-react";
|
|
6
6
|
import { useEffect, useState } from "react";
|
|
7
7
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
8
|
+
import { useTimeout } from "@jbpark/use-hooks";
|
|
8
9
|
import { createPortal } from "react-dom";
|
|
9
10
|
import { createRoot } from "react-dom/client";
|
|
10
11
|
import { v4 } from "uuid";
|
|
@@ -12,6 +13,7 @@ import { v4 } from "uuid";
|
|
|
12
13
|
//#region src/components/organisms/toast.tsx
|
|
13
14
|
const isBrowser = typeof window !== "undefined" && typeof document !== "undefined";
|
|
14
15
|
const containerStates = /* @__PURE__ */ new Map();
|
|
16
|
+
const toastRootElements = /* @__PURE__ */ new WeakMap();
|
|
15
17
|
const getContainerState = (container) => {
|
|
16
18
|
let state = containerStates.get(container);
|
|
17
19
|
if (!state) {
|
|
@@ -26,10 +28,9 @@ const getContainerState = (container) => {
|
|
|
26
28
|
const createToastRoot = (container) => {
|
|
27
29
|
if (!isBrowser) return null;
|
|
28
30
|
const targetContainer = container || document.body;
|
|
29
|
-
let rootEl =
|
|
31
|
+
let rootEl = toastRootElements.get(targetContainer);
|
|
30
32
|
if (!rootEl) {
|
|
31
33
|
rootEl = document.createElement("div");
|
|
32
|
-
rootEl.setAttribute("id", "toast-root");
|
|
33
34
|
rootEl.setAttribute("role", "region");
|
|
34
35
|
rootEl.setAttribute("aria-label", "Notifications");
|
|
35
36
|
rootEl.style.zIndex = "10000";
|
|
@@ -42,6 +43,7 @@ const createToastRoot = (container) => {
|
|
|
42
43
|
rootEl.style.padding = "16px";
|
|
43
44
|
rootEl.style.pointerEvents = "none";
|
|
44
45
|
targetContainer.appendChild(rootEl);
|
|
46
|
+
toastRootElements.set(targetContainer, rootEl);
|
|
45
47
|
}
|
|
46
48
|
return rootEl;
|
|
47
49
|
};
|
|
@@ -98,11 +100,7 @@ const StaticToast = ({ id, duration = DEFAULT_DURATION, container, onClose, ...p
|
|
|
98
100
|
Toast.destroy(id);
|
|
99
101
|
}, 200);
|
|
100
102
|
};
|
|
101
|
-
|
|
102
|
-
if (!duration) return;
|
|
103
|
-
const timer = setTimeout(close, duration);
|
|
104
|
-
return () => clearTimeout(timer);
|
|
105
|
-
}, [duration]);
|
|
103
|
+
useTimeout(close, duration || null);
|
|
106
104
|
if (!isBrowser) return null;
|
|
107
105
|
return createPortal(/* @__PURE__ */ jsx("div", {
|
|
108
106
|
className: cn("transition-all duration-200", visible ? "opacity-100" : "translate-x-2 opacity-0"),
|
|
@@ -152,7 +150,11 @@ Toast.destroy = (id) => {
|
|
|
152
150
|
state.stack = [];
|
|
153
151
|
state.update?.();
|
|
154
152
|
});
|
|
155
|
-
toastRoots.forEach((root) =>
|
|
153
|
+
toastRoots.forEach((root, container) => {
|
|
154
|
+
root.unmount();
|
|
155
|
+
toastRootElements.get(container)?.remove();
|
|
156
|
+
toastRootElements.delete(container);
|
|
157
|
+
});
|
|
156
158
|
toastRoots.clear();
|
|
157
159
|
containerStates.clear();
|
|
158
160
|
return;
|
|
@@ -165,6 +167,8 @@ Toast.destroy = (id) => {
|
|
|
165
167
|
toastRoots.get(container)?.unmount();
|
|
166
168
|
toastRoots.delete(container);
|
|
167
169
|
containerStates.delete(container);
|
|
170
|
+
toastRootElements.get(container)?.remove();
|
|
171
|
+
toastRootElements.delete(container);
|
|
168
172
|
}
|
|
169
173
|
});
|
|
170
174
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toast.mjs","names":["uuid"],"sources":["../../../src/components/organisms/toast.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Root, createRoot } from 'react-dom/client';\n\nimport { Check, Info, OctagonAlert, OctagonX, X } from 'lucide-react';\nimport { v4 as uuid } from 'uuid';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\ntype ToastType = 'info' | 'success' | 'error' | 'warning';\n\nexport interface Props {\n type?: ToastType;\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n closable?: boolean;\n action?: React.ReactNode;\n className?: string;\n onClose?: () => void;\n}\n\ninterface StaticProps extends Props {\n id?: string;\n duration?: number;\n container?: HTMLElement;\n}\n\nconst isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\ntype ContainerState = {\n stack: StaticProps[];\n update: (() => void) | null;\n};\n\nconst containerStates = new Map<HTMLElement, ContainerState>();\n\nconst getContainerState = (container: HTMLElement): ContainerState => {\n let state = containerStates.get(container);\n\n if (!state) {\n state = { stack: [], update: null };\n containerStates.set(container, state);\n }\n\n return state;\n};\n\nconst createToastRoot = (container?: HTMLElement) => {\n if (!isBrowser) {\n return null;\n }\n\n const targetContainer = container || document.body;\n let rootEl = targetContainer.querySelector(\n '#toast-root',\n ) as HTMLElement | null;\n\n if (!rootEl) {\n rootEl = document.createElement('div');\n rootEl.setAttribute('id', 'toast-root');\n rootEl.setAttribute('role', 'region');\n rootEl.setAttribute('aria-label', 'Notifications');\n rootEl.style.zIndex = '10000';\n rootEl.style.position = 'fixed';\n rootEl.style.right = '0';\n rootEl.style.bottom = '0';\n rootEl.style.display = 'flex';\n rootEl.style.flexDirection = 'column-reverse';\n rootEl.style.gap = '8px';\n rootEl.style.padding = '16px';\n rootEl.style.pointerEvents = 'none';\n targetContainer.appendChild(rootEl);\n }\n\n return rootEl;\n};\n\nconst TYPE_ICONS: Record<ToastType, React.ReactNode> = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n};\n\nconst Toast = ({\n type,\n title,\n description,\n icon,\n closable = true,\n action,\n className,\n onClose,\n}: Props) => {\n return (\n <div\n role=\"status\"\n className={cn(\n 'pointer-events-auto flex w-80 items-start gap-3 rounded-lg',\n 'bg-background text-foreground border p-4 shadow-lg',\n className,\n //\n )}\n >\n {(icon || type) && (\n <div className=\"mt-0.5 shrink-0 [&_svg]:size-5\">\n {icon || (type && TYPE_ICONS[type])}\n </div>\n )}\n <div className=\"flex-1 space-y-1\">\n <p className=\"text-sm font-medium\">{title}</p>\n {description && (\n <p className=\"text-muted-foreground text-sm\">{description}</p>\n )}\n {action && <div className=\"pt-1\">{action}</div>}\n </div>\n {closable && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label=\"Close\"\n icon={<X />}\n onClick={onClose}\n />\n )}\n </div>\n );\n};\n\nconst DEFAULT_DURATION = 4000;\n\nconst StaticToast = ({\n id,\n duration = DEFAULT_DURATION,\n container,\n onClose,\n ...props\n}: StaticProps) => {\n const [visible, setVisible] = useState(true);\n\n const close = () => {\n onClose?.();\n setVisible(false);\n setTimeout(() => {\n Toast.destroy(id);\n }, 200);\n };\n\n useEffect(() => {\n if (!duration) {\n return;\n }\n\n const timer = setTimeout(close, duration);\n return () => clearTimeout(timer);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [duration]);\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <div\n className={cn(\n 'transition-all duration-200',\n visible ? 'opacity-100' : 'translate-x-2 opacity-0',\n )}\n >\n <Toast {...props} onClose={close} />\n </div>,\n createToastRoot(container)!,\n );\n};\n\nconst ToastStackRenderer = ({ container }: { container: HTMLElement }) => {\n const [, forceUpdate] = useState({});\n\n useEffect(() => {\n const state = getContainerState(container);\n state.update = () => forceUpdate({});\n return () => {\n state.update = null;\n };\n }, [container]);\n\n const { stack } = getContainerState(container);\n\n return (\n <>\n {stack.map(({ id, ...props }) => (\n <StaticToast key={id} id={id} {...props} />\n ))}\n </>\n );\n};\n\nconst toastRoots = new Map<HTMLElement, Root>();\n\nconst renderToast = (props: StaticProps) => {\n if (!isBrowser) {\n return;\n }\n\n const targetContainer = props.container || document.body;\n const rootElement = createToastRoot(targetContainer)!;\n\n if (!toastRoots.has(targetContainer)) {\n const root = createRoot(rootElement);\n toastRoots.set(targetContainer, root);\n root.render(<ToastStackRenderer container={targetContainer} />);\n }\n\n const state = getContainerState(targetContainer);\n const id = props.id || uuid();\n state.stack.push({ ...props, id });\n state.update?.();\n\n return id;\n};\n\nToast.destroy = (id?: string) => {\n if (!id) {\n containerStates.forEach(state => {\n state.stack = [];\n state.update?.();\n });\n toastRoots.forEach(root => root.unmount());\n toastRoots.clear();\n containerStates.clear();\n return;\n }\n\n containerStates.forEach((state, container) => {\n if (!state.stack.some(toast => toast.id === id)) {\n return;\n }\n\n state.stack = state.stack.filter(toast => toast.id !== id);\n state.update?.();\n\n if (!state.stack.length) {\n toastRoots.get(container)?.unmount();\n toastRoots.delete(container);\n containerStates.delete(container);\n }\n });\n};\n\nToast.destroyAll = () => {\n Toast.destroy();\n};\n\ntype TriggerOptions = Omit<StaticProps, 'type' | 'title'>;\n\nToast.info = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'info', title, ...props });\nToast.success = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'success', title, ...props });\nToast.error = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'error', title, ...props });\nToast.warning = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'warning', title, ...props });\n\nexport default Toast;\nexport type { StaticProps as ToastOptions };\n"],"mappings":";;;;;;;;;;;;AAgCA,MAAM,YACJ,OAAO,WAAW,eAAe,OAAO,aAAa;AAOvD,MAAM,kCAAkB,IAAI,KAAkC;AAE9D,MAAM,qBAAqB,cAA2C;CACpE,IAAI,QAAQ,gBAAgB,IAAI,UAAU;AAE1C,KAAI,CAAC,OAAO;AACV,UAAQ;GAAE,OAAO,EAAE;GAAE,QAAQ;GAAM;AACnC,kBAAgB,IAAI,WAAW,MAAM;;AAGvC,QAAO;;AAGT,MAAM,mBAAmB,cAA4B;AACnD,KAAI,CAAC,UACH,QAAO;CAGT,MAAM,kBAAkB,aAAa,SAAS;CAC9C,IAAI,SAAS,gBAAgB,cAC3B,cACD;AAED,KAAI,CAAC,QAAQ;AACX,WAAS,SAAS,cAAc,MAAM;AACtC,SAAO,aAAa,MAAM,aAAa;AACvC,SAAO,aAAa,QAAQ,SAAS;AACrC,SAAO,aAAa,cAAc,gBAAgB;AAClD,SAAO,MAAM,SAAS;AACtB,SAAO,MAAM,WAAW;AACxB,SAAO,MAAM,QAAQ;AACrB,SAAO,MAAM,SAAS;AACtB,SAAO,MAAM,UAAU;AACvB,SAAO,MAAM,gBAAgB;AAC7B,SAAO,MAAM,MAAM;AACnB,SAAO,MAAM,UAAU;AACvB,SAAO,MAAM,gBAAgB;AAC7B,kBAAgB,YAAY,OAAO;;AAGrC,QAAO;;AAGT,MAAM,aAAiD;CACrD,MAAM,oBAAC,QAAK,WAAU,kBAAkB;CACxC,SAAS,oBAAC,SAAM,WAAU,mBAAmB;CAC7C,OAAO,oBAAC,YAAS,WAAU,iBAAiB;CAC5C,SAAS,oBAAC,gBAAa,WAAU,oBAAoB;CACtD;AAED,MAAM,SAAS,EACb,MACA,OACA,aACA,MACA,WAAW,MACX,QACA,WACA,cACW;AACX,QACE,qBAAC;EACC,MAAK;EACL,WAAW,GACT,8DACA,sDACA,UAED;;IAEC,QAAQ,SACR,oBAAC;IAAI,WAAU;cACZ,QAAS,QAAQ,WAAW;KACzB;GAER,qBAAC;IAAI,WAAU;;KACb,oBAAC;MAAE,WAAU;gBAAuB;OAAU;KAC7C,eACC,oBAAC;MAAE,WAAU;gBAAiC;OAAgB;KAE/D,UAAU,oBAAC;MAAI,WAAU;gBAAQ;OAAa;;KAC3C;GACL,YACC,oBAAC;IACC,MAAK;IACL,OAAM;IACN,MAAK;IACL,cAAW;IACX,MAAM,oBAAC,MAAI;IACX,SAAS;KACT;;GAEA;;AAIV,MAAM,mBAAmB;AAEzB,MAAM,eAAe,EACnB,IACA,WAAW,kBACX,WACA,SACA,GAAG,YACc;CACjB,MAAM,CAAC,SAAS,cAAc,SAAS,KAAK;CAE5C,MAAM,cAAc;AAClB,aAAW;AACX,aAAW,MAAM;AACjB,mBAAiB;AACf,SAAM,QAAQ,GAAG;KAChB,IAAI;;AAGT,iBAAgB;AACd,MAAI,CAAC,SACH;EAGF,MAAM,QAAQ,WAAW,OAAO,SAAS;AACzC,eAAa,aAAa,MAAM;IAE/B,CAAC,SAAS,CAAC;AAEd,KAAI,CAAC,UACH,QAAO;AAGT,QAAO,aACL,oBAAC;EACC,WAAW,GACT,+BACA,UAAU,gBAAgB,0BAC3B;YAED,oBAAC;GAAM,GAAI;GAAO,SAAS;IAAS;GAChC,EACN,gBAAgB,UAAU,CAC3B;;AAGH,MAAM,sBAAsB,EAAE,gBAA4C;CACxE,MAAM,GAAG,eAAe,SAAS,EAAE,CAAC;AAEpC,iBAAgB;EACd,MAAM,QAAQ,kBAAkB,UAAU;AAC1C,QAAM,eAAe,YAAY,EAAE,CAAC;AACpC,eAAa;AACX,SAAM,SAAS;;IAEhB,CAAC,UAAU,CAAC;CAEf,MAAM,EAAE,UAAU,kBAAkB,UAAU;AAE9C,QACE,4CACG,MAAM,KAAK,EAAE,IAAI,GAAG,YACnB,oBAAC;EAAyB;EAAI,GAAI;IAAhB,GAAyB,CAC3C,GACD;;AAIP,MAAM,6BAAa,IAAI,KAAwB;AAE/C,MAAM,eAAe,UAAuB;AAC1C,KAAI,CAAC,UACH;CAGF,MAAM,kBAAkB,MAAM,aAAa,SAAS;CACpD,MAAM,cAAc,gBAAgB,gBAAgB;AAEpD,KAAI,CAAC,WAAW,IAAI,gBAAgB,EAAE;EACpC,MAAM,OAAO,WAAW,YAAY;AACpC,aAAW,IAAI,iBAAiB,KAAK;AACrC,OAAK,OAAO,oBAAC,sBAAmB,WAAW,kBAAmB,CAAC;;CAGjE,MAAM,QAAQ,kBAAkB,gBAAgB;CAChD,MAAM,KAAK,MAAM,MAAMA,IAAM;AAC7B,OAAM,MAAM,KAAK;EAAE,GAAG;EAAO;EAAI,CAAC;AAClC,OAAM,UAAU;AAEhB,QAAO;;AAGT,MAAM,WAAW,OAAgB;AAC/B,KAAI,CAAC,IAAI;AACP,kBAAgB,SAAQ,UAAS;AAC/B,SAAM,QAAQ,EAAE;AAChB,SAAM,UAAU;IAChB;AACF,aAAW,SAAQ,SAAQ,KAAK,SAAS,CAAC;AAC1C,aAAW,OAAO;AAClB,kBAAgB,OAAO;AACvB;;AAGF,iBAAgB,SAAS,OAAO,cAAc;AAC5C,MAAI,CAAC,MAAM,MAAM,MAAK,UAAS,MAAM,OAAO,GAAG,CAC7C;AAGF,QAAM,QAAQ,MAAM,MAAM,QAAO,UAAS,MAAM,OAAO,GAAG;AAC1D,QAAM,UAAU;AAEhB,MAAI,CAAC,MAAM,MAAM,QAAQ;AACvB,cAAW,IAAI,UAAU,EAAE,SAAS;AACpC,cAAW,OAAO,UAAU;AAC5B,mBAAgB,OAAO,UAAU;;GAEnC;;AAGJ,MAAM,mBAAmB;AACvB,OAAM,SAAS;;AAKjB,MAAM,QAAQ,OAAwB,UACpC,YAAY;CAAE,MAAM;CAAQ;CAAO,GAAG;CAAO,CAAC;AAChD,MAAM,WAAW,OAAwB,UACvC,YAAY;CAAE,MAAM;CAAW;CAAO,GAAG;CAAO,CAAC;AACnD,MAAM,SAAS,OAAwB,UACrC,YAAY;CAAE,MAAM;CAAS;CAAO,GAAG;CAAO,CAAC;AACjD,MAAM,WAAW,OAAwB,UACvC,YAAY;CAAE,MAAM;CAAW;CAAO,GAAG;CAAO,CAAC"}
|
|
1
|
+
{"version":3,"file":"toast.mjs","names":["uuid"],"sources":["../../../src/components/organisms/toast.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useState } from 'react';\nimport { createPortal } from 'react-dom';\nimport { Root, createRoot } from 'react-dom/client';\n\nimport { useTimeout } from '@jbpark/use-hooks';\nimport { Check, Info, OctagonAlert, OctagonX, X } from 'lucide-react';\nimport { v4 as uuid } from 'uuid';\n\nimport { cn } from '@repo/ui/utils';\n\nimport Button from '../atoms/button';\n\ntype ToastType = 'info' | 'success' | 'error' | 'warning';\n\nexport interface Props {\n type?: ToastType;\n title: React.ReactNode;\n description?: React.ReactNode;\n icon?: React.ReactNode;\n closable?: boolean;\n action?: React.ReactNode;\n className?: string;\n onClose?: () => void;\n}\n\ninterface StaticProps extends Props {\n id?: string;\n duration?: number;\n container?: HTMLElement;\n}\n\nconst isBrowser =\n typeof window !== 'undefined' && typeof document !== 'undefined';\n\ntype ContainerState = {\n stack: StaticProps[];\n update: (() => void) | null;\n};\n\nconst containerStates = new Map<HTMLElement, ContainerState>();\nconst toastRootElements = new WeakMap<HTMLElement, HTMLElement>();\n\nconst getContainerState = (container: HTMLElement): ContainerState => {\n let state = containerStates.get(container);\n\n if (!state) {\n state = { stack: [], update: null };\n containerStates.set(container, state);\n }\n\n return state;\n};\n\nconst createToastRoot = (container?: HTMLElement) => {\n if (!isBrowser) {\n return null;\n }\n\n const targetContainer = container || document.body;\n let rootEl = toastRootElements.get(targetContainer);\n\n if (!rootEl) {\n rootEl = document.createElement('div');\n rootEl.setAttribute('role', 'region');\n rootEl.setAttribute('aria-label', 'Notifications');\n rootEl.style.zIndex = '10000';\n rootEl.style.position = 'fixed';\n rootEl.style.right = '0';\n rootEl.style.bottom = '0';\n rootEl.style.display = 'flex';\n rootEl.style.flexDirection = 'column-reverse';\n rootEl.style.gap = '8px';\n rootEl.style.padding = '16px';\n rootEl.style.pointerEvents = 'none';\n targetContainer.appendChild(rootEl);\n toastRootElements.set(targetContainer, rootEl);\n }\n\n return rootEl;\n};\n\nconst TYPE_ICONS: Record<ToastType, React.ReactNode> = {\n info: <Info className=\"text-blue-400\" />,\n success: <Check className=\"text-green-400\" />,\n error: <OctagonX className=\"text-red-400\" />,\n warning: <OctagonAlert className=\"text-yellow-400\" />,\n};\n\nconst Toast = ({\n type,\n title,\n description,\n icon,\n closable = true,\n action,\n className,\n onClose,\n}: Props) => {\n return (\n <div\n role=\"status\"\n className={cn(\n 'pointer-events-auto flex w-80 items-start gap-3 rounded-lg',\n 'bg-background text-foreground border p-4 shadow-lg',\n className,\n //\n )}\n >\n {(icon || type) && (\n <div className=\"mt-0.5 shrink-0 [&_svg]:size-5\">\n {icon || (type && TYPE_ICONS[type])}\n </div>\n )}\n <div className=\"flex-1 space-y-1\">\n <p className=\"text-sm font-medium\">{title}</p>\n {description && (\n <p className=\"text-muted-foreground text-sm\">{description}</p>\n )}\n {action && <div className=\"pt-1\">{action}</div>}\n </div>\n {closable && (\n <Button\n type=\"text\"\n shape=\"circle\"\n size=\"small\"\n aria-label=\"Close\"\n icon={<X />}\n onClick={onClose}\n />\n )}\n </div>\n );\n};\n\nconst DEFAULT_DURATION = 4000;\n\nconst StaticToast = ({\n id,\n duration = DEFAULT_DURATION,\n container,\n onClose,\n ...props\n}: StaticProps) => {\n const [visible, setVisible] = useState(true);\n\n const close = () => {\n onClose?.();\n setVisible(false);\n setTimeout(() => {\n Toast.destroy(id);\n }, 200);\n };\n\n useTimeout(close, duration || null);\n\n if (!isBrowser) {\n return null;\n }\n\n return createPortal(\n <div\n className={cn(\n 'transition-all duration-200',\n visible ? 'opacity-100' : 'translate-x-2 opacity-0',\n )}\n >\n <Toast {...props} onClose={close} />\n </div>,\n createToastRoot(container)!,\n );\n};\n\nconst ToastStackRenderer = ({ container }: { container: HTMLElement }) => {\n const [, forceUpdate] = useState({});\n\n useEffect(() => {\n const state = getContainerState(container);\n state.update = () => forceUpdate({});\n return () => {\n state.update = null;\n };\n }, [container]);\n\n const { stack } = getContainerState(container);\n\n return (\n <>\n {stack.map(({ id, ...props }) => (\n <StaticToast key={id} id={id} {...props} />\n ))}\n </>\n );\n};\n\nconst toastRoots = new Map<HTMLElement, Root>();\n\nconst renderToast = (props: StaticProps) => {\n if (!isBrowser) {\n return;\n }\n\n const targetContainer = props.container || document.body;\n const rootElement = createToastRoot(targetContainer)!;\n\n if (!toastRoots.has(targetContainer)) {\n const root = createRoot(rootElement);\n toastRoots.set(targetContainer, root);\n root.render(<ToastStackRenderer container={targetContainer} />);\n }\n\n const state = getContainerState(targetContainer);\n const id = props.id || uuid();\n state.stack.push({ ...props, id });\n state.update?.();\n\n return id;\n};\n\nToast.destroy = (id?: string) => {\n if (!id) {\n containerStates.forEach(state => {\n state.stack = [];\n state.update?.();\n });\n toastRoots.forEach((root, container) => {\n root.unmount();\n toastRootElements.get(container)?.remove();\n toastRootElements.delete(container);\n });\n toastRoots.clear();\n containerStates.clear();\n return;\n }\n\n containerStates.forEach((state, container) => {\n if (!state.stack.some(toast => toast.id === id)) {\n return;\n }\n\n state.stack = state.stack.filter(toast => toast.id !== id);\n state.update?.();\n\n if (!state.stack.length) {\n toastRoots.get(container)?.unmount();\n toastRoots.delete(container);\n containerStates.delete(container);\n toastRootElements.get(container)?.remove();\n toastRootElements.delete(container);\n }\n });\n};\n\nToast.destroyAll = () => {\n Toast.destroy();\n};\n\ntype TriggerOptions = Omit<StaticProps, 'type' | 'title'>;\n\nToast.info = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'info', title, ...props });\nToast.success = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'success', title, ...props });\nToast.error = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'error', title, ...props });\nToast.warning = (title: React.ReactNode, props?: TriggerOptions) =>\n renderToast({ type: 'warning', title, ...props });\n\nexport default Toast;\nexport type { StaticProps as ToastOptions };\n"],"mappings":"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"}
|