@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,468 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ CheckCircle2Icon,
4
+ CreditCardIcon,
5
+ LockIcon,
6
+ MapPinIcon,
7
+ ShieldCheckIcon,
8
+ TruckIcon,
9
+ } from 'lucide-react'
10
+ import { cn } from '../../../lib/utils'
11
+ import { Button } from '../../../components/button'
12
+ import type {
13
+ CheckoutAddress,
14
+ CheckoutPaymentSelection,
15
+ CheckoutShippingOption,
16
+ CheckoutWizardProps,
17
+ } from './checkout-types'
18
+
19
+ const defaultOptions: readonly CheckoutShippingOption[] = [
20
+ {
21
+ deliveryEstimate: '3–5 business days',
22
+ id: 'standard',
23
+ label: 'Standard Delivery',
24
+ price: 'FREE',
25
+ priceValue: 0,
26
+ },
27
+ {
28
+ deliveryEstimate: '1–2 business days',
29
+ id: 'express',
30
+ label: 'Express Courier',
31
+ price: '$12.00',
32
+ priceValue: 12,
33
+ },
34
+ {
35
+ deliveryEstimate: 'Tomorrow by 12 PM',
36
+ id: 'overnight',
37
+ label: 'Priority Overnight',
38
+ price: '$24.00',
39
+ priceValue: 24,
40
+ },
41
+ ]
42
+
43
+ export function CheckoutWizard({
44
+ className,
45
+ currencySymbol = '$',
46
+ defaultAddress,
47
+ initialStep = 1,
48
+ items,
49
+ onPlaceOrder,
50
+ shippingOptions = defaultOptions,
51
+ subtotal,
52
+ }: CheckoutWizardProps) {
53
+ const [step, setStep] = useState<1 | 2 | 3>(initialStep)
54
+ const [address, setAddress] = useState<CheckoutAddress>({
55
+ city: defaultAddress?.city ?? 'San Francisco',
56
+ country: defaultAddress?.country ?? 'United States',
57
+ fullName: defaultAddress?.fullName ?? 'Alex Morgan',
58
+ phone: defaultAddress?.phone ?? '+1 (555) 349-2041',
59
+ postalCode: defaultAddress?.postalCode ?? '94107',
60
+ state: defaultAddress?.state ?? 'CA',
61
+ streetAddress: defaultAddress?.streetAddress ?? '450 Mission Street, Suite 800',
62
+ })
63
+
64
+ const [selectedShipping, setSelectedShipping] = useState<CheckoutShippingOption>(
65
+ shippingOptions[0] ?? defaultOptions[0]!,
66
+ )
67
+
68
+ const [payment, setPayment] = useState<CheckoutPaymentSelection>({
69
+ cvv: '884',
70
+ method: 'card',
71
+ savedCardId: 'card-1',
72
+ })
73
+
74
+ const total = subtotal + selectedShipping.priceValue
75
+
76
+ const handlePlaceOrder = () => {
77
+ onPlaceOrder?.({
78
+ address,
79
+ payment,
80
+ shipping: selectedShipping,
81
+ })
82
+ }
83
+
84
+ return (
85
+ <div className={cn('grid grid-cols-1 lg:grid-cols-12 gap-8', className)}>
86
+ {/* Left Column: Multi-Step Flow */}
87
+ <div className="lg:col-span-8 space-y-6">
88
+ {/* Step Indicator Header */}
89
+ <div className="flex items-center justify-between rounded-2xl border border-border/80 bg-card p-4 shadow-xs">
90
+ {[
91
+ { num: 1, title: 'Shipping & Delivery', icon: MapPinIcon },
92
+ { num: 2, title: 'Payment Method', icon: CreditCardIcon },
93
+ { num: 3, title: 'Review & Place Order', icon: ShieldCheckIcon },
94
+ ].map((s, idx) => {
95
+ const isDone = step > s.num
96
+ const isCurrent = step === s.num
97
+ const Icon = isDone ? CheckCircle2Icon : s.icon
98
+
99
+ return (
100
+ <div key={s.num} className="flex items-center gap-2">
101
+ <button
102
+ type="button"
103
+ onClick={() => isDone && setStep(s.num as 1 | 2 | 3)}
104
+ disabled={!isDone}
105
+ className={cn(
106
+ 'flex size-8 items-center justify-center rounded-full text-xs font-bold transition-all',
107
+ isDone && 'bg-emerald-600 text-white cursor-pointer',
108
+ isCurrent && 'bg-primary text-primary-foreground shadow-xs',
109
+ !isDone && !isCurrent && 'bg-muted text-muted-foreground',
110
+ )}
111
+ >
112
+ <Icon className="size-4" />
113
+ </button>
114
+ <div className="hidden sm:block text-left">
115
+ <div className="text-[10px] uppercase font-bold text-muted-foreground">
116
+ Step {s.num}
117
+ </div>
118
+ <div
119
+ className={cn(
120
+ 'text-xs font-semibold',
121
+ isCurrent ? 'text-foreground' : 'text-muted-foreground',
122
+ )}
123
+ >
124
+ {s.title}
125
+ </div>
126
+ </div>
127
+ {idx < 2 && <div className="h-px w-8 bg-border hidden md:block ml-2" />}
128
+ </div>
129
+ )
130
+ })}
131
+ </div>
132
+
133
+ {/* Step 1: Shipping Address & Delivery Speed */}
134
+ {step === 1 && (
135
+ <div className="space-y-6 rounded-2xl border border-border/80 bg-card p-6 shadow-xs">
136
+ <div className="flex items-center justify-between border-b border-border/60 pb-4">
137
+ <div>
138
+ <h3 className="text-base font-bold text-foreground">Shipping Destination</h3>
139
+ <p className="text-xs text-muted-foreground">Where should we deliver your order?</p>
140
+ </div>
141
+ </div>
142
+
143
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs">
144
+ <div className="space-y-1.5 sm:col-span-2">
145
+ <label className="font-semibold text-foreground">Full Name</label>
146
+ <input
147
+ type="text"
148
+ value={address.fullName}
149
+ onChange={(e) => setAddress({ ...address, fullName: e.target.value })}
150
+ className="w-full rounded-lg border border-input bg-muted/20 px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
151
+ />
152
+ </div>
153
+
154
+ <div className="space-y-1.5 sm:col-span-2">
155
+ <label className="font-semibold text-foreground">Street Address</label>
156
+ <input
157
+ type="text"
158
+ value={address.streetAddress}
159
+ onChange={(e) => setAddress({ ...address, streetAddress: e.target.value })}
160
+ className="w-full rounded-lg border border-input bg-muted/20 px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
161
+ />
162
+ </div>
163
+
164
+ <div className="space-y-1.5">
165
+ <label className="font-semibold text-foreground">City</label>
166
+ <input
167
+ type="text"
168
+ value={address.city}
169
+ onChange={(e) => setAddress({ ...address, city: e.target.value })}
170
+ className="w-full rounded-lg border border-input bg-muted/20 px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
171
+ />
172
+ </div>
173
+
174
+ <div className="space-y-1.5">
175
+ <label className="font-semibold text-foreground">State / Region</label>
176
+ <input
177
+ type="text"
178
+ value={address.state}
179
+ onChange={(e) => setAddress({ ...address, state: e.target.value })}
180
+ className="w-full rounded-lg border border-input bg-muted/20 px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
181
+ />
182
+ </div>
183
+
184
+ <div className="space-y-1.5">
185
+ <label className="font-semibold text-foreground">Postal / Zip Code</label>
186
+ <input
187
+ type="text"
188
+ value={address.postalCode}
189
+ onChange={(e) => setAddress({ ...address, postalCode: e.target.value })}
190
+ className="w-full rounded-lg border border-input bg-muted/20 px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
191
+ />
192
+ </div>
193
+
194
+ <div className="space-y-1.5">
195
+ <label className="font-semibold text-foreground">Phone Number</label>
196
+ <input
197
+ type="text"
198
+ value={address.phone}
199
+ onChange={(e) => setAddress({ ...address, phone: e.target.value })}
200
+ className="w-full rounded-lg border border-input bg-muted/20 px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
201
+ />
202
+ </div>
203
+ </div>
204
+
205
+ {/* Shipping Speed Options */}
206
+ <div className="pt-4 border-t border-border/60 space-y-3">
207
+ <label className="text-xs font-bold text-foreground">Select Delivery Speed</label>
208
+ <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
209
+ {shippingOptions.map((opt) => (
210
+ <button
211
+ key={opt.id}
212
+ type="button"
213
+ onClick={() => setSelectedShipping(opt)}
214
+ className={cn(
215
+ 'flex flex-col text-left p-3.5 rounded-xl border transition-all text-xs',
216
+ selectedShipping.id === opt.id
217
+ ? 'border-primary bg-primary/5 ring-1 ring-primary'
218
+ : 'border-border/80 bg-muted/20 hover:border-border',
219
+ )}
220
+ >
221
+ <div className="flex items-center justify-between font-bold text-foreground">
222
+ <span>{opt.label}</span>
223
+ <span className="text-primary">{opt.price}</span>
224
+ </div>
225
+ <span className="text-[11px] text-muted-foreground mt-1">
226
+ {opt.deliveryEstimate}
227
+ </span>
228
+ </button>
229
+ ))}
230
+ </div>
231
+ </div>
232
+
233
+ <div className="flex justify-end pt-4">
234
+ <Button onClick={() => setStep(2)}>Continue to Payment</Button>
235
+ </div>
236
+ </div>
237
+ )}
238
+
239
+ {/* Step 2: Payment Method */}
240
+ {step === 2 && (
241
+ <div className="space-y-6 rounded-2xl border border-border/80 bg-card p-6 shadow-xs">
242
+ <div className="flex items-center justify-between border-b border-border/60 pb-4">
243
+ <div>
244
+ <h3 className="text-base font-bold text-foreground">Payment Method</h3>
245
+ <p className="text-xs text-muted-foreground">
246
+ Transactions are 256-bit encrypted and secured.
247
+ </p>
248
+ </div>
249
+ <div className="flex items-center gap-1 text-emerald-600 text-xs font-semibold">
250
+ <LockIcon className="size-3.5" />
251
+ <span>SSL Encrypted</span>
252
+ </div>
253
+ </div>
254
+
255
+ {/* Payment Method Tabs */}
256
+ <div className="grid grid-cols-2 sm:grid-cols-4 gap-2">
257
+ {[
258
+ { id: 'card', label: 'Credit Card' },
259
+ { id: 'upi', label: 'UPI / QR' },
260
+ { id: 'wallet', label: 'Wallets' },
261
+ { id: 'cod', label: 'Cash on Delivery' },
262
+ ].map((m) => (
263
+ <button
264
+ key={m.id}
265
+ type="button"
266
+ onClick={() =>
267
+ setPayment({ ...payment, method: m.id as CheckoutPaymentSelection['method'] })
268
+ }
269
+ className={cn(
270
+ 'rounded-xl border p-3 text-xs font-bold transition-all text-center',
271
+ payment.method === m.id
272
+ ? 'border-primary bg-primary/10 text-primary ring-1 ring-primary'
273
+ : 'border-border/80 bg-muted/20 text-muted-foreground hover:text-foreground',
274
+ )}
275
+ >
276
+ {m.label}
277
+ </button>
278
+ ))}
279
+ </div>
280
+
281
+ {/* Card Form */}
282
+ {payment.method === 'card' && (
283
+ <div className="rounded-xl border border-border/80 bg-muted/20 p-4 space-y-3 text-xs">
284
+ <div className="space-y-1">
285
+ <label className="font-semibold text-foreground">Card Number</label>
286
+ <input
287
+ type="text"
288
+ disabled
289
+ value="•••• •••• •••• 4242 (Visa Platinum)"
290
+ className="w-full rounded-lg border border-input bg-background px-3 py-2 text-foreground opacity-80"
291
+ />
292
+ </div>
293
+ <div className="grid grid-cols-2 gap-3">
294
+ <div className="space-y-1">
295
+ <label className="font-semibold text-foreground">Expiry Date</label>
296
+ <input
297
+ type="text"
298
+ disabled
299
+ value="08 / 29"
300
+ className="w-full rounded-lg border border-input bg-background px-3 py-2 text-foreground opacity-80"
301
+ />
302
+ </div>
303
+ <div className="space-y-1">
304
+ <label className="font-semibold text-foreground">CVV Security Code</label>
305
+ <input
306
+ type="password"
307
+ maxLength={4}
308
+ value={payment.cvv}
309
+ onChange={(e) => setPayment({ ...payment, cvv: e.target.value })}
310
+ className="w-full rounded-lg border border-input bg-background px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
311
+ />
312
+ </div>
313
+ </div>
314
+ </div>
315
+ )}
316
+
317
+ {payment.method === 'upi' && (
318
+ <div className="rounded-xl border border-border/80 bg-muted/20 p-4 text-xs space-y-2">
319
+ <label className="font-semibold text-foreground">
320
+ Enter UPI Virtual Payment Address (VPA)
321
+ </label>
322
+ <input
323
+ type="text"
324
+ placeholder="username@okaxis or mobile@upi"
325
+ value={payment.upiId ?? ''}
326
+ onChange={(e) => setPayment({ ...payment, upiId: e.target.value })}
327
+ className="w-full rounded-lg border border-input bg-background px-3 py-2 text-foreground focus:outline-none focus:ring-1 focus:ring-primary"
328
+ />
329
+ </div>
330
+ )}
331
+
332
+ <div className="flex justify-between pt-4">
333
+ <Button variant="outline" onClick={() => setStep(1)}>
334
+ Back to Shipping
335
+ </Button>
336
+ <Button onClick={() => setStep(3)}>Review Order</Button>
337
+ </div>
338
+ </div>
339
+ )}
340
+
341
+ {/* Step 3: Review & Place Order */}
342
+ {step === 3 && (
343
+ <div className="space-y-6 rounded-2xl border border-border/80 bg-card p-6 shadow-xs">
344
+ <div className="border-b border-border/60 pb-4">
345
+ <h3 className="text-base font-bold text-foreground">Final Order Review</h3>
346
+ <p className="text-xs text-muted-foreground">
347
+ Please double check your shipping and payment selection.
348
+ </p>
349
+ </div>
350
+
351
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-4 text-xs">
352
+ <div className="rounded-xl border border-border/80 bg-muted/20 p-3.5 space-y-1">
353
+ <div className="font-bold text-foreground flex items-center justify-between">
354
+ <span>Shipping Address</span>
355
+ <button
356
+ type="button"
357
+ onClick={() => setStep(1)}
358
+ className="text-primary hover:underline"
359
+ >
360
+ Edit
361
+ </button>
362
+ </div>
363
+ <div className="text-muted-foreground">
364
+ <p className="font-semibold text-foreground">{address.fullName}</p>
365
+ <p>{address.streetAddress}</p>
366
+ <p>
367
+ {address.city}, {address.state} {address.postalCode}
368
+ </p>
369
+ <p>{address.phone}</p>
370
+ </div>
371
+ </div>
372
+
373
+ <div className="rounded-xl border border-border/80 bg-muted/20 p-3.5 space-y-1">
374
+ <div className="font-bold text-foreground flex items-center justify-between">
375
+ <span>Payment & Delivery</span>
376
+ <button
377
+ type="button"
378
+ onClick={() => setStep(2)}
379
+ className="text-primary hover:underline"
380
+ >
381
+ Edit
382
+ </button>
383
+ </div>
384
+ <div className="text-muted-foreground">
385
+ <p className="font-semibold text-foreground capitalize">
386
+ Method: {payment.method}
387
+ </p>
388
+ <p>Delivery: {selectedShipping.label}</p>
389
+ <p>Speed: {selectedShipping.deliveryEstimate}</p>
390
+ </div>
391
+ </div>
392
+ </div>
393
+
394
+ <div className="flex justify-between pt-4">
395
+ <Button variant="outline" onClick={() => setStep(2)}>
396
+ Back to Payment
397
+ </Button>
398
+ <Button size="lg" className="gap-2 px-6" onClick={handlePlaceOrder}>
399
+ <ShieldCheckIcon className="size-4" />
400
+ <span>
401
+ Place Order ({currencySymbol}
402
+ {total.toFixed(2)})
403
+ </span>
404
+ </Button>
405
+ </div>
406
+ </div>
407
+ )}
408
+ </div>
409
+
410
+ {/* Right Column: Order Items Summary */}
411
+ <div className="lg:col-span-4 space-y-4">
412
+ <div className="rounded-2xl border border-border/80 bg-card p-5 shadow-xs space-y-4">
413
+ <div className="flex items-center justify-between border-b border-border/60 pb-3">
414
+ <h4 className="text-sm font-bold text-foreground">Order Summary</h4>
415
+ <span className="text-xs text-muted-foreground">{items.length} items</span>
416
+ </div>
417
+
418
+ <div className="space-y-3 max-h-72 overflow-y-auto">
419
+ {items.map((it) => (
420
+ <div key={it.id} className="flex items-center gap-3 text-xs">
421
+ <img
422
+ src={it.image}
423
+ alt={it.title}
424
+ className="size-12 rounded-lg object-cover bg-muted border"
425
+ />
426
+ <div className="flex-1 min-w-0">
427
+ <div className="font-semibold text-foreground truncate">{it.title}</div>
428
+ <div className="text-[11px] text-muted-foreground">Qty: {it.quantity}</div>
429
+ </div>
430
+ <div className="font-bold text-foreground">{it.price}</div>
431
+ </div>
432
+ ))}
433
+ </div>
434
+
435
+ <div className="border-t border-border/60 pt-3 space-y-2 text-xs">
436
+ <div className="flex justify-between text-muted-foreground">
437
+ <span>Subtotal</span>
438
+ <span className="font-semibold text-foreground">
439
+ {currencySymbol}
440
+ {subtotal.toFixed(2)}
441
+ </span>
442
+ </div>
443
+ <div className="flex justify-between text-muted-foreground">
444
+ <span>Shipping</span>
445
+ <span className="font-semibold text-foreground">{selectedShipping.price}</span>
446
+ </div>
447
+ <div className="border-t border-border/60 pt-2 flex justify-between text-base font-bold text-foreground">
448
+ <span>Total</span>
449
+ <span className="text-primary">
450
+ {currencySymbol}
451
+ {total.toFixed(2)}
452
+ </span>
453
+ </div>
454
+ </div>
455
+ </div>
456
+
457
+ {/* Guarantee Banner */}
458
+ <div className="rounded-xl border border-border/60 bg-muted/30 p-3.5 text-xs flex items-center gap-3">
459
+ <TruckIcon className="size-5 text-primary shrink-0" />
460
+ <div className="text-muted-foreground">
461
+ <strong className="text-foreground">Free Returns within 30 days</strong>. If you are not
462
+ satisfied, return for a full refund.
463
+ </div>
464
+ </div>
465
+ </div>
466
+ </div>
467
+ )
468
+ }
@@ -0,0 +1,2 @@
1
+ export * from './checkout-wizard'
2
+ export * from './checkout-types'
@@ -0,0 +1,28 @@
1
+ export type ComparisonProduct = {
2
+ readonly attributes: Record<string, string | boolean | number>
3
+ readonly badge?: string
4
+ readonly id: string
5
+ readonly image: string
6
+ readonly inStock: boolean
7
+ readonly price: string
8
+ readonly rating: number
9
+ readonly reviewsCount?: number
10
+ readonly title: string
11
+ }
12
+
13
+ export type ComparisonFeatureGroup = {
14
+ readonly features: readonly {
15
+ readonly description?: string
16
+ readonly key: string
17
+ readonly label: string
18
+ }[]
19
+ readonly groupName: string
20
+ }
21
+
22
+ export type ProductComparisonProps = {
23
+ readonly className?: string
24
+ readonly featureGroups: readonly ComparisonFeatureGroup[]
25
+ readonly onAddToCart?: (productId: string) => void
26
+ readonly onRemoveProduct?: (productId: string) => void
27
+ readonly products: readonly ComparisonProduct[]
28
+ }
@@ -0,0 +1,2 @@
1
+ export * from './product-comparison'
2
+ export * from './comparison-types'
@@ -0,0 +1,174 @@
1
+ import { useState } from 'react'
2
+ import { CheckIcon, ShoppingBagIcon, StarIcon, XIcon } from 'lucide-react'
3
+ import { cn } from '../../../lib/utils'
4
+ import { Button } from '../../../components/button'
5
+ import { Badge } from '../../../components/badge'
6
+ import type { ProductComparisonProps } from './comparison-types'
7
+
8
+ export function ProductComparison({
9
+ className,
10
+ featureGroups,
11
+ onAddToCart,
12
+ onRemoveProduct,
13
+ products,
14
+ }: ProductComparisonProps) {
15
+ const [highlightDifferences, setHighlightDifferences] = useState(false)
16
+
17
+ if (products.length === 0) {
18
+ return (
19
+ <div className="flex flex-col items-center justify-center p-12 text-center rounded-2xl border border-border/80 bg-card">
20
+ <p className="text-sm font-semibold text-foreground">No products selected for comparison</p>
21
+ <p className="text-xs text-muted-foreground mt-1">
22
+ Select 2 or more products from the catalog to compare features.
23
+ </p>
24
+ </div>
25
+ )
26
+ }
27
+
28
+ const isDifference = (key: string) => {
29
+ if (products.length <= 1) return false
30
+ const firstVal = products[0]?.attributes[key]
31
+ return products.some((p) => p.attributes[key] !== firstVal)
32
+ }
33
+
34
+ return (
35
+ <div className={cn('space-y-4', className)}>
36
+ {/* Controls */}
37
+ <div className="flex items-center justify-between">
38
+ <div className="flex items-center gap-2">
39
+ <span className="text-xs font-semibold text-foreground">
40
+ Comparing {products.length} Products
41
+ </span>
42
+ </div>
43
+
44
+ <label className="flex items-center gap-2 cursor-pointer text-xs font-medium text-muted-foreground hover:text-foreground">
45
+ <input
46
+ type="checkbox"
47
+ checked={highlightDifferences}
48
+ onChange={(e) => setHighlightDifferences(e.target.checked)}
49
+ className="rounded border-border text-primary focus:ring-primary size-3.5"
50
+ />
51
+ <span>Highlight Differences</span>
52
+ </label>
53
+ </div>
54
+
55
+ {/* Comparison Table */}
56
+ <div className="overflow-x-auto rounded-2xl border border-border/80 bg-card shadow-xs">
57
+ <table className="w-full border-collapse text-left text-xs">
58
+ <thead>
59
+ <tr className="border-b border-border/80 bg-muted/20">
60
+ <th className="w-48 p-4 font-bold text-foreground">Overview</th>
61
+ {products.map((p) => (
62
+ <th key={p.id} className="min-w-56 p-4 align-top">
63
+ <div className="relative flex flex-col space-y-2">
64
+ {onRemoveProduct && (
65
+ <button
66
+ type="button"
67
+ onClick={() => onRemoveProduct(p.id)}
68
+ className="absolute -top-2 -right-2 rounded-full bg-muted p-1 text-muted-foreground hover:bg-destructive hover:text-destructive-foreground transition-colors"
69
+ aria-label={`Remove ${p.title}`}
70
+ >
71
+ <XIcon className="size-3" />
72
+ </button>
73
+ )}
74
+
75
+ <img
76
+ src={p.image}
77
+ alt={p.title}
78
+ className="aspect-square w-full rounded-xl object-cover bg-muted border border-border/40"
79
+ />
80
+
81
+ {p.badge && (
82
+ <Badge variant="secondary" className="w-fit text-[10px]">
83
+ {p.badge}
84
+ </Badge>
85
+ )}
86
+
87
+ <h4 className="font-bold text-foreground text-sm line-clamp-1">{p.title}</h4>
88
+ <div className="text-base font-bold text-primary">{p.price}</div>
89
+
90
+ {/* Rating */}
91
+ <div className="flex items-center gap-1 text-[11px] text-muted-foreground">
92
+ <StarIcon className="size-3.5 fill-amber-400 text-amber-400" />
93
+ <span className="font-semibold text-foreground">{p.rating.toFixed(1)}</span>
94
+ {p.reviewsCount && <span>({p.reviewsCount})</span>}
95
+ </div>
96
+
97
+ <Button
98
+ size="sm"
99
+ className="w-full gap-1.5 mt-2 text-xs"
100
+ disabled={!p.inStock}
101
+ onClick={() => onAddToCart?.(p.id)}
102
+ >
103
+ <ShoppingBagIcon className="size-3.5" />
104
+ <span>{p.inStock ? 'Add to Cart' : 'Out of Stock'}</span>
105
+ </Button>
106
+ </div>
107
+ </th>
108
+ ))}
109
+ </tr>
110
+ </thead>
111
+
112
+ <tbody>
113
+ {featureGroups.map((group) => (
114
+ <tr key={group.groupName} className="contents">
115
+ <td
116
+ colSpan={products.length + 1}
117
+ className="bg-muted/50 px-4 py-2 font-bold uppercase tracking-wider text-[11px] text-muted-foreground border-y border-border/80"
118
+ >
119
+ {group.groupName}
120
+ </td>
121
+
122
+ {group.features.map((feat) => {
123
+ const hasDiff = isDifference(feat.key)
124
+ const rowBg =
125
+ highlightDifferences && hasDiff ? 'bg-amber-500/10 dark:bg-amber-500/15' : ''
126
+
127
+ return (
128
+ <tr
129
+ key={feat.key}
130
+ className={cn('border-b border-border/40 transition-colors', rowBg)}
131
+ >
132
+ <td className="p-4 font-semibold text-foreground align-middle">
133
+ <div>{feat.label}</div>
134
+ {feat.description && (
135
+ <div className="text-[10px] text-muted-foreground font-normal">
136
+ {feat.description}
137
+ </div>
138
+ )}
139
+ </td>
140
+
141
+ {products.map((p) => {
142
+ const val = p.attributes[feat.key]
143
+ return (
144
+ <td key={p.id} className="p-4 text-muted-foreground align-middle">
145
+ {typeof val === 'boolean' ? (
146
+ val ? (
147
+ <CheckIcon className="size-4 text-emerald-600 font-bold" />
148
+ ) : (
149
+ <XIcon className="size-4 text-muted-foreground/40" />
150
+ )
151
+ ) : (
152
+ <span
153
+ className={cn(
154
+ 'font-medium',
155
+ val ? 'text-foreground' : 'text-muted-foreground/50',
156
+ )}
157
+ >
158
+ {val ?? '—'}
159
+ </span>
160
+ )}
161
+ </td>
162
+ )
163
+ })}
164
+ </tr>
165
+ )
166
+ })}
167
+ </tr>
168
+ ))}
169
+ </tbody>
170
+ </table>
171
+ </div>
172
+ </div>
173
+ )
174
+ }