medusa-ui-common 2.0.1 → 3.0.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "medusa-ui-common",
3
- "version": "2.0.1",
3
+ "version": "3.0.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.3.0",
26
- "medusa-storefront-data": "^2.0.0",
25
+ "medusa-storefront-analytics": "^1.0.0",
26
+ "medusa-storefront-data": "^2.5.3",
27
27
  "medusa-storefront-hooks": "^1.0.0",
28
28
  "medusa-wishlist-logic": "^2.0.0",
29
+ "medusa-storefront-theme-base": "^3.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"
@@ -86,6 +87,11 @@
86
87
  "types": "./src/constants/*",
87
88
  "import": "./src/constants/*",
88
89
  "default": "./src/constants/*"
90
+ },
91
+ "./page-compose/types": {
92
+ "types": "./src/page-compose/types.ts",
93
+ "import": "./src/page-compose/types.ts",
94
+ "default": "./src/page-compose/types.ts"
89
95
  }
90
96
  },
91
97
  "typesVersions": {
@@ -96,6 +102,11 @@
96
102
  }
97
103
  },
98
104
  "devDependencies": {
105
+ "next": "^15.0.0",
106
+ "medusa-storefront-analytics": "workspace:*",
107
+ "medusa-storefront-core": "workspace:*",
108
+ "medusa-storefront-data": "workspace:*",
109
+ "medusa-storefront-theme-base": "workspace:*",
99
110
  "@headlessui/react": "^2.2.0",
100
111
  "@medusajs/icons": "^2.0.0",
101
112
  "@medusajs/ui": "^4.0.0",
@@ -1,43 +1,2 @@
1
- import { HttpTypes } from "@medusajs/types"
2
- import LocalizedClientLink from "../localized-client-link"
3
-
4
- type BreadcrumbProps = {
5
- product: HttpTypes.StoreProduct
6
- }
7
-
8
- export default function Breadcrumb({ product }: BreadcrumbProps) {
9
- const primaryCategory = product.categories?.[0]
10
-
11
- return (
12
- <nav className="text-xs sm:text-sm text-gray-800 mb-3 sm:mb-4 overflow-x-auto font-medium">
13
- <div className="flex items-center whitespace-nowrap">
14
- <LocalizedClientLink href="/" className="hover:text-gray-900 transition-colors">
15
- Home
16
- </LocalizedClientLink>
17
- <span className="mx-2">/</span>
18
- <LocalizedClientLink href="/store" className="hover:text-gray-900 transition-colors">
19
- Shop
20
- </LocalizedClientLink>
21
- {primaryCategory && (
22
- <>
23
- <span className="mx-2">/</span>
24
- <LocalizedClientLink
25
- href={`/store?category=${primaryCategory.id}`}
26
- className="hover:text-gray-900 transition-colors"
27
- >
28
- {primaryCategory.name}
29
- </LocalizedClientLink>
30
- </>
31
- )}
32
- <span className="mx-2">/</span>
33
- <span className="text-gray-900 truncate max-w-[150px] sm:max-w-[300px] md:max-w-[400px] inline-block align-bottom" title={product.title ?? ""}>
34
- {product.title}
35
- </span>
36
- </div>
37
- </nav>
38
- )
39
- }
40
-
41
-
42
-
43
-
1
+ /** @deprecated Import from `medusa-ui-product/products/components/breadcrumb` */
2
+ export { default } from "medusa-ui-product/products/components/breadcrumb"
@@ -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-[#FFFAFE] 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' }}>
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-gray-900 mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
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-gray-900">
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-gray-900"
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-[#8B5AB1] border-t-transparent rounded-full animate-spin"></span>
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-gray-900 mb-2.5 min-[550px]:mb-3 sm:mb-4 min-[1023px]:mb-3 min-[1150px]:mb-4 min-[1360px]:mb-4">
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
- <button
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="w-full h-10 min-[550px]:h-11 sm:h-12 min-[1023px]:h-11 min-[1150px]:h-12 min-[1360px]:h-12 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-white font-medium transition-colors duration-200 hover:opacity-90 mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6 disabled:opacity-50"
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
- </button>
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
- "w-full py-2.5 min-[550px]:py-3 text-xs min-[550px]:text-sm sm:text-base text-gray-900 bg-white border-2 font-bold rounded-lg transition-colors border-[#8B5AB1]",
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: "#8B5AB1"
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
- "w-full py-2.5 min-[550px]:py-3 text-xs min-[550px]:text-sm sm:text-base text-white font-bold rounded-lg transition-colors bg-[#8B5AB1]",
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
- <button
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="w-full py-2.5 min-[550px]:py-3 sm:py-4 min-[1023px]:py-3 min-[1150px]:py-4 min-[1360px]:py-4 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-white font-medium mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6 rounded-lg transition-colors duration-200 hover:opacity-90"
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
- </button>
532
+ </ThemedButton>
538
533
  ) : (
539
- <button
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="w-full h-10 min-[550px]:h-11 sm:h-12 min-[1023px]:h-11 min-[1150px]:h-12 min-[1360px]:h-12 text-xs min-[550px]:text-sm sm:text-base min-[1023px]:text-sm min-[1150px]:text-base min-[1360px]:text-base text-white font-medium transition-colors duration-200 hover:opacity-90 mt-3 min-[550px]:mt-4 sm:mt-6 min-[1023px]:mt-5 min-[1150px]:mt-6 min-[1360px]:mt-6 disabled:opacity-50"
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
- </button>
549
+ </ThemedButton>
555
550
  )}
556
551
  </div>
557
552
  )}
