@locusai/web 0.1.7 → 0.2.2

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 (346) hide show
  1. package/CHANGELOG.md +26 -0
  2. package/next.config.js +15 -2
  3. package/package.json +26 -3
  4. package/src/app/(auth)/invite/page.tsx +109 -0
  5. package/src/app/(auth)/layout.tsx +19 -0
  6. package/src/app/(auth)/login/page.tsx +65 -0
  7. package/src/app/(auth)/onboarding/workspace/page.tsx +46 -0
  8. package/src/app/(auth)/register/page.tsx +165 -0
  9. package/src/app/(dashboard)/activity/page.tsx +7 -0
  10. package/src/app/(dashboard)/backlog/page.tsx +195 -0
  11. package/src/app/(dashboard)/board/page.tsx +141 -0
  12. package/src/app/(dashboard)/layout.tsx +32 -0
  13. package/src/app/(dashboard)/page.tsx +14 -0
  14. package/src/app/(dashboard)/settings/page.tsx +161 -0
  15. package/src/app/(dashboard)/settings/team/page.tsx +75 -0
  16. package/src/app/globals.css +259 -0
  17. package/src/app/layout.tsx +10 -20
  18. package/src/app/providers.tsx +26 -3
  19. package/src/components/AuthLayoutUI.tsx +53 -0
  20. package/src/components/BoardFilter.tsx +75 -74
  21. package/src/components/CreateModal/CreateModal.tsx +142 -0
  22. package/src/components/CreateModal/index.ts +1 -0
  23. package/src/components/Editor.tsx +279 -0
  24. package/src/components/Header.tsx +99 -12
  25. package/src/components/PageLayout.tsx +69 -0
  26. package/src/components/PropertyItem.tsx +55 -9
  27. package/src/components/Sidebar.tsx +280 -36
  28. package/src/components/SprintCreateModal.tsx +84 -0
  29. package/src/components/TaskCard.tsx +196 -78
  30. package/src/components/TaskCreateModal.tsx +181 -178
  31. package/src/components/TaskPanel.tsx +140 -692
  32. package/src/components/WorkspaceCreateModal.tsx +97 -0
  33. package/src/components/WorkspaceProtected.tsx +91 -0
  34. package/src/components/auth/InviteSteps.tsx +220 -0
  35. package/src/components/auth/LoginSteps.tsx +86 -0
  36. package/src/components/auth/RegisterSteps.tsx +371 -0
  37. package/src/components/auth/index.ts +3 -0
  38. package/src/components/backlog/BacklogList.tsx +92 -0
  39. package/src/components/backlog/BacklogSection.tsx +137 -0
  40. package/src/components/backlog/CompletedSprintItem.tsx +95 -0
  41. package/src/components/backlog/CompletedSprintsSection.tsx +77 -0
  42. package/src/components/backlog/SprintSection.tsx +155 -0
  43. package/src/components/backlog/constants.ts +26 -0
  44. package/src/components/board/BoardColumn.tsx +97 -0
  45. package/src/components/board/BoardContent.tsx +84 -0
  46. package/src/components/board/BoardEmptyState.tsx +82 -0
  47. package/src/components/board/BoardHeader.tsx +47 -0
  48. package/src/components/board/SprintMindmap.tsx +65 -0
  49. package/src/components/board/constants.ts +40 -0
  50. package/src/components/board/index.ts +5 -0
  51. package/src/components/common/ErrorState.tsx +124 -0
  52. package/src/components/common/LoadingState.tsx +83 -0
  53. package/src/components/common/index.ts +40 -0
  54. package/src/components/dashboard/ActivityFeed.tsx +77 -0
  55. package/src/components/dashboard/ActivityItem.tsx +207 -0
  56. package/src/components/dashboard/QuickActions.tsx +50 -0
  57. package/src/components/dashboard/StatCard.tsx +79 -0
  58. package/src/components/dnd/index.tsx +51 -0
  59. package/src/components/docs/DocsEditorArea.tsx +87 -0
  60. package/src/components/docs/DocsHeaderActions.tsx +121 -0
  61. package/src/components/docs/DocsSidebar.tsx +351 -0
  62. package/src/components/index.ts +7 -0
  63. package/src/components/onboarding/StepProgress.tsx +21 -0
  64. package/src/components/onboarding/index.ts +1 -0
  65. package/src/components/settings/ApiKeyConfirmationModal.tsx +81 -0
  66. package/src/components/settings/ApiKeyCreatedModal.tsx +96 -0
  67. package/src/components/settings/ApiKeysList.tsx +143 -0
  68. package/src/components/settings/ApiKeysSettings.tsx +144 -0
  69. package/src/components/settings/InviteMemberModal.tsx +106 -0
  70. package/src/components/settings/ProjectSetupGuide.tsx +147 -0
  71. package/src/components/settings/SettingItem.tsx +32 -0
  72. package/src/components/settings/SettingSection.tsx +50 -0
  73. package/src/components/settings/TeamInvitationsList.tsx +90 -0
  74. package/src/components/settings/TeamMembersList.tsx +95 -0
  75. package/src/components/settings/index.ts +8 -0
  76. package/src/components/task-panel/TaskActivity.tsx +127 -0
  77. package/src/components/task-panel/TaskChecklist.tsx +142 -0
  78. package/src/components/task-panel/TaskDescription.tsx +201 -0
  79. package/src/components/task-panel/TaskDocs.tsx +137 -0
  80. package/src/components/task-panel/TaskHeader.tsx +125 -0
  81. package/src/components/task-panel/TaskProperties.tsx +111 -0
  82. package/src/components/task-panel/index.ts +12 -0
  83. package/src/components/typography/EmptyStateText.tsx +59 -0
  84. package/src/components/typography/MetadataText.tsx +65 -0
  85. package/src/components/typography/SecondaryText.tsx +60 -0
  86. package/src/components/typography/SectionLabel.tsx +60 -0
  87. package/src/components/typography/index.ts +14 -0
  88. package/src/components/typography-scales.tsx +218 -0
  89. package/src/components/ui/Avatar.tsx +123 -0
  90. package/src/components/ui/Badge.tsx +69 -2
  91. package/src/components/ui/Button.tsx +71 -30
  92. package/src/components/ui/Checkbox.tsx +34 -0
  93. package/src/components/ui/Dropdown.tsx +67 -1
  94. package/src/components/ui/EmptyState.tsx +129 -0
  95. package/src/components/ui/Input.tsx +53 -6
  96. package/src/components/ui/Modal.tsx +45 -12
  97. package/src/components/ui/OtpInput.tsx +148 -0
  98. package/src/components/ui/Skeleton.tsx +36 -0
  99. package/src/components/ui/Spinner.tsx +112 -0
  100. package/src/components/ui/Textarea.tsx +28 -3
  101. package/src/components/ui/Toast.tsx +99 -0
  102. package/src/components/ui/Toggle.tsx +63 -0
  103. package/src/components/ui/constants.ts +108 -0
  104. package/src/components/ui/index.ts +7 -0
  105. package/src/context/AuthContext.tsx +140 -0
  106. package/src/context/index.ts +1 -0
  107. package/src/hooks/backlog/index.ts +13 -0
  108. package/src/hooks/backlog/useBacklogActions.ts +144 -0
  109. package/src/hooks/backlog/useBacklogComposite.ts +73 -0
  110. package/src/hooks/backlog/useBacklogData.ts +74 -0
  111. package/src/hooks/backlog/useBacklogDragDrop.ts +118 -0
  112. package/src/hooks/backlog/useBacklogUI.ts +74 -0
  113. package/src/hooks/index.ts +22 -0
  114. package/src/hooks/task-panel/index.ts +13 -0
  115. package/src/hooks/task-panel/useTaskActions.ts +200 -0
  116. package/src/hooks/task-panel/useTaskComputedValues.ts +30 -0
  117. package/src/hooks/task-panel/useTaskData.ts +78 -0
  118. package/src/hooks/task-panel/useTaskPanelComposite.ts +161 -0
  119. package/src/hooks/task-panel/useTaskUIState.ts +80 -0
  120. package/src/hooks/useAuthLayoutLogic.ts +43 -0
  121. package/src/hooks/useAuthenticatedUser.ts +36 -0
  122. package/src/hooks/useAuthenticatedUserWithOrg.ts +41 -0
  123. package/src/hooks/useBacklog.ts +303 -0
  124. package/src/hooks/useBoard.ts +230 -0
  125. package/src/hooks/useDashboardLayout.ts +49 -0
  126. package/src/hooks/useDocs.ts +279 -0
  127. package/src/hooks/useDocsQuery.ts +99 -0
  128. package/src/hooks/useDocsSidebarState.ts +104 -0
  129. package/src/hooks/useFormState.ts +40 -0
  130. package/src/hooks/useGlobalKeydowns.ts +52 -0
  131. package/src/hooks/useInviteForm.ts +122 -0
  132. package/src/hooks/useLoginForm.ts +84 -0
  133. package/src/hooks/useMutationWithToast.ts +56 -0
  134. package/src/hooks/useOrganizationQuery.ts +55 -0
  135. package/src/hooks/useRegisterForm.ts +216 -0
  136. package/src/hooks/useSprintsQuery.ts +38 -0
  137. package/src/hooks/useTaskDescription.ts +102 -0
  138. package/src/hooks/useTaskPanel.ts +341 -0
  139. package/src/hooks/useTasksQuery.ts +39 -0
  140. package/src/hooks/useTeamManagement.ts +92 -0
  141. package/src/hooks/useWorkspaceCreateForm.ts +70 -0
  142. package/src/hooks/useWorkspaceId.ts +29 -0
  143. package/src/lib/api-client.ts +40 -23
  144. package/src/lib/config.ts +25 -0
  145. package/src/lib/constants.ts +83 -0
  146. package/src/lib/options.ts +96 -0
  147. package/src/lib/query-keys.ts +91 -0
  148. package/src/lib/typography.ts +103 -0
  149. package/src/lib/utils.ts +4 -0
  150. package/src/lib/validation.ts +192 -0
  151. package/src/services/index.ts +7 -3
  152. package/src/services/notifications.ts +80 -0
  153. package/src/utils/env.utils.ts +15 -0
  154. package/src/views/ActivityView.tsx +123 -0
  155. package/src/views/Dashboard.tsx +126 -0
  156. package/src/views/Docs.tsx +98 -612
  157. package/tsconfig.tsbuildinfo +1 -1
  158. package/.next/BUILD_ID +0 -1
  159. package/.next/app-build-manifest.json +0 -55
  160. package/.next/app-path-routes-manifest.json +0 -8
  161. package/.next/build-manifest.json +0 -33
  162. package/.next/cache/.previewinfo +0 -1
  163. package/.next/cache/.rscinfo +0 -1
  164. package/.next/cache/.tsbuildinfo +0 -1
  165. package/.next/cache/config.json +0 -7
  166. package/.next/cache/webpack/client-production/0.pack +0 -0
  167. package/.next/cache/webpack/client-production/index.pack +0 -0
  168. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  169. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  170. package/.next/cache/webpack/server-production/0.pack +0 -0
  171. package/.next/cache/webpack/server-production/index.pack +0 -0
  172. package/.next/diagnostics/build-diagnostics.json +0 -6
  173. package/.next/diagnostics/framework.json +0 -1
  174. package/.next/export-detail.json +0 -5
  175. package/.next/export-marker.json +0 -6
  176. package/.next/images-manifest.json +0 -57
  177. package/.next/next-minimal-server.js.nft.json +0 -1
  178. package/.next/next-server.js.nft.json +0 -1
  179. package/.next/package.json +0 -1
  180. package/.next/prerender-manifest.json +0 -137
  181. package/.next/react-loadable-manifest.json +0 -32
  182. package/.next/required-server-files.json +0 -324
  183. package/.next/routes-manifest.json +0 -77
  184. package/.next/server/app/_not-found/page.js +0 -2
  185. package/.next/server/app/_not-found/page.js.nft.json +0 -1
  186. package/.next/server/app/_not-found/page_client-reference-manifest.js +0 -1
  187. package/.next/server/app/_not-found.html +0 -1
  188. package/.next/server/app/_not-found.meta +0 -8
  189. package/.next/server/app/_not-found.rsc +0 -21
  190. package/.next/server/app/backlog/page.js +0 -2
  191. package/.next/server/app/backlog/page.js.nft.json +0 -1
  192. package/.next/server/app/backlog/page_client-reference-manifest.js +0 -1
  193. package/.next/server/app/backlog.html +0 -1
  194. package/.next/server/app/backlog.meta +0 -7
  195. package/.next/server/app/backlog.rsc +0 -25
  196. package/.next/server/app/docs/page.js +0 -97
  197. package/.next/server/app/docs/page.js.nft.json +0 -1
  198. package/.next/server/app/docs/page_client-reference-manifest.js +0 -1
  199. package/.next/server/app/docs.html +0 -1
  200. package/.next/server/app/docs.meta +0 -7
  201. package/.next/server/app/docs.rsc +0 -26
  202. package/.next/server/app/favicon.ico/route.js +0 -1
  203. package/.next/server/app/favicon.ico/route.js.nft.json +0 -1
  204. package/.next/server/app/favicon.ico.body +0 -0
  205. package/.next/server/app/favicon.ico.meta +0 -1
  206. package/.next/server/app/index.html +0 -1
  207. package/.next/server/app/index.meta +0 -7
  208. package/.next/server/app/index.rsc +0 -25
  209. package/.next/server/app/page.js +0 -2
  210. package/.next/server/app/page.js.nft.json +0 -1
  211. package/.next/server/app/page_client-reference-manifest.js +0 -1
  212. package/.next/server/app/settings/page.js +0 -2
  213. package/.next/server/app/settings/page.js.nft.json +0 -1
  214. package/.next/server/app/settings/page_client-reference-manifest.js +0 -1
  215. package/.next/server/app/settings.html +0 -1
  216. package/.next/server/app/settings.meta +0 -7
  217. package/.next/server/app/settings.rsc +0 -25
  218. package/.next/server/app-paths-manifest.json +0 -8
  219. package/.next/server/chunks/496.js +0 -6
  220. package/.next/server/chunks/585.js +0 -1
  221. package/.next/server/chunks/665.js +0 -1
  222. package/.next/server/chunks/699.js +0 -22
  223. package/.next/server/chunks/852.js +0 -7
  224. package/.next/server/functions-config-manifest.json +0 -4
  225. package/.next/server/interception-route-rewrite-manifest.js +0 -1
  226. package/.next/server/middleware-build-manifest.js +0 -1
  227. package/.next/server/middleware-manifest.json +0 -6
  228. package/.next/server/middleware-react-loadable-manifest.js +0 -1
  229. package/.next/server/next-font-manifest.js +0 -1
  230. package/.next/server/next-font-manifest.json +0 -1
  231. package/.next/server/pages/404.html +0 -1
  232. package/.next/server/pages/500.html +0 -1
  233. package/.next/server/pages/_app.js +0 -1
  234. package/.next/server/pages/_app.js.nft.json +0 -1
  235. package/.next/server/pages/_document.js +0 -1
  236. package/.next/server/pages/_document.js.nft.json +0 -1
  237. package/.next/server/pages/_error.js +0 -19
  238. package/.next/server/pages/_error.js.nft.json +0 -1
  239. package/.next/server/pages-manifest.json +0 -6
  240. package/.next/server/server-reference-manifest.js +0 -1
  241. package/.next/server/server-reference-manifest.json +0 -1
  242. package/.next/server/webpack-runtime.js +0 -1
  243. package/.next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
  244. package/.next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
  245. package/.next/static/chunks/138.b98511c56423f8bb.js +0 -1
  246. package/.next/static/chunks/146-34259952c594a3b0.js +0 -1
  247. package/.next/static/chunks/337-d3bb75304d130513.js +0 -1
  248. package/.next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
  249. package/.next/static/chunks/487-1808785ba665f784.js +0 -1
  250. package/.next/static/chunks/544.a9569941cc886e9d.js +0 -1
  251. package/.next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
  252. package/.next/static/chunks/902-d6926825a9fe8784.js +0 -1
  253. package/.next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
  254. package/.next/static/chunks/996.e0a334e6ae90900e.js +0 -1
  255. package/.next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
  256. package/.next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
  257. package/.next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
  258. package/.next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
  259. package/.next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
  260. package/.next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
  261. package/.next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
  262. package/.next/static/chunks/main-843ab130fc1be309.js +0 -1
  263. package/.next/static/chunks/main-app-123e879c5a937a00.js +0 -1
  264. package/.next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
  265. package/.next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
  266. package/.next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  267. package/.next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
  268. package/.next/static/css/13e8617b72f9d3aa.css +0 -1
  269. package/.next/static/css/8aea088cdc4338f0.css +0 -1
  270. package/.next/static/css/b301ab0424111664.css +0 -1
  271. package/.next/static/media/24c15609eaa28576-s.woff2 +0 -0
  272. package/.next/static/media/2c07349e02a7b712-s.woff2 +0 -0
  273. package/.next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
  274. package/.next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
  275. package/.next/static/media/4f77bef990aad698-s.woff2 +0 -0
  276. package/.next/static/media/627d916fd739a539-s.woff2 +0 -0
  277. package/.next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
  278. package/.next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
  279. package/.next/static/media/84602850c8fd81c3-s.woff2 +0 -0
  280. package/.next/trace +0 -46
  281. package/.next/types/app/backlog/page.ts +0 -84
  282. package/.next/types/app/docs/page.ts +0 -84
  283. package/.next/types/app/layout.ts +0 -84
  284. package/.next/types/app/page.ts +0 -84
  285. package/.next/types/app/settings/page.ts +0 -84
  286. package/.next/types/cache-life.d.ts +0 -141
  287. package/.next/types/package.json +0 -1
  288. package/next-env.d.ts +0 -5
  289. package/out/404.html +0 -1
  290. package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_buildManifest.js +0 -1
  291. package/out/_next/static/D0NXe04ZCLNDckV_quc8g/_ssgManifest.js +0 -1
  292. package/out/_next/static/chunks/138.b98511c56423f8bb.js +0 -1
  293. package/out/_next/static/chunks/146-34259952c594a3b0.js +0 -1
  294. package/out/_next/static/chunks/337-d3bb75304d130513.js +0 -1
  295. package/out/_next/static/chunks/477.1a6ecfe53375bd9c.js +0 -1
  296. package/out/_next/static/chunks/487-1808785ba665f784.js +0 -1
  297. package/out/_next/static/chunks/544.a9569941cc886e9d.js +0 -1
  298. package/out/_next/static/chunks/87c73c54-1f4741035a95c140.js +0 -1
  299. package/out/_next/static/chunks/902-d6926825a9fe8784.js +0 -1
  300. package/out/_next/static/chunks/955-c8f8f6235ae8f8c6.js +0 -1
  301. package/out/_next/static/chunks/996.e0a334e6ae90900e.js +0 -1
  302. package/out/_next/static/chunks/app/_not-found/page-44b1804abb44a34d.js +0 -1
  303. package/out/_next/static/chunks/app/backlog/page-dce1450769bfae8f.js +0 -1
  304. package/out/_next/static/chunks/app/docs/page-1efee819f25492cb.js +0 -1
  305. package/out/_next/static/chunks/app/layout-05f504c042b9f7ee.js +0 -1
  306. package/out/_next/static/chunks/app/page-3fd91aaaa4776ced.js +0 -1
  307. package/out/_next/static/chunks/app/settings/page-84e16c9638d657e4.js +0 -1
  308. package/out/_next/static/chunks/framework-152a1bc8c81c7458.js +0 -1
  309. package/out/_next/static/chunks/main-843ab130fc1be309.js +0 -1
  310. package/out/_next/static/chunks/main-app-123e879c5a937a00.js +0 -1
  311. package/out/_next/static/chunks/pages/_app-a050a8e6e4fb04cf.js +0 -1
  312. package/out/_next/static/chunks/pages/_error-3e422ffd891594de.js +0 -1
  313. package/out/_next/static/chunks/polyfills-42372ed130431b0a.js +0 -1
  314. package/out/_next/static/chunks/webpack-99a10a055b5bb9c4.js +0 -1
  315. package/out/_next/static/css/13e8617b72f9d3aa.css +0 -1
  316. package/out/_next/static/css/8aea088cdc4338f0.css +0 -1
  317. package/out/_next/static/css/b301ab0424111664.css +0 -1
  318. package/out/_next/static/media/24c15609eaa28576-s.woff2 +0 -0
  319. package/out/_next/static/media/2c07349e02a7b712-s.woff2 +0 -0
  320. package/out/_next/static/media/456105d6ea6d39e0-s.woff2 +0 -0
  321. package/out/_next/static/media/47cbc4e2adbc5db9-s.p.woff2 +0 -0
  322. package/out/_next/static/media/4f77bef990aad698-s.woff2 +0 -0
  323. package/out/_next/static/media/627d916fd739a539-s.woff2 +0 -0
  324. package/out/_next/static/media/63b255f18bea0ca9-s.woff2 +0 -0
  325. package/out/_next/static/media/70bd82ac89b4fa42-s.woff2 +0 -0
  326. package/out/_next/static/media/84602850c8fd81c3-s.woff2 +0 -0
  327. package/out/backlog.html +0 -1
  328. package/out/backlog.txt +0 -25
  329. package/out/docs.html +0 -1
  330. package/out/docs.txt +0 -26
  331. package/out/favicon.ico +0 -0
  332. package/out/index.html +0 -1
  333. package/out/index.txt +0 -25
  334. package/out/logo.png +0 -0
  335. package/out/settings.html +0 -1
  336. package/out/settings.txt +0 -25
  337. package/src/app/backlog/page.tsx +0 -19
  338. package/src/app/page.tsx +0 -16
  339. package/src/app/settings/page.tsx +0 -194
  340. package/src/hooks/useTasks.ts +0 -119
  341. package/src/services/doc.service.ts +0 -27
  342. package/src/services/sprint.service.ts +0 -24
  343. package/src/services/task.service.ts +0 -75
  344. package/src/views/Backlog.tsx +0 -691
  345. package/src/views/Board.tsx +0 -306
  346. /package/src/app/{docs → (dashboard)/docs}/page.tsx +0 -0
