mirror of
https://github.com/langgenius/dify.git
synced 2025-12-25 01:00:42 -05:00
chore: add more stories (#27403)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import FileImageRender from './file-image-render'
|
||||
|
||||
const SAMPLE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'320\' height=\'180\'><defs><linearGradient id=\'grad\' x1=\'0%\' y1=\'0%\' x2=\'100%\' y2=\'100%\'><stop offset=\'0%\' stop-color=\'#FEE2FF\'/><stop offset=\'100%\' stop-color=\'#E0EAFF\'/></linearGradient></defs><rect width=\'320\' height=\'180\' rx=\'18\' fill=\'url(#grad)\'/><text x=\'50%\' y=\'50%\' dominant-baseline=\'middle\' text-anchor=\'middle\' font-family=\'sans-serif\' font-size=\'24\' fill=\'#1F2937\'>Preview</text></svg>'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/General/FileImageRender',
|
||||
component: FileImageRender,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Renders image previews inside a bordered frame. Often used in upload galleries and logs.',
|
||||
},
|
||||
source: {
|
||||
language: 'tsx',
|
||||
code: `
|
||||
<FileImageRender imageUrl="https://example.com/preview.png" className="h-32 w-52" />
|
||||
`.trim(),
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
imageUrl: SAMPLE_IMAGE,
|
||||
className: 'h-32 w-52',
|
||||
},
|
||||
} satisfies Meta<typeof FileImageRender>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Playground: Story = {}
|
||||
96
web/app/components/base/file-uploader/file-list.stories.tsx
Normal file
96
web/app/components/base/file-uploader/file-list.stories.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import { useState } from 'react'
|
||||
import { FileList } from './file-uploader-in-chat-input/file-list'
|
||||
import type { FileEntity } from './types'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
|
||||
const SAMPLE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'160\' height=\'160\'><rect width=\'160\' height=\'160\' rx=\'16\' fill=\'#D1E9FF\'/><text x=\'50%\' y=\'50%\' dominant-baseline=\'middle\' text-anchor=\'middle\' font-family=\'sans-serif\' font-size=\'20\' fill=\'#1F2937\'>IMG</text></svg>'
|
||||
|
||||
const filesSample: FileEntity[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Project Brief.pdf',
|
||||
size: 256000,
|
||||
type: 'application/pdf',
|
||||
progress: 100,
|
||||
transferMethod: TransferMethod.local_file,
|
||||
supportFileType: SupportUploadFileTypes.document,
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
name: 'Design.png',
|
||||
size: 128000,
|
||||
type: 'image/png',
|
||||
progress: 100,
|
||||
transferMethod: TransferMethod.local_file,
|
||||
supportFileType: SupportUploadFileTypes.image,
|
||||
base64Url: SAMPLE_IMAGE,
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
name: 'Voiceover.mp3',
|
||||
size: 512000,
|
||||
type: 'audio/mpeg',
|
||||
progress: 45,
|
||||
transferMethod: TransferMethod.remote_url,
|
||||
supportFileType: SupportUploadFileTypes.audio,
|
||||
url: '',
|
||||
},
|
||||
]
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Data Display/FileList',
|
||||
component: FileList,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Renders a responsive gallery of uploaded files, handling icons, previews, and progress states.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
files: filesSample,
|
||||
},
|
||||
} satisfies Meta<typeof FileList>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const FileListPlayground = (args: React.ComponentProps<typeof FileList>) => {
|
||||
const [items, setItems] = useState<FileEntity[]>(args.files || [])
|
||||
|
||||
return (
|
||||
<div className="rounded-2xl border border-divider-subtle bg-components-panel-bg p-4">
|
||||
<FileList
|
||||
{...args}
|
||||
files={items}
|
||||
onRemove={fileId => setItems(list => list.filter(file => file.id !== fileId))}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const Playground: Story = {
|
||||
render: args => <FileListPlayground {...args} />,
|
||||
parameters: {
|
||||
docs: {
|
||||
source: {
|
||||
language: 'tsx',
|
||||
code: `
|
||||
const [files, setFiles] = useState(initialFiles)
|
||||
|
||||
<FileList files={files} onRemove={(id) => setFiles(list => list.filter(file => file.id !== id))} />
|
||||
`.trim(),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const UploadStates: Story = {
|
||||
args: {
|
||||
files: filesSample.map(file => ({ ...file, progress: file.id === '3' ? 45 : 100 })),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import FileTypeIcon from './file-type-icon'
|
||||
import { FileAppearanceTypeEnum } from './types'
|
||||
|
||||
const meta = {
|
||||
title: 'Base/General/FileTypeIcon',
|
||||
component: FileTypeIcon,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Displays the appropriate icon and accent colour for a file appearance type. Useful in lists and attachments.',
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
type: FileAppearanceTypeEnum.document,
|
||||
size: 'md',
|
||||
},
|
||||
} satisfies Meta<typeof FileTypeIcon>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Playground: Story = {}
|
||||
|
||||
export const Gallery: Story = {
|
||||
render: () => (
|
||||
<div className="grid grid-cols-4 gap-6 rounded-xl border border-divider-subtle bg-components-panel-bg p-6">
|
||||
{Object.values(FileAppearanceTypeEnum).map(type => (
|
||||
<div key={type} className="flex flex-col items-center gap-2 text-xs text-text-secondary">
|
||||
<FileTypeIcon type={type} size="xl" />
|
||||
<span className="capitalize">{type}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
),
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import { fn } from 'storybook/test'
|
||||
import { useState } from 'react'
|
||||
import FileUploaderInAttachmentWrapper from './index'
|
||||
import type { FileEntity } from '../types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { PreviewMode } from '@/app/components/base/features/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { ToastProvider } from '@/app/components/base/toast'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
|
||||
const SAMPLE_IMAGE = 'data:image/svg+xml;utf8,<svg xmlns=\'http://www.w3.org/2000/svg\' width=\'128\' height=\'128\'><rect width=\'128\' height=\'128\' rx=\'16\' fill=\'#E0F2FE\'/><text x=\'50%\' y=\'50%\' dominant-baseline=\'middle\' text-anchor=\'middle\' font-family=\'sans-serif\' font-size=\'18\' fill=\'#1F2937\'>IMG</text></svg>'
|
||||
|
||||
const mockFiles: FileEntity[] = [
|
||||
{
|
||||
id: 'file-1',
|
||||
name: 'Requirements.pdf',
|
||||
size: 256000,
|
||||
type: 'application/pdf',
|
||||
progress: 100,
|
||||
transferMethod: TransferMethod.local_file,
|
||||
supportFileType: SupportUploadFileTypes.document,
|
||||
url: '',
|
||||
},
|
||||
{
|
||||
id: 'file-2',
|
||||
name: 'Interface.png',
|
||||
size: 128000,
|
||||
type: 'image/png',
|
||||
progress: 100,
|
||||
transferMethod: TransferMethod.local_file,
|
||||
supportFileType: SupportUploadFileTypes.image,
|
||||
base64Url: SAMPLE_IMAGE,
|
||||
},
|
||||
{
|
||||
id: 'file-3',
|
||||
name: 'Voiceover.mp3',
|
||||
size: 512000,
|
||||
type: 'audio/mpeg',
|
||||
progress: 35,
|
||||
transferMethod: TransferMethod.remote_url,
|
||||
supportFileType: SupportUploadFileTypes.audio,
|
||||
url: '',
|
||||
},
|
||||
]
|
||||
|
||||
const fileConfig: FileUpload = {
|
||||
enabled: true,
|
||||
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
|
||||
allowed_file_types: ['document', 'image', 'audio'],
|
||||
number_limits: 5,
|
||||
preview_config: { mode: PreviewMode.NewPage, file_type_list: ['pdf', 'png'] },
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Data Entry/FileUploaderInAttachment',
|
||||
component: FileUploaderInAttachmentWrapper,
|
||||
parameters: {
|
||||
layout: 'centered',
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Attachment-style uploader that supports local files and remote links. Demonstrates upload progress, re-upload, and preview actions.',
|
||||
},
|
||||
},
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
pathname: '/apps/demo-app/uploads',
|
||||
params: { appId: 'demo-app' },
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
fileConfig,
|
||||
},
|
||||
} satisfies Meta<typeof FileUploaderInAttachmentWrapper>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
const AttachmentDemo = (props: React.ComponentProps<typeof FileUploaderInAttachmentWrapper>) => {
|
||||
const [files, setFiles] = useState<FileEntity[]>(mockFiles)
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
<div className="w-[320px] rounded-2xl border border-divider-subtle bg-components-panel-bg p-4 shadow-xs">
|
||||
<FileUploaderInAttachmentWrapper
|
||||
{...props}
|
||||
value={files}
|
||||
onChange={setFiles}
|
||||
/>
|
||||
</div>
|
||||
</ToastProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export const Playground: Story = {
|
||||
render: args => <AttachmentDemo {...args} />,
|
||||
args: {
|
||||
onChange: fn(),
|
||||
},
|
||||
}
|
||||
|
||||
export const Disabled: Story = {
|
||||
render: args => <AttachmentDemo {...args} isDisabled />,
|
||||
args: {
|
||||
onChange: fn(),
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { Meta, StoryObj } from '@storybook/nextjs'
|
||||
import { useState } from 'react'
|
||||
import FileUploaderInChatInput from '.'
|
||||
import { FileContextProvider } from '../store'
|
||||
import type { FileEntity } from '../types'
|
||||
import type { FileUpload } from '@/app/components/base/features/types'
|
||||
import { SupportUploadFileTypes } from '@/app/components/workflow/types'
|
||||
import { TransferMethod } from '@/types/app'
|
||||
import { FileList } from '../file-uploader-in-chat-input/file-list'
|
||||
import { ToastProvider } from '@/app/components/base/toast'
|
||||
|
||||
const mockFiles: FileEntity[] = [
|
||||
{
|
||||
id: '1',
|
||||
name: 'Dataset.csv',
|
||||
size: 64000,
|
||||
type: 'text/csv',
|
||||
progress: 100,
|
||||
transferMethod: TransferMethod.local_file,
|
||||
supportFileType: SupportUploadFileTypes.document,
|
||||
},
|
||||
]
|
||||
|
||||
const chatUploadConfig: FileUpload = {
|
||||
enabled: true,
|
||||
allowed_file_upload_methods: [TransferMethod.local_file, TransferMethod.remote_url],
|
||||
allowed_file_types: ['image', 'document'],
|
||||
number_limits: 3,
|
||||
}
|
||||
|
||||
type ChatInputDemoProps = React.ComponentProps<typeof FileUploaderInChatInput> & {
|
||||
initialFiles?: FileEntity[]
|
||||
}
|
||||
|
||||
const ChatInputDemo = ({ initialFiles = mockFiles, ...props }: ChatInputDemoProps) => {
|
||||
const [files, setFiles] = useState<FileEntity[]>(initialFiles)
|
||||
|
||||
return (
|
||||
<ToastProvider>
|
||||
<FileContextProvider value={files} onChange={setFiles}>
|
||||
<div className="w-[360px] rounded-2xl border border-divider-subtle bg-components-panel-bg p-4">
|
||||
<div className="mb-3 text-xs text-text-secondary">Simulated chat input</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<FileUploaderInChatInput {...props} />
|
||||
<div className="flex-1 rounded-lg border border-divider-subtle bg-background-default-subtle p-2 text-xs text-text-tertiary">Type a message...</div>
|
||||
</div>
|
||||
<div className="mt-4">
|
||||
<FileList files={files} />
|
||||
</div>
|
||||
</div>
|
||||
</FileContextProvider>
|
||||
</ToastProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const meta = {
|
||||
title: 'Base/Data Entry/FileUploaderInChatInput',
|
||||
component: ChatInputDemo,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: 'Attachment trigger suited for chat inputs. Demonstrates integration with the shared file store and preview list.',
|
||||
},
|
||||
},
|
||||
nextjs: {
|
||||
appDirectory: true,
|
||||
navigation: {
|
||||
pathname: '/chats/demo',
|
||||
params: { appId: 'demo-app' },
|
||||
},
|
||||
},
|
||||
},
|
||||
tags: ['autodocs'],
|
||||
args: {
|
||||
fileConfig: chatUploadConfig,
|
||||
initialFiles: mockFiles,
|
||||
},
|
||||
} satisfies Meta<typeof ChatInputDemo>
|
||||
|
||||
export default meta
|
||||
type Story = StoryObj<typeof meta>
|
||||
|
||||
export const Playground: Story = {
|
||||
render: args => <ChatInputDemo {...args} />,
|
||||
}
|
||||
|
||||
export const RemoteOnly: Story = {
|
||||
args: {
|
||||
fileConfig: {
|
||||
...chatUploadConfig,
|
||||
allowed_file_upload_methods: [TransferMethod.remote_url],
|
||||
},
|
||||
initialFiles: [],
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user