shru-design-system 0.1.8 → 0.1.10
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/dist/index.d.mts +12 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +86 -14
- package/dist/index.mjs +86 -15
- package/package.json +1 -1
- package/scripts/apply-theme-sync.js +19 -1
- package/scripts/applyThemeSync.js +22 -0
- package/scripts/init.js +194 -46
- package/scripts/themeUtils.js +22 -0
- package/scripts/tokens/palettes.json +26 -0
package/dist/index.d.mts
CHANGED
|
@@ -170,8 +170,19 @@ declare const THEME_CATEGORY_ORDER: readonly ["color", "typography", "shape", "d
|
|
|
170
170
|
/**
|
|
171
171
|
* Register a custom theme dynamically
|
|
172
172
|
* Allows users to add themes without modifying the base config
|
|
173
|
+
* Can be used for any category including custom
|
|
173
174
|
*/
|
|
174
175
|
declare function registerTheme(category: string, themeId: string, metadata: ThemeMetadata): Record<string, ThemeCategory>;
|
|
176
|
+
/**
|
|
177
|
+
* Register a theme from a token file
|
|
178
|
+
* Helper function to automatically register a theme by loading its file
|
|
179
|
+
* Users can call this after creating a theme file
|
|
180
|
+
*/
|
|
181
|
+
declare function registerThemeFromFile(category: string, themeId: string, filePath?: string): Promise<{
|
|
182
|
+
success: boolean;
|
|
183
|
+
themeId: string;
|
|
184
|
+
category: string;
|
|
185
|
+
}>;
|
|
175
186
|
/**
|
|
176
187
|
* Get merged theme categories (base + discovered)
|
|
177
188
|
*/
|
|
@@ -215,4 +226,4 @@ declare function getCurrentCSSVariables(): Record<string, string>;
|
|
|
215
226
|
*/
|
|
216
227
|
declare function applyThemeSync(): void;
|
|
217
228
|
|
|
218
|
-
export { Badge, Button, Checkbox, Label, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, THEME_CATEGORY_ORDER, TextInput, Textarea, type ThemeMetadata$1 as ThemeMetadata, type ThemeSelection, ThemeToggle, type ThemeToggleProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, enableDebugMode, getCurrentCSSVariables, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, useTheme, useThemeToggle };
|
|
229
|
+
export { Badge, Button, Checkbox, Label, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, THEME_CATEGORY_ORDER, TextInput, Textarea, type ThemeMetadata$1 as ThemeMetadata, type ThemeSelection, ThemeToggle, type ThemeToggleProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, enableDebugMode, getCurrentCSSVariables, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, registerThemeFromFile, useTheme, useThemeToggle };
|
package/dist/index.d.ts
CHANGED
|
@@ -170,8 +170,19 @@ declare const THEME_CATEGORY_ORDER: readonly ["color", "typography", "shape", "d
|
|
|
170
170
|
/**
|
|
171
171
|
* Register a custom theme dynamically
|
|
172
172
|
* Allows users to add themes without modifying the base config
|
|
173
|
+
* Can be used for any category including custom
|
|
173
174
|
*/
|
|
174
175
|
declare function registerTheme(category: string, themeId: string, metadata: ThemeMetadata): Record<string, ThemeCategory>;
|
|
176
|
+
/**
|
|
177
|
+
* Register a theme from a token file
|
|
178
|
+
* Helper function to automatically register a theme by loading its file
|
|
179
|
+
* Users can call this after creating a theme file
|
|
180
|
+
*/
|
|
181
|
+
declare function registerThemeFromFile(category: string, themeId: string, filePath?: string): Promise<{
|
|
182
|
+
success: boolean;
|
|
183
|
+
themeId: string;
|
|
184
|
+
category: string;
|
|
185
|
+
}>;
|
|
175
186
|
/**
|
|
176
187
|
* Get merged theme categories (base + discovered)
|
|
177
188
|
*/
|
|
@@ -215,4 +226,4 @@ declare function getCurrentCSSVariables(): Record<string, string>;
|
|
|
215
226
|
*/
|
|
216
227
|
declare function applyThemeSync(): void;
|
|
217
228
|
|
|
218
|
-
export { Badge, Button, Checkbox, Label, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, THEME_CATEGORY_ORDER, TextInput, Textarea, type ThemeMetadata$1 as ThemeMetadata, type ThemeSelection, ThemeToggle, type ThemeToggleProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, enableDebugMode, getCurrentCSSVariables, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, useTheme, useThemeToggle };
|
|
229
|
+
export { Badge, Button, Checkbox, Label, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, THEME_CATEGORY_ORDER, TextInput, Textarea, type ThemeMetadata$1 as ThemeMetadata, type ThemeSelection, ThemeToggle, type ThemeToggleProps, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, enableDebugMode, getCurrentCSSVariables, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, registerThemeFromFile, useTheme, useThemeToggle };
|
package/dist/index.js
CHANGED
|
@@ -55,9 +55,9 @@ var buttonVariantsConfig = {
|
|
|
55
55
|
link: "text-primary underline-offset-4 hover:underline"
|
|
56
56
|
},
|
|
57
57
|
size: {
|
|
58
|
-
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
59
|
-
sm: "h-8 rounded-md gap-
|
|
60
|
-
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
58
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3 gap-component-sm",
|
|
59
|
+
sm: "h-8 rounded-md gap-component-xs px-3 has-[>svg]:px-2.5",
|
|
60
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4 gap-component-md",
|
|
61
61
|
icon: "size-9",
|
|
62
62
|
"icon-sm": "size-8",
|
|
63
63
|
"icon-lg": "size-10"
|
|
@@ -69,7 +69,7 @@ var buttonVariantsConfig = {
|
|
|
69
69
|
}
|
|
70
70
|
};
|
|
71
71
|
var buttonVariants = classVarianceAuthority.cva(
|
|
72
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
72
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium font-sans transition-all duration-normal disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
73
73
|
buttonVariantsConfig
|
|
74
74
|
);
|
|
75
75
|
var Button = React__namespace.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
@@ -99,7 +99,7 @@ var badgeVariantsConfig = {
|
|
|
99
99
|
}
|
|
100
100
|
};
|
|
101
101
|
var badgeVariants = classVarianceAuthority.cva(
|
|
102
|
-
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-
|
|
102
|
+
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium font-sans w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-component-xs [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] duration-normal overflow-hidden",
|
|
103
103
|
badgeVariantsConfig
|
|
104
104
|
);
|
|
105
105
|
var Badge = React__namespace.forwardRef(({ className, variant, asChild = false, ...props }, ref) => {
|
|
@@ -124,7 +124,7 @@ var TextInput = React__namespace.forwardRef(
|
|
|
124
124
|
type,
|
|
125
125
|
"data-slot": "text-input",
|
|
126
126
|
className: cn(
|
|
127
|
-
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
127
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] duration-normal outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium font-sans disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
128
128
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
129
129
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
130
130
|
className
|
|
@@ -158,7 +158,7 @@ var Textarea = React__namespace.forwardRef(
|
|
|
158
158
|
ref,
|
|
159
159
|
"data-slot": "textarea",
|
|
160
160
|
className: cn(
|
|
161
|
-
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
161
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] duration-normal outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm font-sans",
|
|
162
162
|
className
|
|
163
163
|
),
|
|
164
164
|
...props
|
|
@@ -236,7 +236,7 @@ function ModalOverlay({
|
|
|
236
236
|
{
|
|
237
237
|
"data-slot": "modal-overlay",
|
|
238
238
|
className: cn(
|
|
239
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
239
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 duration-normal",
|
|
240
240
|
className
|
|
241
241
|
),
|
|
242
242
|
...props
|
|
@@ -256,7 +256,7 @@ function ModalContent({
|
|
|
256
256
|
{
|
|
257
257
|
"data-slot": "modal-content",
|
|
258
258
|
className: cn(
|
|
259
|
-
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-
|
|
259
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-component-md rounded-lg border p-component-lg shadow-lg duration-normal font-sans sm:max-w-lg",
|
|
260
260
|
className
|
|
261
261
|
),
|
|
262
262
|
...props,
|
|
@@ -283,7 +283,7 @@ function ModalHeader({ className, ...props }) {
|
|
|
283
283
|
"div",
|
|
284
284
|
{
|
|
285
285
|
"data-slot": "modal-header",
|
|
286
|
-
className: cn("flex flex-col gap-
|
|
286
|
+
className: cn("flex flex-col gap-component-sm text-center sm:text-left font-sans", className),
|
|
287
287
|
...props
|
|
288
288
|
}
|
|
289
289
|
);
|
|
@@ -294,7 +294,7 @@ function ModalFooter({ className, ...props }) {
|
|
|
294
294
|
{
|
|
295
295
|
"data-slot": "modal-footer",
|
|
296
296
|
className: cn(
|
|
297
|
-
"flex flex-col-reverse gap-
|
|
297
|
+
"flex flex-col-reverse gap-component-sm sm:flex-row sm:justify-end",
|
|
298
298
|
className
|
|
299
299
|
),
|
|
300
300
|
...props
|
|
@@ -653,7 +653,39 @@ async function discoverThemes() {
|
|
|
653
653
|
const tokensBase = typeof window !== "undefined" && window.__THEME_TOKENS_BASE__ ? window.__THEME_TOKENS_BASE__ : "/tokens";
|
|
654
654
|
const knownCategories = Object.keys(baseThemeCategories);
|
|
655
655
|
for (const category of knownCategories) {
|
|
656
|
-
const
|
|
656
|
+
const existingThemes = discovered[category]?.themes || {};
|
|
657
|
+
const themeIds = Object.keys(existingThemes);
|
|
658
|
+
const commonThemeNames = ["ocean", "forest", "sunset", "midnight", "pastel", "vibrant", "muted", "high-contrast"];
|
|
659
|
+
for (const themeName of commonThemeNames) {
|
|
660
|
+
if (themeIds.includes(themeName)) continue;
|
|
661
|
+
const themePath = `${tokensBase}/themes/${category}/${themeName}.json`;
|
|
662
|
+
try {
|
|
663
|
+
const response = await fetch(themePath);
|
|
664
|
+
if (response.ok && response.headers.get("content-type")?.includes("application/json")) {
|
|
665
|
+
const themeData = await response.json();
|
|
666
|
+
registerTheme(category, themeName, {
|
|
667
|
+
name: themeData.name || themeName.charAt(0).toUpperCase() + themeName.slice(1),
|
|
668
|
+
file: `${category}/${themeName}.json`,
|
|
669
|
+
icon: themeData.icon || "\u{1F3A8}",
|
|
670
|
+
description: themeData.description || `Custom ${category} theme: ${themeName}`
|
|
671
|
+
});
|
|
672
|
+
if (!discovered[category]) {
|
|
673
|
+
discovered[category] = {
|
|
674
|
+
name: category.charAt(0).toUpperCase() + category.slice(1),
|
|
675
|
+
order: baseThemeCategories[category]?.order || 99,
|
|
676
|
+
themes: {}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
discovered[category].themes[themeName] = {
|
|
680
|
+
name: themeData.name || themeName.charAt(0).toUpperCase() + themeName.slice(1),
|
|
681
|
+
file: `${category}/${themeName}.json`,
|
|
682
|
+
icon: themeData.icon || "\u{1F3A8}",
|
|
683
|
+
description: themeData.description || `Custom ${category} theme: ${themeName}`
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
}
|
|
657
689
|
}
|
|
658
690
|
discoveredThemesCache = discovered;
|
|
659
691
|
return discovered;
|
|
@@ -670,10 +702,14 @@ function registerTheme(category, themeId, metadata) {
|
|
|
670
702
|
}
|
|
671
703
|
const cache = discoveredThemesCache;
|
|
672
704
|
if (!cache[category]) {
|
|
705
|
+
let order = 99;
|
|
706
|
+
if (THEME_CATEGORY_ORDER.includes(category)) {
|
|
707
|
+
const index = THEME_CATEGORY_ORDER.indexOf(category);
|
|
708
|
+
order = (index + 1) * 10;
|
|
709
|
+
}
|
|
673
710
|
cache[category] = {
|
|
674
711
|
name: category.charAt(0).toUpperCase() + category.slice(1),
|
|
675
|
-
order
|
|
676
|
-
// Custom categories get high order
|
|
712
|
+
order,
|
|
677
713
|
themes: {}
|
|
678
714
|
};
|
|
679
715
|
}
|
|
@@ -685,6 +721,29 @@ function registerTheme(category, themeId, metadata) {
|
|
|
685
721
|
};
|
|
686
722
|
return cache;
|
|
687
723
|
}
|
|
724
|
+
async function registerThemeFromFile(category, themeId, filePath) {
|
|
725
|
+
const tokensBase = typeof window !== "undefined" && window.__THEME_TOKENS_BASE__ ? window.__THEME_TOKENS_BASE__ : "/tokens";
|
|
726
|
+
const path = filePath || `${tokensBase}/themes/${category}/${themeId}.json`;
|
|
727
|
+
try {
|
|
728
|
+
const response = await fetch(path);
|
|
729
|
+
if (!response.ok) {
|
|
730
|
+
throw new Error(`Failed to load theme file: ${response.statusText}`);
|
|
731
|
+
}
|
|
732
|
+
const themeData = await response.json();
|
|
733
|
+
registerTheme(category, themeId, {
|
|
734
|
+
name: themeData.name || themeId.charAt(0).toUpperCase() + themeId.slice(1),
|
|
735
|
+
file: filePath || `${category}/${themeId}.json`,
|
|
736
|
+
icon: themeData.icon || "\u{1F3A8}",
|
|
737
|
+
description: themeData.description || `Custom ${category} theme: ${themeId}`
|
|
738
|
+
});
|
|
739
|
+
return { success: true, themeId, category };
|
|
740
|
+
} catch (error) {
|
|
741
|
+
if (typeof window !== "undefined" && window.__DESIGN_SYSTEM_DEBUG__) {
|
|
742
|
+
console.error(`Failed to register theme from ${path}:`, error);
|
|
743
|
+
}
|
|
744
|
+
throw error;
|
|
745
|
+
}
|
|
746
|
+
}
|
|
688
747
|
async function getThemeCategories() {
|
|
689
748
|
return await discoverThemes();
|
|
690
749
|
}
|
|
@@ -863,6 +922,8 @@ function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false)
|
|
|
863
922
|
const enteringColor = key === "color" && prefix === "";
|
|
864
923
|
const enteringTypography = key === "typography" && prefix === "";
|
|
865
924
|
const enteringShape = key === "shape" && prefix === "";
|
|
925
|
+
const enteringAnimation = key === "animation" && prefix === "";
|
|
926
|
+
const enteringDensity = key === "spacing" && prefix === "";
|
|
866
927
|
const inColorContext = isColorContext || enteringColor;
|
|
867
928
|
if (enteringColor) {
|
|
868
929
|
flattenToCSS(value, "", result, true);
|
|
@@ -870,6 +931,10 @@ function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false)
|
|
|
870
931
|
flattenToCSS(value, "", result, false);
|
|
871
932
|
} else if (enteringShape) {
|
|
872
933
|
flattenToCSS(value, "", result, false);
|
|
934
|
+
} else if (enteringAnimation) {
|
|
935
|
+
flattenToCSS(value, "", result, false);
|
|
936
|
+
} else if (enteringDensity) {
|
|
937
|
+
flattenToCSS(value, "spacing", result, false);
|
|
873
938
|
} else if (inColorContext) {
|
|
874
939
|
flattenToCSS(value, "", result, true);
|
|
875
940
|
} else {
|
|
@@ -1585,6 +1650,8 @@ function flattenToCSSSync(tokens, prefix = "", result = {}, isColorContext = fal
|
|
|
1585
1650
|
const enteringColor = key === "color" && prefix === "";
|
|
1586
1651
|
const enteringTypography = key === "typography" && prefix === "";
|
|
1587
1652
|
const enteringShape = key === "shape" && prefix === "";
|
|
1653
|
+
const enteringAnimation = key === "animation" && prefix === "";
|
|
1654
|
+
const enteringDensity = key === "spacing" && prefix === "";
|
|
1588
1655
|
const inColorContext = isColorContext || enteringColor;
|
|
1589
1656
|
if (enteringColor) {
|
|
1590
1657
|
flattenToCSSSync(value, "", result, true);
|
|
@@ -1592,6 +1659,10 @@ function flattenToCSSSync(tokens, prefix = "", result = {}, isColorContext = fal
|
|
|
1592
1659
|
flattenToCSSSync(value, "", result, false);
|
|
1593
1660
|
} else if (enteringShape) {
|
|
1594
1661
|
flattenToCSSSync(value, "", result, false);
|
|
1662
|
+
} else if (enteringAnimation) {
|
|
1663
|
+
flattenToCSSSync(value, "", result, false);
|
|
1664
|
+
} else if (enteringDensity) {
|
|
1665
|
+
flattenToCSSSync(value, "spacing", result, false);
|
|
1595
1666
|
} else if (inColorContext) {
|
|
1596
1667
|
flattenToCSSSync(value, "", result, true);
|
|
1597
1668
|
} else {
|
|
@@ -1678,5 +1749,6 @@ exports.getThemeCategories = getThemeCategories;
|
|
|
1678
1749
|
exports.getThemeFilePath = getThemeFilePath;
|
|
1679
1750
|
exports.getThemesForCategory = getThemesForCategory;
|
|
1680
1751
|
exports.registerTheme = registerTheme;
|
|
1752
|
+
exports.registerThemeFromFile = registerThemeFromFile;
|
|
1681
1753
|
exports.useTheme = useTheme;
|
|
1682
1754
|
exports.useThemeToggle = useThemeToggle;
|
package/dist/index.mjs
CHANGED
|
@@ -28,9 +28,9 @@ var buttonVariantsConfig = {
|
|
|
28
28
|
link: "text-primary underline-offset-4 hover:underline"
|
|
29
29
|
},
|
|
30
30
|
size: {
|
|
31
|
-
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
32
|
-
sm: "h-8 rounded-md gap-
|
|
33
|
-
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
31
|
+
default: "h-9 px-4 py-2 has-[>svg]:px-3 gap-component-sm",
|
|
32
|
+
sm: "h-8 rounded-md gap-component-xs px-3 has-[>svg]:px-2.5",
|
|
33
|
+
lg: "h-10 rounded-md px-6 has-[>svg]:px-4 gap-component-md",
|
|
34
34
|
icon: "size-9",
|
|
35
35
|
"icon-sm": "size-8",
|
|
36
36
|
"icon-lg": "size-10"
|
|
@@ -42,7 +42,7 @@ var buttonVariantsConfig = {
|
|
|
42
42
|
}
|
|
43
43
|
};
|
|
44
44
|
var buttonVariants = cva(
|
|
45
|
-
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
45
|
+
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium font-sans transition-all duration-normal disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
46
46
|
buttonVariantsConfig
|
|
47
47
|
);
|
|
48
48
|
var Button = React.forwardRef(({ className, variant, size, asChild = false, ...props }, ref) => {
|
|
@@ -72,7 +72,7 @@ var badgeVariantsConfig = {
|
|
|
72
72
|
}
|
|
73
73
|
};
|
|
74
74
|
var badgeVariants = cva(
|
|
75
|
-
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-
|
|
75
|
+
"inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium font-sans w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-component-xs [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] duration-normal overflow-hidden",
|
|
76
76
|
badgeVariantsConfig
|
|
77
77
|
);
|
|
78
78
|
var Badge = React.forwardRef(({ className, variant, asChild = false, ...props }, ref) => {
|
|
@@ -97,7 +97,7 @@ var TextInput = React.forwardRef(
|
|
|
97
97
|
type,
|
|
98
98
|
"data-slot": "text-input",
|
|
99
99
|
className: cn(
|
|
100
|
-
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
100
|
+
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] duration-normal outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium font-sans disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
101
101
|
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
|
|
102
102
|
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
|
103
103
|
className
|
|
@@ -131,7 +131,7 @@ var Textarea = React.forwardRef(
|
|
|
131
131
|
ref,
|
|
132
132
|
"data-slot": "textarea",
|
|
133
133
|
className: cn(
|
|
134
|
-
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
|
|
134
|
+
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] duration-normal outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm font-sans",
|
|
135
135
|
className
|
|
136
136
|
),
|
|
137
137
|
...props
|
|
@@ -209,7 +209,7 @@ function ModalOverlay({
|
|
|
209
209
|
{
|
|
210
210
|
"data-slot": "modal-overlay",
|
|
211
211
|
className: cn(
|
|
212
|
-
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50",
|
|
212
|
+
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/50 duration-normal",
|
|
213
213
|
className
|
|
214
214
|
),
|
|
215
215
|
...props
|
|
@@ -229,7 +229,7 @@ function ModalContent({
|
|
|
229
229
|
{
|
|
230
230
|
"data-slot": "modal-content",
|
|
231
231
|
className: cn(
|
|
232
|
-
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-
|
|
232
|
+
"bg-background data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-component-md rounded-lg border p-component-lg shadow-lg duration-normal font-sans sm:max-w-lg",
|
|
233
233
|
className
|
|
234
234
|
),
|
|
235
235
|
...props,
|
|
@@ -256,7 +256,7 @@ function ModalHeader({ className, ...props }) {
|
|
|
256
256
|
"div",
|
|
257
257
|
{
|
|
258
258
|
"data-slot": "modal-header",
|
|
259
|
-
className: cn("flex flex-col gap-
|
|
259
|
+
className: cn("flex flex-col gap-component-sm text-center sm:text-left font-sans", className),
|
|
260
260
|
...props
|
|
261
261
|
}
|
|
262
262
|
);
|
|
@@ -267,7 +267,7 @@ function ModalFooter({ className, ...props }) {
|
|
|
267
267
|
{
|
|
268
268
|
"data-slot": "modal-footer",
|
|
269
269
|
className: cn(
|
|
270
|
-
"flex flex-col-reverse gap-
|
|
270
|
+
"flex flex-col-reverse gap-component-sm sm:flex-row sm:justify-end",
|
|
271
271
|
className
|
|
272
272
|
),
|
|
273
273
|
...props
|
|
@@ -626,7 +626,39 @@ async function discoverThemes() {
|
|
|
626
626
|
const tokensBase = typeof window !== "undefined" && window.__THEME_TOKENS_BASE__ ? window.__THEME_TOKENS_BASE__ : "/tokens";
|
|
627
627
|
const knownCategories = Object.keys(baseThemeCategories);
|
|
628
628
|
for (const category of knownCategories) {
|
|
629
|
-
const
|
|
629
|
+
const existingThemes = discovered[category]?.themes || {};
|
|
630
|
+
const themeIds = Object.keys(existingThemes);
|
|
631
|
+
const commonThemeNames = ["ocean", "forest", "sunset", "midnight", "pastel", "vibrant", "muted", "high-contrast"];
|
|
632
|
+
for (const themeName of commonThemeNames) {
|
|
633
|
+
if (themeIds.includes(themeName)) continue;
|
|
634
|
+
const themePath = `${tokensBase}/themes/${category}/${themeName}.json`;
|
|
635
|
+
try {
|
|
636
|
+
const response = await fetch(themePath);
|
|
637
|
+
if (response.ok && response.headers.get("content-type")?.includes("application/json")) {
|
|
638
|
+
const themeData = await response.json();
|
|
639
|
+
registerTheme(category, themeName, {
|
|
640
|
+
name: themeData.name || themeName.charAt(0).toUpperCase() + themeName.slice(1),
|
|
641
|
+
file: `${category}/${themeName}.json`,
|
|
642
|
+
icon: themeData.icon || "\u{1F3A8}",
|
|
643
|
+
description: themeData.description || `Custom ${category} theme: ${themeName}`
|
|
644
|
+
});
|
|
645
|
+
if (!discovered[category]) {
|
|
646
|
+
discovered[category] = {
|
|
647
|
+
name: category.charAt(0).toUpperCase() + category.slice(1),
|
|
648
|
+
order: baseThemeCategories[category]?.order || 99,
|
|
649
|
+
themes: {}
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
discovered[category].themes[themeName] = {
|
|
653
|
+
name: themeData.name || themeName.charAt(0).toUpperCase() + themeName.slice(1),
|
|
654
|
+
file: `${category}/${themeName}.json`,
|
|
655
|
+
icon: themeData.icon || "\u{1F3A8}",
|
|
656
|
+
description: themeData.description || `Custom ${category} theme: ${themeName}`
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
} catch {
|
|
660
|
+
}
|
|
661
|
+
}
|
|
630
662
|
}
|
|
631
663
|
discoveredThemesCache = discovered;
|
|
632
664
|
return discovered;
|
|
@@ -643,10 +675,14 @@ function registerTheme(category, themeId, metadata) {
|
|
|
643
675
|
}
|
|
644
676
|
const cache = discoveredThemesCache;
|
|
645
677
|
if (!cache[category]) {
|
|
678
|
+
let order = 99;
|
|
679
|
+
if (THEME_CATEGORY_ORDER.includes(category)) {
|
|
680
|
+
const index = THEME_CATEGORY_ORDER.indexOf(category);
|
|
681
|
+
order = (index + 1) * 10;
|
|
682
|
+
}
|
|
646
683
|
cache[category] = {
|
|
647
684
|
name: category.charAt(0).toUpperCase() + category.slice(1),
|
|
648
|
-
order
|
|
649
|
-
// Custom categories get high order
|
|
685
|
+
order,
|
|
650
686
|
themes: {}
|
|
651
687
|
};
|
|
652
688
|
}
|
|
@@ -658,6 +694,29 @@ function registerTheme(category, themeId, metadata) {
|
|
|
658
694
|
};
|
|
659
695
|
return cache;
|
|
660
696
|
}
|
|
697
|
+
async function registerThemeFromFile(category, themeId, filePath) {
|
|
698
|
+
const tokensBase = typeof window !== "undefined" && window.__THEME_TOKENS_BASE__ ? window.__THEME_TOKENS_BASE__ : "/tokens";
|
|
699
|
+
const path = filePath || `${tokensBase}/themes/${category}/${themeId}.json`;
|
|
700
|
+
try {
|
|
701
|
+
const response = await fetch(path);
|
|
702
|
+
if (!response.ok) {
|
|
703
|
+
throw new Error(`Failed to load theme file: ${response.statusText}`);
|
|
704
|
+
}
|
|
705
|
+
const themeData = await response.json();
|
|
706
|
+
registerTheme(category, themeId, {
|
|
707
|
+
name: themeData.name || themeId.charAt(0).toUpperCase() + themeId.slice(1),
|
|
708
|
+
file: filePath || `${category}/${themeId}.json`,
|
|
709
|
+
icon: themeData.icon || "\u{1F3A8}",
|
|
710
|
+
description: themeData.description || `Custom ${category} theme: ${themeId}`
|
|
711
|
+
});
|
|
712
|
+
return { success: true, themeId, category };
|
|
713
|
+
} catch (error) {
|
|
714
|
+
if (typeof window !== "undefined" && window.__DESIGN_SYSTEM_DEBUG__) {
|
|
715
|
+
console.error(`Failed to register theme from ${path}:`, error);
|
|
716
|
+
}
|
|
717
|
+
throw error;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
661
720
|
async function getThemeCategories() {
|
|
662
721
|
return await discoverThemes();
|
|
663
722
|
}
|
|
@@ -836,6 +895,8 @@ function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false)
|
|
|
836
895
|
const enteringColor = key === "color" && prefix === "";
|
|
837
896
|
const enteringTypography = key === "typography" && prefix === "";
|
|
838
897
|
const enteringShape = key === "shape" && prefix === "";
|
|
898
|
+
const enteringAnimation = key === "animation" && prefix === "";
|
|
899
|
+
const enteringDensity = key === "spacing" && prefix === "";
|
|
839
900
|
const inColorContext = isColorContext || enteringColor;
|
|
840
901
|
if (enteringColor) {
|
|
841
902
|
flattenToCSS(value, "", result, true);
|
|
@@ -843,6 +904,10 @@ function flattenToCSS(tokens, prefix = "", result = {}, isColorContext = false)
|
|
|
843
904
|
flattenToCSS(value, "", result, false);
|
|
844
905
|
} else if (enteringShape) {
|
|
845
906
|
flattenToCSS(value, "", result, false);
|
|
907
|
+
} else if (enteringAnimation) {
|
|
908
|
+
flattenToCSS(value, "", result, false);
|
|
909
|
+
} else if (enteringDensity) {
|
|
910
|
+
flattenToCSS(value, "spacing", result, false);
|
|
846
911
|
} else if (inColorContext) {
|
|
847
912
|
flattenToCSS(value, "", result, true);
|
|
848
913
|
} else {
|
|
@@ -1558,6 +1623,8 @@ function flattenToCSSSync(tokens, prefix = "", result = {}, isColorContext = fal
|
|
|
1558
1623
|
const enteringColor = key === "color" && prefix === "";
|
|
1559
1624
|
const enteringTypography = key === "typography" && prefix === "";
|
|
1560
1625
|
const enteringShape = key === "shape" && prefix === "";
|
|
1626
|
+
const enteringAnimation = key === "animation" && prefix === "";
|
|
1627
|
+
const enteringDensity = key === "spacing" && prefix === "";
|
|
1561
1628
|
const inColorContext = isColorContext || enteringColor;
|
|
1562
1629
|
if (enteringColor) {
|
|
1563
1630
|
flattenToCSSSync(value, "", result, true);
|
|
@@ -1565,6 +1632,10 @@ function flattenToCSSSync(tokens, prefix = "", result = {}, isColorContext = fal
|
|
|
1565
1632
|
flattenToCSSSync(value, "", result, false);
|
|
1566
1633
|
} else if (enteringShape) {
|
|
1567
1634
|
flattenToCSSSync(value, "", result, false);
|
|
1635
|
+
} else if (enteringAnimation) {
|
|
1636
|
+
flattenToCSSSync(value, "", result, false);
|
|
1637
|
+
} else if (enteringDensity) {
|
|
1638
|
+
flattenToCSSSync(value, "spacing", result, false);
|
|
1568
1639
|
} else if (inColorContext) {
|
|
1569
1640
|
flattenToCSSSync(value, "", result, true);
|
|
1570
1641
|
} else {
|
|
@@ -1608,4 +1679,4 @@ function mapToTailwindVarsSync(cssVars) {
|
|
|
1608
1679
|
return mapped;
|
|
1609
1680
|
}
|
|
1610
1681
|
|
|
1611
|
-
export { Badge, Button, Checkbox, Label, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, THEME_CATEGORY_ORDER, TextInput, Textarea, ThemeToggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, enableDebugMode, getCurrentCSSVariables, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, useTheme, useThemeToggle };
|
|
1682
|
+
export { Badge, Button, Checkbox, Label, Modal, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalOverlay, ModalPortal, ModalTitle, ModalTrigger, Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectScrollDownButton, SelectScrollUpButton, SelectSeparator, SelectTrigger, SelectValue, Separator, THEME_CATEGORY_ORDER, TextInput, Textarea, ThemeToggle, Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, applyThemeSync, badgeVariants, buttonVariants, enableDebugMode, getCurrentCSSVariables, getTheme, getThemeCategories, getThemeFilePath, getThemesForCategory, registerTheme, registerThemeFromFile, useTheme, useThemeToggle };
|
package/package.json
CHANGED
|
@@ -171,13 +171,31 @@
|
|
|
171
171
|
for (var key in tokens) {
|
|
172
172
|
var value = tokens[key];
|
|
173
173
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
174
|
-
//
|
|
174
|
+
// Special handling: color, typography, shape, animation, and density objects flatten without their category prefix
|
|
175
175
|
var enteringColor = key === 'color' && prefix === '';
|
|
176
|
+
var enteringTypography = key === 'typography' && prefix === '';
|
|
177
|
+
var enteringShape = key === 'shape' && prefix === '';
|
|
178
|
+
var enteringAnimation = key === 'animation' && prefix === '';
|
|
179
|
+
var enteringDensity = key === 'spacing' && prefix === ''; // Density uses spacing key
|
|
176
180
|
var inColorContext = isColorContext || enteringColor;
|
|
177
181
|
|
|
178
182
|
if (enteringColor) {
|
|
179
183
|
// When entering color object, flatten without "color-" prefix
|
|
180
184
|
flattenToCSS(value, '', result, true);
|
|
185
|
+
} else if (enteringTypography) {
|
|
186
|
+
// When entering typography object, flatten without "typography-" prefix
|
|
187
|
+
flattenToCSS(value, '', result, false);
|
|
188
|
+
} else if (enteringShape) {
|
|
189
|
+
// When entering shape object, flatten without "shape-" prefix
|
|
190
|
+
flattenToCSS(value, '', result, false);
|
|
191
|
+
} else if (enteringAnimation) {
|
|
192
|
+
// When entering animation object, flatten without "animation-" prefix
|
|
193
|
+
// animation.duration.fast → --duration-fast
|
|
194
|
+
flattenToCSS(value, '', result, false);
|
|
195
|
+
} else if (enteringDensity) {
|
|
196
|
+
// When entering density/spacing object at root, we need to preserve "spacing-" prefix
|
|
197
|
+
// spacing.component.md → --spacing-component-md
|
|
198
|
+
flattenToCSS(value, 'spacing', result, false);
|
|
181
199
|
} else if (inColorContext) {
|
|
182
200
|
// Already in color context, continue with empty prefix
|
|
183
201
|
flattenToCSS(value, '', result, true);
|
|
@@ -217,11 +217,33 @@ function flattenToCSSSync(tokens, prefix = '', result = {}, isColorContext = fal
|
|
|
217
217
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
218
218
|
// Token files are already in correct structure (no nested typography/shape wrappers)
|
|
219
219
|
const enteringColor = key === 'color' && prefix === '';
|
|
220
|
+
const enteringTypography = key === 'typography' && prefix === '';
|
|
221
|
+
const enteringShape = key === 'shape' && prefix === '';
|
|
222
|
+
const enteringAnimation = key === 'animation' && prefix === '';
|
|
223
|
+
const enteringDensity = key === 'spacing' && prefix === ''; // Density uses spacing key
|
|
220
224
|
const inColorContext = isColorContext || enteringColor;
|
|
221
225
|
if (enteringColor) {
|
|
222
226
|
// When entering color object, flatten without "color-" prefix
|
|
223
227
|
flattenToCSSSync(value, '', result, true);
|
|
224
228
|
}
|
|
229
|
+
else if (enteringTypography) {
|
|
230
|
+
// When entering typography object, flatten without "typography-" prefix
|
|
231
|
+
flattenToCSSSync(value, '', result, false);
|
|
232
|
+
}
|
|
233
|
+
else if (enteringShape) {
|
|
234
|
+
// When entering shape object, flatten without "shape-" prefix
|
|
235
|
+
flattenToCSSSync(value, '', result, false);
|
|
236
|
+
}
|
|
237
|
+
else if (enteringAnimation) {
|
|
238
|
+
// When entering animation object, flatten without "animation-" prefix
|
|
239
|
+
// animation.duration.fast → --duration-fast
|
|
240
|
+
flattenToCSSSync(value, '', result, false);
|
|
241
|
+
}
|
|
242
|
+
else if (enteringDensity) {
|
|
243
|
+
// When entering density/spacing object at root, we need to preserve "spacing-" prefix
|
|
244
|
+
// spacing.component.md → --spacing-component-md
|
|
245
|
+
flattenToCSSSync(value, 'spacing', result, false);
|
|
246
|
+
}
|
|
225
247
|
else if (inColorContext) {
|
|
226
248
|
// Already in color context, continue with empty prefix
|
|
227
249
|
flattenToCSSSync(value, '', result, true);
|
package/scripts/init.js
CHANGED
|
@@ -63,19 +63,159 @@ function installPackage(packageName, isDev = true) {
|
|
|
63
63
|
function createTailwindConfig() {
|
|
64
64
|
const configPath = path.join(process.cwd(), 'tailwind.config.js');
|
|
65
65
|
|
|
66
|
+
// Required configs that must be present
|
|
67
|
+
const requiredConfigs = {
|
|
68
|
+
fontFamily: 'fontFamily',
|
|
69
|
+
spacing: 'spacing',
|
|
70
|
+
gap: 'gap',
|
|
71
|
+
transitionDuration: 'transitionDuration'
|
|
72
|
+
};
|
|
73
|
+
|
|
66
74
|
if (fs.existsSync(configPath)) {
|
|
67
|
-
log('tailwind.config.js already exists.
|
|
75
|
+
log('tailwind.config.js already exists. Checking for required configs...', 'yellow');
|
|
68
76
|
const existing = fs.readFileSync(configPath, 'utf8');
|
|
69
77
|
|
|
70
|
-
// Check if
|
|
71
|
-
|
|
72
|
-
|
|
78
|
+
// Check if all required configs are present with proper values
|
|
79
|
+
const hasFontFamily = existing.includes('fontFamily') && existing.includes('--font-sans');
|
|
80
|
+
const hasSpacing = existing.includes('spacing:') && existing.includes('component-xs');
|
|
81
|
+
const hasGap = existing.includes('gap:') && existing.includes('component-xs');
|
|
82
|
+
const hasTransitionDuration = existing.includes('transitionDuration') && existing.includes('duration-fast');
|
|
83
|
+
|
|
84
|
+
if (hasFontFamily && hasSpacing && hasGap && hasTransitionDuration) {
|
|
85
|
+
log(`Configuration already includes all required ${PACKAGE_NAME} setup.`, 'green');
|
|
73
86
|
return;
|
|
74
87
|
}
|
|
75
88
|
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
89
|
+
// Track what's missing
|
|
90
|
+
const missingConfigs = [];
|
|
91
|
+
if (!hasFontFamily) missingConfigs.push('fontFamily');
|
|
92
|
+
if (!hasSpacing) missingConfigs.push('spacing');
|
|
93
|
+
if (!hasGap) missingConfigs.push('gap');
|
|
94
|
+
if (!hasTransitionDuration) missingConfigs.push('transitionDuration');
|
|
95
|
+
|
|
96
|
+
// If configs are missing, update the file
|
|
97
|
+
if (missingConfigs.length > 0) {
|
|
98
|
+
log(`Missing required configs: ${missingConfigs.join(', ')}. Updating...`, 'yellow');
|
|
99
|
+
|
|
100
|
+
// Read the existing config
|
|
101
|
+
let updated = existing;
|
|
102
|
+
|
|
103
|
+
// Add fontFamily if missing
|
|
104
|
+
if (!hasFontFamily) {
|
|
105
|
+
const fontFamilyConfig = ` // ⚠️ IF YOU UPDATE fontFamily CONFIG, ALSO UPDATE:
|
|
106
|
+
// 1. test/tailwind.config.js - fontFamily config (test app)
|
|
107
|
+
fontFamily: {
|
|
108
|
+
sans: ["var(--font-sans)", "system-ui", "sans-serif"],
|
|
109
|
+
body: ["var(--font-body)", "var(--font-sans)", "system-ui", "sans-serif"],
|
|
110
|
+
},`;
|
|
111
|
+
|
|
112
|
+
// Insert after borderRadius
|
|
113
|
+
if (existing.includes('borderRadius')) {
|
|
114
|
+
updated = updated.replace(
|
|
115
|
+
/(borderRadius: \{[\s\S]*?\},)/,
|
|
116
|
+
`$1\n${fontFamilyConfig}`
|
|
117
|
+
);
|
|
118
|
+
} else {
|
|
119
|
+
// Insert after colors
|
|
120
|
+
updated = updated.replace(
|
|
121
|
+
/(ring: "hsl\(var\(--ring\)\)",)/,
|
|
122
|
+
`$1\n },\n${fontFamilyConfig}`
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Add spacing if missing
|
|
128
|
+
if (!hasSpacing) {
|
|
129
|
+
const spacingConfig = ` // ⚠️ IF YOU UPDATE spacing CONFIG, ALSO UPDATE:
|
|
130
|
+
// 1. test/tailwind.config.js - spacing config (test app)
|
|
131
|
+
spacing: {
|
|
132
|
+
'component-xs': "var(--spacing-component-xs, 0.25rem)",
|
|
133
|
+
'component-sm': "var(--spacing-component-sm, 0.5rem)",
|
|
134
|
+
'component-md': "var(--spacing-component-md, 1rem)",
|
|
135
|
+
'component-lg': "var(--spacing-component-lg, 1.5rem)",
|
|
136
|
+
'component-xl': "var(--spacing-component-xl, 2rem)",
|
|
137
|
+
},`;
|
|
138
|
+
|
|
139
|
+
// Insert after fontFamily or borderRadius
|
|
140
|
+
if (updated.includes('fontFamily')) {
|
|
141
|
+
updated = updated.replace(
|
|
142
|
+
/(fontFamily: \{[\s\S]*?\},)/,
|
|
143
|
+
`$1\n${spacingConfig}`
|
|
144
|
+
);
|
|
145
|
+
} else if (updated.includes('borderRadius')) {
|
|
146
|
+
updated = updated.replace(
|
|
147
|
+
/(borderRadius: \{[\s\S]*?\},)/,
|
|
148
|
+
`$1\n${spacingConfig}`
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Add gap if missing
|
|
154
|
+
if (!hasGap) {
|
|
155
|
+
const gapConfig = ` // ⚠️ IF YOU UPDATE gap CONFIG, ALSO UPDATE:
|
|
156
|
+
// 1. test/tailwind.config.js - gap config (test app)
|
|
157
|
+
gap: {
|
|
158
|
+
'component-xs': "var(--spacing-component-xs, 0.25rem)",
|
|
159
|
+
'component-sm': "var(--spacing-component-sm, 0.5rem)",
|
|
160
|
+
'component-md': "var(--spacing-component-md, 1rem)",
|
|
161
|
+
'component-lg': "var(--spacing-component-lg, 1.5rem)",
|
|
162
|
+
'component-xl': "var(--spacing-component-xl, 2rem)",
|
|
163
|
+
},`;
|
|
164
|
+
|
|
165
|
+
// Insert after spacing
|
|
166
|
+
if (updated.includes('spacing:')) {
|
|
167
|
+
updated = updated.replace(
|
|
168
|
+
/(spacing: \{[\s\S]*?\},)/,
|
|
169
|
+
`$1\n${gapConfig}`
|
|
170
|
+
);
|
|
171
|
+
} else if (updated.includes('fontFamily')) {
|
|
172
|
+
updated = updated.replace(
|
|
173
|
+
/(fontFamily: \{[\s\S]*?\},)/,
|
|
174
|
+
`$1\n${gapConfig}`
|
|
175
|
+
);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Add transitionDuration if missing
|
|
180
|
+
if (!hasTransitionDuration) {
|
|
181
|
+
const transitionConfig = ` // ⚠️ IF YOU UPDATE transitionDuration CONFIG, ALSO UPDATE:
|
|
182
|
+
// 1. test/tailwind.config.js - transitionDuration config (test app)
|
|
183
|
+
transitionDuration: {
|
|
184
|
+
'fast': "var(--duration-fast, 150ms)",
|
|
185
|
+
'normal': "var(--duration-normal, 300ms)",
|
|
186
|
+
'slow': "var(--duration-slow, 500ms)",
|
|
187
|
+
},`;
|
|
188
|
+
|
|
189
|
+
// Insert after gap or spacing
|
|
190
|
+
if (updated.includes('gap:')) {
|
|
191
|
+
updated = updated.replace(
|
|
192
|
+
/(gap: \{[\s\S]*?\},)/,
|
|
193
|
+
`$1\n${transitionConfig}`
|
|
194
|
+
);
|
|
195
|
+
} else if (updated.includes('spacing:')) {
|
|
196
|
+
updated = updated.replace(
|
|
197
|
+
/(spacing: \{[\s\S]*?\},)/,
|
|
198
|
+
`$1\n${transitionConfig}`
|
|
199
|
+
);
|
|
200
|
+
} else if (updated.includes('fontFamily')) {
|
|
201
|
+
updated = updated.replace(
|
|
202
|
+
/(fontFamily: \{[\s\S]*?\},)/,
|
|
203
|
+
`$1\n${transitionConfig}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Write the updated config
|
|
209
|
+
fs.writeFileSync(configPath, updated);
|
|
210
|
+
log('Updated tailwind.config.js with missing configs', 'green');
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// If config exists but doesn't have our package name, warn user
|
|
215
|
+
if (!existing.includes(PACKAGE_NAME) && !existing.includes('shru-design-system')) {
|
|
216
|
+
log('Please manually merge the Tailwind config. See docs for details.', 'yellow');
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
79
219
|
}
|
|
80
220
|
|
|
81
221
|
const config = `/** @type {import('tailwindcss').Config} */
|
|
@@ -119,15 +259,21 @@ export default {
|
|
|
119
259
|
input: "hsl(var(--input))",
|
|
120
260
|
ring: "hsl(var(--ring))",
|
|
121
261
|
},
|
|
262
|
+
// ⚠️ IF YOU UPDATE borderRadius CONFIG, ALSO UPDATE:
|
|
263
|
+
// 1. test/tailwind.config.js - borderRadius config (test app)
|
|
122
264
|
borderRadius: {
|
|
123
265
|
lg: "var(--radius)",
|
|
124
266
|
md: "calc(var(--radius) - 2px)",
|
|
125
267
|
sm: "calc(var(--radius) - 4px)",
|
|
126
268
|
},
|
|
269
|
+
// ⚠️ IF YOU UPDATE fontFamily CONFIG, ALSO UPDATE:
|
|
270
|
+
// 1. test/tailwind.config.js - fontFamily config (test app)
|
|
127
271
|
fontFamily: {
|
|
128
272
|
sans: ["var(--font-sans)", "system-ui", "sans-serif"],
|
|
129
273
|
body: ["var(--font-body)", "var(--font-sans)", "system-ui", "sans-serif"],
|
|
130
274
|
},
|
|
275
|
+
// ⚠️ IF YOU UPDATE spacing CONFIG, ALSO UPDATE:
|
|
276
|
+
// 1. test/tailwind.config.js - spacing config (test app)
|
|
131
277
|
spacing: {
|
|
132
278
|
'component-xs': "var(--spacing-component-xs, 0.25rem)",
|
|
133
279
|
'component-sm': "var(--spacing-component-sm, 0.5rem)",
|
|
@@ -135,6 +281,13 @@ export default {
|
|
|
135
281
|
'component-lg': "var(--spacing-component-lg, 1.5rem)",
|
|
136
282
|
'component-xl': "var(--spacing-component-xl, 2rem)",
|
|
137
283
|
},
|
|
284
|
+
gap: {
|
|
285
|
+
'component-xs': "var(--spacing-component-xs, 0.25rem)",
|
|
286
|
+
'component-sm': "var(--spacing-component-sm, 0.5rem)",
|
|
287
|
+
'component-md': "var(--spacing-component-md, 1rem)",
|
|
288
|
+
'component-lg': "var(--spacing-component-lg, 1.5rem)",
|
|
289
|
+
'component-xl': "var(--spacing-component-xl, 2rem)",
|
|
290
|
+
},
|
|
138
291
|
transitionDuration: {
|
|
139
292
|
'fast': "var(--duration-fast, 150ms)",
|
|
140
293
|
'normal': "var(--duration-normal, 300ms)",
|
|
@@ -222,6 +375,7 @@ function createCSSFile() {
|
|
|
222
375
|
body {
|
|
223
376
|
background-color: hsl(var(--background));
|
|
224
377
|
color: hsl(var(--foreground));
|
|
378
|
+
font-family: var(--font-sans), system-ui, sans-serif;
|
|
225
379
|
}
|
|
226
380
|
}
|
|
227
381
|
`;
|
|
@@ -264,6 +418,7 @@ function createCSSFile() {
|
|
|
264
418
|
body {
|
|
265
419
|
background-color: hsl(var(--background));
|
|
266
420
|
color: hsl(var(--foreground));
|
|
421
|
+
font-family: var(--font-sans), system-ui, sans-serif;
|
|
267
422
|
}
|
|
268
423
|
}
|
|
269
424
|
`;
|
|
@@ -290,8 +445,8 @@ function getLibraryTokensPath() {
|
|
|
290
445
|
}
|
|
291
446
|
|
|
292
447
|
/**
|
|
293
|
-
* Recursively copy directory,
|
|
294
|
-
*
|
|
448
|
+
* Recursively copy directory, always overwriting library files
|
|
449
|
+
* Never preserves existing library files - always overwrites or creates new
|
|
295
450
|
*/
|
|
296
451
|
function copyDirectory(src, dest) {
|
|
297
452
|
if (!fs.existsSync(src)) {
|
|
@@ -314,13 +469,13 @@ function copyDirectory(src, dest) {
|
|
|
314
469
|
const subCopied = copyDirectory(srcPath, destPath);
|
|
315
470
|
if (subCopied) copiedCount += subCopied;
|
|
316
471
|
} else if (entry.isFile() && entry.name.endsWith('.json')) {
|
|
317
|
-
// For JSON files,
|
|
472
|
+
// For JSON files, always overwrite if source is a library file
|
|
318
473
|
try {
|
|
319
474
|
const srcContent = JSON.parse(fs.readFileSync(srcPath, 'utf8'));
|
|
320
475
|
const isLibraryFile = srcContent._createdBy && srcContent._createdBy.includes(LIBRARY_NAME.split(' ')[0]);
|
|
321
476
|
|
|
322
477
|
if (isLibraryFile) {
|
|
323
|
-
// Always
|
|
478
|
+
// Always overwrite library files (never preserve)
|
|
324
479
|
fs.copyFileSync(srcPath, destPath);
|
|
325
480
|
copiedCount++;
|
|
326
481
|
} else if (!fs.existsSync(destPath)) {
|
|
@@ -328,10 +483,20 @@ function copyDirectory(src, dest) {
|
|
|
328
483
|
fs.copyFileSync(srcPath, destPath);
|
|
329
484
|
copiedCount++;
|
|
330
485
|
}
|
|
331
|
-
// If
|
|
486
|
+
// If source is not a library file, don't copy (preserve user's custom files)
|
|
332
487
|
} catch (e) {
|
|
333
|
-
// If JSON parsing fails,
|
|
334
|
-
|
|
488
|
+
// If JSON parsing fails, check if it's a known library file path
|
|
489
|
+
// Known library files: base.json, palettes.json, and all files in themes/
|
|
490
|
+
const isKnownLibraryFile = entry.name === 'base.json' ||
|
|
491
|
+
entry.name === 'palettes.json' ||
|
|
492
|
+
srcPath.includes(path.join('tokens', 'themes'));
|
|
493
|
+
|
|
494
|
+
if (isKnownLibraryFile) {
|
|
495
|
+
// Always overwrite known library files
|
|
496
|
+
fs.copyFileSync(srcPath, destPath);
|
|
497
|
+
copiedCount++;
|
|
498
|
+
} else if (!fs.existsSync(destPath)) {
|
|
499
|
+
// New file that doesn't exist in dest
|
|
335
500
|
fs.copyFileSync(srcPath, destPath);
|
|
336
501
|
copiedCount++;
|
|
337
502
|
}
|
|
@@ -446,39 +611,22 @@ function createTokenFiles() {
|
|
|
446
611
|
tokenFilesToCheck.forEach(relativePath => {
|
|
447
612
|
const destPath = path.join(tokensDir, relativePath);
|
|
448
613
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
if (JSON.stringify(existing) !== JSON.stringify(migrated)) {
|
|
457
|
-
migrated._createdBy = LIBRARY_NAME;
|
|
458
|
-
fs.writeFileSync(destPath, JSON.stringify(migrated, null, 2));
|
|
459
|
-
log(`Migrated ${relativePath} to new structure`, 'yellow');
|
|
460
|
-
}
|
|
461
|
-
} catch (e) {
|
|
462
|
-
// If file is corrupted, try to read from library
|
|
463
|
-
const libraryData = readLibraryTokenFile(relativePath);
|
|
464
|
-
if (libraryData) {
|
|
465
|
-
libraryData._createdBy = LIBRARY_NAME;
|
|
466
|
-
fs.writeFileSync(destPath, JSON.stringify(libraryData, null, 2));
|
|
467
|
-
log(`Restored ${relativePath} from library`, 'green');
|
|
468
|
-
}
|
|
614
|
+
// Always read from library and overwrite (never preserve existing files)
|
|
615
|
+
const libraryData = readLibraryTokenFile(relativePath);
|
|
616
|
+
if (libraryData) {
|
|
617
|
+
// Ensure directory exists
|
|
618
|
+
const destDir = path.dirname(destPath);
|
|
619
|
+
if (!fs.existsSync(destDir)) {
|
|
620
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
469
621
|
}
|
|
470
|
-
|
|
471
|
-
//
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
libraryData._createdBy = LIBRARY_NAME;
|
|
481
|
-
fs.writeFileSync(destPath, JSON.stringify(libraryData, null, 2));
|
|
622
|
+
|
|
623
|
+
// Always overwrite with library version
|
|
624
|
+
libraryData._createdBy = LIBRARY_NAME;
|
|
625
|
+
fs.writeFileSync(destPath, JSON.stringify(libraryData, null, 2));
|
|
626
|
+
|
|
627
|
+
if (fs.existsSync(destPath)) {
|
|
628
|
+
log(`Updated ${relativePath} from library`, 'green');
|
|
629
|
+
} else {
|
|
482
630
|
log(`Created ${relativePath} from library`, 'green');
|
|
483
631
|
}
|
|
484
632
|
}
|
package/scripts/themeUtils.js
CHANGED
|
@@ -231,11 +231,33 @@ function flattenToCSS(tokens, prefix = '', result = {}, isColorContext = false)
|
|
|
231
231
|
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
232
232
|
// Token files are already in correct structure (no nested typography/shape wrappers)
|
|
233
233
|
const enteringColor = key === 'color' && prefix === '';
|
|
234
|
+
const enteringTypography = key === 'typography' && prefix === '';
|
|
235
|
+
const enteringShape = key === 'shape' && prefix === '';
|
|
236
|
+
const enteringAnimation = key === 'animation' && prefix === '';
|
|
237
|
+
const enteringDensity = key === 'spacing' && prefix === ''; // Density uses spacing key
|
|
234
238
|
const inColorContext = isColorContext || enteringColor;
|
|
235
239
|
if (enteringColor) {
|
|
236
240
|
// When entering color object, flatten without "color-" prefix
|
|
237
241
|
flattenToCSS(value, '', result, true);
|
|
238
242
|
}
|
|
243
|
+
else if (enteringTypography) {
|
|
244
|
+
// When entering typography object, flatten without "typography-" prefix
|
|
245
|
+
flattenToCSS(value, '', result, false);
|
|
246
|
+
}
|
|
247
|
+
else if (enteringShape) {
|
|
248
|
+
// When entering shape object, flatten without "shape-" prefix
|
|
249
|
+
flattenToCSS(value, '', result, false);
|
|
250
|
+
}
|
|
251
|
+
else if (enteringAnimation) {
|
|
252
|
+
// When entering animation object, flatten without "animation-" prefix
|
|
253
|
+
// animation.duration.fast → --duration-fast
|
|
254
|
+
flattenToCSS(value, '', result, false);
|
|
255
|
+
}
|
|
256
|
+
else if (enteringDensity) {
|
|
257
|
+
// When entering density/spacing object at root, we need to preserve "spacing-" prefix
|
|
258
|
+
// spacing.component.md → --spacing-component-md
|
|
259
|
+
flattenToCSS(value, 'spacing', result, false);
|
|
260
|
+
}
|
|
239
261
|
else if (inColorContext) {
|
|
240
262
|
// Already in color context, continue with empty prefix
|
|
241
263
|
flattenToCSS(value, '', result, true);
|
|
@@ -42,6 +42,32 @@
|
|
|
42
42
|
"800": "#991b1b",
|
|
43
43
|
"900": "#7f1d1d",
|
|
44
44
|
"950": "#450a0a"
|
|
45
|
+
},
|
|
46
|
+
"purple": {
|
|
47
|
+
"50": "#faf5ff",
|
|
48
|
+
"100": "#f3e8ff",
|
|
49
|
+
"200": "#e9d5ff",
|
|
50
|
+
"300": "#d8b4fe",
|
|
51
|
+
"400": "#c084fc",
|
|
52
|
+
"500": "#a855f7",
|
|
53
|
+
"600": "#9333ea",
|
|
54
|
+
"700": "#7e22ce",
|
|
55
|
+
"800": "#6b21a8",
|
|
56
|
+
"900": "#581c87",
|
|
57
|
+
"950": "#3b0764"
|
|
58
|
+
},
|
|
59
|
+
"pink": {
|
|
60
|
+
"50": "#fdf2f8",
|
|
61
|
+
"100": "#fce7f3",
|
|
62
|
+
"200": "#fbcfe8",
|
|
63
|
+
"300": "#f9a8d4",
|
|
64
|
+
"400": "#f472b6",
|
|
65
|
+
"500": "#ec4899",
|
|
66
|
+
"600": "#db2777",
|
|
67
|
+
"700": "#be185d",
|
|
68
|
+
"800": "#9f1239",
|
|
69
|
+
"900": "#831843",
|
|
70
|
+
"950": "#500724"
|
|
45
71
|
}
|
|
46
72
|
}
|
|
47
73
|
}
|