diff --git a/.github/workflows/build-design-tokens.yml b/.github/workflows/build-design-tokens.yml new file mode 100644 index 0000000..6345a59 --- /dev/null +++ b/.github/workflows/build-design-tokens.yml @@ -0,0 +1,49 @@ +name: Build Design Tokens + +on: + push: + branches: + - develop + paths: + - 'packages/design-tokens/tokens/**' + +concurrency: + group: build-design-tokens + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + token: ${{ secrets.YML_GITHUB_TOKEN }} + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 9 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build design tokens + run: pnpm --filter @infra-support/design-tokens build + + - name: Commit generated dist + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add packages/design-tokens/dist/ + git diff --cached --quiet && echo "No changes to commit" || \ + (git commit -m "chore(design-tokens): auto-build from Figma tokens" && git push) diff --git a/.gitignore b/.gitignore index a6c6742..af9f296 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ yarn.lock # Build outputs dist/ +!packages/design-tokens/dist/ build/ .next/ .turbo/ diff --git a/packages/design-tokens/dist/css/variables.css b/packages/design-tokens/dist/css/variables.css new file mode 100644 index 0000000..7e36d1a --- /dev/null +++ b/packages/design-tokens/dist/css/variables.css @@ -0,0 +1,30 @@ +/** + * Do not edit directly, this file was auto-generated. + */ + +:root { + --color-primary: #3B82F6; + --color-primary-hover: #2563EB; + --color-secondary: #8B5CF6; + --color-surface: #F8FAFC; + --color-surface-elevated: #FFFFFF; + --color-text-default: #111827; + --color-text-muted: #6B7280; + --color-text-inverse: #FFFFFF; + --color-border: #E5E7EB; + --color-success: #10B981; + --color-warning: #F59E0B; + --color-error: #EF4444; + --font-size-xl: 2rem; + --font-size-lg: 1.5rem; + --font-size-md: 1rem; + --font-size-sm: 0.875rem; + --font-size-xs: 0.75rem; + --font-weight-regular: 400; + --font-weight-medium: 500; + --font-weight-bold: 700; + --font-family-base: Pretendard, sans-serif; + --line-height-tight: 1.25; + --line-height-normal: 1.6; + --line-height-relaxed: 1.75; +} diff --git a/packages/design-tokens/dist/js/tokens.d.ts b/packages/design-tokens/dist/js/tokens.d.ts new file mode 100644 index 0000000..fa9c54c --- /dev/null +++ b/packages/design-tokens/dist/js/tokens.d.ts @@ -0,0 +1,28 @@ +/** + * Do not edit directly, this file was auto-generated. + */ + +export const colorPrimary: string; +export const colorPrimaryHover: string; +export const colorSecondary: string; +export const colorSurface: string; +export const colorSurfaceElevated: string; +export const colorTextDefault: string; +export const colorTextMuted: string; +export const colorTextInverse: string; +export const colorBorder: string; +export const colorSuccess: string; +export const colorWarning: string; +export const colorError: string; +export const fontSizeXl: string; +export const fontSizeLg: string; +export const fontSizeMd: string; +export const fontSizeSm: string; +export const fontSizeXs: string; +export const fontWeightRegular: string; +export const fontWeightMedium: string; +export const fontWeightBold: string; +export const fontFamilyBase: string; +export const lineHeightTight: string; +export const lineHeightNormal: string; +export const lineHeightRelaxed: string; diff --git a/packages/design-tokens/dist/js/tokens.js b/packages/design-tokens/dist/js/tokens.js new file mode 100644 index 0000000..62dc736 --- /dev/null +++ b/packages/design-tokens/dist/js/tokens.js @@ -0,0 +1,28 @@ +/** + * Do not edit directly, this file was auto-generated. + */ + +export const colorPrimary = "#3b82f6"; +export const colorPrimaryHover = "#2563eb"; +export const colorSecondary = "#8b5cf6"; +export const colorSurface = "#f8fafc"; +export const colorSurfaceElevated = "#ffffff"; +export const colorTextDefault = "#111827"; +export const colorTextMuted = "#6b7280"; +export const colorTextInverse = "#ffffff"; +export const colorBorder = "#e5e7eb"; +export const colorSuccess = "#10b981"; +export const colorWarning = "#f59e0b"; +export const colorError = "#ef4444"; +export const fontSizeXl = "2rem"; +export const fontSizeLg = "1.5rem"; +export const fontSizeMd = "1rem"; +export const fontSizeSm = "0.875rem"; +export const fontSizeXs = "0.75rem"; +export const fontWeightRegular = "400"; +export const fontWeightMedium = "500"; +export const fontWeightBold = "700"; +export const fontFamilyBase = "Pretendard, sans-serif"; +export const lineHeightTight = "1.25"; +export const lineHeightNormal = "1.6"; +export const lineHeightRelaxed = "1.75"; diff --git a/packages/design-tokens/dist/tailwind/preset.cjs b/packages/design-tokens/dist/tailwind/preset.cjs new file mode 100644 index 0000000..b869d9f --- /dev/null +++ b/packages/design-tokens/dist/tailwind/preset.cjs @@ -0,0 +1,41 @@ +/** Auto-generated by Style Dictionary — do not edit */ +module.exports = { + "theme": { + "extend": { + "colors": { + "primary": "#3b82f6", + "primary-hover": "#2563eb", + "secondary": "#8b5cf6", + "surface": "#f8fafc", + "surface-elevated": "#ffffff", + "text-default": "#111827", + "text-muted": "#6b7280", + "text-inverse": "#ffffff", + "border": "#e5e7eb", + "success": "#10b981", + "warning": "#f59e0b", + "error": "#ef4444" + }, + "fontSize": { + "xl": "2rem", + "lg": "1.5rem", + "md": "1rem", + "sm": "0.875rem", + "xs": "0.75rem" + }, + "fontWeight": { + "regular": "400", + "medium": "500", + "bold": "700" + }, + "fontFamily": { + "base": "Pretendard, sans-serif" + }, + "lineHeight": { + "tight": "1.25", + "normal": "1.6", + "relaxed": "1.75" + } + } + } +}; diff --git a/packages/design-tokens/package.json b/packages/design-tokens/package.json new file mode 100644 index 0000000..79b68d1 --- /dev/null +++ b/packages/design-tokens/package.json @@ -0,0 +1,18 @@ +{ + "name": "@infra-support/design-tokens", + "version": "1.0.0", + "type": "module", + "exports": { + ".": "./dist/js/tokens.js", + "./tailwind": "./dist/tailwind/preset.cjs", + "./css": "./dist/css/variables.css" + }, + "scripts": { + "build": "node sd.config.mjs", + "clean": "rm -rf dist" + }, + "dependencies": { + "@tokens-studio/sd-transforms": "^1.2.0", + "style-dictionary": "^4.0.0" + } +} diff --git a/packages/design-tokens/sd.config.mjs b/packages/design-tokens/sd.config.mjs new file mode 100644 index 0000000..0314ffb --- /dev/null +++ b/packages/design-tokens/sd.config.mjs @@ -0,0 +1,107 @@ +import StyleDictionary from 'style-dictionary'; +import { register } from '@tokens-studio/sd-transforms'; + +register(StyleDictionary); + +// CSS vars는 kebab-case 관례 사용 (tokens-studio 기본은 camelCase) +StyleDictionary.registerTransformGroup({ + name: 'tokens-studio/css', + transforms: [ + 'ts/descriptionToComment', + 'ts/size/px', + 'ts/opacity', + 'ts/size/lineheight', + 'ts/typography/fontWeight', + 'ts/resolveMath', + 'ts/size/css/letterspacing', + 'ts/color/css/hexrgba', + 'ts/color/modifiers', + 'name/kebab', + ], +}); + +function setDeep(obj, path, value) { + let cur = obj; + for (let i = 0; i < path.length - 1; i++) { + if (!cur[path[i]]) cur[path[i]] = {}; + cur = cur[path[i]]; + } + cur[path[path.length - 1]] = value; +} + +StyleDictionary.registerFormat({ + name: 'javascript/tailwind-preset', + format({ dictionary }) { + const colors = {}; + const fontSize = {}; + const fontWeight = {}; + const fontFamily = {}; + const lineHeight = {}; + + for (const token of dictionary.allTokens) { + const [category, ...rest] = token.path; + const key = rest.join('-'); + + if (category === 'color') setDeep(colors, rest, token.value); + else if (category === 'font-size') fontSize[key] = token.value; + else if (category === 'font-weight') fontWeight[key] = token.value; + else if (category === 'font-family') fontFamily[key] = token.value; + else if (category === 'line-height') lineHeight[key] = token.value; + } + + const preset = { + theme: { + extend: { colors, fontSize, fontWeight, fontFamily, lineHeight }, + }, + }; + + return ( + '/** Auto-generated by Style Dictionary — do not edit */\n' + + `module.exports = ${JSON.stringify(preset, null, 2)};\n` + ); + }, +}); + +const sd = new StyleDictionary({ + source: ['tokens/**/*.json', '!tokens/$metadata.json'], + preprocessors: ['tokens-studio'], + platforms: { + css: { + transformGroup: 'tokens-studio/css', + buildPath: 'dist/css/', + files: [ + { + destination: 'variables.css', + format: 'css/variables', + options: { selector: ':root', outputReferences: false }, + }, + ], + }, + tailwind: { + transformGroup: 'tokens-studio', + buildPath: 'dist/tailwind/', + files: [ + { + destination: 'preset.cjs', + format: 'javascript/tailwind-preset', + }, + ], + }, + js: { + transformGroup: 'tokens-studio', + buildPath: 'dist/js/', + files: [ + { + destination: 'tokens.js', + format: 'javascript/es6', + }, + { + destination: 'tokens.d.ts', + format: 'typescript/es6-declarations', + }, + ], + }, + }, +}); + +await sd.buildAllPlatforms(); diff --git a/packages/design-tokens/tokens/$metadata.json b/packages/design-tokens/tokens/$metadata.json new file mode 100644 index 0000000..62fc583 --- /dev/null +++ b/packages/design-tokens/tokens/$metadata.json @@ -0,0 +1,3 @@ +{ + "tokenSetOrder": ["colors", "typography"] +} diff --git a/packages/design-tokens/tokens/colors.json b/packages/design-tokens/tokens/colors.json new file mode 100644 index 0000000..17db2fd --- /dev/null +++ b/packages/design-tokens/tokens/colors.json @@ -0,0 +1,16 @@ +{ + "color": { + "primary": { "value": "#3B82F6", "type": "color" }, + "primary-hover": { "value": "#2563EB", "type": "color" }, + "secondary": { "value": "#8B5CF6", "type": "color" }, + "surface": { "value": "#F8FAFC", "type": "color" }, + "surface-elevated": { "value": "#FFFFFF", "type": "color" }, + "text-default": { "value": "#111827", "type": "color" }, + "text-muted": { "value": "#6B7280", "type": "color" }, + "text-inverse": { "value": "#FFFFFF", "type": "color" }, + "border": { "value": "#E5E7EB", "type": "color" }, + "success": { "value": "#10B981", "type": "color" }, + "warning": { "value": "#F59E0B", "type": "color" }, + "error": { "value": "#EF4444", "type": "color" } + } +} diff --git a/packages/design-tokens/tokens/typography.json b/packages/design-tokens/tokens/typography.json new file mode 100644 index 0000000..4e04a9b --- /dev/null +++ b/packages/design-tokens/tokens/typography.json @@ -0,0 +1,22 @@ +{ + "font-size": { + "xl": { "value": "2rem", "type": "fontSizes" }, + "lg": { "value": "1.5rem", "type": "fontSizes" }, + "md": { "value": "1rem", "type": "fontSizes" }, + "sm": { "value": "0.875rem", "type": "fontSizes" }, + "xs": { "value": "0.75rem", "type": "fontSizes" } + }, + "font-weight": { + "regular": { "value": "400", "type": "fontWeights" }, + "medium": { "value": "500", "type": "fontWeights" }, + "bold": { "value": "700", "type": "fontWeights" } + }, + "font-family": { + "base": { "value": "Pretendard, sans-serif", "type": "fontFamilies" } + }, + "line-height": { + "tight": { "value": "1.25", "type": "lineHeights" }, + "normal": { "value": "1.6", "type": "lineHeights" }, + "relaxed": { "value": "1.75", "type": "lineHeights" } + } +}