Skip to content

Commit

Permalink
New Metric Card Component (#8987)
Browse files Browse the repository at this point in the history
* feat(sparkle): add metric card component

* patch version

* refactor(sparkle): reorganize metric card component structure

* update dependencies

* fix issue

* card:improvement

* resolving conflict

* fixing package

* fix package lock

* [sparkle] - refactor: clean up formatting in Card component

 - Standardize object and component formatting for consistency
 - Remove extraneous newlines and ensure proper newline at EOF in Card component files
 - Simplify export statements in components index file

* [sparkle] - feature: bump version to 0.2.328

 - Updated package version in package-lock.json and package.json for new release
 - Changes include synchronization of the version across both files

---------

Co-authored-by: JulesBelveze <[email protected]>
  • Loading branch information
pinotalexandre and JulesBelveze authored Nov 28, 2024
1 parent c210dff commit 66e190e
Show file tree
Hide file tree
Showing 4 changed files with 264 additions and 1 deletion.
140 changes: 140 additions & 0 deletions sparkle/src/components/Card.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof cardVariants> {
children: React.ReactNode;
className?: string;
}
const Root = ({ size, className, children }: CardRootProps) => (
<div className={cn(cardVariants({ size }), className)}>{children}</div>
);

interface CardHeaderProps {
children: React.ReactNode;
className?: string;
}

const Header = ({ className, children }: CardHeaderProps) => (
<div className={cn("s-space-y-0.5", className)}>{children}</div>
);

interface CardTitleProps {
children: React.ReactNode;
className?: string;
}

const Title = ({ className, children }: CardTitleProps) => (
<div
className={cn("s-text-sm s-font-semibold s-text-element-800", className)}
>
{children}
</div>
);

interface CardSubtitleProps {
children: React.ReactNode;
className?: string;
}

const Subtitle = ({ className, children }: CardSubtitleProps) => (
<div className={cn("s-text-sm s-text-element-700", className)}>
{children}
</div>
);

interface CardContentProps {
children?: React.ReactNode;
className?: string;
isLoading?: boolean;
}

const Content = ({
className,
children,
isLoading = false,
}: CardContentProps) => {
if (isLoading) {
return (
<div className="s-flex s-items-center s-justify-start">
<Spinner size="sm" />
</div>
);
}
return (
<div className={cn("s-flex s-flex-col s-gap-3", className)}>{children}</div>
);
};

interface CardFooterProps {
children: React.ReactNode;
className?: string;
}

const Footer = ({ className, children }: CardFooterProps) => (
<div className={cn("s-flex s-items-center s-gap-2", className)}>
{children}
</div>
);

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 (
<Root size={size} className={className}>
<Header>
<Title>{title}</Title>
{subtitle && <Subtitle>{subtitle}</Subtitle>}
</Header>
<Content isLoading={isLoading}>{content}</Content>
{footer && <Footer>{footer}</Footer>}
</Root>
);
};

export const ComposableCard = {
Root,
Header,
Title,
Subtitle,
Content,
Footer,
};
1 change: 1 addition & 0 deletions sparkle/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion sparkle/src/index_with_tw_base.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
import "./styles/global_with_tw_base.css";
export * from "./index";
export * from "./index";
122 changes: 122 additions & 0 deletions sparkle/src/stories/Card.stories.tsx
Original file line number Diff line number Diff line change
@@ -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<typeof Card> = {
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<typeof Card>;

export const Basic: Story = {
args: {
title: "Messages",
subtitle: "Monthly activity",
content: (
<div className="s-flex s-items-center s-gap-2">
<div className="s-text-lg s-font-semibold s-text-element-900">
847
</div>
</div>
),
},
};

export const Loading: Story = {
args: {
...Basic.args,
isLoading: true,
},
};

export const WithIcons: Story = {
args: {
title: "Reactions",
content: (
<div className="s-flex s-items-center s-gap-2">
<HandThumbUpIcon className="s-h-4 s-w-4 s-text-element-600" />
<div className="s-text-lg s-font-semibold s-text-element-900">12</div>
</div>
),
},
};

export const Sizes: Story = {
render: (args) => (
<div className="s-flex s-flex-wrap s-gap-4">
{(["xs", "sm", "md"] as const).map((size) => (
<Card
key={size}
{...args}
size={size}
title={`Size: ${size}`}
content={
<div className="s-text-lg s-font-semibold s-text-element-900">
847
</div>
}
/>
))}
</div>
),
};

export const Composable: Story = {
render: () => (
<ComposableCard.Root size="sm">
<ComposableCard.Header>
<ComposableCard.Title>Messages</ComposableCard.Title>
<ComposableCard.Subtitle>Monthly activity</ComposableCard.Subtitle>
</ComposableCard.Header>
<ComposableCard.Content>
<div className="s-flex s-items-center s-gap-2">
<HandThumbUpIcon className="s-h-4 s-w-4 s-text-element-600" />
<div className="s-text-lg s-font-semibold">847</div>
</div>
</ComposableCard.Content>
<ComposableCard.Footer>
<div className="s-flex -s-space-x-2">
<Avatar
size="sm"
name="John Doe"
visual="https://dust.tt/static/droidavatar/Droid_Lime_3.jpg"
/>
<Avatar
size="sm"
name="Jane Smith"
visual="https://dust.tt/static/droidavatar/Droid_Yellow_3.jpg"
/>
<Avatar
size="sm"
name="Bob Johnson"
visual="https://dust.tt/static/droidavatar/Droid_Red_3.jpg"
/>
</div>
</ComposableCard.Footer>
</ComposableCard.Root>
),
};

0 comments on commit 66e190e

Please sign in to comment.