@cartbase/storefront 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (176) hide show
  1. package/package.json +226 -0
  2. package/src/api/auth.ts +108 -0
  3. package/src/api/carts.ts +506 -0
  4. package/src/api/categories.ts +184 -0
  5. package/src/api/checkout.ts +440 -0
  6. package/src/api/collections.ts +130 -0
  7. package/src/api/consent.ts +75 -0
  8. package/src/api/content.ts +125 -0
  9. package/src/api/customers.ts +307 -0
  10. package/src/api/gift-cards.ts +112 -0
  11. package/src/api/http.ts +122 -0
  12. package/src/api/index.ts +29 -0
  13. package/src/api/integrations.ts +130 -0
  14. package/src/api/menus.ts +77 -0
  15. package/src/api/metaobjects.ts +136 -0
  16. package/src/api/orders.ts +290 -0
  17. package/src/api/products.ts +303 -0
  18. package/src/api/redirects.ts +37 -0
  19. package/src/api/regions.ts +141 -0
  20. package/src/api/reviews.ts +259 -0
  21. package/src/api/search.ts +133 -0
  22. package/src/api/types.ts +91 -0
  23. package/src/cart-drawer/cart-drawer.tsx +86 -0
  24. package/src/cart-drawer/context.tsx +569 -0
  25. package/src/cart-drawer/continue-shopping.tsx +28 -0
  26. package/src/cart-drawer/cross-sell-carousel.tsx +212 -0
  27. package/src/cart-drawer/cross-sell-sidebar.tsx +158 -0
  28. package/src/cart-drawer/empty.tsx +52 -0
  29. package/src/cart-drawer/free-gift.tsx +71 -0
  30. package/src/cart-drawer/gift-wrap.tsx +83 -0
  31. package/src/cart-drawer/header.tsx +52 -0
  32. package/src/cart-drawer/index.ts +69 -0
  33. package/src/cart-drawer/item/index.tsx +164 -0
  34. package/src/cart-drawer/item/quantity.tsx +100 -0
  35. package/src/cart-drawer/item/upsell.tsx +110 -0
  36. package/src/cart-drawer/item/variant.tsx +46 -0
  37. package/src/cart-drawer/labels-bg.ts +72 -0
  38. package/src/cart-drawer/labels.ts +119 -0
  39. package/src/cart-drawer/notes.tsx +131 -0
  40. package/src/cart-drawer/payment-badges.tsx +96 -0
  41. package/src/cart-drawer/promo-banner.tsx +43 -0
  42. package/src/cart-drawer/rewards-points.tsx +78 -0
  43. package/src/cart-drawer/sticky-footer.tsx +73 -0
  44. package/src/cart-drawer/summary-breakdown.tsx +196 -0
  45. package/src/cart-drawer/template.tsx +225 -0
  46. package/src/cart-drawer/tiered-progress.tsx +168 -0
  47. package/src/checkout/address-error-copy.ts +119 -0
  48. package/src/checkout/address-form.tsx +224 -0
  49. package/src/checkout/address-select.tsx +79 -0
  50. package/src/checkout/boxnow-locker-selector.tsx +410 -0
  51. package/src/checkout/checkout-client.tsx +222 -0
  52. package/src/checkout/company-details.tsx +94 -0
  53. package/src/checkout/compare-addresses.ts +40 -0
  54. package/src/checkout/context.tsx +76 -0
  55. package/src/checkout/discount-section.tsx +218 -0
  56. package/src/checkout/econt-office-selector.tsx +332 -0
  57. package/src/checkout/error-message.tsx +25 -0
  58. package/src/checkout/geocode.ts +154 -0
  59. package/src/checkout/gift-card-section.tsx +224 -0
  60. package/src/checkout/index.ts +74 -0
  61. package/src/checkout/labels-bg.ts +128 -0
  62. package/src/checkout/labels.ts +263 -0
  63. package/src/checkout/line-item-card.tsx +152 -0
  64. package/src/checkout/order-summary.tsx +524 -0
  65. package/src/checkout/payment-button.tsx +373 -0
  66. package/src/checkout/payment-error-copy.ts +269 -0
  67. package/src/checkout/payment-method-list.tsx +365 -0
  68. package/src/checkout/payment-wrapper.tsx +102 -0
  69. package/src/checkout/promotion-error-copy.ts +124 -0
  70. package/src/checkout/shipping-method-list.tsx +335 -0
  71. package/src/checkout/stripe-wrapper.tsx +165 -0
  72. package/src/checkout/use-checkout-orchestration.ts +1504 -0
  73. package/src/common/cart-button-client.tsx +39 -0
  74. package/src/common/cart-button.tsx +28 -0
  75. package/src/common/country-select.tsx +65 -0
  76. package/src/common/delete-button.tsx +66 -0
  77. package/src/common/index.ts +17 -0
  78. package/src/common/language-select.tsx +78 -0
  79. package/src/common/localized-link.tsx +45 -0
  80. package/src/common/skeleton.tsx +29 -0
  81. package/src/index.ts +12 -0
  82. package/src/lib/cart-helpers.ts +113 -0
  83. package/src/lib/dual-price.tsx +73 -0
  84. package/src/lib/get-percentage-diff.ts +5 -0
  85. package/src/lib/get-product-price.ts +133 -0
  86. package/src/lib/hooks/use-intersection.ts +30 -0
  87. package/src/lib/hooks/use-toggle-state.ts +25 -0
  88. package/src/lib/money.ts +73 -0
  89. package/src/lib/payment-constants.ts +66 -0
  90. package/src/lib/product.ts +22 -0
  91. package/src/lib/sort-products.ts +63 -0
  92. package/src/lib/store-api-error.ts +36 -0
  93. package/src/lib/utils.ts +16 -0
  94. package/src/order/context.tsx +32 -0
  95. package/src/order/index.ts +63 -0
  96. package/src/order/labels-bg.ts +39 -0
  97. package/src/order/labels.ts +79 -0
  98. package/src/order/order-address-card.tsx +47 -0
  99. package/src/order/order-completed-template.tsx +165 -0
  100. package/src/order/order-confirmation-header.tsx +65 -0
  101. package/src/order/order-delivery-card.tsx +258 -0
  102. package/src/order/order-help-section.tsx +47 -0
  103. package/src/order/order-item.tsx +201 -0
  104. package/src/order/order-items-list.tsx +52 -0
  105. package/src/order/order-payment-card.tsx +95 -0
  106. package/src/order/order-timeline.tsx +141 -0
  107. package/src/order/order-totals.tsx +245 -0
  108. package/src/primitives/field.tsx +125 -0
  109. package/src/primitives/select-field.tsx +77 -0
  110. package/src/primitives/ui/accordion.tsx +61 -0
  111. package/src/primitives/ui/button.tsx +68 -0
  112. package/src/primitives/ui/collapsible.tsx +16 -0
  113. package/src/primitives/ui/dialog.tsx +112 -0
  114. package/src/primitives/ui/input.tsx +30 -0
  115. package/src/primitives/ui/label.tsx +31 -0
  116. package/src/primitives/ui/popover.tsx +38 -0
  117. package/src/primitives/ui/select.tsx +163 -0
  118. package/src/primitives/ui/sheet.tsx +131 -0
  119. package/src/primitives/ui/tabs.tsx +62 -0
  120. package/src/products/context.tsx +34 -0
  121. package/src/products/image-gallery.tsx +43 -0
  122. package/src/products/index.ts +44 -0
  123. package/src/products/labels-bg.ts +35 -0
  124. package/src/products/labels.ts +57 -0
  125. package/src/products/mobile-actions.tsx +180 -0
  126. package/src/products/option-select.tsx +67 -0
  127. package/src/products/preview-price.tsx +36 -0
  128. package/src/products/product-actions-wrapper.tsx +58 -0
  129. package/src/products/product-actions.tsx +217 -0
  130. package/src/products/product-info.tsx +43 -0
  131. package/src/products/product-preview.tsx +49 -0
  132. package/src/products/product-price.tsx +69 -0
  133. package/src/products/product-tabs.tsx +169 -0
  134. package/src/products/product-template.tsx +114 -0
  135. package/src/products/purchase-options.tsx +130 -0
  136. package/src/products/related-products.tsx +86 -0
  137. package/src/products/thumbnail.tsx +71 -0
  138. package/src/products/variant-matching.ts +71 -0
  139. package/src/reviews-ui/helpers.ts +174 -0
  140. package/src/reviews-ui/index.ts +74 -0
  141. package/src/reviews-ui/labels-bg.ts +91 -0
  142. package/src/reviews-ui/labels.ts +199 -0
  143. package/src/reviews-ui/photo-upload.tsx +345 -0
  144. package/src/reviews-ui/review-list.tsx +249 -0
  145. package/src/reviews-ui/review-widget.tsx +224 -0
  146. package/src/reviews-ui/review-wizard.tsx +560 -0
  147. package/src/reviews-ui/star-badge.tsx +104 -0
  148. package/src/reviews-ui/wizard-state.ts +81 -0
  149. package/src/store/category-template.tsx +129 -0
  150. package/src/store/collection-template.tsx +139 -0
  151. package/src/store/index.ts +41 -0
  152. package/src/store/labels-bg.ts +22 -0
  153. package/src/store/labels.ts +52 -0
  154. package/src/store/paginated-products.tsx +116 -0
  155. package/src/store/pagination.tsx +103 -0
  156. package/src/store/search-params.ts +256 -0
  157. package/src/store/search-template.tsx +249 -0
  158. package/src/store/skeleton-product-grid.tsx +26 -0
  159. package/src/store/sort-select.tsx +81 -0
  160. package/src/store/store-template.tsx +65 -0
  161. package/src/tracking/attribution.ts +418 -0
  162. package/src/tracking/consent-banner.tsx +355 -0
  163. package/src/tracking/consent-init.tsx +44 -0
  164. package/src/tracking/consent.ts +243 -0
  165. package/src/tracking/fbq.ts +168 -0
  166. package/src/tracking/ga4.tsx +49 -0
  167. package/src/tracking/get-tracking-attribution.ts +224 -0
  168. package/src/tracking/get-tracking-config.ts +50 -0
  169. package/src/tracking/gtag.ts +200 -0
  170. package/src/tracking/index.ts +133 -0
  171. package/src/tracking/meta-pixel.tsx +166 -0
  172. package/src/tracking/rybbit-events.ts +242 -0
  173. package/src/tracking/rybbit.tsx +40 -0
  174. package/src/tracking/types.ts +185 -0
  175. package/src/tracking/use-engagement-time.ts +58 -0
  176. package/tailwind-preset.cjs +72 -0
