/** * DataView Storybook Stories */ import React, { useEffect, useState } from 'react'; import { PiChartLineLight, PiFunnelLight, PiGearLight, PiUserLight } from 'react-icons/pi'; import type { Meta, StoryObj } from '@storybook/react-vite'; import type { AnyResource } from '@wener/common/resource'; import { createDataViewStore, DataViewLayout, DataViewProvider, PageInfo, PageNav, useDataViewStore, useDataViewStoreContext, } from './index'; import { ResourceListItem } from './ResourceListItem'; const meta: Meta = { title: 'Components/DataView', component: DataViewLayout.Composite, tags: ['autodocs'], parameters: { layout: 'fullscreen', }, }; export default meta; type Story = StoryObj; // Sample data for demonstrations const sampleUsers = Array.from({ length: 100 }, (_, i) => ({ id: i + 1, name: `User ${i + 1}`, email: `user${i + 1}@example.com`, status: i % 3 === 0 ? 'Active' : i % 3 === 1 ? 'Inactive' : 'Pending', })); // ============================================ // Story 1: Complete DataView with all features // ============================================ export const Complete: Story = { render: () => { const [showSummary, setShowSummary] = useState(true); const [pageNumber, setPageNumber] = useState(1); const [pageSize, setPageSize] = useState(20); const pageCount = Math.ceil(sampleUsers.length / pageSize); const startIndex = (pageNumber - 1) * pageSize; const endIndex = Math.min(startIndex + pageSize, sampleUsers.length); const currentPageData = sampleUsers.slice(startIndex, endIndex); return (
} actions={ <> } /> } leftPanel={ 重置筛选}>
} rightPanel={ showSummary ? ( setShowSummary(false)} tabs={[ { key: 'overview', label: '概览', icon: , content: (

基本信息

John Doe

john@example.com

Active
), }, { key: 'analytics', label: '分析', icon: , content: (

使用统计

登录次数
142
最后登录
2024-01-15
), }, { key: 'settings', label: '设置', icon: , content: (
), }, ]} /> ) : undefined } footer={ {!showSummary && ( )} } >
{currentPageData.map((user) => ( ))}
ID 姓名 邮箱 状态
{user.id} {user.name} {user.email} {user.status}
); }, }; export const Minimal: Story = { render: () => (
} footer={ } >

主要内容区域

这是一个最简化的 DataView 示例,只有 header、content 和 footer。

), }; // ============================================ // Story 3: With Left Panel Only // ============================================ export const WithLeftPanel: Story = { render: () => (
} leftPanel={ } >
{Array.from({ length: 12 }, (_, i) => (

项目 {i + 1}

项目描述信息...

))}
), }; // ============================================ // Story 4: With Right Panel Only (No Tabs) // ============================================ export const WithRightPanel: Story = { render: () => { const [showSummary, setShowSummary] = useState(true); return (
} rightPanel={ showSummary ? ( setShowSummary(false)} tabs={[ { key: 'info', label: '基本信息', content: (

#ORD-2024-001

John Doe

¥1,234.56

已完成
), }, { key: 'items', label: '订单项', content: (

商品 A

数量: 2 × ¥500.00

商品 B

数量: 1 × ¥234.56

), }, ]} >
已支付 已发货
) : undefined } >
订单号 客户 金额 状态
#ORD-2024-001 John Doe ¥1,234.56 已完成
#ORD-2024-002 Jane Smith ¥2,345.67 处理中
{!showSummary && (
)}
); }, }; // ============================================ // Story 5: Header Variants // ============================================ export const HeaderVariants: Story = { render: () => { return (
} actions={ <> } />
); }, }; // ============================================ // Story 6: Summary Tab Variants // ============================================ export const SummaryTabs: Story = { render: () => { const [activeTab, setActiveTab] = useState('tab1'); return (
console.log('Close')} tabs={[ { key: 'tab1', label: '标签页 1', icon: , content: (

第一个标签页

这是第一个标签页的内容。

), }, { key: 'tab2', label: '标签页 2', icon: , content: (

第二个标签页

这是第二个标签页的内容。

), }, { key: 'tab3', label: '标签页 3', icon: , content: (

第三个标签页

这是第三个标签页的内容。

), }, { key: 'tab4', label: '禁用', disabled: true, content:
禁用的标签页
, }, ]} >
这是 children 内容,显示在标签页之前
); }, }; // ============================================ // Story 7: Pagination Only // ============================================ export const PaginationOnly: Story = { render: () => { const [pageNumber, setPageNumber] = useState(1); const [pageSize, setPageSize] = useState(30); return (

PageInfo 组件

PageNav 组件

组合使用

); }, }; // ============================================ // Story 7: ListItem Component // ============================================ export const ListItemDemo: Story = { render: () => { const [selected, setSelected] = useState([]); const handleSelect = (id: number, isSelected: boolean) => { setSelected((prev) => (isSelected ? [...prev, id] : prev.filter((x) => x !== id))); }; return (

