diff --git a/sparkle/src/components/Card.tsx b/sparkle/src/components/Card.tsx new file mode 100644 index 000000000000..b112737a551f --- /dev/null +++ b/sparkle/src/components/Card.tsx @@ -0,0 +1,140 @@ +import { cva, type VariantProps } from "class-variance-authority"; +import * as React from "react"; + +import { Spinner } from "@sparkle/components"; +import { cn } from "@sparkle/lib/utils"; + +const CARD_SIZES = ["xs", "sm", "md", "lg"] as const; +export type CardSizeType = (typeof CARD_SIZES)[number]; + +const cardVariants = cva( + "s-flex s-flex-col s-gap-2 s-rounded-2xl s-bg-structure-50 s-p-4 s-min-h-[128px]", + { + variants: { + size: { + xs: "s-w-[180px]", + sm: "s-w-[240px]", + md: "s-w-[300px]", + lg: "s-w-[360px]", + }, + }, + defaultVariants: { + size: "sm", + }, + } +); + +interface CardRootProps extends VariantProps { + children: React.ReactNode; + className?: string; +} +const Root = ({ size, className, children }: CardRootProps) => ( +
{children}
+); + +interface CardHeaderProps { + children: React.ReactNode; + className?: string; +} + +const Header = ({ className, children }: CardHeaderProps) => ( +
{children}
+); + +interface CardTitleProps { + children: React.ReactNode; + className?: string; +} + +const Title = ({ className, children }: CardTitleProps) => ( +
+ {children} +
+); + +interface CardSubtitleProps { + children: React.ReactNode; + className?: string; +} + +const Subtitle = ({ className, children }: CardSubtitleProps) => ( +
+ {children} +
+); + +interface CardContentProps { + children?: React.ReactNode; + className?: string; + isLoading?: boolean; +} + +const Content = ({ + className, + children, + isLoading = false, +}: CardContentProps) => { + if (isLoading) { + return ( +
+ +
+ ); + } + return ( +
{children}
+ ); +}; + +interface CardFooterProps { + children: React.ReactNode; + className?: string; +} + +const Footer = ({ className, children }: CardFooterProps) => ( +
+ {children} +
+); + +interface CardProps { + title: string; + subtitle?: string; + content: React.ReactNode; + footer?: React.ReactNode; + isLoading?: boolean; + size?: CardSizeType; + className?: string; +} + +export const Card = ({ + title, + subtitle, + content, + footer, + isLoading = false, + size = "sm", + className, +}: CardProps) => { + return ( + +
+ {title} + {subtitle && {subtitle}} +
+ {content} + {footer &&
{footer}
} +
+ ); +}; + +export const ComposableCard = { + Root, + Header, + Title, + Subtitle, + Content, + Footer, +}; diff --git a/sparkle/src/components/index.ts b/sparkle/src/components/index.ts index 8fbaaa7cf221..9a7ea1cd10ef 100644 --- a/sparkle/src/components/index.ts +++ b/sparkle/src/components/index.ts @@ -7,6 +7,7 @@ export type { ButtonProps } from "./Button"; export type { MetaButtonProps } from "./Button"; export { Button } from "./Button"; export { MetaButton } from "./Button"; +export { Card, ComposableCard } from "./Card"; export { CardButton } from "./CardButton"; export type { CheckboxProps } from "./Checkbox"; export { diff --git a/sparkle/src/index_with_tw_base.ts b/sparkle/src/index_with_tw_base.ts index 1e1ce6baa07a..581c172aa339 100644 --- a/sparkle/src/index_with_tw_base.ts +++ b/sparkle/src/index_with_tw_base.ts @@ -1,2 +1,2 @@ import "./styles/global_with_tw_base.css"; -export * from "./index"; +export * from "./index"; \ No newline at end of file diff --git a/sparkle/src/stories/Card.stories.tsx b/sparkle/src/stories/Card.stories.tsx new file mode 100644 index 000000000000..3a363da6392c --- /dev/null +++ b/sparkle/src/stories/Card.stories.tsx @@ -0,0 +1,122 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import * as React from "react"; + +import { + Avatar, +} from "@sparkle/components"; +import { HandThumbUpIcon } from "@sparkle/icons/solid"; + +import { Card,ComposableCard } from "../components/Card"; + +const meta: Meta = { + title: "Components/Card", + component: Card, + parameters: { + layout: "padded", + }, + argTypes: { + size: { + control: 'select', + options: ['xs', 'sm', 'md', 'lg'], + }, + className: { + control: 'text' + }, + }, + args: { + size: 'sm', + }, + tags: ["autodocs"], +}; + +export default meta; +type Story = StoryObj; + +export const Basic: Story = { + args: { + title: "Messages", + subtitle: "Monthly activity", + content: ( +
+
+ 847 +
+
+ ), + }, +}; + +export const Loading: Story = { + args: { + ...Basic.args, + isLoading: true, + }, +}; + +export const WithIcons: Story = { + args: { + title: "Reactions", + content: ( +
+ +
12
+
+ ), + }, +}; + +export const Sizes: Story = { + render: (args) => ( +
+ {(["xs", "sm", "md"] as const).map((size) => ( + + 847 +
+ } + /> + ))} + + ), +}; + +export const Composable: Story = { + render: () => ( + + + Messages + Monthly activity + + +
+ +
847
+
+
+ +
+ + + +
+
+
+ ), +}; \ No newline at end of file