mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2025-11-29 02:38:00 +08:00
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>
This commit is contained in:
269
scripts/system-token/convert-tokens.cjs
Normal file
269
scripts/system-token/convert-tokens.cjs
Normal file
@@ -0,0 +1,269 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Ensure target directory exists
|
||||
function ensureDirectoryExists (dirPath) {
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
fs.mkdirSync(dirPath, { recursive: true });
|
||||
console.log(`Created directory: ${dirPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Read JSON file
|
||||
function readJsonFile (filePath) {
|
||||
try {
|
||||
const data = fs.readFileSync(filePath, 'utf8');
|
||||
return JSON.parse(data);
|
||||
} catch (error) {
|
||||
console.error(`Error reading file ${filePath}:`, error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve reference values
|
||||
function resolveReferences (tokens, primitiveTokens) {
|
||||
const resolvedTokens = JSON.parse(JSON.stringify(tokens));
|
||||
|
||||
function resolveValue (value, visitedRefs = new Set()) {
|
||||
if (typeof value !== 'string' || !value.startsWith('{') || !value.endsWith('}')) {
|
||||
return value;
|
||||
}
|
||||
|
||||
const reference = value.slice(1, -1);
|
||||
if (visitedRefs.has(reference)) {
|
||||
console.warn(`Circular reference detected: ${reference}`);
|
||||
return value;
|
||||
}
|
||||
|
||||
visitedRefs.add(reference);
|
||||
|
||||
// Split reference path
|
||||
const parts = reference.split('.');
|
||||
let current;
|
||||
|
||||
// First look in primitive tokens
|
||||
if (primitiveTokens) {
|
||||
current = primitiveTokens;
|
||||
for (const part of parts) {
|
||||
if (current && current[part]) {
|
||||
current = current[part];
|
||||
} else {
|
||||
current = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If not found in primitives, look in current tokens
|
||||
if (!current) {
|
||||
current = resolvedTokens;
|
||||
for (const part of parts) {
|
||||
if (current && current[part]) {
|
||||
current = current[part];
|
||||
} else {
|
||||
console.warn(`Reference not found: ${reference}`);
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (current && current.$value) {
|
||||
return resolveValue(current.$value, visitedRefs);
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
function processObject (obj) {
|
||||
for (const key in obj) {
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null) {
|
||||
processObject(obj[key]);
|
||||
} else if (key === '$value') {
|
||||
obj.$value = resolveValue(obj.$value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
processObject(resolvedTokens);
|
||||
return resolvedTokens;
|
||||
}
|
||||
|
||||
// Convert to CSS variables
|
||||
function convertToCssVariables (tokens, prefix = '', isDarkTheme = false, sourceFile) {
|
||||
let variableNames = []; // Store all generated variable names
|
||||
|
||||
// Create CSS file header with warning comments
|
||||
let css = `/**
|
||||
* Design system ${isDarkTheme ? 'dark' : 'light'} theme variables
|
||||
* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
|
||||
*
|
||||
* Generated from: ${sourceFile}
|
||||
* Generated time: ${new Date().toISOString()}
|
||||
*
|
||||
* To modify these values, edit the source JSON files and run the token conversion script:
|
||||
* node scripts/system-token/convert-tokens.cjs
|
||||
*/
|
||||
|
||||
`;
|
||||
|
||||
// Select root selector based on theme
|
||||
const rootSelector = isDarkTheme ? ':root[data-dark-mode=true]' : ':root';
|
||||
|
||||
function processTokens (obj, path = '') {
|
||||
for (const key in obj) {
|
||||
const newPath = path ? `${path}-${key}` : key;
|
||||
|
||||
// If object but not a value object (i.e., doesn't have $value)
|
||||
if (typeof obj[key] === 'object' && obj[key] !== null && !obj[key].$value) {
|
||||
processTokens(obj[key], newPath);
|
||||
}
|
||||
// If it's a value object (has $value)
|
||||
else if (typeof obj[key] === 'object' && obj[key] !== null && obj[key].$value) {
|
||||
const token = obj[key];
|
||||
const variableName = `--${prefix}${newPath.toLowerCase().replace(/_/g, '-')}`;
|
||||
|
||||
let value = token.$value;
|
||||
|
||||
// Add to variable names list
|
||||
variableNames.push(variableName.substring(2)); // Remove leading --
|
||||
|
||||
// Format value based on type
|
||||
if (token.$type === 'dimension') {
|
||||
// Keep dimension values as is, they already include units like px
|
||||
css += ` ${variableName}: ${value};\n`;
|
||||
} else if (token.$type === 'color') {
|
||||
css += ` ${variableName}: ${value};\n`;
|
||||
} else {
|
||||
css += ` ${variableName}: ${value};\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
css += `${rootSelector} {\n`;
|
||||
processTokens(tokens);
|
||||
css += '}\n';
|
||||
|
||||
return { css, variableNames };
|
||||
}
|
||||
|
||||
// Generate Tailwind color config from CSS variable names
|
||||
function createTailwindColorsFromVariables (variableNames) {
|
||||
const colors = {};
|
||||
|
||||
// Group by prefix
|
||||
variableNames.forEach(varName => {
|
||||
const parts = varName.split('-');
|
||||
const mainCategory = parts[0];
|
||||
|
||||
// Handle main category
|
||||
if (!colors[mainCategory]) {
|
||||
colors[mainCategory] = {};
|
||||
}
|
||||
|
||||
// Generate key without main category
|
||||
const subKey = parts.slice(1).join('-');
|
||||
|
||||
// Use 'DEFAULT' if empty
|
||||
const finalKey = subKey || 'DEFAULT';
|
||||
|
||||
// Set CSS variable reference
|
||||
colors[mainCategory][finalKey] = `var(--${varName})`;
|
||||
});
|
||||
|
||||
// Fix special categories to match Tailwind conventions
|
||||
delete colors['spacing'];
|
||||
delete colors['border-radius'];
|
||||
delete colors['shadow'];
|
||||
|
||||
// Generate CommonJS module code
|
||||
const tailwindCode = `/**
|
||||
* TailwindCSS color configuration
|
||||
* AUTO-GENERATED FILE - DO NOT EDIT DIRECTLY
|
||||
*
|
||||
* This file is auto-generated by convert-tokens.cjs script
|
||||
* Generation time: ${new Date().toISOString()}
|
||||
*
|
||||
* To modify these colors, edit the source JSON files and run the token conversion script:
|
||||
* node scripts/system-token/convert-tokens.cjs
|
||||
*
|
||||
* These colors reference CSS variables, ensure the corresponding CSS files are loaded
|
||||
*/
|
||||
|
||||
module.exports = ${JSON.stringify(colors, null, 2)};
|
||||
`;
|
||||
|
||||
return tailwindCode;
|
||||
}
|
||||
|
||||
// Main function
|
||||
function convertDesignTokens (primitiveFilePath, semanticFilePath, outputFilePath, isDarkTheme = false) {
|
||||
// Read files
|
||||
const primitiveTokens = readJsonFile(primitiveFilePath);
|
||||
const semanticTokens = readJsonFile(semanticFilePath);
|
||||
|
||||
// Resolve references
|
||||
const resolvedTokens = resolveReferences(semanticTokens, primitiveTokens);
|
||||
|
||||
// Convert to CSS with source file information
|
||||
const { css, variableNames } = convertToCssVariables(
|
||||
resolvedTokens,
|
||||
'',
|
||||
isDarkTheme,
|
||||
path.basename(semanticFilePath),
|
||||
);
|
||||
|
||||
// Ensure output directory exists
|
||||
const outputDir = path.dirname(outputFilePath);
|
||||
ensureDirectoryExists(outputDir);
|
||||
|
||||
// Write file
|
||||
fs.writeFileSync(outputFilePath, css, 'utf8');
|
||||
console.log(`CSS variables written to ${outputFilePath}`);
|
||||
|
||||
return { variableNames };
|
||||
}
|
||||
|
||||
// Define project root directory
|
||||
const projectRoot = path.resolve(__dirname, '../..'); // Go up two levels from scripts/system-token to project root
|
||||
|
||||
// Define input and output paths
|
||||
const inputDir = path.join(__dirname); // Current script directory
|
||||
const cssOutputDir = path.join(projectRoot, 'src/styles/variables');
|
||||
const tailwindOutputDir = path.join(projectRoot, 'tailwind');
|
||||
|
||||
// Ensure output directories exist
|
||||
ensureDirectoryExists(cssOutputDir);
|
||||
ensureDirectoryExists(tailwindOutputDir);
|
||||
|
||||
// Execute conversion
|
||||
console.log('Converting design tokens to CSS variables...');
|
||||
|
||||
// Collect all variable names
|
||||
let allVariableNames = [];
|
||||
|
||||
// Convert light theme (default theme)
|
||||
const lightResult = convertDesignTokens(
|
||||
path.join(inputDir, 'primitive.json'),
|
||||
path.join(inputDir, 'semantic.light.json'),
|
||||
path.join(cssOutputDir, 'semantic.light.css'),
|
||||
false, // Light theme
|
||||
);
|
||||
allVariableNames = allVariableNames.concat(lightResult.variableNames);
|
||||
|
||||
// Convert dark theme (using data-dark-mode attribute)
|
||||
const darkResult = convertDesignTokens(
|
||||
path.join(inputDir, 'primitive.json'),
|
||||
path.join(inputDir, 'semantic.dark.json'),
|
||||
path.join(cssOutputDir, 'semantic.dark.css'),
|
||||
true, // Dark theme
|
||||
);
|
||||
// Dark theme variables are the same as light theme, no need to merge
|
||||
|
||||
// Generate Tailwind color configuration
|
||||
console.log('Generating Tailwind color configuration with CSS variable references...');
|
||||
const tailwindColors = createTailwindColorsFromVariables(allVariableNames);
|
||||
fs.writeFileSync(path.join(tailwindOutputDir, 'new-colors.cjs'), tailwindColors);
|
||||
console.log(`Tailwind colors written to ${path.join(tailwindOutputDir, 'new-colors.cjs')}`);
|
||||
|
||||
console.log('Conversion completed successfully!');
|
||||
Reference in New Issue
Block a user