ai-design-system 0.1.0

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 (290) hide show
  1. package/README.md +307 -0
  2. package/components/ai-elements/actions.tsx +65 -0
  3. package/components/ai-elements/artifact.tsx +147 -0
  4. package/components/ai-elements/branch.tsx +212 -0
  5. package/components/ai-elements/canvas.tsx +24 -0
  6. package/components/ai-elements/chain-of-thought.tsx +228 -0
  7. package/components/ai-elements/code-block.tsx +179 -0
  8. package/components/ai-elements/confirmation.tsx +169 -0
  9. package/components/ai-elements/connection.tsx +28 -0
  10. package/components/ai-elements/context.tsx +408 -0
  11. package/components/ai-elements/controls.tsx +18 -0
  12. package/components/ai-elements/conversation.tsx +97 -0
  13. package/components/ai-elements/edge.tsx +140 -0
  14. package/components/ai-elements/image.tsx +24 -0
  15. package/components/ai-elements/inline-citation.tsx +287 -0
  16. package/components/ai-elements/loader.tsx +96 -0
  17. package/components/ai-elements/message.tsx +80 -0
  18. package/components/ai-elements/node.tsx +71 -0
  19. package/components/ai-elements/open-in-chat.tsx +363 -0
  20. package/components/ai-elements/panel.tsx +15 -0
  21. package/components/ai-elements/plan.tsx +142 -0
  22. package/components/ai-elements/prompt-input.tsx +1352 -0
  23. package/components/ai-elements/queue.tsx +274 -0
  24. package/components/ai-elements/reasoning.tsx +178 -0
  25. package/components/ai-elements/response.tsx +22 -0
  26. package/components/ai-elements/shimmer.tsx +64 -0
  27. package/components/ai-elements/sources.tsx +77 -0
  28. package/components/ai-elements/suggestion.tsx +56 -0
  29. package/components/ai-elements/task.tsx +87 -0
  30. package/components/ai-elements/tool.tsx +179 -0
  31. package/components/ai-elements/toolbar.tsx +16 -0
  32. package/components/ai-elements/web-preview.tsx +263 -0
  33. package/components/blocks/AIConversation/AIConversation.stories.tsx +164 -0
  34. package/components/blocks/AIConversation/AIConversation.tsx +186 -0
  35. package/components/blocks/AIConversation/index.ts +8 -0
  36. package/components/blocks/AppSidebar/AppSidebar.stories.tsx +63 -0
  37. package/components/blocks/AppSidebar/AppSidebar.tsx +87 -0
  38. package/components/blocks/AppSidebar/index.ts +2 -0
  39. package/components/blocks/DocumentEditorWithComments/DocumentEditorWithComments.stories.tsx +341 -0
  40. package/components/blocks/DocumentEditorWithComments/DocumentEditorWithComments.tsx +255 -0
  41. package/components/blocks/DocumentEditorWithComments/index.ts +9 -0
  42. package/components/blocks/FileChangeQueue/FileChangeQueue.stories.tsx +207 -0
  43. package/components/blocks/FileChangeQueue/FileChangeQueue.tsx +143 -0
  44. package/components/blocks/FileChangeQueue/index.ts +7 -0
  45. package/components/blocks/LayoutProvider/LayoutProvider.tsx +34 -0
  46. package/components/blocks/LayoutProvider/index.ts +1 -0
  47. package/components/blocks/index.ts +2 -0
  48. package/components/composites/AgentIndicator/AgentIndicator.stories.tsx +154 -0
  49. package/components/composites/AgentIndicator/AgentIndicator.tsx +102 -0
  50. package/components/composites/AgentIndicator/index.ts +8 -0
  51. package/components/composites/AppHeader/AppHeader.stories.tsx +46 -0
  52. package/components/composites/AppHeader/AppHeader.tsx +24 -0
  53. package/components/composites/AppHeader/index.ts +2 -0
  54. package/components/composites/CommentBox/CommentBox.stories.tsx +192 -0
  55. package/components/composites/CommentBox/CommentBox.tsx +364 -0
  56. package/components/composites/CommentBox/index.ts +8 -0
  57. package/components/composites/Confirmation/Confirmation.stories.tsx +151 -0
  58. package/components/composites/Confirmation/Confirmation.tsx +93 -0
  59. package/components/composites/Confirmation/index.ts +7 -0
  60. package/components/composites/DataTable/DataTable.stories.tsx +35 -0
  61. package/components/composites/DataTable/DataTable.tsx +95 -0
  62. package/components/composites/DataTable/index.ts +2 -0
  63. package/components/composites/DocumentEditor/DocumentEditor.css +106 -0
  64. package/components/composites/DocumentEditor/DocumentEditor.stories.tsx +927 -0
  65. package/components/composites/DocumentEditor/DocumentEditor.tsx +279 -0
  66. package/components/composites/DocumentEditor/index.ts +8 -0
  67. package/components/composites/FileQueue/FileQueue.stories.tsx +175 -0
  68. package/components/composites/FileQueue/FileQueue.tsx +161 -0
  69. package/components/composites/FileQueue/FileStatusBadge.tsx +74 -0
  70. package/components/composites/FileQueue/index.ts +24 -0
  71. package/components/composites/InteractiveChart/InteractiveChart.stories.tsx +49 -0
  72. package/components/composites/InteractiveChart/InteractiveChart.tsx +69 -0
  73. package/components/composites/InteractiveChart/index.ts +2 -0
  74. package/components/composites/ModeToggle/ModeToggle.stories.tsx +212 -0
  75. package/components/composites/ModeToggle/ModeToggle.tsx +100 -0
  76. package/components/composites/ModeToggle/index.ts +7 -0
  77. package/components/composites/NavUser/NavUser.stories.tsx +50 -0
  78. package/components/composites/NavUser/NavUser.tsx +60 -0
  79. package/components/composites/NavUser/index.ts +2 -0
  80. package/components/composites/NavigationList/NavigationList.stories.tsx +46 -0
  81. package/components/composites/NavigationList/NavigationList.tsx +46 -0
  82. package/components/composites/NavigationList/index.ts +2 -0
  83. package/components/composites/OrchestratorMessage/OrchestratorMessage.stories.tsx +188 -0
  84. package/components/composites/OrchestratorMessage/OrchestratorMessage.tsx +72 -0
  85. package/components/composites/OrchestratorMessage/index.ts +8 -0
  86. package/components/composites/PageContainer/PageContainer.stories.tsx +41 -0
  87. package/components/composites/PageContainer/PageContainer.tsx +24 -0
  88. package/components/composites/PageContainer/index.ts +2 -0
  89. package/components/composites/PromptInput/PromptInput.stories.tsx +200 -0
  90. package/components/composites/PromptInput/PromptInput.tsx +129 -0
  91. package/components/composites/PromptInput/index.ts +8 -0
  92. package/components/composites/SpecialistMessage/SpecialistMessage.stories.tsx +286 -0
  93. package/components/composites/SpecialistMessage/SpecialistMessage.tsx +107 -0
  94. package/components/composites/SpecialistMessage/index.ts +8 -0
  95. package/components/composites/StatsCard/StatsCard.stories.tsx +76 -0
  96. package/components/composites/StatsCard/StatsCard.tsx +81 -0
  97. package/components/composites/StatsCard/index.ts +2 -0
  98. package/components/composites/TablePagination/TablePagination.stories.tsx +38 -0
  99. package/components/composites/TablePagination/TablePagination.tsx +119 -0
  100. package/components/composites/TablePagination/index.ts +2 -0
  101. package/components/composites/TableToolbar/TableToolbar.stories.tsx +60 -0
  102. package/components/composites/TableToolbar/TableToolbar.tsx +66 -0
  103. package/components/composites/TableToolbar/index.ts +2 -0
  104. package/components/composites/ThemeSelector/ThemeSelector.stories.tsx +48 -0
  105. package/components/composites/ThemeSelector/ThemeSelector.tsx +79 -0
  106. package/components/composites/ThemeSelector/index.ts +2 -0
  107. package/components/composites/ToolCallDisplay/ToolCallDisplay.stories.tsx +49 -0
  108. package/components/composites/ToolCallDisplay/ToolCallDisplay.tsx +108 -0
  109. package/components/composites/ToolCallDisplay/index.ts +8 -0
  110. package/components/composites/UserMessage/UserMessage.stories.tsx +59 -0
  111. package/components/composites/UserMessage/UserMessage.tsx +52 -0
  112. package/components/composites/UserMessage/index.ts +8 -0
  113. package/components/composites/index.ts +90 -0
  114. package/components/features/AIDocEditor/AIDocEditor.behaviors.stories.tsx +451 -0
  115. package/components/features/AIDocEditor/AIDocEditor.mocks.ts +96 -0
  116. package/components/features/AIDocEditor/AIDocEditor.stories.tsx +140 -0
  117. package/components/features/AIDocEditor/AIDocEditor.tsx +130 -0
  118. package/components/features/AIDocEditor/index.ts +8 -0
  119. package/components/features/AIDocEditor/useAIDocEditor.d.ts +97 -0
  120. package/components/features/AIDocEditor/useAIDocEditor.mock.ts +83 -0
  121. package/components/features/PageLayout/PageLayout.behaviors.stories.tsx +119 -0
  122. package/components/features/PageLayout/PageLayout.mocks.ts +27 -0
  123. package/components/features/PageLayout/PageLayout.stories.tsx +142 -0
  124. package/components/features/PageLayout/PageLayout.tsx +94 -0
  125. package/components/features/PageLayout/index.ts +4 -0
  126. package/components/features/PageLayout/usePageLayout.d.ts +24 -0
  127. package/components/features/PageLayout/usePageLayout.mock.ts +19 -0
  128. package/components/features/RefinementPanel/README.md +189 -0
  129. package/components/features/RefinementPanel/RefinementPanel.behaviors.stories.tsx +475 -0
  130. package/components/features/RefinementPanel/RefinementPanel.mocks.ts +131 -0
  131. package/components/features/RefinementPanel/RefinementPanel.stories.tsx +141 -0
  132. package/components/features/RefinementPanel/RefinementPanel.tsx +160 -0
  133. package/components/features/RefinementPanel/index.ts +25 -0
  134. package/components/features/RefinementPanel/useRefinementPanel.d.ts +74 -0
  135. package/components/features/RefinementPanel/useRefinementPanel.mock.ts +121 -0
  136. package/components/features/SpecNavigator/SpecNavigator.behaviors.stories.tsx +379 -0
  137. package/components/features/SpecNavigator/SpecNavigator.mocks.ts +131 -0
  138. package/components/features/SpecNavigator/SpecNavigator.stories.tsx +122 -0
  139. package/components/features/SpecNavigator/SpecNavigator.tsx +43 -0
  140. package/components/features/SpecNavigator/index.ts +2 -0
  141. package/components/features/SpecNavigator/useSpecNavigator.d.ts +122 -0
  142. package/components/features/SpecNavigator/useSpecNavigator.mock.ts +93 -0
  143. package/components/features/index.ts +18 -0
  144. package/components/index.ts +14 -0
  145. package/components/primitives/Accordion/Accordion.stories.tsx +87 -0
  146. package/components/primitives/Accordion/Accordion.tsx +66 -0
  147. package/components/primitives/Accordion/index.ts +13 -0
  148. package/components/primitives/Alert/Alert.stories.tsx +422 -0
  149. package/components/primitives/Alert/Alert.tsx +61 -0
  150. package/components/primitives/Alert/index.ts +8 -0
  151. package/components/primitives/AlertDialog/AlertDialog.stories.tsx +367 -0
  152. package/components/primitives/AlertDialog/AlertDialog.tsx +182 -0
  153. package/components/primitives/AlertDialog/index.ts +25 -0
  154. package/components/primitives/Avatar/Avatar.stories.tsx +321 -0
  155. package/components/primitives/Avatar/Avatar.tsx +63 -0
  156. package/components/primitives/Avatar/index.ts +8 -0
  157. package/components/primitives/Badge/Badge.stories.tsx +74 -0
  158. package/components/primitives/Badge/Badge.tsx +49 -0
  159. package/components/primitives/Badge/index.ts +2 -0
  160. package/components/primitives/Button/Button.stories.tsx +445 -0
  161. package/components/primitives/Button/Button.tsx +89 -0
  162. package/components/primitives/Button/index.ts +7 -0
  163. package/components/primitives/Card/Card.stories.tsx +831 -0
  164. package/components/primitives/Card/Card.tsx +242 -0
  165. package/components/primitives/Card/index.ts +30 -0
  166. package/components/primitives/Carousel/Carousel.stories.tsx +32 -0
  167. package/components/primitives/Carousel/Carousel.tsx +63 -0
  168. package/components/primitives/Carousel/index.ts +13 -0
  169. package/components/primitives/Chart/Chart.stories.tsx +346 -0
  170. package/components/primitives/Chart/Chart.tsx +117 -0
  171. package/components/primitives/Chart/index.ts +20 -0
  172. package/components/primitives/Checkbox/Checkbox.stories.tsx +87 -0
  173. package/components/primitives/Checkbox/Checkbox.tsx +38 -0
  174. package/components/primitives/Checkbox/index.ts +2 -0
  175. package/components/primitives/Collapsible/Collapsible.stories.tsx +38 -0
  176. package/components/primitives/Collapsible/Collapsible.tsx +39 -0
  177. package/components/primitives/Collapsible/index.ts +8 -0
  178. package/components/primitives/Command/Command.stories.tsx +150 -0
  179. package/components/primitives/Command/Command.tsx +147 -0
  180. package/components/primitives/Command/index.ts +20 -0
  181. package/components/primitives/Dialog/Dialog.stories.tsx +390 -0
  182. package/components/primitives/Dialog/Dialog.tsx +140 -0
  183. package/components/primitives/Dialog/index.ts +22 -0
  184. package/components/primitives/Drawer/Drawer.stories.tsx +327 -0
  185. package/components/primitives/Drawer/Drawer.tsx +208 -0
  186. package/components/primitives/Drawer/index.ts +27 -0
  187. package/components/primitives/DropdownMenu/DropdownMenu.stories.tsx +150 -0
  188. package/components/primitives/DropdownMenu/DropdownMenu.tsx +73 -0
  189. package/components/primitives/DropdownMenu/index.ts +1 -0
  190. package/components/primitives/HoverCard/HoverCard.stories.tsx +26 -0
  191. package/components/primitives/HoverCard/HoverCard.tsx +39 -0
  192. package/components/primitives/HoverCard/index.ts +8 -0
  193. package/components/primitives/Icon/Icon.stories.tsx +281 -0
  194. package/components/primitives/Icon/Icon.tsx +87 -0
  195. package/components/primitives/Icon/index.ts +8 -0
  196. package/components/primitives/Input/Input.stories.tsx +370 -0
  197. package/components/primitives/Input/Input.tsx +88 -0
  198. package/components/primitives/Input/index.ts +7 -0
  199. package/components/primitives/InputGroup/InputGroup.stories.tsx +40 -0
  200. package/components/primitives/InputGroup/InputGroup.tsx +72 -0
  201. package/components/primitives/InputGroup/index.ts +14 -0
  202. package/components/primitives/Label/Label.stories.tsx +227 -0
  203. package/components/primitives/Label/Label.tsx +53 -0
  204. package/components/primitives/Label/index.ts +7 -0
  205. package/components/primitives/Popover/Popover.stories.tsx +42 -0
  206. package/components/primitives/Popover/Popover.tsx +107 -0
  207. package/components/primitives/Popover/index.ts +2 -0
  208. package/components/primitives/Progress/Progress.stories.tsx +340 -0
  209. package/components/primitives/Progress/Progress.tsx +31 -0
  210. package/components/primitives/Progress/index.ts +1 -0
  211. package/components/primitives/ScrollArea/ScrollArea.stories.tsx +26 -0
  212. package/components/primitives/ScrollArea/ScrollArea.tsx +28 -0
  213. package/components/primitives/ScrollArea/index.ts +6 -0
  214. package/components/primitives/Select/Select.stories.tsx +288 -0
  215. package/components/primitives/Select/Select.tsx +162 -0
  216. package/components/primitives/Select/index.ts +22 -0
  217. package/components/primitives/Separator/Separator.stories.tsx +264 -0
  218. package/components/primitives/Separator/Separator.tsx +48 -0
  219. package/components/primitives/Separator/index.ts +7 -0
  220. package/components/primitives/Sidebar/Sidebar.stories.tsx +358 -0
  221. package/components/primitives/Sidebar/Sidebar.tsx +317 -0
  222. package/components/primitives/Sidebar/index.ts +41 -0
  223. package/components/primitives/Table/Table.stories.tsx +389 -0
  224. package/components/primitives/Table/Table.tsx +191 -0
  225. package/components/primitives/Table/index.ts +26 -0
  226. package/components/primitives/Tabs/Tabs.stories.tsx +129 -0
  227. package/components/primitives/Tabs/Tabs.tsx +70 -0
  228. package/components/primitives/Tabs/index.ts +13 -0
  229. package/components/primitives/Textarea/Textarea.stories.tsx +358 -0
  230. package/components/primitives/Textarea/Textarea.tsx +91 -0
  231. package/components/primitives/Textarea/index.ts +7 -0
  232. package/components/primitives/ToggleGroup/ToggleGroup.stories.tsx +87 -0
  233. package/components/primitives/ToggleGroup/ToggleGroup.tsx +52 -0
  234. package/components/primitives/ToggleGroup/index.ts +6 -0
  235. package/components/primitives/Tooltip/Tooltip.stories.tsx +336 -0
  236. package/components/primitives/Tooltip/Tooltip.tsx +78 -0
  237. package/components/primitives/Tooltip/index.ts +10 -0
  238. package/components/primitives/index.ts +34 -0
  239. package/components/ui/accordion.tsx +66 -0
  240. package/components/ui/alert-dialog.tsx +157 -0
  241. package/components/ui/alert.tsx +66 -0
  242. package/components/ui/avatar.tsx +53 -0
  243. package/components/ui/badge.tsx +46 -0
  244. package/components/ui/button.tsx +60 -0
  245. package/components/ui/card.tsx +117 -0
  246. package/components/ui/carousel.tsx +241 -0
  247. package/components/ui/chart.tsx +334 -0
  248. package/components/ui/checkbox.tsx +32 -0
  249. package/components/ui/collapsible.tsx +33 -0
  250. package/components/ui/command.tsx +184 -0
  251. package/components/ui/dialog.tsx +143 -0
  252. package/components/ui/drawer.tsx +118 -0
  253. package/components/ui/dropdown-menu.tsx +257 -0
  254. package/components/ui/hover-card.tsx +44 -0
  255. package/components/ui/input-group.tsx +170 -0
  256. package/components/ui/input.tsx +48 -0
  257. package/components/ui/label.tsx +26 -0
  258. package/components/ui/popover.tsx +33 -0
  259. package/components/ui/progress.tsx +31 -0
  260. package/components/ui/scroll-area.tsx +58 -0
  261. package/components/ui/select.tsx +187 -0
  262. package/components/ui/separator.tsx +31 -0
  263. package/components/ui/sidebar.tsx +577 -0
  264. package/components/ui/table.tsx +120 -0
  265. package/components/ui/tabs.tsx +66 -0
  266. package/components/ui/textarea.tsx +46 -0
  267. package/components/ui/toggle-group.tsx +83 -0
  268. package/components/ui/toggle.tsx +47 -0
  269. package/components/ui/tooltip.tsx +61 -0
  270. package/dist/index.cjs +7389 -0
  271. package/dist/index.cjs.map +1 -0
  272. package/dist/index.css +75 -0
  273. package/dist/index.css.map +1 -0
  274. package/dist/index.js +7160 -0
  275. package/dist/index.js.map +1 -0
  276. package/hooks/useAIDocReviewer.d.ts +0 -0
  277. package/lib/utils.ts +6 -0
  278. package/package.json +140 -0
  279. package/tokens/color/base.json +14 -0
  280. package/tokens/color/dark.json +40 -0
  281. package/tokens/color/green.json +21 -0
  282. package/tokens/color/light.json +52 -0
  283. package/tokens/color/neutral.json +20 -0
  284. package/tokens/color/violet.json +21 -0
  285. package/tokens/spacing.json +22 -0
  286. package/utils/ai-editor/format-date.ts +41 -0
  287. package/utils/ai-editor/index.ts +22 -0
  288. package/utils/ai-editor/type-guards.ts +72 -0
  289. package/utils/ai-editor/validation.ts +130 -0
  290. package/utils/editor-annotations.ts +122 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Blocks
