@pradip1995/theme-sahsha 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +29 -0
  2. package/assets/hero-desktop.svg +10 -0
  3. package/assets/hero-mobile.svg +9 -0
  4. package/assets/logo.svg +3 -0
  5. package/package.json +60 -0
  6. package/src/blocks/home/Features/index.tsx +87 -0
  7. package/src/blocks/home/Hero/index.tsx +98 -0
  8. package/src/blocks/home/LovedByMoms/bestsellers-carousel.tsx +1 -0
  9. package/src/blocks/home/LovedByMoms/index.tsx +43 -0
  10. package/src/blocks/home/LovedByMoms/loved-by-moms-section.tsx +46 -0
  11. package/src/blocks/home/NewArrivals/index.tsx +91 -0
  12. package/src/blocks/home/PromotionalBanners/index.tsx +81 -0
  13. package/src/blocks/home/ShopByAge/collections-showcase-client.tsx +131 -0
  14. package/src/blocks/home/ShopByAge/collections-showcase-types.ts +4 -0
  15. package/src/blocks/home/ShopByAge/index.tsx +168 -0
  16. package/src/blocks/home/ShopByCategory/index.tsx +111 -0
  17. package/src/blocks/home/Testimonials/index.tsx +25 -0
  18. package/src/blocks/home/Testimonials/reviews-scroll.tsx +122 -0
  19. package/src/blocks/home/Testimonials/testimonials-client.tsx +127 -0
  20. package/src/blocks/home/WhyChooseUs/index.tsx +39 -0
  21. package/src/components/product-carousel.tsx +79 -0
  22. package/src/layouts/MainLayoutShell.tsx +14 -0
  23. package/src/primitives/Button.tsx +31 -0
  24. package/src/primitives/Card.tsx +32 -0
  25. package/src/primitives/index.ts +2 -0
  26. package/src/slots/account/ForgotPassword/index.tsx +1 -0
  27. package/src/slots/account/GoogleLogin/index.tsx +28 -0
  28. package/src/slots/account/Login/index.tsx +1 -0
  29. package/src/slots/account/LoginTemplate/index.tsx +12 -0
  30. package/src/slots/account/LoginTemplate/login-template-client.tsx +83 -0
  31. package/src/slots/account/Register/index.tsx +1 -0
  32. package/src/slots/cart/CartItem/index.tsx +11 -0
  33. package/src/slots/cart/CartSummary/index.tsx +8 -0
  34. package/src/slots/checkout/CheckoutForm/index.tsx +1 -0
  35. package/src/slots/checkout/CheckoutSummary/index.tsx +1 -0
  36. package/src/slots/layout/Footer/index.tsx +95 -0
  37. package/src/slots/layout/Nav/index.tsx +50 -0
  38. package/src/slots/layout/Nav/nav-categories-dropdown.tsx +74 -0
  39. package/src/slots/layout/Nav/nav-collections-dropdown.tsx +106 -0
  40. package/src/slots/layout/Nav/nav-header-content.tsx +165 -0
  41. package/src/slots/layout/Nav/nav-header-shell.tsx +47 -0
  42. package/src/slots/layout/Nav/nav-link-luxury.tsx +15 -0
  43. package/src/slots/layout/PromoBar/index.tsx +9 -0
  44. package/src/slots/layout/PromoBar/promo-bar-content.tsx +118 -0
  45. package/src/slots/order/OrderDetails/index.tsx +12 -0
  46. package/src/slots/product/ProductActions/ProductCTASection.tsx +232 -0
  47. package/src/slots/product/ProductActions/ProductDetailsSection.tsx +200 -0
  48. package/src/slots/product/ProductActions/ProductFeaturePanel.tsx +150 -0
  49. package/src/slots/product/ProductActions/ProductHighlightsSection.tsx +112 -0
  50. package/src/slots/product/ProductActions/ProductOptionsSection.tsx +215 -0
  51. package/src/slots/product/ProductActions/ProductPriceSection.tsx +53 -0
  52. package/src/slots/product/ProductActions/ProductTrustSection.tsx +84 -0
  53. package/src/slots/product/ProductActions/SizeChartPanel.tsx +93 -0
  54. package/src/slots/product/ProductActions/index.tsx +156 -0
  55. package/src/slots/product/ProductActions/product-metadata-fields.ts +503 -0
  56. package/src/slots/product/ProductActions/size-chart-data.ts +108 -0
  57. package/src/slots/product/ProductCard/index.tsx +258 -0
  58. package/src/slots/product/ProductInfo/index.tsx +35 -0
  59. package/src/templates/CollectionsPage/index.tsx +72 -0
  60. package/src/templates/StorePage/index.tsx +134 -0
  61. package/src/tokens/colors.ts +21 -0
  62. package/src/tokens/fonts.ts +16 -0
  63. package/src/tokens/index.ts +3 -0
  64. package/src/tokens/spacing.ts +9 -0
  65. package/src/tokens/theme.css +12754 -0