@@ -0,0 +1,123 @@
1
+ "use client";
2
+
3
+ import Image from "next/image";
4
+ import { cn } from "@/lib/utils";
5
+
6
+ /**
7
+ * Avatar component props
8
+ *
9
+ * @property name - User name for initials fallback and alt text
10
+ * @property src - Optional image URL (if not provided, shows initials)
11
+ * @property size - Avatar size (default: "md")
12
+ */
13
+ interface AvatarProps {
14
+ /** Image URL for avatar photo */
15
+ src?: string | null;
16
+ /** User name (used for initials and alt text) */
17
+ name: string;
18
+ /** Avatar size */
19
+ size?: "sm" | "md" | "lg";
20
+ /** Additional CSS classes */
21
+ className?: string;
22
+ }
23
+
24
+ const sizeClasses = {
25
+ sm: "h-6 w-6 text-[10px]",
26
+ md: "h-8 w-8 text-xs",
27
+ lg: "h-10 w-10 text-sm",
28
+ };
29
+
30
+ const imageSizes = {
31
+ sm: 24,
32
+ md: 32,
33
+ lg: 40,
34
+ };
35
+
36
+ /**
37
+ * Extract initials from user name
38
+ * @example getInitials("John Doe") // "JD"
39
+ */
40
+ function getInitials(name: string): string {
41
+ return name
42
+ .split(" ")
43
+ .map((n) => n[0])
44
+ .join("")
45
+ .toUpperCase()
46
+ .slice(0, 2);
47
+ }
48
+
49
+ /**
50
+ * Get consistent color from user name using hash
51
+ * Ensures same name always gets same color
52
+ */
53
+ function getColorFromName(name: string): string {
54
+ const colors = [
55
+ "bg-rose-500",
56
+ "bg-pink-500",
57
+ "bg-fuchsia-500",
58
+ "bg-purple-500",
59
+ "bg-violet-500",
60
+ "bg-indigo-500",
61
+ "bg-blue-500",
62
+ "bg-sky-500",
63
+ "bg-cyan-500",
64
+ "bg-teal-500",
65
+ "bg-emerald-500",
66
+ "bg-green-500",
67
+ "bg-lime-500",
68
+ "bg-yellow-500",
69
+ "bg-amber-500",
70
+ "bg-orange-500",
71
+ ];
72
+
73
+ let hash = 0;
74
+ for (let i = 0; i < name.length; i++) {
75
+ hash = name.charCodeAt(i) + ((hash << 5) - hash);
76
+ }
77
+
78
+ return colors[Math.abs(hash) % colors.length];
79
+ }
80
+
81
+ /**
82
+ * Avatar component
83
+ *
84
+ * Displays user avatar with image or initials fallback.
85
+ * Automatically assigns consistent colors based on name hash.
86
+ *
87
+ * @example
88
+ * // With image
89
+ * <Avatar src="/avatar.jpg" name="John Doe" />
90
+ *
91
+ * @example
92
+ * // Without image (shows initials)
93
+ * <Avatar name="Jane Smith" size="lg" />
94
+ */
95
+ export function Avatar({ src, name, size = "md", className }: AvatarProps) {
96
+ if (src) {
97
+ return (
98
+ <Image
99
+ src={src}
100
+ alt={name}
101
+ width={imageSizes[size]}
102
+ height={imageSizes[size]}
103
+ className={cn(
104
+ "rounded-full object-cover ring-2 ring-border/50",
105
+ className
106
+ )}
107
+ />
108
+ );
109
+ }
110
+
111
+ return (
112
+ <div
113
+ className={cn(
114
+ "rounded-full flex items-center justify-center font-bold text-white ring-2 ring-border/50",
115
+ sizeClasses[size],
116
+ getColorFromName(name),
117
+ className
118
+ )}
119
+ >
120
+ {getInitials(name)}
121
+ </div>
122
+ );
123
+ }
@@ -1,16 +1,54 @@
1
+ /**
2
+ * Badge component props
3
+ *
4
+ * @property variant - Badge color variant
5
+ * @property size - Badge size (default: "sm")
6
+ */
1
7
  interface BadgeProps {
8
+ /** Badge content */
2
9
  children: React.ReactNode;
3
- variant?: "default" | "success" | "warning" | "error" | "info" | "purple";
10
+ /** Badge color variant */
11
+ variant?:
12
+ | "default"
13
+ | "success"
14
+ | "warning"
15
+ | "error"
16
+ | "info"
17
+ | "purple"
18
+ | "primary"
19
+ | "secondary"
20
+ | "outline";
21
+ /** Badge size */
4
22
  size?: "sm" | "md";
23
+ /** Additional CSS classes */
24
+ className?: string;
5
25
  }
