From e7f53c04a3cc1d2d1de7d9c1de76825271e4175f Mon Sep 17 00:00:00 2001 From: william garrity Date: Thu, 29 Jan 2026 20:01:35 -0500 Subject: [PATCH] fix: update Skeleton stories --- src/components/Skeleton/Skeleton.stories.tsx | 562 +++++++++++++++---- 1 file changed, 464 insertions(+), 98 deletions(-) diff --git a/src/components/Skeleton/Skeleton.stories.tsx b/src/components/Skeleton/Skeleton.stories.tsx index 9889f26..5bbc9a8 100644 --- a/src/components/Skeleton/Skeleton.stories.tsx +++ b/src/components/Skeleton/Skeleton.stories.tsx @@ -1,3 +1,4 @@ +import * as React from 'react'; import type { Meta, StoryObj } from '@storybook/react-vite'; import { Skeleton, @@ -6,174 +7,498 @@ import { SkeletonTable, } from './Skeleton'; -const meta: Meta = { +// ============================================================================= +// Example Types +// ============================================================================= + +type ExampleType = + | 'custom' + | 'default' + | 'text' + | 'title' + | 'avatar' + | 'button' + | 'image' + | 'textBlock' + | 'card' + | 'cardWithoutImage' + | 'cardMinimal' + | 'table' + | 'profileCard' + | 'listItems'; + +// ============================================================================= +// Wrapper Component +// ============================================================================= + +interface SkeletonExampleProps { + example: ExampleType; + /** Custom width (CSS value like '100%' or '200px') */ + customWidth?: string; + /** Custom height in pixels */ + customHeight?: number; + /** Custom variant */ + customVariant?: + | 'default' + | 'text' + | 'title' + | 'avatar' + | 'button' + | 'card' + | 'image'; + /** Custom border radius */ + customRounded?: 'none' | 'sm' | 'md' | 'lg' | 'full'; + /** Avatar size in pixels */ + avatarSize?: number; + /** Number of text lines (for textBlock) */ + textLines?: number; + /** Last line width (for textBlock) */ + lastLineWidth?: string; + /** Text gap size (for textBlock) */ + textGap?: 'sm' | 'md' | 'lg'; + /** Show image in card */ + cardShowImage?: boolean; + /** Show avatar in card */ + cardShowAvatar?: boolean; + /** Number of text lines in card */ + cardTextLines?: number; + /** Number of table rows */ + tableRows?: number; + /** Number of table columns */ + tableColumns?: number; + /** Number of list items */ + listItemCount?: number; +} + +function SkeletonExample({ + example, + customWidth = '100%', + customHeight = 40, + customVariant = 'default', + customRounded = 'md', + avatarSize = 48, + textLines = 4, + lastLineWidth = '70%', + textGap = 'sm', + cardShowImage = true, + cardShowAvatar = true, + cardTextLines = 2, + tableRows = 5, + tableColumns = 4, + listItemCount = 4, +}: SkeletonExampleProps) { + const roundedClasses = { + none: 'rounded-none', + sm: 'rounded-sm', + md: 'rounded-md', + lg: 'rounded-lg', + full: 'rounded-full', + }; + + switch (example) { + case 'custom': + return ( +
+ +
+ ); + + case 'default': + return ( +
+ +
+ ); + + case 'text': + return ( +
+ +
+ ); + + case 'title': + return ( +
+ +
+ ); + + case 'avatar': + return ( +
+ +
+ ); + + case 'button': + return ( +
+ +
+ ); + + case 'image': + return ( +
+ +
+ ); + + case 'textBlock': + return ( +
+ +
+ ); + + case 'card': + return ( +
+ +
+ ); + + case 'cardWithoutImage': + return ( +
+ +
+ ); + + case 'cardMinimal': + return ( +
+ +
+ ); + + case 'table': + return ( +
+ +
+ ); + + case 'profileCard': + return ( +
+
+ +
+ + +
+
+ +
+ + +
+
+ ); + + case 'listItems': + return ( +
+ {Array.from({ length: listItemCount }).map((_, i) => ( +
+ +
+ + +
+
+ ))} +
+ ); + + default: + return null; + } +} + +// ============================================================================= +// Meta Configuration +// ============================================================================= + +const meta = { title: 'Components/Skeleton', - component: Skeleton, + component: SkeletonExample, parameters: { layout: 'centered', }, tags: ['autodocs'], argTypes: { - variant: { + example: { control: 'select', options: [ + 'custom', 'default', 'text', 'title', 'avatar', 'button', - 'card', 'image', + 'textBlock', + 'card', + 'cardWithoutImage', + 'cardMinimal', + 'table', + 'profileCard', + 'listItems', ], - description: 'The visual style variant of the skeleton', - table: { - defaultValue: { summary: 'default' }, - }, + description: 'Type of skeleton example to display', }, - width: { + customWidth: { control: 'text', - description: - 'Width of the skeleton (number for px or string for CSS value)', + description: 'Width (CSS value like "100%" or "200px")', }, - height: { + customHeight: { + control: { type: 'number', min: 8, max: 400 }, + description: 'Height in pixels', + }, + customVariant: { + control: 'select', + options: [ + 'default', + 'text', + 'title', + 'avatar', + 'button', + 'card', + 'image', + ], + description: 'Variant type', + }, + customRounded: { + control: 'select', + options: ['none', 'sm', 'md', 'lg', 'full'], + description: 'Border radius', + }, + avatarSize: { + control: { type: 'number', min: 24, max: 128 }, + description: 'Avatar size in pixels', + if: { arg: 'example', eq: 'avatar' }, + }, + textLines: { + control: { type: 'number', min: 1, max: 10 }, + description: 'Number of text lines', + if: { arg: 'example', eq: 'textBlock' }, + }, + lastLineWidth: { control: 'text', - description: - 'Height of the skeleton (number for px or string for CSS value)', + description: 'Width of the last line', + if: { arg: 'example', eq: 'textBlock' }, + }, + textGap: { + control: 'select', + options: ['sm', 'md', 'lg'], + description: 'Gap between text lines', + if: { arg: 'example', eq: 'textBlock' }, }, - circle: { + cardShowImage: { control: 'boolean', - description: 'Whether to render as a circle (rounded-full)', + description: 'Show image in card', + if: { arg: 'example', eq: 'card' }, }, - className: { - control: 'text', - description: 'Additional CSS classes', + cardShowAvatar: { + control: 'boolean', + description: 'Show avatar in card', + if: { arg: 'example', eq: 'card' }, + }, + cardTextLines: { + control: { type: 'number', min: 0, max: 10 }, + description: 'Number of text lines in card', + if: { arg: 'example', eq: 'card' }, + }, + tableRows: { + control: { type: 'number', min: 1, max: 20 }, + description: 'Number of table rows', + if: { arg: 'example', eq: 'table' }, + }, + tableColumns: { + control: { type: 'number', min: 1, max: 10 }, + description: 'Number of table columns', + if: { arg: 'example', eq: 'table' }, + }, + listItemCount: { + control: { type: 'number', min: 1, max: 10 }, + description: 'Number of list items', + if: { arg: 'example', eq: 'listItems' }, }, }, -}; +} satisfies Meta; export default meta; type Story = StoryObj; +// ============================================================================= +// Stories with Args (Controls Work) +// ============================================================================= + export const Default: Story = { - render: () => ( -
- -
- ), + args: { + example: 'custom', + customWidth: '200px', + customHeight: 40, + customVariant: 'default', + customRounded: 'md', + }, }; export const Text: Story = { - render: () => ( -
- -
- ), + args: { + example: 'text', + }, }; export const Title: Story = { - render: () => ( -
- -
- ), + args: { + example: 'title', + }, }; export const Avatar: Story = { - render: () => ( -
- - - -
- ), + args: { + example: 'avatar', + avatarSize: 48, + }, }; export const Button: Story = { - render: () => ( -
- - - -
- ), + args: { + example: 'button', + }, }; export const Image: Story = { - render: () => ( -
- -
- ), + args: { + example: 'image', + }, }; export const TextBlock: Story = { - render: () => ( -
- -
- ), + args: { + example: 'textBlock', + textLines: 4, + lastLineWidth: '70%', + textGap: 'sm', + }, }; export const Card: Story = { - render: () => ( -
- -
- ), + args: { + example: 'card', + cardShowImage: true, + cardShowAvatar: true, + cardTextLines: 2, + }, }; export const CardWithoutImage: Story = { - render: () => ( -
- -
- ), + args: { + example: 'cardWithoutImage', + cardShowAvatar: true, + cardTextLines: 3, + }, +}; + +export const CardMinimal: Story = { + args: { + example: 'cardMinimal', + cardTextLines: 2, + }, }; export const Table: Story = { - render: () => ( -
- -
- ), + args: { + example: 'table', + tableRows: 5, + tableColumns: 4, + }, }; export const ProfileCard: Story = { + args: { + example: 'profileCard', + }, +}; + +export const ListItems: Story = { + args: { + example: 'listItems', + listItemCount: 4, + }, +}; + +// ============================================================================= +// Showcase Stories (Controls Disabled) +// ============================================================================= + +export const AvatarSizes: Story = { + args: { + example: 'avatar', + }, + parameters: { + controls: { disable: true }, + }, render: () => ( -
-
- -
- - -
-
- -
- - -
+
+ + + + +
), }; -export const ListItems: Story = { +export const ButtonVariations: Story = { + args: { + example: 'button', + }, + parameters: { + controls: { disable: true }, + }, render: () => ( -
- {Array.from({ length: 4 }).map((_, i) => ( -
- -
- - -
-
- ))} +
+ + + +
), }; export const Grid: Story = { + args: { + example: 'card', + }, + parameters: { + controls: { disable: true }, + }, render: () => (
{Array.from({ length: 6 }).map((_, i) => ( @@ -182,3 +507,44 @@ export const Grid: Story = {
), }; + +export const AllVariants: Story = { + args: { + example: 'default', + }, + parameters: { + controls: { disable: true }, + }, + render: () => ( +
+
+

Default

+ +
+
+

Text

+ +
+
+

Title

+ +
+
+

Avatar

+ +
+
+

Button

+ +
+
+

Card

+ +
+
+

Image

+ +
+
+ ), +};