@@ -0,0 +1,106 @@
1
+ "use client"
2
+
3
+ import Image from "next/image"
4
+ import { useRef, useState } from "react"
5
+ import { HttpTypes } from "@medusajs/types"
6
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
7
+ import PlaceholderImage from "@modules/common/icons/placeholder-image"
8
+ import { getCollectionImage } from "@lib/category-collection-images"
9
+
10
+ type NavCollectionsDropdownProps = {
11
+ collections: HttpTypes.StoreCollection[]
12
+ }
13
+
14
+ export default function NavCollectionsDropdown({
15
+ collections,
16
+ }: NavCollectionsDropdownProps) {
17
+ const [open, setOpen] = useState(false)
18
+ const closeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
19
+
20
+ const handleOpen = () => {
21
+ if (closeTimerRef.current) {
22
+ clearTimeout(closeTimerRef.current)
23
+ closeTimerRef.current = null
24
+ }
25
+ setOpen(true)
26
+ }
27
+
28
+ const handleClose = () => {
29
+ closeTimerRef.current = setTimeout(() => setOpen(false), 200)
30
+ }
31
+
32
+ if (!collections.length) {
33
+ return (
34
+ <LocalizedClientLink href="/collections" className="nav-luxury-link group">
35
+ <span className="nav-luxury-link-label">Collections</span>
36
+ <span className="nav-luxury-link-line" aria-hidden />
37
+ </LocalizedClientLink>
38
+ )
39
+ }
40
+
41
+ return (
42
+ <div
43
+ className={`nav-collections${open ? " nav-collections--open" : ""}`}
44
+ onMouseEnter={handleOpen}
45
+ onMouseLeave={handleClose}
46
+ onFocus={handleOpen}
47
+ onBlur={handleClose}
48
+ >
49
+ <button
50
+ type="button"
51
+ className="nav-luxury-link nav-collections__trigger group"
52
+ aria-expanded={open}
53
+ aria-haspopup="true"
54
+ >
55
+ <span className="nav-luxury-link-label">Collections</span>
56
+ <span className="nav-luxury-link-line" aria-hidden />
57
+ </button>
58
+
59
+ <div className="nav-collections__panel" aria-hidden={!open}>
60
+ <div className="nav-collections__grid">
61
+ {collections.map((collection) => {
62
+ const imageUrl = getCollectionImage(collection)
63
+ const title = collection.title || "Collection"
64
+
65
+ return (
66
+ <LocalizedClientLink
67
+ key={collection.id}
68
+ href={`/store?collection=${collection.id}`}
69
+ className="nav-collections__card"
70
+ onClick={() => setOpen(false)}
71
+ >
72
+ <div className="nav-collections__card-media">
73
+ {imageUrl ? (
74
+ <Image
75
+ src={imageUrl}
76
+ alt={title}
77
+ fill
78
+ unoptimized
79
+ sizes="160px"
80
+ className="nav-collections__card-image"
81
+ />
82
+ ) : (
83
+ <div className="nav-collections__card-placeholder">
84
+ <PlaceholderImage size={28} />
85
+ </div>
86
+ )}
87
+ </div>
88
+ <span className="nav-collections__card-title">{title}</span>
89
+ </LocalizedClientLink>
90
+ )
91
+ })}
92
+ </div>
93
+
94
+ <div className="nav-collections__footer">
95
+ <LocalizedClientLink
96
+ href="/collections"
97
+ className="nav-collections__view-all"
98
+ onClick={() => setOpen(false)}
99
+ >
100
+ View all collections
101
+ </LocalizedClientLink>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ )
106
+ }
@@ -0,0 +1,165 @@
1
+ "use client"
2
+
3
+ import { type ReactNode } from "react"
4
+ import { HttpTypes } from "@medusajs/types"
5
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
6
+ import MobileMenu from "@modules/layout/components/mobile-menu"
7
+ import {
8
+ DesktopSearchIcon,
9
+ useSearchContext,
10
+ } from "@modules/layout/components/desktop-search"
11
+ import AccountDropdown from "@modules/layout/components/account-dropdown"
12
+ import WishlistCounter from "@modules/layout/components/wishlist-counter"
13
+ import SmartSearchResults from "@modules/layout/components/smart-search/smart-search-results"
14
+ import SmartSearchInline from "@modules/layout/components/smart-search/smart-search-inline"
15
+ import NavLinkLuxury from "./nav-link-luxury"
16
+ import NavCategoriesDropdown from "./nav-categories-dropdown"
17
+ import NavCollectionsDropdown from "./nav-collections-dropdown"
18
+
19
+ type NavHeaderContentProps = {
20
+ currentLocale: string
21
+ customer: HttpTypes.StoreCustomer | null
22
+ categories: HttpTypes.StoreProductCategory[]
23
+ collections: HttpTypes.StoreCollection[]
24
+ logo: ReactNode
25
+ cartButton: ReactNode
26
+ }
27
+
28
+ export default function NavHeaderContent({
29
+ currentLocale,
30
+ customer,
31
+ categories,
32
+ collections,
33
+ logo,
34
+ cartButton,
35
+ }: NavHeaderContentProps) {
36
+ const { isSearchOpen } = useSearchContext()
37
+
38
+ return (
39
+ <div
40
+ className="nav-header-search-wrap relative"
41
+ data-search-open={isSearchOpen ? "true" : "false"}
42
+ >
43
+ <header className="nav-header">
44
+ <div className="nav-header-inner">
45
+ <div className="nav-header-mobile relative flex h-full w-full items-center lg:hidden">
46
+ {isSearchOpen ? (
47
+ <div className="nav-header-search-inline">
48
+ <SmartSearchInline />
49
+ </div>
50
+ ) : (
51
+ <>
52
+ <div className="nav-header-mobile__start flex flex-1 items-center justify-start min-w-0">
53
+ <MobileMenu
54
+ customer={customer}
55
+ countryCode={currentLocale || "in"}
56
+ categories={categories}
57
+ collections={collections}
58
+ />
59
+ </div>
60
+
61
+ <div className="nav-header-mobile__logo absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 pointer-events-none">
62
+ <div className="nav-header-logo-wrap pointer-events-auto flex justify-center">
63
+ {logo}
64
+ </div>
65
+ </div>
66
+
67
+ <div className="nav-header-mobile__end flex flex-1 items-center justify-end min-w-0">
68
+ <div className="nav-header-icons flex items-center justify-end gap-2 min-w-0">
69
+ <DesktopSearchIcon />
70
+
71
+ {!customer ? (
72
+ <LocalizedClientLink
73
+ href="/account"
74
+ className="nav-header-profile-link"
75
+ data-testid="nav-login-link"
76
+ >
77
+ <span className="sr-only">Login</span>
78
+ {/* eslint-disable-next-line @next/next/no-img-element */}
79
+ <img
80
+ src="/Account.svg"
81
+ alt="Login"
82
+ width={22}
83
+ height={22}
84
+ className="nav-header-icon w-[22px] h-[22px]"
85
+ />
86
+ </LocalizedClientLink>
87
+ ) : (
88
+ <AccountDropdown customer={customer} />
89
+ )}
90
+
91
+ <div className="nav-header-cart flex items-center">{cartButton}</div>
92
+ </div>
93
+ </div>
94
+ </>
95
+ )}
96
+ </div>
97
+
98
+ {isSearchOpen ? (
99
+ <div className="nav-header-search-inline nav-header-search-inline--desktop">
100
+ <SmartSearchInline />
101
+ </div>
102
+ ) : (
103
+ <div className="hidden lg:grid h-full grid-cols-[1fr_auto_1fr] items-center gap-4">
104
+ <div className="flex items-center justify-start min-w-0">
105
+ <nav
106
+ className="nav-header-menu flex items-center gap-8 xl:gap-10"
107
+ aria-label="Main"
108
+ >
109
+ <NavLinkLuxury href="/" label="Home" />
110
+ <NavLinkLuxury href="/store" label="Shop" />
111
+ <NavCategoriesDropdown categories={categories} />
112
+ <NavCollectionsDropdown collections={collections} />
113
+ <NavLinkLuxury href="/help" label="Help" />
114
+ </nav>
115
+ </div>
116
+
117
+ <div className="nav-header-logo-wrap flex justify-center px-2">
118
+ {logo}
119
+ </div>
120
+
121
+ <div className="nav-header-actions flex items-center justify-end h-full w-full min-w-0">
122
+ <div className="nav-header-icons flex items-center justify-end gap-5 min-w-0">
123
+ <DesktopSearchIcon />
124
+
125
+ {!customer ? (
126
+ <LocalizedClientLink
127
+ href="/account"
128
+ className="nav-header-login-btn"
129
+ data-testid="nav-login-link"
130
+ >
131
+ Login
132
+ </LocalizedClientLink>
133
+ ) : (
134
+ <AccountDropdown customer={customer} />
135
+ )}
136
+
137
+ <LocalizedClientLink
138
+ href="/wishlist"
139
+ className="nav-header-icon-link inline-flex items-center relative"
140
+ data-testid="nav-wishlist-link"
141
+ >
142
+ <span className="sr-only">Wishlist</span>
143
+ {/* eslint-disable-next-line @next/next/no-img-element */}
144
+ <img
145
+ src="/Wishlist.svg"
146
+ alt=""
147
+ width={22}
148
+ height={22}
149
+ className="nav-header-icon w-[22px] h-[22px]"
150
+ />
151
+ <WishlistCounter />
152
+ </LocalizedClientLink>
153
+
154
+ <div className="nav-header-cart flex items-center">{cartButton}</div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ )}
159
+ </div>
160
+ </header>
161
+
162
+ <SmartSearchResults countryCode={currentLocale || "in"} />
163
+ </div>
164
+ )
165
+ }
@@ -0,0 +1,47 @@
1
+ "use client"
2
+
3
+ import { useSearchContext } from "@modules/layout/components/desktop-search"
4
+ import { usePathname } from "next/navigation"
5
+ import { useEffect, useState, type ReactNode } from "react"
6
+
7
+ function isHomePath(pathname: string) {
8
+ const segments = pathname.split("/").filter(Boolean)
9
+ return segments.length <= 1
10
+ }
11
+
12
+ export default function NavHeaderShell({ children }: { children: ReactNode }) {
13
+ const pathname = usePathname() || ""
14
+ const isHome = isHomePath(pathname)
15
+ const { isSearchOpen } = useSearchContext()
16
+ const [scrolled, setScrolled] = useState(false)
17
+
18
+ useEffect(() => {
19
+ if (!isHome) {
20
+ setScrolled(true)
21
+ return
22
+ }
23
+
24
+ const onScroll = () => {
25
+ setScrolled(window.scrollY > 24)
26
+ }
27
+
28
+ onScroll()
29
+ window.addEventListener("scroll", onScroll, { passive: true })
30
+ return () => window.removeEventListener("scroll", onScroll)
31
+ }, [isHome])
32
+
33
+ const isTransparent = isHome && !scrolled && !isSearchOpen
34
+
35
+ return (
36
+ <>
37
+ <div
38
+ className="nav-header-fixed"
39
+ data-nav-transparent={isTransparent ? "true" : "false"}
40
+ data-search-open={isSearchOpen ? "true" : "false"}
41
+ >
42
+ {children}
43
+ </div>
44
+ <div className="nav-header-spacer" aria-hidden />
45
+ </>
46
+ )
47
+ }
@@ -0,0 +1,15 @@
1
+ import LocalizedClientLink from "@modules/common/components/localized-client-link"
2
+
3
+ type NavLinkLuxuryProps = {
4
+ href: string
5
+ label: string
6
+ }
7
+
8
+ export default function NavLinkLuxury({ href, label }: NavLinkLuxuryProps) {
9
+ return (
10
+ <LocalizedClientLink href={href} className="nav-luxury-link group">
11
+ <span className="nav-luxury-link-label">{label}</span>
12
+ <span className="nav-luxury-link-line" aria-hidden />
13
+ </LocalizedClientLink>
14
+ )
15
+ }
@@ -0,0 +1,9 @@
1
+ import PromoBarContent from "./promo-bar-content"
2
+
3
+ export default function PromoBar() {
4
+ return (
5
+ <div className="promo-bar" role="region" aria-label="Store promotions">
6
+ <PromoBarContent />
7
+ </div>
8
+ )
9
+ }
@@ -0,0 +1,118 @@
1
+ "use client"
2
+
3
+ import { useEffect, useState } from "react"
4
+ import {
5
+ Truck,
6
+ Sparkles,
7
+ Gem,
8
+ Tag,
9
+ ShieldCheck,
10
+ type LucideIcon,
11
+ } from "lucide-react"
12
+
13
+ export type PromoMessage = {
14
+ icon: LucideIcon
15
+ text: string
16
+ highlight?: string
17
+ }
18
+
19
+ const PROMO_MESSAGES: PromoMessage[] = [
20
+ {
21
+ icon: Truck,
22
+ text: "Free Shipping on Orders Above ₹999",
23
+ },
24
+ {
25
+ icon: Sparkles,
26
+ text: "New Arrivals Are Live • Shop The Latest Saree Collection",
27
+ },
28
+ {
29
+ icon: Gem,
30
+ text: "Handpicked Sarees Crafted For Every Celebration",
31
+ },
32
+ {
33
+ icon: Tag,
34
+ text: "Flat 10% OFF On Your First Order • Use Code: WELCOME10",
35
+ highlight: "WELCOME10",
36
+ },
37
+ {
38
+ icon: ShieldCheck,
39
+ text: "Free Shipping • Easy Returns • Secure Payments",
40
+ },
41
+ ]
42
+
43
+ const HOLD_MS = 1000
44
+ const ANIMATION_MS = 280
45
+
46
+ function PromoMessageItem({ text, highlight }: PromoMessage) {
47
+ if (!highlight || !text.includes(highlight)) {
48
+ return <span>{text}</span>
49
+ }
50
+
51
+ const [before, after] = text.split(highlight)
52
+
53
+ return (
54
+ <span>
55
+ {before}
56
+ <span className="promo-bar__highlight">{highlight}</span>
57
+ {after}
58
+ </span>
59
+ )
60
+ }
61
+
62
+ export default function PromoBarContent() {
63
+ const [activeIndex, setActiveIndex] = useState(0)
64
+ const [phase, setPhase] = useState<"idle" | "exit" | "enter">("enter")
65
+
66
+ useEffect(() => {
67
+ let holdTimer: ReturnType<typeof setTimeout>
68
+ let exitTimer: ReturnType<typeof setTimeout>
69
+ let enterTimer: ReturnType<typeof setTimeout>
70
+
71
+ const scheduleNext = () => {
72
+ holdTimer = setTimeout(() => {
73
+ setPhase("exit")
74
+
75
+ exitTimer = setTimeout(() => {
76
+ setActiveIndex((i) => (i + 1) % PROMO_MESSAGES.length)
77
+ setPhase("enter")
78
+
79
+ enterTimer = setTimeout(() => {
80
+ setPhase("idle")
81
+ scheduleNext()
82
+ }, ANIMATION_MS)
83
+ }, ANIMATION_MS)
84
+ }, HOLD_MS)
85
+ }
86
+
87
+ const startTimer = setTimeout(() => {
88
+ setPhase("idle")
89
+ scheduleNext()
90
+ }, ANIMATION_MS)
91
+
92
+ return () => {
93
+ clearTimeout(startTimer)
94
+ clearTimeout(holdTimer)
95
+ clearTimeout(exitTimer)
96
+ clearTimeout(enterTimer)
97
+ }
98
+ }, [])
99
+
100
+ const message = PROMO_MESSAGES[activeIndex]
101
+ const Icon = message.icon
102
+
103
+ return (
104
+ <div className="promo-bar__viewport" aria-live="polite">
105
+ <div
106
+ key={activeIndex}
107
+ className={`promo-bar__slide promo-bar__slide--${phase}`}
108
+ >
109
+ <span className="promo-bar__icon" aria-hidden>
110
+ <Icon size={14} strokeWidth={1.65} />
111
+ </span>
112
+ <span className="promo-bar__text">
113
+ <PromoMessageItem {...message} />
114
+ </span>
115
+ </div>
116
+ </div>
117
+ )
118
+ }
@@ -0,0 +1,12 @@
1
+ "use client"
2
+
3
+ import OrderDetailsTemplate from "@modules/order/templates/order-details-template"
4
+ import type { HttpTypes } from "@medusajs/types"
5
+
6
+ type OrderDetailsProps = {
7
+ order: HttpTypes.StoreOrder
8
+ }
9
+
10
+ export default function OrderDetails({ order }: OrderDetailsProps) {
11
+ return <OrderDetailsTemplate order={order} />
12
+ }