@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.
- package/.pnp.cjs +16484 -0
- package/.pnp.loader.mjs +2126 -0
- package/.yarn/install-state.gz +0 -0
- package/.yarn/releases/yarn-4.12.0.cjs +942 -0
- package/.yarnrc.yml +1 -0
- package/README.md +60 -41
- package/cypress/fixtures/example.json +5 -0
- package/cypress/support/commands.js +25 -0
- package/cypress/support/component-index.html +15 -0
- package/cypress/support/component.js +26 -0
- package/cypress.config.js +10 -0
- package/eslint.config.js +32 -0
- package/index.html +13 -0
- package/package.json +91 -67
- package/playground/index.html +14 -0
- package/playground/main.jsx +36 -0
- package/public/vite.svg +1 -0
- package/src/App.css +0 -0
- package/src/App.jsx +65 -0
- package/src/components/CollapsibleStoreHours/index.jsx +269 -0
- package/src/components/HoursList/index.jsx +225 -0
- package/src/components/LeadForm/index.jsx +241 -0
- package/src/components/MessageDialog/index.jsx +169 -0
- package/src/components/QuoteForm/index.jsx +82 -0
- package/src/components/QuoteFormSearch/index.jsx +276 -0
- package/src/components/QuoteFormStoreList/index.jsx +65 -0
- package/src/components/QuoteFormStoreListItem/index.jsx +134 -0
- package/src/components/QuoteLeadForm/index.jsx +16 -0
- package/src/components/QuoteMap/index.jsx +96 -0
- package/src/components/QuoteMapMarker/index.jsx +56 -0
- package/src/components/StaticMap/index.jsx +24 -0
- package/src/components/Store/index.jsx +44 -0
- package/src/components/StoreContact/index.jsx +96 -0
- package/src/components/StoreInfo/index.jsx +50 -0
- package/src/components/StoreList/index.jsx +59 -0
- package/src/components/StoreListItem/index.jsx +99 -0
- package/src/components/StoreListItem/indexStoreListItem.cy.jsx +30 -0
- package/src/components/StoreListNoneFound/index.jsx +16 -0
- package/src/components/StoreLocator/index.jsx +43 -0
- package/src/components/StoreLocatorMap/index.jsx +93 -0
- package/src/components/StoreLocatorMapMarker/index.jsx +55 -0
- package/src/components/StoreLocatorMessageDialog/index.jsx +20 -0
- package/src/components/StoreLocatorSearch/index.jsx +316 -0
- package/src/components/StoreMap/index.jsx +30 -0
- package/src/components/StoreMeta/index.jsx +7 -0
- package/src/components/StoreProducts/index.jsx +112 -0
- package/src/components/ui/Badge/index.jsx +46 -0
- package/src/components/ui/Button/index.jsx +56 -0
- package/src/components/ui/Button/indexButton.cy.jsx +9 -0
- package/src/components/ui/Card/index.jsx +90 -0
- package/src/components/ui/Input/index.jsx +19 -0
- package/src/components/ui/Input/indexInput.cy.jsx +9 -0
- package/src/components/ui/LoadingPuff/index.jsx +10 -0
- package/src/components/ui/Panel/index.jsx +23 -0
- package/src/components/ui/PhoneNumberInput/index.jsx +17 -0
- package/src/contexts/quote-form.jsx +94 -0
- package/src/contexts/store-locator.jsx +83 -0
- package/src/contexts/store.jsx +59 -0
- package/src/contexts/translations.jsx +11 -0
- package/src/dist.css +229 -0
- package/src/entries/QuoteForm.js +2 -0
- package/src/entries/Store.js +2 -0
- package/src/entries/StoreLocator.js +2 -0
- package/src/entries/StoreLocatorProvider.js +2 -0
- package/src/entries/styles.js +2 -0
- package/src/entries/useStoreLocator.js +2 -0
- package/src/i18n/defaultResources.js +19 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/mergeResources.js +22 -0
- package/src/index.css +214 -0
- package/src/lib/addressComponentsToAddress.js +43 -0
- package/src/lib/productSchema.js +6 -0
- package/src/lib/useGeolocation.js +266 -0
- package/src/lib/useHours.js +205 -0
- package/src/lib/usePlacesAutocomplete.js +288 -0
- package/src/lib/useProductAvailability.js +38 -0
- package/src/lib/useRudderAnalytics.js +50 -0
- package/src/lib/useSearchResults.js +102 -0
- package/src/lib/useStoreLocatorConfig.js +50 -0
- package/src/lib/utils/cn.js +6 -0
- package/src/lib/utils/measure.js +31 -0
- package/src/locales/en/default.json +58 -0
- package/src/locales/es/default.json +58 -0
- package/src/locales/fr/default.json +58 -0
- package/src/locales/it/default.json +58 -0
- package/src/main.jsx +10 -0
- package/vite.config.js +60 -53
- package/dist/CartForm.js +0 -617
- package/dist/Container-CU_WrBOi.js +0 -22
- package/dist/Modal-DTBKy_6d.js +0 -863
- package/dist/ProductForm.js +0 -343
- package/dist/Retailer.js +0 -3637
- package/dist/StoreLocator.js +0 -797
- package/dist/addressComponentsToAddress-DCL-K8mn.js +0 -1932
- package/dist/browser-ponyfill-DcK7_cJB.js +0 -339
- package/dist/globals-B8-hYoIU.js +0 -8518
- package/dist/index-CqSfhXDd.js +0 -137
- package/dist/index-FM02Uq_P.js +0 -100
- package/dist/style.css +0 -1
|
@@ -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,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
|
+
}
|