@colabcommerce/elements 0.0.4 → 0.9.1

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 (99) hide show
  1. package/.pnp.cjs +16484 -0
  2. package/.pnp.loader.mjs +2126 -0
  3. package/.yarn/install-state.gz +0 -0
  4. package/.yarn/releases/yarn-4.12.0.cjs +942 -0
  5. package/.yarnrc.yml +1 -0
  6. package/README.md +60 -41
  7. package/cypress/fixtures/example.json +5 -0
  8. package/cypress/support/commands.js +25 -0
  9. package/cypress/support/component-index.html +15 -0
  10. package/cypress/support/component.js +26 -0
  11. package/cypress.config.js +10 -0
  12. package/eslint.config.js +32 -0
  13. package/index.html +13 -0
  14. package/package.json +91 -67
  15. package/playground/index.html +14 -0
  16. package/playground/main.jsx +36 -0
  17. package/public/vite.svg +1 -0
  18. package/src/App.css +0 -0
  19. package/src/App.jsx +65 -0
  20. package/src/components/CollapsibleStoreHours/index.jsx +269 -0
  21. package/src/components/HoursList/index.jsx +225 -0
  22. package/src/components/LeadForm/index.jsx +241 -0
  23. package/src/components/MessageDialog/index.jsx +169 -0
  24. package/src/components/QuoteForm/index.jsx +82 -0
  25. package/src/components/QuoteFormSearch/index.jsx +276 -0
  26. package/src/components/QuoteFormStoreList/index.jsx +65 -0
  27. package/src/components/QuoteFormStoreListItem/index.jsx +134 -0
  28. package/src/components/QuoteLeadForm/index.jsx +16 -0
  29. package/src/components/QuoteMap/index.jsx +96 -0
  30. package/src/components/QuoteMapMarker/index.jsx +56 -0
  31. package/src/components/StaticMap/index.jsx +24 -0
  32. package/src/components/Store/index.jsx +44 -0
  33. package/src/components/StoreContact/index.jsx +96 -0
  34. package/src/components/StoreInfo/index.jsx +50 -0
  35. package/src/components/StoreList/index.jsx +59 -0
  36. package/src/components/StoreListItem/index.jsx +99 -0
  37. package/src/components/StoreListItem/indexStoreListItem.cy.jsx +30 -0
  38. package/src/components/StoreListNoneFound/index.jsx +16 -0
  39. package/src/components/StoreLocator/index.jsx +43 -0
  40. package/src/components/StoreLocatorMap/index.jsx +93 -0
  41. package/src/components/StoreLocatorMapMarker/index.jsx +55 -0
  42. package/src/components/StoreLocatorMessageDialog/index.jsx +20 -0
  43. package/src/components/StoreLocatorSearch/index.jsx +316 -0
  44. package/src/components/StoreMap/index.jsx +30 -0
  45. package/src/components/StoreMeta/index.jsx +7 -0
  46. package/src/components/StoreProducts/index.jsx +112 -0
  47. package/src/components/ui/Badge/index.jsx +46 -0
  48. package/src/components/ui/Button/index.jsx +56 -0
  49. package/src/components/ui/Button/indexButton.cy.jsx +9 -0
  50. package/src/components/ui/Card/index.jsx +90 -0
  51. package/src/components/ui/Input/index.jsx +19 -0
  52. package/src/components/ui/Input/indexInput.cy.jsx +9 -0
  53. package/src/components/ui/LoadingPuff/index.jsx +10 -0
  54. package/src/components/ui/Panel/index.jsx +23 -0
  55. package/src/components/ui/PhoneNumberInput/index.jsx +17 -0
  56. package/src/contexts/quote-form.jsx +94 -0
  57. package/src/contexts/store-locator.jsx +83 -0
  58. package/src/contexts/store.jsx +59 -0
  59. package/src/contexts/translations.jsx +11 -0
  60. package/src/dist.css +229 -0
  61. package/src/entries/QuoteForm.js +2 -0
  62. package/src/entries/Store.js +2 -0
  63. package/src/entries/StoreLocator.js +2 -0
  64. package/src/entries/StoreLocatorProvider.js +2 -0
  65. package/src/entries/styles.js +2 -0
  66. package/src/entries/useStoreLocator.js +2 -0
  67. package/src/i18n/defaultResources.js +19 -0
  68. package/src/i18n/index.js +44 -0
  69. package/src/i18n/mergeResources.js +22 -0
  70. package/src/index.css +214 -0
  71. package/src/lib/addressComponentsToAddress.js +43 -0
  72. package/src/lib/productSchema.js +6 -0
  73. package/src/lib/useGeolocation.js +266 -0
  74. package/src/lib/useHours.js +205 -0
  75. package/src/lib/usePlacesAutocomplete.js +288 -0
  76. package/src/lib/useProductAvailability.js +38 -0
  77. package/src/lib/useRudderAnalytics.js +50 -0
  78. package/src/lib/useSearchResults.js +102 -0
  79. package/src/lib/useStoreLocatorConfig.js +50 -0
  80. package/src/lib/utils/cn.js +6 -0
  81. package/src/lib/utils/measure.js +31 -0
  82. package/src/locales/en/default.json +58 -0
  83. package/src/locales/es/default.json +58 -0
  84. package/src/locales/fr/default.json +58 -0
  85. package/src/locales/it/default.json +58 -0
  86. package/src/main.jsx +10 -0
  87. package/vite.config.js +60 -53
  88. package/dist/CartForm.js +0 -617
  89. package/dist/Container-CU_WrBOi.js +0 -22
  90. package/dist/Modal-DTBKy_6d.js +0 -863
  91. package/dist/ProductForm.js +0 -343
  92. package/dist/Retailer.js +0 -3637
  93. package/dist/StoreLocator.js +0 -797
  94. package/dist/addressComponentsToAddress-DCL-K8mn.js +0 -1932
  95. package/dist/browser-ponyfill-DcK7_cJB.js +0 -339
  96. package/dist/globals-B8-hYoIU.js +0 -8518
  97. package/dist/index-CqSfhXDd.js +0 -137
  98. package/dist/index-FM02Uq_P.js +0 -100
  99. package/dist/style.css +0 -1
