diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index e1329f8a659..3fa42b8232c 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -16,6 +16,7 @@ "@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0", + "@radix-ui/react-toast": "^1.2.1", "@radix-ui/react-tooltip": "^1.1.2", "@webiny/react-composition": "0.0.0", "@webiny/utils": "0.0.0", diff --git a/packages/admin-ui/src/Providers/Providers.tsx b/packages/admin-ui/src/Providers/Providers.tsx index 194ca280f26..9f5ffc94289 100644 --- a/packages/admin-ui/src/Providers/Providers.tsx +++ b/packages/admin-ui/src/Providers/Providers.tsx @@ -1,4 +1,5 @@ import React from "react"; +import { ToastProvider, ToastViewport } from "~/Toast"; import { TooltipProvider } from "~/Tooltip"; export interface ProvidersProps { @@ -6,5 +7,10 @@ export interface ProvidersProps { } export const Providers = ({ children }: ProvidersProps) => { - return {children}; + return ( + + {children} + + + ); }; diff --git a/packages/admin-ui/src/Toast/Toast.stories.tsx b/packages/admin-ui/src/Toast/Toast.stories.tsx new file mode 100644 index 00000000000..fbc18ce4c42 --- /dev/null +++ b/packages/admin-ui/src/Toast/Toast.stories.tsx @@ -0,0 +1,111 @@ +import React from "react"; +import { ReactComponent as SettingsIcon } from "@material-design-icons/svg/outlined/settings.svg"; +import type { Meta, StoryObj } from "@storybook/react"; +import { + Toast, + ToastProvider, + ToastViewport, + ToastAction, + ToastTitle, + ToastDescription +} from "./Toast"; +import { Button } from "~/Button"; + +const meta: Meta = { + title: "Components/Toast", + component: Toast, + tags: ["autodocs"], + parameters: { + layout: "fullscreen" + }, + argTypes: { + // Note: after upgrading to Storybook 8.X, use `fn`from `@storybook/test` to spy on the onOpenChange argument. + onOpenChange: { action: "onOpenChange" } + }, + decorators: [ + (Story, context) => { + const { args } = context; + const [open, setOpen] = React.useState(false); + const timerRef = React.useRef(0); + + React.useEffect(() => { + return () => clearTimeout(timerRef.current); + }, []); + + return ( + +
+
+
+ ); + } + ] +}; + +export default meta; + +type Story = StoryObj; + +export const Default: Story = { + args: { + title: + } +}; + +export const SubtleVariant: Story = { + args: { + ...Default.args, + variant: "subtle" + } +}; + +export const WithDescription: Story = { + args: { + ...Default.args, + description: + } +}; + +export const WithActions: Story = { + args: { + ...Default.args, + actions: [ + console.log("open", e)} + /> + ] + } +}; + +export const WithCustomIcon: Story = { + args: { + ...Default.args, + icon: + } +}; diff --git a/packages/admin-ui/src/Toast/Toast.tsx b/packages/admin-ui/src/Toast/Toast.tsx new file mode 100644 index 00000000000..3f19be848dd --- /dev/null +++ b/packages/admin-ui/src/Toast/Toast.tsx @@ -0,0 +1,203 @@ +import * as React from "react"; +import { ReactComponent as CloseIcon } from "@material-design-icons/svg/outlined/close.svg"; +import { ReactComponent as NotificationsIcon } from "@material-design-icons/svg/outlined/notifications_active.svg"; +import * as ToastPrimitives from "@radix-ui/react-toast"; +import { makeDecoratable } from "@webiny/react-composition"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "~/utils"; +import { Heading } from "~/Heading"; +import { Text } from "~/Text"; +import { Button } from "~/Button"; + +const ToastProvider = ToastPrimitives.Provider; + +/** + * Toast Viewport + */ +const ToastViewportBase = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +ToastViewportBase.displayName = ToastPrimitives.Viewport.displayName; + +const ToastViewport = makeDecoratable("ToastViewport", ToastViewportBase); + +/** + * Toast Root + */ +const toastVariants = cva( + "group pointer-events-auto relative flex w-full items-start justify-start p-md gap-sm-extra self-stretch overflow-hidden rounded-md border-sm border-neutral-dimmed shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full", + { + variants: { + variant: { + default: "bg-neutral-dark text-neutral-light fill-neutral-base", + subtle: "bg-white fill-neutral-xstrong" + } + }, + defaultVariants: { + variant: "default" + } + } +); + +const ToastRootBase = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & VariantProps +>(({ className, variant, ...props }, ref) => { + return ( + + ); +}); +ToastRootBase.displayName = ToastPrimitives.Root.displayName; + +const ToastRoot = makeDecoratable("ToastRoot", ToastRootBase); + +/** + * Toast Action + */ +type ToastActionProps = ToastPrimitives.ToastActionProps & { + text: React.ReactNode; +}; + +const ToastActionBase = React.forwardRef< + React.ElementRef, + ToastActionProps +>(({ onClick, text, ...props }, ref) => ( + +