From 29b389125c1fafc78dd6401a1afbf1e5c925f0a2 Mon Sep 17 00:00:00 2001 From: Jules Belveze <32683010+JulesBelveze@users.noreply.github.com> Date: Thu, 28 Nov 2024 13:13:04 +0100 Subject: [PATCH] [sparkle] - feat: `Sheet` (#8943) * [sparkle] - feature: add 'tale' box shadow configuration to tailwind - Introduced a new box shadow option 'tale' for use within UI components * [sparkle] - feature: implement Sheet component with theming and accessibility - Added Sheet component leveraging Radix UI for accessibility - Integrated class-variance-authority for managing className variants - Provided size variants for Sheet content with responsive design support - Included components for Sheet's header, container, footer, title, and description for a complete UI unit - Ensured theming consistency and transition animations for open/close states * [sparkle] - feature: add Sheet component stories for Storybook - Introduce Storybook stories for the Sheet component showcasing various usage scenarios. - Provide examples including SheetDemo, ContentDemo, and SheetCustom to illustrate different configurations and contents. * [sparkle] - feature: add Sheet component exports to component library - Make the Suite of Sheet components available for import - Expand the UI component library with modals and overlays functionality * [sparkle] - feature: add radix-ui react-dialog dependency - New dependency `@radix-ui/react-dialog` added for dialog components in the UI - Included necessary peer dependencies and sub-dependencies for proper integration of the dialog feature * [sparkle] - feature: enhance Sheet component with flexible headers and footers - Extend SheetHeader to support optional close button through hideButton prop - SheetFooter now accepts props for left and right buttons and can render children - Ensure consistent export formatting and end of file newlines in component index - Improve Sheet story to demonstrate new header and footer capabilities * [sparkle] - feature: bump package version to 0.2.326 - Updated the package version in both package.json and package-lock.json for a new release - Ensure the latest features and fixes are included in the new package version * [sparkle] - fix: adjust button wrapping based on disabled state - Buttons now render directly without wrapping in SheetClose if disabled - Ensures correct visual and interaction pattern for disabled state buttons in SheetFooter * [sparkle] - refactor: use composability for SheetClose with Button - Wrap Button components in SheetClose with `asChild` prop to leverage composability - Ensure SheetClose component can act as a parent without affecting button functionality --- sparkle/package-lock.json | 285 +++++++++++++++++++- sparkle/package.json | 3 +- sparkle/src/components/Sheet.tsx | 188 +++++++++++++ sparkle/src/components/index.ts | 13 + sparkle/src/stories/Sheet.stories.tsx | 373 ++++++++++++++++++++++++++ sparkle/tailwind.config.js | 1 + 6 files changed, 860 insertions(+), 3 deletions(-) create mode 100644 sparkle/src/components/Sheet.tsx create mode 100644 sparkle/src/stories/Sheet.stories.tsx diff --git a/sparkle/package-lock.json b/sparkle/package-lock.json index 64ab778fe32d..b8a6c555a2f0 100644 --- a/sparkle/package-lock.json +++ b/sparkle/package-lock.json @@ -1,18 +1,19 @@ { "name": "@dust-tt/sparkle", - "version": "0.2.325", + "version": "0.2.326", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dust-tt/sparkle", - "version": "0.2.325", + "version": "0.2.326", "license": "ISC", "dependencies": { "@emoji-mart/data": "^1.1.2", "@emoji-mart/react": "^1.1.1", "@headlessui/react": "^1.7.19", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", @@ -3790,6 +3791,286 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz", + "integrity": "sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-context": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.1", + "@radix-ui/react-focus-guards": "1.1.1", + "@radix-ui/react-focus-scope": "1.1.0", + "@radix-ui/react-id": "1.1.0", + "@radix-ui/react-portal": "1.1.2", + "@radix-ui/react-presence": "1.1.1", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-slot": "1.1.0", + "@radix-ui/react-use-controllable-state": "1.1.0", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.6.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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz", + "integrity": "sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==" + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz", + "integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-context": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz", + "integrity": "sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.1.tgz", + "integrity": "sha512-QSxg29lfr/xcev6kSz7MAlmDnzbP1eI/Dwn3Tp1ip0KT5CUELsxkekFEMVBEoykI3oV39hKT4TKZzBNMbcTZYQ==", + "dependencies": { + "@radix-ui/primitive": "1.1.0", + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "1.1.0", + "@radix-ui/react-use-escape-keydown": "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz", + "integrity": "sha512-pSIwfrT1a6sIoDASCSpFwOasEwKTZWDw/iBdtnqKO7v6FeOzYJ7U53cPzYFVR3geGGXgVHaH+CdngrrAzqUGxg==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz", + "integrity": "sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.0", + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-callback-ref": "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-id": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.0.tgz", + "integrity": "sha512-EJUrI8yYh7WOjNOqpoJaf1jlFIH2LvtgAl+YcFqNCa+4hj64ZXmPkAKOFs/ukjz3byN6bdb/AVUqHkI8/uWWMA==", + "dependencies": { + "@radix-ui/react-use-layout-effect": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz", + "integrity": "sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==", + "dependencies": { + "@radix-ui/react-primitive": "2.0.0", + "@radix-ui/react-use-layout-effect": "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz", + "integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==", + "dependencies": { + "@radix-ui/react-slot": "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 + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz", + "integrity": "sha512-CasTfvsy+frcFkbXtSJ2Zu9JHpN8TYKxkgJGWbjiZhFivxaeW7rMeZt7QELGVLaYVfFMsKHjb7Ak0nMEe+2Vfw==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.1.0.tgz", + "integrity": "sha512-MtfMVJiSr2NjzS0Aa90NPTnvTSg6C/JLCV7ma0W6+OMV78vd8OyRpID+Ng9LxzsPbLeuBnWBA1Nq30AtBIDChw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.0.tgz", + "integrity": "sha512-L7vwWlR1kTTQ3oh7g1O0CBF3YCyyTj8NmhLR+phShpyA50HCfBFKVJTpshm9PzLiKmehsrQzTYTpX9HvmC9rhw==", + "dependencies": { + "@radix-ui/react-use-callback-ref": "1.1.0" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.0.tgz", + "integrity": "sha512-+FPE0rOdziWSrH9athwI1R0HDVbWlEhd+FR+aSDk4uWGmSJ9Z54sdZVDQPZAinJhJXwfT+qnj969mCsT2gfm5w==", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz", + "integrity": "sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.6", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", diff --git a/sparkle/package.json b/sparkle/package.json index 1d6340d7cc7d..a7e561363e4e 100644 --- a/sparkle/package.json +++ b/sparkle/package.json @@ -1,6 +1,6 @@ { "name": "@dust-tt/sparkle", - "version": "0.2.325", + "version": "0.2.326", "scripts": { "build": "rm -rf dist && npm run tailwind && npm run build:esm && npm run build:cjs", "tailwind": "tailwindcss -i ./src/styles/tailwind.css -o dist/sparkle.css", @@ -98,6 +98,7 @@ "@emoji-mart/react": "^1.1.1", "@headlessui/react": "^1.7.19", "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", "@radix-ui/react-dropdown-menu": "^2.1.2", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-popover": "^1.1.2", diff --git a/sparkle/src/components/Sheet.tsx b/sparkle/src/components/Sheet.tsx new file mode 100644 index 000000000000..47baacda54cc --- /dev/null +++ b/sparkle/src/components/Sheet.tsx @@ -0,0 +1,188 @@ +import * as SheetPrimitive from "@radix-ui/react-dialog"; +import { cva } from "class-variance-authority"; +import * as React from "react"; + +import { Button, ScrollArea } from "@sparkle/components"; +import { XMarkIcon } from "@sparkle/icons"; +import { cn } from "@sparkle/lib/utils"; + +const Sheet = SheetPrimitive.Root; + +const SheetTrigger = SheetPrimitive.Trigger; + +const SheetClose = SheetPrimitive.Close; + +const SheetPortal = SheetPrimitive.Portal; + +const SheetOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetOverlay.displayName = SheetPrimitive.Overlay.displayName; + +const SHEET_SIZES = ["md", "lg", "xl"] as const; + +type SheetSizeType = (typeof SHEET_SIZES)[number]; + +const sizeClasses: Record = { + md: "sm:s-max-w-md", + lg: "sm:s-max-w-xl", + xl: "sm:s-max-w-3xl", +}; + +const sheetVariants = cva( + "s-fixed s-z-50 s-overflow-hidden s-bg-background s-transition s-ease-in-out data-[state=open]:s-animate-in data-[state=closed]:s-animate-out data-[state=closed]:s-duration-300 data-[state=open]:s-duration-500 s-flex s-flex-col s-inset-y-0 s-right-0 s-h-full s-w-full data-[state=closed]:s-slide-out-to-right data-[state=open]:s-slide-in-from-right", + { + variants: { + size: sizeClasses, + }, + defaultVariants: { + size: "md", + }, + } +); + +interface SheetContentProps + extends React.ComponentPropsWithoutRef { + size?: SheetSizeType; +} + +const SheetContent = React.forwardRef< + React.ElementRef, + SheetContentProps +>(({ className, children, size, ...props }, ref) => ( + + + + {children} + + +)); +SheetContent.displayName = SheetPrimitive.Content.displayName; + +interface SheetHeaderProps extends React.HTMLAttributes { + hideButton?: boolean; +} + +const SheetHeader = ({ + className, + children, + hideButton = false, + ...props +}: SheetHeaderProps) => ( +
+ {children} + + {!hideButton &&
+); +SheetHeader.displayName = "SheetHeader"; + +const SheetContainer = ({ children }: React.HTMLAttributes) => ( + +
+ {children} +
+
+); +SheetContainer.displayName = "SheetContainer"; + +interface SheetFooterProps extends React.HTMLAttributes { + leftButtonProps?: React.ComponentProps; + rightButtonProps?: React.ComponentProps; + sheetCloseClassName?: string; +} + +const SheetFooter = ({ + className, + children, + leftButtonProps, + rightButtonProps, + sheetCloseClassName, + ...props +}: SheetFooterProps) => ( +
+ {leftButtonProps && + (leftButtonProps.disabled ? ( +
+); +SheetFooter.displayName = "SheetFooter"; + +const SheetTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetTitle.displayName = SheetPrimitive.Title.displayName; + +const SheetDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +SheetDescription.displayName = SheetPrimitive.Description.displayName; + +export { + Sheet, + SheetClose, + SheetContainer, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetOverlay, + SheetPortal, + SheetTitle, + SheetTrigger, +}; diff --git a/sparkle/src/components/index.ts b/sparkle/src/components/index.ts index e4a60c56d757..8fbaaa7cf221 100644 --- a/sparkle/src/components/index.ts +++ b/sparkle/src/components/index.ts @@ -90,6 +90,19 @@ export { RainbowEffect } from "./RainbowEffect"; export { ScrollArea, ScrollBar } from "./ScrollArea"; export { SearchInput } from "./SearchInput"; export { Separator } from "./Separator"; +export { + Sheet, + SheetClose, + SheetContainer, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetOverlay, + SheetPortal, + SheetTitle, + SheetTrigger, +} from "./Sheet"; export { SliderToggle } from "./SliderToggle"; export { default as Spinner } from "./Spinner"; export { SplitButton } from "./SplitButton"; diff --git a/sparkle/src/stories/Sheet.stories.tsx b/sparkle/src/stories/Sheet.stories.tsx new file mode 100644 index 000000000000..ebb6d9605344 --- /dev/null +++ b/sparkle/src/stories/Sheet.stories.tsx @@ -0,0 +1,373 @@ +import type { Meta } from "@storybook/react"; +import React from "react"; + +import { + Avatar, + Button, + Icon, + Input, + Page, + Separator, + Sheet, + SheetContainer, + SheetContent, + SheetDescription, + SheetFooter, + SheetHeader, + SheetTitle, + SheetTrigger, + TextArea, +} from "@sparkle/components"; +import { + CloudArrowLeftRightIcon, + FolderIcon, + GlobeAltIcon, + PencilSquareIcon, + RocketIcon, + StarIcon, + TrashIcon, +} from "@sparkle/icons"; + +const meta = { + title: "NewLayouts/Sheet", +} satisfies Meta; + +export default meta; + +export function Demo() { + return ( +
+ + + + +
+ ); +} + +export function SheetDemo() { + return ( +
+ + +
+ ); +} + +export function ContentDemo() { + return ( +
+ + +
+ ); +} + +export function SheetCustom() { + return ( +
+ + +
+ + + +