diff --git a/.changeset/hungry-maps-end.md b/.changeset/hungry-maps-end.md new file mode 100644 index 00000000..0ad8603f --- /dev/null +++ b/.changeset/hungry-maps-end.md @@ -0,0 +1,5 @@ +--- +"@animareflection/ui": minor +--- + +Update `Toast` components to use sonner diff --git a/bun.lockb b/bun.lockb index 22ccc274..929e7852 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/docs/usage.md b/docs/usage.md index 6a6a1bd5..3560ee2d 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -59,7 +59,7 @@ Now you are ready to install the UI library. You can either install it [from the ## Remote -Install from remote repository along with required dependencies: `bun add @animareflection/ui @ark-ui/react framer-motion react-hot-toast` +Install from remote repository along with required dependencies: `bun add @animareflection/ui @ark-ui/react framer-motion sonner` ## Local diff --git a/package.json b/package.json index 0698e81e..d0dfc96b 100644 --- a/package.json +++ b/package.json @@ -110,8 +110,8 @@ "radash": "^11.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-hot-toast": "^2.4.1", "react-icons": "^4.12.0", + "sonner": "^1.3.1", "storybook": "^7.6.6", "storybook-dark-mode": "^3.0.3", "ts-node": "^10.9.2", diff --git a/src/components/core/Toast/Toast.recipe.ts b/src/components/core/Toast/Toast.recipe.ts index f219b81f..8afa41e0 100644 --- a/src/components/core/Toast/Toast.recipe.ts +++ b/src/components/core/Toast/Toast.recipe.ts @@ -3,15 +3,24 @@ import { defineSlotRecipe } from "@pandacss/dev"; export const toastRecipe = defineSlotRecipe({ className: "toast", description: "The styles for the Toast component", - slots: ["closeTrigger", "title", "description"], + slots: ["root", "closeTrigger", "title", "description"], base: { + root: { + display: "flex", + flexDirection: "column", + borderRadius: "md", + borderWidth: "1px", + boxShadow: "sm", + minW: "300px", + px: 5, + py: 2, + }, closeTrigger: { display: "flex", position: "absolute", cursor: "pointer", - color: "fg.muted", - top: 2, - right: 2, + top: 3, + right: 3, _hover: { opacity: 0.8, }, @@ -21,34 +30,75 @@ export const toastRecipe = defineSlotRecipe({ }, description: { fontSize: "sm", - color: "fg.muted", }, }, + defaultVariants: { + variant: "unstyled", + }, variants: { variant: { + unstyled: { + root: { + bgColor: "bg.default", + borderColor: "border.default", + }, + closeTrigger: { + color: "fg.muted", + }, + description: { + color: "fg.muted", + }, + title: { + color: "fg.default", + }, + }, success: { + root: { + bgColor: { base: "green.50", _dark: "green.950" }, + borderColor: { base: "green.200", _dark: "green.800" }, + }, closeTrigger: { color: "green.500", }, description: { color: "green.500", }, + title: { + color: { base: "green.950", _dark: "green.50" }, + }, }, error: { + root: { + bgColor: { base: "red.50", _dark: "red.950" }, + borderColor: { base: "red.200", _dark: "red.800" }, + }, closeTrigger: { color: "red.500", }, description: { color: "red.500", }, + title: { + color: { base: "red.950", _dark: "red.50" }, + }, }, loading: { + root: { + bgColor: { base: "brand.primary.50", _dark: "brand.primary.950" }, + borderColor: { + base: "brand.primary.200", + _dark: "brand.primary.800", + }, + }, closeTrigger: { color: "brand.primary.500", }, description: { color: "brand.primary.500", }, + title: { + color: { base: "brand.primary.950", _dark: "brand.primary.50" }, + }, }, }, }, diff --git a/src/components/core/Toast/Toast.tsx b/src/components/core/Toast/Toast.tsx index fa4d1238..882328c0 100644 --- a/src/components/core/Toast/Toast.tsx +++ b/src/components/core/Toast/Toast.tsx @@ -1,12 +1,17 @@ import { FiX } from "react-icons/fi"; import Icon from "components/core/Icon/Icon"; -import { Flex, panda } from "generated/panda/jsx"; +import { + PrimitiveToast, + PrimitiveToastClose, + PrimitiveToastDescription, + PrimitiveToastTitle, +} from "components/primitives"; -import type { FlexProps } from "generated/panda/jsx"; +import type { PrimitiveToastProps } from "components/primitives"; import type { ToastVariantProps } from "generated/panda/recipes"; -export interface Props extends FlexProps, ToastVariantProps { +export interface Props extends PrimitiveToastProps, ToastVariantProps { title: string; description?: string; onClose?: () => void; @@ -16,9 +21,9 @@ export interface Props extends FlexProps, ToastVariantProps { * Core UI toast. */ const Toast = ({ title, description, onClose, ...rest }: Props) => ( - + {onClose && ( - ( - + )} - {title} - {description} - + {title} + {description && ( + {description} + )} + ); export default Toast; diff --git a/src/components/core/Toast/Toaster.stories.tsx b/src/components/core/Toast/Toaster.stories.tsx index 3df7670f..6988e508 100644 --- a/src/components/core/Toast/Toaster.stories.tsx +++ b/src/components/core/Toast/Toaster.stories.tsx @@ -1,11 +1,13 @@ -import { default as toast } from "react-hot-toast"; +import { toast } from "sonner"; import { toastState } from "./Toast.spec"; import { Button, Toast, Toaster } from "components/core"; import { Flex, Grid } from "generated/panda/jsx"; import type { Meta, StoryObj } from "@storybook/react"; -import type { ToastPosition } from "react-hot-toast"; +import type { ToastT } from "sonner"; + +type Position = ToastT["position"]; type Story = StoryObj; @@ -18,7 +20,6 @@ const notify = () => description="toast description" onClose={closeToast} />, - { icon: "🏝" }, ); const success = () => toast.success( @@ -59,12 +60,17 @@ const promise = () => { ); }; -const PositionTemplate = ({ position }: { position: ToastPosition }) => { +const PositionTemplate = ({ position }: { position: Position }) => { const showToastPosition = () => - toast(`Position set to ${position}`, { position }); + toast(, { position }); return ( - ); @@ -119,7 +125,7 @@ export const ToastState: Story = { tags: ["test"], }; -const meta = { +const meta: Meta = { title: "Components/Core/Toaster", component: Toaster, tags: ["autodocs"], diff --git a/src/components/core/Toast/Toaster.tsx b/src/components/core/Toast/Toaster.tsx index 29477e98..0de2a584 100644 --- a/src/components/core/Toast/Toaster.tsx +++ b/src/components/core/Toast/Toaster.tsx @@ -1,51 +1,16 @@ -import { Toaster as ReactHotToaster } from "react-hot-toast"; +import { Toaster as SonnerToaster } from "sonner"; -import type { ToasterProps } from "react-hot-toast"; +import type { ComponentProps } from "react"; -export interface Props extends ToasterProps {} +export interface Props extends ComponentProps {} /** * Core UI toaster. */ const Toaster = ({ ...props }: Props) => ( - diff --git a/src/components/primitives/Toast/Toast.tsx b/src/components/primitives/Toast/Toast.tsx new file mode 100644 index 00000000..fe8669ac --- /dev/null +++ b/src/components/primitives/Toast/Toast.tsx @@ -0,0 +1,34 @@ +import { panda } from "generated/panda/jsx"; +import { toast } from "generated/panda/recipes"; +import { createStyleContext } from "lib/util"; + +import type { PandaComponent } from "generated/panda/types/jsx"; +import type { ComponentProps } from "react"; + +const { withProvider, withContext } = createStyleContext(toast); + +/** + * Core UI toast primitives. + */ +export type PrimitiveToastProps = ComponentProps; +const PrimitiveToast: PandaComponent = withProvider( + panda.div, + "root", +); + +export type PrimitiveToastTitleProps = ComponentProps< + typeof PrimitiveToastTitle +>; +export const PrimitiveToastTitle = withContext(panda.p, "title"); + +export type PrimitiveToastDescriptionProps = ComponentProps< + typeof PrimitiveToastDescription +>; +export const PrimitiveToastDescription = withContext(panda.p, "description"); + +export type PrimitiveToastCloseProps = ComponentProps< + typeof PrimitiveToastClose +>; +export const PrimitiveToastClose = withContext(panda.button, "closeTrigger"); + +export default PrimitiveToast; diff --git a/src/components/primitives/index.ts b/src/components/primitives/index.ts index 13595f51..0a961ccd 100644 --- a/src/components/primitives/index.ts +++ b/src/components/primitives/index.ts @@ -46,6 +46,9 @@ export * from "./Tabs/Tabs"; export { default as PrimitiveTextarea } from "./Textarea/Textarea"; export * from "./Textarea/Textarea"; +export { default as PrimitiveToast } from "./Toast/Toast"; +export * from "./Toast/Toast"; + export { default as PrimitiveToggle } from "./Toggle/Toggle"; export * from "./Toggle/Toggle"; diff --git a/src/components/utility/CopyToClipboard/CopyToClipboard.tsx b/src/components/utility/CopyToClipboard/CopyToClipboard.tsx index e3832abc..f123e876 100644 --- a/src/components/utility/CopyToClipboard/CopyToClipboard.tsx +++ b/src/components/utility/CopyToClipboard/CopyToClipboard.tsx @@ -54,7 +54,7 @@ const CopyToClipboard = ({ > {children} {icon && ( - + )} diff --git a/src/index.ts b/src/index.ts index 22e65756..2ea363b7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,9 +8,8 @@ export * from "generated/panda/css"; export * from "generated/panda/jsx"; export type { JsxStyleProps } from "generated/panda/types"; -// export backfill of react hot toast functions and types -export { default as toast } from "react-hot-toast"; -export type { ToastPosition } from "react-hot-toast"; +// export backfill of sonner functions +export { toast } from "sonner"; // export Panda preset (to be used in downstream Panda configurations) export { default as anirefPreset } from "lib/panda/aniref.preset"; diff --git a/tsup.config.ts b/tsup.config.ts index c60e8e6b..e3d4fe3a 100644 --- a/tsup.config.ts +++ b/tsup.config.ts @@ -32,13 +32,7 @@ const tsupConfig = defineTsupConfig({ format: ["cjs", "esm"], // NB: `peerDeps`, among others, are excluded (marked external) by default // see https://tsup.egoist.dev/#excluding-packages - external: [ - "@ark-ui/react", - "react-icons", - "next", - "framer-motion", - "react-hot-toast", - ], + external: ["@ark-ui/react", "react-icons", "next", "framer-motion", "sonner"], outDir: "build", esbuildOptions: (opt, _ctx) => { // https://esbuild.github.io/api/#resolve-extensions