ai-design-system 0.1.52 → 0.1.54

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/components/ai-elements/file-tree.tsx +304 -0
  2. package/components/blocks/AIConversation/AIConversation.tsx +6 -1
  3. package/components/composites/AdjustableLayout/AdjustableLayout.tsx +31 -22
  4. package/components/composites/FileTreeExplorer/FileTreeExplorer.stories.tsx +73 -0
  5. package/components/composites/FileTreeExplorer/FileTreeExplorer.tsx +145 -0
  6. package/components/composites/FileTreeExplorer/index.ts +8 -0
  7. package/components/composites/FormReports/FormReports.stories.tsx +33 -1
  8. package/components/composites/FormReports/FormReports.tsx +113 -0
  9. package/components/composites/FormReports/index.ts +8 -0
  10. package/components/composites/ToolCallDisplay/ToolCallDisplay.tsx +29 -1
  11. package/components/features/AIDocEditor/AIDocEditor.mocks.ts +10 -3
  12. package/components/features/AIDocEditor/AIDocEditor.stories.tsx +1 -0
  13. package/components/features/AIDocEditor/AIDocEditor.tsx +146 -58
  14. package/components/features/AIDocEditor/useAIDocEditor.d.ts +6 -0
  15. package/components/features/AIDocEditor/useAIDocEditor.mock.ts +62 -20
  16. package/components/features/RefinementPanel/RefinementPanel.tsx +9 -3
  17. package/components/features/RefinementPanel/useRefinementPanel.d.ts +4 -0
  18. package/components/features/RefinementPanel/useRefinementPanel.mock.ts +3 -0
  19. package/components/features/WorkflowObservabilityFeature/WorkflowObservabilityFeature.tsx +12 -14
  20. package/components/ui/collapsible.tsx +1 -1
  21. package/components/ui/command.tsx +2 -2
  22. package/components/ui/input-group.tsx +1 -1
  23. package/components/ui/popover.tsx +1 -1
  24. package/dist/index.cjs +584 -80
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.css +29 -67
  27. package/dist/index.d.ts +58 -0
  28. package/dist/index.js +585 -80
  29. package/dist/index.js.map +1 -1
  30. package/package.json +2 -1