@@ -1,4 +1,3 @@
1
- import { Label } from "@medusajs/ui"
2
1
  import React from "react"
3
2
  import Color from "color"
4
3
 
@@ -43,21 +42,24 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
43
42
  }
44
43
  }, [hex, color])
45
44
 
45
+ const labelId = React.useId()
46
+
46
47
  return (
47
48
  <div className="flex items-center space-x-2 px-1 py-0.5">
48
49
  <button
49
50
  type="button"
50
51
  role="checkbox"
51
52
  aria-checked={checked}
53
+ aria-labelledby={labelId}
52
54
  onClick={onChange}
53
55
  name={name}
54
56
  data-testid={dataTestId}
55
57
  data-ga-event={dataGaEvent}
56
58
  data-ga-label={dataGaLabel}
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"
59
+ className="w-5 h-5 shrink-0 rounded-md border-2 flex items-center justify-center transition-all focus:outline-none focus:ring-2 focus:ring-offset-2"
58
60
  style={checked ? {
59
- backgroundColor: '#8B5AB1',
60
- borderColor: '#8B5AB1'
61
+ backgroundColor: 'var(--sf-btn-primary)',
62
+ borderColor: 'var(--sf-btn-primary)'
61
63
  } : {
62
64
  backgroundColor: 'white',
63
65
  borderColor: '#C0C0C0'
@@ -77,11 +79,17 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
77
79
  </svg>
78
80
  )}
79
81
  </button>
80
- <Label
81
- htmlFor={name || "checkbox"}
82
- className="!transform-none !txt-medium cursor-pointer flex items-center gap-2"
83
- size="large"
82
+ <span
83
+ id={labelId}
84
+ role="presentation"
84
85
  onClick={onChange}
86
+ onKeyDown={(e) => {
87
+ if (e.key === "Enter" || e.key === " ") {
88
+ e.preventDefault()
89
+ onChange?.()
90
+ }
91
+ }}
92
+ className="txt-medium cursor-pointer flex items-center gap-2 text-[var(--sf-color-text)]"
85
93
  >
86
94
  {colorHex && (
87
95
  <div
@@ -90,7 +98,7 @@ const CheckboxWithLabel: React.FC<CheckboxProps> = ({
90
98
  />
91
99
  )}
92
100
  {label}
93
- </Label>
101
+ </span>
94
102
  </div>
95
103
  )
96
104
  }
@@ -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-gray-50 rounded-sm overflow-hidden border border-gray-100 shadow-sm">
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-gray-300 bg-gray-50 uppercase text-[10px] font-bold">No Image</div>
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-gray-800 mb-1.5 leading-tight">Move from Bag</h3>
120
- <p className="text-[15px] text-gray-500 leading-snug font-medium max-w-[280px]">
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-gray-500 tracking-wider hover:bg-gray-50 transition-colors uppercase"
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-gray-50 transition-colors uppercase"
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-gray-900 mb-4 uppercase">
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-gray-900 font-bold text-xl">%</span>
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-gray-900">
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-gray-500">
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-gray-900"
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-[#8B5AB1] hover:!bg-[#7a4a9f] text-white border-0"
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-gray-900 uppercase tracking-tight">
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-purple-50/50 transition-all hover:bg-purple-50 group"
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-[#8B5AB1]">
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: '#8B5AB1' }} data-testid="discount-code">
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-gray-400 hover:text-red-500 hover:bg-red-50 rounded-full transition-all"
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)
@@ -1,134 +1,2 @@
1
- import { Heading } from "@medusajs/ui"
2
- import CheckboxWithLabel from "medusa-ui-common/common/components/checkbox"
3
- import React from "react"
4
-
5
- type FilterCheckboxGroupProps = {
6
- title: string
7
- items: { value: string; label: string }[]
8
- values: string[]
9
- handleChange: (value: string) => void
10
- isColor?: boolean
11
- 'data-testid'?: string
12
- }
13
-
14
- const FilterCheckboxGroup = ({
15
- title,
16
- items,
17
- values,
18
- handleChange,
19
- isColor,
20
- 'data-testid': dataTestId,
21
- }: FilterCheckboxGroupProps) => {
22
- const [isOpen, setIsOpen] = React.useState(true)
23
- const [showAll, setShowAll] = React.useState(false)
24
-
25
- // Optimistic local state — reflects clicks instantly without waiting for server
26
- const [optimisticValues, setOptimisticValues] = React.useState<string[]>(values)
27
-
28
- // Sync back when server props update (after navigation completes)
29
- React.useEffect(() => {
30
- setOptimisticValues(values)
31
- }, [values])
32
-
33
- const initialItemsCount = 6
34
- const hasMore = items?.length > initialItemsCount
35
-
36
- // Auto-expand if a selected item is hidden or if any item is selected
37
- React.useEffect(() => {
38
- if (values && values.length > 0) {
39
- setIsOpen(true)
40
- if (hasMore && items) {
41
- const hiddenItems = items.slice(initialItemsCount)
42
- const isAnyHiddenItemSelected = hiddenItems.some(item => values.includes(item.value))
43
- if (isAnyHiddenItemSelected) {
44
- setShowAll(true)
45
- }
46
- }
47
- }
48
- }, [values, items, hasMore])
49
-
50
- const handleOptimisticChange = (value: string) => {
51
- // Immediately toggle locally — no flicker
52
- setOptimisticValues((prev) =>
53
- prev.includes(value) ? prev.filter((v) => v !== value) : [...prev, value]
54
- )
55
- // Trigger actual URL navigation in background
56
- handleChange(value)
57
- }
58
-
59
- const displayedItems = showAll ? items : items?.slice(0, initialItemsCount)
60
-
61
- return (
62
- <div className="flex flex-col border-b border-gray-100 pb-4 last:border-0 last:pb-0">
63
- <button
64
- onClick={() => setIsOpen(!isOpen)}
65
- className="flex items-center justify-between w-full group transition-all py-1"
66
- >
67
- <Heading
68
- level="h3"
69
- className="text-sm font-bold text-gray-900 uppercase tracking-wider group-hover:text-[#8B5AB1] transition-colors"
70
- >
71
- {title}
72
- </Heading>
73
- <span
74
- className={`transform transition-transform duration-300 text-gray-400 group-hover:text-[#8B5AB1] ${isOpen ? "" : "-rotate-180"}`}
75
- >
76
- <svg
77
- width="18"
78
- height="18"
79
- viewBox="0 0 24 24"
80
- fill="none"
81
- stroke="currentColor"
82
- strokeWidth="2.5"
83
- strokeLinecap="round"
84
- strokeLinejoin="round"
85
- >
86
- <polyline points="18 15 12 9 6 15"></polyline>
87
- </svg>
88
- </span>
89
- </button>
90
-
91
- <div
92
- className={`flex flex-col gap-y-1 transition-all duration-300 ease-in-out ${isOpen ? "mt-3 opacity-100 px-0.5" : "max-h-0 opacity-0 invisible overflow-hidden"}`}
93
- style={isOpen ? { maxHeight: showAll ? '1000px' : '300px' } : {}}
94
- >
95
- {displayedItems?.map((i) => (
96
- <CheckboxWithLabel
97
- key={i.value}
98
- label={i.label}
99
- color={isColor ? i.value : undefined}
100
- checked={optimisticValues.includes(i.value)}
101
- onChange={() => handleOptimisticChange(i.value)}
102
- data-testid={dataTestId}
103
- data-ga-event="filter_checkbox_click"
104
- data-ga-label={`${title} - ${i.label}`}
105
- />
106
- ))}
107
-
108
- {hasMore && isOpen && (
109
- <button
110
- onClick={() => setShowAll(!showAll)}
111
- className="text-xs font-bold text-[#8B5AB1] hover:underline mt-2 flex items-center gap-1 uppercase tracking-tight"
112
- >
113
- {showAll ? "View Less" : `View More (${items.length - initialItemsCount})`}
114
- <svg
115
- width="12"
116
- height="12"
117
- viewBox="0 0 24 24"
118
- fill="none"
119
- stroke="currentColor"
120
- strokeWidth="3"
121
- strokeLinecap="round"
122
- strokeLinejoin="round"
123
- className={`transform transition-transform ${showAll ? "rotate-180" : ""}`}
124
- >
125
- <polyline points="6 9 12 15 18 9"></polyline>
126
- </svg>
127
- </button>
128
- )}
129
- </div>
130
- </div>
131
- )
132
- }
133
-
134
- export default FilterCheckboxGroup
1
+ /** @deprecated Import from `medusa-ui-product/store/components/filter-checkbox-group` */
2
+ export { default } from "medusa-ui-product/store/components/filter-checkbox-group"