Feat: Display AvatarUpload and RAGFlowAvatar in Storybook #9914 (#9920)

### What problem does this PR solve?

Feat: Display AvatarUpload and RAGFlowAvatar in Storybook #9914

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-09-04 18:02:17 +08:00 committed by GitHub
parent abd19b0f48
commit d04ae3f943
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 682 additions and 1 deletions

View File

@ -13,7 +13,7 @@ module.exports = {
'check-file/filename-naming-convention': [ 'check-file/filename-naming-convention': [
'error', 'error',
{ {
'**/*.{jsx,tsx}': 'KEBAB_CASE', '**/*.{jsx,tsx}': '[a-z0-9.-]*',
'**/*.{js,ts}': '[a-z0-9.-]*', '**/*.{js,ts}': '[a-z0-9.-]*',
}, },
], ],

View File

@ -1,4 +1,7 @@
import type { Preview } from '@storybook/react-webpack5'; import type { Preview } from '@storybook/react-webpack5';
import { createElement } from 'react';
import { TooltipProvider } from '../src/components/ui/tooltip';
import '../tailwind.css'; import '../tailwind.css';
const preview: Preview = { const preview: Preview = {
@ -10,6 +13,9 @@ const preview: Preview = {
}, },
}, },
}, },
decorators: [
(Story) => createElement(TooltipProvider, null, createElement(Story)),
],
}; };
export default preview; export default preview;

View File

@ -0,0 +1,99 @@
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { fn } from 'storybook/test';
import { AvatarUpload } from '@/components/avatar-upload';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Example/AvatarUpload',
component: AvatarUpload,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
docs: {
description: {
component: `
## AvatarUpload Component
AvatarUpload is a file upload component specifically designed for uploading and displaying avatar images. It supports image preview, removal, and provides a user-friendly interface for avatar management.
### Import Path
\`\`\`typescript
import { AvatarUpload } from '@/components/avatar-upload';
\`\`\`
### Basic Usage
\`\`\`tsx
import { useState } from 'react';
import { AvatarUpload } from '@/components/avatar-upload';
function MyComponent() {
const [avatarValue, setAvatarValue] = useState('');
return (
<AvatarUpload
value={avatarValue}
onChange={(base64String) => setAvatarValue(base64String)}
/>
);
}
\`\`\`
### Features
- Supports image upload with drag & drop
- Image preview with hover effects
- Remove button to clear selected image
- Base64 encoding for easy handling
- Accepts common image formats (jpg, jpeg, png, webp, bmp)
`,
},
},
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
value: {
description: 'The current avatar value as base64 string',
control: { type: 'text' },
type: { name: 'string', required: false },
},
onChange: {
description: 'Callback function called when avatar changes',
control: false,
type: { name: 'function', required: false },
},
},
// Use `fn` to spy on the onChange arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onChange: fn() },
} satisfies Meta<typeof AvatarUpload>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const EmptyState: Story = {
args: {
value: '',
},
parameters: {
docs: {
description: {
story: `
### Empty State
Shows the upload area when no avatar is selected.
\`\`\`tsx
<AvatarUpload
value=""
onChange={(base64String) => console.log('Avatar uploaded:', base64String)}
/>
\`\`\`
`,
},
},
},
tags: ['!dev'],
};

View File

