From 2492a414e249b0580aa3138d7ffe98816059085a Mon Sep 17 00:00:00 2001 From: malangcat Date: Fri, 17 Jan 2025 03:59:25 +0900 Subject: [PATCH] feat: re-write dialog without stackflow --- ...mitive-npm-1.1.1-758e8c9172-d7e8191775.zip | Bin 0 -> 4268 bytes ...-layer-npm-1.1.3-2114a37d20-9905ff3d8d.zip | Bin 0 -> 19640 bytes ...-scope-npm-1.1.1-eaf894ac65-128508e7e3.zip | Bin 0 -> 20653 bytes ...mitive-npm-2.0.1-a63c88e534-ed6829b8ff.zip | Bin 0 -> 10524 bytes ...-push-npm-1.1.13-5582c8a4fe-ca498d6553.zip | Bin 0 -> 8072 bytes .../example/alert-dialog-danger.tsx | 50 ++++-- .../example/alert-dialog-default-activity.tsx | 112 ------------ .../example/alert-dialog-neutral.tsx | 50 ++++-- .../example/alert-dialog-nonpreferred.tsx | 32 ---- .../example/alert-dialog-preview.tsx | 50 ++++-- .../example/alert-dialog-single.tsx | 38 ++++- .../example/alert-dialog-stackflow.tsx | 37 ++++ .../docs/react/components/alert-dialog.mdx | 31 ++++ .../components/stackflow/alert-dialog.mdx | 31 ---- docs/registry/ui/alert-dialog.tsx | 87 +++++++--- docs/registry/util/mergeRefs.ts | 19 --- docs/registry/util/types.ts | 1 - docs/registry/util/visuallyHidden.ts | 13 -- examples/stackflow-spa/package.json | 2 + .../src/activities/ActivityAlertDialog.tsx | 69 +++++--- .../src/activities/ActivityHome.tsx | 55 +++++- .../src/design-system/ui/alert-dialog.tsx | 61 +++++++ .../src/design-system/util/mergeRefs.ts | 19 --- .../src/design-system/util/types.ts | 1 - .../design-system/util/use-step-dialog.tsx | 58 +++++++ .../src/design-system/util/visuallyHidden.ts | 13 -- packages/qvism-preset/src/recipes/dialog.ts | 106 ++++++------ packages/react-headless/dialog/.gitignore | 2 + packages/react-headless/dialog/package.json | 51 ++++++ .../dialog/src/Dialog.namespace.ts | 18 ++ packages/react-headless/dialog/src/Dialog.tsx | 112 ++++++++++++ packages/react-headless/dialog/src/index.ts | 22 +++ .../dialog/src/private/Presence.tsx | 35 ++++ .../dialog/src/private/usePresence.tsx | 159 ++++++++++++++++++ .../dialog/src/private/usePresenceContext.tsx | 19 +++ .../react-headless/dialog/src/useDialog.ts | 135 +++++++++++++++ .../dialog/src/useDialogContext.tsx | 19 +++ packages/react-headless/dialog/tsconfig.json | 20 +++ .../react-headless/primitive/src/index.tsx | 1 + packages/react/package.json | 1 + .../src/components/Dialog/Dialog.namespace.ts | 22 +++ .../react/src/components/Dialog/Dialog.tsx | 94 +++++++++++ packages/react/src/components/Dialog/index.ts | 24 +++ packages/react/src/components/index.ts | 1 + packages/recipe/lib/dialog.d.ts | 7 +- packages/recipe/lib/dialog.mjs | 19 +-- packages/stackflow/src/Dialog.tsx | 2 +- packages/stylesheet/dialog.css | 73 ++++---- yarn.lock | 103 +++++++++++- 49 files changed, 1414 insertions(+), 460 deletions(-) create mode 100644 .yarn/cache/@radix-ui-primitive-npm-1.1.1-758e8c9172-d7e8191775.zip create mode 100644 .yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.3-2114a37d20-9905ff3d8d.zip create mode 100644 .yarn/cache/@radix-ui-react-focus-scope-npm-1.1.1-eaf894ac65-128508e7e3.zip create mode 100644 .yarn/cache/@radix-ui-react-primitive-npm-2.0.1-a63c88e534-ed6829b8ff.zip create mode 100644 .yarn/cache/@stackflow-compat-await-push-npm-1.1.13-5582c8a4fe-ca498d6553.zip delete mode 100644 docs/components/example/alert-dialog-default-activity.tsx delete mode 100644 docs/components/example/alert-dialog-nonpreferred.tsx create mode 100644 docs/components/example/alert-dialog-stackflow.tsx create mode 100644 docs/content/docs/react/components/alert-dialog.mdx delete mode 100644 docs/content/docs/react/components/stackflow/alert-dialog.mdx delete mode 100644 docs/registry/util/mergeRefs.ts delete mode 100644 docs/registry/util/types.ts delete mode 100644 docs/registry/util/visuallyHidden.ts create mode 100644 examples/stackflow-spa/src/design-system/ui/alert-dialog.tsx delete mode 100644 examples/stackflow-spa/src/design-system/util/mergeRefs.ts delete mode 100644 examples/stackflow-spa/src/design-system/util/types.ts create mode 100644 examples/stackflow-spa/src/design-system/util/use-step-dialog.tsx delete mode 100644 examples/stackflow-spa/src/design-system/util/visuallyHidden.ts create mode 100644 packages/react-headless/dialog/.gitignore create mode 100644 packages/react-headless/dialog/package.json create mode 100644 packages/react-headless/dialog/src/Dialog.namespace.ts create mode 100644 packages/react-headless/dialog/src/Dialog.tsx create mode 100644 packages/react-headless/dialog/src/index.ts create mode 100644 packages/react-headless/dialog/src/private/Presence.tsx create mode 100644 packages/react-headless/dialog/src/private/usePresence.tsx create mode 100644 packages/react-headless/dialog/src/private/usePresenceContext.tsx create mode 100644 packages/react-headless/dialog/src/useDialog.ts create mode 100644 packages/react-headless/dialog/src/useDialogContext.tsx create mode 100644 packages/react-headless/dialog/tsconfig.json create mode 100644 packages/react/src/components/Dialog/Dialog.namespace.ts create mode 100644 packages/react/src/components/Dialog/Dialog.tsx create mode 100644 packages/react/src/components/Dialog/index.ts diff --git a/.yarn/cache/@radix-ui-primitive-npm-1.1.1-758e8c9172-d7e8191775.zip b/.yarn/cache/@radix-ui-primitive-npm-1.1.1-758e8c9172-d7e8191775.zip new file mode 100644 index 0000000000000000000000000000000000000000..7bfd44d43f7dd3d9ba91a48e88799b293ca27dcf GIT binary patch literal 4268 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 XT2E1W%q(l_5c&tY0|2-QS^M{I+@XuK literal 0 HcmV?d00001 diff --git a/.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.3-2114a37d20-9905ff3d8d.zip b/.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.3-2114a37d20-9905ff3d8d.zip new file mode 100644 index 0000000000000000000000000000000000000000..5a5a3edf58decd2e002dd996dec2066db1d239f4 GIT binary patch literal 19640 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 RfehmT!kNH?#_0j#0RSw}Pk#Ua literal 0 HcmV?d00001 diff --git a/.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.1-eaf894ac65-128508e7e3.zip b/.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.1-eaf894ac65-128508e7e3.zip new file mode 100644 index 0000000000000000000000000000000000000000..61f6a115e1072cf87420d23ab2cf3332ae25e239 GIT binary patch literal 20653 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 C&oxj0 literal 0 HcmV?d00001 diff --git a/.yarn/cache/@radix-ui-react-primitive-npm-2.0.1-a63c88e534-ed6829b8ff.zip b/.yarn/cache/@radix-ui-react-primitive-npm-2.0.1-a63c88e534-ed6829b8ff.zip new file mode 100644 index 0000000000000000000000000000000000000000..ced2008b4984a9d9829595774e59965f9352671f GIT binary patch literal 10524 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 literal 0 HcmV?d00001 diff --git a/.yarn/cache/@stackflow-compat-await-push-npm-1.1.13-5582c8a4fe-ca498d6553.zip b/.yarn/cache/@stackflow-compat-await-push-npm-1.1.13-5582c8a4fe-ca498d6553.zip new file mode 100644 index 0000000000000000000000000000000000000000..de18538b9c92b3a1aa3fee169b3579fa9b722260 GIT binary patch literal 8072 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 { +const AlertDialogDanger = () => { return ( - - - 취소 - - - 확인 - - + // You can set z-index dialog with "--layer-index" custom property. useful for stackflow integration. + + + 열기 + + + + 제목 + 파괴적, 비가역적 작업을 경고합니다. + + + + + + 취소 + + + + + 확인 + + + + + + ); }; -export default AlertDialogDangerActivity; +export default AlertDialogDanger; diff --git a/docs/components/example/alert-dialog-default-activity.tsx b/docs/components/example/alert-dialog-default-activity.tsx deleted file mode 100644 index f909fb589..000000000 --- a/docs/components/example/alert-dialog-default-activity.tsx +++ /dev/null @@ -1,112 +0,0 @@ -import * as React from "react"; - -import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { type ActivityComponentType, useStepFlow, useStack } from "@stackflow/react/future"; - -import { ActionButton } from "seed-design/ui/action-button"; -import { AlertDialog as UIAlertDialog } from "seed-design/ui/alert-dialog"; - -declare module "@stackflow/config" { - interface Register { - AlertDialogDefault: { - alert: boolean; - }; - } -} - -const AlertDialogDefaultActivity: ActivityComponentType<"AlertDialogDefault"> = ({ params }) => { - const { alert } = params; - const stack = useStack(); - const { pushStep, popStep } = useStepFlow("AlertDialogDefault"); - - const appBarLeft = () =>
Left
; - const appBarRight = () =>
Right
; - - const onInteractOutside = () => { - popStep(); - }; - - const onButtonClick = () => { - pushStep({ - alert: true, - }); - }; - - const mainActivitySteps = stack.activities[0].steps; - - return ( - -
- Open - {alert && ( - - )} -
- -
- - Steps - - {mainActivitySteps.map((step) => ( -
- ))} -
- -
- popStep}>Back -
- - ); -}; - -export default AlertDialogDefaultActivity; - -AlertDialogDefaultActivity.displayName = "AlertDialogDefaultActivity"; diff --git a/docs/components/example/alert-dialog-neutral.tsx b/docs/components/example/alert-dialog-neutral.tsx index 56aa79337..ebf7b0295 100644 --- a/docs/components/example/alert-dialog-neutral.tsx +++ b/docs/components/example/alert-dialog-neutral.tsx @@ -1,19 +1,47 @@ "use client"; import { ActionButton } from "seed-design/ui/action-button"; -import { AlertDialog, AlertDialogAction } from "seed-design/ui/alert-dialog"; +import { + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogRoot, + AlertDialogTitle, + AlertDialogTrigger, +} from "seed-design/ui/alert-dialog"; +import { Column, Columns } from "@seed-design/react"; -const AlertDialogNeutralActivity = () => { +const AlertDialogNeutral = () => { return ( - - - 취소 - - - 확인 - - + // You can set z-index dialog with "--layer-index" custom property. useful for stackflow integration. + + + 열기 + + + + 제목 + 중립적인 선택지를 제공합니다. + + + + + + 취소 + + + + + 확인 + + + + + + ); }; -export default AlertDialogNeutralActivity; +export default AlertDialogNeutral; diff --git a/docs/components/example/alert-dialog-nonpreferred.tsx b/docs/components/example/alert-dialog-nonpreferred.tsx deleted file mode 100644 index 45b394f71..000000000 --- a/docs/components/example/alert-dialog-nonpreferred.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import { ActionButton } from "seed-design/ui/action-button"; -import { AlertDialog } from "seed-design/ui/alert-dialog"; -import { Flex } from "seed-design/ui/layout"; - -const AlertDialogNonpreferredActivity = () => { - return ( - - - 확인 - - - - ); -}; - -export default AlertDialogNonpreferredActivity; diff --git a/docs/components/example/alert-dialog-preview.tsx b/docs/components/example/alert-dialog-preview.tsx index 39b626842..a7c935b86 100644 --- a/docs/components/example/alert-dialog-preview.tsx +++ b/docs/components/example/alert-dialog-preview.tsx @@ -1,19 +1,47 @@ "use client"; +import { Column, Columns } from "@seed-design/react"; import { ActionButton } from "seed-design/ui/action-button"; -import { AlertDialog, AlertDialogAction } from "seed-design/ui/alert-dialog"; +import { + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogRoot, + AlertDialogTitle, + AlertDialogTrigger, +} from "seed-design/ui/alert-dialog"; -const AlertDialogPreviewActivity = () => { +const AlertDialogSingle = () => { return ( - - - 취소 - - - 확인 - - + // You can set z-index dialog with "--layer-index" custom property. useful for stackflow integration. + + + 열기 + + + + 주의 + 이 작업은 되돌릴 수 없습니다. + + + + + + 취소 + + + + + 확인 + + + + + + ); }; -export default AlertDialogPreviewActivity; +export default AlertDialogSingle; diff --git a/docs/components/example/alert-dialog-single.tsx b/docs/components/example/alert-dialog-single.tsx index 5f022f203..30ff191e1 100644 --- a/docs/components/example/alert-dialog-single.tsx +++ b/docs/components/example/alert-dialog-single.tsx @@ -1,17 +1,37 @@ "use client"; -import { Flex } from "seed-design/ui/layout"; import { ActionButton } from "seed-design/ui/action-button"; -import { AlertDialog } from "seed-design/ui/alert-dialog"; +import { + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogRoot, + AlertDialogTitle, + AlertDialogTrigger, +} from "seed-design/ui/alert-dialog"; -const AlertDialogSingleActivity = () => { +const AlertDialogSingle = () => { + // You can set z-index dialog with "--layer-index" custom property. useful for stackflow integration. return ( - - - 확인 - - + + + 열기 + + + + 제목 + 단일 선택지를 제공합니다. + + + + 확인 + + + + ); }; -export default AlertDialogSingleActivity; +export default AlertDialogSingle; diff --git a/docs/components/example/alert-dialog-stackflow.tsx b/docs/components/example/alert-dialog-stackflow.tsx new file mode 100644 index 000000000..8bd3f8d00 --- /dev/null +++ b/docs/components/example/alert-dialog-stackflow.tsx @@ -0,0 +1,37 @@ +"use client"; + +import { useActivity } from "@stackflow/react"; +import { useFlow } from "@stackflow/react/future"; +import { ActionButton } from "seed-design/ui/action-button"; +import { + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogRoot, + AlertDialogTitle, +} from "seed-design/ui/alert-dialog"; + +const AlertDialogStackflow = () => { + const activity = useActivity(); + const { pop } = useFlow(); + + return ( + !open && pop()}> + + + 제목 + Stackflow + + + + 확인 + + + + + ); +}; + +export default AlertDialogStackflow; diff --git a/docs/content/docs/react/components/alert-dialog.mdx b/docs/content/docs/react/components/alert-dialog.mdx new file mode 100644 index 000000000..e867e77ce --- /dev/null +++ b/docs/content/docs/react/components/alert-dialog.mdx @@ -0,0 +1,31 @@ +--- +title: Alert Dialog +--- + + + +## 설치 + + + +## Props + + + +## 예제 + +### Single Action + + + +### Neutral Secondary Action + + + +### Danger Action + + + +### Stackflow + + diff --git a/docs/content/docs/react/components/stackflow/alert-dialog.mdx b/docs/content/docs/react/components/stackflow/alert-dialog.mdx deleted file mode 100644 index cc11a16be..000000000 --- a/docs/content/docs/react/components/stackflow/alert-dialog.mdx +++ /dev/null @@ -1,31 +0,0 @@ ---- -title: Alert Dialog ---- - - - -## 설치 - - - -## Props - - - -## 예제 - -### Single Action - - - -### Neutral Secondary Action - - - -### Non-Preferred Secondary Action - - - -### Danger Action - - \ No newline at end of file diff --git a/docs/registry/ui/alert-dialog.tsx b/docs/registry/ui/alert-dialog.tsx index 64f098c76..3befd5599 100644 --- a/docs/registry/ui/alert-dialog.tsx +++ b/docs/registry/ui/alert-dialog.tsx @@ -2,37 +2,70 @@ import "@seed-design/stylesheet/dialog.css"; -import { Dialog } from "@seed-design/stackflow"; +import { Dialog } from "@seed-design/react"; import { forwardRef } from "react"; -export interface AlertDialogProps extends Dialog.RootProps { - title: string; - description: string; -} +export interface AlertDialogRootProps extends Dialog.RootProps {} /** - * @see https://v3.seed-design.io/docs/react/components/stackflow/alert-dialog + * @see https://v3.seed-design.io/docs/react/components/alert-dialog */ -export const AlertDialog = forwardRef( - ({ title, description, children, ...otherProps }, ref) => { - // FIXME: Footer 안의 action 배열을 다룰 쓸만한 인터페이스가 생각이 안남. 인터페이스 다시 생각할 것. - return ( - - - - - {title} - {description} - - {children} - - - ); - }, -); - -AlertDialog.displayName = "AlertDialog"; - -export type AlertDialogActionProps = Dialog.ActionProps; +export const AlertDialogRoot = ({ + children, + ...otherProps +}: AlertDialogRootProps) => { + return ( + + {children} + + ); +}; +AlertDialogRoot.displayName = "AlertDialogRoot"; + +export interface AlertDialogContentProps extends Dialog.ContentProps { + layerIndex?: number; +} + +export const AlertDialogContent = forwardRef< + HTMLDivElement, + AlertDialogContentProps +>(({ children, layerIndex, ...otherProps }, ref) => { + return ( + + + + {children} + + + ); +}); + +export interface AlertDialogTriggerProps extends Dialog.TriggerProps {} + +export const AlertDialogTrigger = Dialog.Trigger; + +export interface AlertDialogHeaderProps extends Dialog.HeaderProps {} + +export const AlertDialogHeader = Dialog.Header; + +export interface AlertDialogTitleProps extends Dialog.TitleProps {} + +export const AlertDialogTitle = Dialog.Title; + +export interface AlertDialogDescriptionProps extends Dialog.DescriptionProps {} + +export const AlertDialogDescription = Dialog.Description; + +export interface AlertDialogFooterProps extends Dialog.FooterProps {} + +export const AlertDialogFooter = Dialog.Footer; + +export interface AlertDialogActionProps extends Dialog.ActionProps {} export const AlertDialogAction = Dialog.Action; diff --git a/docs/registry/util/mergeRefs.ts b/docs/registry/util/mergeRefs.ts deleted file mode 100644 index e3d220be5..000000000 --- a/docs/registry/util/mergeRefs.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type * as React from "react"; - -export function mergeRefs( - ...refs: React.ForwardedRef[] -): React.ForwardedRef { - if (refs.length === 1) { - return refs[0]; - } - - return (value: T | null) => { - for (const ref of refs) { - if (typeof ref === "function") { - ref(value); - } else if (ref != null) { - ref.current = value; - } - } - }; -} diff --git a/docs/registry/util/types.ts b/docs/registry/util/types.ts deleted file mode 100644 index 7e0e3c9f9..000000000 --- a/docs/registry/util/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type Assign = Omit & U; diff --git a/docs/registry/util/visuallyHidden.ts b/docs/registry/util/visuallyHidden.ts deleted file mode 100644 index a31846088..000000000 --- a/docs/registry/util/visuallyHidden.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { CSSProperties } from "react"; - -export const visuallyHidden: CSSProperties = { - border: 0, - clip: "rect(0 0 0 0)", - height: "1px", - margin: "-1px", - overflow: "hidden", - padding: 0, - position: "absolute", - whiteSpace: "nowrap", - width: "1px", -}; diff --git a/examples/stackflow-spa/package.json b/examples/stackflow-spa/package.json index 1469f71ff..1ef06e878 100644 --- a/examples/stackflow-spa/package.json +++ b/examples/stackflow-spa/package.json @@ -10,6 +10,7 @@ "dependencies": { "@daangn/react-monochrome-icon": "^0.0.13", "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-use-callback-ref": "^1.1.0", "@seed-design/react": "0.0.0", "@seed-design/react-popover": "0.0.0-alpha-20241030023710", "@seed-design/react-tabs": "0.0.0-alpha-20241209060641", @@ -18,6 +19,7 @@ "@seed-design/stackflow": "0.0.0", "@seed-design/stylesheet": "3.0.0-alpha-20241212122822", "@seed-design/vars": "0.0.0", + "@stackflow/compat-await-push": "^1.1.13", "@stackflow/core": "^1.1.0", "@stackflow/plugin-basic-ui": "^1.11.1", "@stackflow/plugin-history-sync": "^1.7.0", diff --git a/examples/stackflow-spa/src/activities/ActivityAlertDialog.tsx b/examples/stackflow-spa/src/activities/ActivityAlertDialog.tsx index 4d0776fd4..6656dfcb6 100644 --- a/examples/stackflow-spa/src/activities/ActivityAlertDialog.tsx +++ b/examples/stackflow-spa/src/activities/ActivityAlertDialog.tsx @@ -1,35 +1,54 @@ -import type { ActivityComponentType } from "@stackflow/react"; +import { useActivity, type ActivityComponentType } from "@stackflow/react"; -import { AlertDialog, AlertDialogAction } from "../design-system/stackflow/AlertDialog"; import { ActionButton } from "../design-system/ui/action-button"; +import { + AlertDialogAction, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogRoot, + AlertDialogTitle, +} from "../design-system/ui/alert-dialog"; import { useFlow } from "../stackflow"; +import { Stack } from "@seed-design/react"; +import { send } from "@stackflow/compat-await-push"; const ActivityAlertDialog: ActivityComponentType = () => { - const { pop } = useFlow(); + const activity = useActivity(); + const { pop, push } = useFlow(); + + const handleClose = (open: boolean) => { + if (!open) { + pop(); + send({ + activityId: activity.id, + data: { + message: "hello", + }, + }); + } + }; return ( - - - { - pop(); - }} - variant="neutralWeak" - > - 취소 - - - - { - pop(); - }} - variant="neutralSolid" - > - 확인 - - - + + + + 제목 + 다람쥐 헌 쳇바퀴에 타고파 + + + + + 확인 + + push("ActivityActionChip", {})}> + Push + + + + + ); }; diff --git a/examples/stackflow-spa/src/activities/ActivityHome.tsx b/examples/stackflow-spa/src/activities/ActivityHome.tsx index cfc4fb412..c611c4f52 100644 --- a/examples/stackflow-spa/src/activities/ActivityHome.tsx +++ b/examples/stackflow-spa/src/activities/ActivityHome.tsx @@ -2,22 +2,41 @@ import type { ActivityComponentType } from "@stackflow/react"; import { AppScreen } from "@stackflow/plugin-basic-ui"; -import { List, ListItem } from "../components/List"; -import { useFlow } from "../stackflow"; import { useSnackbarAdapter } from "@seed-design/react"; +import { receive } from "@stackflow/compat-await-push"; +import { useRef } from "react"; +import { List, ListItem } from "../components/List"; +import { ActionButton } from "../design-system/ui/action-button"; +import { + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogRoot, + AlertDialogTitle, + AlertDialogTrigger, +} from "../design-system/ui/alert-dialog"; import { Snackbar } from "../design-system/ui/snackbar"; +import { useStepDialog } from "../design-system/util/use-step-dialog"; +import { useFlow } from "../stackflow"; const ActivityHome: ActivityComponentType = () => { const { push } = useFlow(); + const { dialogProps } = useStepDialog(); + const ref = useRef(null); const snackbarAdapter = useSnackbarAdapter(); return (
push("ActivityActionButton", {})} title="ActionButton" /> @@ -26,7 +45,29 @@ const ActivityHome: ActivityComponentType = () => { push("ActivityHelpBubble", {})} title="HelpBubble" /> push("ActivityLayerBar", {})} title="LayerBar" /> push("ActivityTransparentBar", {})} title="TransparentBar" /> - push("ActivityAlertDialog", {})} title="AlertDialog" /> + + + + + + + 제목 + + Lorem ipsum dolor sit amet consectetur adipisicing elit. + + + + push("ActivityActionChip", {})}>확인 + + + + { + const result = await receive(push("ActivityAlertDialog", {})); + console.log(result.message); + }} + title="AlertDialog (activity)" + /> push("ActivityBottomSheet", {})} title="BottomSheet" /> push("ActivityActionSheet", {})} title="ActionSheet" /> { + return ( + + {children} + + ); +}; +AlertDialogRoot.displayName = "AlertDialogRoot"; + +export interface AlertDialogContentProps extends Dialog.ContentProps { + layerIndex?: number; +} + +export const AlertDialogContent = forwardRef( + ({ children, layerIndex, ...otherProps }, ref) => { + return ( + + + + {children} + + + ); + }, +); + +export interface AlertDialogTriggerProps extends Dialog.TriggerProps {} + +export const AlertDialogTrigger = Dialog.Trigger; + +export interface AlertDialogHeaderProps extends Dialog.HeaderProps {} + +export const AlertDialogHeader = Dialog.Header; + +export interface AlertDialogTitleProps extends Dialog.TitleProps {} + +export const AlertDialogTitle = Dialog.Title; + +export interface AlertDialogDescriptionProps extends Dialog.DescriptionProps {} + +export const AlertDialogDescription = Dialog.Description; + +export interface AlertDialogFooterProps extends Dialog.FooterProps {} + +export const AlertDialogFooter = Dialog.Footer; + +export interface AlertDialogActionProps extends Dialog.ActionProps {} + +export const AlertDialogAction = Dialog.Action; diff --git a/examples/stackflow-spa/src/design-system/util/mergeRefs.ts b/examples/stackflow-spa/src/design-system/util/mergeRefs.ts deleted file mode 100644 index e3d220be5..000000000 --- a/examples/stackflow-spa/src/design-system/util/mergeRefs.ts +++ /dev/null @@ -1,19 +0,0 @@ -import type * as React from "react"; - -export function mergeRefs( - ...refs: React.ForwardedRef[] -): React.ForwardedRef { - if (refs.length === 1) { - return refs[0]; - } - - return (value: T | null) => { - for (const ref of refs) { - if (typeof ref === "function") { - ref(value); - } else if (ref != null) { - ref.current = value; - } - } - }; -} diff --git a/examples/stackflow-spa/src/design-system/util/types.ts b/examples/stackflow-spa/src/design-system/util/types.ts deleted file mode 100644 index 7e0e3c9f9..000000000 --- a/examples/stackflow-spa/src/design-system/util/types.ts +++ /dev/null @@ -1 +0,0 @@ -export type Assign = Omit & U; diff --git a/examples/stackflow-spa/src/design-system/util/use-step-dialog.tsx b/examples/stackflow-spa/src/design-system/util/use-step-dialog.tsx new file mode 100644 index 000000000..be1cbe865 --- /dev/null +++ b/examples/stackflow-spa/src/design-system/util/use-step-dialog.tsx @@ -0,0 +1,58 @@ +import { useCallbackRef } from "@radix-ui/react-use-callback-ref"; +import { useActivity, useActivityParams } from "@stackflow/react"; +import { useStepFlow } from "@stackflow/react/future"; +import { useCallback, useEffect, useId, useMemo, useState } from "react"; + +export interface UseStepDialogProps { + defaultOpen?: boolean; + onOpenChange?: (open: boolean) => void; +} + +export function useStepDialog(props: UseStepDialogProps = {}) { + const [open, setOpen] = useState(props.defaultOpen ?? false); + + const id = useId(); + const activity = useActivity(); + const { pushStep, popStep } = useStepFlow(activity.name as any); + const params = useActivityParams>(); + const isDialogPersist = params[id] === "dialog"; + + useEffect(() => { + if (!isDialogPersist) { + setOpen(false); + } + }, [isDialogPersist]); + + const onOpenChange = useCallbackRef(props.onOpenChange); + const handleOpenChange = useCallback( + (open: boolean) => { + setOpen(open); + onOpenChange?.(open); + if (open) { + if (!isDialogPersist) { + pushStep({ + ...params, + [id]: "dialog", + }); + } + } else { + if (isDialogPersist) { + popStep(); + } + } + }, + [pushStep, popStep, onOpenChange, isDialogPersist, params, id], + ); + + return useMemo( + () => ({ + open, + setOpen: handleOpenChange, + dialogProps: { + open, + onOpenChange: handleOpenChange, + }, + }), + [open, handleOpenChange], + ); +} diff --git a/examples/stackflow-spa/src/design-system/util/visuallyHidden.ts b/examples/stackflow-spa/src/design-system/util/visuallyHidden.ts deleted file mode 100644 index a31846088..000000000 --- a/examples/stackflow-spa/src/design-system/util/visuallyHidden.ts +++ /dev/null @@ -1,13 +0,0 @@ -import type { CSSProperties } from "react"; - -export const visuallyHidden: CSSProperties = { - border: 0, - clip: "rect(0 0 0 0)", - height: "1px", - margin: "-1px", - overflow: "hidden", - padding: 0, - position: "absolute", - whiteSpace: "nowrap", - width: "1px", -}; diff --git a/packages/qvism-preset/src/recipes/dialog.ts b/packages/qvism-preset/src/recipes/dialog.ts index 090fe7e9b..f8ed777fe 100644 --- a/packages/qvism-preset/src/recipes/dialog.ts +++ b/packages/qvism-preset/src/recipes/dialog.ts @@ -1,36 +1,48 @@ import { dialog as vars } from "@seed-design/vars/component"; -import { defineRecipe } from "../utils/define-recipe"; import { enterAnimation, exitAnimation } from "../utils/animation"; -import { pseudo } from "../utils/pseudo"; +import { defineRecipe } from "../utils/define-recipe"; +import { not, open, pseudo } from "../utils/pseudo"; const dialog = defineRecipe({ name: "dialog", - slots: ["backdrop", "container", "content", "header", "footer", "action", "title", "description"], + slots: [ + "positioner", + "backdrop", + "content", + "header", + "footer", + "action", + "title", + "description", + ], base: { - backdrop: { + positioner: { position: "fixed", + display: "flex", + justifyContent: "center", + alignItems: "center", inset: 0, - background: vars.base.enabled.backdrop.color, + overscrollBehaviorY: "none", - [pseudo(":is([data-transition-state='exit-active'],[data-transition-state='exit-done'])")]: - exitAnimation({ - timingFunction: vars.base.enabled.backdrop.exitTimingFunction, - duration: vars.base.enabled.backdrop.exitDuration, - opacity: vars.base.enabled.backdrop.exitOpacity, - }), - [pseudo(":is([data-transition-state='enter-active'],[data-transition-state='enter-done'])")]: - enterAnimation({ - timingFunction: vars.base.enabled.backdrop.enterTimingFunction, - duration: vars.base.enabled.backdrop.enterDuration, - opacity: vars.base.enabled.backdrop.enterOpacity, - }), + "--dialog-z-index": "2", + zIndex: "calc(var(--dialog-z-index) + var(--layer-index, 0))", }, - container: { + backdrop: { position: "fixed", - display: "flex", - justifyContent: "center", - alignItems: "center", inset: 0, + background: vars.base.enabled.backdrop.color, + zIndex: "calc(var(--dialog-z-index) + var(--layer-index, 0))", + + [pseudo(open)]: enterAnimation({ + timingFunction: vars.base.enabled.backdrop.enterTimingFunction, + duration: vars.base.enabled.backdrop.enterDuration, + opacity: vars.base.enabled.backdrop.enterOpacity, + }), + [pseudo(not(open))]: exitAnimation({ + timingFunction: vars.base.enabled.backdrop.exitTimingFunction, + duration: vars.base.enabled.backdrop.exitDuration, + opacity: vars.base.enabled.backdrop.exitOpacity, + }), }, content: { position: "relative", @@ -39,6 +51,7 @@ const dialog = defineRecipe({ flexDirection: "column", boxSizing: "border-box", wordBreak: "break-all", + zIndex: "calc(var(--dialog-z-index) + var(--layer-index, 0))", background: vars.base.enabled.content.color, maxWidth: vars.base.enabled.content.maxWidth, @@ -46,19 +59,17 @@ const dialog = defineRecipe({ padding: `${vars.base.enabled.content.paddingY} ${vars.base.enabled.content.paddingX}`, borderRadius: vars.base.enabled.content.cornerRadius, - [pseudo(":is([data-transition-state='exit-active'],[data-transition-state='exit-done'])")]: - exitAnimation({ - timingFunction: vars.base.enabled.content.exitTimingFunction, - duration: vars.base.enabled.content.exitDuration, - opacity: vars.base.enabled.content.exitOpacity, - }), - [pseudo(":is([data-transition-state='enter-active'],[data-transition-state='enter-done'])")]: - enterAnimation({ - timingFunction: vars.base.enabled.content.enterTimingFunction, - duration: vars.base.enabled.content.enterDuration, - opacity: vars.base.enabled.content.enterOpacity, - scale: vars.base.enabled.content.enterScale, - }), + [pseudo(open)]: enterAnimation({ + timingFunction: vars.base.enabled.content.enterTimingFunction, + duration: vars.base.enabled.content.enterDuration, + opacity: vars.base.enabled.content.enterOpacity, + scale: vars.base.enabled.content.enterScale, + }), + [pseudo(not(open))]: exitAnimation({ + timingFunction: vars.base.enabled.content.exitTimingFunction, + duration: vars.base.enabled.content.exitDuration, + opacity: vars.base.enabled.content.exitOpacity, + }), }, header: { display: "flex", @@ -85,35 +96,14 @@ const dialog = defineRecipe({ }, footer: { display: "flex", - flexWrap: "wrap", - justifyContent: "space-between", + flexDirection: "column", alignItems: "stretch", paddingTop: vars.base.enabled.footer.paddingTop, - gap: vars.base.enabled.footer.gap, }, - action: { - width: "initial", - minWidth: `calc(50% - ${vars.base.enabled.footer.gap} / 2)`, - }, - }, - variants: { - footerLayout: { - horizontal: { - footer: { - flexDirection: "row-reverse", - }, - }, - vertical: { - footer: { - flexDirection: "column", - }, - }, - }, - }, - defaultVariants: { - footerLayout: "horizontal", }, + variants: {}, + defaultVariants: {}, }); export default dialog; diff --git a/packages/react-headless/dialog/.gitignore b/packages/react-headless/dialog/.gitignore new file mode 100644 index 000000000..32a94bfe8 --- /dev/null +++ b/packages/react-headless/dialog/.gitignore @@ -0,0 +1,2 @@ +/lib/ +/dist/ diff --git a/packages/react-headless/dialog/package.json b/packages/react-headless/dialog/package.json new file mode 100644 index 000000000..c94519afd --- /dev/null +++ b/packages/react-headless/dialog/package.json @@ -0,0 +1,51 @@ +{ + "name": "@seed-design/react-dialog", + "version": "0.0.0", + "repository": { + "type": "git", + "url": "git+https://github.com/daangn/seed-design.git", + "directory": "packages/react-headless/dialog" + }, + "sideEffects": false, + "exports": { + ".": { + "types": "./lib/index.d.ts", + "require": "./lib/index.js", + "import": "./lib/index.mjs" + }, + "./package.json": "./package.json" + }, + "main": "./lib/index.js", + "files": [ + "lib", + "src" + ], + "scripts": { + "prepack": "yarn build", + "clean": "rm -rf lib", + "build": "nanobundle build" + }, + "dependencies": { + "@radix-ui/react-dismissable-layer": "^1.1.3", + "@radix-ui/react-focus-scope": "^1.1.1", + "@radix-ui/react-use-controllable-state": "1.1.0", + "@radix-ui/react-use-layout-effect": "^1.1.0", + "@seed-design/dom-utils": "0.0.0-alpha-20241030023710" + }, + "devDependencies": { + "nanobundle": "^1.6.0" + }, + "peerDependencies": { + "react": ">=18.0.0", + "react-dom": ">=18.0.0" + }, + "publishConfig": { + "access": "public" + }, + "ultra": { + "concurrent": [ + "dev", + "build" + ] + } +} diff --git a/packages/react-headless/dialog/src/Dialog.namespace.ts b/packages/react-headless/dialog/src/Dialog.namespace.ts new file mode 100644 index 000000000..a74b1607d --- /dev/null +++ b/packages/react-headless/dialog/src/Dialog.namespace.ts @@ -0,0 +1,18 @@ +export { + DialogBackdrop as Backdrop, + DialogCloseButton as CloseButton, + DialogContent as Content, + DialogDescription as Description, + DialogPositioner as Positioner, + DialogRoot as Root, + DialogTitle as Title, + DialogTrigger as Trigger, + type DialogBackdropProps as BackdropProps, + type DialogCloseButtonProps as CloseButtonProps, + type DialogContentProps as ContentProps, + type DialogDescriptionProps as DescriptionProps, + type DialogPositionerProps as PositionerProps, + type DialogRootProps as RootProps, + type DialogTitleProps as TitleProps, + type DialogTriggerProps as TriggerProps, +} from "./Dialog"; diff --git a/packages/react-headless/dialog/src/Dialog.tsx b/packages/react-headless/dialog/src/Dialog.tsx new file mode 100644 index 000000000..2be510526 --- /dev/null +++ b/packages/react-headless/dialog/src/Dialog.tsx @@ -0,0 +1,112 @@ +import { DismissableLayer } from "@radix-ui/react-dismissable-layer"; +import { FocusScope } from "@radix-ui/react-focus-scope"; +import { mergeProps } from "@seed-design/dom-utils"; +import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive"; +import type * as React from "react"; +import { forwardRef } from "react"; +import { Presence } from "./private/Presence"; +import { useDialog, type UseDialogProps } from "./useDialog"; +import { DialogProvider, useDialogContext } from "./useDialogContext"; + +export interface DialogRootProps extends UseDialogProps { + children: React.ReactNode; +} + +export const DialogRoot = (props: DialogRootProps) => { + const { children, ...otherProps } = props; + const api = useDialog(otherProps); + return {children}; +}; + +export interface DialogTriggerProps + extends PrimitiveProps, + React.HTMLAttributes {} + +export const DialogTrigger = forwardRef((props, ref) => { + const api = useDialogContext(); + return ; +}); +DialogTrigger.displayName = "DialogTrigger"; + +export interface DialogPositionerProps + extends PrimitiveProps, + React.HTMLAttributes {} + +export const DialogPositioner = forwardRef((props, ref) => { + const api = useDialogContext(); + return ; +}); + +export interface DialogBackdropProps extends PrimitiveProps, React.HTMLAttributes {} + +// We might need scroll lock here; not needed yet in stackflow based webview. +export const DialogBackdrop = forwardRef((props, ref) => { + const api = useDialogContext(); + return ( + + + + ); +}); +DialogBackdrop.displayName = "DialogBackdrop"; + +export interface DialogContentProps extends PrimitiveProps, React.HTMLAttributes {} + +// TODO: implement DismissableLayer in useDialog instead of radix-ui +export const DialogContent = forwardRef((props, ref) => { + const api = useDialogContext(); + + return ( + + + { + if (!api.closeOnEscape) { + e.preventDefault(); + } + }} + onInteractOutside={(e) => { + if (!api.closeOnInteractOutside) { + e.preventDefault(); + } + }} + onDismiss={() => api.setOpen(false)} + {...mergeProps(api.contentProps, props)} + /> + + + ); +}); +DialogContent.displayName = "DialogContent"; + +export interface DialogTitleProps + extends PrimitiveProps, + React.HTMLAttributes {} + +export const DialogTitle = forwardRef((props, ref) => { + const api = useDialogContext(); + return ; +}); + +export interface DialogDescriptionProps + extends PrimitiveProps, + React.HTMLAttributes {} + +export const DialogDescription = forwardRef( + (props, ref) => { + const api = useDialogContext(); + return ; + }, +); + +export interface DialogCloseButtonProps + extends PrimitiveProps, + React.ButtonHTMLAttributes {} + +export const DialogCloseButton = forwardRef( + (props, ref) => { + const api = useDialogContext(); + return ; + }, +); diff --git a/packages/react-headless/dialog/src/index.ts b/packages/react-headless/dialog/src/index.ts new file mode 100644 index 000000000..eeccac225 --- /dev/null +++ b/packages/react-headless/dialog/src/index.ts @@ -0,0 +1,22 @@ +export { + DialogBackdrop, + DialogCloseButton, + DialogContent, + DialogDescription, + DialogPositioner, + DialogRoot, + DialogTitle, + DialogTrigger, + type DialogBackdropProps, + type DialogCloseButtonProps, + type DialogContentProps, + type DialogDescriptionProps, + type DialogPositionerProps, + type DialogRootProps, + type DialogTitleProps, + type DialogTriggerProps, +} from "./Dialog"; + +export { useDialogContext, type UseDialogContext } from "./useDialogContext"; + +export * as Dialog from "./Dialog.namespace"; diff --git a/packages/react-headless/dialog/src/private/Presence.tsx b/packages/react-headless/dialog/src/private/Presence.tsx new file mode 100644 index 000000000..75ac654e5 --- /dev/null +++ b/packages/react-headless/dialog/src/private/Presence.tsx @@ -0,0 +1,35 @@ +import { composeRefs } from "@radix-ui/react-compose-refs"; +import { Primitive } from "@seed-design/react-primitive"; +import { useRef } from "react"; +import { usePresence } from "./usePresence"; + +export interface PresenceProps { + present: boolean; + unmountOnExit: boolean; + lazyMount: boolean; + children: React.ReactNode; +} + +export const Presence = (props: PresenceProps) => { + const { isPresent, ref } = usePresence(props.present); + const wasEverPresent = useRef(false); + + if (isPresent) { + wasEverPresent.current = true; + } + + const unmounted = + (!isPresent && !wasEverPresent.current && props.lazyMount) || + (props.unmountOnExit && !isPresent && wasEverPresent.current); + + if (unmounted) { + return null; + } + + return ( + + {props.children} + + ); +}; +Presence.displayName = "Presence"; diff --git a/packages/react-headless/dialog/src/private/usePresence.tsx b/packages/react-headless/dialog/src/private/usePresence.tsx new file mode 100644 index 000000000..2244b4e15 --- /dev/null +++ b/packages/react-headless/dialog/src/private/usePresence.tsx @@ -0,0 +1,159 @@ +// This code includes portions derived from radix-ui/primitives (https://github.com/radix-ui/primitives) +// Used under the MIT License: https://opensource.org/licenses/MIT + +import { useLayoutEffect } from "@radix-ui/react-use-layout-effect"; +import * as React from "react"; + +export type UsePresenceReturn = ReturnType; + +export function usePresence(present: boolean) { + const [node, setNode] = React.useState(); + const stylesRef = React.useRef({} as any); + const prevPresentRef = React.useRef(present); + const prevAnimationNameRef = React.useRef("none"); + const initialState = present ? "mounted" : "unmounted"; + const [state, send] = useStateMachine(initialState, { + mounted: { + UNMOUNT: "unmounted", + ANIMATION_OUT: "unmountSuspended", + }, + unmountSuspended: { + MOUNT: "mounted", + ANIMATION_END: "unmounted", + }, + unmounted: { + MOUNT: "mounted", + }, + }); + + React.useEffect(() => { + const currentAnimationName = getAnimationName(stylesRef.current); + prevAnimationNameRef.current = state === "mounted" ? currentAnimationName : "none"; + }, [state]); + + useLayoutEffect(() => { + const styles = stylesRef.current; + const wasPresent = prevPresentRef.current; + const hasPresentChanged = wasPresent !== present; + + if (hasPresentChanged) { + const prevAnimationName = prevAnimationNameRef.current; + const currentAnimationName = getAnimationName(styles); + + if (present) { + send("MOUNT"); + } else if (currentAnimationName === "none" || styles?.display === "none") { + // If there is no exit animation or the element is hidden, animations won't run + // so we unmount instantly + send("UNMOUNT"); + } else { + /** + * When `present` changes to `false`, we check changes to animation-name to + * determine whether an animation has started. We chose this approach (reading + * computed styles) because there is no `animationrun` event and `animationstart` + * fires after `animation-delay` has expired which would be too late. + */ + const isAnimating = prevAnimationName !== currentAnimationName; + + if (wasPresent && isAnimating) { + send("ANIMATION_OUT"); + } else { + send("UNMOUNT"); + } + } + + prevPresentRef.current = present; + } + }, [present, send]); + + useLayoutEffect(() => { + if (node) { + let timeoutId: number; + const ownerWindow = node.ownerDocument.defaultView ?? window; + /** + * Triggering an ANIMATION_OUT during an ANIMATION_IN will fire an `animationcancel` + * event for ANIMATION_IN after we have entered `unmountSuspended` state. So, we + * make sure we only trigger ANIMATION_END for the currently active animation. + */ + const handleAnimationEnd = (event: AnimationEvent) => { + const currentAnimationName = getAnimationName(stylesRef.current); + const isCurrentAnimation = currentAnimationName.includes(event.animationName); + if (event.target === node && isCurrentAnimation) { + // With React 18 concurrency this update is applied a frame after the + // animation ends, creating a flash of visible content. By setting the + // animation fill mode to "forwards", we force the node to keep the + // styles of the last keyframe, removing the flash. + // + // Previously we flushed the update via ReactDom.flushSync, but with + // exit animations this resulted in the node being removed from the + // DOM before the synthetic animationEnd event was dispatched, meaning + // user-provided event handlers would not be called. + // https://github.com/radix-ui/primitives/pull/1849 + send("ANIMATION_END"); + if (!prevPresentRef.current) { + const currentFillMode = node.style.animationFillMode; + node.style.animationFillMode = "forwards"; + // Reset the style after the node had time to unmount (for cases + // where the component chooses not to unmount). Doing this any + // sooner than `setTimeout` (e.g. with `requestAnimationFrame`) + // still causes a flash. + timeoutId = ownerWindow.setTimeout(() => { + if (node.style.animationFillMode === "forwards") { + node.style.animationFillMode = currentFillMode; + } + }); + } + } + }; + const handleAnimationStart = (event: AnimationEvent) => { + if (event.target === node) { + // if animation occurred, store its name as the previous animation. + prevAnimationNameRef.current = getAnimationName(stylesRef.current); + } + }; + node.addEventListener("animationstart", handleAnimationStart); + node.addEventListener("animationcancel", handleAnimationEnd); + node.addEventListener("animationend", handleAnimationEnd); + return () => { + ownerWindow.clearTimeout(timeoutId); + node.removeEventListener("animationstart", handleAnimationStart); + node.removeEventListener("animationcancel", handleAnimationEnd); + node.removeEventListener("animationend", handleAnimationEnd); + }; + } else { + // Transition to the unmounted state if the node is removed prematurely. + // We avoid doing so during cleanup as the node may change but still exist. + send("ANIMATION_END"); + } + }, [node, send]); + + return { + isPresent: ["mounted", "unmountSuspended"].includes(state), + ref: React.useCallback((node: HTMLElement) => { + if (node) stylesRef.current = getComputedStyle(node); + setNode(node); + }, []), + }; +} + +/* -----------------------------------------------------------------------------------------------*/ + +function getAnimationName(styles?: CSSStyleDeclaration) { + return styles?.animationName || "none"; +} + +type Machine = { [k: string]: { [k: string]: S } }; +type MachineState = keyof T; +type MachineEvent = keyof UnionToIntersection; + +// 🤯 https://fettblog.eu/typescript-union-to-intersection/ +type UnionToIntersection = (T extends any ? (x: T) => any : never) extends (x: infer R) => any + ? R + : never; + +function useStateMachine(initialState: MachineState, machine: M & Machine>) { + return React.useReducer((state: MachineState, event: MachineEvent): MachineState => { + const nextState = (machine[state] as any)[event]; + return nextState ?? state; + }, initialState); +} diff --git a/packages/react-headless/dialog/src/private/usePresenceContext.tsx b/packages/react-headless/dialog/src/private/usePresenceContext.tsx new file mode 100644 index 000000000..c599261f8 --- /dev/null +++ b/packages/react-headless/dialog/src/private/usePresenceContext.tsx @@ -0,0 +1,19 @@ +import { createContext, useContext } from "react"; +import type { UsePresenceReturn } from "./usePresence"; + +export interface UsePresenceContext extends UsePresenceReturn {} + +const PresenceContext = createContext(null); + +export const PresenceProvider = PresenceContext.Provider; + +export function usePresenceContext({ + strict = true, +}: { strict?: T } = {}): T extends false ? UsePresenceContext | null : UsePresenceContext { + const context = useContext(PresenceContext); + if (!context && strict) { + throw new Error("usePresenceContext must be used within a Presence"); + } + + return context as UsePresenceContext; +} diff --git a/packages/react-headless/dialog/src/useDialog.ts b/packages/react-headless/dialog/src/useDialog.ts new file mode 100644 index 000000000..18ce1a5bd --- /dev/null +++ b/packages/react-headless/dialog/src/useDialog.ts @@ -0,0 +1,135 @@ +import { useControllableState } from "@radix-ui/react-use-controllable-state"; +import { buttonProps, dataAttr, elementProps } from "@seed-design/dom-utils"; +import { useId, useMemo } from "react"; + +export interface UseDialogStateProps { + open?: boolean; + + defaultOpen?: boolean; + + onOpenChange?: (open: boolean) => void; +} + +function useDialogState(props: UseDialogStateProps) { + const [open = false, onOpenChange] = useControllableState({ + prop: props.open, + defaultProp: props.defaultOpen, + onChange: props.onOpenChange, + }); + + return useMemo(() => ({ open, onOpenChange }), [open, onOpenChange]); +} + +export interface UseDialogProps extends UseDialogStateProps { + /** + * The role of the dialog. + * @default "dialog" + */ + role?: "dialog" | "alertdialog"; + + /** + * Whether to close the dialog when the outside is clicked + * @default true + */ + closeOnInteractOutside?: boolean; + + /** + * Whether to close the dialog when the escape key is pressed + * @default true + */ + closeOnEscape?: boolean; + + /** + * Whether to enable lazy mounting + * @default false + */ + lazyMount?: boolean; + /** + * Whether to unmount on exit. + * @default false + */ + unmountOnExit?: boolean; +} + +export type UseDialogReturn = ReturnType; + +export function useDialog(props: UseDialogProps = {}) { + const { open, onOpenChange } = useDialogState(props); + + const id = useId(); + const titleId = `${id}-title`; + const descriptionId = `${id}-description`; + + const stateProps = useMemo( + () => + elementProps({ + "data-open": dataAttr(open), + "data-hidden": dataAttr(!open), + }), + [open], + ); + + return useMemo( + () => ({ + open, + setOpen: onOpenChange, + closeOnInteractOutside: props.closeOnInteractOutside ?? true, + closeOnEscape: props.closeOnEscape ?? true, + lazyMount: props.lazyMount ?? false, + unmountOnExit: props.unmountOnExit ?? false, + stateProps, + triggerProps: buttonProps({ + "aria-haspopup": "dialog", + "aria-expanded": open, + ...stateProps, + onClick: (e) => { + if (e.defaultPrevented) return; + onOpenChange(true); + }, + }), + positionerProps: elementProps({ + ...stateProps, + style: { + pointerEvents: open ? undefined : "none", + }, + }), + backdropProps: elementProps({ + ...stateProps, + }), + contentProps: elementProps({ + ...stateProps, + role: props.role ?? "dialog", + "aria-modal": true, + "aria-labelledby": titleId, + "aria-describedby": descriptionId, + }), + titleProps: elementProps({ + id: titleId, + ...stateProps, + }), + descriptionProps: elementProps({ + id: descriptionId, + ...stateProps, + }), + closeButtonProps: buttonProps({ + ...stateProps, + onClick: (e) => { + if (e.defaultPrevented) return; + onOpenChange(false); + }, + }), + }), + [ + open, + onOpenChange, + stateProps, + titleId, + descriptionId, + props.role, + props.closeOnInteractOutside, + props.closeOnEscape, + props.lazyMount, + props.unmountOnExit, + ], + ); +} diff --git a/packages/react-headless/dialog/src/useDialogContext.tsx b/packages/react-headless/dialog/src/useDialogContext.tsx new file mode 100644 index 000000000..3ab9ba327 --- /dev/null +++ b/packages/react-headless/dialog/src/useDialogContext.tsx @@ -0,0 +1,19 @@ +import { createContext, useContext } from "react"; +import type { UseDialogReturn } from "./useDialog"; + +export interface UseDialogContext extends UseDialogReturn {} + +const DialogContext = createContext(null); + +export const DialogProvider = DialogContext.Provider; + +export function useDialogContext({ + strict = true, +}: { strict?: T } = {}): T extends false ? UseDialogContext | null : UseDialogContext { + const context = useContext(DialogContext); + if (!context && strict) { + throw new Error("useDialogContext must be used within a Dialog"); + } + + return context as UseDialogContext; +} diff --git a/packages/react-headless/dialog/tsconfig.json b/packages/react-headless/dialog/tsconfig.json new file mode 100644 index 000000000..59fbc0a08 --- /dev/null +++ b/packages/react-headless/dialog/tsconfig.json @@ -0,0 +1,20 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "moduleResolution": "Bundler", + "verbatimModuleSyntax": true, + + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + "noPropertyAccessFromIndexSignature": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + + "rootDir": "src", + "outDir": "lib", + "jsx": "react-jsx" + } +} diff --git a/packages/react-headless/primitive/src/index.tsx b/packages/react-headless/primitive/src/index.tsx index aa6743f3f..41ca8a219 100644 --- a/packages/react-headless/primitive/src/index.tsx +++ b/packages/react-headless/primitive/src/index.tsx @@ -35,6 +35,7 @@ export const Primitive = { textarea: createPrimitive("textarea"), a: createPrimitive("a"), p: createPrimitive("p"), + h2: createPrimitive("h2"), svg: createPrimitive("svg"), circle: createPrimitive("circle"), }; diff --git a/packages/react/package.json b/packages/react/package.json index c80e78ded..fb262372a 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -42,6 +42,7 @@ "@seed-design/dom-utils": "0.0.0-alpha-20241030023710", "@seed-design/react-avatar": "0.0.0-alpha-20241030023710", "@seed-design/react-checkbox": "0.0.0-alpha-20241030023710", + "@seed-design/react-dialog": "0.0.0", "@seed-design/react-popover": "0.0.0-alpha-20241030023710", "@seed-design/react-primitive": "0.0.0", "@seed-design/react-progress": "0.0.0", diff --git a/packages/react/src/components/Dialog/Dialog.namespace.ts b/packages/react/src/components/Dialog/Dialog.namespace.ts new file mode 100644 index 000000000..890d0fa62 --- /dev/null +++ b/packages/react/src/components/Dialog/Dialog.namespace.ts @@ -0,0 +1,22 @@ +export { + DialogBackdrop as Backdrop, + DialogPositioner as Positioner, + DialogContent as Content, + DialogDescription as Description, + DialogFooter as Footer, + DialogHeader as Header, + DialogRoot as Root, + DialogTitle as Title, + DialogTrigger as Trigger, + DialogAction as Action, + type DialogBackdropProps as BackdropProps, + type DialogPositionerProps as PositionerProps, + type DialogContentProps as ContentProps, + type DialogDescriptionProps as DescriptionProps, + type DialogFooterProps as FooterProps, + type DialogHeaderProps as HeaderProps, + type DialogRootProps as RootProps, + type DialogTitleProps as TitleProps, + type DialogTriggerProps as TriggerProps, + type DialogActionProps as ActionProps, +} from "./Dialog"; diff --git a/packages/react/src/components/Dialog/Dialog.tsx b/packages/react/src/components/Dialog/Dialog.tsx new file mode 100644 index 000000000..f7f616f2d --- /dev/null +++ b/packages/react/src/components/Dialog/Dialog.tsx @@ -0,0 +1,94 @@ +import { Dialog as DialogPrimitive, useDialogContext } from "@seed-design/react-dialog"; +import { Primitive, type PrimitiveProps } from "@seed-design/react-primitive"; +import { dialog, type DialogVariantProps } from "@seed-design/recipe/dialog"; +import { forwardRef } from "react"; +import { createStyleContext } from "../../utils/createStyleContext"; +import { createWithStateProps } from "../../utils/createWithStateProps"; + +const { withRootProvider, withContext } = createStyleContext(dialog); +const withStateProps = createWithStateProps([useDialogContext]); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogRootProps extends DialogVariantProps, DialogPrimitive.RootProps {} + +export const DialogRoot = withRootProvider(DialogPrimitive.Root, { + defaultProps: { + lazyMount: true, + unmountOnExit: true, + }, +}); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogTriggerProps extends DialogPrimitive.TriggerProps {} + +export const DialogTrigger = DialogPrimitive.Trigger; + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogPositionerProps extends DialogPrimitive.PositionerProps {} + +export const DialogPositioner = withContext( + DialogPrimitive.Positioner, + "positioner", +); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogBackdropProps extends DialogPrimitive.BackdropProps {} + +export const DialogBackdrop = withContext( + DialogPrimitive.Backdrop, + "backdrop", +); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogContentProps extends DialogPrimitive.ContentProps {} + +export const DialogContent = withContext( + DialogPrimitive.Content, + "content", +); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogHeaderProps extends PrimitiveProps, React.HTMLAttributes {} + +export const DialogHeader = withContext(Primitive.div, "header"); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogTitleProps extends DialogPrimitive.TitleProps {} + +export const DialogTitle = withContext( + withStateProps(Primitive.span), + "title", +); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogDescriptionProps extends DialogPrimitive.DescriptionProps {} + +export const DialogDescription = withContext( + withStateProps(Primitive.div), + "description", +); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogFooterProps extends PrimitiveProps, React.HTMLAttributes {} + +export const DialogFooter = withContext(Primitive.div, "footer"); + +//////////////////////////////////////////////////////////////////////////////////// + +export interface DialogActionProps + extends PrimitiveProps, + React.HTMLAttributes {} + +export const DialogAction = forwardRef((props, ref) => { + const api = useDialogContext(); + return api.setOpen(false)} />; +}); diff --git a/packages/react/src/components/Dialog/index.ts b/packages/react/src/components/Dialog/index.ts new file mode 100644 index 000000000..066fdee84 --- /dev/null +++ b/packages/react/src/components/Dialog/index.ts @@ -0,0 +1,24 @@ +export { + DialogBackdrop, + DialogPositioner, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogRoot, + DialogTitle, + DialogTrigger, + DialogAction, + type DialogBackdropProps, + type DialogPositionerProps, + type DialogContentProps, + type DialogDescriptionProps, + type DialogFooterProps, + type DialogHeaderProps, + type DialogRootProps, + type DialogTitleProps, + type DialogTriggerProps, + type DialogActionProps, +} from "./Dialog"; + +export * as Dialog from "./Dialog.namespace"; diff --git a/packages/react/src/components/index.ts b/packages/react/src/components/index.ts index 587227552..036e6ba95 100644 --- a/packages/react/src/components/index.ts +++ b/packages/react/src/components/index.ts @@ -7,6 +7,7 @@ export * from "./Callout"; export * from "./Checkbox"; export * from "./Columns"; export * from "./ControlChip"; +export * from "./Dialog"; export * from "./ExtendedFab"; export * from "./Fab"; export * from "./Flex"; diff --git a/packages/recipe/lib/dialog.d.ts b/packages/recipe/lib/dialog.d.ts index b7f74fca0..86bf520a2 100644 --- a/packages/recipe/lib/dialog.d.ts +++ b/packages/recipe/lib/dialog.d.ts @@ -1,8 +1,5 @@ declare interface DialogVariant { - /** - * @default horizontal - */ - footerLayout: "horizontal" | "vertical"; + } declare type DialogVariantMap = { @@ -11,7 +8,7 @@ declare type DialogVariantMap = { export declare type DialogVariantProps = Partial; -export declare type DialogSlotName = "backdrop" | "container" | "content" | "header" | "footer" | "action" | "title" | "description"; +export declare type DialogSlotName = "positioner" | "backdrop" | "content" | "header" | "footer" | "action" | "title" | "description"; export declare const dialogVariantMap: DialogVariantMap; diff --git a/packages/recipe/lib/dialog.mjs b/packages/recipe/lib/dialog.mjs index bc720d01c..9df45d6a2 100644 --- a/packages/recipe/lib/dialog.mjs +++ b/packages/recipe/lib/dialog.mjs @@ -4,12 +4,12 @@ import { splitVariantProps } from "./splitVariantProps.mjs"; const dialogSlotNames = [ [ - "backdrop", - "dialog__backdrop" + "positioner", + "dialog__positioner" ], [ - "container", - "dialog__container" + "backdrop", + "dialog__backdrop" ], [ "content", @@ -37,18 +37,11 @@ const dialogSlotNames = [ ] ]; -const defaultVariant = { - "footerLayout": "horizontal" -}; +const defaultVariant = {}; const compoundVariants = []; -export const dialogVariantMap = { - "footerLayout": [ - "horizontal", - "vertical" - ] -}; +export const dialogVariantMap = {}; export const dialogVariantKeys = Object.keys(dialogVariantMap); diff --git a/packages/stackflow/src/Dialog.tsx b/packages/stackflow/src/Dialog.tsx index 572378b9c..3af61f563 100644 --- a/packages/stackflow/src/Dialog.tsx +++ b/packages/stackflow/src/Dialog.tsx @@ -143,7 +143,7 @@ export const DialogRoot = forwardRef((props, re ref={ref} role={role} data-stackflow-component-name="Dialog" - className={clsx(classNames.container, className)} + className={clsx(classNames.positioner, className)} {...otherProps} > {children} diff --git a/packages/stylesheet/dialog.css b/packages/stylesheet/dialog.css index 537b7d902..45d764686 100644 --- a/packages/stylesheet/dialog.css +++ b/packages/stylesheet/dialog.css @@ -1,19 +1,20 @@ +.dialog__positioner { + position: fixed; + display: flex; + justify-content: center; + align-items: center; + inset: 0; + overscroll-behavior-y: none; + --dialog-z-index: 2; + z-index: calc(var(--dialog-z-index) + var(--layer-index, 0)); +} .dialog__backdrop { position: fixed; inset: 0; background: var(--seed-v3-color-bg-overlay); + z-index: calc(var(--dialog-z-index) + var(--layer-index, 0)); } -.dialog__backdrop:is([data-transition-state='exit-active'],[data-transition-state='exit-done']) { - animation: seed-exit; - animation-timing-function: var(--seed-v3-timing-function-exit); - animation-duration: var(--seed-v3-duration-s2); - animation-fill-mode: forwards; - --seed-exit-translate-x: 0; - --seed-exit-translate-y: 0; - --seed-exit-opacity: 0; - --seed-exit-scale: 1; -} -.dialog__backdrop:is([data-transition-state='enter-active'],[data-transition-state='enter-done']) { +.dialog__backdrop:is([data-state="open"], [data-open]) { animation: seed-enter; animation-timing-function: var(--seed-v3-timing-function-enter); animation-duration: var(--seed-v3-duration-s2); @@ -22,12 +23,15 @@ --seed-enter-opacity: 0; --seed-enter-scale: 1; } -.dialog__container { - position: fixed; - display: flex; - justify-content: center; - align-items: center; - inset: 0; +.dialog__backdrop:not(:is([data-state="open"], [data-open])) { + animation: seed-exit; + animation-timing-function: var(--seed-v3-timing-function-exit); + animation-duration: var(--seed-v3-duration-s2); + animation-fill-mode: forwards; + --seed-exit-translate-x: 0; + --seed-exit-translate-y: 0; + --seed-exit-opacity: 0; + --seed-exit-scale: 1; } .dialog__content { position: relative; @@ -36,13 +40,23 @@ flex-direction: column; box-sizing: border-box; word-break: break-all; + z-index: calc(var(--dialog-z-index) + var(--layer-index, 0)); background: var(--seed-v3-color-bg-layer-default); max-width: 272px; margin: auto var(--seed-v3-dimension-s8); padding: var(--seed-v3-dimension-s5) var(--seed-v3-dimension-s5); border-radius: var(--seed-v3-radius-s5); } -.dialog__content:is([data-transition-state='exit-active'],[data-transition-state='exit-done']) { +.dialog__content:is([data-state="open"], [data-open]) { + animation: seed-enter; + animation-timing-function: var(--seed-v3-timing-function-enter-expressive); + animation-duration: var(--seed-v3-duration-s4); + --seed-enter-translate-x: 0; + --seed-enter-translate-y: 0; + --seed-enter-opacity: 0; + --seed-enter-scale: 1.3; +} +.dialog__content:not(:is([data-state="open"], [data-open])) { animation: seed-exit; animation-timing-function: var(--seed-v3-timing-function-exit); animation-duration: var(--seed-v3-duration-s2); @@ -52,15 +66,6 @@ --seed-exit-opacity: 0; --seed-exit-scale: 1; } -.dialog__content:is([data-transition-state='enter-active'],[data-transition-state='enter-done']) { - animation: seed-enter; - animation-timing-function: var(--seed-v3-timing-function-enter-expressive); - animation-duration: var(--seed-v3-duration-s4); - --seed-enter-translate-x: 0; - --seed-enter-translate-y: 0; - --seed-enter-opacity: 0; - --seed-enter-scale: 1.3; -} .dialog__header { display: flex; flex-direction: column; @@ -83,19 +88,7 @@ } .dialog__footer { display: flex; - flex-wrap: wrap; - justify-content: space-between; + flex-direction: column; align-items: stretch; padding-top: var(--seed-v3-dimension-s4); - gap: var(--seed-v3-dimension-s2); -} -.dialog__action { - width: initial; - min-width: calc(50% - var(--seed-v3-dimension-s2) / 2); -} -.dialog__footer--footerLayout_horizontal { - flex-direction: row-reverse; -} -.dialog__footer--footerLayout_vertical { - flex-direction: column; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d84813880..9b38c54de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6006,6 +6006,13 @@ __metadata: languageName: node linkType: hard +"@radix-ui/primitive@npm:1.1.1": + version: 1.1.1 + resolution: "@radix-ui/primitive@npm:1.1.1" + checksum: 10/d7e819177590108b74139809d52ec043c0962ae3513e947998be575fb13639c5c1c091896ddcf1d6a22a777d44ade59d22c2019ce9099607fc62a5de09c59707 + languageName: node + linkType: hard + "@radix-ui/react-accordion@npm:^1.2.1": version: 1.2.1 resolution: "@radix-ui/react-accordion@npm:1.2.1" @@ -6220,6 +6227,29 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-dismissable-layer@npm:^1.1.3": + version: 1.1.3 + resolution: "@radix-ui/react-dismissable-layer@npm:1.1.3" + dependencies: + "@radix-ui/primitive": "npm:1.1.1" + "@radix-ui/react-compose-refs": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.1" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + "@radix-ui/react-use-escape-keydown": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/9905ff3d8d630223fd40bf31cdd8027b6e750cecd31aa04c2a5912e6e628f72973e58032bb944a5f4685dd888256a306a1c296a6e18648187974455a9660d95f + languageName: node + linkType: hard + "@radix-ui/react-focus-guards@npm:1.1.1": version: 1.1.1 resolution: "@radix-ui/react-focus-guards@npm:1.1.1" @@ -6254,6 +6284,27 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-focus-scope@npm:^1.1.1": + version: 1.1.1 + resolution: "@radix-ui/react-focus-scope@npm:1.1.1" + dependencies: + "@radix-ui/react-compose-refs": "npm:1.1.1" + "@radix-ui/react-primitive": "npm:2.0.1" + "@radix-ui/react-use-callback-ref": "npm:1.1.0" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/128508e7e34a47fd44d51bdb3d66a35a337c54b64125548d4a98bb377ee89b2fd8f96e0a075368d393c6664abba1e5a2f167734a6adbb170c41da0aa7a06d05f + languageName: node + linkType: hard + "@radix-ui/react-id@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-id@npm:1.1.0" @@ -6421,6 +6472,25 @@ __metadata: languageName: node linkType: hard +"@radix-ui/react-primitive@npm:2.0.1": + version: 2.0.1 + resolution: "@radix-ui/react-primitive@npm:2.0.1" + dependencies: + "@radix-ui/react-slot": "npm:1.1.1" + peerDependencies: + "@types/react": "*" + "@types/react-dom": "*" + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + checksum: 10/ed6829b8ff4117cde2c02b14325ff78b7902fe9e8324b9fdbfd11646c5bb703f38711d8da5029ffc873384496481b7d398d0e3c17f7cc287b52fb92fbaf67da2 + languageName: node + linkType: hard + "@radix-ui/react-roving-focus@npm:1.1.0": version: 1.1.0 resolution: "@radix-ui/react-roving-focus@npm:1.1.0" @@ -6490,7 +6560,7 @@ __metadata: languageName: node linkType: hard -"@radix-ui/react-slot@npm:^1.1.1": +"@radix-ui/react-slot@npm:1.1.1, @radix-ui/react-slot@npm:^1.1.1": version: 1.1.1 resolution: "@radix-ui/react-slot@npm:1.1.1" dependencies: @@ -7743,6 +7813,22 @@ __metadata: languageName: unknown linkType: soft +"@seed-design/react-dialog@npm:0.0.0, @seed-design/react-dialog@workspace:packages/react-headless/dialog": + version: 0.0.0-use.local + resolution: "@seed-design/react-dialog@workspace:packages/react-headless/dialog" + dependencies: + "@radix-ui/react-dismissable-layer": "npm:^1.1.3" + "@radix-ui/react-focus-scope": "npm:^1.1.1" + "@radix-ui/react-use-controllable-state": "npm:1.1.0" + "@radix-ui/react-use-layout-effect": "npm:^1.1.0" + "@seed-design/dom-utils": "npm:0.0.0-alpha-20241030023710" + nanobundle: "npm:^1.6.0" + peerDependencies: + react: ">=18.0.0" + react-dom: ">=18.0.0" + languageName: unknown + linkType: soft + "@seed-design/react-icon@npm:^0.7.3": version: 0.7.3 resolution: "@seed-design/react-icon@npm:0.7.3" @@ -7921,6 +8007,7 @@ __metadata: "@seed-design/dom-utils": "npm:0.0.0-alpha-20241030023710" "@seed-design/react-avatar": "npm:0.0.0-alpha-20241030023710" "@seed-design/react-checkbox": "npm:0.0.0-alpha-20241030023710" + "@seed-design/react-dialog": "npm:0.0.0" "@seed-design/react-popover": "npm:0.0.0-alpha-20241030023710" "@seed-design/react-primitive": "npm:0.0.0" "@seed-design/react-progress": "npm:0.0.0" @@ -7993,6 +8080,7 @@ __metadata: dependencies: "@daangn/react-monochrome-icon": "npm:^0.0.13" "@radix-ui/react-slot": "npm:^1.1.1" + "@radix-ui/react-use-callback-ref": "npm:^1.1.0" "@seed-design/cli": "npm:0.0.0-alpha-20241204134404" "@seed-design/react": "npm:0.0.0" "@seed-design/react-popover": "npm:0.0.0-alpha-20241030023710" @@ -8002,6 +8090,7 @@ __metadata: "@seed-design/stackflow": "npm:0.0.0" "@seed-design/stylesheet": "npm:3.0.0-alpha-20241212122822" "@seed-design/vars": "npm:0.0.0" + "@stackflow/compat-await-push": "npm:^1.1.13" "@stackflow/core": "npm:^1.1.0" "@stackflow/plugin-basic-ui": "npm:^1.11.1" "@stackflow/plugin-history-sync": "npm:^1.7.0" @@ -8336,6 +8425,18 @@ __metadata: languageName: node linkType: hard +"@stackflow/compat-await-push@npm:^1.1.13": + version: 1.1.13 + resolution: "@stackflow/compat-await-push@npm:1.1.13" + peerDependencies: + "@stackflow/core": ^1.1.0-canary.0 + "@stackflow/react": ^1.3.2-canary.0 + "@types/react": ">=16.8.0" + react: ">=16.8.0" + checksum: 10/ca498d65533f88ce88b70b3f58806397b4c9fcea2857ce085ae3e506cbb9083ab2cb7951deac5ca92f78fbeddc8cc48180b3bf9d27ef175cda88f37b65c7e05b + languageName: node + linkType: hard + "@stackflow/config@npm:^1.2.0": version: 1.2.0 resolution: "@stackflow/config@npm:1.2.0"