medusa-ui-common 2.0.1 → 2.3.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 +8 -2
- package/src/common/components/breadcrumb/index.tsx +5 -5
- package/src/common/components/cart-totals/index.tsx +39 -44
- package/src/common/components/checkbox/index.tsx +2 -2
- package/src/common/components/delete-button/index.tsx +6 -6
- package/src/common/components/discount-code/index.tsx +11 -11
- package/src/common/components/filter-checkbox-group/index.tsx +3 -3
- package/src/common/components/input/index.tsx +18 -6
- package/src/common/components/login-popup/index.tsx +8 -8
- package/src/common/components/modal/index.tsx +2 -2
- package/src/common/components/native-select/index.tsx +1 -1
- package/src/common/components/processing-overlay/index.tsx +4 -4
- package/src/common/components/side-panel/index.tsx +3 -3
- package/src/common/components/submit-button/index.tsx +17 -14
- package/src/common/components/themed-button/index.tsx +63 -0
- package/src/context/button-theme-context.tsx +131 -0
- package/src/context/site-theme-context.tsx +89 -0
- package/src/context/storefront-theme-classes-context.tsx +84 -0
- package/src/index.ts +8 -0
- package/src/skeletons/templates/skeleton-order-confirmed/index.tsx +1 -1
- package/src/storefront-providers.tsx +111 -3
- package/src/util/button-css-vars.ts +6 -0
- package/src/util/storefront-theme.ts +13 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "medusa-ui-common",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Shared storefront UI primitives.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,10 +22,11 @@
|
|
|
22
22
|
"lucide-react": "*",
|
|
23
23
|
"medusa-contact-logic-plugin": "^2.0.0",
|
|
24
24
|
"medusa-reviews-logic": "^2.0.0",
|
|
25
|
-
"medusa-storefront-analytics": "^1.
|
|
25
|
+
"medusa-storefront-analytics": "^1.0.0",
|
|
26
26
|
"medusa-storefront-data": "^2.0.0",
|
|
27
27
|
"medusa-storefront-hooks": "^1.0.0",
|
|
28
28
|
"medusa-wishlist-logic": "^2.0.0",
|
|
29
|
+
"medusa-storefront-theme-base": "^2.0.0",
|
|
29
30
|
"next": ">=14.0.0",
|
|
30
31
|
"react": "^18.0.0 || ^19.0.0",
|
|
31
32
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
@@ -96,6 +97,11 @@
|
|
|
96
97
|
}
|
|
97
98
|
},
|
|
98
99
|
"devDependencies": {
|
|
100
|
+
"next": "^15.0.0",
|
|
101
|
+
"medusa-storefront-analytics": "workspace:*",
|
|
102
|
+
"medusa-storefront-core": "workspace:*",
|
|
103
|
+
"medusa-storefront-data": "workspace:*",
|
|
104
|
+
"medusa-storefront-theme-base": "workspace:*",
|
|
99
105
|
"@headlessui/react": "^2.2.0",
|
|
100
106
|
"@medusajs/icons": "^2.0.0",
|
|
101
107
|
"@medusajs/ui": "^4.0.0",
|
|
@@ -9,13 +9,13 @@ export default function Breadcrumb({ product }: BreadcrumbProps) {
|
|
|
9
9
|
const primaryCategory = product.categories?.[0]
|
|
10
10
|
|
|
11
11
|
return (
|
|
12
|
-
<nav className="text-xs sm:text-sm text-
|
|
12
|
+
<nav className="text-xs sm:text-sm text-[var(--sf-color-text)] mb-3 sm:mb-4 overflow-x-auto font-medium">
|
|
13
13
|
<div className="flex items-center whitespace-nowrap">
|
|
14
|
-
<LocalizedClientLink href="/" className="hover:text-
|
|
14
|
+
<LocalizedClientLink href="/" className="hover:text-[var(--sf-color-text)] transition-colors">
|
|
15
15
|
Home
|
|
16
16
|
</LocalizedClientLink>
|
|
17
17
|
<span className="mx-2">/</span>
|
|
18
|
-
<LocalizedClientLink href="/store" className="hover:text-
|
|
18
|
+
<LocalizedClientLink href="/store" className="hover:text-[var(--sf-color-text)] transition-colors">
|
|
19
19
|
Shop
|
|
20
20
|
</LocalizedClientLink>
|
|
21
21
|
{primaryCategory && (
|
|
@@ -23,14 +23,14 @@ export default function Breadcrumb({ product }: BreadcrumbProps) {
|
|
|
23
23
|
<span className="mx-2">/</span>
|
|
24
24
|
<LocalizedClientLink
|
|
25
25
|
href={`/store?category=${primaryCategory.id}`}
|
|
26
|
-
className="hover:text-
|
|
26
|
+
className="hover:text-[var(--sf-color-text)] transition-colors"
|
|
27
27
|
>
|
|
28
28
|
{primaryCategory.name}
|
|
29
29
|
</LocalizedClientLink>
|
|
30
30
|
</>
|
|
31
31
|
)}
|
|
32
32
|
<span className="mx-2">/</span>
|
|
33
|
-
<span className="text-
|
|
33
|
+
<span className="text-[var(--sf-color-text)] truncate max-w-[150px] sm:max-w-[300px] md:max-w-[400px] inline-block align-bottom" title={product.title ?? ""}>
|
|
34
34
|
{product.title}
|
|
35
35
|
</span>
|
|
36
36
|
</div>
|
|
@@ -9,6 +9,8 @@ import LocalizedClientLink from "medusa-ui-common/common/components/localized-cl
|
|
|
9
9
|
import { useRouter, usePathname, useSearchParams } from "next/navigation"
|
|
10
10
|
import ProcessingOverlay from "medusa-ui-common/common/components/processing-overlay"
|
|
11
11
|
import { useSiteCompany } from "medusa-storefront-core"
|
|
12
|
+
import { useButtonClassName } from "../../../context/button-theme-context"
|
|
13
|
+
import { ThemedButton } from "../themed-button"
|
|
12
14
|
|
|
13
15
|
type CartTotalsProps = {
|
|
14
16
|
totals: {
|
|
@@ -27,6 +29,8 @@ type CartTotalsProps = {
|
|
|
27
29
|
|
|
28
30
|
const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutStep, customer }) => {
|
|
29
31
|
const company = useSiteCompany()
|
|
32
|
+
const primaryBtn = useButtonClassName("primary", { size: "lg", className: "w-full" })
|
|
33
|
+
const secondaryBtn = useButtonClassName("secondary", { size: "lg", className: "w-full" })
|
|
30
34
|
const router = useRouter()
|
|
31
35
|
const pathname = usePathname()
|
|
32
36
|
const searchParams = useSearchParams()
|
|
@@ -270,13 +274,13 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
270
274
|
}
|
|
271
275
|
|
|
272
276
|
return (
|
|
273
|
-
<div className="bg-[
|
|
277
|
+
<div className="bg-[var(--sf-color-background)] rounded-lg p-3 min-[550px]:p-4 sm:p-5 md:p-6 min-[1023px]:p-4 min-[1150px]:p-5 min-[1360px]:p-6 border shadow-sm w-full" style={{ borderColor: '#C0C0C0' }}>
|
|
274
278
|
<ProcessingOverlay isOpen={showOverlay} variant={processingVariant} />
|
|
275
|
-
<h3 className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg font-bold text-
|
|
279
|
+
<h3 className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg font-bold text-[var(--sf-color-text)] mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
|
|
276
280
|
PRICE SUMMARY ({itemCount} {itemCount === 1 ? "ITEM" : "ITEMS"})
|
|
277
281
|
</h3>
|
|
278
282
|
|
|
279
|
-
<div className="flex flex-col gap-y-2 min-[550px]:gap-y-2.5 sm:gap-y-3 min-[1023px]:gap-y-2 min-[1150px]:gap-y-3 min-[1360px]:gap-y-3 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-
|
|
283
|
+
<div className="flex flex-col gap-y-2 min-[550px]:gap-y-2.5 sm:gap-y-3 min-[1023px]:gap-y-2 min-[1150px]:gap-y-3 min-[1360px]:gap-y-3 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-[var(--sf-color-text)]">
|
|
280
284
|
<div className="flex items-center justify-between">
|
|
281
285
|
<span>Total MRP</span>
|
|
282
286
|
<span data-testid="cart-mrp" data-value={totalMRP}>
|
|
@@ -288,7 +292,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
288
292
|
<div className="flex items-center justify-between">
|
|
289
293
|
<span>Discount</span>
|
|
290
294
|
<span
|
|
291
|
-
className="text-
|
|
295
|
+
className="text-[var(--sf-color-text)]"
|
|
292
296
|
data-testid="cart-discount"
|
|
293
297
|
data-value={calculatedDiscount}
|
|
294
298
|
>
|
|
@@ -311,7 +315,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
311
315
|
<span>Shipping</span>
|
|
312
316
|
<span data-testid="cart-shipping" data-value={shippingAmount} className="flex items-center gap-x-2">
|
|
313
317
|
{isAutoSettingShipping && (
|
|
314
|
-
<span className="w-4 h-4 border-2 border-[
|
|
318
|
+
<span className="w-4 h-4 border-2 border-[var(--sf-btn-primary)] border-t-transparent rounded-full animate-spin"></span>
|
|
315
319
|
)}
|
|
316
320
|
{convertToLocale({ amount: shippingAmount, currency_code })}
|
|
317
321
|
</span>
|
|
@@ -320,7 +324,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
320
324
|
|
|
321
325
|
<div className="h-px w-full border-b border-gray-200 my-3 min-[550px]:my-4" />
|
|
322
326
|
|
|
323
|
-
<div className="flex items-center justify-between text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base font-bold text-
|
|
327
|
+
<div className="flex items-center justify-between text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base font-bold text-[var(--sf-color-text)] mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
|
|
324
328
|
<span>Total</span>
|
|
325
329
|
<span
|
|
326
330
|
className="text-sm min-[550px]:text-base sm:text-lg min-[1023px]:text-base min-[1150px]:text-lg min-[1360px]:text-lg"
|
|
@@ -344,22 +348,22 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
344
348
|
|
|
345
349
|
{/* No manual continue button needed as we are in one-step mode */}
|
|
346
350
|
{isOnCartPage && (
|
|
347
|
-
<
|
|
351
|
+
<ThemedButton
|
|
352
|
+
variant="primary"
|
|
353
|
+
size="lg"
|
|
354
|
+
fullWidth
|
|
348
355
|
onClick={handleProceedToCheckout}
|
|
349
356
|
disabled={isRedirecting}
|
|
357
|
+
isLoading={isRedirecting}
|
|
350
358
|
data-ga-event="proceed_to_checkout_click"
|
|
351
359
|
data-meta-event="InitiateCheckout"
|
|
352
360
|
data-meta-action="proceed_to_checkout"
|
|
353
361
|
data-ga-label={convertToLocale({ amount: finalTotal, currency_code })}
|
|
354
|
-
className="
|
|
355
|
-
style={{
|
|
356
|
-
backgroundColor: '#8B5AB1',
|
|
357
|
-
borderRadius: '30px'
|
|
358
|
-
}}
|
|
362
|
+
className="mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6"
|
|
359
363
|
data-testid="checkout-button"
|
|
360
364
|
>
|
|
361
365
|
{isRedirecting ? "Loading..." : "Proceed to Checkout"}
|
|
362
|
-
</
|
|
366
|
+
</ThemedButton>
|
|
363
367
|
)}
|
|
364
368
|
{!isOnCartPage && (
|
|
365
369
|
<div className="w-full">
|
|
@@ -376,6 +380,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
376
380
|
return (
|
|
377
381
|
<>
|
|
378
382
|
<button
|
|
383
|
+
type="button"
|
|
379
384
|
onClick={async () => {
|
|
380
385
|
trackPaymentMethodClick("cod")
|
|
381
386
|
setProcessingVariant("order")
|
|
@@ -404,18 +409,14 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
404
409
|
data-meta-event="AddPaymentInfo"
|
|
405
410
|
data-meta-action="select_payment_method"
|
|
406
411
|
data-meta-payment-type="cod"
|
|
407
|
-
className={clx(
|
|
408
|
-
"
|
|
409
|
-
|
|
410
|
-
"hover:bg-purple-50": !isPaymentDisabled,
|
|
411
|
-
"opacity-50 cursor-not-allowed": isPaymentDisabled
|
|
412
|
-
}
|
|
413
|
-
)}
|
|
414
|
-
style={{ borderRadius: '30px' }}
|
|
412
|
+
className={clx(secondaryBtn, {
|
|
413
|
+
"opacity-50 cursor-not-allowed": isPaymentDisabled,
|
|
414
|
+
})}
|
|
415
415
|
>
|
|
416
416
|
Cash on Delivery
|
|
417
417
|
</button>
|
|
418
418
|
<button
|
|
419
|
+
type="button"
|
|
419
420
|
onClick={async () => {
|
|
420
421
|
trackPaymentMethodClick("razorpay")
|
|
421
422
|
setProcessingVariant("payment")
|
|
@@ -469,7 +470,7 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
469
470
|
contact: paymentPhone || undefined,
|
|
470
471
|
},
|
|
471
472
|
theme: {
|
|
472
|
-
color: "
|
|
473
|
+
color: "var(--sf-btn-primary)"
|
|
473
474
|
},
|
|
474
475
|
modal: {
|
|
475
476
|
ondismiss: () => {
|
|
@@ -498,14 +499,9 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
498
499
|
data-meta-event="AddPaymentInfo"
|
|
499
500
|
data-meta-action="select_payment_method"
|
|
500
501
|
data-meta-payment-type="razorpay"
|
|
501
|
-
className={clx(
|
|
502
|
-
"
|
|
503
|
-
|
|
504
|
-
"hover:opacity-90": !isPaymentDisabled,
|
|
505
|
-
"opacity-50 cursor-not-allowed": isPaymentDisabled
|
|
506
|
-
}
|
|
507
|
-
)}
|
|
508
|
-
style={{ borderRadius: '30px' }}
|
|
502
|
+
className={clx(primaryBtn, {
|
|
503
|
+
"opacity-50 cursor-not-allowed": isPaymentDisabled,
|
|
504
|
+
})}
|
|
509
505
|
>
|
|
510
506
|
Pay with Razorpay
|
|
511
507
|
</button>
|
|
@@ -522,36 +518,35 @@ const CartTotals: React.FC<CartTotalsProps> = ({ totals, items = [], checkoutSte
|
|
|
522
518
|
)}
|
|
523
519
|
</div>
|
|
524
520
|
) : activeStep === "address" ? (
|
|
525
|
-
<
|
|
521
|
+
<ThemedButton
|
|
522
|
+
variant="primary"
|
|
523
|
+
size="lg"
|
|
524
|
+
fullWidth
|
|
526
525
|
onClick={() => {
|
|
527
526
|
window.dispatchEvent(new CustomEvent('trigger-checkout-step'))
|
|
528
527
|
}}
|
|
529
|
-
className="
|
|
530
|
-
style={{
|
|
531
|
-
backgroundColor: '#8B5AB1',
|
|
532
|
-
borderRadius: '30px'
|
|
533
|
-
}}
|
|
528
|
+
className="mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6"
|
|
534
529
|
data-testid="checkout-button-address"
|
|
535
530
|
>
|
|
536
531
|
Continue to delivery
|
|
537
|
-
</
|
|
532
|
+
</ThemedButton>
|
|
538
533
|
) : (
|
|
539
|
-
<
|
|
534
|
+
<ThemedButton
|
|
535
|
+
variant="primary"
|
|
536
|
+
size="lg"
|
|
537
|
+
fullWidth
|
|
540
538
|
onClick={handleProceedToCheckout}
|
|
541
539
|
disabled={isRedirecting}
|
|
540
|
+
isLoading={isRedirecting}
|
|
542
541
|
data-ga-event="proceed_to_checkout_click"
|
|
543
542
|
data-meta-event="InitiateCheckout"
|
|
544
543
|
data-meta-action="proceed_to_checkout"
|
|
545
544
|
data-ga-label={convertToLocale({ amount: finalTotal, currency_code })}
|
|
546
|
-
className="
|
|
547
|
-
style={{
|
|
548
|
-
backgroundColor: '#8B5AB1',
|
|
549
|
-
borderRadius: '30px'
|
|
550
|
-
}}
|
|
545
|
+
className="mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6"
|
|
551
546
|
data-testid="checkout-button"
|
|
552
547
|
>
|
|
553
548
|
{isRedirecting ? "Loading..." : "Proceed to Checkout"}
|
|
554
|
-
</
|
|
549
|
+
</ThemedButton>
|
|
555
550
|
)}
|
|
556
551
|
</div>
|
|
557
552
|
)}
|
|
@@ -56,8 +56,8 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
|
|
|
56
56
|
data-ga-label={dataGaLabel}
|
|
57
57
|
className="w-5 h-5 rounded-md border-2 flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-offset-2"
|
|
58
58
|
style={checked ? {
|
|
59
|
-
backgroundColor: '
|
|
60
|
-
borderColor: '
|
|
59
|
+
backgroundColor: 'var(--sf-btn-primary)',
|
|
60
|
+
borderColor: 'var(--sf-btn-primary)'
|
|
61
61
|
} : {
|
|
62
62
|
backgroundColor: 'white',
|
|
63
63
|
borderColor: '#C0C0C0'
|
|
@@ -100,7 +100,7 @@ const DeleteButton = ({
|
|
|
100
100
|
{/* Body Content */}
|
|
101
101
|
<div className="p-6 flex gap-6 pr-12">
|
|
102
102
|
{/* Product Thumbnail */}
|
|
103
|
-
<div className="w-24 h-24 sm:w-28 sm:h-28 flex-shrink-0 bg-
|
|
103
|
+
<div className="w-24 h-24 sm:w-28 sm:h-28 flex-shrink-0 bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,var(--sf-color-surface))] rounded-sm overflow-hidden border border-gray-100 shadow-sm">
|
|
104
104
|
{thumbnail ? (
|
|
105
105
|
<Image
|
|
106
106
|
src={thumbnail}
|
|
@@ -110,14 +110,14 @@ const DeleteButton = ({
|
|
|
110
110
|
className="w-full h-full object-cover"
|
|
111
111
|
/>
|
|
112
112
|
) : (
|
|
113
|
-
<div className="w-full h-full flex items-center justify-center text-
|
|
113
|
+
<div className="w-full h-full flex items-center justify-center text-[var(--sf-color-text-muted)] bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,var(--sf-color-surface))] uppercase text-[10px] font-bold">No Image</div>
|
|
114
114
|
)}
|
|
115
115
|
</div>
|
|
116
116
|
|
|
117
117
|
{/* Title and Description */}
|
|
118
118
|
<div className="flex flex-col justify-center">
|
|
119
|
-
<h3 className="text-[17px] font-bold text-
|
|
120
|
-
<p className="text-[15px] text-
|
|
119
|
+
<h3 className="text-[17px] font-bold text-[var(--sf-color-text)] mb-1.5 leading-tight">Move from Bag</h3>
|
|
120
|
+
<p className="text-[15px] text-[var(--sf-color-text-muted)] leading-snug font-medium max-w-[280px]">
|
|
121
121
|
Are you sure you want to move this item from bag?
|
|
122
122
|
</p>
|
|
123
123
|
|
|
@@ -132,7 +132,7 @@ const DeleteButton = ({
|
|
|
132
132
|
<button
|
|
133
133
|
onClick={handleDelete}
|
|
134
134
|
disabled={isDeleting}
|
|
135
|
-
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold text-
|
|
135
|
+
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold text-[var(--sf-color-text-muted)] tracking-wider hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)] transition-colors uppercase"
|
|
136
136
|
>
|
|
137
137
|
{isDeleting ? <Spinner className="animate-spin" /> : "REMOVE"}
|
|
138
138
|
</button>
|
|
@@ -143,7 +143,7 @@ const DeleteButton = ({
|
|
|
143
143
|
<button
|
|
144
144
|
onClick={handleMoveToWishlist}
|
|
145
145
|
disabled={wishlistLoading}
|
|
146
|
-
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold tracking-wider hover:bg-
|
|
146
|
+
className="flex-1 h-full flex items-center justify-center text-[13px] font-bold tracking-wider hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)] transition-colors uppercase"
|
|
147
147
|
style={{ color: '#D25C78' }} // Pinkish color from reference
|
|
148
148
|
>
|
|
149
149
|
{wishlistLoading ? <Spinner className="animate-spin" /> : "MOVE TO WISHLIST"}
|
|
@@ -71,7 +71,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
71
71
|
style={{ width: '480px', maxWidth: '100%' }}
|
|
72
72
|
>
|
|
73
73
|
{/* COUPONS Heading */}
|
|
74
|
-
<Heading className="text-lg font-bold text-
|
|
74
|
+
<Heading className="text-lg font-bold text-[var(--sf-color-text)] mb-4 uppercase">
|
|
75
75
|
COUPONS
|
|
76
76
|
</Heading>
|
|
77
77
|
|
|
@@ -86,15 +86,15 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
86
86
|
<div className="flex items-center gap-3">
|
|
87
87
|
{/* Percentage Icon - White circle with black % symbol */}
|
|
88
88
|
<div className="w-10 h-10 rounded-full bg-white flex items-center justify-center flex-shrink-0 shadow-sm">
|
|
89
|
-
<span className="text-
|
|
89
|
+
<span className="text-[var(--sf-color-text)] font-bold text-xl">%</span>
|
|
90
90
|
</div>
|
|
91
91
|
|
|
92
92
|
{/* Text Content */}
|
|
93
93
|
<div className="flex flex-col items-start">
|
|
94
|
-
<span className="font-bold text-base text-
|
|
94
|
+
<span className="font-bold text-base text-[var(--sf-color-text)]">
|
|
95
95
|
Apply Coupons
|
|
96
96
|
</span>
|
|
97
|
-
<span className="text-sm text-
|
|
97
|
+
<span className="text-sm text-[var(--sf-color-text-muted)]">
|
|
98
98
|
View all offers
|
|
99
99
|
</span>
|
|
100
100
|
</div>
|
|
@@ -102,7 +102,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
102
102
|
|
|
103
103
|
{/* Chevron Icon */}
|
|
104
104
|
<svg
|
|
105
|
-
className="w-5 h-5 text-
|
|
105
|
+
className="w-5 h-5 text-[var(--sf-color-text)]"
|
|
106
106
|
fill="none"
|
|
107
107
|
stroke="currentColor"
|
|
108
108
|
viewBox="0 0 24 24"
|
|
@@ -131,7 +131,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
131
131
|
/>
|
|
132
132
|
<SubmitButton
|
|
133
133
|
variant="secondary"
|
|
134
|
-
className="!bg-[
|
|
134
|
+
className="!bg-[var(--sf-btn-primary)] hover:!bg-[var(--sf-btn-primary-hover)] text-white border-0"
|
|
135
135
|
data-testid="discount-apply-button"
|
|
136
136
|
>
|
|
137
137
|
Apply
|
|
@@ -157,7 +157,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
157
157
|
{/* Applied Promotions - Clean Card Redesign */}
|
|
158
158
|
{promotions.length > 0 && (
|
|
159
159
|
<div className="w-full flex flex-col mt-6 pt-4 border-t border-gray-100">
|
|
160
|
-
<Heading className="text-sm font-bold mb-3 text-
|
|
160
|
+
<Heading className="text-sm font-bold mb-3 text-[var(--sf-color-text)] uppercase tracking-tight">
|
|
161
161
|
Promotion(s) applied:
|
|
162
162
|
</Heading>
|
|
163
163
|
|
|
@@ -166,12 +166,12 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
166
166
|
return (
|
|
167
167
|
<div
|
|
168
168
|
key={promotion.id}
|
|
169
|
-
className="flex items-center justify-between w-full p-3 rounded-xl border border-purple-100 bg-
|
|
169
|
+
className="flex items-center justify-between w-full p-3 rounded-xl border border-purple-100 bg-[var(--sf-color-primary)]/10 transition-all hover:bg-[var(--sf-color-primary)]/10 group"
|
|
170
170
|
data-testid="discount-row"
|
|
171
171
|
>
|
|
172
172
|
<div className="flex items-center gap-x-3 overflow-hidden">
|
|
173
173
|
{/* Tag Icon */}
|
|
174
|
-
<div className="flex-shrink-0 text-[
|
|
174
|
+
<div className="flex-shrink-0 text-[var(--sf-btn-primary)]">
|
|
175
175
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
176
176
|
<path d="M20.59 13.41l-7.17 7.17a2 2 0 0 1-2.83 0L2 12V2h10l8.59 8.59a2 2 0 0 1 0 2.82z"></path>
|
|
177
177
|
<line x1="7" y1="7" x2="7.01" y2="7"></line>
|
|
@@ -179,7 +179,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
179
179
|
</div>
|
|
180
180
|
|
|
181
181
|
<div className="flex flex-col overflow-hidden">
|
|
182
|
-
<span className="font-bold truncate text-sm uppercase tracking-wider" style={{ color: '
|
|
182
|
+
<span className="font-bold truncate text-sm uppercase tracking-wider" style={{ color: 'var(--sf-btn-primary)' }} data-testid="discount-code">
|
|
183
183
|
{promotion.code}
|
|
184
184
|
</span>
|
|
185
185
|
{promotion.application_method?.value !== undefined && (
|
|
@@ -197,7 +197,7 @@ const DiscountCode: React.FC<DiscountCodeProps> = ({ cart }) => {
|
|
|
197
197
|
|
|
198
198
|
{!promotion.is_automatic && (
|
|
199
199
|
<button
|
|
200
|
-
className="p-2 text-
|
|
200
|
+
className="p-2 text-[var(--sf-color-text-muted)] hover:text-red-500 hover:bg-red-50 rounded-full transition-all"
|
|
201
201
|
onClick={() => {
|
|
202
202
|
if (!promotion.code) return
|
|
203
203
|
removePromotionCode(promotion.code)
|
|
@@ -66,12 +66,12 @@ const FilterCheckboxGroup = ({
|
|
|
66
66
|
>
|
|
67
67
|
<Heading
|
|
68
68
|
level="h3"
|
|
69
|
-
className="text-sm font-bold text-
|
|
69
|
+
className="text-sm font-bold text-[var(--sf-color-text)] uppercase tracking-wider group-hover:text-[var(--sf-btn-primary)] transition-colors"
|
|
70
70
|
>
|
|
71
71
|
{title}
|
|
72
72
|
</Heading>
|
|
73
73
|
<span
|
|
74
|
-
className={`transform transition-transform duration-300 text-
|
|
74
|
+
className={`transform transition-transform duration-300 text-[var(--sf-color-text-muted)] group-hover:text-[var(--sf-btn-primary)] ${isOpen ? "" : "-rotate-180"}`}
|
|
75
75
|
>
|
|
76
76
|
<svg
|
|
77
77
|
width="18"
|
|
@@ -108,7 +108,7 @@ const FilterCheckboxGroup = ({
|
|
|
108
108
|
{hasMore && isOpen && (
|
|
109
109
|
<button
|
|
110
110
|
onClick={() => setShowAll(!showAll)}
|
|
111
|
-
className="text-xs font-bold text-[
|
|
111
|
+
className="text-xs font-bold text-[var(--sf-btn-primary)] hover:underline mt-2 flex items-center gap-1 uppercase tracking-tight"
|
|
112
112
|
>
|
|
113
113
|
{showAll ? "View Less" : `View More (${items.length - initialItemsCount})`}
|
|
114
114
|
<svg
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import { Label } from "@medusajs/ui"
|
|
4
|
+
import { clx } from "@medusajs/ui"
|
|
2
5
|
import React, { useEffect, useImperativeHandle, useState } from "react"
|
|
3
6
|
|
|
4
7
|
import Eye from "medusa-ui-common/common/icons/eye"
|
|
5
8
|
import EyeOff from "medusa-ui-common/common/icons/eye-off"
|
|
9
|
+
import { useFormThemeClasses } from "../../../context/storefront-theme-classes-context"
|
|
10
|
+
import type { FormThemeClassNames } from "medusa-storefront-theme-base/form-theme"
|
|
6
11
|
|
|
7
12
|
type InputProps = Omit<
|
|
8
13
|
Omit<React.InputHTMLAttributes<HTMLInputElement>, "size">,
|
|
@@ -13,13 +18,17 @@ type InputProps = Omit<
|
|
|
13
18
|
touched?: Record<string, unknown>
|
|
14
19
|
name: string
|
|
15
20
|
topLabel?: string
|
|
21
|
+
/** Override site form theme per field */
|
|
22
|
+
classNames?: Partial<FormThemeClassNames>
|
|
16
23
|
}
|
|
17
24
|
|
|
18
25
|
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
19
|
-
({ type, name, label, touched, required, topLabel, ...props }, ref) => {
|
|
26
|
+
({ type, name, label, touched, required, topLabel, classNames, className, ...props }, ref) => {
|
|
20
27
|
const inputRef = React.useRef<HTMLInputElement>(null)
|
|
21
28
|
const [showPassword, setShowPassword] = useState(false)
|
|
22
29
|
const [inputType, setInputType] = useState(type)
|
|
30
|
+
const formTheme = useFormThemeClasses(classNames)
|
|
31
|
+
const cn = formTheme
|
|
23
32
|
|
|
24
33
|
useEffect(() => {
|
|
25
34
|
if (type === "password" && showPassword) {
|
|
@@ -36,7 +45,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
36
45
|
return (
|
|
37
46
|
<div className="flex flex-col w-full">
|
|
38
47
|
{topLabel && (
|
|
39
|
-
<Label className=
|
|
48
|
+
<Label className={cn.topLabel}>{topLabel}</Label>
|
|
40
49
|
)}
|
|
41
50
|
<div className="flex relative z-0 w-full txt-compact-medium overflow-visible">
|
|
42
51
|
<input
|
|
@@ -44,8 +53,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
44
53
|
name={name}
|
|
45
54
|
placeholder=" "
|
|
46
55
|
required={required}
|
|
47
|
-
className=
|
|
48
|
-
style={{ borderColor: '#C0C0C0' }}
|
|
56
|
+
className={clx(cn.field, className)}
|
|
49
57
|
{...props}
|
|
50
58
|
ref={inputRef}
|
|
51
59
|
/>
|
|
@@ -53,7 +61,11 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
53
61
|
<label
|
|
54
62
|
htmlFor={name}
|
|
55
63
|
onClick={() => inputRef.current?.focus()}
|
|
56
|
-
className=
|
|
64
|
+
className={clx(
|
|
65
|
+
"flex items-center justify-center absolute left-4 px-1 transition-all duration-300 top-1/2 -translate-y-1/2 origin-0 pointer-events-none",
|
|
66
|
+
cn.label,
|
|
67
|
+
cn.labelFloating,
|
|
68
|
+
)}
|
|
57
69
|
>
|
|
58
70
|
{label}
|
|
59
71
|
{required && <span className="text-rose-500">*</span>}
|
|
@@ -63,7 +75,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
|
63
75
|
<button
|
|
64
76
|
type="button"
|
|
65
77
|
onClick={() => setShowPassword(!showPassword)}
|
|
66
|
-
className="text-
|
|
78
|
+
className="text-[var(--sf-input-label)] px-4 focus:outline-none transition-all duration-150 outline-none hover:text-[var(--sf-input-text)] absolute right-0 top-3"
|
|
67
79
|
>
|
|
68
80
|
{showPassword ? <Eye /> : <EyeOff />}
|
|
69
81
|
</button>
|
|
@@ -34,38 +34,38 @@ export default function LoginPopup({ isOpen, onClose, countryCode = "us", messag
|
|
|
34
34
|
return (
|
|
35
35
|
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-white/10 backdrop-blur-md p-4 animate-in fade-in duration-200">
|
|
36
36
|
<div
|
|
37
|
-
className="bg-[
|
|
37
|
+
className="bg-[var(--sf-color-background)] rounded-2xl shadow-2xl p-8 max-w-sm w-full mx-auto border border-gray-100 transform transition-all scale-100 animate-in zoom-in-95 duration-200"
|
|
38
38
|
onClick={(e) => e.stopPropagation()}
|
|
39
39
|
>
|
|
40
40
|
<div className="text-center">
|
|
41
41
|
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-purple-100 mb-4">
|
|
42
|
-
<svg className="h-6 w-6 text-[
|
|
42
|
+
<svg className="h-6 w-6 text-[var(--sf-btn-primary)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
43
43
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z" />
|
|
44
44
|
</svg>
|
|
45
45
|
</div>
|
|
46
|
-
<h2 className="text-xl font-bold text-
|
|
46
|
+
<h2 className="text-xl font-bold text-[var(--sf-color-text)] mb-2">
|
|
47
47
|
Login Required
|
|
48
48
|
</h2>
|
|
49
|
-
<p className="text-
|
|
49
|
+
<p className="text-[var(--sf-color-text-muted)] mb-8 text-sm leading-relaxed">
|
|
50
50
|
{message}
|
|
51
51
|
</p>
|
|
52
52
|
|
|
53
53
|
<div className="grid grid-cols-2 gap-3">
|
|
54
54
|
<button
|
|
55
55
|
onClick={onClose}
|
|
56
|
-
className="px-4 py-2.5 border border-gray-200 rounded-xl font-semibold text-
|
|
56
|
+
className="px-4 py-2.5 border border-gray-200 rounded-xl font-semibold text-[var(--sf-color-text-muted)] hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)] hover:border-gray-300 active:scale-95 transition-all duration-200"
|
|
57
57
|
>
|
|
58
58
|
Cancel
|
|
59
59
|
</button>
|
|
60
60
|
<button
|
|
61
61
|
onClick={handleLoginClick}
|
|
62
62
|
className="px-4 py-2.5 rounded-xl font-semibold text-white transition-all duration-200 hover:shadow-lg hover:shadow-purple-100 active:scale-95"
|
|
63
|
-
style={{ backgroundColor: '
|
|
63
|
+
style={{ backgroundColor: 'var(--sf-btn-primary)' }}
|
|
64
64
|
onMouseEnter={(e) => {
|
|
65
|
-
e.currentTarget.style.backgroundColor = '
|
|
65
|
+
e.currentTarget.style.backgroundColor = 'var(--sf-btn-primary-hover)'
|
|
66
66
|
}}
|
|
67
67
|
onMouseLeave={(e) => {
|
|
68
|
-
e.currentTarget.style.backgroundColor = '
|
|
68
|
+
e.currentTarget.style.backgroundColor = 'var(--sf-btn-primary)'
|
|
69
69
|
}}
|
|
70
70
|
>
|
|
71
71
|
Login
|
|
@@ -89,9 +89,9 @@ const Title: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
|
89
89
|
|
|
90
90
|
return (
|
|
91
91
|
<Dialog.Title className="flex items-center justify-between px-6 small:px-8 py-5 border-b border-gray-100 flex-shrink-0">
|
|
92
|
-
<div className="text-xl small:text-2xl font-bold text-
|
|
92
|
+
<div className="text-xl small:text-2xl font-bold text-[var(--sf-color-text)]">{children}</div>
|
|
93
93
|
<div>
|
|
94
|
-
<button onClick={close} data-testid="close-modal-button" className="p-2 hover:bg-
|
|
94
|
+
<button onClick={close} data-testid="close-modal-button" className="p-2 hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_10%,transparent)] rounded-full transition-colors text-[var(--sf-color-text-muted)]">
|
|
95
95
|
<X size={24} />
|
|
96
96
|
</button>
|
|
97
97
|
</div>
|
|
@@ -42,7 +42,7 @@ const NativeSelect = forwardRef<HTMLSelectElement, NativeSelectProps>(
|
|
|
42
42
|
onFocus={() => innerRef.current?.focus()}
|
|
43
43
|
onBlur={() => innerRef.current?.blur()}
|
|
44
44
|
className={clx(
|
|
45
|
-
"relative flex items-center text-base-regular border bg-white rounded-lg hover:bg-
|
|
45
|
+
"relative flex items-center text-base-regular border bg-white rounded-lg hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,transparent)]",
|
|
46
46
|
className,
|
|
47
47
|
{
|
|
48
48
|
"text-ui-fg-muted": isPlaceholder,
|
|
@@ -60,19 +60,19 @@ const ProcessingOverlay = ({
|
|
|
60
60
|
/>
|
|
61
61
|
</div>
|
|
62
62
|
|
|
63
|
-
<h3 className="text-xl font-bold text-
|
|
63
|
+
<h3 className="text-xl font-bold text-[var(--sf-color-text)] mb-1 text-center">{displayTitle}</h3>
|
|
64
64
|
|
|
65
65
|
<div className="h-6 flex items-center justify-center mb-2 text-center px-4">
|
|
66
|
-
<p key={messageIndex} className="text-[
|
|
66
|
+
<p key={messageIndex} className="text-[var(--sf-btn-primary)] font-semibold text-sm animate-in slide-in-from-bottom-2 fade-in duration-500">
|
|
67
67
|
{messages[messageIndex]}
|
|
68
68
|
</p>
|
|
69
69
|
</div>
|
|
70
70
|
|
|
71
71
|
<div className="w-full bg-gray-100 h-1.5 rounded-full overflow-hidden mb-4">
|
|
72
|
-
<div className="h-full bg-[
|
|
72
|
+
<div className="h-full bg-[var(--sf-btn-primary)] rounded-full animate-progress transition-all duration-500 ease-out"></div>
|
|
73
73
|
</div>
|
|
74
74
|
|
|
75
|
-
<p className="text-xs text-
|
|
75
|
+
<p className="text-xs text-[var(--sf-color-text-muted)] text-center italic leading-relaxed">
|
|
76
76
|
{subtitle}
|
|
77
77
|
</p>
|
|
78
78
|
</div>
|
|
@@ -35,16 +35,16 @@ const SidePanel: React.FC<SidePanelProps> = ({ isOpen, onClose, title, children
|
|
|
35
35
|
{/* Panel */}
|
|
36
36
|
<div
|
|
37
37
|
className={clx(
|
|
38
|
-
"fixed top-0 right-0 h-full w-[90%] min-[400px]:w-[350px] sm:w-[400px] md:w-[450px] lg:w-[480px] bg-[
|
|
38
|
+
"fixed top-0 right-0 h-full w-[90%] min-[400px]:w-[350px] sm:w-[400px] md:w-[450px] lg:w-[480px] bg-[var(--sf-color-background)] shadow-2xl transition-transform duration-300 ease-in-out z-[1000] flex flex-col",
|
|
39
39
|
isOpen ? "translate-x-0" : "translate-x-full"
|
|
40
40
|
)}
|
|
41
41
|
>
|
|
42
42
|
{/* Header */}
|
|
43
43
|
<div className="flex items-center justify-between p-6 border-b border-gray-100">
|
|
44
|
-
<h2 className="text-xl font-bold text-
|
|
44
|
+
<h2 className="text-xl font-bold text-[var(--sf-color-text)]">{title}</h2>
|
|
45
45
|
<button
|
|
46
46
|
onClick={onClose}
|
|
47
|
-
className="p-2 hover:bg-
|
|
47
|
+
className="p-2 hover:bg-[color-mix(in_srgb,var(--sf-color-text-muted)_10%,transparent)] rounded-full transition-colors"
|
|
48
48
|
>
|
|
49
49
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
|
|
50
50
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { useFormStatus } from "react-dom";
|
|
5
|
+
import { ThemedButton, type ThemedButtonProps } from "../themed-button";
|
|
6
6
|
|
|
7
7
|
export function SubmitButton({
|
|
8
8
|
children,
|
|
9
9
|
variant = "primary",
|
|
10
10
|
className,
|
|
11
|
+
size = "lg",
|
|
11
12
|
"data-testid": dataTestId,
|
|
12
13
|
}: {
|
|
13
|
-
children: React.ReactNode
|
|
14
|
-
variant?: "
|
|
15
|
-
className?: string
|
|
16
|
-
"
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
variant?: ThemedButtonProps["variant"];
|
|
16
|
+
className?: string;
|
|
17
|
+
size?: ThemedButtonProps["size"];
|
|
18
|
+
"data-testid"?: string;
|
|
17
19
|
}) {
|
|
18
|
-
const { pending } = useFormStatus()
|
|
20
|
+
const { pending } = useFormStatus();
|
|
19
21
|
|
|
20
22
|
return (
|
|
21
|
-
<
|
|
22
|
-
size=
|
|
23
|
+
<ThemedButton
|
|
24
|
+
size={size}
|
|
23
25
|
className={className}
|
|
24
26
|
type="submit"
|
|
25
27
|
isLoading={pending}
|
|
26
|
-
variant={variant
|
|
28
|
+
variant={variant}
|
|
27
29
|
data-testid={dataTestId}
|
|
30
|
+
fullWidth
|
|
28
31
|
>
|
|
29
32
|
{children}
|
|
30
|
-
</
|
|
31
|
-
)
|
|
33
|
+
</ThemedButton>
|
|
34
|
+
);
|
|
32
35
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { clx } from "@medusajs/ui";
|
|
4
|
+
import React, { forwardRef } from "react";
|
|
5
|
+
import {
|
|
6
|
+
useButtonClassName,
|
|
7
|
+
type ButtonSize,
|
|
8
|
+
type ButtonTheme,
|
|
9
|
+
type ButtonVariant,
|
|
10
|
+
} from "../../../context/button-theme-context";
|
|
11
|
+
import Spinner from "../../icons/spinner";
|
|
12
|
+
|
|
13
|
+
export type ThemedButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
14
|
+
variant?: ButtonVariant;
|
|
15
|
+
size?: ButtonSize;
|
|
16
|
+
/** Override site theme for this button only (e.g. import `minimalButtonTheme`). */
|
|
17
|
+
theme?: ButtonTheme;
|
|
18
|
+
isLoading?: boolean;
|
|
19
|
+
fullWidth?: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const ThemedButton = forwardRef<HTMLButtonElement, ThemedButtonProps>(
|
|
23
|
+
function ThemedButton(
|
|
24
|
+
{
|
|
25
|
+
variant = "primary",
|
|
26
|
+
size = "md",
|
|
27
|
+
theme,
|
|
28
|
+
className,
|
|
29
|
+
children,
|
|
30
|
+
disabled,
|
|
31
|
+
isLoading,
|
|
32
|
+
fullWidth,
|
|
33
|
+
type = "button",
|
|
34
|
+
...props
|
|
35
|
+
},
|
|
36
|
+
ref,
|
|
37
|
+
) {
|
|
38
|
+
const resolvedClassName = useButtonClassName(variant, {
|
|
39
|
+
size,
|
|
40
|
+
theme,
|
|
41
|
+
className: clx(fullWidth && "w-full", className),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
return (
|
|
45
|
+
<button
|
|
46
|
+
ref={ref}
|
|
47
|
+
type={type}
|
|
48
|
+
disabled={disabled || isLoading}
|
|
49
|
+
className={resolvedClassName}
|
|
50
|
+
{...props}
|
|
51
|
+
>
|
|
52
|
+
{isLoading ? (
|
|
53
|
+
<span className="inline-flex items-center justify-center gap-2">
|
|
54
|
+
<Spinner />
|
|
55
|
+
{children}
|
|
56
|
+
</span>
|
|
57
|
+
) : (
|
|
58
|
+
children
|
|
59
|
+
)}
|
|
60
|
+
</button>
|
|
61
|
+
);
|
|
62
|
+
},
|
|
63
|
+
);
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useCallback,
|
|
6
|
+
useContext,
|
|
7
|
+
useEffect,
|
|
8
|
+
useMemo,
|
|
9
|
+
type ReactNode,
|
|
10
|
+
} from "react";
|
|
11
|
+
import { clx } from "@medusajs/ui";
|
|
12
|
+
import {
|
|
13
|
+
defaultButtonTheme,
|
|
14
|
+
type ButtonSize,
|
|
15
|
+
type ButtonTheme,
|
|
16
|
+
type ButtonVariant,
|
|
17
|
+
} from "medusa-storefront-theme-base/button-theme";
|
|
18
|
+
import { buildButtonCssVars } from "../util/button-css-vars";
|
|
19
|
+
|
|
20
|
+
const fallbackButtonCssVars = buildButtonCssVars({
|
|
21
|
+
primary: "#8B5AB1",
|
|
22
|
+
primaryHover: "#7a4a9a",
|
|
23
|
+
secondary: "#D4AF37",
|
|
24
|
+
secondaryHover: "#B8942F",
|
|
25
|
+
black: "#111111",
|
|
26
|
+
blackHover: "#000000",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
export type { ButtonSize, ButtonTheme, ButtonVariant };
|
|
30
|
+
|
|
31
|
+
type ButtonThemeContextValue = {
|
|
32
|
+
theme: ButtonTheme;
|
|
33
|
+
classNameFor: (
|
|
34
|
+
variant: ButtonVariant,
|
|
35
|
+
options?: { size?: ButtonSize; className?: string; theme?: ButtonTheme },
|
|
36
|
+
) => string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const ButtonThemeContext = createContext<ButtonThemeContextValue | undefined>(
|
|
40
|
+
undefined,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
export type ButtonThemeProviderProps = {
|
|
44
|
+
children: ReactNode;
|
|
45
|
+
/** Site-wide button styles — import a library preset and extend in compositions/theme.ts */
|
|
46
|
+
theme?: ButtonTheme;
|
|
47
|
+
/** Injected on documentElement so all sections (banner, login, checkout) share colors */
|
|
48
|
+
cssVars?: Record<string, string>;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
function applyButtonCssVars(cssVars: Record<string, string>) {
|
|
52
|
+
const root = document.documentElement;
|
|
53
|
+
Object.entries(cssVars).forEach(([key, value]) => {
|
|
54
|
+
root.style.setProperty(key, value);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function clearButtonCssVars(cssVars: Record<string, string>) {
|
|
59
|
+
const root = document.documentElement;
|
|
60
|
+
Object.keys(cssVars).forEach((key) => {
|
|
61
|
+
root.style.removeProperty(key);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function ButtonThemeProvider({
|
|
66
|
+
children,
|
|
67
|
+
theme = defaultButtonTheme,
|
|
68
|
+
cssVars: cssVarsProp,
|
|
69
|
+
}: ButtonThemeProviderProps) {
|
|
70
|
+
const cssVars = cssVarsProp ?? fallbackButtonCssVars;
|
|
71
|
+
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
if (typeof document === "undefined") return;
|
|
74
|
+
applyButtonCssVars(cssVars);
|
|
75
|
+
return () => clearButtonCssVars(cssVars);
|
|
76
|
+
}, [cssVars]);
|
|
77
|
+
|
|
78
|
+
const classNameFor = useCallback(
|
|
79
|
+
(
|
|
80
|
+
variant: ButtonVariant,
|
|
81
|
+
options?: { size?: ButtonSize; className?: string; theme?: ButtonTheme },
|
|
82
|
+
) => {
|
|
83
|
+
const active = options?.theme ?? theme;
|
|
84
|
+
const size = options?.size ?? "md";
|
|
85
|
+
return clx(
|
|
86
|
+
active.base,
|
|
87
|
+
active.sizes?.[size],
|
|
88
|
+
active.variants[variant],
|
|
89
|
+
options?.className,
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
[theme],
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const value = useMemo(
|
|
96
|
+
() => ({ theme, classNameFor }),
|
|
97
|
+
[theme, classNameFor],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const cssVarStyle =
|
|
101
|
+
cssVars &&
|
|
102
|
+
`:root{${Object.entries(cssVars)
|
|
103
|
+
.map(([k, v]) => `${k}:${v}`)
|
|
104
|
+
.join(";")}}`;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<ButtonThemeContext.Provider value={value}>
|
|
108
|
+
{cssVarStyle ? (
|
|
109
|
+
<style dangerouslySetInnerHTML={{ __html: cssVarStyle }} />
|
|
110
|
+
) : null}
|
|
111
|
+
{children}
|
|
112
|
+
</ButtonThemeContext.Provider>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function useButtonTheme(): ButtonThemeContextValue {
|
|
117
|
+
const ctx = useContext(ButtonThemeContext);
|
|
118
|
+
if (!ctx) {
|
|
119
|
+
throw new Error("useButtonTheme must be used within a ButtonThemeProvider");
|
|
120
|
+
}
|
|
121
|
+
return ctx;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Resolve Tailwind classes for a variant (optionally override theme per call). */
|
|
125
|
+
export function useButtonClassName(
|
|
126
|
+
variant: ButtonVariant = "primary",
|
|
127
|
+
options?: { size?: ButtonSize; className?: string; theme?: ButtonTheme },
|
|
128
|
+
): string {
|
|
129
|
+
const { classNameFor } = useButtonTheme();
|
|
130
|
+
return classNameFor(variant, options);
|
|
131
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
type ReactNode,
|
|
9
|
+
} from "react";
|
|
10
|
+
import {
|
|
11
|
+
buildStorefrontRootCss,
|
|
12
|
+
buildStorefrontRootCssVars,
|
|
13
|
+
buildStorefrontThemeCssVars,
|
|
14
|
+
defaultStorefrontTheme,
|
|
15
|
+
type StorefrontTheme,
|
|
16
|
+
} from "../util/storefront-theme";
|
|
17
|
+
|
|
18
|
+
export type SiteThemeContextValue = {
|
|
19
|
+
theme: StorefrontTheme;
|
|
20
|
+
cssVars: Record<string, string>;
|
|
21
|
+
buttonCssVars: Record<string, string>;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const SiteThemeContext = createContext<SiteThemeContextValue | undefined>(
|
|
25
|
+
undefined,
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function applyCssVars(cssVars: Record<string, string>) {
|
|
29
|
+
const root = document.documentElement;
|
|
30
|
+
Object.entries(cssVars).forEach(([key, value]) => {
|
|
31
|
+
root.style.setProperty(key, value);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function clearCssVars(cssVars: Record<string, string>) {
|
|
36
|
+
const root = document.documentElement;
|
|
37
|
+
Object.keys(cssVars).forEach((key) => {
|
|
38
|
+
root.style.removeProperty(key);
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SiteThemeProviderProps = {
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
/** Site theme from compositions/theme.ts */
|
|
45
|
+
theme?: StorefrontTheme;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function SiteThemeProvider({
|
|
49
|
+
children,
|
|
50
|
+
theme = defaultStorefrontTheme,
|
|
51
|
+
}: SiteThemeProviderProps) {
|
|
52
|
+
const themeCssVars = useMemo(() => buildStorefrontThemeCssVars(theme), [theme]);
|
|
53
|
+
const allCssVars = useMemo(() => buildStorefrontRootCssVars(theme), [theme]);
|
|
54
|
+
const buttonCssVars = useMemo(
|
|
55
|
+
() =>
|
|
56
|
+
Object.fromEntries(
|
|
57
|
+
Object.entries(allCssVars).filter(([key]) => key.startsWith("--sf-btn-")),
|
|
58
|
+
) as Record<string, string>,
|
|
59
|
+
[allCssVars],
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (typeof document === "undefined") return;
|
|
64
|
+
applyCssVars(allCssVars);
|
|
65
|
+
return () => clearCssVars(allCssVars);
|
|
66
|
+
}, [allCssVars]);
|
|
67
|
+
|
|
68
|
+
const cssVarStyle = buildStorefrontRootCss(theme);
|
|
69
|
+
|
|
70
|
+
const value = useMemo(
|
|
71
|
+
() => ({ theme, cssVars: themeCssVars, buttonCssVars }),
|
|
72
|
+
[theme, themeCssVars, buttonCssVars],
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<SiteThemeContext.Provider value={value}>
|
|
77
|
+
<style dangerouslySetInnerHTML={{ __html: cssVarStyle }} />
|
|
78
|
+
{children}
|
|
79
|
+
</SiteThemeContext.Provider>
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function useSiteTheme(): SiteThemeContextValue {
|
|
84
|
+
const ctx = useContext(SiteThemeContext);
|
|
85
|
+
if (!ctx) {
|
|
86
|
+
throw new Error("useSiteTheme must be used within a SiteThemeProvider");
|
|
87
|
+
}
|
|
88
|
+
return ctx;
|
|
89
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createContext,
|
|
5
|
+
useContext,
|
|
6
|
+
useMemo,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
import {
|
|
10
|
+
createDefaultStorefrontThemeClasses,
|
|
11
|
+
defaultFormThemeClassNames,
|
|
12
|
+
mergeFormTheme,
|
|
13
|
+
mergeThemeSlots,
|
|
14
|
+
type FormThemeClassNames,
|
|
15
|
+
type StorefrontThemeClasses,
|
|
16
|
+
} from "medusa-storefront-theme-base";
|
|
17
|
+
|
|
18
|
+
export type StorefrontThemeClassesContextValue = {
|
|
19
|
+
themeClasses: StorefrontThemeClasses;
|
|
20
|
+
form: FormThemeClassNames;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const StorefrontThemeClassesContext = createContext<
|
|
24
|
+
StorefrontThemeClassesContextValue | undefined
|
|
25
|
+
>(undefined);
|
|
26
|
+
|
|
27
|
+
export type StorefrontThemeClassesProviderProps = {
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
themeClasses?: StorefrontThemeClasses;
|
|
30
|
+
form?: FormThemeClassNames;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export function StorefrontThemeClassesProvider({
|
|
34
|
+
children,
|
|
35
|
+
themeClasses = createDefaultStorefrontThemeClasses(),
|
|
36
|
+
form = defaultFormThemeClassNames,
|
|
37
|
+
}: StorefrontThemeClassesProviderProps) {
|
|
38
|
+
const value = useMemo(
|
|
39
|
+
() => ({ themeClasses, form }),
|
|
40
|
+
[themeClasses, form],
|
|
41
|
+
);
|
|
42
|
+
return (
|
|
43
|
+
<StorefrontThemeClassesContext.Provider value={value}>
|
|
44
|
+
{children}
|
|
45
|
+
</StorefrontThemeClassesContext.Provider>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function useStorefrontThemeClasses(): StorefrontThemeClassesContextValue {
|
|
50
|
+
const ctx = useContext(StorefrontThemeClassesContext);
|
|
51
|
+
if (!ctx) {
|
|
52
|
+
return {
|
|
53
|
+
themeClasses: createDefaultStorefrontThemeClasses(),
|
|
54
|
+
form: defaultFormThemeClassNames,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return ctx;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Theme slot for a UI area (account, order, cart, …) with optional prop overrides. */
|
|
61
|
+
export function useThemeSection<K extends keyof StorefrontThemeClasses>(
|
|
62
|
+
section: K,
|
|
63
|
+
propOverrides?: Partial<StorefrontThemeClasses[K]>,
|
|
64
|
+
): StorefrontThemeClasses[K] {
|
|
65
|
+
const { themeClasses } = useStorefrontThemeClasses();
|
|
66
|
+
return useMemo(
|
|
67
|
+
() =>
|
|
68
|
+
mergeThemeSlots(
|
|
69
|
+
themeClasses[section] as Record<string, unknown>,
|
|
70
|
+
propOverrides as Record<string, unknown> | undefined,
|
|
71
|
+
) as StorefrontThemeClasses[K],
|
|
72
|
+
[themeClasses, section, propOverrides],
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function useFormThemeClasses(
|
|
77
|
+
propOverrides?: Partial<FormThemeClassNames>,
|
|
78
|
+
): FormThemeClassNames {
|
|
79
|
+
const { form } = useStorefrontThemeClasses();
|
|
80
|
+
return useMemo(
|
|
81
|
+
() => mergeFormTheme(form, propOverrides),
|
|
82
|
+
[form, propOverrides],
|
|
83
|
+
);
|
|
84
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
/** Shared contexts */
|
|
2
2
|
export { ModalProvider, useModal } from "./context/modal-context";
|
|
3
3
|
export { WishlistProvider, useWishlist } from "./context/wishlist-context";
|
|
4
|
+
export {
|
|
5
|
+
ButtonThemeProvider,
|
|
6
|
+
useButtonClassName,
|
|
7
|
+
useButtonTheme,
|
|
8
|
+
type ButtonSize,
|
|
9
|
+
type ButtonTheme,
|
|
10
|
+
type ButtonVariant,
|
|
11
|
+
} from "./context/button-theme-context";
|
|
4
12
|
|
|
5
13
|
/** Utilities */
|
|
6
14
|
export { getBaseURL } from "./util/env";
|
|
@@ -4,7 +4,7 @@ import SkeletonOrderItems from "medusa-ui-common/skeletons/components/skeleton-o
|
|
|
4
4
|
|
|
5
5
|
const SkeletonOrderConfirmed = () => {
|
|
6
6
|
return (
|
|
7
|
-
<div className="bg-
|
|
7
|
+
<div className="bg-[color-mix(in_srgb,var(--sf-color-text-muted)_8%,var(--sf-color-surface))] py-6 min-h-[calc(100vh-64px)] animate-pulse">
|
|
8
8
|
<div className="content-container flex justify-center">
|
|
9
9
|
<div className="max-w-4xl h-full bg-white w-full p-10">
|
|
10
10
|
<SkeletonOrderConfirmedHeader />
|
|
@@ -1,11 +1,119 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import type { ReactNode } from "react";
|
|
4
|
+
import {
|
|
5
|
+
defaultButtonTheme,
|
|
6
|
+
type ButtonTheme,
|
|
7
|
+
} from "medusa-storefront-theme-base/button-theme";
|
|
8
|
+
import {
|
|
9
|
+
ButtonThemeProvider,
|
|
10
|
+
useButtonClassName,
|
|
11
|
+
useButtonTheme,
|
|
12
|
+
} from "./context/button-theme-context";
|
|
13
|
+
import { SiteThemeProvider, useSiteTheme } from "./context/site-theme-context";
|
|
14
|
+
import {
|
|
15
|
+
StorefrontThemeClassesProvider,
|
|
16
|
+
useFormThemeClasses,
|
|
17
|
+
useStorefrontThemeClasses,
|
|
18
|
+
useThemeSection,
|
|
19
|
+
} from "./context/storefront-theme-classes-context";
|
|
4
20
|
import { WishlistProvider } from "./context/wishlist-context";
|
|
21
|
+
import {
|
|
22
|
+
defaultStorefrontTheme,
|
|
23
|
+
type StorefrontTheme,
|
|
24
|
+
} from "./util/storefront-theme";
|
|
25
|
+
import type {
|
|
26
|
+
FormThemeClassNames,
|
|
27
|
+
StorefrontThemeClasses,
|
|
28
|
+
} from "medusa-storefront-theme-base";
|
|
5
29
|
|
|
6
30
|
export { WishlistProvider, useWishlist } from "./context/wishlist-context";
|
|
31
|
+
export {
|
|
32
|
+
ButtonThemeProvider,
|
|
33
|
+
useButtonClassName,
|
|
34
|
+
useButtonTheme,
|
|
35
|
+
type ButtonSize,
|
|
36
|
+
type ButtonTheme,
|
|
37
|
+
type ButtonVariant,
|
|
38
|
+
} from "./context/button-theme-context";
|
|
39
|
+
export {
|
|
40
|
+
SiteThemeProvider,
|
|
41
|
+
useSiteTheme,
|
|
42
|
+
type SiteThemeContextValue,
|
|
43
|
+
} from "./context/site-theme-context";
|
|
44
|
+
export {
|
|
45
|
+
StorefrontThemeClassesProvider,
|
|
46
|
+
useStorefrontThemeClasses,
|
|
47
|
+
useThemeSection,
|
|
48
|
+
useFormThemeClasses,
|
|
49
|
+
} from "./context/storefront-theme-classes-context";
|
|
50
|
+
export {
|
|
51
|
+
buttonCssClasses,
|
|
52
|
+
buttonCssVarNames,
|
|
53
|
+
buildButtonCssVars,
|
|
54
|
+
} from "./util/button-css-vars";
|
|
55
|
+
export {
|
|
56
|
+
themeCssClasses,
|
|
57
|
+
themeCssVarNames,
|
|
58
|
+
buildStorefrontThemeCssVars,
|
|
59
|
+
themeColorsToButtonCssVars,
|
|
60
|
+
mergeStorefrontTheme,
|
|
61
|
+
defaultStorefrontTheme,
|
|
62
|
+
type StorefrontTheme,
|
|
63
|
+
type StorefrontThemeColors,
|
|
64
|
+
type StorefrontThemeFonts,
|
|
65
|
+
} from "./util/storefront-theme";
|
|
7
66
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
67
|
+
export type StorefrontUiProvidersProps = {
|
|
68
|
+
children: ReactNode;
|
|
69
|
+
/** Site-wide colors and fonts — compositions/theme.ts */
|
|
70
|
+
siteTheme?: StorefrontTheme;
|
|
71
|
+
/** Button Tailwind variants — derived from siteTheme.colors when omitted */
|
|
72
|
+
buttonTheme?: ButtonTheme;
|
|
73
|
+
/** Per-area className slots — from defineSiteTheme().themeClasses */
|
|
74
|
+
themeClasses?: StorefrontThemeClasses;
|
|
75
|
+
/** Form field classNames — from defineSiteTheme().siteFormTheme */
|
|
76
|
+
formTheme?: FormThemeClassNames;
|
|
77
|
+
/**
|
|
78
|
+
* @deprecated Prefer siteTheme — button CSS vars are derived from siteTheme.colors.
|
|
79
|
+
*/
|
|
80
|
+
buttonCssVars?: Record<string, string>;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function ButtonThemeFromSite({
|
|
84
|
+
children,
|
|
85
|
+
buttonTheme,
|
|
86
|
+
legacyButtonCssVars,
|
|
87
|
+
}: {
|
|
88
|
+
children: ReactNode;
|
|
89
|
+
buttonTheme: ButtonTheme;
|
|
90
|
+
legacyButtonCssVars?: Record<string, string>;
|
|
91
|
+
}) {
|
|
92
|
+
const { buttonCssVars: fromSite } = useSiteTheme();
|
|
93
|
+
const cssVars = legacyButtonCssVars ?? fromSite;
|
|
94
|
+
return (
|
|
95
|
+
<ButtonThemeProvider theme={buttonTheme} cssVars={cssVars}>
|
|
96
|
+
{children}
|
|
97
|
+
</ButtonThemeProvider>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Wrap the storefront app (site theme, buttons, wishlist). */
|
|
102
|
+
export function StorefrontUiProviders({
|
|
103
|
+
children,
|
|
104
|
+
siteTheme = defaultStorefrontTheme,
|
|
105
|
+
buttonTheme = defaultButtonTheme,
|
|
106
|
+
themeClasses,
|
|
107
|
+
formTheme,
|
|
108
|
+
buttonCssVars,
|
|
109
|
+
}: StorefrontUiProvidersProps) {
|
|
110
|
+
return (
|
|
111
|
+
<SiteThemeProvider theme={siteTheme}>
|
|
112
|
+
<StorefrontThemeClassesProvider themeClasses={themeClasses} form={formTheme}>
|
|
113
|
+
<ButtonThemeFromSite buttonTheme={buttonTheme} legacyButtonCssVars={buttonCssVars}>
|
|
114
|
+
<WishlistProvider>{children}</WishlistProvider>
|
|
115
|
+
</ButtonThemeFromSite>
|
|
116
|
+
</StorefrontThemeClassesProvider>
|
|
117
|
+
</SiteThemeProvider>
|
|
118
|
+
);
|
|
11
119
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export {
|
|
2
|
+
themeCssVarNames,
|
|
3
|
+
themeCssClasses,
|
|
4
|
+
defaultStorefrontTheme,
|
|
5
|
+
buildStorefrontThemeCssVars,
|
|
6
|
+
buildStorefrontRootCssVars,
|
|
7
|
+
buildStorefrontRootCss,
|
|
8
|
+
themeColorsToButtonCssVars,
|
|
9
|
+
mergeStorefrontTheme,
|
|
10
|
+
type StorefrontTheme,
|
|
11
|
+
type StorefrontThemeColors,
|
|
12
|
+
type StorefrontThemeFonts,
|
|
13
|
+
} from "medusa-storefront-theme-base/storefront-theme";
|