@ -0,0 +1,192 @@
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Example/RAGFlowAvatar',
component: RAGFlowAvatar,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
docs: {
description: {
component: `
## RAGFlowAvatar Component
RAGFlowAvatar is a customizable avatar component that displays user avatars with intelligent fallbacks. When an image is not available, it generates colorful gradient backgrounds with user initials.
### Import Path
\`\`\`typescript
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
\`\`\`
### Basic Usage
\`\`\`tsx
import { RAGFlowAvatar } from '@/components/ragflow-avatar';
function MyComponent() {
return (
<RAGFlowAvatar
name="John Doe"
avatar="https://example.com/avatar.jpg"
isPerson={true}
/>
);
}
\`\`\`
### Features
- Displays user avatar images when available
- Generates colorful gradient fallbacks with initials
- Supports both person (circular) and non-person (rounded) styles
- Automatic font size calculation based on container size
- Color generation based on name for consistent appearance
- Responsive design with resize observer
`,
},
},
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
name: {
description:
'The name to display initials for when no avatar is available',
control: { type: 'text' },
type: { name: 'string', required: false },
},
avatar: {
description: 'The URL of the avatar image',
control: { type: 'text' },
type: { name: 'string', required: false },
},
isPerson: {
description: 'Whether this avatar represents a person (affects styling)',
control: { type: 'boolean' },
type: { name: 'boolean', required: false },
defaultValue: false,
},
className: {
description: 'Additional CSS classes to apply',
control: { type: 'text' },
type: { name: 'string', required: false },
},
},
args: {
name: 'John Doe',
isPerson: false,
},
} satisfies Meta<typeof RAGFlowAvatar>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const WithInitials: Story = {
args: {
name: 'John Doe',
isPerson: false,
},
parameters: {
docs: {
description: {
story: `
### With Initials Only
Shows the avatar component with only a name, displaying generated initials with a gradient background.
\`\`\`tsx
<RAGFlowAvatar
name="John Doe"
isPerson={false}
/>
\`\`\`
`,
},
},
},
tags: ['!dev'],
};
export const WithAvatar: Story = {
args: {
name: 'Jane Smith',
avatar:
'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjY0IiBoZWlnaHQ9IjY0IiByeD0iMzIiIGZpbGw9IiM0RjZERUUiLz4KPGNpcmNsZSBjeD0iMzIiIGN5PSIyNCIgcj0iOCIgZmlsbD0id2hpdGUiLz4KPHBhdGggZD0iTTQ4IDQ4QzQ4IDQxLjM3MyA0MS45NDA2IDM2IDM0LjUgMzZIMjkuNUMyMi4wNTk0IDM2IDE2IDQxLjM3MyAxNiA0OCIgZmlsbD0id2hpdGUiLz4KPC9zdmc+',
isPerson: true,
},
parameters: {
docs: {
description: {
story: `
### With Avatar Image
Shows the avatar component with an actual image. When isPerson is true, the avatar will be circular.
\`\`\`tsx
<RAGFlowAvatar
name="Jane Smith"
avatar="https://example.com/avatar.jpg"
isPerson={true}
/>
\`\`\`
`,
},
},
},
tags: ['!dev'],
};
export const PersonStyle: Story = {
args: {
name: 'Alice Johnson',
isPerson: true,
},
parameters: {
docs: {
description: {
story: `
### Person Style (Circular)
Shows the avatar component with isPerson set to true, which makes it circular.
\`\`\`tsx
<RAGFlowAvatar
name="Alice Johnson"
isPerson={true}
/>
\`\`\`
`,
},
},
},
tags: ['!dev'],
};
export const NonPersonStyle: Story = {
args: {
name: 'Bot Assistant',
isPerson: false,
},
parameters: {
docs: {
description: {
story: `
### Non-Person Style (Rounded Rectangle)
Shows the avatar component with isPerson set to false, which makes it a rounded rectangle.
\`\`\`tsx
<RAGFlowAvatar
name="Bot Assistant"
isPerson={false}
/>
\`\`\`
`,
},
},
},
tags: ['!dev'],
};

View File

@ -0,0 +1,231 @@
import { zodResolver } from '@hookform/resolvers/zod';
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Form } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
// Define form schema
const FormSchema = z.object({
username: z.string().min(2, {
message: 'Username must be at least 2 characters.',
}),
email: z.string().email({
message: 'Please enter a valid email address.',
}),
description: z.string().optional(),
});
// Create a wrapper component to demonstrate RAGFlowFormItem
function FormExample({
horizontal = false,
fieldName = 'username',
label = 'Username',
tooltip = 'Please enter your username',
placeholder = 'Enter username',
}: {
horizontal?: boolean;
fieldName?: string;
label?: string;
tooltip?: string;
placeholder?: string;
}) {
const form = useForm({
resolver: zodResolver(FormSchema),
defaultValues: {
username: '',
email: '',
description: '',
},
});
return (
<div className="w-full p-4 border rounded-lg">
<Form {...form}>
<form className="space-y-4">
<RAGFlowFormItem
name={fieldName}
label={label}
tooltip={tooltip}
horizontal={horizontal}
>
<Input placeholder={placeholder} />
</RAGFlowFormItem>
</form>
</Form>
</div>
);
}
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Example/RAGFlowForm',
component: FormExample,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
docs: {
description: {
component: `
## RAGFlowFormItem Component
RAGFlowFormItem is a wrapper component built on top of shadcn/ui Form components, providing unified form item styling and layout.
### Import Path
\`\`\`typescript
import { RAGFlowFormItem } from '@/components/ragflow-form';
import { Form } from '@/components/ui/form';
\`\`\`
### Basic Usage
\`\`\`tsx
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
const FormSchema = z.object({
username: z.string(),
});
function MyForm() {
const form = useForm({
resolver: zodResolver(FormSchema),
defaultValues: { username: '' },
});
return (
<Form {...form}>
<form>
<RAGFlowFormItem
name="username"
label="Username"
tooltip="Please enter your username"
>
<Input placeholder="Enter username" />
</RAGFlowFormItem>
</form>
</Form>
);
}
\`\`\`
### Features
- Built-in FormField, FormItem, FormLabel, FormControl and FormMessage
- Supports both horizontal and vertical layouts
- Supports tooltip hints
- Fully compatible with react-hook-form
`,
},
},
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
horizontal: {
description: 'Whether to display the form item horizontally',
control: { type: 'boolean' },
type: { name: 'boolean', required: false },
defaultValue: false,
},
fieldName: {
description: 'The name of the form field',
control: { type: 'text' },
type: { name: 'string', required: true },
},
label: {
description: 'The label of the form field',
control: { type: 'text' },
type: { name: 'string', required: false },
},
tooltip: {
description: 'The tooltip text for the form field',
control: { type: 'text' },
type: { name: 'string', required: false },
},
placeholder: {
description: 'The placeholder text for the input',
control: { type: 'text' },
type: { name: 'string', required: false },
},
},
args: {
horizontal: false,
fieldName: 'username',
label: 'Username',
tooltip: 'Please enter your username',
placeholder: 'Enter username',
},
} satisfies Meta<typeof FormExample>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const VerticalLayout: Story = {
args: {
horizontal: false,
fieldName: 'username',
label: 'Username',
tooltip: 'Please enter your username',
placeholder: 'Enter username',
},
parameters: {
docs: {
description: {
story: `
### Vertical Layout Example
Default vertical layout with label above the input field.
\`\`\`tsx
<RAGFlowFormItem
name="username"
label="Username"
tooltip="Please enter your username"
horizontal={false}
>
<Input placeholder="Enter username" />
</RAGFlowFormItem>
\`\`\`
`,
},
},
},
// tags: ['!dev'],
};
export const HorizontalLayout: Story = {
args: {
horizontal: true,
fieldName: 'email',
label: 'Email Address',
tooltip: 'Please enter a valid email address',
placeholder: 'Enter email',
},
parameters: {
docs: {
description: {
story: `
### Horizontal Layout Example
Horizontal layout with label and input field on the same row.
\`\`\`tsx
<RAGFlowFormItem
name="email"
label="Email Address"
tooltip="Please enter a valid email address"
horizontal={true}
>
<Input type="email" placeholder="Enter email" />
</RAGFlowFormItem>
\`\`\`
`,
},
},
},
// tags: ['!dev'],
};