6
26
 
27
+ /**
28
+ * Badge component
29
+ *
30
+ * A small component used to display status, labels, or tags.
31
+ * Supports multiple variants and sizes.
32
+ *
33
+ * @example
34
+ * // Basic badge
35
+ * <Badge>New</Badge>
36
+ *
37
+ * @example
38
+ * // Success badge
39
+ * <Badge variant="success">Active</Badge>
40
+ */
7
41
  export function Badge({
8
42
  children,
9
43
  variant = "default",
10
44
  size = "sm",
45
+ className = "",
11
46
  }: BadgeProps) {
12
47
  const variants = {
13
48
  default: "bg-primary text-primary-foreground shadow hover:bg-primary/80",
49
+ primary: "bg-primary/15 text-primary border-primary/20",
50
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
51
+ outline: "text-foreground border-border",
14
52
  success: "bg-status-done/15 text-status-done border-status-done/20",
15
53
  warning:
16
54
  "bg-status-progress/15 text-status-progress border-status-progress/20",
@@ -26,14 +64,20 @@ export function Badge({
26
64
 
27
65
  return (
28
66
  <span
29
- className={`inline-flex items-center rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${variants[variant]} ${sizes[size]}`}
67
+ className={`inline-flex items-center rounded-md border transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 ${variants[variant]} ${sizes[size]} ${className}`}
30
68
  >
31
69
  {children}
32
70
  </span>
33
71
  );
34
72
  }
35
73
 
74
+ /**
75
+ * Priority badge component props
76
+ *
77
+ * @property priority - Task priority level
78
+ */
36
79
  interface PriorityBadgeProps {
80
+ /** Task priority level */
37
81
  priority: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
38
82
  }
39
83
 
@@ -44,12 +88,26 @@ const PRIORITY_CONFIG = {
44
88
  CRITICAL: { label: "Critical", variant: "error" as const },
45
89
  };
46
90
 
91
+ /**
92
+ * Priority badge component
93
+ *
94
+ * Displays task priority with appropriate color coding.
95
+ *
96
+ * @example
97
+ * <PriorityBadge priority="HIGH" />
98
+ */
47
99
  export function PriorityBadge({ priority }: PriorityBadgeProps) {
48
100
  const config = PRIORITY_CONFIG[priority];
49
101
  return <Badge variant={config.variant}>{config.label}</Badge>;
50
102
  }
51
103
 
104
+ /**
105
+ * Status badge component props
106
+ *
107
+ * @property status - Task status
108
+ */
52
109
  interface StatusBadgeProps {
110
+ /** Task status value */
53
111
  status: string;
54
112
  }
55
113
 
@@ -68,6 +126,15 @@ const STATUS_CONFIG: Record<
68
126
  BLOCKED: { label: "Blocked", variant: "error" },
69
127
  };
70
128
 
129
+ /**
130
+ * Status badge component
131
+ *
132
+ * Displays task status with appropriate color coding.
133
+ * Automatically maps status values to display labels and colors.
134
+ *
135
+ * @example
136
+ * <StatusBadge status="IN_PROGRESS" />
137
+ */
71
138
  export function StatusBadge({ status }: StatusBadgeProps) {
72
139
  const config = STATUS_CONFIG[status] || {
73
140
  label: status,
@@ -1,47 +1,88 @@
1
1
  import { type ButtonHTMLAttributes, forwardRef } from "react";
2
+ import { cn } from "@/lib/utils";
3
+ import { BUTTON_SIZES, BUTTON_VARIANTS } from "./constants";
4
+ import { Spinner } from "./Spinner";
2
5
 
3
- interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
4
- variant?:
5
- | "primary"
6
- | "secondary"
7
- | "outline"
8
- | "ghost"
9
- | "danger"
10
- | "success";
11
- size?: "sm" | "md" | "lg" | "icon";
6
+ /**
7
+ * Button component props
8
+ *
9
+ * @property variant - Button visual style (default: "primary")
10
+ * @property size - Button size (default: "md")
11
+ * @property isLoading - Show loading state with spinner
12
+ * @property loadingText - Text to show while loading (if not provided, shows original children)
13
+ */
14
+ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
15
+ /** Button visual style */
16
+ variant?: keyof typeof BUTTON_VARIANTS;
17
+ /** Button size */
18
+ size?: keyof typeof BUTTON_SIZES;
19
+ /** Show loading state */
20
+ isLoading?: boolean;
21
+ /** Text to display when loading */
22
+ loadingText?: string;
12
23
  }
13
24
 
14
25
  const Button = forwardRef<HTMLButtonElement, ButtonProps>(
15
- ({ className, variant = "primary", size = "md", ...props }, ref) => {
16
- const variants = {
17
- primary: "bg-primary text-primary-foreground shadow hover:bg-primary/90",
18
- secondary:
19
- "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
20
- outline:
21
- "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
22
- ghost: "hover:bg-accent hover:text-accent-foreground",
23
- danger:
24
- "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
25
- success: "bg-emerald-600 text-white shadow-sm hover:bg-emerald-700",
26
- };
27
-
28
- const sizes = {
29
- sm: "h-8 px-3 text-xs",
30
- md: "h-9 px-4 py-2 text-sm",
31
- lg: "h-10 px-8 text-base",
32
- icon: "h-9 w-9",
33
- };
26
+ (
27
+ {
28
+ className,
29
+ variant = "primary",
30
+ size = "md",
31
+ isLoading = false,
32
+ loadingText,
33
+ disabled,
34
+ children,
35
+ ...props
36
+ },
37
+ ref
38
+ ) => {
39
+ const isDisabled = isLoading || disabled;
34
40
 
35
41
  return (
36
42
  <button
37
43
  ref={ref}
38
- className={`inline-flex items-center justify-center gap-2 font-semibold rounded-lg transition-all duration-200 active:scale-95 disabled:opacity-50 disabled:pointer-events-none ${variants[variant]} ${sizes[size]} ${className || ""}`}
44
+ disabled={isDisabled}
45
+ className={cn(
46
+ "inline-flex items-center justify-center gap-2 font-semibold rounded-lg transition-all duration-200 active:scale-95 disabled:opacity-50 disabled:pointer-events-none",
47
+ BUTTON_VARIANTS[variant],
48
+ BUTTON_SIZES[size],
49
+ className
50
+ )}
51
+ aria-busy={isLoading}
39
52
  {...props}
40
- />
53
+ >
54
+ {isLoading ? (
55
+ <>
56
+ <Spinner size="sm" className="mr-2" aria-hidden="true" />
57
+ {loadingText || children}
58
+ </>
59
+ ) : (
60
+ children
61
+ )}
62
+ </button>
41
63
  );
42
64
  }
43
65
  );
44
66
 
67
+ /**
68
+ * Button component
69
+ *
70
+ * A reusable button component with multiple variants and sizes.
71
+ * Supports loading state with spinner and automatic disabling.
72
+ *
73
+ * @example
74
+ * // Primary button
75
+ * <Button onClick={handleClick}>Click me</Button>
76
+ *
77
+ * @example
78
+ * // Loading state
79
+ * <Button isLoading loadingText="Saving...">Save</Button>
80
+ *
81
+ * @example
82
+ * // Different variants
83
+ * <Button variant="danger">Delete</Button>
84
+ * <Button variant="ghost">Cancel</Button>
85
+ */
45
86
  Button.displayName = "Button";
46
87
 
47
88
  export { Button };
@@ -1,14 +1,46 @@
1
1
  import { Check } from "lucide-react";
2
2
  import { cn } from "@/lib/utils";
3
3
 
4
+ /**
5
+ * Checkbox component props
6
+ *
7
+ * @property checked - Whether checkbox is checked
8
+ * @property onChange - Callback when checkbox state changes
9
+ * @property label - Optional label text displayed next to checkbox
10
+ * @property disabled - Disable the checkbox (default: false)
11
+ */
4
12
  interface CheckboxProps {
13
+ /** Whether checkbox is currently checked */
5
14
  checked: boolean;
15
+ /** Callback when checkbox state changes */
6
16
  onChange: (checked: boolean) => void;
17
+ /** Optional label text */
7
18
  label?: string;
19
+ /** Disable checkbox interaction */
8
20
  disabled?: boolean;
21
+ /** Additional CSS classes */
9
22
  className?: string;
10
23
  }
11
24
 
25
+ /**
26
+ * Checkbox component
27
+ *
28
+ * A custom checkbox with optional label and full accessibility support.
29
+ * Automatically applies line-through style to label when checked.
30
+ *
31
+ * @example
32
+ * // Basic checkbox
33
+ * const [checked, setChecked] = useState(false);
34
+ * <Checkbox checked={checked} onChange={setChecked} />
35
+ *
36
+ * @example
37
+ * // With label
38
+ * <Checkbox
39
+ * checked={isSubscribed}
40
+ * onChange={setIsSubscribed}
41
+ * label="Subscribe to newsletter"
42
+ * />
43
+ */
12
44
  export function Checkbox({
13
45
  checked,
14
46
  onChange,
@@ -30,11 +62,13 @@ export function Checkbox({
30
62
  onChange={(e) => onChange(e.target.checked)}
31
63
  disabled={disabled}
32
64
  className="hidden"
65
+ aria-label={label}
33
66
  />
34
67
  <span
35
68
  className={`h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 transition-all duration-150 flex items-center justify-center ${
36
69
  checked ? "bg-primary text-primary-foreground" : "bg-transparent"
37
70
  }`}
71
+ aria-hidden="true"
38
72
  >
39
73
  {checked && <Check size={12} strokeWidth={3} />}
40
74
  </span>
@@ -3,22 +3,79 @@
3
3
  import { ChevronDown } from "lucide-react";
4
4
  import { type ReactNode, useEffect, useRef, useState } from "react";
5
5
 
6
+ /**
7
+ * Dropdown option configuration
8
+ *
9
+ * @property value - Unique value identifier
10
+ * @property label - Display label for option
11
+ * @property color - Optional color indicator
12
+ */
6
13
  interface DropdownOption<T extends string> {
14
+ /** Unique value identifier */
7
15
  value: T;
16
+ /** Display label */
8
17
  label: string;
18
+ /** Optional color for indicator dot */
9
19
  color?: string;
10
20
  }
11
21
 
22
+ /**
23
+ * Dropdown component props
24
+ *
25
+ * @property value - Currently selected value
26
+ * @property onChange - Callback when selection changes
27
+ * @property options - Available options to select from
28
+ * @property placeholder - Placeholder text when no selection
29
+ * @property label - Optional label above dropdown
30
+ * @property disabled - Disable dropdown interaction
31
+ * @property renderOption - Custom option rendering
32
+ */
12
33
  interface DropdownProps<T extends string> {
34
+ /** Currently selected value */
13
35
  value: T | undefined;
36
+ /** Callback when selection changes */
14
37
  onChange: (value: T) => void;
38
+ /** Available options */
15
39
  options: DropdownOption<T>[];
40
+ /** Placeholder text */
16
41
  placeholder?: string;
42
+ /** Optional label */
17
43
  label?: string;
44
+ /** Disable dropdown */
18
45
  disabled?: boolean;
46
+ /** Custom option renderer */
19
47
  renderOption?: (option: DropdownOption<T>) => ReactNode;
20
48
  }
21
49
 
50
+ /**
51
+ * Dropdown component
52
+ *
53
+ * A generic, type-safe dropdown select component with support for
54
+ * custom option rendering and color indicators.
55
+ *
56
+ * @example
57
+ * // Basic dropdown
58
+ * const [status, setStatus] = useState<"pending" | "done">();
59
+ * <Dropdown
60
+ * value={status}
61
+ * onChange={setStatus}
62
+ * options={[
63
+ * { value: "pending", label: "Pending" },
64
+ * { value: "done", label: "Done" },
65
+ * ]}
66
+ * />
67
+ *
68
+ * @example
69
+ * // With color indicators
70
+ * <Dropdown
71
+ * value={priority}
72
+ * onChange={setPriority}
73
+ * options={[
74
+ * { value: "low", label: "Low", color: "#22c55e" },
75
+ * { value: "high", label: "High", color: "#ef4444" },
76
+ * ]}
77
+ * />
78
+ */
22
79
  export function Dropdown<T extends string>({
23
80
  value,
24
81
  onChange,
@@ -59,6 +116,8 @@ export function Dropdown<T extends string>({
59
116
  className="w-full flex items-center justify-between gap-2 px-3.5 py-2.5 bg-background border border-input rounded-md text-foreground text-sm cursor-pointer transition-all hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-50 disabled:cursor-not-allowed shadow-sm"
60
117
  onClick={() => !disabled && setIsOpen(!isOpen)}
61
118
  disabled={disabled}
119
+ aria-haspopup="listbox"
120
+ aria-expanded={isOpen}
62
121
  >
63
122
  <span className={selectedOption ? "" : "text-muted-foreground"}>
64
123
  {selectedOption ? selectedOption.label : placeholder}
@@ -66,11 +125,15 @@ export function Dropdown<T extends string>({
66
125
  <ChevronDown
67
126
  size={16}
68
127
  className={`transition-transform duration-200 text-muted-foreground ${isOpen ? "rotate-180" : "rotate-0"}`}
128
+ aria-hidden="true"
69
129
  />
70
130
  </button>
71
131
 
72
132
  {isOpen && (
73
- <div className="absolute top-full left-0 right-0 mt-2 bg-popover border border-border rounded-md p-1 z-50 max-h-[240px] overflow-y-auto animate-in fade-in slide-in-from-top-2 duration-200 shadow-md">
133
+ <div
134
+ className="absolute top-full left-0 right-0 mt-2 bg-popover border border-border rounded-md p-1 z-50 max-h-[240px] overflow-y-auto animate-in fade-in slide-in-from-top-2 duration-200 shadow-md"
135
+ role="listbox"
136
+ >
74
137
  {options.map((option) => (
75
138
  <button
76
139
  key={option.value}
@@ -84,6 +147,8 @@ export function Dropdown<T extends string>({
84
147
  onChange(option.value);
85
148
  setIsOpen(false);
86
149
  }}
150
+ role="option"
151
+ aria-selected={option.value === value}
87
152
  >
88
153
  {renderOption ? (
89
154
  renderOption(option)
@@ -93,6 +158,7 @@ export function Dropdown<T extends string>({
93
158
  <span
94
159
  className="w-2 h-2 rounded-full shrink-0"
95
160
  style={{ background: option.color }}
161
+ aria-hidden="true"
96
162
  />
97
163
  )}
98
164
  {option.label}
@@ -0,0 +1,129 @@
1
+ "use client";
2
+
3
+ import { type LucideIcon, Sparkles } from "lucide-react";
4
+ import type { ReactNode } from "react";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ /**
8
+ * Empty state component props
9
+ *
10
+ * @property icon - Icon component to display
11
+ * @property title - Main empty state title
12
+ * @property description - Optional description text
13
+ * @property action - Optional action button/element
14
+ * @property variant - Display variant: default, compact, or minimal
15
+ */
16
+ interface EmptyStateProps {
17
+ /** Icon component (default: Sparkles) */
18
+ icon?: LucideIcon;
19
+ /** Empty state title */
20
+ title: string;
21
+ /** Optional description text */
22
+ description?: string;
23
+ /** Optional action element (e.g., Button) */
24
+ action?: ReactNode;
25
+ /** Additional CSS classes */
26
+ className?: string;
27
+ /** Display variant */
28
+ variant?: "default" | "compact" | "minimal";
29
+ }
30
+
31
+ /**
32
+ * Empty state component
33
+ *
34
+ * Displays a friendly empty state UI for when no data is available.
35
+ * Supports three variants for different contexts.
36
+ *
37
+ * @example
38
+ * // Default variant (large, centered)
39
+ * <EmptyState
40
+ * title="No tasks yet"
41
+ * description="Create your first task to get started"
42
+ * action={<Button>Create Task</Button>}
43
+ * />
44
+ *
45
+ * @example
46
+ * // Compact variant (smaller)
47
+ * <EmptyState
48
+ * variant="compact"
49
+ * title="No results"
50
+ * icon={Search}
51
+ * />
52
+ *
53
+ * @example
54
+ * // Minimal variant (inline)
55
+ * <EmptyState
56
+ * variant="minimal"
57
+ * title="No items"
58
+ * icon={ListX}
59
+ * />
60
+ */
61
+ export function EmptyState({
62
+ icon: Icon = Sparkles,
63
+ title,
64
+ description,
65
+ action,
66
+ className,
67
+ variant = "default",
68
+ }: EmptyStateProps) {
69
+ if (variant === "minimal") {
70
+ return (
71
+ <div
72
+ className={cn(
73
+ "flex flex-col items-center justify-center py-8 text-center",
74
+ className
75
+ )}
76
+ >
77
+ <Icon
78
+ size={24}
79
+ className="text-muted-foreground/40 mb-2"
80
+ aria-hidden="true"
81
+ />
82
+ <p className="text-sm text-muted-foreground/60 font-medium">{title}</p>
83
+ </div>
84
+ );
85
+ }
86
+
87
+ if (variant === "compact") {
88
+ return (
89
+ <div
90
+ className={cn(
91
+ "flex flex-col items-center justify-center p-6 text-center bg-secondary/10 rounded-xl border border-dashed border-border/50",
92
+ className
93
+ )}
94
+ >
95
+ <Icon size={20} className="text-primary/40 mb-2" aria-hidden="true" />
96
+ <h3 className="text-sm font-semibold text-foreground">{title}</h3>
97
+ {description && (
98
+ <p className="text-xs text-muted-foreground mt-1">{description}</p>
99
+ )}
100
+ {action && <div className="mt-3">{action}</div>}
101
+ </div>
102
+ );
103
+ }
104
+
105
+ return (
106
+ <div
107
+ className={cn(
108
+ "flex flex-col items-center justify-center p-12 text-center",
109
+ className
110
+ )}
111
+ >
112
+ <div className="relative mb-6">
113
+ <div className="absolute inset-0 rounded-full" />
114
+ <div className="relative flex items-center justify-center w-20 h-20 rounded-2xl bg-linear-to-br from-secondary to-secondary/30 border border-border/50">
115
+ <Icon size={32} className="text-primary/60" aria-hidden="true" />
116
+ </div>
117
+ </div>
118
+
119
+ <h3 className="text-xl font-bold text-foreground mb-2">{title}</h3>
120
+ {description && (
121
+ <p className="max-w-[300px] text-sm text-muted-foreground mb-8 leading-relaxed">
122
+ {description}
123
+ </p>
124
+ )}
125
+
126
+ {action && <div>{action}</div>}
127
+ </div>
128
+ );
129
+ }