From 444bd35f0c2b340ba4a83428e3589c44da22a9a6 Mon Sep 17 00:00:00 2001 From: Alban Dumouilla Date: Tue, 28 Jan 2025 09:57:36 +0100 Subject: [PATCH 1/3] Getting there --- .../HorizontalCollapsible.stories.tsx | 217 +++++++++++++++ .../src/components/HorizontalCollapsible.tsx | 246 ++++++++++++++++++ 2 files changed, 463 insertions(+) create mode 100644 sparkle/src/components/HorizontalCollapsible.stories.tsx create mode 100644 sparkle/src/components/HorizontalCollapsible.tsx diff --git a/sparkle/src/components/HorizontalCollapsible.stories.tsx b/sparkle/src/components/HorizontalCollapsible.stories.tsx new file mode 100644 index 000000000000..f0002661e0f7 --- /dev/null +++ b/sparkle/src/components/HorizontalCollapsible.stories.tsx @@ -0,0 +1,217 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React from "react"; + +import { HorizontalCollapsible } from "./HorizontalCollapsible"; + +type ComponentType = typeof HorizontalCollapsible; + +const meta = { + title: "Components/HorizontalCollapsible", + component: HorizontalCollapsible, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const SAMPLE_IMAGE = + "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=400&h=300&fit=crop"; + +export const Default: Story = { + args: { + children: ( + + +
+ + Click to expand + + +

+ This is the expandable content that appears when you click the + button. It can contain any React components or HTML elements. +

+
+
+
+ ), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const DefaultOpen: Story = { + args: { + defaultValue: "1", + children: ( + + +
+ + Already expanded + + +

+ This content is visible by default because we set defaultValue to + "1". +

+
+
+
+ ), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const SecondaryVariant: Story = { + args: { + children: ( + + +
+ + Secondary Button Style + + +

+ This example uses the secondary button variant for a different + visual style. +

+
+
+
+ ), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const Disabled: Story = { + args: { + children: ( + + +
+ + Disabled State + + +

+ This content won't be accessible because the button is disabled. +

+
+
+
+ ), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const WithCustomImage: Story = { + args: { + children: ( + + +
+ + Custom Image Styling + + +

+ This example shows how to apply custom styling to the image using + the className prop. +

+
+
+
+ ), + }, + decorators: [ + (Story) => ( +
+ +
+ ), + ], +}; + +export const MultipleItems: Story = { + render: function MultipleItemsStory() { + return ( +
+ + + +
+ + First Item + + +

Content for the first item.

+
+
+
+ + + +
+ + Second Item + + +

Content for the second item.

+
+
+
+
+
+ ); + }, +}; diff --git a/sparkle/src/components/HorizontalCollapsible.tsx b/sparkle/src/components/HorizontalCollapsible.tsx new file mode 100644 index 000000000000..e6b09ecfe5ec --- /dev/null +++ b/sparkle/src/components/HorizontalCollapsible.tsx @@ -0,0 +1,246 @@ +import { RadioGroup, Transition } from "@headlessui/react"; +import React, { createContext, useContext } from "react"; + +import { ChevronDownIcon, ChevronRightIcon } from "@sparkle/icons/solid"; +import { classNames } from "@sparkle/lib/utils"; + +import { Icon } from "./Icon"; + +interface OpenState { + value: string; + isSelected: boolean; +} + +const OpenStateContext = createContext(undefined); + +const useOpenState = (): OpenState => { + const context = useContext(OpenStateContext); + if (context === undefined) { + throw new Error("useOpenState must be used within a OpenStateProvider"); + } + return context; +}; + +export interface HorizontalCollapsibleProps { + children: React.ReactNode; + defaultValue?: string; +} + +export const HorizontalCollapsible: React.FC & { + Item: React.FC; + Button: React.FC; + Panel: React.FC; + Image: React.FC; +} = ({ children, defaultValue = "1" }) => ( + + {({ value }) => ( + +
{children}
+
+ )} +
+); + +interface HorizontalCollapsibleItemProps { + children: React.ReactNode; + value: string; +} + +HorizontalCollapsible.Item = function ({ + children, + value, +}: HorizontalCollapsibleItemProps) { + return ( + + {({ checked }) => ( + +
+ {children} +
+
+ )} +
+ ); +}; + +interface HorizontalCollapsibleImageProps { + src: string; + alt: string; + className?: string; +} + +HorizontalCollapsible.Image = function ({ + src, + alt, + className = "", +}: HorizontalCollapsibleImageProps) { + const { isSelected } = useOpenState(); + + return ( +
+ + {alt} + +
+ ); +}; + +export interface HorizontalCollapsibleButtonProps { + label?: string; + className?: string; + disabled?: boolean; + children?: React.ReactNode; + variant?: "primary" | "secondary"; +} + +HorizontalCollapsible.Button = function ({ + label, + children, + className = "", + disabled = false, + variant = "primary", +}: HorizontalCollapsibleButtonProps) { + const { isSelected } = useOpenState(); + + const labelClasses = { + primary: { + base: "s-text-action-500 s-inline-flex s-transition-colors s-ease-out s-duration-400 s-box-border s-gap-x-2 s-select-none", + hover: "group-hover/col:s-text-action-400", + active: "active:s-text-action-600", + dark: { + base: "dark:s-text-action-500-dark", + hover: "dark:group-hover/col:s-text-action-400-dark", + active: "dark:active:s-text-action-600-dark", + disabled: "dark:s-element-500-dark", + }, + disabled: "s-element-500", + }, + secondary: { + base: "s-text-foreground s-inline-flex s-transition-colors s-ease-out s-duration-400 s-box-border s-gap-x-2 s-select-none", + hover: "group-hover/col:s-text-action-500", + active: "active:s-text-action-600", + dark: { + base: "dark:s-text-foreground-dark", + hover: "dark:group-hover/col:s-text-action-400-dark", + active: "dark:active:s-text-action-600-dark", + disabled: "dark:s-element-500-dark", + }, + disabled: "s-element-500", + }, + }; + + const chevronClasses = { + primary: { + base: "s-text-element-600", + hover: "group-hover/col:s-text-action-400", + active: "active:s-text-action-700", + disabled: "s-element-500", + dark: { + base: "dark:s-text-element-600-dark", + hover: "dark:group-hover/col:s-text-action-500-dark", + active: "dark:active:s-text-action-700-dark", + disabled: "dark:s-element-500-dark", + }, + }, + secondary: { + base: "s-text-element-600", + hover: "group-hover/col:s-text-action-400", + active: "active:s-text-action-700", + disabled: "s-element-500", + dark: { + base: "dark:s-text-element-600-dark", + hover: "dark:group-hover/col:s-text-action-500-dark", + active: "dark:active:s-text-action-700-dark", + disabled: "dark:s-element-500-dark", + }, + }, + }; + + const finalLabelClasses = classNames( + labelClasses[variant].base, + labelClasses[variant].dark.base, + !disabled ? labelClasses[variant].active : "", + !disabled ? labelClasses[variant].dark.active : "", + !disabled ? labelClasses[variant].hover : "", + !disabled ? labelClasses[variant].dark.hover : "", + disabled ? labelClasses[variant].disabled : "" + ); + + const finalChevronClasses = classNames( + chevronClasses[variant].base, + chevronClasses[variant].dark.base, + !disabled ? chevronClasses[variant].active : "", + !disabled ? chevronClasses[variant].dark.active : "", + !disabled ? chevronClasses[variant].hover : "", + !disabled ? chevronClasses[variant].dark.hover : "", + disabled ? chevronClasses[variant].disabled : "" + ); + + return ( +
+ + {children ? children : {label}} +
+ ); +}; + +export interface HorizontalCollapsiblePanelProps { + children: React.ReactNode; + className?: string; +} + +HorizontalCollapsible.Panel = function ({ + children, + className = "", +}: HorizontalCollapsiblePanelProps) { + const { isSelected } = useOpenState(); + + return ( +
+ +
+
{children}
+
+
+
+ ); +}; From 0f3017a98b28946bad5012ded56667ac343d77db Mon Sep 17 00:00:00 2001 From: Alban Dumouilla Date: Tue, 28 Jan 2025 10:03:02 +0100 Subject: [PATCH 2/3] Make collapsible work --- .../HorizontalCollapsible.stories.tsx | 34 +++++--- .../src/components/HorizontalCollapsible.tsx | 79 ++++++++++++------- 2 files changed, 72 insertions(+), 41 deletions(-) diff --git a/sparkle/src/components/HorizontalCollapsible.stories.tsx b/sparkle/src/components/HorizontalCollapsible.stories.tsx index f0002661e0f7..4c8591bfe255 100644 --- a/sparkle/src/components/HorizontalCollapsible.stories.tsx +++ b/sparkle/src/components/HorizontalCollapsible.stories.tsx @@ -27,6 +27,7 @@ export const Default: Story = {
@@ -59,6 +60,7 @@ export const DefaultOpen: Story = {
@@ -90,6 +92,7 @@ export const SecondaryVariant: Story = {
@@ -121,6 +124,7 @@ export const Disabled: Story = {
@@ -152,6 +156,7 @@ export const WithCustomImage: Story = { src={SAMPLE_IMAGE} alt="Colorful gradient" className="s-border-2 s-border-action-500" + value="1" />
@@ -181,35 +186,38 @@ export const MultipleItems: Story = { return (
- + -
+ + + + + First Item

Content for the first item.

-
-
+ - - -
+ Second Item

Content for the second item.

-
-
+ +
); diff --git a/sparkle/src/components/HorizontalCollapsible.tsx b/sparkle/src/components/HorizontalCollapsible.tsx index e6b09ecfe5ec..7b386151e4a9 100644 --- a/sparkle/src/components/HorizontalCollapsible.tsx +++ b/sparkle/src/components/HorizontalCollapsible.tsx @@ -31,16 +31,42 @@ export const HorizontalCollapsible: React.FC & { Button: React.FC; Panel: React.FC; Image: React.FC; + ImageContainer: React.FC; + Content: React.FC; } = ({ children, defaultValue = "1" }) => ( {({ value }) => ( -
{children}
+
{children}
)}
); +interface HorizontalCollapsibleImageContainerProps { + children: React.ReactNode; +} + +HorizontalCollapsible.ImageContainer = function ({ + children, +}: HorizontalCollapsibleImageContainerProps) { + return ( +
{children}
+ ); +}; + +interface HorizontalCollapsibleContentProps { + children: React.ReactNode; +} + +HorizontalCollapsible.Content = function ({ + children, +}: HorizontalCollapsibleContentProps) { + return ( +
{children}
+ ); +}; + interface HorizontalCollapsibleItemProps { children: React.ReactNode; value: string; @@ -56,7 +82,7 @@ HorizontalCollapsible.Item = function ({
@@ -72,37 +98,38 @@ interface HorizontalCollapsibleImageProps { src: string; alt: string; className?: string; + value: string; } HorizontalCollapsible.Image = function ({ src, alt, className = "", + value, }: HorizontalCollapsibleImageProps) { - const { isSelected } = useOpenState(); + const { value: selectedValue } = useOpenState(); + const isSelected = value === selectedValue; return ( -
- - {alt} - -
+ + {alt} + ); }; @@ -232,10 +259,6 @@ HorizontalCollapsible.Panel = function ({ show={isSelected} enter="s-transition s-duration-300 s-ease-out" enterFrom="s-transform s-scale-95 s-opacity-0" - enterTo="s-transform s-scale-100 s-opacity-100" - leave="s-transition s-duration-300 s-ease-out" - leaveFrom="s-transform s-scale-100 s-opacity-100" - leaveTo="s-transform s-scale-95 s-opacity-0" >
{children}
From 48b97f2478f6dd5610b085907d899f633ed37c5a Mon Sep 17 00:00:00 2001 From: Alban Dumouilla Date: Tue, 28 Jan 2025 10:13:31 +0100 Subject: [PATCH 3/3] Bump sparkle + move story --- sparkle/package-lock.json | 4 +- sparkle/package.json | 2 +- .../HorizontalCollapsible.stories.tsx | 225 ------------------ .../src/components/HorizontalCollapsible.tsx | 75 +++--- .../stories/HorizontalCollapsible.stories.tsx | 105 ++++++++ 5 files changed, 145 insertions(+), 266 deletions(-) delete mode 100644 sparkle/src/components/HorizontalCollapsible.stories.tsx create mode 100644 sparkle/src/stories/HorizontalCollapsible.stories.tsx diff --git a/sparkle/package-lock.json b/sparkle/package-lock.json index 22b049cddcc9..3eefc0cfe091 100644 --- a/sparkle/package-lock.json +++ b/sparkle/package-lock.json @@ -1,12 +1,12 @@ { "name": "@dust-tt/sparkle", - "version": "0.2.373", + "version": "0.2.374", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@dust-tt/sparkle", - "version": "0.2.373", + "version": "0.2.374", "license": "ISC", "dependencies": { "@emoji-mart/data": "^1.1.2", diff --git a/sparkle/package.json b/sparkle/package.json index 409120b31393..1829d17dee54 100644 --- a/sparkle/package.json +++ b/sparkle/package.json @@ -1,6 +1,6 @@ { "name": "@dust-tt/sparkle", - "version": "0.2.373", + "version": "0.2.374", "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", diff --git a/sparkle/src/components/HorizontalCollapsible.stories.tsx b/sparkle/src/components/HorizontalCollapsible.stories.tsx deleted file mode 100644 index 4c8591bfe255..000000000000 --- a/sparkle/src/components/HorizontalCollapsible.stories.tsx +++ /dev/null @@ -1,225 +0,0 @@ -import type { Meta, StoryObj } from "@storybook/react"; -import React from "react"; - -import { HorizontalCollapsible } from "./HorizontalCollapsible"; - -type ComponentType = typeof HorizontalCollapsible; - -const meta = { - title: "Components/HorizontalCollapsible", - component: HorizontalCollapsible, - parameters: { - layout: "centered", - }, - tags: ["autodocs"], -} satisfies Meta; - -export default meta; -type Story = StoryObj; - -const SAMPLE_IMAGE = - "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=400&h=300&fit=crop"; - -export const Default: Story = { - args: { - children: ( - - -
- - Click to expand - - -

- This is the expandable content that appears when you click the - button. It can contain any React components or HTML elements. -

-
-
-
- ), - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export const DefaultOpen: Story = { - args: { - defaultValue: "1", - children: ( - - -
- - Already expanded - - -

- This content is visible by default because we set defaultValue to - "1". -

-
-
-
- ), - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export const SecondaryVariant: Story = { - args: { - children: ( - - -
- - Secondary Button Style - - -

- This example uses the secondary button variant for a different - visual style. -

-
-
-
- ), - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export const Disabled: Story = { - args: { - children: ( - - -
- - Disabled State - - -

- This content won't be accessible because the button is disabled. -

-
-
-
- ), - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export const WithCustomImage: Story = { - args: { - children: ( - - -
- - Custom Image Styling - - -

- This example shows how to apply custom styling to the image using - the className prop. -

-
-
-
- ), - }, - decorators: [ - (Story) => ( -
- -
- ), - ], -}; - -export const MultipleItems: Story = { - render: function MultipleItemsStory() { - return ( -
- - - - - - - - - - First Item - - -

Content for the first item.

-
-
- - - - Second Item - - -

Content for the second item.

-
-
-
-
-
- ); - }, -}; diff --git a/sparkle/src/components/HorizontalCollapsible.tsx b/sparkle/src/components/HorizontalCollapsible.tsx index 7b386151e4a9..0588901c16cf 100644 --- a/sparkle/src/components/HorizontalCollapsible.tsx +++ b/sparkle/src/components/HorizontalCollapsible.tsx @@ -26,6 +26,39 @@ export interface HorizontalCollapsibleProps { defaultValue?: string; } +export interface HorizontalCollapsibleContentProps { + children: React.ReactNode; +} + +export interface HorizontalCollapsibleImageContainerProps { + children: React.ReactNode; +} + +export interface HorizontalCollapsibleItemProps { + children: React.ReactNode; + value: string; +} + +export interface HorizontalCollapsibleImageProps { + src: string; + alt: string; + className?: string; + value: string; +} + +export interface HorizontalCollapsibleButtonProps { + label?: string; + className?: string; + disabled?: boolean; + children?: React.ReactNode; + variant?: "primary" | "secondary"; +} + +export interface HorizontalCollapsiblePanelProps { + children: React.ReactNode; + className?: string; +} + export const HorizontalCollapsible: React.FC & { Item: React.FC; Button: React.FC; @@ -43,22 +76,16 @@ export const HorizontalCollapsible: React.FC & { ); -interface HorizontalCollapsibleImageContainerProps { - children: React.ReactNode; -} - HorizontalCollapsible.ImageContainer = function ({ children, }: HorizontalCollapsibleImageContainerProps) { return ( -
{children}
+
+ {children} +
); }; -interface HorizontalCollapsibleContentProps { - children: React.ReactNode; -} - HorizontalCollapsible.Content = function ({ children, }: HorizontalCollapsibleContentProps) { @@ -67,11 +94,6 @@ HorizontalCollapsible.Content = function ({ ); }; -interface HorizontalCollapsibleItemProps { - children: React.ReactNode; - value: string; -} - HorizontalCollapsible.Item = function ({ children, value, @@ -94,13 +116,6 @@ HorizontalCollapsible.Item = function ({ ); }; -interface HorizontalCollapsibleImageProps { - src: string; - alt: string; - className?: string; - value: string; -} - HorizontalCollapsible.Image = function ({ src, alt, @@ -124,23 +139,12 @@ HorizontalCollapsible.Image = function ({ {alt} ); }; -export interface HorizontalCollapsibleButtonProps { - label?: string; - className?: string; - disabled?: boolean; - children?: React.ReactNode; - variant?: "primary" | "secondary"; -} - HorizontalCollapsible.Button = function ({ label, children, @@ -242,11 +246,6 @@ HorizontalCollapsible.Button = function ({ ); }; -export interface HorizontalCollapsiblePanelProps { - children: React.ReactNode; - className?: string; -} - HorizontalCollapsible.Panel = function ({ children, className = "", diff --git a/sparkle/src/stories/HorizontalCollapsible.stories.tsx b/sparkle/src/stories/HorizontalCollapsible.stories.tsx new file mode 100644 index 000000000000..05a051121023 --- /dev/null +++ b/sparkle/src/stories/HorizontalCollapsible.stories.tsx @@ -0,0 +1,105 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import React from "react"; + +import { HorizontalCollapsible } from "../components/HorizontalCollapsible"; + +type ComponentType = typeof HorizontalCollapsible; + +const meta = { + title: "Components/HorizontalCollapsible", + component: HorizontalCollapsible, + parameters: { + layout: "centered", + }, + tags: ["autodocs"], +} satisfies Meta; + +export default meta; +type Story = StoryObj; + +const SAMPLE_IMAGE = + "https://images.unsplash.com/photo-1579546929518-9e396f3cc809?w=400&h=300&fit=crop"; + +export const LeftAligned: Story = { + render: function MultipleItemsStory() { + return ( +
+ + + + + First Item + + +

Content for the first item.

+
+
+ + + + Second Item + + +

Content for the second item.

+
+
+
+ + + + +
+
+ ); + }, +}; +export const RightAligned: Story = { + render: function MultipleItemsStory() { + return ( +
+ + + + + + + + + + First Item + + +

Content for the first item.

+
+
+ + + + Second Item + + +

Content for the second item.

+
+
+
+
+
+ ); + }, +};