@@ -0,0 +1,112 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as DialogPrimitive from "@radix-ui/react-dialog"
5
+ import { X } from "lucide-react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ /**
10
+ * Dialog — shadcn/ui dialog primitive (wraps Radix Dialog).
11
+ *
12
+ * Modal dialog with overlay, focus trap, scroll lock, escape-to-close. Used
13
+ * as the foundation for confirm dialogs, auth modals, and anything else that
14
+ * needs a centered modal.
15
+ *
16
+ * For side-panel UX (like the cart drawer), see `sheet.tsx` which is built
17
+ * on the same Radix Dialog primitive.
18
+ */
19
+
20
+ const Dialog = DialogPrimitive.Root
21
+ const DialogTrigger = DialogPrimitive.Trigger
22
+ const DialogPortal = DialogPrimitive.Portal
23
+ const DialogClose = DialogPrimitive.Close
24
+
25
+ const DialogOverlay = React.forwardRef<
26
+ React.ElementRef<typeof DialogPrimitive.Overlay>,
27
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
28
+ >(({ className, ...props }, ref) => (
29
+ <DialogPrimitive.Overlay
30
+ ref={ref}
31
+ className={cn(
32
+ "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
33
+ className
34
+ )}
35
+ {...props}
36
+ />
37
+ ))
38
+ DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
39
+
40
+ const DialogContent = React.forwardRef<
41
+ React.ElementRef<typeof DialogPrimitive.Content>,
42
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
43
+ >(({ className, children, ...props }, ref) => (
44
+ <DialogPortal>
45
+ <DialogOverlay />
46
+ <DialogPrimitive.Content
47
+ ref={ref}
48
+ className={cn(
49
+ "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border border-border bg-card p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
50
+ className
51
+ )}
52
+ {...props}
53
+ >
54
+ {children}
55
+ <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-surface transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:pointer-events-none">
56
+ <X className="h-4 w-4" />
57
+ <span className="sr-only">Close</span>
58
+ </DialogPrimitive.Close>
59
+ </DialogPrimitive.Content>
60
+ </DialogPortal>
61
+ ))
62
+ DialogContent.displayName = DialogPrimitive.Content.displayName
63
+
64
+ const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
65
+ <div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
66
+ )
67
+ DialogHeader.displayName = "DialogHeader"
68
+
69
+ const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
70
+ <div
71
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
72
+ {...props}
73
+ />
74
+ )
75
+ DialogFooter.displayName = "DialogFooter"
76
+
77
+ const DialogTitle = React.forwardRef<
78
+ React.ElementRef<typeof DialogPrimitive.Title>,
79
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
80
+ >(({ className, ...props }, ref) => (
81
+ <DialogPrimitive.Title
82
+ ref={ref}
83
+ className={cn("text-lg font-semibold leading-none tracking-tight", className)}
84
+ {...props}
85
+ />
86
+ ))
87
+ DialogTitle.displayName = DialogPrimitive.Title.displayName
88
+
89
+ const DialogDescription = React.forwardRef<
90
+ React.ElementRef<typeof DialogPrimitive.Description>,
91
+ React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
92
+ >(({ className, ...props }, ref) => (
93
+ <DialogPrimitive.Description
94
+ ref={ref}
95
+ className={cn("text-sm text-muted-foreground", className)}
96
+ {...props}
97
+ />
98
+ ))
99
+ DialogDescription.displayName = DialogPrimitive.Description.displayName
100
+
101
+ export {
102
+ Dialog,
103
+ DialogPortal,
104
+ DialogOverlay,
105
+ DialogTrigger,
106
+ DialogClose,
107
+ DialogContent,
108
+ DialogHeader,
109
+ DialogFooter,
110
+ DialogTitle,
111
+ DialogDescription,
112
+ }
@@ -0,0 +1,30 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+
5
+ import { cn } from "../../lib/utils"
6
+
7
+ /**
8
+ * Input — shadcn/ui base input primitive.
9
+ *
10
+ * Unstyled enough to be composed into richer inputs (see `Field` for the
11
+ * floating-label variant). Accepts all native input props.
12
+ */
13
+ const Input = React.forwardRef<HTMLInputElement, React.InputHTMLAttributes<HTMLInputElement>>(
14
+ ({ className, type, ...props }, ref) => {
15
+ return (
16
+ <input
17
+ type={type}
18
+ className={cn(
19
+ "flex h-9 w-full rounded-md border border-border bg-card px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-accent disabled:cursor-not-allowed disabled:opacity-50",
20
+ className
21
+ )}
22
+ ref={ref}
23
+ {...props}
24
+ />
25
+ )
26
+ }
27
+ )
28
+ Input.displayName = "Input"
29
+
30
+ export { Input }
@@ -0,0 +1,31 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as LabelPrimitive from "@radix-ui/react-label"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ /**
10
+ * Label — shadcn/ui label primitive (wraps Radix Label).
11
+ *
12
+ * Automatic focus delegation to the associated input, respects disabled peers.
13
+ */
14
+ const labelVariants = cva(
15
+ "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
16
+ )
17
+
18
+ const Label = React.forwardRef<
19
+ React.ElementRef<typeof LabelPrimitive.Root>,
20
+ React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
21
+ VariantProps<typeof labelVariants>
22
+ >(({ className, ...props }, ref) => (
23
+ <LabelPrimitive.Root
24
+ ref={ref}
25
+ className={cn(labelVariants(), className)}
26
+ {...props}
27
+ />
28
+ ))
29
+ Label.displayName = LabelPrimitive.Root.displayName
30
+
31
+ export { Label }
@@ -0,0 +1,38 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as PopoverPrimitive from "@radix-ui/react-popover"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ /**
9
+ * Popover — shadcn/ui popover primitive (wraps Radix Popover).
10
+ *
11
+ * Lightweight floating UI for disclosure, toolbars, inline menus. Used by
12
+ * the Econt office selector's expandable search panel and elsewhere.
13
+ */
14
+
15
+ const Popover = PopoverPrimitive.Root
16
+ const PopoverTrigger = PopoverPrimitive.Trigger
17
+ const PopoverAnchor = PopoverPrimitive.Anchor
18
+
19
+ const PopoverContent = React.forwardRef<
20
+ React.ElementRef<typeof PopoverPrimitive.Content>,
21
+ React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
22
+ >(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
23
+ <PopoverPrimitive.Portal>
24
+ <PopoverPrimitive.Content
25
+ ref={ref}
26
+ align={align}
27
+ sideOffset={sideOffset}
28
+ className={cn(
29
+ "z-50 w-72 rounded-md border border-border bg-card p-4 text-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
30
+ className
31
+ )}
32
+ {...props}
33
+ />
34
+ </PopoverPrimitive.Portal>
35
+ ))
36
+ PopoverContent.displayName = PopoverPrimitive.Content.displayName
37
+
38
+ export { Popover, PopoverTrigger, PopoverAnchor, PopoverContent }
@@ -0,0 +1,163 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SelectPrimitive from "@radix-ui/react-select"
5
+ import { Check, ChevronDown, ChevronUp } from "lucide-react"
6
+
7
+ import { cn } from "../../lib/utils"
8
+
9
+ /**
10
+ * Select — shadcn/ui select primitive (wraps Radix Select).
11
+ *
12
+ * Accessible, keyboard-navigated dropdown. Used as the foundation for
13
+ * `SelectField` (floating-label variant) and any other select UX.
14
+ */
15
+
16
+ const Select = SelectPrimitive.Root
17
+ const SelectGroup = SelectPrimitive.Group
18
+ const SelectValue = SelectPrimitive.Value
19
+
20
+ const SelectTrigger = React.forwardRef<
21
+ React.ElementRef<typeof SelectPrimitive.Trigger>,
22
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
23
+ >(({ className, children, ...props }, ref) => (
24
+ <SelectPrimitive.Trigger
25
+ ref={ref}
26
+ className={cn(
27
+ "flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-border bg-card px-3 py-2 text-sm shadow-sm ring-offset-surface placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-primary disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
28
+ className
29
+ )}
30
+ {...props}
31
+ >
32
+ {children}
33
+ <SelectPrimitive.Icon asChild>
34
+ <ChevronDown className="h-4 w-4 opacity-50" />
35
+ </SelectPrimitive.Icon>
36
+ </SelectPrimitive.Trigger>
37
+ ))
38
+ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
39
+
40
+ const SelectScrollUpButton = React.forwardRef<
41
+ React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
42
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
43
+ >(({ className, ...props }, ref) => (
44
+ <SelectPrimitive.ScrollUpButton
45
+ ref={ref}
46
+ className={cn(
47
+ "flex cursor-default items-center justify-center py-1",
48
+ className
49
+ )}
50
+ {...props}
51
+ >
52
+ <ChevronUp className="h-4 w-4" />
53
+ </SelectPrimitive.ScrollUpButton>
54
+ ))
55
+ SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
56
+
57
+ const SelectScrollDownButton = React.forwardRef<
58
+ React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
59
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
60
+ >(({ className, ...props }, ref) => (
61
+ <SelectPrimitive.ScrollDownButton
62
+ ref={ref}
63
+ className={cn(
64
+ "flex cursor-default items-center justify-center py-1",
65
+ className
66
+ )}
67
+ {...props}
68
+ >
69
+ <ChevronDown className="h-4 w-4" />
70
+ </SelectPrimitive.ScrollDownButton>
71
+ ))
72
+ SelectScrollDownButton.displayName = SelectPrimitive.ScrollDownButton.displayName
73
+
74
+ const SelectContent = React.forwardRef<
75
+ React.ElementRef<typeof SelectPrimitive.Content>,
76
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
77
+ >(({ className, children, position = "popper", ...props }, ref) => (
78
+ <SelectPrimitive.Portal>
79
+ <SelectPrimitive.Content
80
+ ref={ref}
81
+ className={cn(
82
+ "relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border border-border bg-card text-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
83
+ position === "popper" &&
84
+ "data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
85
+ className
86
+ )}
87
+ position={position}
88
+ {...props}
89
+ >
90
+ <SelectScrollUpButton />
91
+ <SelectPrimitive.Viewport
92
+ className={cn(
93
+ "p-1",
94
+ position === "popper" &&
95
+ "h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
96
+ )}
97
+ >
98
+ {children}
99
+ </SelectPrimitive.Viewport>
100
+ <SelectScrollDownButton />
101
+ </SelectPrimitive.Content>
102
+ </SelectPrimitive.Portal>
103
+ ))
104
+ SelectContent.displayName = SelectPrimitive.Content.displayName
105
+
106
+ const SelectLabel = React.forwardRef<
107
+ React.ElementRef<typeof SelectPrimitive.Label>,
108
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
109
+ >(({ className, ...props }, ref) => (
110
+ <SelectPrimitive.Label
111
+ ref={ref}
112
+ className={cn("px-2 py-1.5 text-sm font-semibold", className)}
113
+ {...props}
114
+ />
115
+ ))
116
+ SelectLabel.displayName = SelectPrimitive.Label.displayName
117
+
118
+ const SelectItem = React.forwardRef<
119
+ React.ElementRef<typeof SelectPrimitive.Item>,
120
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
121
+ >(({ className, children, ...props }, ref) => (
122
+ <SelectPrimitive.Item
123
+ ref={ref}
124
+ className={cn(
125
+ "relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-muted focus:text-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
126
+ className
127
+ )}
128
+ {...props}
129
+ >
130
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
131
+ <SelectPrimitive.ItemIndicator>
132
+ <Check className="h-4 w-4" />
133
+ </SelectPrimitive.ItemIndicator>
134
+ </span>
135
+ <SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
136
+ </SelectPrimitive.Item>
137
+ ))
138
+ SelectItem.displayName = SelectPrimitive.Item.displayName
139
+
140
+ const SelectSeparator = React.forwardRef<
141
+ React.ElementRef<typeof SelectPrimitive.Separator>,
142
+ React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
143
+ >(({ className, ...props }, ref) => (
144
+ <SelectPrimitive.Separator
145
+ ref={ref}
146
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
147
+ {...props}
148
+ />
149
+ ))
150
+ SelectSeparator.displayName = SelectPrimitive.Separator.displayName
151
+
152
+ export {
153
+ Select,
154
+ SelectGroup,
155
+ SelectValue,
156
+ SelectTrigger,
157
+ SelectContent,
158
+ SelectLabel,
159
+ SelectItem,
160
+ SelectSeparator,
161
+ SelectScrollUpButton,
162
+ SelectScrollDownButton,
163
+ }
@@ -0,0 +1,131 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as SheetPrimitive from "@radix-ui/react-dialog"
5
+ import { cva, type VariantProps } from "class-variance-authority"
6
+ import { X } from "lucide-react"
7
+
8
+ import { cn } from "../../lib/utils"
9
+
10
+ /**
11
+ * Sheet — side-panel primitive built on Radix Dialog.
12
+ *
13
+ * This is the foundation for the cart drawer. Slides in from any side
14
+ * (left, right, top, bottom), handles overlay, focus trap, scroll lock,
15
+ * and escape-to-close. The actual cart-drawer composition lives in
16
+ * `src/cart-drawer/` and uses Sheet as its shell.
17
+ */
18
+
19
+ const Sheet = SheetPrimitive.Root
20
+ const SheetTrigger = SheetPrimitive.Trigger
21
+ const SheetClose = SheetPrimitive.Close
22
+ const SheetPortal = SheetPrimitive.Portal
23
+
24
+ const SheetOverlay = React.forwardRef<
25
+ React.ElementRef<typeof SheetPrimitive.Overlay>,
26
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
27
+ >(({ className, ...props }, ref) => (
28
+ <SheetPrimitive.Overlay
29
+ className={cn(
30
+ "fixed inset-0 z-50 bg-black/50 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
31
+ className
32
+ )}
33
+ {...props}
34
+ ref={ref}
35
+ />
36
+ ))
37
+ SheetOverlay.displayName = SheetPrimitive.Overlay.displayName
38
+
39
+ const sheetVariants = cva(
40
+ "fixed z-50 gap-4 bg-card p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
41
+ {
42
+ variants: {
43
+ side: {
44
+ top: "inset-x-0 top-0 border-b border-border data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
45
+ bottom:
46
+ "inset-x-0 bottom-0 border-t border-border data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
47
+ left: "inset-y-0 left-0 h-full w-3/4 border-r border-border data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
48
+ right:
49
+ "inset-y-0 right-0 h-full w-3/4 border-l border-border data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
50
+ },
51
+ },
52
+ defaultVariants: {
53
+ side: "right",
54
+ },
55
+ }
56
+ )
57
+
58
+ interface SheetContentProps
59
+ extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
60
+ VariantProps<typeof sheetVariants> {}
61
+
62
+ const SheetContent = React.forwardRef<
63
+ React.ElementRef<typeof SheetPrimitive.Content>,
64
+ SheetContentProps
65
+ >(({ side = "right", className, children, ...props }, ref) => (
66
+ <SheetPortal>
67
+ <SheetOverlay />
68
+ <SheetPrimitive.Content
69
+ ref={ref}
70
+ className={cn(sheetVariants({ side }), className)}
71
+ {...props}
72
+ >
73
+ {children}
74
+ <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-surface transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-primary focus:ring-offset-2 disabled:pointer-events-none">
75
+ <X className="h-4 w-4" />
76
+ <span className="sr-only">Close</span>
77
+ </SheetPrimitive.Close>
78
+ </SheetPrimitive.Content>
79
+ </SheetPortal>
80
+ ))
81
+ SheetContent.displayName = SheetPrimitive.Content.displayName
82
+
83
+ const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
84
+ <div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
85
+ )
86
+ SheetHeader.displayName = "SheetHeader"
87
+
88
+ const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
89
+ <div
90
+ className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
91
+ {...props}
92
+ />
93
+ )
94
+ SheetFooter.displayName = "SheetFooter"
95
+
96
+ const SheetTitle = React.forwardRef<
97
+ React.ElementRef<typeof SheetPrimitive.Title>,
98
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
99
+ >(({ className, ...props }, ref) => (
100
+ <SheetPrimitive.Title
101
+ ref={ref}
102
+ className={cn("text-lg font-semibold text-foreground", className)}
103
+ {...props}
104
+ />
105
+ ))
106
+ SheetTitle.displayName = SheetPrimitive.Title.displayName
107
+
108
+ const SheetDescription = React.forwardRef<
109
+ React.ElementRef<typeof SheetPrimitive.Description>,
110
+ React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
111
+ >(({ className, ...props }, ref) => (
112
+ <SheetPrimitive.Description
113
+ ref={ref}
114
+ className={cn("text-sm text-muted-foreground", className)}
115
+ {...props}
116
+ />
117
+ ))
118
+ SheetDescription.displayName = SheetPrimitive.Description.displayName
119
+
120
+ export {
121
+ Sheet,
122
+ SheetPortal,
123
+ SheetOverlay,
124
+ SheetTrigger,
125
+ SheetClose,
126
+ SheetContent,
127
+ SheetHeader,
128
+ SheetFooter,
129
+ SheetTitle,
130
+ SheetDescription,
131
+ }
@@ -0,0 +1,62 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import * as TabsPrimitive from "@radix-ui/react-tabs"
5
+
6
+ import { cn } from "../../lib/utils"
7
+
8
+ /**
9
+ * Tabs — shadcn/ui tabs primitive (wraps Radix Tabs).
10
+ *
11
+ * Used by the checkout payment method switcher (card vs COD) and anywhere
12
+ * else we need a horizontal tab switcher.
13
+ */
14
+
15
+ const Tabs = TabsPrimitive.Root
16
+
17
+ const TabsList = React.forwardRef<
18
+ React.ElementRef<typeof TabsPrimitive.List>,
19
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.List>
20
+ >(({ className, ...props }, ref) => (
21
+ <TabsPrimitive.List
22
+ ref={ref}
23
+ className={cn(
24
+ "inline-flex h-9 items-center justify-center rounded-lg bg-muted p-1 text-muted-foreground",
25
+ className
26
+ )}
27
+ {...props}
28
+ />
29
+ ))
30
+ TabsList.displayName = TabsPrimitive.List.displayName
31
+
32
+ const TabsTrigger = React.forwardRef<
33
+ React.ElementRef<typeof TabsPrimitive.Trigger>,
34
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
35
+ >(({ className, ...props }, ref) => (
36
+ <TabsPrimitive.Trigger
37
+ ref={ref}
38
+ className={cn(
39
+ "inline-flex items-center justify-center whitespace-nowrap rounded-md px-3 py-1 text-sm font-medium ring-offset-surface transition-all focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow",
40
+ className
41
+ )}
42
+ {...props}
43
+ />
44
+ ))
45
+ TabsTrigger.displayName = TabsPrimitive.Trigger.displayName
46
+
47
+ const TabsContent = React.forwardRef<
48
+ React.ElementRef<typeof TabsPrimitive.Content>,
49
+ React.ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
50
+ >(({ className, ...props }, ref) => (
51
+ <TabsPrimitive.Content
52
+ ref={ref}
53
+ className={cn(
54
+ "mt-2 ring-offset-surface focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent focus-visible:ring-offset-2",
55
+ className
56
+ )}
57
+ {...props}
58
+ />
59
+ ))
60
+ TabsContent.displayName = TabsPrimitive.Content.displayName
61
+
62
+ export { Tabs, TabsList, TabsTrigger, TabsContent }
@@ -0,0 +1,34 @@
1
+ "use client"
2
+
3
+ /**
4
+ * Product-labels context — ported from `@1click/ui/src/products/context.tsx`
5
+ * (v2.3.1) unchanged. Wrap the product page (or the whole app) in
6
+ * `<ProductLabelsProvider labels={bulgarianProductLabels}>` to localize every
7
+ * product component at once; unset keys fall back to the English defaults.
8
+ */
9
+ import { createContext, useContext, type ReactNode } from "react"
10
+ import { defaultProductLabels, type ProductLabels } from "./labels"
11
+
12
+ const ProductLabelsContext = createContext<ProductLabels>(defaultProductLabels)
13
+
14
+ export function ProductLabelsProvider({
15
+ labels,
16
+ children,
17
+ }: {
18
+ labels?: Partial<ProductLabels>
19
+ children: ReactNode
20
+ }) {
21
+ const merged = labels
22
+ ? { ...defaultProductLabels, ...labels }
23
+ : defaultProductLabels
24
+
25
+ return (
26
+ <ProductLabelsContext.Provider value={merged}>
27
+ {children}
28
+ </ProductLabelsContext.Provider>
29
+ )
30
+ }
31
+
32
+ export function useProductLabels() {
33
+ return useContext(ProductLabelsContext)
34
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * PDP image gallery — ported from
3
+ * `@1click/ui/src/products/image-gallery.tsx` (v2.3.1). Data seam:
4
+ * `HttpTypes.StoreProductImage[]` → barter `StoreProductImage[]`
5
+ * (`@cartbase/storefront/api/products`; images arrive rank-ordered from the
6
+ * API). Server-safe.
7
+ */
8
+ import Image from "next/image"
9
+ import type { StoreProductImage } from "../api/products"
10
+
11
+ type ImageGalleryProps = {
12
+ images: StoreProductImage[]
13
+ }
14
+
15
+ const ImageGallery = ({ images }: ImageGalleryProps) => {
16
+ return (
17
+ <div className="flex items-start relative">
18
+ <div className="flex flex-col flex-1 sm:mx-16 gap-y-4">
19
+ {images.map((image, index) => (
20
+ <div
21
+ key={image.id}
22
+ className="relative aspect-[29/34] w-full overflow-hidden bg-muted rounded-lg"
23
+ id={image.id}
24
+ >
25
+ {!!image.url && (
26
+ <Image
27
+ src={image.url}
28
+ priority={index <= 2}
29
+ className="absolute inset-0 rounded-lg"
30
+ alt={`Product image ${index + 1}`}
31
+ fill
32
+ sizes="(max-width: 576px) 280px, (max-width: 768px) 360px, (max-width: 992px) 480px, 800px"
33
+ style={{ objectFit: "cover" }}
34
+ />
35
+ )}
36
+ </div>
37
+ ))}
38
+ </div>
39
+ </div>
40
+ )
41
+ }
42
+
43
+ export { ImageGallery, type ImageGalleryProps }