@@ -0,0 +1,9 @@
1
+ import React from 'react'
2
+ import Input from './index'
3
+
4
+ describe('<Input />', () => {
5
+ it('renders', () => {
6
+ // see: https://on.cypress.io/mounting-react
7
+ cy.mount(<Input />)
8
+ })
9
+ })
@@ -0,0 +1,10 @@
1
+ const LoadingPuff = ({ size = 48 }) => {
2
+ return (
3
+ <div className="flex items-center justify-center" style={{ width: size, height: size }}>
4
+ {/* By Sam Herbert (@sherb), for everyone. More @ http://goo.gl/7AJzbL */}
5
+ <svg viewBox="0 0 44 44" xmlns="http://www.w3.org/2000/svg" stroke="#7033FFFF"><g fill="none" fill-rule="evenodd" stroke-width="2"><circle cx="22" cy="22" r="1"><animate attributeName="r" begin="0s" dur="1.8s" values="1; 20" calcMode="spline" keyTimes="0; 1" keySplines="0.165, 0.84, 0.44, 1" repeatCount="indefinite" /><animate attributeName="stroke-opacity" begin="0s" dur="1.8s" values="1; 0" calcMode="spline" keyTimes="0; 1" keySplines="0.3, 0.61, 0.355, 1" repeatCount="indefinite" /></circle><circle cx="22" cy="22" r="1"><animate attributeName="r" begin="-0.9s" dur="1.8s" values="1; 20" calcMode="spline" keyTimes="0; 1" keySplines="0.165, 0.84, 0.44, 1" repeatCount="indefinite" /><animate attributeName="stroke-opacity" begin="-0.9s" dur="1.8s" values="1; 0" calcMode="spline" keyTimes="0; 1" keySplines="0.3, 0.61, 0.355, 1" repeatCount="indefinite" /></circle></g></svg>
6
+ </div>
7
+ )
8
+ }
9
+
10
+ export default LoadingPuff
@@ -0,0 +1,23 @@
1
+ import cn from '@/lib/utils/cn'
2
+
3
+ function Panel({
4
+ active,
5
+ children,
6
+ className,
7
+ }) {
8
+ return (
9
+ <div
10
+ data-state={active ? "open" : "closed"}
11
+ className={cn(
12
+ "absolute inset-0",
13
+ "transition duration-300 ease-out will-change-transform",
14
+ "data-[state=closed]:pointer-events-none",
15
+ className
16
+ )}
17
+ >
18
+ <div className="w-full h-[100%]">{children}</div>
19
+ </div>
20
+ )
21
+ }
22
+
23
+ export default Panel
@@ -0,0 +1,17 @@
1
+ import { forwardRef } from 'react'
2
+ import Input from '@/components/ui/Input'
3
+
4
+ export const PhoneNumberInput = forwardRef(
5
+ ({ value, onChange, onBlur, ...props }, ref) => (
6
+ <Input
7
+ {...props}
8
+ ref={ref}
9
+ value={value ?? ''}
10
+ onChange={(e) => onChange(e.target.value)}
11
+ onBlur={onBlur}
12
+ className="pl-24 h-12"
13
+ />
14
+ )
15
+ )
16
+
17
+ PhoneNumberInput.displayName = 'PhoneNumberInput'
@@ -0,0 +1,94 @@
1
+ /*
2
+ * Copyright (c) 2025 Colab Commerce <https://colabcommerce.com>
3
+ * All rights reserved.
4
+ *
5
+ */
6
+
7
+ import { useState, useEffect, useContext, createContext } from 'react'
8
+ import useSearchResults from '@/lib/useSearchResults'
9
+ import useGeolocation from '@/lib/useGeolocation'
10
+ import useStoreLocatorConfig from '@/lib/useStoreLocatorConfig'
11
+
12
+ const QuoteFormContext = createContext()
13
+
14
+ export const QuoteFormProvider = ({
15
+ organizationId,
16
+ locale,
17
+ baseUrl = '/retailers',
18
+ products = [],
19
+ filterBy = { stocked: ['sku'], available: ['sku'] },
20
+ children }) => {
21
+
22
+ const [stepIndex, setStepIndex] = useState(0)
23
+ const [selectedStoreId, setSelectedStoreId] = useState(null)
24
+ const [focusedStoreId, setFocusedStoreId] = useState(null)
25
+ const [messageStoreId, setMessageStoreId] = useState(null)
26
+ const [messageDialogOpen, setMessageDialogOpen] = useState(false)
27
+ const [searchRadius, setSearchRadius] = useState('300mi')
28
+ const [searchLocation, setSearchLocation] = useState(null)
29
+ const [useLocationType, setUseLocationType] = useState('geolocation') // 'geolocation' or 'search'
30
+
31
+ const { location: geoLocation } = useGeolocation({ autoGeoLocate: false })
32
+
33
+ // Determine which location to use based on useLocationType. If 'geolocation', use geoLocation.
34
+ // If 'search', use searchLocation if available, otherwise fallback to geoLocation.
35
+ const location = useLocationType === 'geolocation' ? geoLocation : (searchLocation || geoLocation)
36
+
37
+ const { stores, meta, isLoading: searchIsLoading, error: searchError } = useSearchResults({
38
+ organizationId,
39
+ location,
40
+ page: 1,
41
+ radius: searchRadius,
42
+ pageSize: 10,
43
+ })
44
+
45
+ const { config, isLoading: configIsLoading, error: configError } = useStoreLocatorConfig(organizationId)
46
+
47
+ const sharedState = {
48
+ stepIndex,
49
+ setStepIndex,
50
+ selectedStoreId,
51
+ setSelectedStoreId,
52
+ focusedStoreId,
53
+ setFocusedStoreId,
54
+ organizationId,
55
+ location,
56
+ locale,
57
+ stores,
58
+ meta,
59
+ searchIsLoading,
60
+ searchError,
61
+ config,
62
+ configIsLoading,
63
+ configError,
64
+ messageDialogOpen,
65
+ setMessageDialogOpen,
66
+ messageStoreId,
67
+ setMessageStoreId,
68
+ baseUrl,
69
+ searchRadius,
70
+ setSearchRadius,
71
+ searchLocation,
72
+ setSearchLocation,
73
+ useLocationType,
74
+ setUseLocationType,
75
+ products,
76
+ filterBy
77
+ }
78
+
79
+ return (
80
+ <QuoteFormContext.Provider value={sharedState}>
81
+ {children}
82
+ </QuoteFormContext.Provider>
83
+ )
84
+
85
+ }
86
+
87
+ export const useQuoteForm = () => {
88
+ // Log a warning if used outside of a QuoteFormProvider
89
+ const context = useContext(QuoteFormContext)
90
+ if (!context) {
91
+ console.warn('useQuoteForm must be used within a QuoteFormProvider')
92
+ }
93
+ return context
94
+ }
@@ -0,0 +1,83 @@
1
+ /*
2
+ * Copyright (c) 2025 Colab Commerce <https://colabcommerce.com>
3
+ * All rights reserved.
4
+ *
5
+ */
6
+
7
+ import { useState, useEffect, useContext, createContext } from 'react'
8
+ import useSearchResults from '@/lib/useSearchResults'
9
+ import useGeolocation from '@/lib/useGeolocation'
10
+ import useStoreLocatorConfig from '@/lib/useStoreLocatorConfig'
11
+
12
+ const StoreLocatorContext = createContext()
13
+
14
+ export const StoreLocatorProvider = ({ organizationId, locale, baseUrl = '/retailers', children }) => {
15
+
16
+ const [selectedStoreId, setSelectedStoreId] = useState(null)
17
+ const [focusedStoreId, setFocusedStoreId] = useState(null)
18
+ const [messageStoreId, setMessageStoreId] = useState(null)
19
+ const [messageDialogOpen, setMessageDialogOpen] = useState(false)
20
+ const [searchRadius, setSearchRadius] = useState('25mi')
21
+ const [searchLocation, setSearchLocation] = useState(null)
22
+ const [useLocationType, setUseLocationType] = useState('geolocation') // 'geolocation' or 'search'
23
+
24
+ const { location: geoLocation } = useGeolocation()
25
+
26
+ // Determine which location to use based on useLocationType. If 'geolocation', use geoLocation.
27
+ // If 'search', use searchLocation if available, otherwise fallback to geoLocation.
28
+ const location = useLocationType === 'geolocation' ? geoLocation : (searchLocation || geoLocation)
29
+
30
+ const { stores, meta, isLoading: searchIsLoading, error: searchError } = useSearchResults({
31
+ organizationId,
32
+ location,
33
+ page: 1,
34
+ radius: searchRadius,
35
+ pageSize: 10,
36
+ })
37
+
38
+ const { config, isLoading: configIsLoading, error: configError } = useStoreLocatorConfig(organizationId)
39
+
40
+ const sharedState = {
41
+ selectedStoreId,
42
+ setSelectedStoreId,
43
+ focusedStoreId,
44
+ setFocusedStoreId,
45
+ organizationId,
46
+ location,
47
+ locale,
48
+ stores,
49
+ meta,
50
+ searchIsLoading,
51
+ searchError,
52
+ config,
53
+ configIsLoading,
54
+ configError,
55
+ messageDialogOpen,
56
+ setMessageDialogOpen,
57
+ messageStoreId,
58
+ setMessageStoreId,
59
+ baseUrl,
60
+ searchRadius,
61
+ setSearchRadius,
62
+ searchLocation,
63
+ setSearchLocation,
64
+ useLocationType,
65
+ setUseLocationType,
66
+ }
67
+
68
+ return (
69
+ <StoreLocatorContext.Provider value={sharedState}>
70
+ {children}
71
+ </StoreLocatorContext.Provider>
72
+ )
73
+
74
+ }
75
+
76
+ export const useStoreLocator = () => {
77
+ // Log a warning if used outside of a StoreLocatorProvider
78
+ const context = useContext(StoreLocatorContext)
79
+ if (!context) {
80
+ console.warn('useStoreLocator must be used within a StoreLocatorProvider')
81
+ }
82
+ return context
83
+ }
@@ -0,0 +1,59 @@
1
+ /*
2
+ * Copyright (c) 2025 Colab Commerce <https://colabcommerce.com>
3
+ * All rights reserved.
4
+ *
5
+ */
6
+
7
+ import { useState, useEffect, useContext, createContext } from 'react'
8
+
9
+ const StoreContext = createContext()
10
+
11
+ export const StoreProvider = ({ organizationId, id, locale, baseUrl = '/retailers', initialData = null, children }) => {
12
+
13
+ const [store, setStore] = useState(initialData?.company_retailer_location || null)
14
+
15
+ useEffect(() => {
16
+ async function fetchStore() {
17
+ if (!id) {
18
+ setStore(null)
19
+ return
20
+ }
21
+
22
+ try {
23
+ const response = await fetch(`https://api.colabcommerce.com/widget_api/company_retailer_locations/${id}`)
24
+ if (!response.ok) {
25
+ throw new Error(`Error fetching retailer: ${response.statusText}`)
26
+ }
27
+ const data = await response.json()
28
+ setStore(data?.company_retailer_location || null)
29
+ } catch (error) {
30
+ console.error('Failed to fetch retailer:', error)
31
+ setStore(null)
32
+ }
33
+ }
34
+
35
+ fetchStore()
36
+ }, [organizationId, id, baseUrl])
37
+
38
+ const sharedState = {
39
+ organizationId,
40
+ id,
41
+ locale,
42
+ baseUrl,
43
+ store
44
+ }
45
+
46
+ return (
47
+ <StoreContext.Provider value={sharedState}>
48
+ {children}
49
+ </StoreContext.Provider>
50
+ )
51
+ }
52
+
53
+ export const useStore = () => {
54
+ const context = useContext(StoreContext)
55
+ if (context === undefined) {
56
+ throw new Error('useStore must be used within a StoreProvider')
57
+ }
58
+ return context
59
+ }
@@ -0,0 +1,11 @@
1
+ import { useMemo } from "react"
2
+ import { I18nextProvider } from "react-i18next"
3
+ import { initLibraryI18n } from "@/i18n"
4
+
5
+ export default function LibraryI18nProvider({
6
+ children,
7
+ options = {},
8
+ }) {
9
+ const memoizedI18n = useMemo(() => initLibraryI18n(options), [options])
10
+ return <I18nextProvider i18n={memoizedI18n}>{children}</I18nextProvider>
11
+ }
package/src/dist.css ADDED
@@ -0,0 +1,229 @@
1
+ /* This works for build, not dev. */
2
+ .cc {
3
+ @import "tailwindcss";
4
+ @import "react-phone-number-input/style.css";
5
+ }
6
+
7
+ @import "tw-animate-css";
8
+
9
+ /* Keep this if you want to continue using `dark:` variants on descendants */
10
+ @custom-variant dark (&:is(.cc.dark *));
11
+
12
+ .cc {
13
+
14
+ --background: #fdfdfd;
15
+ --foreground: #000000;
16
+ --color-black: #000000;
17
+ --color-white: #ffffff;
18
+ --card: #fdfdfd;
19
+ --card-foreground: #000000;
20
+ --popover: #fcfcfc;
21
+ --popover-foreground: #000000;
22
+ --primary: #7033ff;
23
+ --primary-foreground: #ffffff;
24
+ --secondary: #edf0f4;
25
+ --secondary-foreground: #080808;
26
+ --muted: #f5f5f5;
27
+ --muted-foreground: #525252;
28
+ --accent: #e2ebff;
29
+ --accent-foreground: #1e69dc;
30
+ --destructive: #e54b4f;
31
+ --destructive-foreground: #ffffff;
32
+ --border: #e7e7ee;
33
+ --input: #ebebeb;
34
+ --ring: #000000;
35
+ --chart-1: #4ac885;
36
+ --chart-2: #7033ff;
37
+ --chart-3: #fd822b;
38
+ --chart-4: #3276e4;
39
+ --chart-5: #747474;
40
+ --radius: 1.4rem;
41
+ --sidebar: #f5f8fb;
42
+ --sidebar-foreground: #000000;
43
+ --sidebar-primary: #000000;
44
+ --sidebar-primary-foreground: #ffffff;
45
+ --sidebar-accent: #ebebeb;
46
+ --sidebar-accent-foreground: #000000;
47
+ --sidebar-border: #ebebeb;
48
+ --sidebar-ring: #000000;
49
+ --shadow-x: 0px;
50
+ --shadow-y: 2px;
51
+ --shadow-blur: 3px;
52
+ --shadow-spread: 0px;
53
+ --shadow-opacity: 0.2;
54
+ --shadow-color: #000000;
55
+
56
+ /* PhoneInput Variables, need to be defined here as the scoping for the styles
57
+ doesn't work as expected otherwise */
58
+ --PhoneInput-color--focus: #03b2cb;
59
+ --PhoneInputInternationalIconPhone-opacity: .8;
60
+ --PhoneInputInternationalIconGlobe-opacity: .65;
61
+ --PhoneInputCountrySelect-marginRight: .35em;
62
+ --PhoneInputCountrySelectArrow-width: .3em;
63
+ --PhoneInputCountrySelectArrow-marginLeft: var(--PhoneInputCountrySelect-marginRight);
64
+ --PhoneInputCountrySelectArrow-borderWidth: 1px;
65
+ --PhoneInputCountrySelectArrow-opacity: .45;
66
+ --PhoneInputCountrySelectArrow-color: currentColor;
67
+ --PhoneInputCountrySelectArrow-color--focus: var(--PhoneInput-color--focus);
68
+ --PhoneInputCountrySelectArrow-transform: rotate(45deg);
69
+ --PhoneInputCountryFlag-aspectRatio: 1.5;
70
+ --PhoneInputCountryFlag-height: 1em;
71
+ --PhoneInputCountryFlag-borderWidth: 1px;
72
+ --PhoneInputCountryFlag-borderColor: #00000080;
73
+ --PhoneInputCountryFlag-borderColor--focus: var(--PhoneInput-color--focus);
74
+ --PhoneInputCountryFlag-backgroundColor--loading: #0000001a
75
+
76
+ /* ===== Scrollbar CSS ===== */
77
+ /* Firefox */
78
+ * {
79
+ scrollbar-width: thin;
80
+ scrollbar-color: #858585 #ffffff;
81
+ }
82
+
83
+ /* Chrome, Edge, and Safari */
84
+ *::-webkit-scrollbar {
85
+ width: 8px;
86
+ }
87
+
88
+ *::-webkit-scrollbar-track {
89
+ background: #ffffff;
90
+ }
91
+
92
+ *::-webkit-scrollbar-thumb {
93
+ background-color: #858585;
94
+ border-radius: 4px;
95
+ border: 3px solid #ffffff;
96
+ }
97
+
98
+ /* Phone Input Override */
99
+ .PhoneInputCountry {
100
+ @apply absolute left-8 top-0 h-12 flex items-center gap-2 px-3 bg-transparent;
101
+ }
102
+ }
103
+
104
+ .cc.dark {
105
+ --background: #1a1b1e;
106
+ --foreground: #f0f0f0;
107
+ --card: #222327;
108
+ --card-foreground: #f0f0f0;
109
+ --popover: #222327;
110
+ --popover-foreground: #f0f0f0;
111
+ --primary: #8c5cff;
112
+ --primary-foreground: #ffffff;
113
+ --secondary: #2a2c33;
114
+ --secondary-foreground: #f0f0f0;
115
+ --muted: #2a2c33;
116
+ --muted-foreground: #a0a0a0;
117
+ --accent: #1e293b;
118
+ --accent-foreground: #79c0ff;
119
+ --destructive: #f87171;
120
+ --destructive-foreground: #ffffff;
121
+ --border: #33353a;
122
+ --input: #33353a;
123
+ --ring: #8c5cff;
124
+ --chart-1: #4ade80;
125
+ --chart-2: #8c5cff;
126
+ --chart-3: #fca5a5;
127
+ --chart-4: #5993f4;
128
+ --chart-5: #a0a0a0;
129
+ --sidebar: #161618;
130
+ --sidebar-foreground: #f0f0f0;
131
+ --sidebar-primary: #8c5cff;
132
+ --sidebar-primary-foreground: #ffffff;
133
+ --sidebar-accent: #2a2c33;
134
+ --sidebar-accent-foreground: #8c5cff;
135
+ --sidebar-border: #33353a;
136
+ --sidebar-ring: #8c5cff;
137
+ --shadow-x: 0px;
138
+ --shadow-y: 2px;
139
+ --shadow-blur: 3px;
140
+ --shadow-spread: 0px;
141
+ --shadow-opacity: 0.2;
142
+ --shadow-color: #000000;
143
+ }
144
+
145
+ .cc {
146
+ @theme inline {
147
+ /* core colors */
148
+ --color-background: var(--background);
149
+ --color-foreground: var(--foreground);
150
+
151
+ --color-card: var(--card);
152
+ --color-card-foreground: var(--card-foreground);
153
+
154
+ --color-popover: var(--popover);
155
+ --color-popover-foreground: var(--popover-foreground);
156
+
157
+ --color-primary: var(--primary);
158
+ --color-primary-foreground: var(--primary-foreground);
159
+
160
+ --color-secondary: var(--secondary);
161
+ --color-secondary-foreground: var(--secondary-foreground);
162
+
163
+ --color-muted: var(--muted);
164
+ --color-muted-foreground: var(--muted-foreground);
165
+
166
+ --color-accent: var(--accent);
167
+ --color-accent-foreground: var(--accent-foreground);
168
+
169
+ --color-destructive: var(--destructive);
170
+ --color-destructive-foreground: var(--destructive-foreground);
171
+
172
+ /* borders & inputs */
173
+ --color-border: var(--border);
174
+ --color-input: var(--input);
175
+ --color-ring: var(--ring);
176
+
177
+ /* charts */
178
+ --color-chart-1: var(--chart-1);
179
+ --color-chart-2: var(--chart-2);
180
+ --color-chart-3: var(--chart-3);
181
+ --color-chart-4: var(--chart-4);
182
+ --color-chart-5: var(--chart-5);
183
+
184
+ /* sidebar */
185
+ --color-sidebar: var(--sidebar);
186
+ --color-sidebar-foreground: var(--sidebar-foreground);
187
+ --color-sidebar-primary: var(--sidebar-primary);
188
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
189
+ --color-sidebar-accent: var(--sidebar-accent);
190
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
191
+ --color-sidebar-border: var(--sidebar-border);
192
+ --color-sidebar-ring: var(--sidebar-ring);
193
+
194
+ /* radius */
195
+ --radius-sm: calc(var(--radius) - 4px);
196
+ --radius-md: calc(var(--radius) - 2px);
197
+ --radius-lg: var(--radius);
198
+ --radius-xl: calc(var(--radius) + 4px);
199
+
200
+ /* shadows (optional but nice) */
201
+ --shadow-2xs: var(--shadow-x) var(--shadow-y) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
202
+
203
+ --shadow-xs: var(--shadow-x) var(--shadow-y) var(--shadow-blur) var(--shadow-spread) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
204
+
205
+ --shadow-sm: var(--shadow-x) var(--shadow-y) calc(var(--shadow-blur) * 1.5) var(--shadow-spread) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
206
+
207
+ --shadow-md: calc(var(--shadow-x) * 2) calc(var(--shadow-y) * 2) calc(var(--shadow-blur) * 3) calc(var(--shadow-spread) - 1px) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
208
+
209
+ --shadow-lg: calc(var(--shadow-x) * 4) calc(var(--shadow-y) * 4) calc(var(--shadow-blur) * 5) calc(var(--shadow-spread) - 3px) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
210
+
211
+ --shadow-xl: calc(var(--shadow-x) * 6) calc(var(--shadow-y) * 6) calc(var(--shadow-blur) * 7) calc(var(--shadow-spread) - 5px) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 100%), transparent);
212
+
213
+ --shadow-2xl: calc(var(--shadow-x) * 8) calc(var(--shadow-y) * 8) calc(max(0px, var(--shadow-blur) * 9 - 8px)) calc(var(--shadow-spread) - 12px) color-mix(in srgb, var(--shadow-color) calc(var(--shadow-opacity) * 250%), transparent);
214
+
215
+ --shadow: var(--shadow-md);
216
+ --color-shadow-color: var(--shadow-color);
217
+ }
218
+ }
219
+
220
+ /* Scope base styles to the wrapper (no global leakage) */
221
+ @layer base {
222
+ .cc {
223
+ @apply bg-background text-foreground;
224
+ }
225
+
226
+ .cc * {
227
+ @apply border-border outline-ring/50;
228
+ }
229
+ }
@@ -0,0 +1,2 @@
1
+ import QuoteForm from "@/components/QuoteForm"
2
+ export default QuoteForm
@@ -0,0 +1,2 @@
1
+ import Store from "@/components/Store"
2
+ export default Store
@@ -0,0 +1,2 @@
1
+ import StoreLocator from "@/components/StoreLocator"
2
+ export default StoreLocator
@@ -0,0 +1,2 @@
1
+ export { StoreLocatorProvider as default } from "@/contexts/store-locator";
2
+ export { StoreLocatorProvider } from "@/contexts/store-locator";
@@ -0,0 +1,2 @@
1
+ import "@/dist.css"
2
+ export { }
@@ -0,0 +1,2 @@
1
+ export { useStoreLocator as default } from "@/contexts/store-locator";
2
+ export { useStoreLocator } from "@/contexts/store-locator";
@@ -0,0 +1,19 @@
1
+ const modules = import.meta.glob("../locales/*/*.json", { eager: true })
2
+
3
+ export function getDefaultResources() {
4
+ const resources = {}
5
+
6
+ for (const path in modules) {
7
+ const match = path.match(/\.\.\/locales\/([^/]+)\/([^/]+)\.json$/)
8
+ if (!match) continue
9
+
10
+ const [, lng, ns] = match
11
+ const mod = modules[path]
12
+ const json = mod.default ?? mod
13
+
14
+ resources[lng] ||= {}
15
+ resources[lng][ns] = json
16
+ }
17
+
18
+ return resources
19
+ }
@@ -0,0 +1,44 @@
1
+ import i18n from "i18next"
2
+ import { initReactI18next } from "react-i18next"
3
+ import { getDefaultResources } from "./defaultResources"
4
+ import { mergeResources } from "./mergeResources"
5
+
6
+ export function initLibraryI18n(opts = {}) {
7
+ const defaults = opts.disableDefaults ? {} : getDefaultResources()
8
+ const resources = mergeResources(defaults, opts.resources)
9
+
10
+ if (!i18n.isInitialized) {
11
+ i18n
12
+ .use(initReactI18next)
13
+ .init({
14
+ resources,
15
+ lng: opts.lng,
16
+ fallbackLng: opts.fallbackLng ?? "en",
17
+ ns: opts.ns ?? Object.keys(resources[opts.fallbackLng ?? "en"] || { common: true }),
18
+ defaultNS: opts.defaultNS ?? "common",
19
+ interpolation: { escapeValue: false },
20
+ ...opts.i18next,
21
+ })
22
+ } else {
23
+ // If already initialized, just add/overwrite resources safely
24
+ for (const [lng, namespaces] of Object.entries(resources)) {
25
+ for (const [ns, bundle] of Object.entries(namespaces)) {
26
+ i18n.addResourceBundle(lng, ns, bundle, true, true) // deep merge + overwrite
27
+ }
28
+ }
29
+ if (opts.lng) i18n.changeLanguage(opts.lng)
30
+ }
31
+
32
+ return i18n
33
+ }
34
+
35
+ /**
36
+ * Optional helper to add/override resources after init.
37
+ */
38
+ export function addLibraryResources(resources) {
39
+ for (const [lng, namespaces] of Object.entries(resources)) {
40
+ for (const [ns, bundle] of Object.entries(namespaces)) {
41
+ i18n.addResourceBundle(lng, ns, bundle, true, true)
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,22 @@
1
+ function isPlainObject(v) {
2
+ return v && typeof v === "object" && !Array.isArray(v)
3
+ }
4
+
5
+ export function deepMerge(base, override) {
6
+ if (!isPlainObject(base) || !isPlainObject(override)) return override ?? base
7
+
8
+ const out = { ...base }
9
+ for (const key of Object.keys(override)) {
10
+ out[key] =
11
+ key in out ? deepMerge(out[key], override[key]) : override[key]
12
+ }
13
+ return out
14
+ }
15
+
16
+ export function mergeResources(
17
+ defaults,
18
+ user
19
+ ) {
20
+ if (!user) return defaults
21
+ return deepMerge(defaults, user)
22
+ }