diff --git a/packages/sdk-components-react-radix/package.json b/packages/sdk-components-react-radix/package.json index d60459e0490b..952525fb0218 100644 --- a/packages/sdk-components-react-radix/package.json +++ b/packages/sdk-components-react-radix/package.json @@ -59,10 +59,12 @@ "@radix-ui/react-switch": "^1.1.1", "@radix-ui/react-tabs": "^1.1.1", "@radix-ui/react-tooltip": "^1.1.4", + "@radix-ui/react-use-controllable-state": "^1.1.0", "@webstudio-is/css-engine": "workspace:*", "@webstudio-is/icons": "workspace:*", "@webstudio-is/react-sdk": "workspace:*", - "@webstudio-is/sdk": "workspace:*" + "@webstudio-is/sdk": "workspace:*", + "await-interaction-response": "^0.0.2" }, "devDependencies": { "@types/node": "^22.9.3", diff --git a/packages/sdk-components-react-radix/src/dialog.tsx b/packages/sdk-components-react-radix/src/dialog.tsx index 2541bcc822ef..af249abd2b5b 100644 --- a/packages/sdk-components-react-radix/src/dialog.tsx +++ b/packages/sdk-components-react-radix/src/dialog.tsx @@ -7,6 +7,7 @@ import { useEffect, useRef, useContext, + useCallback, } from "react"; import * as DialogPrimitive from "@radix-ui/react-dialog"; import { @@ -14,6 +15,8 @@ import { getClosestInstance, type Hook, } from "@webstudio-is/react-sdk/runtime"; +import { useControllableState } from "@radix-ui/react-use-controllable-state"; +import interactionResponse from "await-interaction-response"; /** * Naive heuristic to determine if a click event will cause navigate @@ -49,9 +52,22 @@ export const Dialog = forwardRef< HTMLDivElement, Omit, "defaultOpen"> >((props, _ref) => { - const { open, onOpenChange } = props; const { renderer } = useContext(ReactSdkContext); + const [open, onOpenChange] = useControllableState({ + prop: props.open, + defaultProp: false, + onChange: props.onOpenChange, + }); + + const onOpenChangeHandler = useCallback( + async (open: boolean) => { + await interactionResponse(); + onOpenChange(open); + }, + [onOpenChange] + ); + /** * Close the dialog when a navigable link within it is clicked. */ @@ -76,15 +92,21 @@ export const Dialog = forwardRef< } if (target.closest('[role="dialog"]')) { - onOpenChange?.(false); + onOpenChangeHandler?.(false); } }; window.addEventListener("click", handleClick); return () => window.removeEventListener("click", handleClick); - }, [open, onOpenChange, renderer]); + }, [open, onOpenChangeHandler, renderer]); - return ; + return ( + + ); }); /** diff --git a/packages/sdk-components-react-radix/src/tabs.tsx b/packages/sdk-components-react-radix/src/tabs.tsx index b0183d57b1da..4e3f771f6021 100644 --- a/packages/sdk-components-react-radix/src/tabs.tsx +++ b/packages/sdk-components-react-radix/src/tabs.tsx @@ -1,20 +1,43 @@ -import { - type ComponentPropsWithoutRef, - type ForwardRefExoticComponent, - forwardRef, - type ComponentProps, - type RefAttributes, -} from "react"; +import { type ComponentPropsWithoutRef, forwardRef, useCallback } from "react"; import { Root, List, Trigger, Content } from "@radix-ui/react-tabs"; import { getClosestInstance, getIndexWithinAncestorFromComponentProps, type Hook, } from "@webstudio-is/react-sdk/runtime"; +import { useControllableState } from "@radix-ui/react-use-controllable-state"; +import interactionResponse from "await-interaction-response"; + +export const Tabs = forwardRef< + HTMLDivElement, + Omit, "value" | "onValueChange"> & { + value?: string; + onValueChange?: (value: string) => void; + } +>(({ defaultValue, ...props }, ref) => { + const [value, onValueChange] = useControllableState({ + prop: props.value, + defaultProp: defaultValue, + onChange: props.onValueChange, + }); -export const Tabs: ForwardRefExoticComponent< - Omit, "asChild"> & RefAttributes -> = Root; + const handleValueChange = useCallback( + async (value: string) => { + await interactionResponse(); + onValueChange(value); + }, + [onValueChange] + ); + + return ( + + ); +}); export const TabsList = List; diff --git a/packages/sdk-components-react/package.json b/packages/sdk-components-react/package.json index ceaec2aaa469..c1271c32c0a3 100644 --- a/packages/sdk-components-react/package.json +++ b/packages/sdk-components-react/package.json @@ -52,7 +52,8 @@ "@webstudio-is/react-sdk": "workspace:*", "@webstudio-is/sdk": "workspace:*", "colord": "^2.9.3", - "micromark": "^4.0.0" + "micromark": "^4.0.0", + "await-interaction-response": "^0.0.2" }, "devDependencies": { "@testing-library/react": "^14.2.2", diff --git a/packages/sdk-components-react/src/vimeo-play-button.tsx b/packages/sdk-components-react/src/vimeo-play-button.tsx index e12e440b09c3..0567d6eb7249 100644 --- a/packages/sdk-components-react/src/vimeo-play-button.tsx +++ b/packages/sdk-components-react/src/vimeo-play-button.tsx @@ -3,9 +3,11 @@ import { type ElementRef, type ComponentProps, useContext, + useCallback, } from "react"; import { VimeoContext } from "./vimeo"; import { Button, defaultTag } from "./button"; +import interactionResponse from "await-interaction-response"; export { defaultTag }; @@ -14,10 +16,17 @@ type Props = ComponentProps; export const VimeoPlayButton = forwardRef, Props>( (props, ref) => { const vimeoContext = useContext(VimeoContext); + + const handleClick = useCallback(async () => { + await interactionResponse(); + vimeoContext.onInitPlayer(); + }, [vimeoContext]); + if (vimeoContext.status !== "initial") { return; } - return