@@ -0,0 +1,304 @@
1
+ "use client";
2
+
3
+ import {
4
+ Collapsible,
5
+ CollapsibleContent,
6
+ CollapsibleTrigger,
7
+ } from "@/components/ui/collapsible";
8
+ import { cn } from "@/lib/utils";
9
+ import {
10
+ ChevronRightIcon,
11
+ FileIcon,
12
+ FolderIcon,
13
+ FolderOpenIcon,
14
+ } from "lucide-react";
15
+ import type { HTMLAttributes, ReactNode } from "react";
16
+ import {
17
+ createContext,
18
+ useCallback,
19
+ useContext,
20
+ useMemo,
21
+ useState,
22
+ } from "react";
23
+
24
+ interface FileTreeContextType {
25
+ expandedPaths: Set<string>;
26
+ togglePath: (path: string) => void;
27
+ selectedPath?: string;
28
+ onSelect?: (path: string) => void;
29
+ }
30
+
31
+ // Default noop for context default value
32
+ // oxlint-disable-next-line eslint(no-empty-function)
33
+ const noop = () => {};
34
+
35
+ const FileTreeContext = createContext<FileTreeContextType>({
36
+ // oxlint-disable-next-line eslint-plugin-unicorn(no-new-builtin)
37
+ expandedPaths: new Set(),
38
+ togglePath: noop,
39
+ });
40
+
41
+ export type FileTreeProps = Omit<HTMLAttributes<HTMLDivElement>, "onSelect"> & {
42
+ expanded?: Set<string>;
43
+ defaultExpanded?: Set<string>;
44
+ selectedPath?: string;
45
+ onSelect?: (path: string) => void;
46
+ onExpandedChange?: (expanded: Set<string>) => void;
47
+ };
48
+
49
+ export const FileTree = ({
50
+ expanded: controlledExpanded,
51
+ defaultExpanded = new Set(),
52
+ selectedPath,
53
+ onSelect,
54
+ onExpandedChange,
55
+ className,
56
+ children,
57
+ ...props
58
+ }: FileTreeProps) => {
59
+ const [internalExpanded, setInternalExpanded] = useState(defaultExpanded);
60
+ const expandedPaths = controlledExpanded ?? internalExpanded;
61
+
62
+ const togglePath = useCallback(
63
+ (path: string) => {
64
+ const newExpanded = new Set(expandedPaths);
65
+ if (newExpanded.has(path)) {
66
+ newExpanded.delete(path);
67
+ } else {
68
+ newExpanded.add(path);
69
+ }
70
+ setInternalExpanded(newExpanded);
71
+ onExpandedChange?.(newExpanded);
72
+ },
73
+ [expandedPaths, onExpandedChange]
74
+ );
75
+
76
+ const contextValue = useMemo(
77
+ () => ({ expandedPaths, onSelect, selectedPath, togglePath }),
78
+ [expandedPaths, onSelect, selectedPath, togglePath]
79
+ );
80
+
81
+ return (
82
+ <FileTreeContext.Provider value={contextValue}>
83
+ <div
84
+ className={cn(
85
+ "rounded-lg border bg-background font-mono text-sm",
86
+ className
87
+ )}
88
+ role="tree"
89
+ {...props}
90
+ >
91
+ <div className="p-2">{children}</div>
92
+ </div>
93
+ </FileTreeContext.Provider>
94
+ );
95
+ };
96
+
97
+ export type FileTreeIconProps = HTMLAttributes<HTMLSpanElement>;
98
+
99
+ export const FileTreeIcon = ({
100
+ className,
101
+ children,
102
+ ...props
103
+ }: FileTreeIconProps) => (
104
+ <span className={cn("shrink-0", className)} {...props}>
105
+ {children}
106
+ </span>
107
+ );
108
+
109
+ export type FileTreeNameProps = HTMLAttributes<HTMLSpanElement>;
110
+
111
+ export const FileTreeName = ({
112
+ className,
113
+ children,
114
+ ...props
115
+ }: FileTreeNameProps) => (
116
+ <span className={cn("truncate", className)} {...props}>
117
+ {children}
118
+ </span>
119
+ );
120
+
121
+ interface FileTreeFolderContextType {
122
+ path: string;
123
+ name: string;
124
+ isExpanded: boolean;
125
+ }
126
+
127
+ const FileTreeFolderContext = createContext<FileTreeFolderContextType>({
128
+ isExpanded: false,
129
+ name: "",
130
+ path: "",
131
+ });
132
+
133
+ export type FileTreeFolderProps = HTMLAttributes<HTMLDivElement> & {
134
+ path: string;
135
+ name: string;
136
+ };
137
+
138
+ export const FileTreeFolder = ({
139
+ path,
140
+ name,
141
+ className,
142
+ children,
143
+ ...props
144
+ }: FileTreeFolderProps) => {
145
+ const { expandedPaths, togglePath, selectedPath, onSelect } =
146
+ useContext(FileTreeContext);
147
+ const isExpanded = expandedPaths.has(path);
148
+ const isSelected = selectedPath === path;
149
+
150
+ const handleOpenChange = useCallback(() => {
151
+ togglePath(path);
152
+ }, [togglePath, path]);
153
+
154
+ const handleSelect = useCallback(() => {
155
+ onSelect?.(path);
156
+ }, [onSelect, path]);
157
+
158
+ const folderContextValue = useMemo(
159
+ () => ({ isExpanded, name, path }),
160
+ [isExpanded, name, path]
161
+ );
162
+
163
+ return (
164
+ <FileTreeFolderContext.Provider value={folderContextValue}>
165
+ <Collapsible onOpenChange={handleOpenChange} open={isExpanded}>
166
+ <div
167
+ className={cn("", className)}
168
+ role="treeitem"
169
+ tabIndex={0}
170
+ {...props}
171
+ >
172
+ <div
173
+ className={cn(
174
+ "flex w-full items-center gap-1 rounded px-2 py-1 text-left transition-colors hover:bg-muted/50",
175
+ isSelected && "bg-muted"
176
+ )}
177
+ >
178
+ <CollapsibleTrigger asChild>
179
+ <button
180
+ className="flex shrink-0 cursor-pointer items-center border-none bg-transparent p-0"
181
+ type="button"
182
+ >
183
+ <ChevronRightIcon
184
+ className={cn(
185
+ "size-4 shrink-0 text-muted-foreground transition-transform",
186
+ isExpanded && "rotate-90"
187
+ )}
188
+ />
189
+ </button>
190
+ </CollapsibleTrigger>
191
+ <button
192
+ className="flex min-w-0 flex-1 cursor-pointer items-center gap-1 border-none bg-transparent p-0 text-left"
193
+ onClick={handleSelect}
194
+ type="button"
195
+ >
196
+ <FileTreeIcon>
197
+ {isExpanded ? (
198
+ <FolderOpenIcon className="size-4 text-blue-500" />
199
+ ) : (
200
+ <FolderIcon className="size-4 text-blue-500" />
201
+ )}
202
+ </FileTreeIcon>
203
+ <FileTreeName>{name}</FileTreeName>
204
+ </button>
205
+ </div>
206
+ <CollapsibleContent>
207
+ <div className="ml-4 border-l pl-2">{children}</div>
208
+ </CollapsibleContent>
209
+ </div>
210
+ </Collapsible>
211
+ </FileTreeFolderContext.Provider>
212
+ );
213
+ };
214
+
215
+ interface FileTreeFileContextType {
216
+ path: string;
217
+ name: string;
218
+ }
219
+
220
+ const FileTreeFileContext = createContext<FileTreeFileContextType>({
221
+ name: "",
222
+ path: "",
223
+ });
224
+
225
+ export type FileTreeFileProps = HTMLAttributes<HTMLDivElement> & {
226
+ path: string;
227
+ name: string;
228
+ icon?: ReactNode;
229
+ };
230
+
231
+ export const FileTreeFile = ({
232
+ path,
233
+ name,
234
+ icon,
235
+ className,
236
+ children,
237
+ ...props
238
+ }: FileTreeFileProps) => {
239
+ const { selectedPath, onSelect } = useContext(FileTreeContext);
240
+ const isSelected = selectedPath === path;
241
+
242
+ const handleClick = useCallback(() => {
243
+ onSelect?.(path);
244
+ }, [onSelect, path]);
245
+
246
+ const handleKeyDown = useCallback(
247
+ (e: React.KeyboardEvent) => {
248
+ if (e.key === "Enter" || e.key === " ") {
249
+ onSelect?.(path);
250
+ }
251
+ },
252
+ [onSelect, path]
253
+ );
254
+
255
+ const fileContextValue = useMemo(() => ({ name, path }), [name, path]);
256
+
257
+ return (
258
+ <FileTreeFileContext.Provider value={fileContextValue}>
259
+ <div
260
+ className={cn(
261
+ "flex cursor-pointer items-center gap-1 rounded px-2 py-1 transition-colors hover:bg-muted/50",
262
+ isSelected && "bg-muted",
263
+ className
264
+ )}
265
+ onClick={handleClick}
266
+ onKeyDown={handleKeyDown}
267
+ role="treeitem"
268
+ tabIndex={0}
269
+ {...props}
270
+ >
271
+ {children ?? (
272
+ <>
273
+ {/* Spacer for alignment */}
274
+ <span className="size-4 shrink-0" />
275
+ <FileTreeIcon>
276
+ {icon ?? <FileIcon className="size-4 text-muted-foreground" />}
277
+ </FileTreeIcon>
278
+ <FileTreeName>{name}</FileTreeName>
279
+ </>
280
+ )}
281
+ </div>
282
+ </FileTreeFileContext.Provider>
283
+ );
284
+ };
285
+
286
+ export type FileTreeActionsProps = HTMLAttributes<HTMLDivElement>;
287
+
288
+ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
289
+
290
+ export const FileTreeActions = ({
291
+ className,
292
+ children,
293
+ ...props
294
+ }: FileTreeActionsProps) => (
295
+ <div
296
+ className={cn("ml-auto flex items-center gap-1", className)}
297
+ onClick={stopPropagation}
298
+ onKeyDown={stopPropagation}
299
+ role="group"
300
+ {...props}
301
+ >
302
+ {children}
303
+ </div>
304
+ );
@@ -61,6 +61,10 @@ export interface AIConversationProps
61
61
  description?: string
