mirror of
https://github.com/AppFlowy-IO/AppFlowy-Web.git
synced 2025-08-06 19:20:25 +08:00

* 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>
269 lines
7.9 KiB
JavaScript
269 lines
7.9 KiB
JavaScript
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!'); |