@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.
- package/package.json +226 -0
- package/src/api/auth.ts +108 -0
- package/src/api/carts.ts +506 -0
- package/src/api/categories.ts +184 -0
- package/src/api/checkout.ts +440 -0
- package/src/api/collections.ts +130 -0
- package/src/api/consent.ts +75 -0
- package/src/api/content.ts +125 -0
- package/src/api/customers.ts +307 -0
- package/src/api/gift-cards.ts +112 -0
- package/src/api/http.ts +122 -0
- package/src/api/index.ts +29 -0
- package/src/api/integrations.ts +130 -0
- package/src/api/menus.ts +77 -0
- package/src/api/metaobjects.ts +136 -0
- package/src/api/orders.ts +290 -0
- package/src/api/products.ts +303 -0
- package/src/api/redirects.ts +37 -0
- package/src/api/regions.ts +141 -0
- package/src/api/reviews.ts +259 -0
- package/src/api/search.ts +133 -0
- package/src/api/types.ts +91 -0
- package/src/cart-drawer/cart-drawer.tsx +86 -0
- package/src/cart-drawer/context.tsx +569 -0
- package/src/cart-drawer/continue-shopping.tsx +28 -0
- package/src/cart-drawer/cross-sell-carousel.tsx +212 -0
- package/src/cart-drawer/cross-sell-sidebar.tsx +158 -0
- package/src/cart-drawer/empty.tsx +52 -0
- package/src/cart-drawer/free-gift.tsx +71 -0
- package/src/cart-drawer/gift-wrap.tsx +83 -0
- package/src/cart-drawer/header.tsx +52 -0
- package/src/cart-drawer/index.ts +69 -0
- package/src/cart-drawer/item/index.tsx +164 -0
- package/src/cart-drawer/item/quantity.tsx +100 -0
- package/src/cart-drawer/item/upsell.tsx +110 -0
- package/src/cart-drawer/item/variant.tsx +46 -0
- package/src/cart-drawer/labels-bg.ts +72 -0
- package/src/cart-drawer/labels.ts +119 -0
- package/src/cart-drawer/notes.tsx +131 -0
- package/src/cart-drawer/payment-badges.tsx +96 -0
- package/src/cart-drawer/promo-banner.tsx +43 -0
- package/src/cart-drawer/rewards-points.tsx +78 -0
- package/src/cart-drawer/sticky-footer.tsx +73 -0
- package/src/cart-drawer/summary-breakdown.tsx +196 -0
- package/src/cart-drawer/template.tsx +225 -0
- package/src/cart-drawer/tiered-progress.tsx +168 -0
- package/src/checkout/address-error-copy.ts +119 -0
- package/src/checkout/address-form.tsx +224 -0
- package/src/checkout/address-select.tsx +79 -0
- package/src/checkout/boxnow-locker-selector.tsx +410 -0
- package/src/checkout/checkout-client.tsx +222 -0
- package/src/checkout/company-details.tsx +94 -0
- package/src/checkout/compare-addresses.ts +40 -0
- package/src/checkout/context.tsx +76 -0
- package/src/checkout/discount-section.tsx +218 -0
- package/src/checkout/econt-office-selector.tsx +332 -0
- package/src/checkout/error-message.tsx +25 -0
- package/src/checkout/geocode.ts +154 -0
- package/src/checkout/gift-card-section.tsx +224 -0
- package/src/checkout/index.ts +74 -0
- package/src/checkout/labels-bg.ts +128 -0
- package/src/checkout/labels.ts +263 -0
- package/src/checkout/line-item-card.tsx +152 -0
- package/src/checkout/order-summary.tsx +524 -0
- package/src/checkout/payment-button.tsx +373 -0
- package/src/checkout/payment-error-copy.ts +269 -0
- package/src/checkout/payment-method-list.tsx +365 -0
- package/src/checkout/payment-wrapper.tsx +102 -0
- package/src/checkout/promotion-error-copy.ts +124 -0
- package/src/checkout/shipping-method-list.tsx +335 -0
- package/src/checkout/stripe-wrapper.tsx +165 -0
- package/src/checkout/use-checkout-orchestration.ts +1504 -0
- package/src/common/cart-button-client.tsx +39 -0
- package/src/common/cart-button.tsx +28 -0
- package/src/common/country-select.tsx +65 -0
- package/src/common/delete-button.tsx +66 -0
- package/src/common/index.ts +17 -0
- package/src/common/language-select.tsx +78 -0
- package/src/common/localized-link.tsx +45 -0
- package/src/common/skeleton.tsx +29 -0
- package/src/index.ts +12 -0
- package/src/lib/cart-helpers.ts +113 -0
- package/src/lib/dual-price.tsx +73 -0
- package/src/lib/get-percentage-diff.ts +5 -0
- package/src/lib/get-product-price.ts +133 -0
- package/src/lib/hooks/use-intersection.ts +30 -0
- package/src/lib/hooks/use-toggle-state.ts +25 -0
- package/src/lib/money.ts +73 -0
- package/src/lib/payment-constants.ts +66 -0
- package/src/lib/product.ts +22 -0
- package/src/lib/sort-products.ts +63 -0
- package/src/lib/store-api-error.ts +36 -0
- package/src/lib/utils.ts +16 -0
- package/src/order/context.tsx +32 -0
- package/src/order/index.ts +63 -0
- package/src/order/labels-bg.ts +39 -0
- package/src/order/labels.ts +79 -0
- package/src/order/order-address-card.tsx +47 -0
- package/src/order/order-completed-template.tsx +165 -0
- package/src/order/order-confirmation-header.tsx +65 -0
- package/src/order/order-delivery-card.tsx +258 -0
- package/src/order/order-help-section.tsx +47 -0
- package/src/order/order-item.tsx +201 -0
- package/src/order/order-items-list.tsx +52 -0
- package/src/order/order-payment-card.tsx +95 -0
- package/src/order/order-timeline.tsx +141 -0
- package/src/order/order-totals.tsx +245 -0
- package/src/primitives/field.tsx +125 -0
- package/src/primitives/select-field.tsx +77 -0
- package/src/primitives/ui/accordion.tsx +61 -0
- package/src/primitives/ui/button.tsx +68 -0
- package/src/primitives/ui/collapsible.tsx +16 -0
- package/src/primitives/ui/dialog.tsx +112 -0
- package/src/primitives/ui/input.tsx +30 -0
- package/src/primitives/ui/label.tsx +31 -0
- package/src/primitives/ui/popover.tsx +38 -0
- package/src/primitives/ui/select.tsx +163 -0
- package/src/primitives/ui/sheet.tsx +131 -0
- package/src/primitives/ui/tabs.tsx +62 -0
- package/src/products/context.tsx +34 -0
- package/src/products/image-gallery.tsx +43 -0
- package/src/products/index.ts +44 -0
- package/src/products/labels-bg.ts +35 -0
- package/src/products/labels.ts +57 -0
- package/src/products/mobile-actions.tsx +180 -0
- package/src/products/option-select.tsx +67 -0
- package/src/products/preview-price.tsx +36 -0
- package/src/products/product-actions-wrapper.tsx +58 -0
- package/src/products/product-actions.tsx +217 -0
- package/src/products/product-info.tsx +43 -0
- package/src/products/product-preview.tsx +49 -0
- package/src/products/product-price.tsx +69 -0
- package/src/products/product-tabs.tsx +169 -0
- package/src/products/product-template.tsx +114 -0
- package/src/products/purchase-options.tsx +130 -0
- package/src/products/related-products.tsx +86 -0
- package/src/products/thumbnail.tsx +71 -0
- package/src/products/variant-matching.ts +71 -0
- package/src/reviews-ui/helpers.ts +174 -0
- package/src/reviews-ui/index.ts +74 -0
- package/src/reviews-ui/labels-bg.ts +91 -0
- package/src/reviews-ui/labels.ts +199 -0
- package/src/reviews-ui/photo-upload.tsx +345 -0
- package/src/reviews-ui/review-list.tsx +249 -0
- package/src/reviews-ui/review-widget.tsx +224 -0
- package/src/reviews-ui/review-wizard.tsx +560 -0
- package/src/reviews-ui/star-badge.tsx +104 -0
- package/src/reviews-ui/wizard-state.ts +81 -0
- package/src/store/category-template.tsx +129 -0
- package/src/store/collection-template.tsx +139 -0
- package/src/store/index.ts +41 -0
- package/src/store/labels-bg.ts +22 -0
- package/src/store/labels.ts +52 -0
- package/src/store/paginated-products.tsx +116 -0
- package/src/store/pagination.tsx +103 -0
- package/src/store/search-params.ts +256 -0
- package/src/store/search-template.tsx +249 -0
- package/src/store/skeleton-product-grid.tsx +26 -0
- package/src/store/sort-select.tsx +81 -0
- package/src/store/store-template.tsx +65 -0
- package/src/tracking/attribution.ts +418 -0
- package/src/tracking/consent-banner.tsx +355 -0
- package/src/tracking/consent-init.tsx +44 -0
- package/src/tracking/consent.ts +243 -0
- package/src/tracking/fbq.ts +168 -0
- package/src/tracking/ga4.tsx +49 -0
- package/src/tracking/get-tracking-attribution.ts +224 -0
- package/src/tracking/get-tracking-config.ts +50 -0
- package/src/tracking/gtag.ts +200 -0
- package/src/tracking/index.ts +133 -0
- package/src/tracking/meta-pixel.tsx +166 -0
- package/src/tracking/rybbit-events.ts +242 -0
- package/src/tracking/rybbit.tsx +40 -0
- package/src/tracking/types.ts +185 -0
- package/src/tracking/use-engagement-time.ts +58 -0
- 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 }
|