Skip to content

Commit 15ec240

Browse files
chore: import new ui system (#73)
* chore: import new ui system * chore: modified padding and rounded * chore: remove vertical padding * chore: add shadow * chore: add shadcn popover (#76) * chore: add circle progress (#77) * chore: add input (#78) * feat: add shadcn toast (#80) * chore: initial import * chore: style * chore: important * chore: add dropdown menu (#81) * feat: add shadcn tooltip (#79) * chore: initial import * chore: style * chore: fix text size * chore: add tooltip shortcut * chore: code cleanup and add flex col * chore: add avatar (#82) * chore: add dialog (#83) --------- Co-authored-by: Richard Shiue <71320345+richardshiue@users.noreply.github.com>
1 parent 0f281d1 commit 15ec240

36 files changed

+5599
-151
lines changed

components.json

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": false,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "",
8+
"css": "src/styles/global.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/utils",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
},
20+
"iconLibrary": "lucide"
21+
}

package.json

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"test:components": "cypress run --component --browser chrome --headless",
1616
"test:unit": "jest --coverage",
1717
"test:cy": "cypress run",
18-
"coverage": "pnpm run test:unit && pnpm run test:components"
18+
"coverage": "pnpm run test:unit && pnpm run test:components",
19+
"generate-tokens": "node scripts/system-token/convert-tokens.cjs"
1920
},
2021
"dependencies": {
2122
"@appflowyinc/ai-chat": "0.1.25",
@@ -30,11 +31,21 @@
3031
"@mui/icons-material": "^5.11.11",
3132
"@mui/material": "6.0.0-alpha.2",
3233
"@mui/x-date-pickers-pro": "^6.18.2",
34+
"@radix-ui/react-avatar": "^1.1.3",
35+
"@radix-ui/react-dialog": "^1.1.6",
36+
"@radix-ui/react-dropdown-menu": "^2.1.6",
37+
"@radix-ui/react-label": "^2.1.2",
38+
"@radix-ui/react-popover": "^1.1.6",
39+
"@radix-ui/react-separator": "^1.1.2",
40+
"@radix-ui/react-slot": "^1.1.2",
41+
"@radix-ui/react-tooltip": "^1.1.8",
3342
"@reduxjs/toolkit": "2.0.0",
3443
"@slate-yjs/core": "^1.0.2",
3544
"@types/react-swipeable-views": "^0.13.4",
3645
"async-retry": "^1.3.3",
3746
"axios": "^1.6.8",
47+
"class-variance-authority": "^0.7.1",
48+
"clsx": "^2.1.1",
3849
"colorthief": "^2.4.0",
3950
"dayjs": "^1.11.9",
4051
"decimal.js": "^10.4.3",
@@ -59,8 +70,10 @@
5970
"katex": "^0.16.7",
6071
"lightgallery": "^2.7.2",
6172
"lodash-es": "^4.17.21",
73+
"lucide-react": "^0.485.0",
6274
"mermaid": "^11.4.1",
6375
"nanoid": "^4.0.0",
76+
"next-themes": "^0.4.6",
6477
"notistack": "^3.0.1",
6578
"numeral": "^2.0.6",
6679
"prismjs": "^1.29.0",
@@ -100,7 +113,10 @@
100113
"slate-history": "^0.100.0",
101114
"slate-react": "^0.101.3",
102115
"smooth-scroll-into-view-if-needed": "^2.0.2",
116+
"sonner": "^2.0.3",
117+
"tailwind-merge": "^3.0.2",
103118
"ts-results": "^3.3.0",
119+
"tw-animate-css": "^1.2.5",
104120
"unified": "^11.0.5",
105121
"unist": "^0.0.1",
106122
"unsplash-js": "^7.0.19",

pnpm-lock.yaml

Lines changed: 223 additions & 30 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
const fs = require('fs');
2+
const path = require('path');
3+
4+
// Ensure target directory exists
5+
function ensureDirectoryExists (dirPath) {
6+
if (!fs.existsSync(dirPath)) {
7+
fs.mkdirSync(dirPath, { recursive: true });
8+
console.log(`Created directory: ${dirPath}`);
9+
}
10+
}
11+
12+
// Read JSON file
13+
function readJsonFile (filePath) {
14+
try {
15+
const data = fs.readFileSync(filePath, 'utf8');
16+
return JSON.parse(data);
17+
} catch (error) {
18+
console.error(`Error reading file ${filePath}:`, error);
19+
process.exit(1);
20+
}
21+
}
22+
23+
// Resolve reference values
24+
function resolveReferences (tokens, primitiveTokens) {
25+
const resolvedTokens = JSON.parse(JSON.stringify(tokens));
26+
27+
function resolveValue (value, visitedRefs = new Set()) {
28+
if (typeof value !== 'string' || !value.startsWith('{') || !value.endsWith('}')) {
29+
return value;
30+
}
31+
32+
const reference = value.slice(1, -1);
33+
if (visitedRefs.has(reference)) {
34+
console.warn(`Circular reference detected: ${reference}`);
35+
return value;
36+
}
37+
38+
visitedRefs.add(reference);
39+
40+
// Split reference path
41+
const parts = reference.split('.');
42+
let current;
43+
44+
// First look in primitive tokens
45+
if (primitiveTokens) {
46+
current = primitiveTokens;
47+
for (const part of parts) {
48+
if (current && current[part]) {
49+
current = current[part];
50+
} else {
51+
current = null;
52+
break;
53+
}
54+
}
55+
}
56+
57+
// If not found in primitives, look in current tokens
58+
if (!current) {
59+
current = resolvedTokens;
60+
for (const part of parts) {
61+
if (current && current[part]) {
62+
current = current[part];
63+
} else {
64+
console.warn(`Reference not found: ${reference}`);
65+
return value;
66+
}
67+
}
68+
}
69+
70+
if (current && current.$value) {
71+
return resolveValue(current.$value, visitedRefs);
72+
}
73+
74+
return current;
75+
}
76+
77+
function processObject (obj) {
78+
for (const key in obj) {
79+
if (typeof obj[key] === 'object' && obj[key] !== null) {
80+
processObject(obj[key]);
81+
} else if (key === '$value') {
82+
obj.$value = resolveValue(obj.$value);
83+
}
84+
}
85+
}
86+
87+
processObject(resolvedTokens);
88+
return resolvedTokens;
89+
}
90+
91+
// Convert to CSS variables
92+
function convertToCssVariables (tokens, prefix = '', isDarkTheme = false, sourceFile) {
93+
let variableNames = []; // Store all generated variable names
94+
95+
// Create CSS file header with warning comments
96+
let css = `/**
97+
* Design system ${isDarkTheme ? 'dark' : 'light'} theme variables
98+
* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
99+
*
100+
* Generated from: ${sourceFile}
101+
* Generated time: ${new Date().toISOString()}
102+
*
103+
* To modify these values, edit the source JSON files and run the token conversion script:
104+
* node scripts/system-token/convert-tokens.cjs
105+
*/
106+
107+
`;
108+
109+
// Select root selector based on theme
110+
const rootSelector = isDarkTheme ? ':root[data-dark-mode=true]' : ':root';
111+
112+
function processTokens (obj, path = '') {
113+
for (const key in obj) {
114+
const newPath = path ? `${path}-${key}` : key;
115+
116+
// If object but not a value object (i.e., doesn't have $value)
117+
if (typeof obj[key] === 'object' && obj[key] !== null && !obj[key].$value) {
118+
processTokens(obj[key], newPath);
119+
}
120+
// If it's a value object (has $value)
121+
else if (typeof obj[key] === 'object' && obj[key] !== null && obj[key].$value) {
122+
const token = obj[key];
123+
const variableName = `--${prefix}${newPath.toLowerCase().replace(/_/g, '-')}`;
124+
125+
let value = token.$value;
126+
127+
// Add to variable names list
128+
variableNames.push(variableName.substring(2)); // Remove leading --
129+
130+
// Format value based on type
131+
if (token.$type === 'dimension') {
132+
// Keep dimension values as is, they already include units like px
133+
css += ` ${variableName}: ${value};\n`;
134+
} else if (token.$type === 'color') {
135+
css += ` ${variableName}: ${value};\n`;
136+
} else {
137+
css += ` ${variableName}: ${value};\n`;
138+
}
139+
}
140+
}
141+
}
142+
143+
css += `${rootSelector} {\n`;
144+
processTokens(tokens);
145+
css += '}\n';
146+
147+
return { css, variableNames };
148+
}
149+
150+
// Generate Tailwind color config from CSS variable names
151+
function createTailwindColorsFromVariables (variableNames) {
152+
const colors = {};
153+
154+
// Group by prefix
155+
variableNames.forEach(varName => {
156+
const parts = varName.split('-');
157+
const mainCategory = parts[0];
158+
159+
// Handle main category
160+
if (!colors[mainCategory]) {
161+
colors[mainCategory] = {};
162+
}
163+
164+
// Generate key without main category
165+
const subKey = parts.slice(1).join('-');
166+
167+
// Use 'DEFAULT' if empty
168+
const finalKey = subKey || 'DEFAULT';
169+
170+
// Set CSS variable reference
171+
colors[mainCategory][finalKey] = `var(--${varName})`;
172+
});
173+
174+
// Fix special categories to match Tailwind conventions
175+
delete colors['spacing'];
176+
delete colors['border-radius'];
177+
delete colors['shadow'];
178+
179+
// Generate CommonJS module code
180+
const tailwindCode = `/**
181+
* TailwindCSS color configuration
182+
* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
183+
*
184+
* This file is auto-generated by convert-tokens.cjs script
185+
* Generation time: ${new Date().toISOString()}
186+
*
187+
* To modify these colors, edit the source JSON files and run the token conversion script:
188+
* node scripts/system-token/convert-tokens.cjs
189+
*
190+
* These colors reference CSS variables, ensure the corresponding CSS files are loaded
191+
*/
192+
193+
module.exports = ${JSON.stringify(colors, null, 2)};
194+
`;
195+
196+
return tailwindCode;
197+
}
198+
199+
// Main function
200+
function convertDesignTokens (primitiveFilePath, semanticFilePath, outputFilePath, isDarkTheme = false) {
201+
// Read files
202+
const primitiveTokens = readJsonFile(primitiveFilePath);
203+
const semanticTokens = readJsonFile(semanticFilePath);
204+
205+
// Resolve references
206+
const resolvedTokens = resolveReferences(semanticTokens, primitiveTokens);
207+
208+
// Convert to CSS with source file information
209+
const { css, variableNames } = convertToCssVariables(
210+
resolvedTokens,
211+
'',
212+
isDarkTheme,
213+
path.basename(semanticFilePath),
214+
);
215+
216+
// Ensure output directory exists
217+
const outputDir = path.dirname(outputFilePath);
218+
ensureDirectoryExists(outputDir);
219+
220+
// Write file
221+
fs.writeFileSync(outputFilePath, css, 'utf8');
222+
console.log(`CSS variables written to ${outputFilePath}`);
223+
224+
return { variableNames };
225+
}
226+
227+
// Define project root directory
228+
const projectRoot = path.resolve(__dirname, '../..'); // Go up two levels from scripts/system-token to project root
229+
230+
// Define input and output paths
231+
const inputDir = path.join(__dirname); // Current script directory
232+
const cssOutputDir = path.join(projectRoot, 'src/styles/variables');
233+
const tailwindOutputDir = path.join(projectRoot, 'tailwind');
234+
235+
// Ensure output directories exist
236+
ensureDirectoryExists(cssOutputDir);
237+
ensureDirectoryExists(tailwindOutputDir);
238+
239+
// Execute conversion
240+
console.log('Converting design tokens to CSS variables...');
241+
242+
// Collect all variable names
243+
let allVariableNames = [];
244+
245+
// Convert light theme (default theme)
246+
const lightResult = convertDesignTokens(
247+
path.join(inputDir, 'primitive.json'),
248+
path.join(inputDir, 'semantic.light.json'),
249+
path.join(cssOutputDir, 'semantic.light.css'),
250+
false, // Light theme
251+
);
252+
allVariableNames = allVariableNames.concat(lightResult.variableNames);
253+
254+
// Convert dark theme (using data-dark-mode attribute)
255+
const darkResult = convertDesignTokens(
256+
path.join(inputDir, 'primitive.json'),
257+
path.join(inputDir, 'semantic.dark.json'),
258+
path.join(cssOutputDir, 'semantic.dark.css'),
259+
true, // Dark theme
260+
);
261+
// Dark theme variables are the same as light theme, no need to merge
262+
263+
// Generate Tailwind color configuration
264+
console.log('Generating Tailwind color configuration with CSS variable references...');
265+
const tailwindColors = createTailwindColorsFromVariables(allVariableNames);
266+
fs.writeFileSync(path.join(tailwindOutputDir, 'new-colors.cjs'), tailwindColors);
267+
console.log(`Tailwind colors written to ${path.join(tailwindOutputDir, 'new-colors.cjs')}`);
268+
269+
console.log('Conversion completed successfully!');

0 commit comments

Comments
 (0)