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 000000000..7bfd44d43
Binary files /dev/null and b/.yarn/cache/@radix-ui-primitive-npm-1.1.1-758e8c9172-d7e8191775.zip differ
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 000000000..5a5a3edf5
Binary files /dev/null and b/.yarn/cache/@radix-ui-react-dismissable-layer-npm-1.1.3-2114a37d20-9905ff3d8d.zip differ
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 000000000..61f6a115e
Binary files /dev/null and b/.yarn/cache/@radix-ui-react-focus-scope-npm-1.1.1-eaf894ac65-128508e7e3.zip differ
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 000000000..ced2008b4
Binary files /dev/null and b/.yarn/cache/@radix-ui-react-primitive-npm-2.0.1-a63c88e534-ed6829b8ff.zip differ
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 000000000..de18538b9
Binary files /dev/null and b/.yarn/cache/@stackflow-compat-await-push-npm-1.1.13-5582c8a4fe-ca498d6553.zip differ
diff --git a/docs/components/example/alert-dialog-danger.tsx b/docs/components/example/alert-dialog-danger.tsx
index 218478597..32b0dce96 100644
--- a/docs/components/example/alert-dialog-danger.tsx
+++ b/docs/components/example/alert-dialog-danger.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 AlertDialogDangerActivity = () => {
+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) => (
-
- ))}
-
-
-
-
- );
-};
-
-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"