62
62
  icon?: React.ReactNode
63
63
  }
64
+ /**
65
+ * Callback fired when a tool action (like a link click) is triggered
66
+ */
67
+ onToolAction?: (toolCall: ToolCall, action: string) => void
64
68
  }
65
69
 
66
70
  /**
@@ -73,6 +77,7 @@ export const AIConversation = React.memo<AIConversationProps>(
73
77
  onSelectSubAgent,
74
78
  selectedSubAgent,
75
79
  emptyState,
80
+ onToolAction,
76
81
  ...conversationProps
77
82
  }) => {
78
83
  const isEmpty = React.useMemo(
@@ -146,7 +151,7 @@ export const AIConversation = React.memo<AIConversationProps>(
146
151
  >
147
152
  {/* Render direct tool calls */}
148
153
  {directToolCalls.map((tc) => (
149
- <ToolCallDisplay key={tc.id} toolCall={tc} />
154
+ <ToolCallDisplay key={tc.id} toolCall={tc} onToolAction={onToolAction} />
150
155
  ))}
151
156
 
152
157
  {/* Render specialist sub-agents */}
@@ -220,36 +220,45 @@ export const AdjustableLayout = React.memo<AdjustableLayoutProps>(
220
220
  {section.content}
221
221
  </div>
222
222
 
223
- {/* Show drag handle after this panel if it's not the last one */}
224
- {isResizable && (
225
- <div
226
- className={cn(
227
- "flex-shrink-0 flex items-center justify-center relative group",
228
- orientation === "vertical"
229
- ? "cursor-row-resize h-2 w-full"
230
- : "cursor-col-resize w-2 h-full"
231
- )}
232
- onMouseDown={(e) => handleMouseDown(index, e)}
233
- >
234
- {/* Visible pill */}
223
+ {/* Show drag handle or spacer after this panel if it's not the last one */}
224
+ {index < sections.length - 1 && (
225
+ isResizable ? (
235
226
  <div
236
227
  className={cn(
237
- `${colorMap[dragHandleColor]} transition-colors duration-200 rounded-full`,
228
+ "flex-shrink-0 flex items-center justify-center relative group",
238
229
  orientation === "vertical"
239
- ? "h-1 w-8"
240
- : "w-1 h-8"
230
+ ? "cursor-row-resize h-2 w-full"
231
+ : "cursor-col-resize w-2 h-full"
241
232
  )}
242
- />
243
- {/* Invisible large hit area */}
233
+ onMouseDown={(e) => handleMouseDown(index, e)}
234
+ >
235
+ {/* Visible pill */}
236
+ <div
237
+ className={cn(
238
+ `${colorMap[dragHandleColor]} transition-colors duration-200 rounded-full`,
239
+ orientation === "vertical"
240
+ ? "h-1 w-8"
241
+ : "w-1 h-8"
242
+ )}
243
+ />
244
+ {/* Invisible large hit area */}
245
+ <div
246
+ className={cn(
247
+ "absolute z-10",
248
+ orientation === "vertical"
249
+ ? "inset-x-0 -top-2 -bottom-2"
250
+ : "inset-y-0 -left-2 -right-2"
251
+ )}
252
+ />
253
+ </div>
254
+ ) : (
244
255
  <div
245
256
  className={cn(
246
- "absolute z-10",
247
- orientation === "vertical"
248
- ? "inset-x-0 -top-2 -bottom-2"
249
- : "inset-y-0 -left-2 -right-2"
257
+ "flex-shrink-0",
258
+ orientation === "vertical" ? "h-2 w-full" : "w-2 h-full"
250
259
  )}
251
260
  />
252
- </div>
261
+ )
253
262
  )}
254
263
  </React.Fragment>
255
264
  )
@@ -0,0 +1,73 @@
1
+ import type { Meta, StoryObj } from '@storybook/react'
2
+ import { fn } from '@storybook/test'
3
+ import { FileTreeExplorer } from './FileTreeExplorer'
4
+
5
+ const sampleTree = [
6
+ {
7
+ name: 'src',
8
+ path: 'src',
9
+ type: 'folder' as const,
10
+ children: [
11
+ {
12
+ name: 'components',
13
+ path: 'src/components',
14
+ type: 'folder' as const,
15
+ children: [
16
+ { name: 'Button.tsx', path: 'src/components/Button.tsx', type: 'file' as const },
17
+ { name: 'Input.tsx', path: 'src/components/Input.tsx', type: 'file' as const },
18
+ ],
19
+ },
20
+ {
21
+ name: 'hooks',
22
+ path: 'src/hooks',
23
+ type: 'folder' as const,
24
+ children: [
25
+ { name: 'use-toast.ts', path: 'src/hooks/use-toast.ts', type: 'file' as const },
26
+ ],
27
+ },
28
+ { name: 'index.ts', path: 'src/index.ts', type: 'file' as const },
29
+ ],
30
+ },
31
+ { name: 'package.json', path: 'package.json', type: 'file' as const },
32
+ ]
33
+
34
+ const meta = {
35
+ title: 'Composites/FileTreeExplorer',
36
+ component: FileTreeExplorer,
37
+ tags: ['autodocs'],
38
+ parameters: {
39
+ layout: 'padded',
40
+ },
41
+ } satisfies Meta<typeof FileTreeExplorer>
42
+
43
+ export default meta
44
+ type Story = StoryObj<typeof meta>
45
+
46
+ export const Default: Story = {
47
+ args: {
48
+ tree: sampleTree,
49
+ defaultExpanded: new Set(['src', 'src/components']),
50
+ selectedPath: 'src/components/Button.tsx',
51
+ onSelect: fn(),
52
+ createButtonLabel: 'New File',
53
+ },
54
+ }
55
+
56
+ export const Empty: Story = {
57
+ args: {
58
+ tree: [],
59
+ createButtonLabel: 'New File',
60
+ },
61
+ }
62
+
63
+ export const DarkMode: Story = {
64
+ args: {
65
+ tree: sampleTree,
66
+ defaultExpanded: new Set(['src']),
67
+ selectedPath: 'src/index.ts',
68
+ onSelect: fn(),
69
+ },
70
+ parameters: {
71
+ backgrounds: { disable: true },
72
+ },
73
+ }
@@ -0,0 +1,145 @@
1
+ import * as React from "react"
2
+
3
+ import {
4
+ FileTree,
5
+ FileTreeFolder,
6
+ FileTreeFile,
7
+ } from "@/components/ai-elements/file-tree"
8
+ import { Icon } from "@/components/primitives/Icon"
9
+ import { Input } from "@/components/primitives/Input"
10
+ import { cn } from "@/lib/utils"
11
+
12
+ export type FileTreeNode = {
13
+ name: string
14
+ path: string
15
+ type: "file" | "folder"
16
+ children?: FileTreeNode[]
17
+ }
18
+
19
+ export interface FileTreeExplorerProps {
20
+ tree: FileTreeNode[]
21
+ defaultExpanded?: Set<string>
22
+ selectedPath?: string
23
+ onSelect?: (path: string) => void
24
+ searchPlaceholder?: string
25
+ onCreateClick?: () => void
26
+ createButtonLabel?: string
27
+ headerClassName?: string
28
+ className?: string
29
+ }
30
+
31
+ function renderTree(nodes: FileTreeNode[]): React.ReactNode {
32
+ return nodes.map((node) => {
33
+ if (node.type === "folder") {
34
+ return (
35
+ <FileTreeFolder key={node.path} name={node.name} path={node.path}>
36
+ {node.children ? renderTree(node.children) : null}
37
+ </FileTreeFolder>
38
+ )
39
+ }
40
+ return <FileTreeFile key={node.path} name={node.name} path={node.path} />
41
+ })
42
+ }
43
+
44
+ function filterTree(nodes: FileTreeNode[], query: string): FileTreeNode[] {
45
+ if (!query) return nodes;
46
+ const lowerQuery = query.toLowerCase();
47
+
48
+ return nodes.reduce<FileTreeNode[]>((acc, node) => {
49
+ if (node.type === 'file') {
50
+ if (node.name.toLowerCase().includes(lowerQuery)) {
51
+ acc.push(node);
52
+ }
53
+ } else if (node.type === 'folder' && node.children) {
54
+ const filteredChildren = filterTree(node.children, query);
55
+ // Keep the folder if it matches the query itself, or if any of its children match
56
+ if (node.name.toLowerCase().includes(lowerQuery) || filteredChildren.length > 0) {
57
+ acc.push({ ...node, children: filteredChildren });
58
+ }
59
+ }
60
+ return acc;
61
+ }, []);
62
+ }
63
+
64
+ function getAllFolderPaths(nodes: FileTreeNode[]): Set<string> {
65
+ const paths = new Set<string>();
66
+ function traverse(n: FileTreeNode[]) {
67
+ for (const node of n) {
68
+ if (node.type === 'folder') {
69
+ paths.add(node.path);
70
+ if (node.children) traverse(node.children);
71
+ }
72
+ }
73
+ }
74
+ traverse(nodes);
75
+ return paths;
76
+ }
77
+
78
+ export const FileTreeExplorer = React.memo<FileTreeExplorerProps>(
79
+ ({
80
+ tree,
81
+ defaultExpanded,
82
+ selectedPath,
83
+ onSelect,
84
+ searchPlaceholder = "Filter files...",
85
+ onCreateClick,
86
+ createButtonLabel,
87
+ headerClassName,
88
+ className,
89
+ }) => {
90
+ const [searchQuery, setSearchQuery] = React.useState("")
91
+
92
+ const filteredTree = React.useMemo(() => {
93
+ return filterTree(tree, searchQuery)
94
+ }, [tree, searchQuery])
95
+
96
+ const expandedPaths = React.useMemo(() => {
97
+ if (searchQuery) {
98
+ return getAllFolderPaths(filteredTree)
99
+ }
100
+ return defaultExpanded
101
+ }, [searchQuery, filteredTree, defaultExpanded])
102
+
103
+ return (
104
+ <div className={cn("flex flex-col rounded-lg border bg-background", className)}>
105
+ <div className={cn("flex items-center gap-2 border-b px-3 py-2", headerClassName)}>
106
+ <div className="relative flex-1">
107
+ <Icon
108
+ name="search"
109
+ size="sm"
110
+ className="absolute left-2 top-1/2 -translate-y-1/2 text-muted-foreground"
111
+ />
112
+ <Input
113
+ placeholder={searchPlaceholder}
114
+ value={searchQuery}
115
+ onChange={(e) => setSearchQuery(e.target.value)}
116
+ className="pl-8"
117
+ />
118
+ </div>
119
+ {onCreateClick ? (
120
+ <button
121
+ type="button"
122
+ onClick={onCreateClick}
123
+ className="inline-flex items-center gap-1 rounded-md bg-primary px-3 py-1.5 text-xs font-medium text-primary-foreground transition-colors hover:bg-primary/90"
124
+ >
125
+ <Icon name="plus" size="sm" />
126
+ {createButtonLabel ?? "New"}
127
+ </button>
128
+ ) : null}
129
+ </div>
130
+ <div className="p-2">
131
+ <FileTree
132
+ className="border-none bg-transparent"
133
+ defaultExpanded={expandedPaths}
134
+ selectedPath={selectedPath}
135
+ onSelect={onSelect}
136
+ >
137
+ {renderTree(filteredTree)}
138
+ </FileTree>
139
+ </div>
140
+ </div>
141
+ )
142
+ }
143
+ )
144
+
145
+ FileTreeExplorer.displayName = "FileTreeExplorer"
@@ -0,0 +1,8 @@
1
+ export {
2
+ FileTreeExplorer,
3
+ } from "./FileTreeExplorer"
4
+
5
+ export type {
6
+ FileTreeExplorerProps,
7
+ FileTreeNode,
8
+ } from "./FileTreeExplorer"
@@ -1,5 +1,25 @@
1
1
  import type { Meta, StoryObj } from '@storybook/react'
2
2
  import { FormReports } from './FormReports'
3
+ import type { FormReportsFieldDefinition } from './FormReportsDrawerForm'
4
+ import type { FormReportsColumn, FormReportsEntity } from './FormReportsTable'
5
+
6
+ const sampleColumns: FormReportsColumn[] = [
7
+ { key: "name", label: "Name" },
8
+ { key: "email", label: "Email" },
9
+ { key: "role", label: "Role" },
10
+ ]
11
+
12
+ const sampleItems: FormReportsEntity[] = [
13
+ { id: 1, name: "Alice Smith", email: "alice@example.com", role: "Admin" },
14
+ { id: 2, name: "Bob Jones", email: "bob@example.com", role: "Editor" },
15
+ { id: 3, name: "Carol Lee", email: "carol@example.com", role: "Viewer" },
16
+ ]
17
+
18
+ const sampleFields: FormReportsFieldDefinition[] = [
19
+ { name: "name", label: "Name", type: "text", required: true },
20
+ { name: "email", label: "Email", type: "text", required: true },
21
+ { name: "role", label: "Role", type: "select", options: [{ value: "Admin", label: "Admin" }, { value: "Editor", label: "Editor" }, { value: "Viewer", label: "Viewer" }] },
22
+ ]
3
23
 
4
24
  const meta = {
5
25
  title: 'Composites/FormReports',
@@ -14,5 +34,17 @@ export default meta
14
34
  type Story = StoryObj<typeof meta>
15
35
 
16
36
  export const Default: Story = {
17
- args: {} as any,
37
+ args: {
38
+ items: sampleItems,
39
+ columns: sampleColumns,
40
+ fields: sampleFields,
41
+ },
42
+ }
43
+
44
+ export const Empty: Story = {
45
+ args: {
46
+ items: [],
47
+ columns: sampleColumns,
48
+ fields: sampleFields,
49
+ },
18
50
  }