View File

@ -0,0 +1,68 @@
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { fn } from 'storybook/test';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Example/RAGFlowPagination',
component: RAGFlowPagination,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
docs: {
description: {
component: `
## Component Description
RAGFlowPagination is a pagination component that helps navigate through large datasets by dividing them into pages. It provides intuitive controls for users to move between pages, adjust page size, and view their current position within the dataset.`,
},
},
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
current: { control: 'number' },
pageSize: { control: 'number' },
total: { control: 'number' },
},
// Use `fn` to spy on the onClick arg, which will appear in the actions panel once invoked: https://storybook.js.org/docs/essentials/actions#action-args
args: { onChange: fn() },
} satisfies Meta<typeof RAGFlowPagination>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const WithLoading: Story = {
args: {
current: 1,
pageSize: 10,
total: 100,
showSizeChanger: true,
},
parameters: {
docs: {
description: {
story: `
### Usage Examples
\`\`\`tsx
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={pagination.total}
onChange={(page, pageSize) => {
setPagination({ page, pageSize });
}}>
</RAGFlowPagination>
\`\`\`
`,
},
},
},
tags: ['!dev'],
};

View File

@ -0,0 +1,85 @@
import type { Meta, StoryObj } from '@storybook/react-webpack5';
import { SkeletonCard } from '@/components/skeleton-card';
// More on how to set up stories at: https://storybook.js.org/docs/writing-stories#default-export
const meta = {
title: 'Example/SkeletonCard',
component: SkeletonCard,
parameters: {
// Optional parameter to center the component in the Canvas. More info: https://storybook.js.org/docs/configure/story-layout
layout: 'centered',
docs: {
description: {
component: `
## SkeletonCard Component
SkeletonCard is a loading placeholder component that displays skeleton lines while content is being loaded. It provides a consistent loading experience with animated placeholders.
### Import Path
\`\`\`typescript
import { SkeletonCard } from '@/components/skeleton-card';
\`\`\`
### Basic Usage
\`\`\`tsx
import { SkeletonCard } from '@/components/skeleton-card';
function MyComponent() {
return (
<SkeletonCard className="w-64" />
);
}
\`\`\`
### Features
- Displays animated skeleton loading placeholders
- Three lines of skeleton content with varying widths
- Customizable styling through className prop
- Consistent spacing and appearance
- Built on top of the Skeleton UI component
`,
},
},
},
// This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/writing-docs/autodocs
tags: ['autodocs'],
// More on argTypes: https://storybook.js.org/docs/api/argtypes
argTypes: {
className: {
description: 'Additional CSS classes to apply to the skeleton card',
control: { type: 'text' },
type: { name: 'string', required: false },
},
},
args: {
className: '',
},
} satisfies Meta<typeof SkeletonCard>;
export default meta;
type Story = StoryObj<typeof meta>;
// More on writing stories with args: https://storybook.js.org/docs/writing-stories/args
export const WithCustomWidth: Story = {
args: {
className: 'w-80',
},
parameters: {
docs: {
description: {
story: `
### Custom Width
Shows the skeleton card with a custom width applied.
\`\`\`tsx
<SkeletonCard className="w-80" />
\`\`\`
`,
},
},
},
tags: ['!dev'],
};