@hanzo/ui 4.7.0 → 4.8.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 (272) hide show
  1. package/assets/ai-icons.tsx +207 -0
  2. package/assets/crypto.tsx +33 -0
  3. package/assets/file-type-icon.tsx +66 -0
  4. package/assets/file.tsx +45 -0
  5. package/assets/general.tsx +2318 -0
  6. package/assets/hanzo-logo.svg +9 -0
  7. package/assets/hanzo-logo.tsx +17 -0
  8. package/assets/index.ts +122 -0
  9. package/assets/index.tsx +4 -0
  10. package/assets/llm-provider.tsx +1094 -0
  11. package/blocks/auth/index.ts +6 -0
  12. package/blocks/auth/login-2fa.tsx +165 -0
  13. package/blocks/auth/login-basic.tsx +94 -0
  14. package/blocks/auth/login-social.tsx +148 -0
  15. package/blocks/auth/magic-link.tsx +129 -0
  16. package/blocks/auth/password-reset.tsx +97 -0
  17. package/blocks/auth/signup.tsx +157 -0
  18. package/blocks/components/accordian-block.tsx +48 -0
  19. package/blocks/components/block-component-props.ts +11 -0
  20. package/blocks/components/bullet-cards-block.tsx +46 -0
  21. package/blocks/components/card-block/index.tsx +171 -0
  22. package/blocks/components/card-block/link-out-button.tsx +20 -0
  23. package/blocks/components/card-block/util.ts +28 -0
  24. package/blocks/components/carte-blanche-block/index.tsx +127 -0
  25. package/blocks/components/carte-blanche-block/variant-content-left.tsx +49 -0
  26. package/blocks/components/content.tsx +70 -0
  27. package/blocks/components/cta-block.tsx +115 -0
  28. package/blocks/components/enh-heading-block.tsx +204 -0
  29. package/blocks/components/grid-block/grid-block-mutator.ts +12 -0
  30. package/blocks/components/grid-block/index.tsx +83 -0
  31. package/blocks/components/grid-block/mutator-registry.ts +10 -0
  32. package/blocks/components/grid-block/table-borders.mutator.ts +47 -0
  33. package/blocks/components/group-block.tsx +83 -0
  34. package/blocks/components/heading-block.tsx +88 -0
  35. package/blocks/components/image-block.tsx +111 -0
  36. package/blocks/components/index.ts +30 -0
  37. package/blocks/components/screenful-block/content.tsx +123 -0
  38. package/blocks/components/screenful-block/index.tsx +107 -0
  39. package/blocks/components/screenful-block/poster-background.tsx +34 -0
  40. package/blocks/components/screenful-block/video-background.tsx +45 -0
  41. package/blocks/components/space-block.tsx +66 -0
  42. package/blocks/components/video-block.tsx +138 -0
  43. package/blocks/data-display/activity-feed.tsx +242 -0
  44. package/blocks/data-display/data-table.tsx +235 -0
  45. package/blocks/data-display/stats-grid.tsx +194 -0
  46. package/blocks/def/accordian-block.ts +14 -0
  47. package/blocks/def/block.ts +7 -0
  48. package/blocks/def/bullet-cards-block.ts +22 -0
  49. package/blocks/def/card-block.ts +22 -0
  50. package/blocks/def/carte-blanche-block.ts +21 -0
  51. package/blocks/def/cta-block.ts +19 -0
  52. package/blocks/def/element-block.ts +11 -0
  53. package/blocks/def/enh-heading-block.ts +44 -0
  54. package/blocks/def/grid-block.ts +16 -0
  55. package/blocks/def/group-block.ts +11 -0
  56. package/blocks/def/heading-block.ts +15 -0
  57. package/blocks/def/image-block.ts +31 -0
  58. package/blocks/def/index.ts +35 -0
  59. package/blocks/def/screenful-block.ts +54 -0
  60. package/blocks/def/space-block.ts +64 -0
  61. package/blocks/def/video-block.ts +9 -0
  62. package/blocks/ecommerce/checkout.tsx +242 -0
  63. package/blocks/ecommerce/index.ts +7 -0
  64. package/blocks/ecommerce/product-detail.tsx +257 -0
  65. package/blocks/ecommerce/product-grid.tsx +148 -0
  66. package/blocks/ecommerce/shopping-cart.tsx +181 -0
  67. package/blocks/index.ts +2 -0
  68. package/blocks/marketing/cta-section.tsx +207 -0
  69. package/blocks/marketing/faq.tsx +159 -0
  70. package/blocks/marketing/features-grid.tsx +156 -0
  71. package/blocks/marketing/hero-section.tsx +192 -0
  72. package/blocks/marketing/index.ts +6 -0
  73. package/blocks/marketing/pricing-table.tsx +121 -0
  74. package/blocks/marketing/testimonials.tsx +196 -0
  75. package/components/index.ts +9 -0
  76. package/dist/index.js +1407 -1514
  77. package/dist/index.mjs +1363 -1472
  78. package/dist/tailwind/index.js +3 -1
  79. package/dist/tailwind/index.mjs +3 -1
  80. package/dist/util/format-text.js +51 -0
  81. package/dist/util/format-text.mjs +32 -0
  82. package/dist/util/index.js +384 -0
  83. package/dist/util/index.mjs +363 -0
  84. package/frameworks/core/index.ts +6 -0
  85. package/frameworks/core/utils/index.ts +64 -0
  86. package/frameworks/react/components/button.tsx +26 -0
  87. package/frameworks/react/components/index.ts +5 -0
  88. package/frameworks/react/hooks/index.ts +5 -0
  89. package/frameworks/react/index.ts +9 -0
  90. package/frameworks/react/package.json +8 -0
  91. package/frameworks/react/utils/index.ts +2 -0
  92. package/frameworks/react-native/index.ts +9 -0
  93. package/frameworks/react-native/package.json +8 -0
  94. package/frameworks/registry.json +371 -0
  95. package/frameworks/setup.sh +69 -0
  96. package/frameworks/svelte/index.ts +9 -0
  97. package/frameworks/svelte/package.json +8 -0
  98. package/frameworks/tracker.json +1854 -0
  99. package/frameworks/vue/index.ts +9 -0
  100. package/frameworks/vue/package.json +8 -0
  101. package/helpers/file.ts +33 -0
  102. package/helpers/memoization.ts +40 -0
  103. package/package.json +49 -11
  104. package/primitives/accordion.tsx +74 -0
  105. package/primitives/action-button.tsx +42 -0
  106. package/primitives/alert-dialog.tsx +185 -0
  107. package/primitives/alert.tsx +74 -0
  108. package/primitives/apply-typography.tsx +55 -0
  109. package/primitives/aspect-ratio.tsx +5 -0
  110. package/primitives/avatar.tsx +57 -0
  111. package/primitives/background-beams.tsx +142 -0
  112. package/primitives/badge.tsx +45 -0
  113. package/primitives/breadcrumb.tsx +130 -0
  114. package/primitives/breakpoint-indicator.tsx +19 -0
  115. package/primitives/button.tsx +72 -0
  116. package/primitives/calendar.tsx +72 -0
  117. package/primitives/card.tsx +97 -0
  118. package/primitives/carousel.tsx +238 -0
  119. package/primitives/chat/chat-input-area.tsx +88 -0
  120. package/primitives/chat/chat-input.tsx +71 -0
  121. package/primitives/chat/files-preview.tsx +331 -0
  122. package/primitives/chat/index.ts +6 -0
  123. package/primitives/chat/json-form.tsx +8 -0
  124. package/primitives/chat/message-list.tsx +308 -0
  125. package/primitives/chat/message.tsx +569 -0
  126. package/primitives/chat/sqlite-preview.tsx +215 -0
  127. package/primitives/checkbox.tsx +32 -0
  128. package/primitives/collapsible.tsx +9 -0
  129. package/primitives/combobox.tsx +239 -0
  130. package/primitives/command.tsx +151 -0
  131. package/primitives/context-menu.tsx +206 -0
  132. package/primitives/copy-to-clipboard-icon.tsx +60 -0
  133. package/primitives/dialog-video-controller.tsx +38 -0
  134. package/primitives/dialog.tsx +128 -0
  135. package/primitives/dot-pattern.tsx +57 -0
  136. package/primitives/dots-loader.tsx +13 -0
  137. package/primitives/drawer.tsx +113 -0
  138. package/primitives/dropdown-menu.tsx +199 -0
  139. package/primitives/error-message.tsx +19 -0
  140. package/primitives/file-uploader.tsx +202 -0
  141. package/primitives/form.tsx +183 -0
  142. package/primitives/hover-card.tsx +28 -0
  143. package/primitives/icons/github.tsx +14 -0
  144. package/primitives/icons/index.ts +18 -0
  145. package/primitives/icons/youtube-logo.tsx +59 -0
  146. package/primitives/index-common.ts +304 -0
  147. package/primitives/index-next.ts +4 -0
  148. package/primitives/input-otp.tsx +65 -0
  149. package/primitives/input.tsx +126 -0
  150. package/primitives/label.tsx +21 -0
  151. package/primitives/list-adaptor.ts +12 -0
  152. package/primitives/list-box.tsx +74 -0
  153. package/primitives/loading-spinner.tsx +33 -0
  154. package/primitives/markdown-preview.tsx +612 -0
  155. package/primitives/mermaid.tsx +191 -0
  156. package/primitives/navigation-menu.tsx +147 -0
  157. package/primitives/next/image.tsx +91 -0
  158. package/primitives/next/index.ts +7 -0
  159. package/primitives/next/inline-icon.tsx +36 -0
  160. package/primitives/next/link-element.tsx +109 -0
  161. package/primitives/next/mdx-link.tsx +22 -0
  162. package/primitives/next/media-stack.tsx +52 -0
  163. package/primitives/next/nav-items.tsx +45 -0
  164. package/primitives/next/youtube-embed.tsx +83 -0
  165. package/primitives/pagination.tsx +117 -0
  166. package/primitives/popover.tsx +34 -0
  167. package/primitives/pretty-json-print.tsx +28 -0
  168. package/primitives/progress.tsx +27 -0
  169. package/primitives/prompt-textarea.tsx +72 -0
  170. package/primitives/qr-code.tsx +112 -0
  171. package/primitives/radio-group.tsx +42 -0
  172. package/primitives/resizable.tsx +47 -0
  173. package/primitives/scroll-area.tsx +57 -0
  174. package/primitives/search-input.tsx +66 -0
  175. package/primitives/select.tsx +122 -0
  176. package/primitives/separator.tsx +26 -0
  177. package/primitives/sheet.tsx +139 -0
  178. package/primitives/skeleton.tsx +18 -0
  179. package/primitives/slider.tsx +63 -0
  180. package/primitives/sonner.tsx +35 -0
  181. package/primitives/step-indicator.tsx +69 -0
  182. package/primitives/stepper.tsx +272 -0
  183. package/primitives/switch.tsx +27 -0
  184. package/primitives/table.tsx +105 -0
  185. package/primitives/tabs.tsx +50 -0
  186. package/primitives/text-area.tsx +26 -0
  187. package/primitives/text-link.tsx +25 -0
  188. package/primitives/textarea.tsx +62 -0
  189. package/primitives/textfield.tsx +76 -0
  190. package/primitives/toast.tsx +30 -0
  191. package/primitives/toggle-group.tsx +63 -0
  192. package/primitives/toggle.tsx +44 -0
  193. package/primitives/tooltip.tsx +47 -0
  194. package/primitives/video-player.tsx +23 -0
  195. package/src/button.ts +1 -0
  196. package/src/hooks/index.ts +7 -0
  197. package/src/hooks/use-click-away.ts +31 -0
  198. package/src/hooks/use-combined-refs.ts +22 -0
  199. package/src/hooks/use-copy-clipboard.ts +30 -0
  200. package/src/hooks/use-debounce.ts +17 -0
  201. package/src/hooks/use-fill-ids.ts +25 -0
  202. package/src/hooks/use-map.ts +26 -0
  203. package/src/hooks/use-measure.ts +42 -0
  204. package/src/hooks/use-reverse-video-playback.ts +43 -0
  205. package/src/hooks/use-scroll-restoration.ts +50 -0
  206. package/src/index-lean.ts +87 -0
  207. package/src/index.ts +54 -0
  208. package/src/mcp/README.md +141 -0
  209. package/src/mcp/enhanced-server.ts +1208 -0
  210. package/src/mcp/index.ts +518 -0
  211. package/src/mcp/package.json +10 -0
  212. package/src/registry/api.ts +164 -0
  213. package/src/registry/index.ts +60 -0
  214. package/src/registry/package.json +10 -0
  215. package/src/utils.ts +19 -0
  216. package/tailwind/colors.tailwind.js +53 -0
  217. package/tailwind/fontFamily.tailwind.ts +7 -0
  218. package/tailwind/fontSize.tailwind.ts +13 -0
  219. package/tailwind/index.ts +7 -0
  220. package/tailwind/safelist.tailwind.js +26 -0
  221. package/tailwind/screens.tailwind.js +8 -0
  222. package/tailwind/spacing.tailwind.js +65 -0
  223. package/tailwind/tailwind.config.hanzo-preset.d.ts +5 -0
  224. package/tailwind/tailwind.config.hanzo-preset.js +915 -0
  225. package/tailwind/tw-font-desc.ts +15 -0
  226. package/tailwind/typo-plugin/get-plugin-styles.js +679 -0
  227. package/tailwind/typo-plugin/index.d.ts +9 -0
  228. package/tailwind/typo-plugin/index.js +141 -0
  229. package/tailwind/typo-plugin/utils.js +60 -0
  230. package/tailwind/typography-test.mdx +35 -0
  231. package/tailwind/z-index.tailwind.js +71 -0
  232. package/types/animation-def.ts +3 -0
  233. package/types/breakpoints.ts +11 -0
  234. package/types/bullet-item.ts +10 -0
  235. package/types/button-def.ts +39 -0
  236. package/types/dimensions.ts +8 -0
  237. package/types/grid-def.ts +56 -0
  238. package/types/image-def.ts +32 -0
  239. package/types/index.ts +30 -0
  240. package/types/link-def.ts +56 -0
  241. package/types/media-stack-def.ts +31 -0
  242. package/types/t-shirt-size.ts +5 -0
  243. package/types/tshirt-dimensions.ts +20 -0
  244. package/types/video-def.ts +25 -0
  245. package/util/blob.ts +33 -0
  246. package/util/copy-to-clipboard.ts +17 -0
  247. package/util/create-shadow-root.ts +22 -0
  248. package/util/date.ts +84 -0
  249. package/util/debounce.ts +11 -0
  250. package/util/file.ts +15 -0
  251. package/util/format-and-abbreviate-as-currency.ts +125 -0
  252. package/util/format-text.ts +34 -0
  253. package/util/format-to-max-char.ts +68 -0
  254. package/util/index-client.ts +3 -0
  255. package/util/index.ts +112 -0
  256. package/util/number-abbreviate.ts +49 -0
  257. package/util/specifier.ts +43 -0
  258. package/util/spread-to-transform.ts +25 -0
  259. package/util/step-animation.ts +90 -0
  260. package/util/timing.ts +3 -0
  261. package/util/toasts.tsx +17 -0
  262. package/util/two-way-map.ts +19 -0
  263. package/dist/index.d.mts +0 -16
  264. package/dist/index.d.ts +0 -16
  265. package/dist/lib/utils.d.mts +0 -2
  266. package/dist/lib/utils.d.ts +0 -2
  267. package/dist/src/utils.d.mts +0 -7
  268. package/dist/src/utils.d.ts +0 -7
  269. package/dist/tailwind/index.d.mts +0 -2
  270. package/dist/tailwind/index.d.ts +0 -2
  271. package/dist/types/index.d.mts +0 -12
  272. package/dist/types/index.d.ts +0 -12