ListItem 组件演示

Basic ListItem

handleSelect(1, s)} > This is a simple list item with title, description and children content.

ListItem with Actions

handleSelect(2, s)} actions={ <> } > Additional content about this item goes here.

ListItem with Clickable Title

alert('Title clicked!')} selected={selected.includes(3)} onSelectedChange={(s) => handleSelect(3, s)} > Click the title above to trigger an action.

ListItem with Meta Info

handleSelect(4, s)} actions={ <> } meta={ Created by John Doe • Updated 2 hours ago • Priority: High } >

Review and finalize the project documentation, including API reference, user guides, and deployment instructions.

Multiple Items

{Array.from({ length: 5 }, (_, i) => ( handleSelect(i + 10, s)} onTitleClick={() => alert(`Clicked item ${i + 1}`)} actions={} meta={`Created ${i + 1} days ago`} > Additional details and content for item {i + 1} ))}
); }, }; // ============================================ // Story 8: Virtual List Component // ============================================ export const VirtualListDemo: Story = { render: () => { const [selected, setSelected] = useState([]); // Generate large dataset const largeDataset = Array.from({ length: 10000 }, (_, i) => ({ id: i + 1, title: `Item ${i + 1}`, description: `This is the description for item ${i + 1}`, createdBy: `User ${(i % 50) + 1}`, createdAt: new Date(Date.now() - i * 60000).toISOString(), status: ['Active', 'Pending', 'Completed', 'Archived'][i % 4], })); const handleSelect = (id: number, isSelected: boolean) => { setSelected((prev) => (isSelected ? [...prev, id] : prev.filter((x) => x !== id))); }; return (

Virtual List - 10,000 Items

Selected: {selected.length}
( handleSelect(item.id, s)} onTitleClick={() => alert(`Clicked: ${item.title}`)} actions={ <> } meta={ Created by {item.createdBy} • {new Date(item.createdAt).toLocaleDateString()} • Status: {item.status} } /> )} />
); }, }; // ============================================ // Story 9: Complete with Virtual List // ============================================ export const CompleteWithList: Story = { render: () => { const [selected, setSelected] = useState([]); const [showSummary, setShowSummary] = useState(false); const [activeItem, setActiveItem] = useState(null); const items = Array.from({ length: 500 }, (_, i) => ({ id: i + 1, title: `Project ${i + 1}`, description: `Project description for item ${i + 1}`, createdBy: `User ${(i % 20) + 1}`, createdAt: new Date(Date.now() - i * 3600000).toISOString(), status: ['Active', 'Pending', 'Completed'][i % 3], })); const handleSelect = (id: number, isSelected: boolean) => { setSelected((prev) => (isSelected ? [...prev, id] : prev.filter((x) => x !== id))); }; return (
} actions={
} /> } leftPanel={ } rightPanel={ showSummary && activeItem && ( setShowSummary(false)} tabs={[ { key: 'details', label: '详情', icon: , content: (

{activeItem.createdBy}

{new Date(activeItem.createdAt).toLocaleString()}

{activeItem.status}

), }, { key: 'activity', label: '活动', icon: , content:
活动记录...
, }, ]} /> ) } footer={ } > ( handleSelect(item.id, s)} onTitleClick={() => { setActiveItem(item); setShowSummary(true); }} actions={ <> } meta={ 创建者: {item.createdBy} • {new Date(item.createdAt).toLocaleDateString()} • 状态:{' '} {item.status} } /> )} />
); }, }; // ============================================ // Story 10: ResourceListItem Component // ============================================ export const ResourceListItemDemo: Story = { render: () => { const [selected, setSelected] = useState([]); // Sample resource data const resources: AnyResource[] = [ { id: '1', code: 'USR-001', displayName: 'John Doe', fullName: 'John William Doe', title: 'Senior Developer', description: 'Full-stack developer with 10 years of experience', email: 'john.doe@example.com', phoneNumber: '+1-234-567-8900', status: 'active', createdAt: new Date(Date.now() - 86400000 * 30), updatedAt: new Date(Date.now() - 3600000 * 2), createdBy: { id: '10', displayName: 'Admin User' }, updatedBy: { id: '11', displayName: 'HR Manager' }, }, { id: '2', code: 'PRJ-042', title: 'Website Redesign Project', description: 'Complete overhaul of company website with modern UI/UX', status: 'in_progress', createdAt: new Date(Date.now() - 86400000 * 15), updatedAt: new Date(Date.now() - 86400000 * 1), createdBy: { id: '12', displayName: 'Project Manager' }, }, { id: '3', code: 'TSK-156', displayName: 'API Integration', topic: 'Backend Development', description: 'Integrate third-party payment gateway API', notes: 'High priority - deadline next week', status: 'pending', createdAt: new Date(Date.now() - 86400000 * 5), updatedAt: new Date(Date.now() - 86400000 * 5), createdBy: { id: '1', displayName: 'John Doe' }, }, { id: '4', loginName: 'jane.smith', fullName: 'Jane Smith', displayName: 'Jane S.', description: 'Marketing specialist focusing on digital campaigns', email: 'jane.smith@example.com', status: 'active', createdAt: new Date(Date.now() - 86400000 * 90), updatedAt: new Date(Date.now() - 86400000 * 10), createdBy: { id: '10', displayName: 'Admin User' }, updatedBy: { id: '10', displayName: 'Admin User' }, }, { id: '5', code: 'DOC-789', title: 'Q4 Financial Report', description: 'Quarterly financial analysis and projections', remark: 'Awaiting CFO approval', status: 'draft', createdAt: new Date(Date.now() - 86400000 * 7), updatedAt: new Date(Date.now() - 3600000 * 6), createdBy: { id: '13', displayName: 'Finance Team' }, updatedBy: { id: '14', displayName: 'Accountant' }, }, ]; const handleSelect = (id: string, isSelected: boolean) => { setSelected((prev) => (isSelected ? [...prev, id] : prev.filter((x) => x !== id))); }; return (

