@devxcrew/react-ui 0.0.0-stage → 0.1.7

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 (326) hide show
  1. package/README.md +85 -2
  2. package/package.json +105 -4
  3. package/src/blocks/agent-chat-workspace/agent-chat-workspace.tsx +162 -0
  4. package/src/blocks/agent-chat-workspace/index.ts +2 -0
  5. package/src/blocks/agent-provider-settings/agent-provider-settings.tsx +183 -0
  6. package/src/blocks/agent-provider-settings/index.ts +2 -0
  7. package/src/blocks/agent-task-workspace/agent-task-workspace.tsx +48 -0
  8. package/src/blocks/agent-task-workspace/index.ts +2 -0
  9. package/src/blocks/analysis-context/analysis-context.tsx +6 -0
  10. package/src/blocks/analysis-context/index.ts +1 -0
  11. package/src/blocks/app-header/app-header.tsx +159 -0
  12. package/src/blocks/app-header/index.ts +2 -0
  13. package/src/blocks/archived-chat-workspace/archived-chat-workspace.tsx +29 -0
  14. package/src/blocks/archived-chat-workspace/index.ts +2 -0
  15. package/src/blocks/auth/README.md +32 -0
  16. package/src/blocks/auth/admin-login-page.tsx +15 -0
  17. package/src/blocks/auth/admin-password-forgot-page.tsx +5 -0
  18. package/src/blocks/auth/auth-page-layout.tsx +90 -0
  19. package/src/blocks/auth/client-login-page.tsx +20 -0
  20. package/src/blocks/auth/client-password-forgot-page.tsx +5 -0
  21. package/src/blocks/auth/identity-management-desk.tsx +530 -0
  22. package/src/blocks/auth/index.ts +12 -0
  23. package/src/blocks/auth/login-page.tsx +206 -0
  24. package/src/blocks/auth/password-forgot-page.tsx +60 -0
  25. package/src/blocks/auth/portal-pages.tsx +32 -0
  26. package/src/blocks/auth/privileged-desk.tsx +55 -0
  27. package/src/blocks/auth/register-page.tsx +117 -0
  28. package/src/blocks/auth/session-boundary.tsx +296 -0
  29. package/src/blocks/auth/super-admin-login-page.tsx +15 -0
  30. package/src/blocks/auth/super-admin-password-forgot-page.tsx +5 -0
  31. package/src/blocks/blog/blog-card.tsx +156 -0
  32. package/src/blocks/blog/blog-reader.tsx +175 -0
  33. package/src/blocks/blog/blog-types.ts +47 -0
  34. package/src/blocks/blog/index.ts +3 -0
  35. package/src/blocks/chat-composer/chat-composer.tsx +140 -0
  36. package/src/blocks/chat-composer/index.ts +1 -0
  37. package/src/blocks/chat-history/chat-history.tsx +59 -0
  38. package/src/blocks/chat-history/index.ts +1 -0
  39. package/src/blocks/chat-response-progress/chat-response-progress.tsx +34 -0
  40. package/src/blocks/chat-response-progress/index.ts +2 -0
  41. package/src/blocks/chat-runtime-controls/chat-runtime-controls.tsx +56 -0
  42. package/src/blocks/chat-runtime-controls/index.ts +2 -0
  43. package/src/blocks/chat-runtime-trace/chat-runtime-trace.tsx +71 -0
  44. package/src/blocks/chat-runtime-trace/index.ts +2 -0
  45. package/src/blocks/codex-connection-settings/codex-connection-settings.tsx +42 -0
  46. package/src/blocks/codex-connection-settings/index.ts +1 -0
  47. package/src/blocks/dropzone/dropzone-file-item.tsx +113 -0
  48. package/src/blocks/dropzone/dropzone-types.ts +32 -0
  49. package/src/blocks/dropzone/dropzone.tsx +216 -0
  50. package/src/blocks/dropzone/index.ts +3 -0
  51. package/src/blocks/ecommerce/cart/cart-drawer.tsx +228 -0
  52. package/src/blocks/ecommerce/cart/cart-types.ts +27 -0
  53. package/src/blocks/ecommerce/cart/index.ts +2 -0
  54. package/src/blocks/ecommerce/categories/categories-types.ts +23 -0
  55. package/src/blocks/ecommerce/categories/category-showcase.tsx +125 -0
  56. package/src/blocks/ecommerce/categories/index.ts +2 -0
  57. package/src/blocks/ecommerce/checkout/checkout-types.ts +48 -0
  58. package/src/blocks/ecommerce/checkout/checkout-wizard.tsx +468 -0
  59. package/src/blocks/ecommerce/checkout/index.ts +2 -0
  60. package/src/blocks/ecommerce/comparison/comparison-types.ts +28 -0
  61. package/src/blocks/ecommerce/comparison/index.ts +2 -0
  62. package/src/blocks/ecommerce/comparison/product-comparison.tsx +174 -0
  63. package/src/blocks/ecommerce/coupon-wallet/coupon-wallet-types.ts +21 -0
  64. package/src/blocks/ecommerce/coupon-wallet/coupon-wallet.tsx +154 -0
  65. package/src/blocks/ecommerce/coupon-wallet/index.ts +2 -0
  66. package/src/blocks/ecommerce/delivery-tracker/delivery-tracker-types.ts +20 -0
  67. package/src/blocks/ecommerce/delivery-tracker/delivery-tracker.tsx +169 -0
  68. package/src/blocks/ecommerce/delivery-tracker/index.ts +2 -0
  69. package/src/blocks/ecommerce/index.ts +12 -0
  70. package/src/blocks/ecommerce/payment-methods/index.ts +2 -0
  71. package/src/blocks/ecommerce/payment-methods/payment-method-selector.tsx +224 -0
  72. package/src/blocks/ecommerce/payment-methods/payment-methods-types.ts +21 -0
  73. package/src/blocks/ecommerce/price-history/index.ts +2 -0
  74. package/src/blocks/ecommerce/price-history/price-history-chart.tsx +217 -0
  75. package/src/blocks/ecommerce/price-history/price-history-types.ts +16 -0
  76. package/src/blocks/ecommerce/reviews/index.ts +2 -0
  77. package/src/blocks/ecommerce/reviews/reviews-section.tsx +309 -0
  78. package/src/blocks/ecommerce/reviews/reviews-types.ts +26 -0
  79. package/src/blocks/ecommerce/wishlist/index.ts +2 -0
  80. package/src/blocks/ecommerce/wishlist/wishlist-grid.tsx +149 -0
  81. package/src/blocks/ecommerce/wishlist/wishlist-types.ts +19 -0
  82. package/src/blocks/execution-status/README.md +25 -0
  83. package/src/blocks/execution-status/execution-status.tsx +135 -0
  84. package/src/blocks/execution-status/index.ts +2 -0
  85. package/src/blocks/file-tree/file-icons.tsx +84 -0
  86. package/src/blocks/file-tree/file-tree-node.tsx +158 -0
  87. package/src/blocks/file-tree/file-tree-types.ts +33 -0
  88. package/src/blocks/file-tree/file-tree.tsx +163 -0
  89. package/src/blocks/file-tree/index.ts +5 -0
  90. package/src/blocks/filter-builder/filter-builder.tsx +169 -0
  91. package/src/blocks/filter-builder/filter-row.tsx +164 -0
  92. package/src/blocks/filter-builder/filter-types.ts +61 -0
  93. package/src/blocks/filter-builder/index.ts +12 -0
  94. package/src/blocks/footer/footer-types.ts +35 -0
  95. package/src/blocks/footer/index.ts +2 -0
  96. package/src/blocks/footer/site-footer.tsx +212 -0
  97. package/src/blocks/form/form-block.tsx +113 -0
  98. package/src/blocks/form/form-lookup-field.tsx +60 -0
  99. package/src/blocks/form/index.ts +4 -0
  100. package/src/blocks/handover-stack/handover-stack.tsx +41 -0
  101. package/src/blocks/handover-stack/index.ts +1 -0
  102. package/src/blocks/idea-handover/idea-handover-readiness.ts +41 -0
  103. package/src/blocks/idea-handover/idea-handover-workspace.tsx +150 -0
  104. package/src/blocks/idea-handover/index.ts +4 -0
  105. package/src/blocks/kanban/index.ts +11 -0
  106. package/src/blocks/kanban/kanban-board.tsx +146 -0
  107. package/src/blocks/kanban/kanban-card.tsx +149 -0
  108. package/src/blocks/kanban/kanban-column.tsx +102 -0
  109. package/src/blocks/kanban/kanban-types.ts +38 -0
  110. package/src/blocks/loader/global-loader.tsx +90 -0
  111. package/src/blocks/loader/index.ts +1 -0
  112. package/src/blocks/mascot/README.md +21 -0
  113. package/src/blocks/mascot/index.ts +2 -0
  114. package/src/blocks/mascot/mascot.tsx +202 -0
  115. package/src/blocks/master-list/README.md +21 -0
  116. package/src/blocks/master-list/index.ts +12 -0
  117. package/src/blocks/master-list/master-form.tsx +157 -0
  118. package/src/blocks/master-list/master-list-desk.tsx +370 -0
  119. package/src/blocks/master-list/master-list.tsx +189 -0
  120. package/src/blocks/master-list/types.ts +18 -0
  121. package/src/blocks/mermaid-preview/index.ts +1 -0
  122. package/src/blocks/mermaid-preview/mermaid-preview.tsx +164 -0
  123. package/src/blocks/notifications/README.md +7 -0
  124. package/src/blocks/notifications/index.ts +1 -0
  125. package/src/blocks/notifications/notification-center-page.tsx +73 -0
  126. package/src/blocks/pricing/index.ts +7 -0
  127. package/src/blocks/pricing/pricing-table.tsx +173 -0
  128. package/src/blocks/pricing/pricing-types.ts +34 -0
  129. package/src/blocks/product-card/index.ts +2 -0
  130. package/src/blocks/product-card/product-card-types.ts +35 -0
  131. package/src/blocks/product-card/product-card.tsx +183 -0
  132. package/src/blocks/table/data-table-block.tsx +313 -0
  133. package/src/blocks/table/data-table-filter-menu.tsx +48 -0
  134. package/src/blocks/table/data-table-icon-menu-trigger.tsx +44 -0
  135. package/src/blocks/table/data-table-menu-content.tsx +32 -0
  136. package/src/blocks/table/data-table-pagination.tsx +149 -0
  137. package/src/blocks/table/data-table-row-actions.tsx +53 -0
  138. package/src/blocks/table/data-table-status.tsx +42 -0
  139. package/src/blocks/table/data-table-toolbar.tsx +124 -0
  140. package/src/blocks/table/data-table-totals.tsx +31 -0
  141. package/src/blocks/table/index.ts +13 -0
  142. package/src/blocks/workspace/index.ts +19 -0
  143. package/src/blocks/workspace/workspace-action-card.tsx +73 -0
  144. package/src/blocks/workspace/workspace-application-header.tsx +67 -0
  145. package/src/blocks/workspace/workspace-entity-card.tsx +48 -0
  146. package/src/blocks/workspace/workspace-health-summary.tsx +15 -0
  147. package/src/blocks/workspace/workspace-metric-card.tsx +97 -0
  148. package/src/blocks/workspace/workspace-page-header.tsx +43 -0
  149. package/src/blocks/workspace/workspace-publish-status.tsx +15 -0
  150. package/src/blocks/workspace/workspace-route-checklist.tsx +28 -0
  151. package/src/blocks/workspace/workspace-runtime-status.tsx +48 -0
  152. package/src/blocks/workspace/workspace-section-card.tsx +42 -0
  153. package/src/components/accordion.tsx +65 -0
  154. package/src/components/alert-dialog.tsx +160 -0
  155. package/src/components/alert.tsx +68 -0
  156. package/src/components/aspect-ratio.tsx +22 -0
  157. package/src/components/attachment.tsx +195 -0
  158. package/src/components/avatar.tsx +90 -0
  159. package/src/components/badge.tsx +55 -0
  160. package/src/components/breadcrumb.tsx +100 -0
  161. package/src/components/bubble.tsx +123 -0
  162. package/src/components/button-group.tsx +79 -0
  163. package/src/components/button.tsx +71 -0
  164. package/src/components/calendar.tsx +185 -0
  165. package/src/components/card.tsx +100 -0
  166. package/src/components/carousel.tsx +229 -0
  167. package/src/components/chart.tsx +338 -0
  168. package/src/components/checkbox.tsx +27 -0
  169. package/src/components/collapsible.tsx +15 -0
  170. package/src/components/combobox.tsx +269 -0
  171. package/src/components/command.tsx +181 -0
  172. package/src/components/compact-model-switcher.tsx +128 -0
  173. package/src/components/context-menu.tsx +244 -0
  174. package/src/components/dialog.tsx +138 -0
  175. package/src/components/direction.tsx +1 -0
  176. package/src/components/drawer.tsx +208 -0
  177. package/src/components/dropdown-menu.tsx +257 -0
  178. package/src/components/empty.tsx +93 -0
  179. package/src/components/field.tsx +232 -0
  180. package/src/components/hover-card.tsx +45 -0
  181. package/src/components/input-group.tsx +146 -0
  182. package/src/components/input-otp.tsx +83 -0
  183. package/src/components/input.tsx +19 -0
  184. package/src/components/item.tsx +188 -0
  185. package/src/components/kbd.tsx +26 -0
  186. package/src/components/label.tsx +17 -0
  187. package/src/components/markdown-content.tsx +113 -0
  188. package/src/components/marker.tsx +67 -0
  189. package/src/components/menubar.tsx +267 -0
  190. package/src/components/message-scroller.tsx +91 -0
  191. package/src/components/message.tsx +84 -0
  192. package/src/components/native-select.tsx +54 -0
  193. package/src/components/navigation-menu.tsx +160 -0
  194. package/src/components/pagination.tsx +118 -0
  195. package/src/components/popover.tsx +74 -0
  196. package/src/components/progress.tsx +82 -0
  197. package/src/components/questionnaire.tsx +150 -0
  198. package/src/components/radio-group.tsx +35 -0
  199. package/src/components/resizable.tsx +41 -0
  200. package/src/components/rich-text-editor.tsx +604 -0
  201. package/src/components/scroll-area.tsx +47 -0
  202. package/src/components/select.tsx +189 -0
  203. package/src/components/separator.tsx +18 -0
  204. package/src/components/sheet.tsx +125 -0
  205. package/src/components/sidebar.tsx +693 -0
  206. package/src/components/skeleton.tsx +13 -0
  207. package/src/components/slider.tsx +51 -0
  208. package/src/components/sonner.tsx +45 -0
  209. package/src/components/sparkline.tsx +231 -0
  210. package/src/components/spinner.tsx +20 -0
  211. package/src/components/status-badge.tsx +114 -0
  212. package/src/components/switch.tsx +31 -0
  213. package/src/components/table.tsx +91 -0
  214. package/src/components/tabs.tsx +72 -0
  215. package/src/components/textarea.tsx +17 -0
  216. package/src/components/toast.tsx +222 -0
  217. package/src/components/toggle-group.tsx +85 -0
  218. package/src/components/toggle.tsx +42 -0
  219. package/src/components/tooltip.tsx +53 -0
  220. package/src/design-system/README.md +55 -0
  221. package/src/design-system/action-intents.ts +21 -0
  222. package/src/design-system/agent-manifest.ts +83 -0
  223. package/src/design-system/block-registry.ts +272 -0
  224. package/src/design-system/component-registry.ts +142 -0
  225. package/src/design-system/contracts.ts +69 -0
  226. package/src/design-system/defaults.ts +3 -0
  227. package/src/design-system/index.ts +29 -0
  228. package/src/design-system/mcp-server.ts +156 -0
  229. package/src/design-system/page-registry.ts +74 -0
  230. package/src/design-system/selection.ts +67 -0
  231. package/src/design-system/template-registry.ts +33 -0
  232. package/src/features/interface-topology/index.ts +14 -0
  233. package/src/features/interface-topology/interface-topology.types.ts +40 -0
  234. package/src/features/interface-topology/topology-control.tsx +18 -0
  235. package/src/features/interface-topology/topology-inspector.tsx +228 -0
  236. package/src/features/interface-topology/topology-marker.tsx +47 -0
  237. package/src/features/interface-topology/topology-region.tsx +44 -0
  238. package/src/features/interface-topology/use-interface-topology.ts +178 -0
  239. package/src/hooks/use-mobile.ts +19 -0
  240. package/src/hooks/use-scroll-follow.ts +22 -0
  241. package/src/index.ts +11 -0
  242. package/src/layouts/README.md +65 -0
  243. package/src/layouts/agent-workspace/agent-activity-rail.tsx +101 -0
  244. package/src/layouts/agent-workspace/agent-workspace.tsx +27 -0
  245. package/src/layouts/agent-workspace/agent-workspace.types.ts +28 -0
  246. package/src/layouts/agent-workspace/index.ts +7 -0
  247. package/src/layouts/blog-header/blog-header-types.ts +25 -0
  248. package/src/layouts/blog-header/blog-header.tsx +172 -0
  249. package/src/layouts/blog-header/index.ts +2 -0
  250. package/src/layouts/documentation-workspace/README.md +8 -0
  251. package/src/layouts/documentation-workspace/documentation-workspace.tsx +34 -0
  252. package/src/layouts/documentation-workspace/index.ts +1 -0
  253. package/src/layouts/ecommerce-header/ecommerce-actions.tsx +89 -0
  254. package/src/layouts/ecommerce-header/ecommerce-announcement.tsx +43 -0
  255. package/src/layouts/ecommerce-header/ecommerce-drawer.tsx +97 -0
  256. package/src/layouts/ecommerce-header/ecommerce-header-types.ts +59 -0
  257. package/src/layouts/ecommerce-header/ecommerce-header.tsx +47 -0
  258. package/src/layouts/ecommerce-header/ecommerce-navigation.tsx +98 -0
  259. package/src/layouts/ecommerce-header/ecommerce-search.tsx +86 -0
  260. package/src/layouts/ecommerce-header/index.ts +2 -0
  261. package/src/layouts/main-workspace/index.ts +28 -0
  262. package/src/layouts/mdi/index.ts +1 -0
  263. package/src/layouts/mdi/mdi.tsx +20 -0
  264. package/src/layouts/mdi-main/index.ts +27 -0
  265. package/src/layouts/mdi-main/mdi-app-catalog.generated.ts +159 -0
  266. package/src/layouts/mdi-main/mdi-app-catalog.ts +76 -0
  267. package/src/layouts/mdi-main/mdi-app-switcher.tsx +107 -0
  268. package/src/layouts/mdi-main/mdi-feature-settings.tsx +135 -0
  269. package/src/layouts/mdi-main/mdi-global-search.tsx +192 -0
  270. package/src/layouts/mdi-main/mdi-main.tsx +241 -0
  271. package/src/layouts/mdi-main/mdi-notifications-menu.tsx +197 -0
  272. package/src/layouts/mdi-main/mdi-profile-menu.tsx +79 -0
  273. package/src/layouts/mdi-main/mdi-sidebar-navigation.tsx +226 -0
  274. package/src/layouts/mdi-main/mdi-sidebar.tsx +110 -0
  275. package/src/layouts/mdi-main/mdi-status-bar.tsx +26 -0
  276. package/src/layouts/mdi-main/mdi-top-menu-control.ts +4 -0
  277. package/src/layouts/mdi-main/mdi-top-menu.tsx +117 -0
  278. package/src/layouts/mdi-main/mdi-topology.tsx +297 -0
  279. package/src/layouts/mdi-main/mdi-tweak-panel.tsx +104 -0
  280. package/src/layouts/mdi-main/mdi-types.ts +131 -0
  281. package/src/layouts/mdi-main/use-mdi-features.ts +36 -0
  282. package/src/layouts/mdi-main/use-mdi-sidebar-state.ts +59 -0
  283. package/src/layouts/site-header/announcement-bar.tsx +55 -0
  284. package/src/layouts/site-header/category-strip.tsx +41 -0
  285. package/src/layouts/site-header/index.ts +16 -0
  286. package/src/layouts/site-header/main-navbar.tsx +179 -0
  287. package/src/layouts/site-header/mobile-drawer.tsx +140 -0
  288. package/src/layouts/site-header/site-header-types.ts +63 -0
  289. package/src/layouts/site-header/site-header.tsx +58 -0
  290. package/src/lib/random-id.ts +19 -0
  291. package/src/lib/utils.ts +6 -0
  292. package/src/providers/pdf-provider.ts +137 -0
  293. package/src/styles/globals.css +374 -0
  294. package/src/templates/README.md +12 -0
  295. package/src/templates/dashboard-01/components/app-sidebar.tsx +177 -0
  296. package/src/templates/dashboard-01/components/chart-area-interactive.tsx +276 -0
  297. package/src/templates/dashboard-01/components/data-table-cell-viewer.tsx +213 -0
  298. package/src/templates/dashboard-01/components/data-table.tsx +577 -0
  299. package/src/templates/dashboard-01/components/nav-documents.tsx +79 -0
  300. package/src/templates/dashboard-01/components/nav-main.tsx +55 -0
  301. package/src/templates/dashboard-01/components/nav-secondary.tsx +39 -0
  302. package/src/templates/dashboard-01/components/nav-user.tsx +97 -0
  303. package/src/templates/dashboard-01/components/section-cards.tsx +99 -0
  304. package/src/templates/dashboard-01/components/site-header.tsx +14 -0
  305. package/src/templates/dashboard-01/data.json +614 -0
  306. package/src/templates/dashboard-01/index.ts +7 -0
  307. package/src/templates/documentation-sidebar/app-sidebar.tsx +95 -0
  308. package/src/templates/documentation-sidebar/index.ts +2 -0
  309. package/src/templates/sidebar-07/components/app-sidebar.tsx +175 -0
  310. package/src/templates/sidebar-07/components/nav-main.tsx +64 -0
  311. package/src/templates/sidebar-07/components/nav-projects.tsx +79 -0
  312. package/src/templates/sidebar-07/components/nav-user.tsx +105 -0
  313. package/src/templates/sidebar-07/components/team-switcher.tsx +94 -0
  314. package/src/templates/sidebar-07/index.ts +1 -0
  315. package/src/templates/ui-page/index.ts +7 -0
  316. package/src/templates/ui-page/ui-browser-frame.tsx +27 -0
  317. package/src/templates/ui-page/ui-template-code.tsx +48 -0
  318. package/src/templates/ui-page/ui-template-header.tsx +87 -0
  319. package/src/templates/ui-page/ui-template-navigation.tsx +60 -0
  320. package/src/templates/ui-page/ui-template-page.tsx +97 -0
  321. package/src/theme/index.ts +12 -0
  322. package/src/theme/theme-config.ts +30 -0
  323. package/src/theme/theme-provider.tsx +142 -0
  324. package/src/theme/theme-selector.tsx +75 -0
  325. package/src/tokens/index.ts +15 -0
  326. package/src/vite-env.d.ts +3 -0
