Skip to content

Commit c70981b

Browse files
committed
feat: add tailwind-plugin subpackage
1 parent 17bd51c commit c70981b

File tree

8 files changed

+1264
-0
lines changed

8 files changed

+1264
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "skyroc-tailwind-plugin",
3+
"type": "module",
4+
"version": "0.0.1",
5+
"sideEffects": false,
6+
"exports": {
7+
".": "./src/index.ts"
8+
},
9+
"typesVersions": {
10+
"*": {
11+
"*": ["./src/*"]
12+
}
13+
},
14+
"files": ["./dist"],
15+
"scripts": {
16+
"build": "tsup"
17+
},
18+
"dependencies": {
19+
"@unocss/core": "66.3.2",
20+
"tailwindcss": "4.1.11"
21+
}
22+
}
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
import { mergeDeep } from '@unocss/core';
2+
3+
import themes from './theme.json';
4+
import type {
5+
ColorOptions,
6+
FeedbackColorOfThemeCssVarsVariant,
7+
PresetShadcnOptions,
8+
SidebarColorOfThemeCssVarsVariant,
9+
ThemeCSSVars,
10+
ThemeCSSVarsVariant,
11+
ThemeConfig
12+
} from './types';
13+
14+
const builtinThemes = themes as ThemeConfig[];
15+
16+
function getRadiusCSSVars(radius: number) {
17+
return `--radius: ${radius}rem;`;
18+
}
19+
20+
function getRadiusCSSVarsStyles(radius: number) {
21+
const radiusCSS = getRadiusCSSVars(radius);
22+
23+
return radiusCSS;
24+
}
25+
26+
export function generateGlobalStyles() {
27+
return {
28+
'*': {
29+
borderColor: 'hsl(var(--border))'
30+
},
31+
'.lucide': {
32+
height: '1.25em',
33+
width: '1.25em'
34+
},
35+
body: {
36+
background: 'hsl(var(--background))',
37+
color: 'hsl(var(--foreground))'
38+
}
39+
};
40+
}
41+
42+
function getBuiltInTheme(name: string): ThemeCSSVarsVariant {
43+
const theme = builtinThemes.find(t => t.name === name);
44+
45+
if (!theme) {
46+
throw new Error(`Unknown color: ${name}`);
47+
}
48+
49+
return {
50+
name,
51+
...theme.cssVars
52+
};
53+
}
54+
55+
function getColorTheme(color: ColorOptions): ThemeCSSVarsVariant {
56+
let light: ThemeCSSVars;
57+
let dark: ThemeCSSVars;
58+
let name: string;
59+
60+
if (typeof color === 'string') {
61+
name = color;
62+
({ dark, light } = getBuiltInTheme(color));
63+
} else if ('base' in color) {
64+
name = color.base;
65+
({ dark, light } = mergeDeep(getBuiltInTheme(color.base), color));
66+
} else {
67+
name = color.name;
68+
({ dark, light } = color);
69+
}
70+
return { dark, light, name };
71+
}
72+
73+
function createBuiltinFeedbackColorTheme() {
74+
const feedbackColor: FeedbackColorOfThemeCssVarsVariant = {
75+
dark: {
76+
'--carbon': '220 14.3% 95.9%',
77+
'--carbon-foreground': '220.9 39.3% 11%',
78+
'--info': '215 100% 54%',
79+
'--info-foreground': '0 0% 100%',
80+
'--success': '140 79% 45%',
81+
'--success-foreground': '0 0% 100%',
82+
'--warning': '37 91% 55%',
83+
'--warning-foreground': '0 0% 100%'
84+
},
85+
light: {
86+
'--carbon': '240 4% 16%',
87+
'--carbon-foreground': '0 0% 98%',
88+
'--info': '215 100% 54%',
89+
'--info-foreground': '0 0% 100%',
90+
'--success': '140 79% 45%',
91+
'--success-foreground': '0 0% 100%',
92+
'--warning': '37 91% 55%',
93+
'--warning-foreground': '0 0% 100%'
94+
}
95+
};
96+
97+
return feedbackColor;
98+
}
99+
100+
function createBuiltinSidebarColorTheme() {
101+
const sidebarColor: SidebarColorOfThemeCssVarsVariant = {
102+
dark: {
103+
'--sidebar-accent': '240 3.7% 15.9%',
104+
'--sidebar-accent-foreground': '240 4.8% 95.9%',
105+
'--sidebar-background': '240 5.9% 10%',
106+
'--sidebar-border': '240 3.7% 15.9%',
107+
'--sidebar-foreground': '240 4.8% 95.9%',
108+
'--sidebar-primary': '236.9 100% 69.61%',
109+
'--sidebar-primary-foreground': '0 0% 100%',
110+
'--sidebar-ring': '217.2 91.2% 59.8%'
111+
},
112+
light: {
113+
'--sidebar-accent': '240 4.8% 95.9%',
114+
'--sidebar-accent-foreground': '240 5.9% 10%',
115+
'--sidebar-background': '0 0% 98%',
116+
'--sidebar-border': '220 13% 91%',
117+
'--sidebar-foreground': '240 5.3% 26.1%',
118+
'--sidebar-primary': '236.9 100% 69.61%',
119+
'--sidebar-primary-foreground': '0 0% 98%',
120+
'--sidebar-ring': '217.2 91.2% 59.8%'
121+
}
122+
};
123+
124+
return sidebarColor;
125+
}
126+
127+
export function generateCSSVars(theme: PresetShadcnOptions, onlyOne = true): object {
128+
if (Array.isArray(theme)) {
129+
return theme.map(t => generateCSSVars(t, false));
130+
}
131+
132+
const {
133+
color = 'default',
134+
darkSelector = '.dark',
135+
feedbackColor = createBuiltinFeedbackColorTheme(),
136+
radius = 0.5,
137+
sidebar = createBuiltinSidebarColorTheme()
138+
} = theme;
139+
140+
if (!color) {
141+
if (radius) {
142+
return {
143+
root: getRadiusCSSVarsStyles(radius)
144+
};
145+
}
146+
} else {
147+
const { dark, light, name } = getColorTheme(color);
148+
149+
const themeName = !onlyOne && name;
150+
151+
const addThemeName = themeName && themeName !== 'default';
152+
153+
const themeSelector = addThemeName ? `.theme-${themeName}` : ':root';
154+
155+
const darkThemeSelector = addThemeName ? `.theme-${themeName}${darkSelector}` : darkSelector;
156+
157+
return {
158+
[darkThemeSelector]: {
159+
...sidebar.dark,
160+
...feedbackColor.dark,
161+
...dark
162+
},
163+
[themeSelector]: {
164+
...sidebar.light,
165+
...feedbackColor.light,
166+
...light,
167+
'--radius': `${radius}rem`
168+
}
169+
};
170+
}
171+
172+
return {};
173+
}
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
import plugin from 'tailwindcss/plugin';
2+
3+
import { generateCSSVars } from './generate';
4+
import { presetSkyrocUI } from './presets';
5+
import themes from './theme.json';
6+
import { skyrocUITheme } from './themePresets';
7+
import type {
8+
PresetShadcnOptions,
9+
SoybeanUIPluginOptions,
10+
ThemeColorKey,
11+
ThemeConfig,
12+
ThemeConfigColor,
13+
ThemeOptions
14+
} from './types';
15+
16+
export const builtinColors = themes.map(theme => theme.name) as ThemeConfigColor[];
17+
18+
export const builtinColorMap = themes.reduce(
19+
(acc, theme) => {
20+
acc[theme.name as ThemeConfigColor] = theme.cssVars.light['--primary'];
21+
return acc;
22+
},
23+
{} as Record<ThemeConfigColor, string>
24+
);
25+
26+
export const builtinRadiuses = [0, 0.3, 0.5, 0.75, 1] as const;
27+
28+
/**
29+
* The UnoCSS preset for Soybean UI.
30+
*
31+
* @param options - The options for the preset.
32+
* @param globals - Whether to generate global variables, like *.border-color, body.color, body.background.
33+
*/
34+
export const soybeanUIPlugin = (options: SoybeanUIPluginOptions = {}) =>
35+
plugin(
36+
({ addBase, addUtilities }) => {
37+
presetSkyrocUI(addUtilities);
38+
39+
skyrocUITheme(addBase, options);
40+
},
41+
{
42+
theme: {
43+
extend: {
44+
borderRadius: {
45+
lg: 'var(--radius)',
46+
md: 'calc(var(--radius) - 2px)',
47+
sm: 'calc(var(--radius) - 4px)',
48+
xl: 'calc(var(--radius) + 4px)'
49+
},
50+
colors: {
51+
accent: {
52+
DEFAULT: 'hsl(var(--accent))',
53+
foreground: 'hsl(var(--accent-foreground))'
54+
},
55+
background: 'hsl(var(--background))',
56+
border: 'hsl(var(--border))',
57+
carbon: {
58+
50: 'hsl(var(--carbon-50))',
59+
100: 'hsl(var(--carbon-100))',
60+
200: 'hsl(var(--carbon-200))',
61+
300: 'hsl(var(--carbon-300))',
62+
400: 'hsl(var(--carbon-400))',
63+
500: 'hsl(var(--carbon-500))',
64+
600: 'hsl(var(--carbon-600))',
65+
700: 'hsl(var(--carbon-700))',
66+
800: 'hsl(var(--carbon-800))',
67+
900: 'hsl(var(--carbon-900))',
68+
950: 'hsl(var(--carbon-950))',
69+
DEFAULT: 'hsl(var(--carbon))',
70+
foreground: 'hsl(var(--carbon-foreground))'
71+
},
72+
card: {
73+
DEFAULT: 'hsl(var(--card))',
74+
foreground: 'hsl(var(--card-foreground))'
75+
},
76+
destructive: {
77+
50: 'hsl(var(--destructive-50))',
78+
100: 'hsl(var(--destructive-100))',
79+
200: 'hsl(var(--destructive-200))',
80+
300: 'hsl(var(--destructive-300))',
81+
400: 'hsl(var(--destructive-400))',
82+
500: 'hsl(var(--destructive-500))',
83+
600: 'hsl(var(--destructive-600))',
84+
700: 'hsl(var(--destructive-700))',
85+
800: 'hsl(var(--destructive-800))',
86+
900: 'hsl(var(--destructive-900))',
87+
950: 'hsl(var(--destructive-950))',
88+
DEFAULT: 'hsl(var(--destructive))',
89+
foreground: 'hsl(var(--destructive-foreground))'
90+
},
91+
foreground: 'hsl(var(--foreground))',
92+
info: {
93+
50: 'hsl(var(--info-50))',
94+
100: 'hsl(var(--info-100))',
95+
200: 'hsl(var(--info-200))',
96+
300: 'hsl(var(--info-300))',
97+
400: 'hsl(var(--info-400))',
98+
500: 'hsl(var(--info-500))',
99+
600: 'hsl(var(--info-600))',
100+
700: 'hsl(var(--info-700))',
101+
800: 'hsl(var(--info-800))',
102+
900: 'hsl(var(--info-900))',
103+
950: 'hsl(var(--info-950))',
104+
DEFAULT: 'hsl(var(--info))',
105+
foreground: 'hsl(var(--info-foreground))'
106+
},
107+
input: 'hsl(var(--input))',
108+
muted: {
109+
DEFAULT: 'hsl(var(--muted))',
110+
foreground: 'hsl(var(--muted-foreground))'
111+
},
112+
popover: {
113+
DEFAULT: 'hsl(var(--popover))',
114+
foreground: 'hsl(var(--popover-foreground))'
115+
},
116+
// 示例部分(你也可以写一个生成器函数自动输出完整变量映射)
117+
primary: {
118+
50: 'hsl(var(--primary-50))',
119+
100: 'hsl(var(--primary-100))',
120+
200: 'hsl(var(--primary-200))',
121+
300: 'hsl(var(--primary-300))',
122+
400: 'hsl(var(--primary-400))',
123+
500: 'hsl(var(--primary-500))',
124+
600: 'hsl(var(--primary-600))',
125+
700: 'hsl(var(--primary-700))',
126+
800: 'hsl(var(--primary-800))',
127+
900: 'hsl(var(--primary-900))',
128+
950: 'hsl(var(--primary-950))',
129+
DEFAULT: 'hsl(var(--primary))',
130+
foreground: 'hsl(var(--primary-foreground))'
131+
},
132+
ring: 'hsl(var(--ring))',
133+
secondary: {
134+
DEFAULT: 'hsl(var(--secondary))',
135+
foreground: 'hsl(var(--secondary-foreground))'
136+
},
137+
'sidebar-accent': 'hsl(var(--sidebar-accent))',
138+
'sidebar-accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
139+
'sidebar-background': 'hsl(var(--sidebar-background))',
140+
'sidebar-border': 'hsl(var(--sidebar-border))',
141+
'sidebar-foreground': 'hsl(var(--sidebar-foreground))',
142+
'sidebar-primary': 'hsl(var(--sidebar-primary))',
143+
'sidebar-primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
144+
'sidebar-ring': 'hsl(var(--sidebar-ring))',
145+
success: {
146+
50: 'hsl(var(--success-50))',
147+
100: 'hsl(var(--success-100))',
148+
200: 'hsl(var(--success-200))',
149+
300: 'hsl(var(--success-300))',
150+
400: 'hsl(var(--success-400))',
151+
500: 'hsl(var(--success-500))',
152+
600: 'hsl(var(--success-600))',
153+
700: 'hsl(var(--success-700))',
154+
800: 'hsl(var(--success-800))',
155+
900: 'hsl(var(--success-900))',
156+
950: 'hsl(var(--success-950))',
157+
DEFAULT: 'hsl(var(--success))',
158+
foreground: 'hsl(var(--success-foreground))'
159+
},
160+
warning: {
161+
50: 'hsl(var(--warning-50))',
162+
100: 'hsl(var(--warning-100))',
163+
200: 'hsl(var(--warning-200))',
164+
300: 'hsl(var(--warning-300))',
165+
400: 'hsl(var(--warning-400))',
166+
500: 'hsl(var(--warning-500))',
167+
600: 'hsl(var(--warning-600))',
168+
700: 'hsl(var(--warning-700))',
169+
800: 'hsl(var(--warning-800))',
170+
900: 'hsl(var(--warning-900))',
171+
950: 'hsl(var(--warning-950))',
172+
DEFAULT: 'hsl(var(--warning))',
173+
foreground: 'hsl(var(--warning-foreground))'
174+
}
175+
},
176+
fontSize: {
177+
'2xs': ['0.625rem', '0.75rem'],
178+
'3xs': ['0.5rem', '0.625rem'],
179+
'4xs': ['0.375rem', '0.5rem']
180+
}
181+
}
182+
}
183+
}
184+
);
185+
186+
export { generateCSSVars };
187+
188+
export type { PresetShadcnOptions, ThemeColorKey, ThemeConfig, ThemeConfigColor, ThemeOptions };
189+
190+
export default soybeanUIPlugin;

0 commit comments

Comments
 (0)