ResourceListItem 组件演示

Selected: {selected.length}

Basic Usage

ResourceListItem 自动检测 resource 字段并智能显示:title/displayName/fullName,description,creator,dates 等

{resources.map((resource) => ( handleSelect(resource.id, s)} onTitleClick={() => alert(`Clicked: ${resource.id}`)} actions={ <> } /> ))}

Custom Options

Without Code Display (showCode=false)

handleSelect(resources[0].id, s)} />

Without Meta Info (showMeta=false)

handleSelect(resources[1].id, s)} />

Custom Title Renderer

( {data.status} {data.displayName || data.title} )} selected={selected.includes(resources[2].id)} onSelectedChange={(s) => handleSelect(resources[2].id, s)} />
); }, }; // Sample data generator const generateSampleData = (count: number): AnyResource[] => { return Array.from({ length: count }, (_, i) => ({ id: `item-${i + 1}`, code: `ITEM-${String(i + 1).padStart(4, '0')}`, displayName: `Item ${i + 1}`, title: `Resource Item ${i + 1}`, description: `This is the description for item ${i + 1}`, status: ['active', 'pending', 'inactive'][i % 3], createdAt: new Date(Date.now() - i * 3600000), updatedAt: new Date(Date.now() - i * 1800000), createdBy: { id: `user-${(i % 10) + 1}`, displayName: `User ${(i % 10) + 1}` }, })); }; // DataView with Store Component function DataViewWithStore() { const store = useDataViewStoreContext(); const { data, total, loading, selected, active, query, view, components } = store.getState(); const { actions } = store.getState(); // Simulate data loading useEffect(() => { const loadData = async () => { actions.setLoading(true); // Simulate API call await new Promise((resolve) => setTimeout(resolve, 1000)); const sampleData = generateSampleData(100); actions.setData(sampleData, sampleData.length); actions.setLoading(false); }; loadData(); }, [actions]); const handleItemClick = (item: AnyResource) => { actions.setActive(item); actions.setComponentState('summary', { open: true }); }; const handleCloseSummary = () => { actions.setComponentState('summary', { open: false }); actions.setActive(undefined); }; const pageCount = Math.ceil(total / query.pageSize); const startIndex = query.pageIndex * query.pageSize; const endIndex = Math.min(startIndex + query.pageSize, total); return (
actions.setQuery({ search: e.target.value })} /> } actions={
} /> } leftPanel={ } rightPanel={ components.summary?.open && active && ( , content: (

{active.code}

{active.status}

{new Date(active.createdAt!).toLocaleString()}

), }, { key: 'history', label: '历史', icon: , content:
历史记录...
, }, ]} /> ) } footer={ } > {loading ? (
) : ( ( actions.toggleSelection(item.id)} onTitleClick={() => handleItemClick(item)} actions={ <> } /> )} /> )}
); } export const DataViewWithStoreDemo: Story = { render: () => { // Create store with initial configuration const store = createDataViewStore({ query: { pageSize: 20, }, view: { mode: 'list', }, components: { summary: { open: false }, sidebar: { open: true }, }, }); // Set up event listeners store.getState().events.on('Refresh', () => { console.log('Refreshing data...'); }); store.getState().events.on('DataChanged', ({ data, total }) => { console.log('Data changed:', { count: data.length, total }); }); store.getState().events.on('SelectionChanged', ({ selected }) => { console.log('Selection changed:', selected); }); store.getState().events.on('ActiveChanged', ({ active }) => { console.log('Active changed:', active?.id); }); store.getState().events.on('QueryChanged', ({ query }) => { console.log('Query changed:', query); }); return ( ); }, parameters: { docs: { description: { story: 'Complete DataView implementation with DataViewStore for state management. Features include search, pagination, selection, active item tracking, and component state management.', }, }, }, };