@@ -0,0 +1,216 @@
1
+ import { CloudUpload, FileCheck } from 'lucide-react'
2
+ import { useRef, useState } from 'react'
3
+ import { Button } from '../../components/button'
4
+ import { cn } from '../../lib/utils'
5
+ import { DropzoneFileItem, formatBytes } from './dropzone-file-item'
6
+ import type { DropzoneProps } from './dropzone-types'
7
+
8
+ function validateFile(
9
+ file: File,
10
+ accept?: readonly string[],
11
+ maxSizeBytes?: number,
12
+ ): string | null {
13
+ if (maxSizeBytes && file.size > maxSizeBytes) {
14
+ return `File exceeds max size of ${formatBytes(maxSizeBytes)}`
15
+ }
16
+
17
+ if (accept && accept.length > 0) {
18
+ const ext = '.' + file.name.split('.').pop()?.toLowerCase()
19
+ const mime = file.type.toLowerCase()
20
+ const isAccepted = accept.some((pattern) => {
21
+ const p = pattern.toLowerCase().trim()
22
+ if (p.startsWith('.')) return ext === p
23
+ if (p.endsWith('/*')) return mime.startsWith(p.replace('/*', '/'))
24
+ return mime === p
25
+ })
26
+ if (!isAccepted) {
27
+ return `File type not supported (expected ${accept.join(', ')})`
28
+ }
29
+ }
30
+
31
+ return null
32
+ }
33
+
34
+ export function Dropzone({
35
+ accept,
36
+ allowMultiple = true,
37
+ ariaLabel = 'File upload dropzone',
38
+ className,
39
+ disabled = false,
40
+ emptyMessage = 'No files staged for upload',
41
+ files = [],
42
+ hint,
43
+ maxFiles,
44
+ maxSizeBytes,
45
+ onClearFiles,
46
+ onFilesDrop,
47
+ onRemoveFile,
48
+ onRetryFile,
49
+ showFileList = true,
50
+ title = 'Drag and drop files here, or click to browse',
51
+ }: DropzoneProps) {
52
+ const [isDragActive, setIsDragActive] = useState(false)
53
+ const inputRef = useRef<HTMLInputElement>(null)
54
+
55
+ function handleProcessFiles(incomingFiles: FileList | File[]) {
56
+ if (disabled) return
57
+ const fileArray = Array.from(incomingFiles)
58
+ const accepted: File[] = []
59
+ const rejected: { error: string; file: File }[] = []
60
+
61
+ for (const f of fileArray) {
62
+ if (maxFiles && files.length + accepted.length >= maxFiles) {
63
+ rejected.push({ error: `Exceeds max allowed file limit of ${maxFiles}`, file: f })
64
+ continue
65
+ }
66
+ const err = validateFile(f, accept, maxSizeBytes)
67
+ if (err) {
68
+ rejected.push({ error: err, file: f })
69
+ } else {
70
+ accepted.push(f)
71
+ }
72
+ }
73
+
74
+ onFilesDrop?.(accepted, rejected)
75
+ }
76
+
77
+ function handleDragOver(e: React.DragEvent) {
78
+ e.preventDefault()
79
+ e.stopPropagation()
80
+ if (!disabled && !isDragActive) {
81
+ setIsDragActive(true)
82
+ }
83
+ }
84
+
85
+ function handleDragLeave(e: React.DragEvent) {
86
+ e.preventDefault()
87
+ e.stopPropagation()
88
+ setIsDragActive(false)
89
+ }
90
+
91
+ function handleDrop(e: React.DragEvent) {
92
+ e.preventDefault()
93
+ e.stopPropagation()
94
+ setIsDragActive(false)
95
+ if (disabled || !e.dataTransfer.files) return
96
+ handleProcessFiles(e.dataTransfer.files)
97
+ }
98
+
99
+ function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
100
+ if (e.target.files) {
101
+ handleProcessFiles(e.target.files)
102
+ }
103
+ if (inputRef.current) {
104
+ inputRef.current.value = ''
105
+ }
106
+ }
107
+
108
+ function handleBrowseClick() {
109
+ if (!disabled) {
110
+ inputRef.current?.click()
111
+ }
112
+ }
113
+
114
+ return (
115
+ <div className={cn('flex flex-col gap-4', className)}>
116
+ <div
117
+ aria-disabled={disabled}
118
+ aria-label={ariaLabel}
119
+ className={cn(
120
+ 'group relative flex flex-col items-center justify-center rounded-2xl border-2 border-dashed border-border/80 bg-muted/20 p-8 text-center transition-all cursor-pointer select-none',
121
+ isDragActive && 'border-primary bg-primary/5 ring-4 ring-primary/10',
122
+ disabled && 'cursor-not-allowed opacity-50',
123
+ !disabled && 'hover:border-foreground/30 hover:bg-muted/30',
124
+ )}
125
+ role="region"
126
+ tabIndex={disabled ? -1 : 0}
127
+ onClick={handleBrowseClick}
128
+ onDragLeave={handleDragLeave}
129
+ onDragOver={handleDragOver}
130
+ onDrop={handleDrop}
131
+ onKeyDown={(e) => {
132
+ if ((e.key === 'Enter' || e.key === ' ') && !disabled) {
133
+ e.preventDefault()
134
+ handleBrowseClick()
135
+ }
136
+ }}
137
+ >
138
+ <input
139
+ accept={accept?.join(',')}
140
+ className="sr-only"
141
+ disabled={disabled}
142
+ multiple={allowMultiple}
143
+ ref={inputRef}
144
+ type="file"
145
+ onChange={handleInputChange}
146
+ />
147
+
148
+ <div
149
+ className={cn(
150
+ 'flex size-12 items-center justify-center rounded-2xl bg-muted/80 text-muted-foreground transition-transform duration-200 group-hover:scale-105',
151
+ isDragActive && 'scale-110 bg-primary/15 text-primary',
152
+ )}
153
+ >
154
+ <CloudUpload className="size-6" />
155
+ </div>
156
+
157
+ <h4 className="mt-3 text-sm font-semibold tracking-tight text-foreground">{title}</h4>
158
+
159
+ <p className="mt-1 text-xs text-muted-foreground max-w-sm">
160
+ {hint ??
161
+ `Supports ${accept ? accept.join(', ') : 'any files'}${
162
+ maxSizeBytes ? ` up to ${formatBytes(maxSizeBytes)}` : ''
163
+ }`}
164
+ </p>
165
+
166
+ <Button
167
+ className="mt-4 pointer-events-none"
168
+ disabled={disabled}
169
+ size="sm"
170
+ variant="secondary"
171
+ >
172
+ Select from device
173
+ </Button>
174
+ </div>
175
+
176
+ {showFileList && (
177
+ <div className="flex flex-col gap-2">
178
+ <div className="flex items-center justify-between px-1">
179
+ <span className="flex items-center gap-1.5 text-xs font-semibold text-foreground">
180
+ <FileCheck className="size-3.5 text-muted-foreground" />
181
+ Staged files ({files.length}
182
+ {maxFiles ? ` / ${maxFiles}` : ''})
183
+ </span>
184
+
185
+ {files.length > 0 && onClearFiles && (
186
+ <button
187
+ className="text-xs text-muted-foreground hover:text-foreground"
188
+ type="button"
189
+ onClick={onClearFiles}
190
+ >
191
+ Clear all
192
+ </button>
193
+ )}
194
+ </div>
195
+
196
+ {files.length > 0 ? (
197
+ <div className="flex flex-col gap-2">
198
+ {files.map((file) => (
199
+ <DropzoneFileItem
200
+ file={file}
201
+ key={file.id}
202
+ onRemove={onRemoveFile}
203
+ onRetry={onRetryFile}
204
+ />
205
+ ))}
206
+ </div>
207
+ ) : (
208
+ <div className="rounded-xl border border-dashed border-border/60 py-4 text-center text-xs text-muted-foreground/70">
209
+ {emptyMessage}
210
+ </div>
211
+ )}
212
+ </div>
213
+ )}
214
+ </div>
215
+ )
216
+ }
@@ -0,0 +1,3 @@
1
+ export { Dropzone } from './dropzone'
2
+ export { DropzoneFileItem, formatBytes } from './dropzone-file-item'
3
+ export type { DropzoneFile, DropzoneFileStatus, DropzoneProps } from './dropzone-types'
@@ -0,0 +1,228 @@
1
+ import {
2
+ ArrowRightIcon,
3
+ MinusIcon,
4
+ PlusIcon,
5
+ ShoppingBagIcon,
6
+ Trash2Icon,
7
+ TruckIcon,
8
+ XIcon,
9
+ } from 'lucide-react'
10
+ import { cn } from '../../../lib/utils'
11
+ import { Button } from '../../../components/button'
12
+ import type { StorefrontCartProps } from './cart-types'
13
+
14
+ export function StorefrontCart({
15
+ className,
16
+ currencySymbol = '$',
17
+ freeShippingThreshold = 75,
18
+ isDrawer = true,
19
+ items,
20
+ onCheckout,
21
+ onClose,
22
+ onQuantityChange,
23
+ onRemoveItem,
24
+ open = true,
25
+ promoDiscountValue = 0,
26
+ }: StorefrontCartProps) {
27
+ if (isDrawer && !open) return null
28
+
29
+ const subtotal = items.reduce((acc, item) => acc + item.priceValue * item.quantity, 0)
30
+ const total = Math.max(0, subtotal - promoDiscountValue)
31
+ const freeShippingProgress = Math.min(100, Math.round((subtotal / freeShippingThreshold) * 100))
32
+ const remainingForFreeShipping = Math.max(0, freeShippingThreshold - subtotal)
33
+
34
+ const content = (
35
+ <div className="flex h-full flex-col justify-between bg-background">
36
+ {/* Header */}
37
+ <div className="flex items-center justify-between border-b border-border/80 px-5 py-4">
38
+ <div className="flex items-center gap-2">
39
+ <ShoppingBagIcon className="size-5 text-primary" />
40
+ <h2 className="text-base font-bold text-foreground">Shopping Cart</h2>
41
+ <span className="rounded-full bg-muted px-2 py-0.5 text-xs font-semibold text-muted-foreground">
42
+ {items.reduce((acc, it) => acc + it.quantity, 0)}
43
+ </span>
44
+ </div>
45
+ {isDrawer && onClose && (
46
+ <Button
47
+ size="icon"
48
+ variant="ghost"
49
+ className="size-8"
50
+ onClick={onClose}
51
+ aria-label="Close cart"
52
+ >
53
+ <XIcon className="size-4" />
54
+ </Button>
55
+ )}
56
+ </div>
57
+
58
+ {/* Free Shipping Progress Meter */}
59
+ <div className="border-b border-border/40 bg-muted/30 px-5 py-3 text-xs">
60
+ <div className="flex items-center gap-2 text-foreground font-medium mb-1.5">
61
+ <TruckIcon className="size-4 text-primary" />
62
+ {remainingForFreeShipping > 0 ? (
63
+ <span>
64
+ Add{' '}
65
+ <strong className="text-primary">
66
+ {currencySymbol}
67
+ {remainingForFreeShipping.toFixed(2)}
68
+ </strong>{' '}
69
+ more to get Free Shipping!
70
+ </span>
71
+ ) : (
72
+ <span className="font-semibold text-emerald-600 dark:text-emerald-400">
73
+ 🎉 Congratulations! You have unlocked Free Shipping!
74
+ </span>
75
+ )}
76
+ </div>
77
+ <div className="h-2 w-full rounded-full bg-border overflow-hidden">
78
+ <div
79
+ className="h-full bg-primary transition-all duration-300"
80
+ style={{ width: `${freeShippingProgress}%` }}
81
+ />
82
+ </div>
83
+ </div>
84
+
85
+ {/* Item List */}
86
+ <div className="flex-1 overflow-y-auto px-5 py-4 space-y-4">
87
+ {items.length === 0 ? (
88
+ <div className="flex flex-col items-center justify-center py-16 text-center text-muted-foreground">
89
+ <ShoppingBagIcon className="size-12 stroke-[1.2] mb-3 opacity-40" />
90
+ <p className="text-sm font-semibold text-foreground">Your cart is empty</p>
91
+ <p className="text-xs text-muted-foreground mt-1">
92
+ Add products to your cart to checkout.
93
+ </p>
94
+ </div>
95
+ ) : (
96
+ items.map((item) => (
97
+ <div
98
+ key={item.id}
99
+ className="flex items-center gap-3 rounded-xl border border-border/60 bg-card p-3 shadow-xs transition-colors hover:border-border"
100
+ >
101
+ <img
102
+ src={item.image}
103
+ alt={item.title}
104
+ className="size-18 rounded-lg object-cover bg-muted shrink-0 border border-border/40"
105
+ />
106
+
107
+ <div className="flex-1 min-w-0">
108
+ <h4 className="text-xs font-semibold text-foreground truncate">{item.title}</h4>
109
+ {item.variant && (
110
+ <p className="text-[11px] text-muted-foreground truncate">{item.variant}</p>
111
+ )}
112
+ <div className="flex items-baseline gap-1.5 mt-1">
113
+ <span className="text-xs font-bold text-foreground">{item.price}</span>
114
+ {item.originalPrice && (
115
+ <span className="text-[10px] text-muted-foreground line-through">
116
+ {item.originalPrice}
117
+ </span>
118
+ )}
119
+ </div>
120
+
121
+ {/* Quantity Stepper */}
122
+ <div className="flex items-center gap-2 mt-2">
123
+ <div className="flex items-center rounded-lg border border-border/80 bg-muted/40">
124
+ <button
125
+ type="button"
126
+ disabled={item.quantity <= 1}
127
+ onClick={() => onQuantityChange?.(item.id, item.quantity - 1)}
128
+ className="flex size-6 items-center justify-center text-muted-foreground hover:text-foreground disabled:opacity-30"
129
+ >
130
+ <MinusIcon className="size-3" />
131
+ </button>
132
+ <span className="w-6 text-center text-xs font-bold text-foreground">
133
+ {item.quantity}
134
+ </span>
135
+ <button
136
+ type="button"
137
+ onClick={() => onQuantityChange?.(item.id, item.quantity + 1)}
138
+ className="flex size-6 items-center justify-center text-muted-foreground hover:text-foreground"
139
+ >
140
+ <PlusIcon className="size-3" />
141
+ </button>
142
+ </div>
143
+
144
+ <button
145
+ type="button"
146
+ onClick={() => onRemoveItem?.(item.id)}
147
+ className="text-muted-foreground hover:text-destructive p-1 transition-colors"
148
+ aria-label={`Remove ${item.title}`}
149
+ >
150
+ <Trash2Icon className="size-3.5" />
151
+ </button>
152
+ </div>
153
+ </div>
154
+ </div>
155
+ ))
156
+ )}
157
+ </div>
158
+
159
+ {/* Order Summary & Checkout */}
160
+ {items.length > 0 && (
161
+ <div className="border-t border-border/80 bg-muted/20 p-5 space-y-3">
162
+ <div className="space-y-1.5 text-xs">
163
+ <div className="flex justify-between text-muted-foreground">
164
+ <span>Subtotal</span>
165
+ <span className="font-semibold text-foreground">
166
+ {currencySymbol}
167
+ {subtotal.toFixed(2)}
168
+ </span>
169
+ </div>
170
+ {promoDiscountValue > 0 && (
171
+ <div className="flex justify-between text-emerald-600 dark:text-emerald-400">
172
+ <span>Discount</span>
173
+ <span className="font-semibold">
174
+ -{currencySymbol}
175
+ {promoDiscountValue.toFixed(2)}
176
+ </span>
177
+ </div>
178
+ )}
179
+ <div className="flex justify-between text-muted-foreground">
180
+ <span>Shipping</span>
181
+ <span className="font-semibold text-foreground">
182
+ {remainingForFreeShipping === 0 ? 'FREE' : `${currencySymbol}9.99`}
183
+ </span>
184
+ </div>
185
+ <div className="border-t border-border/60 pt-2 flex justify-between text-sm font-bold text-foreground">
186
+ <span>Estimated Total</span>
187
+ <span>
188
+ {currencySymbol}
189
+ {total.toFixed(2)}
190
+ </span>
191
+ </div>
192
+ </div>
193
+
194
+ <Button className="w-full gap-2 shadow-xs" onClick={onCheckout}>
195
+ <span>Proceed to Checkout</span>
196
+ <ArrowRightIcon className="size-4" />
197
+ </Button>
198
+ </div>
199
+ )}
200
+ </div>
201
+ )
202
+
203
+ if (!isDrawer) {
204
+ return (
205
+ <div
206
+ className={cn(
207
+ 'rounded-2xl border border-border/80 bg-card overflow-hidden shadow-xs',
208
+ className,
209
+ )}
210
+ >
211
+ {content}
212
+ </div>
213
+ )
214
+ }
215
+
216
+ return (
217
+ <div className="fixed inset-0 z-50 flex justify-end bg-background/80 backdrop-blur-sm animate-in fade-in-0 duration-200">
218
+ <div
219
+ className={cn(
220
+ 'w-full max-w-md border-l border-border/80 shadow-2xl animate-in slide-in-from-right duration-300',
221
+ className,
222
+ )}
223
+ >
224
+ {content}
225
+ </div>
226
+ </div>
227
+ )
228
+ }
@@ -0,0 +1,27 @@
1
+ export type CartItem = {
2
+ readonly discount?: string
3
+ readonly id: string
4
+ readonly image: string
5
+ readonly inStock?: boolean
6
+ readonly originalPrice?: string
7
+ readonly price: string
8
+ readonly priceValue: number
9
+ readonly quantity: number
10
+ readonly title: string
11
+ readonly variant?: string
12
+ }
13
+
14
+ export type StorefrontCartProps = {
15
+ readonly className?: string
16
+ readonly currencySymbol?: string
17
+ readonly freeShippingThreshold?: number
18
+ readonly isDrawer?: boolean
19
+ readonly items: readonly CartItem[]
20
+ readonly onCheckout?: () => void
21
+ readonly onClose?: () => void
22
+ readonly onQuantityChange?: (itemId: string, newQuantity: number) => void
23
+ readonly onRemoveItem?: (itemId: string) => void
24
+ readonly open?: boolean
25
+ readonly promoCode?: string
26
+ readonly promoDiscountValue?: number
27
+ }
@@ -0,0 +1,2 @@
1
+ export * from './cart-drawer'
2
+ export * from './cart-types'
@@ -0,0 +1,23 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ export type StoreCategoryCard = {
4
+ readonly badge?: string
5
+ readonly href: string
6
+ readonly icon?: ReactNode
7
+ readonly id: string
8
+ readonly image?: string
9
+ readonly itemCount?: number
10
+ readonly subtitle?: string
11
+ readonly tags?: readonly string[]
12
+ readonly title: string
13
+ }
14
+
15
+ export type CategoryShowcaseProps = {
16
+ readonly categories: readonly StoreCategoryCard[]
17
+ readonly className?: string
18
+ readonly columns?: 2 | 3 | 4 | 6
19
+ readonly description?: string
20
+ readonly onSelectCategory?: (categoryId: string) => void
21
+ readonly title?: string
22
+ readonly variant?: 'cards' | 'minimal' | 'pills'
23
+ }
@@ -0,0 +1,125 @@
1
+ import { ArrowRightIcon } from 'lucide-react'
2
+ import { cn } from '../../../lib/utils'
3
+ import { Badge } from '../../../components/badge'
4
+ import type { CategoryShowcaseProps } from './categories-types'
5
+
6
+ export function CategoryShowcase({
7
+ categories,
8
+ className,
9
+ columns = 4,
10
+ description = 'Browse by catalog department and find curated essentials.',
11
+ onSelectCategory,
12
+ title = 'Shop by Category',
13
+ variant = 'cards',
14
+ }: CategoryShowcaseProps) {
15
+ const colClass = {
16
+ 2: 'grid-cols-1 sm:grid-cols-2',
17
+ 3: 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3',
18
+ 4: 'grid-cols-2 sm:grid-cols-3 lg:grid-cols-4',
19
+ 6: 'grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6',
20
+ }[columns]
21
+
22
+ if (variant === 'pills') {
23
+ return (
24
+ <div className={cn('flex flex-wrap items-center gap-2', className)}>
25
+ {categories.map((cat) => (
26
+ <button
27
+ key={cat.id}
28
+ type="button"
29
+ onClick={() => onSelectCategory?.(cat.id)}
30
+ className="group flex items-center gap-2 rounded-full border border-border/80 bg-card px-3.5 py-1.5 text-xs font-semibold text-foreground shadow-xs transition-all hover:border-primary hover:bg-primary/5"
31
+ >
32
+ {cat.icon}
33
+ <span>{cat.title}</span>
34
+ {cat.itemCount !== undefined && (
35
+ <span className="rounded-full bg-muted px-1.5 py-0.2 text-[10px] text-muted-foreground group-hover:text-primary">
36
+ {cat.itemCount}
37
+ </span>
38
+ )}
39
+ </button>
40
+ ))}
41
+ </div>
42
+ )
43
+ }
44
+
45
+ return (
46
+ <div className={cn('space-y-6', className)}>
47
+ {(title || description) && (
48
+ <div className="flex flex-col gap-1 sm:flex-row sm:items-end sm:justify-between">
49
+ <div>
50
+ {title && <h3 className="text-xl font-bold tracking-tight text-foreground">{title}</h3>}
51
+ {description && <p className="text-xs text-muted-foreground">{description}</p>}
52
+ </div>
53
+ </div>
54
+ )}
55
+
56
+ <div className={cn('grid gap-4', colClass)}>
57
+ {categories.map((cat) => (
58
+ <div
59
+ key={cat.id}
60
+ onClick={() => onSelectCategory?.(cat.id)}
61
+ className="group relative cursor-pointer overflow-hidden rounded-2xl border border-border/80 bg-card p-4 shadow-xs transition-all duration-200 hover:-translate-y-1 hover:border-primary/50 hover:shadow-md"
62
+ >
63
+ {cat.image ? (
64
+ <div className="aspect-4/3 w-full overflow-hidden rounded-xl bg-muted mb-3 relative">
65
+ <img
66
+ src={cat.image}
67
+ alt={cat.title}
68
+ className="h-full w-full object-cover transition-transform duration-300 group-hover:scale-105"
69
+ />
70
+ {cat.badge && (
71
+ <Badge variant="default" className="absolute top-2 left-2 text-[10px] font-bold">
72
+ {cat.badge}
73
+ </Badge>
74
+ )}
75
+ </div>
76
+ ) : (
77
+ <div className="flex items-center justify-between mb-3">
78
+ <div className="flex size-12 items-center justify-center rounded-xl bg-primary/10 text-primary transition-colors group-hover:bg-primary group-hover:text-primary-foreground">
79
+ {cat.icon}
80
+ </div>
81
+ {cat.badge && (
82
+ <Badge variant="default" className="text-[10px] font-bold">
83
+ {cat.badge}
84
+ </Badge>
85
+ )}
86
+ </div>
87
+ )}
88
+
89
+ <div className="space-y-1">
90
+ <div className="flex items-center justify-between">
91
+ <h4 className="text-sm font-bold text-foreground group-hover:text-primary transition-colors">
92
+ {cat.title}
93
+ </h4>
94
+ <ArrowRightIcon className="size-3.5 text-muted-foreground transition-transform group-hover:translate-x-1 group-hover:text-primary" />
95
+ </div>
96
+
97
+ {cat.subtitle && (
98
+ <p className="text-xs text-muted-foreground line-clamp-1">{cat.subtitle}</p>
99
+ )}
100
+
101
+ {cat.itemCount !== undefined && (
102
+ <p className="text-[11px] font-medium text-muted-foreground/80">
103
+ {cat.itemCount.toLocaleString()} products
104
+ </p>
105
+ )}
106
+
107
+ {cat.tags && cat.tags.length > 0 && (
108
+ <div className="flex flex-wrap gap-1 pt-2">
109
+ {cat.tags.slice(0, 3).map((tag) => (
110
+ <span
111
+ key={tag}
112
+ className="rounded bg-muted px-1.5 py-0.5 text-[10px] text-muted-foreground"
113
+ >
114
+ {tag}
115
+ </span>
116
+ ))}
117
+ </div>
118
+ )}
119
+ </div>
120
+ </div>
121
+ ))}
122
+ </div>
123
+ </div>
124
+ )
125
+ }
@@ -0,0 +1,2 @@
1
+ export * from './category-showcase'
2
+ export * from './categories-types'
@@ -0,0 +1,48 @@
1
+ export type CheckoutAddress = {
2
+ city: string
3
+ country: string
4
+ fullName: string
5
+ phone: string
6
+ postalCode: string
7
+ state: string
8
+ streetAddress: string
9
+ }
10
+
11
+ export type CheckoutShippingOption = {
12
+ deliveryEstimate: string
13
+ id: string
14
+ label: string
15
+ price: string
16
+ priceValue: number
17
+ }
18
+
19
+ export type CheckoutPaymentSelection = {
20
+ cvv?: string
21
+ method: 'card' | 'upi' | 'wallet' | 'cod'
22
+ savedCardId?: string
23
+ upiId?: string
24
+ }
25
+
26
+ export type CheckoutItemSummary = {
27
+ id: string
28
+ image: string
29
+ price: string
30
+ quantity: number
31
+ title: string
32
+ variant?: string
33
+ }
34
+
35
+ export type CheckoutWizardProps = {
36
+ readonly className?: string
37
+ readonly currencySymbol?: string
38
+ readonly defaultAddress?: Partial<CheckoutAddress>
39
+ readonly initialStep?: 1 | 2 | 3
40
+ readonly items: readonly CheckoutItemSummary[]
41
+ readonly onPlaceOrder?: (data: {
42
+ address: CheckoutAddress
43
+ payment: CheckoutPaymentSelection
44
+ shipping: CheckoutShippingOption
45
+ }) => void
46
+ readonly shippingOptions?: readonly CheckoutShippingOption[]
47
+ readonly subtotal: number
48
+ }