3
+ *
4
+ * Central export point for all block components.
5
+ * Blocks are composite components that extend AI elements with enhanced functionality.
6
+ */
7
+
8
+ // ToolCallDisplay Block
9
+ export { ToolCallDisplay } from './ToolCallDisplay'
10
+ export type { ToolCallDisplayProps, ToolCall } from './ToolCallDisplay'
11
+
12
+ // AgentIndicator Block
13
+ export { AgentIndicator } from './AgentIndicator'
14
+ export type { AgentIndicatorProps, SubAgent } from './AgentIndicator'
15
+
16
+ // UserMessage Block
17
+ export { UserMessage } from './UserMessage'
18
+ export type { UserMessageProps, UserMessageData } from './UserMessage'
19
+
20
+ // SpecialistMessage Block
21
+ export { SpecialistMessage } from './SpecialistMessage'
22
+ export type { SpecialistMessageProps, SpecialistMessageData } from './SpecialistMessage'
23
+
24
+ // OrchestratorMessage Block
25
+ export { OrchestratorMessage } from './OrchestratorMessage'
26
+ export type { OrchestratorMessageProps, OrchestratorMessageData } from './OrchestratorMessage'
27
+
28
+ // PromptInput Block
29
+ export { PromptInput } from './PromptInput'
30
+ export type { PromptInputBlockProps } from './PromptInput'
31
+
32
+ // FileQueue Block
33
+ export { FileQueue, FileStatusBadge } from './FileQueue'
34
+ export type { FileQueueProps, FileChangeData, FileStatusBadgeProps, FileStatus } from './FileQueue'
35
+
36
+ // Confirmation Block
37
+ export { Confirmation } from './Confirmation'
38
+ export type { ConfirmationProps } from './Confirmation'
39
+
40
+ // CommentBox Block
41
+ export { CommentBox } from './CommentBox'
42
+ export type { CommentBoxProps } from '@/types/ai-editor'
43
+
44
+ // DocumentEditor Block
45
+ export { DocumentEditor } from './DocumentEditor'
46
+ export type { DocumentEditorProps } from '@/types/ai-editor'
47
+
48
+ // ModeToggle Composite
49
+ export { ModeToggle } from './ModeToggle'
50
+ export type { ModeToggleProps } from './ModeToggle'
51
+
52
+ // ThemeSelector Composite
53
+ export { ThemeSelector } from './ThemeSelector'
54
+ export type { ThemeSelectorProps, Theme } from './ThemeSelector'
55
+
56
+ // TablePagination Composite
57
+ export { TablePagination } from './TablePagination'
58
+ export type { TablePaginationProps } from './TablePagination'
59
+
60
+ // TableToolbar Composite
61
+ export { TableToolbar } from './TableToolbar'
62
+ export type { TableToolbarProps } from './TableToolbar'
63
+
64
+ // StatsCard Composite
65
+ export { StatsCard } from './StatsCard'
66
+ export type { StatsCardProps } from './StatsCard'
67
+
68
+ // NavigationList Composite
69
+ export { NavigationList } from './NavigationList'
70
+ export type { NavigationListProps, NavigationItem } from './NavigationList'
71
+
72
+ // NavUser Composite
73
+ export { NavUser } from './NavUser'
74
+ export type { NavUserProps } from './NavUser'
75
+
76
+ // AppHeader Composite
77
+ export { AppHeader } from './AppHeader'
78
+ export type { AppHeaderProps } from './AppHeader'
79
+
80
+ // InteractiveChart Composite
81
+ export { InteractiveChart } from './InteractiveChart'
82
+ export type { InteractiveChartProps } from './InteractiveChart'
83
+
84
+ // DataTable Composite
85
+ export { DataTable } from './DataTable'
86
+ export type { DataTableProps } from './DataTable'
87
+
88
+ // PageContainer Composite
89
+ export { PageContainer } from './PageContainer'
90
+ export type { PageContainerProps } from './PageContainer'
@@ -0,0 +1,451 @@
1
+ /**
2
+ * AIDocEditor Behavior Tests
3
+ *
4
+ * Tests user interactions and state changes to prevent regressions.
5
+ * These tests validate functionality, not visual appearance.
6
+ */
7
+
8
+ import type { Meta, StoryObj } from '@storybook/react'
9
+ import { expect, fn, userEvent, within, waitFor, screen } from '@storybook/test'
10
+ import type { Annotation } from '@/types/ai-editor'
11
+ import { AIDocEditor } from './AIDocEditor'
12
+ import { currentUser, sampleAnnotations, sampleContent } from './AIDocEditor.mocks'
13
+
14
+ const meta: Meta<typeof AIDocEditor> = {
15
+ title: 'Features/AIDocEditor/Behaviors',
16
+ component: AIDocEditor,
17
+ tags: ['test'],
18
+ parameters: {
19
+ layout: 'fullscreen',
20
+ },
21
+ }
22
+
23
+ export default meta
24
+ type Story = StoryObj<typeof AIDocEditor>
25
+
26
+ // ============================================================================
27
+ // CRITICAL PRIORITY TESTS (100% coverage required)
28
+ // ============================================================================
29
+
30
+ /**
31
+ * Test: Click annotation opens CommentBox with thread
32
+ * Verifies that clicking an existing annotation displays the comment thread
33
+ */
34
+ export const ClickAnnotationOpensCommentBox: Story = {
35
+ args: {
36
+ content: sampleContent,
37
+ annotations: sampleAnnotations,
38
+ currentUser,
39
+ mode: 'review',
40
+ onAnnotationClick: fn(),
41
+ onAnnotationAdd: fn(),
42
+ onAnnotationUpdate: fn(),
43
+ },
44
+ play: async ({ canvasElement, args }) => {
45
+ const canvas = within(canvasElement)
46
+
47
+ // Wait for document to render
48
+ await waitFor(() => {
49
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
50
+ })
51
+
52
+ // Find and click the annotation (look for highlighted text)
53
+ // The annotation is on "t with various annot" based on sampleAnnotations
54
+ const annotatedText = canvas.getByText(/t with various annot/i)
55
+ await userEvent.click(annotatedText)
56
+
57
+ // Verify CommentBox appears with the comment thread
58
+ await waitFor(() => {
59
+ expect(canvas.getByText(/t with various annot/i)).toBeVisible()
60
+ })
61
+
62
+ // Verify reply input is present
63
+ await waitFor(() => {
64
+ expect(screen.getByPlaceholderText("Reply...")).toBeInTheDocument()
65
+ })
66
+ },
67
+ }
68
+
69
+ /**
70
+ * Test: Reply to comment adds to thread
71
+ * Verifies that adding a reply calls onAnnotationUpdate with updated thread
72
+ */
73
+ export const ReplyToCommentAddsToThread: Story = {
74
+ args: {
75
+ content: sampleContent,
76
+ annotations: sampleAnnotations,
77
+ currentUser,
78
+ mode: 'review',
79
+ onAnnotationClick: fn(),
80
+ onAnnotationAdd: fn(),
81
+ onAnnotationUpdate: fn(),
82
+ },
83
+ play: async ({ canvasElement, args }) => {
84
+ const canvas = within(canvasElement)
85
+
86
+ // Wait for document to render
87
+ await waitFor(() => {
88
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
89
+ })
90
+
91
+ // Click annotation to open CommentBox
92
+ const annotatedText = canvas.getByText(/t with various annot/i)
93
+ await userEvent.click(annotatedText)
94
+
95
+ // Wait for CommentBox and reply input
96
+ const replyInput = await screen.findByPlaceholderText("Reply...")
97
+
98
+ // Type a reply
99
+ await userEvent.type(replyInput, 'I agree, this needs more detail')
100
+
101
+ // Submit reply with Enter key
102
+ await userEvent.keyboard('{Enter}')
103
+
104
+ // Verify onAnnotationUpdate was called
105
+ await waitFor(() => {
106
+ expect(args.onAnnotationUpdate).toHaveBeenCalled()
107
+ })
108
+
109
+ // Verify the updated annotation has the new comment in thread
110
+ await waitFor(() => {
111
+ if (args.onAnnotationUpdate) {
112
+ const mockFn = args.onAnnotationUpdate as ReturnType<typeof fn>
113
+ const calls = mockFn.mock.calls
114
+ expect(calls.length).toBeGreaterThan(0)
115
+ const updatedAnnotation = calls[0][0] as Annotation
116
+ expect(updatedAnnotation.data.thread.length).toBe(2) // Original + reply
117
+ }
118
+ })
119
+ },
120
+ }
121
+
122
+ // ============================================================================
123
+ // HIGH PRIORITY TESTS (90% coverage required)
124
+ // ============================================================================
125
+
126
+ /**
127
+ * Test: ESC key closes CommentBox
128
+ * Verifies that pressing ESC closes an open CommentBox
129
+ */
130
+ export const EscKeyClosesCommentBox: Story = {
131
+ args: {
132
+ content: sampleContent,
133
+ annotations: sampleAnnotations,
134
+ currentUser,
135
+ mode: 'review',
136
+ onAnnotationClick: fn(),
137
+ onAnnotationAdd: fn(),
138
+ onAnnotationUpdate: fn(),
139
+ },
140
+ play: async ({ canvasElement, args }) => {
141
+ const canvas = within(canvasElement)
142
+
143
+ // Wait for document to render
144
+ await waitFor(() => {
145
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
146
+ })
147
+
148
+ // Click annotation to open CommentBox
149
+ const annotatedText = canvas.getByText(/t with various annot/i)
150
+ await userEvent.click(annotatedText)
151
+
152
+ // Wait for CommentBox to appear
153
+ const replyInput = await screen.findByPlaceholderText("Reply...")
154
+ expect(replyInput).toBeVisible()
155
+
156
+ // Press ESC key
157
+ await userEvent.keyboard('{Escape}')
158
+
159
+ // Verify CommentBox is closed (reply input should not be visible)
160
+ await waitFor(() => {
161
+ expect(screen.queryByPlaceholderText("Reply...")).not.toBeInTheDocument()
162
+ })
163
+ },
164
+ }
165
+
166
+ /**
167
+ * Test: Enter key submits reply
168
+ * Verifies that pressing Enter submits a reply without Shift
169
+ */
170
+ export const EnterKeySubmitsReply: Story = {
171
+ args: {
172
+ content: sampleContent,
173
+ annotations: sampleAnnotations,
174
+ currentUser,
175
+ mode: 'review',
176
+ onAnnotationClick: fn(),
177
+ onAnnotationAdd: fn(),
178
+ onAnnotationUpdate: fn(),
179
+ },
180
+ play: async ({ canvasElement, args }) => {
181
+ const canvas = within(canvasElement)
182
+
183
+ // Wait for document to render
184
+ await waitFor(() => {
185
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
186
+ })
187
+
188
+ // Click annotation to open CommentBox
189
+ const annotatedText = canvas.getByText(/t with various annot/i)
190
+ await userEvent.click(annotatedText)
191
+
192
+ // Wait for CommentBox and reply input
193
+ const replyInput = await screen.findByPlaceholderText("Reply...")
194
+
195
+ // Type a reply
196
+ await userEvent.type(replyInput, 'Test reply')
197
+
198
+ // Submit with Enter key
199
+ await userEvent.keyboard('{Enter}')
200
+
201
+ // Verify onAnnotationUpdate was called
202
+ await waitFor(() => {
203
+ expect(args.onAnnotationUpdate).toHaveBeenCalled()
204
+ })
205
+ },
206
+ }
207
+
208
+ /**
209
+ * Test: Shift+Enter adds line break without submitting
210
+ * Verifies that Shift+Enter inserts a line break instead of submitting
211
+ */
212
+ export const ShiftEnterAddsLineBreak: Story = {
213
+ args: {
214
+ content: sampleContent,
215
+ annotations: sampleAnnotations,
216
+ currentUser,
217
+ mode: 'review',
218
+ onAnnotationClick: fn(),
219
+ onAnnotationAdd: fn(),
220
+ onAnnotationUpdate: fn(),
221
+ },
222
+ play: async ({ canvasElement, args }) => {
223
+ const canvas = within(canvasElement)
224
+
225
+ // Wait for document to render
226
+ await waitFor(() => {
227
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
228
+ })
229
+
230
+ // Click annotation to open CommentBox
231
+ const annotatedText = canvas.getByText(/t with various annot/i)
232
+ await userEvent.click(annotatedText)
233
+
234
+ // Wait for CommentBox and reply input
235
+ const replyInput = await screen.findByPlaceholderText("Reply...") as HTMLTextAreaElement
236
+
237
+ // Type first line
238
+ await userEvent.type(replyInput, 'First line')
239
+
240
+ // Press Shift+Enter to add line break
241
+ await userEvent.keyboard('{Shift>}{Enter}{/Shift}')
242
+
243
+ // Type second line
244
+ await userEvent.type(replyInput, 'Second line')
245
+
246
+ // Verify onAnnotationUpdate was NOT called yet (Shift+Enter doesn't submit)
247
+ expect(args.onAnnotationUpdate).not.toHaveBeenCalled()
248
+
249
+ // Verify textarea contains both lines with line break
250
+ expect(replyInput.value).toContain('First line\nSecond line')
251
+ },
252
+ }
253
+
254
+ // ============================================================================
255
+ // MEDIUM PRIORITY TESTS (75% coverage required)
256
+ // ============================================================================
257
+
258
+ /**
259
+ * Test: CommentBox positions near annotation
260
+ * Verifies that CommentBox appears within viewport when annotation is clicked
261
+ */
262
+ export const CommentBoxPositionsNearAnnotation: Story = {
263
+ args: {
264
+ content: sampleContent,
265
+ annotations: sampleAnnotations,
266
+ currentUser,
267
+ mode: 'review',
268
+ onAnnotationClick: fn(),
269
+ onAnnotationAdd: fn(),
270
+ onAnnotationUpdate: fn(),
271
+ },
272
+ play: async ({ canvasElement, args }) => {
273
+ const canvas = within(canvasElement)
274
+
275
+ // Wait for document to render
276
+ await waitFor(() => {
277
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
278
+ })
279
+
280
+ // Click annotation to open CommentBox
281
+ const annotatedText = canvas.getByText(/t with various annot/i)
282
+ await userEvent.click(annotatedText)
283
+
284
+ // Wait for CommentBox to appear
285
+ const replyInput = await screen.findByPlaceholderText("Reply...")
286
+
287
+ // Verify CommentBox is visible (within viewport)
288
+ expect(replyInput).toBeVisible()
289
+
290
+ // Verify CommentBox has position styling (absolute positioning)
291
+ const popoverContent = replyInput.closest('[role="dialog"]') || replyInput.closest('.popover-content')
292
+ expect(popoverContent).toBeInTheDocument()
293
+ },
294
+ }
295
+
296
+ /**
297
+ * Test: Readonly mode disables interactions
298
+ * Verifies that readonly mode prevents comment creation
299
+ */
300
+ export const ReadonlyModeDisablesInteractions: Story = {
301
+ args: {
302
+ content: sampleContent,
303
+ annotations: sampleAnnotations,
304
+ currentUser,
305
+ mode: 'readonly',
306
+ onAnnotationClick: fn(),
307
+ onAnnotationAdd: fn(),
308
+ onAnnotationUpdate: fn(),
309
+ },
310
+ play: async ({ canvasElement, args }) => {
311
+ const canvas = within(canvasElement)
312
+
313
+ // Wait for document to render
314
+ await waitFor(() => {
315
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
316
+ })
317
+
318
+ // Click annotation to open CommentBox (should still work in readonly)
319
+ const annotatedText = canvas.getByText(/t with various annot/i)
320
+ await userEvent.click(annotatedText)
321
+
322
+ // Wait for CommentBox to appear
323
+ await waitFor(() => {
324
+ expect(canvas.getByText(/t with various annot/i)).toBeVisible()
325
+ })
326
+
327
+ // Verify reply input is present (readonly mode still allows viewing)
328
+ // Note: The actual readonly behavior depends on component implementation
329
+ // This test verifies the component renders in readonly mode
330
+ const replyInput = screen.queryByPlaceholderText("Reply...")
331
+
332
+ // In readonly mode, reply input should still be present but interactions are limited
333
+ // The key is that onAnnotationAdd should not be called for new comments
334
+ expect(args.onAnnotationAdd).not.toHaveBeenCalled()
335
+ },
336
+ }
337
+
338
+ // ============================================================================
339
+ // EDGE CASE TESTS (60% coverage required)
340
+ // ============================================================================
341
+
342
+ /**
343
+ * Test: Empty reply submission prevented
344
+ * Verifies that empty replies cannot be submitted
345
+ */
346
+ export const EmptyReplySubmissionPrevented: Story = {
347
+ args: {
348
+ content: sampleContent,
349
+ annotations: sampleAnnotations,
350
+ currentUser,
351
+ mode: 'review',
352
+ onAnnotationClick: fn(),
353
+ onAnnotationAdd: fn(),
354
+ onAnnotationUpdate: fn(),
355
+ },
356
+ play: async ({ canvasElement, args }) => {
357
+ const canvas = within(canvasElement)
358
+
359
+ // Wait for document to render
360
+ await waitFor(() => {
361
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
362
+ })
363
+
364
+ // Click annotation to open CommentBox
365
+ const annotatedText = canvas.getByText(/t with various annot/i)
366
+ await userEvent.click(annotatedText)
367
+
368
+ // Wait for CommentBox and reply input
369
+ const replyInput = await screen.findByPlaceholderText("Reply...")
370
+
371
+ // Try to submit empty reply with Enter
372
+ await userEvent.click(replyInput)
373
+ await userEvent.keyboard('{Enter}')
374
+
375
+ // Verify onAnnotationUpdate was NOT called (empty submission prevented)
376
+ expect(args.onAnnotationUpdate).not.toHaveBeenCalled()
377
+ },
378
+ }
379
+
380
+ /**
381
+ * Test: Whitespace-only reply prevented
382
+ * Verifies that replies with only whitespace cannot be submitted
383
+ */
384
+ export const WhitespaceOnlyReplyPrevented: Story = {
385
+ args: {
386
+ content: sampleContent,
387
+ annotations: sampleAnnotations,
388
+ currentUser,
389
+ mode: 'review',
390
+ onAnnotationClick: fn(),
391
+ onAnnotationAdd: fn(),
392
+ onAnnotationUpdate: fn(),
393
+ },
394
+ play: async ({ canvasElement, args }) => {
395
+ const canvas = within(canvasElement)
396
+
397
+ // Wait for document to render
398
+ await waitFor(() => {
399
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
400
+ })
401
+
402
+ // Click annotation to open CommentBox
403
+ const annotatedText = canvas.getByText(/t with various annot/i)
404
+ await userEvent.click(annotatedText)
405
+
406
+ // Wait for CommentBox and reply input
407
+ const replyInput = await screen.findByPlaceholderText("Reply...")
408
+
409
+ // Type only whitespace
410
+ await userEvent.type(replyInput, ' ')
411
+
412
+ // Try to submit with Enter
413
+ await userEvent.keyboard('{Enter}')
414
+
415
+ // Verify onAnnotationUpdate was NOT called (whitespace-only prevented)
416
+ expect(args.onAnnotationUpdate).not.toHaveBeenCalled()
417
+ },
418
+ }
419
+
420
+ /**
421
+ * Test: Multiple annotations render correctly
422
+ * Verifies that multiple annotations can coexist and be clicked independently
423
+ */
424
+ export const MultipleAnnotationsRenderCorrectly: Story = {
425
+ args: {
426
+ content: sampleContent,
427
+ annotations: sampleAnnotations, // Contains both comment and suggestion
428
+ currentUser,
429
+ mode: 'review',
430
+ onAnnotationClick: fn(),
431
+ onAnnotationAdd: fn(),
432
+ onAnnotationUpdate: fn(),
433
+ },
434
+ play: async ({ canvasElement, args }) => {
435
+ const canvas = within(canvasElement)
436
+
437
+ // Wait for document to render
438
+ await waitFor(() => {
439
+ expect(canvas.getByText(/Document Review Example/i)).toBeInTheDocument()
440
+ })
441
+
442
+ // Verify both annotations are present in the document
443
+ // First annotation: comment
444
+ expect(canvas.getByText(/t with various annot/i)).toBeInTheDocument()
445
+
446
+ // Second annotation: suggestion (check for the reason text or badge)
447
+ // Note: The exact text depends on how suggestions are rendered
448
+ // For now, just verify the document renders without errors
449
+ expect(canvas.getByText(/Document Review Example/i)).toBeVisible()
450
+ },
451
+ }
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Mock data for AIDocEditor stories and tests
3
+ *
4
+ * This file contains reusable mock data that can be imported by:
5
+ * - AIDocEditor.stories.tsx (regular stories)
6
+ * - AIDocEditor.behaviors.stories.tsx (behavior tests)
7
+ * - Any other test files that need AIDocEditor mock data
8
+ */
9
+
10
+ import type { JSONContent } from '@tiptap/core'
11
+ import type { Annotation, User } from '@/types/ai-editor'
12
+
13
+ /**
14
+ * Sample document content for stories and tests
15
+ */
16
+ export const sampleContent: JSONContent = {
17
+ type: 'doc',
18
+ content: [
19
+ {
20
+ type: 'heading',
21
+ attrs: { level: 1 },
22
+ content: [{ type: 'text', text: 'Document Review Example' }],
23
+ },
24
+ {
25
+ type: 'paragraph',
26
+ content: [
27
+ { type: 'text', text: 'This is a sample document with various annotations. ' },
28
+ { type: 'text', text: 'Some text has comments, ' },
29
+ { type: 'text', text: 'some has suggestions, ' },
30
+ { type: 'text', text: 'and some has block additions.' },
31
+ ],
32
+ },
33
+ {
34
+ type: 'paragraph',
35
+ content: [
36
+ { type: 'text', text: 'The editor supports inline annotations for comments and suggestions. ' },
37
+ { type: 'text', text: 'Click on any highlighted text to see the annotation details.' },
38
+ ],
39
+ },
40
+ ],
41
+ }
42
+
43
+ /**
44
+ * Current user for stories and tests
45
+ */
46
+ export const currentUser: User = {
47
+ id: 'user-1',
48
+ name: 'John Doe',
49
+ avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=John',
50
+ }
51
+
52
+ /**
53
+ * Sample annotations for stories and tests
54
+ */
55
+ export const sampleAnnotations: Annotation[] = [
56
+ {
57
+ type: 'comment',
58
+ id: 'comment-1',
59
+ range: { from: 50, to: 70 },
60
+ createdAt: Date.now() - 3600000,
61
+ userId: 'user-1',
62
+ data: {
63
+ thread: [
64
+ {
65
+ id: 'thread-1',
66
+ userId: 'user-1',
67
+ userName: 'John Doe',
68
+ avatarSrc: 'https://api.dicebear.com/7.x/avataaars/svg?seed=John',
69
+ contentRich: {
70
+ type: 'doc',
71
+ content: [
72
+ {
73
+ type: 'paragraph',
74
+ content: [{ type: 'text', text: 'This section needs clarification.' }],
75
+ },
76
+ ],
77
+ },
78
+ timestamp: Date.now() - 3600000,
79
+ },
80
+ ],
81
+ },
82
+ },
83
+ {
84
+ type: 'suggestion',
85
+ id: 'suggestion-1',
86
+ range: { from: 80, to: 100 },
87
+ createdAt: Date.now() - 1800000,
88
+ userId: 'ai-1',
89
+ data: {
90
+ action: 'delete',
91
+ oldText: 'some has suggestions',
92
+ reason: 'Remove redundant text',
93
+ thread: [],
94
+ },
95
+ },
96
+ ]