Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions src/components/Card/Card.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import type { Meta, StoryObj } from "@storybook/react-vite";
import { CardLayout } from "./CardLayout";
import { HeadingField } from "../Heading/HeadingField";
import { RichTextDisplayField } from "../RichText/RichTextDisplayField";
import { TextItem } from "../RichText/TextItem";
import { ButtonWidget } from "../Button/ButtonWidget";

const meta = {
title: "Components/Card",
Expand Down Expand Up @@ -235,6 +239,113 @@ export const BorderAndShadow: Story = {
},
};

export const VerticalAlignment: Story = {
args: { children: null },
parameters: { layout: "padded" },
render: () => (
<div className="space-y-8">
<div>
<p className="text-sm text-gray-600 mb-3">
Cards use <code className="bg-gray-100 px-1 rounded text-xs">flex flex-col</code> internally.
Put <code className="bg-gray-100 px-1 rounded text-xs">mt-auto</code> on your footer element to pin it to the bottom when cards stretch to equal height.
</p>
</div>

{/* Food menu pattern */}
<div>
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide mb-2">Variable content, footer pinned to bottom</p>
<div className="grid grid-cols-4 gap-4">
<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<div className="w-full h-24 bg-gradient-to-br from-green-200 to-green-400 rounded mb-3"></div>
<HeadingField text="Edamame" size="MEDIUM" fontWeight="SEMI_BOLD" marginBelow="EVEN_LESS" />
<RichTextDisplayField value={[<TextItem text="Steamed soybeans with sea salt." />]} marginBelow="NONE" className="[&>div]:leading-tight!" />
<div className="flex items-center justify-between mt-auto pt-3">
<RichTextDisplayField value={[<TextItem text="$6.99" size="MEDIUM" style="STRONG" />]} marginBelow="NONE" />
<ButtonWidget icon="plus" style="OUTLINE" color="ACCENT" size="SMALL" />
</div>
</CardLayout>

<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<div className="w-full h-24 bg-gradient-to-br from-amber-200 to-amber-400 rounded mb-3"></div>
<HeadingField text="Gyoza" size="MEDIUM" fontWeight="SEMI_BOLD" marginBelow="EVEN_LESS" />
<RichTextDisplayField value={[<TextItem text="Pan-fried pork and vegetable dumplings served with a soy-vinegar dipping sauce." />]} marginBelow="NONE" className="[&>div]:leading-tight!" />
<div className="flex items-center justify-between mt-auto pt-3">
<span className="font-bold text-gray-900">$8.00</span>
<ButtonWidget icon="plus" style="OUTLINE" color="ACCENT" size="SMALL" />
</div>
</CardLayout>

<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<div className="w-full h-24 bg-gradient-to-br from-yellow-200 to-orange-300 rounded mb-3"></div>
<HeadingField text="Agedashi Tofu" size="MEDIUM" fontWeight="SEMI_BOLD" marginBelow="EVEN_LESS" />
<RichTextDisplayField value={[<TextItem text="Lightly fried tofu cubes in warm dashi broth with green onions." />]} marginBelow="NONE" className="[&>div]:leading-tight!" />
<div className="flex items-center justify-between mt-auto pt-3">
<span className="font-bold text-gray-900">$8.50</span>
<ButtonWidget icon="plus" style="OUTLINE" color="ACCENT" size="SMALL" />
</div>
</CardLayout>

<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<div className="w-full h-24 bg-gradient-to-br from-gray-700 to-gray-900 rounded mb-3"></div>
<HeadingField text="Seaweed Salad" size="MEDIUM" fontWeight="SEMI_BOLD" marginBelow="EVEN_LESS" />
<RichTextDisplayField value={[<TextItem text="Chilled seasoned seaweed with sesame seeds and light vinegar dressing." />]} marginBelow="NONE" className="[&>div]:leading-tight!" />
<div className="flex items-center justify-between mt-auto pt-3">
<span className="font-bold text-gray-900">$7.00</span>
<ButtonWidget icon="plus" style="OUTLINE" color="ACCENT" size="SMALL" />
</div>
</CardLayout>
</div>
</div>

{/* Pricing pattern */}
<div>
<p className="text-xs font-medium text-gray-600 uppercase tracking-wide mb-2">Variable feature list, button at bottom</p>
<div className="grid grid-cols-3 gap-4">
<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<HeadingField text="Starter" size="SMALL" fontWeight="SEMI_BOLD" color="SECONDARY" marginBelow="NONE" />
<HeadingField text="$9/mo" size="LARGE" fontWeight="BOLD" marginBelow="LESS" />
<ul className="text-sm text-gray-600 space-y-1.5">
<li>✓ 5 projects</li>
<li>✓ Basic analytics</li>
</ul>
<div className="mt-auto pt-4">
<ButtonWidget label="Get Started" style="SOLID" color="ACCENT" width="FILL" />
</div>
</CardLayout>

<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<HeadingField text="Professional" size="SMALL" fontWeight="SEMI_BOLD" color="SECONDARY" marginBelow="NONE" />
<HeadingField text="$29/mo" size="LARGE" fontWeight="BOLD" marginBelow="LESS" />
<ul className="text-sm text-gray-600 space-y-1.5">
<li>✓ Unlimited projects</li>
<li>✓ Advanced analytics</li>
<li>✓ Team collaboration</li>
<li>✓ Priority support</li>
<li>✓ Custom integrations</li>
</ul>
<div className="mt-auto pt-4">
<ButtonWidget label="Upgrade" style="SOLID" color="ACCENT" width="FILL" />
</div>
</CardLayout>

<CardLayout padding="STANDARD" showBorder={true} marginBelow="NONE">
<HeadingField text="Enterprise" size="SMALL" fontWeight="SEMI_BOLD" color="SECONDARY" marginBelow="NONE" />
<HeadingField text="Custom" size="LARGE" fontWeight="BOLD" marginBelow="LESS" />
<ul className="text-sm text-gray-600 space-y-1.5">
<li>✓ Everything in Pro</li>
<li>✓ SSO & SAML</li>
<li>✓ Dedicated support</li>
</ul>
<div className="mt-auto pt-4">
<ButtonWidget label="Contact Sales" style="SOLID" color="ACCENT" width="FILL" />
</div>
</CardLayout>
</div>
</div>
</div>
),
};

export const CardStyles: Story = {
args: { children: null },
render: () => (
Expand Down
5 changes: 3 additions & 2 deletions src/components/Card/CardLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ export const CardLayout: React.FC<CardLayoutProps> = ({
marginBelowMap[marginBelow],
showBorder ? `border ${borderProps.className || ''}` : '',
showShadow ? 'shadow-md' : '',
'relative'
'relative',
'flex flex-col'
].filter(Boolean).join(' ')

// Merge with optional className override
Expand Down Expand Up @@ -202,7 +203,7 @@ export const CardLayout: React.FC<CardLayoutProps> = ({
aria-hidden="true"
/>
)}
<div className={decorativeBarPosition === "TOP" ? "pt-1" : ""}>
<div className={`flex-1 flex flex-col ${decorativeBarPosition === "TOP" ? "pt-1" : ""}`}>
{children}
</div>
</div>
Expand Down
Loading