@@ -0,0 +1,242 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@hanzo/ui/util'
4
+ import { Button } from '@hanzo/ui/primitives'
5
+ import { Input } from '@hanzo/ui/primitives'
6
+ import { Label } from '@hanzo/ui/primitives'
7
+ import { RadioGroup, RadioGroupItem } from '@hanzo/ui/primitives'
8
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@hanzo/ui/primitives'
9
+ import { Separator } from '@hanzo/ui/primitives'
10
+ import { Checkbox } from '@hanzo/ui/primitives'
11
+
12
+ interface CheckoutProps extends React.ComponentPropsWithoutRef<'div'> {
13
+ orderSummary: {
14
+ items: Array<{
15
+ name: string
16
+ quantity: number
17
+ price: number
18
+ }>
19
+ subtotal: number
20
+ shipping: number
21
+ tax: number
22
+ total: number
23
+ currency?: string
24
+ }
25
+ onSubmit?: (data: any) => void
26
+ onBack?: () => void
27
+ }
28
+
29
+ export function Checkout({
30
+ className,
31
+ orderSummary,
32
+ onSubmit,
33
+ onBack,
34
+ ...props
35
+ }: CheckoutProps) {
36
+ const formatPrice = (price: number, currency = '$') => {
37
+ return `${currency}${price.toFixed(2)}`
38
+ }
39
+
40
+ const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
41
+ e.preventDefault()
42
+ const formData = new FormData(e.currentTarget)
43
+ const data = Object.fromEntries(formData.entries())
44
+ onSubmit?.(data)
45
+ }
46
+
47
+ return (
48
+ <div className={cn('container py-8', className)} {...props}>
49
+ <form onSubmit={handleSubmit} className="grid gap-8 lg:grid-cols-3">
50
+ <div className="lg:col-span-2 space-y-6">
51
+ {/* Shipping Information */}
52
+ <Card>
53
+ <CardHeader>
54
+ <CardTitle>Shipping Information</CardTitle>
55
+ <CardDescription>
56
+ Enter your shipping address details
57
+ </CardDescription>
58
+ </CardHeader>
59
+ <CardContent className="grid gap-4">
60
+ <div className="grid gap-4 sm:grid-cols-2">
61
+ <div className="grid gap-2">
62
+ <Label htmlFor="firstName">First Name</Label>
63
+ <Input id="firstName" name="firstName" required />
64
+ </div>
65
+ <div className="grid gap-2">
66
+ <Label htmlFor="lastName">Last Name</Label>
67
+ <Input id="lastName" name="lastName" required />
68
+ </div>
69
+ </div>
70
+ <div className="grid gap-2">
71
+ <Label htmlFor="email">Email</Label>
72
+ <Input id="email" name="email" type="email" required />
73
+ </div>
74
+ <div className="grid gap-2">
75
+ <Label htmlFor="address">Street Address</Label>
76
+ <Input id="address" name="address" required />
77
+ </div>
78
+ <div className="grid gap-2">
79
+ <Label htmlFor="address2">Apartment, suite, etc. (optional)</Label>
80
+ <Input id="address2" name="address2" />
81
+ </div>
82
+ <div className="grid gap-4 sm:grid-cols-3">
83
+ <div className="grid gap-2">
84
+ <Label htmlFor="city">City</Label>
85
+ <Input id="city" name="city" required />
86
+ </div>
87
+ <div className="grid gap-2">
88
+ <Label htmlFor="state">State / Province</Label>
89
+ <Input id="state" name="state" required />
90
+ </div>
91
+ <div className="grid gap-2">
92
+ <Label htmlFor="zip">ZIP / Postal Code</Label>
93
+ <Input id="zip" name="zip" required />
94
+ </div>
95
+ </div>
96
+ <div className="grid gap-2">
97
+ <Label htmlFor="phone">Phone Number</Label>
98
+ <Input id="phone" name="phone" type="tel" />
99
+ </div>
100
+ </CardContent>
101
+ </Card>
102
+
103
+ {/* Payment Method */}
104
+ <Card>
105
+ <CardHeader>
106
+ <CardTitle>Payment Method</CardTitle>
107
+ <CardDescription>
108
+ Select your payment method
109
+ </CardDescription>
110
+ </CardHeader>
111
+ <CardContent className="grid gap-6">
112
+ <RadioGroup defaultValue="card" name="paymentMethod">
113
+ <div className="flex items-center space-x-2">
114
+ <RadioGroupItem value="card" id="card" />
115
+ <Label htmlFor="card">Credit Card</Label>
116
+ </div>
117
+ <div className="flex items-center space-x-2">
118
+ <RadioGroupItem value="paypal" id="paypal" />
119
+ <Label htmlFor="paypal">PayPal</Label>
120
+ </div>
121
+ <div className="flex items-center space-x-2">
122
+ <RadioGroupItem value="crypto" id="crypto" />
123
+ <Label htmlFor="crypto">Cryptocurrency</Label>
124
+ </div>
125
+ </RadioGroup>
126
+
127
+ <div className="grid gap-4">
128
+ <div className="grid gap-2">
129
+ <Label htmlFor="cardNumber">Card Number</Label>
130
+ <Input
131
+ id="cardNumber"
132
+ name="cardNumber"
133
+ placeholder="1234 5678 9012 3456"
134
+ />
135
+ </div>
136
+ <div className="grid gap-4 sm:grid-cols-3">
137
+ <div className="grid gap-2 sm:col-span-2">
138
+ <Label htmlFor="expiry">Expiry Date</Label>
139
+ <Input
140
+ id="expiry"
141
+ name="expiry"
142
+ placeholder="MM/YY"
143
+ />
144
+ </div>
145
+ <div className="grid gap-2">
146
+ <Label htmlFor="cvc">CVC</Label>
147
+ <Input
148
+ id="cvc"
149
+ name="cvc"
150
+ placeholder="123"
151
+ />
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </CardContent>
156
+ </Card>
157
+
158
+ {/* Billing Address */}
159
+ <Card>
160
+ <CardHeader>
161
+ <CardTitle>Billing Address</CardTitle>
162
+ </CardHeader>
163
+ <CardContent>
164
+ <div className="flex items-center space-x-2">
165
+ <Checkbox id="sameAsShipping" name="sameAsShipping" defaultChecked />
166
+ <label
167
+ htmlFor="sameAsShipping"
168
+ className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
169
+ >
170
+ Same as shipping address
171
+ </label>
172
+ </div>
173
+ </CardContent>
174
+ </Card>
175
+ </div>
176
+
177
+ {/* Order Summary */}
178
+ <div>
179
+ <Card>
180
+ <CardHeader>
181
+ <CardTitle>Order Summary</CardTitle>
182
+ </CardHeader>
183
+ <CardContent className="space-y-4">
184
+ <div className="space-y-2">
185
+ {orderSummary.items.map((item, i) => (
186
+ <div key={i} className="flex justify-between text-sm">
187
+ <span className="text-muted-foreground">
188
+ {item.name} x {item.quantity}
189
+ </span>
190
+ <span>
191
+ {formatPrice(item.price * item.quantity, orderSummary.currency)}
192
+ </span>
193
+ </div>
194
+ ))}
195
+ </div>
196
+ <Separator />
197
+ <div className="space-y-2">
198
+ <div className="flex justify-between">
199
+ <span>Subtotal</span>
200
+ <span>{formatPrice(orderSummary.subtotal, orderSummary.currency)}</span>
201
+ </div>
202
+ <div className="flex justify-between">
203
+ <span>Shipping</span>
204
+ <span>{formatPrice(orderSummary.shipping, orderSummary.currency)}</span>
205
+ </div>
206
+ <div className="flex justify-between">
207
+ <span>Tax</span>
208
+ <span>{formatPrice(orderSummary.tax, orderSummary.currency)}</span>
209
+ </div>
210
+ </div>
211
+ <Separator />
212
+ <div className="flex justify-between text-lg font-semibold">
213
+ <span>Total</span>
214
+ <span>{formatPrice(orderSummary.total, orderSummary.currency)}</span>
215
+ </div>
216
+ </CardContent>
217
+ <CardFooter className="flex-col gap-2">
218
+ <Button type="submit" className="w-full" size="lg">
219
+ Complete Order
220
+ </Button>
221
+ {onBack && (
222
+ <Button
223
+ type="button"
224
+ variant="outline"
225
+ className="w-full"
226
+ onClick={onBack}
227
+ >
228
+ Back to Cart
229
+ </Button>
230
+ )}
231
+ </CardFooter>
232
+ </Card>
233
+
234
+ <div className="mt-4 text-xs text-center text-muted-foreground">
235
+ Your payment information is encrypted and secure.
236
+ We never store your credit card details.
237
+ </div>
238
+ </div>
239
+ </form>
240
+ </div>
241
+ )
242
+ }
@@ -0,0 +1,7 @@
1
+ export { ProductGrid } from './product-grid'
2
+ export { ProductDetail } from './product-detail'
3
+ export { ShoppingCart } from './shopping-cart'
4
+ export { Checkout } from './checkout'
5
+
6
+ export type { Product } from './product-grid'
7
+ export type { CartItem } from './shopping-cart'
@@ -0,0 +1,257 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { cn } from '@hanzo/ui/util'
5
+ import { Button } from '@hanzo/ui/primitives'
6
+ import { Badge } from '@hanzo/ui/primitives'
7
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@hanzo/ui/primitives'
8
+ import { Star, ShoppingCart, Heart, Share2 } from 'lucide-react'
9
+
10
+ interface ProductDetailProps extends React.ComponentPropsWithoutRef<'div'> {
11
+ product: {
12
+ id: string | number
13
+ name: string
14
+ description: string
15
+ price: number
16
+ originalPrice?: number
17
+ currency?: string
18
+ images: string[]
19
+ rating?: number
20
+ reviewCount?: number
21
+ badge?: string
22
+ inStock?: boolean
23
+ variants?: Array<{
24
+ id: string
25
+ name: string
26
+ value: string
27
+ }>
28
+ features?: string[]
29
+ specifications?: Record<string, string>
30
+ }
31
+ onAddToCart?: (quantity: number, variant?: string) => void
32
+ onAddToWishlist?: () => void
33
+ onShare?: () => void
34
+ }
35
+
36
+ export function ProductDetail({
37
+ className,
38
+ product,
39
+ onAddToCart,
40
+ onAddToWishlist,
41
+ onShare,
42
+ ...props
43
+ }: ProductDetailProps) {
44
+ const [selectedImage, setSelectedImage] = useState(0)
45
+ const [quantity, setQuantity] = useState(1)
46
+ const [selectedVariant, setSelectedVariant] = useState(
47
+ product.variants?.[0]?.id || ''
48
+ )
49
+
50
+ const formatPrice = (price: number, currency = '$') => {
51
+ return `${currency}${price.toFixed(2)}`
52
+ }
53
+
54
+ const calculateDiscount = (price: number, originalPrice: number) => {
55
+ return Math.round(((originalPrice - price) / originalPrice) * 100)
56
+ }
57
+
58
+ return (
59
+ <div className={cn('container py-8', className)} {...props}>
60
+ <div className="grid gap-8 lg:grid-cols-2">
61
+ {/* Images */}
62
+ <div className="space-y-4">
63
+ <div className="aspect-square overflow-hidden rounded-lg">
64
+ <img
65
+ src={product.images[selectedImage]}
66
+ alt={product.name}
67
+ className="h-full w-full object-cover"
68
+ />
69
+ </div>
70
+ {product.images.length > 1 && (
71
+ <div className="grid grid-cols-4 gap-4">
72
+ {product.images.map((image, i) => (
73
+ <button
74
+ key={i}
75
+ onClick={() => setSelectedImage(i)}
76
+ className={cn(
77
+ 'aspect-square overflow-hidden rounded-lg border-2',
78
+ selectedImage === i ? 'border-primary' : 'border-transparent'
79
+ )}
80
+ >
81
+ <img
82
+ src={image}
83
+ alt=""
84
+ className="h-full w-full object-cover"
85
+ />
86
+ </button>
87
+ ))}
88
+ </div>
89
+ )}
90
+ </div>
91
+
92
+ {/* Product Info */}
93
+ <div className="space-y-6">
94
+ <div>
95
+ {product.badge && (
96
+ <Badge className="mb-2">{product.badge}</Badge>
97
+ )}
98
+ <h1 className="text-3xl font-bold">{product.name}</h1>
99
+ {product.rating !== undefined && (
100
+ <div className="mt-4 flex items-center gap-2">
101
+ <div className="flex items-center gap-1">
102
+ {Array.from({ length: 5 }).map((_, i) => (
103
+ <Star
104
+ key={i}
105
+ className={cn(
106
+ 'h-5 w-5',
107
+ i < Math.floor(product.rating!)
108
+ ? 'fill-primary text-primary'
109
+ : 'text-muted-foreground'
110
+ )}
111
+ />
112
+ ))}
113
+ </div>
114
+ <span className="font-medium">{product.rating.toFixed(1)}</span>
115
+ {product.reviewCount !== undefined && (
116
+ <span className="text-muted-foreground">
117
+ ({product.reviewCount} reviews)
118
+ </span>
119
+ )}
120
+ </div>
121
+ )}
122
+ </div>
123
+
124
+ <div className="space-y-2">
125
+ <div className="flex items-center gap-2">
126
+ <span className="text-3xl font-bold">
127
+ {formatPrice(product.price, product.currency)}
128
+ </span>
129
+ {product.originalPrice && product.originalPrice > product.price && (
130
+ <>
131
+ <span className="text-xl text-muted-foreground line-through">
132
+ {formatPrice(product.originalPrice, product.currency)}
133
+ </span>
134
+ <Badge variant="destructive">
135
+ -{calculateDiscount(product.price, product.originalPrice)}%
136
+ </Badge>
137
+ </>
138
+ )}
139
+ </div>
140
+ {product.inStock === false && (
141
+ <p className="text-sm text-destructive">Out of stock</p>
142
+ )}
143
+ </div>
144
+
145
+ <p className="text-muted-foreground">{product.description}</p>
146
+
147
+ {/* Variants */}
148
+ {product.variants && product.variants.length > 0 && (
149
+ <div className="space-y-2">
150
+ <label className="text-sm font-medium">
151
+ {product.variants[0].name}
152
+ </label>
153
+ <div className="flex flex-wrap gap-2">
154
+ {product.variants.map((variant) => (
155
+ <Button
156
+ key={variant.id}
157
+ variant={selectedVariant === variant.id ? 'default' : 'outline'}
158
+ size="sm"
159
+ onClick={() => setSelectedVariant(variant.id)}
160
+ >
161
+ {variant.value}
162
+ </Button>
163
+ ))}
164
+ </div>
165
+ </div>
166
+ )}
167
+
168
+ {/* Quantity and Actions */}
169
+ <div className="space-y-4">
170
+ <div className="flex items-center gap-4">
171
+ <label className="text-sm font-medium">Quantity:</label>
172
+ <div className="flex items-center gap-2">
173
+ <Button
174
+ size="icon"
175
+ variant="outline"
176
+ onClick={() => setQuantity(Math.max(1, quantity - 1))}
177
+ disabled={quantity <= 1}
178
+ >
179
+ -
180
+ </Button>
181
+ <span className="w-12 text-center">{quantity}</span>
182
+ <Button
183
+ size="icon"
184
+ variant="outline"
185
+ onClick={() => setQuantity(quantity + 1)}
186
+ >
187
+ +
188
+ </Button>
189
+ </div>
190
+ </div>
191
+
192
+ <div className="flex gap-4">
193
+ <Button
194
+ className="flex-1"
195
+ size="lg"
196
+ disabled={product.inStock === false}
197
+ onClick={() => onAddToCart?.(quantity, selectedVariant)}
198
+ >
199
+ <ShoppingCart className="mr-2 h-5 w-5" />
200
+ Add to Cart
201
+ </Button>
202
+ <Button
203
+ size="lg"
204
+ variant="outline"
205
+ onClick={onAddToWishlist}
206
+ >
207
+ <Heart className="h-5 w-5" />
208
+ </Button>
209
+ <Button
210
+ size="lg"
211
+ variant="outline"
212
+ onClick={onShare}
213
+ >
214
+ <Share2 className="h-5 w-5" />
215
+ </Button>
216
+ </div>
217
+ </div>
218
+
219
+ {/* Tabs */}
220
+ {(product.features || product.specifications) && (
221
+ <Tabs defaultValue="features" className="w-full">
222
+ <TabsList className="grid w-full grid-cols-2">
223
+ {product.features && <TabsTrigger value="features">Features</TabsTrigger>}
224
+ {product.specifications && (
225
+ <TabsTrigger value="specifications">Specifications</TabsTrigger>
226
+ )}
227
+ </TabsList>
228
+ {product.features && (
229
+ <TabsContent value="features" className="space-y-2">
230
+ <ul className="list-inside list-disc space-y-1">
231
+ {product.features.map((feature, i) => (
232
+ <li key={i} className="text-sm">
233
+ {feature}
234
+ </li>
235
+ ))}
236
+ </ul>
237
+ </TabsContent>
238
+ )}
239
+ {product.specifications && (
240
+ <TabsContent value="specifications">
241
+ <dl className="space-y-2">
242
+ {Object.entries(product.specifications).map(([key, value]) => (
243
+ <div key={key} className="flex gap-4 text-sm">
244
+ <dt className="min-w-[120px] font-medium">{key}:</dt>
245
+ <dd className="text-muted-foreground">{value}</dd>
246
+ </div>
247
+ ))}
248
+ </dl>
249
+ </TabsContent>
250
+ )}
251
+ </Tabs>
252
+ )}
253
+ </div>
254
+ </div>
255
+ </div>
256
+ )
257
+ }
@@ -0,0 +1,148 @@
1
+ 'use client'
2
+
3
+ import { cn } from '@hanzo/ui/util'
4
+ import { Button } from '@hanzo/ui/primitives'
5
+ import { Card, CardContent, CardFooter } from '@hanzo/ui/primitives'
6
+ import { Badge } from '@hanzo/ui/primitives'
7
+ import { Star, ShoppingCart } from 'lucide-react'
8
+
9
+ export interface Product {
10
+ id: string | number
11
+ name: string
12
+ description?: string
13
+ price: number
14
+ originalPrice?: number
15
+ currency?: string
16
+ image: string
17
+ rating?: number
18
+ reviewCount?: number
19
+ badge?: string
20
+ inStock?: boolean
21
+ }
22
+
23
+ interface ProductGridProps extends React.ComponentPropsWithoutRef<'section'> {
24
+ products: Product[]
25
+ columns?: 2 | 3 | 4
26
+ onAddToCart?: (product: Product) => void
27
+ onProductClick?: (product: Product) => void
28
+ }
29
+
30
+ export function ProductGrid({
31
+ className,
32
+ products,
33
+ columns = 4,
34
+ onAddToCart,
35
+ onProductClick,
36
+ ...props
37
+ }: ProductGridProps) {
38
+ const gridCols = {
39
+ 2: 'sm:grid-cols-2',
40
+ 3: 'sm:grid-cols-2 lg:grid-cols-3',
41
+ 4: 'sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4',
42
+ }[columns]
43
+
44
+ const formatPrice = (price: number, currency = '$') => {
45
+ return `${currency}${price.toFixed(2)}`
46
+ }
47
+
48
+ const calculateDiscount = (price: number, originalPrice: number) => {
49
+ return Math.round(((originalPrice - price) / originalPrice) * 100)
50
+ }
51
+
52
+ return (
53
+ <section className={cn('py-8', className)} {...props}>
54
+ <div className="container">
55
+ <div className={cn('grid gap-6', gridCols)}>
56
+ {products.map((product) => (
57
+ <Card
58
+ key={product.id}
59
+ className="group relative overflow-hidden"
60
+ >
61
+ {product.badge && (
62
+ <Badge className="absolute left-2 top-2 z-10">
63
+ {product.badge}
64
+ </Badge>
65
+ )}
66
+ {product.originalPrice && product.originalPrice > product.price && (
67
+ <Badge
68
+ variant="destructive"
69
+ className="absolute right-2 top-2 z-10"
70
+ >
71
+ -{calculateDiscount(product.price, product.originalPrice)}%
72
+ </Badge>
73
+ )}
74
+
75
+ <div
76
+ className="aspect-square cursor-pointer overflow-hidden"
77
+ onClick={() => onProductClick?.(product)}
78
+ >
79
+ <img
80
+ src={product.image}
81
+ alt={product.name}
82
+ className="h-full w-full object-cover transition-transform group-hover:scale-105"
83
+ />
84
+ </div>
85
+
86
+ <CardContent className="p-4">
87
+ <h3
88
+ className="cursor-pointer font-semibold hover:underline"
89
+ onClick={() => onProductClick?.(product)}
90
+ >
91
+ {product.name}
92
+ </h3>
93
+
94
+ {product.description && (
95
+ <p className="mt-1 line-clamp-2 text-sm text-muted-foreground">
96
+ {product.description}
97
+ </p>
98
+ )}
99
+
100
+ {product.rating !== undefined && (
101
+ <div className="mt-2 flex items-center gap-2">
102
+ <div className="flex items-center gap-1">
103
+ <Star className="h-4 w-4 fill-primary text-primary" />
104
+ <span className="text-sm font-medium">
105
+ {product.rating.toFixed(1)}
106
+ </span>
107
+ </div>
108
+ {product.reviewCount !== undefined && (
109
+ <span className="text-sm text-muted-foreground">
110
+ ({product.reviewCount})
111
+ </span>
112
+ )}
113
+ </div>
114
+ )}
115
+
116
+ <div className="mt-3 flex items-center gap-2">
117
+ <span className="text-lg font-bold">
118
+ {formatPrice(product.price, product.currency)}
119
+ </span>
120
+ {product.originalPrice && product.originalPrice > product.price && (
121
+ <span className="text-sm text-muted-foreground line-through">
122
+ {formatPrice(product.originalPrice, product.currency)}
123
+ </span>
124
+ )}
125
+ </div>
126
+
127
+ {product.inStock === false && (
128
+ <p className="mt-2 text-sm text-destructive">Out of stock</p>
129
+ )}
130
+ </CardContent>
131
+
132
+ <CardFooter className="p-4 pt-0">
133
+ <Button
134
+ className="w-full"
135
+ disabled={product.inStock === false}
136
+ onClick={() => onAddToCart?.(product)}
137
+ >
138
+ <ShoppingCart className="mr-2 h-4 w-4" />
139
+ Add to cart
140
+ </Button>
141
+ </CardFooter>
142
+ </Card>
143
+ ))}
144
+ </div>
145
+ </div>
146
+ </section>
147
+ )
148
+ }