From d89ac748bede12830bb2db8b64d3d6ce082d0315 Mon Sep 17 00:00:00 2001 From: Ed Date: Wed, 18 Dec 2024 12:39:50 +0900 Subject: [PATCH] Support for SCSS output in bezier-tokens (#2568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Self Checklist - [x] I wrote a PR title in **English** and added an appropriate **label** to the PR. - [x] I wrote the commit message in **English** and to follow [**the Conventional Commits specification**](https://www.conventionalcommits.org/en/v1.0.0/). - [x] I [added the **changeset**](https://github.com/changesets/changesets/blob/main/docs/adding-a-changeset.md) about the changes that needed to be released. (or didn't have to) - [x] I wrote or updated **documentation** related to the changes. (or didn't have to) - [x] I wrote or updated **tests** related to the changes. (or didn't have to) - [x] I tested the changes in various browsers. (or didn't have to) - Windows: Chrome, Edge, (Optional) Firefox - macOS: Chrome, Edge, Safari, (Optional) Firefox ## Related Issue ## Summary bezier-tokens에서 scss 파일을 내보냅니다. ## Details - 일부 유틸 클래스를 만드는 CSS 모듈에서 토큰에 대한 종류를 모두 알고있어야하는 불편함이 있었습니다. 이번에 #2566 PR 작업을 진행하면서 동일한 작업이 반복되는걸 느껴 이를 함께 수정합니다. - bezier-tokens에서 [map-deep](https://styledictionary.com/reference/hooks/formats/predefined/#scssmap-deep) 포맷으로 scss output을 만들도록 합니다. 자바스크립트 케이스처럼 `index.scss` 를 만드는 과정을 추가했습니다. - bezier-token의 `sideEffects` 필드에 scss 파일을 추가합니다. - scss의 `pkg:` Importer 규칙에 따라 bezier-tokens의 conditional export field를 수정했습니다. bezier-react에선 storybook & build 과정 모두 상대 경로로 지정하는 게 잘 동작해서 pkg 프로토콜은 이 PR에서 사용하지 않았습니다 (시간이 좀 더 걸릴듯함) ### Breaking change? (Yes/No) No - ev-inner, ev-base 에 대한 유틸 클래스가 추가로 생기지만 사용처 영향은 없습니다 (매우 미미한 스타일 시트 파일 크기 상승) ## References - https://sass-lang.com/documentation/js-api/classes/nodepackageimporter/ - https://webpack.kr/guides/tree-shaking/ --- .changeset/rotten-numbers-explain.md | 5 +++ .../styles/components/elevation.module.scss | 5 ++- .../src/styles/components/radius.module.scss | 16 ++----- .../src/styles/components/z-index.module.scss | 5 ++- packages/bezier-tokens/README.md | 45 ++++++++++++++----- packages/bezier-tokens/package.json | 13 ++++-- .../bezier-tokens/scripts/build-scss-index.ts | 35 +++++++++++++++ .../bezier-tokens/scripts/build-tokens.ts | 25 +++++++++++ packages/bezier-tokens/scripts/lib/utils.ts | 3 ++ 9 files changed, 121 insertions(+), 31 deletions(-) create mode 100644 .changeset/rotten-numbers-explain.md create mode 100644 packages/bezier-tokens/scripts/build-scss-index.ts diff --git a/.changeset/rotten-numbers-explain.md b/.changeset/rotten-numbers-explain.md new file mode 100644 index 0000000000..824e4078b8 --- /dev/null +++ b/.changeset/rotten-numbers-explain.md @@ -0,0 +1,5 @@ +--- +'@channel.io/bezier-tokens': minor +--- + +Add SCSS support to access design tokens directly through SCSS variables. diff --git a/packages/bezier-react/src/styles/components/elevation.module.scss b/packages/bezier-react/src/styles/components/elevation.module.scss index c28b6ef285..2ae0f35247 100644 --- a/packages/bezier-react/src/styles/components/elevation.module.scss +++ b/packages/bezier-react/src/styles/components/elevation.module.scss @@ -1,6 +1,7 @@ -$elevations: 1, 2, 3, 4, 5, 6; +@use 'sass:map'; +@use '../../../../../node_modules/@channel.io/bezier-tokens/dist/scss' as *; -@each $ev in $elevations { +@each $ev in map.keys(map.get($tokens, 'light-theme', 'ev')) { :where(.elevation-#{$ev}) { /* stylelint-disable-next-line bezier/validate-token */ box-shadow: var(--ev-#{$ev}); diff --git a/packages/bezier-react/src/styles/components/radius.module.scss b/packages/bezier-react/src/styles/components/radius.module.scss index 513950519a..1d5e3b0fe2 100644 --- a/packages/bezier-react/src/styles/components/radius.module.scss +++ b/packages/bezier-react/src/styles/components/radius.module.scss @@ -1,17 +1,7 @@ -$radiuses: - 2, - 3, - 4, - 6, - 8, - 12, - 16, - 20, - 32, - 44, - 42-p; +@use 'sass:map'; +@use '../../../../../node_modules/@channel.io/bezier-tokens/dist/scss' as *; -@each $radius in $radiuses { +@each $radius in map.keys(map.get($tokens, 'global', 'radius')) { :where(.radius-#{$radius}) { /* stylelint-disable-next-line bezier/validate-token */ border-radius: var(--radius-#{$radius}); diff --git a/packages/bezier-react/src/styles/components/z-index.module.scss b/packages/bezier-react/src/styles/components/z-index.module.scss index 7d91189dfd..4e67025681 100644 --- a/packages/bezier-react/src/styles/components/z-index.module.scss +++ b/packages/bezier-react/src/styles/components/z-index.module.scss @@ -1,6 +1,7 @@ -$z-indices: hidden, base, floating, overlay, modal, toast, tooltip, important; +@use 'sass:map'; +@use '../../../../../node_modules/@channel.io/bezier-tokens/dist/scss' as *; -@each $z-index in $z-indices { +@each $z-index in map.keys(map.get($tokens, 'global', 'z-index')) { :where(.z-index-#{$z-index}) { /* stylelint-disable-next-line bezier/validate-token */ z-index: var(--z-index-#{$z-index}); diff --git a/packages/bezier-tokens/README.md b/packages/bezier-tokens/README.md index 4b8e6ee4ed..3064117880 100644 --- a/packages/bezier-tokens/README.md +++ b/packages/bezier-tokens/README.md @@ -10,29 +10,54 @@ npm i -D @channel.io/bezier-tokens ## Usage -### JavaScript +### CSS -You can access and use values by token group. +Provide all design tokens as CSS variables. If you want to apply dark theme tokens, add the `data-bezier-theme="dark"` attribute to the parent element. The default is light theme tokens, which can also be applied by adding the `data-bezier-theme="light"` attribute to the parent element. ```ts -import { tokens } from '@channel.io/bezier-tokens' +import '@channel.io/bezier-tokens/css/styles.css' +``` -console.log(tokens.global.color['blue-300']) // "#..." -console.log(tokens.lightTheme.color['bg-black-dark']) // "#..." +```html +
+
+
+
+
+
``` -### CSS +```css +.foo { + background-color: var(--bg-black-dark); +} +``` -Provide all design tokens as CSS variables. If you want to apply dark theme tokens, add the `data-bezier-theme="dark"` attribute to the parent element. The default is light theme tokens, which can also be applied by adding the `data-bezier-theme="light"` attribute to the parent element. +### SCSS -```ts -import '@channel.io/bezier-tokens/css/styles.css' +While CSS variables are recommended, you can also use SCSS variables directly if you need to. + +```scss +@use "sass:map"; +@use "pkg:@channel.io/bezier-tokens" as *; div { - background: var(--bg-black-dark); + border-radius: map.get($tokens, "global", "radius", "4"); // ...px + background-color: map.get($tokens, "light-theme", "bg", "black", "dark"); // #... } ``` +### JavaScript + +You can access and use values by token group. + +```ts +import { tokens } from '@channel.io/bezier-tokens' + +console.log(tokens.global.color['blue-300']) // "#..." +console.log(tokens.lightTheme.color['bg-black-dark']) // "#..." +``` + ## Contributing See [contribution guide](https://github.com/channel-io/bezier-react/wiki/Contribute). diff --git a/packages/bezier-tokens/package.json b/packages/bezier-tokens/package.json index 236b63396d..7c8428f3f6 100644 --- a/packages/bezier-tokens/package.json +++ b/packages/bezier-tokens/package.json @@ -19,7 +19,8 @@ "import": { "types": "./dist/types/esm/index.d.mts", "default": "./dist/esm/index.mjs" - } + }, + "sass": "./dist/scss/index.scss" }, "./alpha": { "require": { @@ -29,10 +30,13 @@ "import": { "types": "./dist/types/alpha/esm/index.d.mts", "default": "./dist/alpha/esm/index.mjs" - } + }, + "sass": "./dist/alpha/scss/index.scss" }, "./css/*": "./dist/css/*", - "./alpha/css/*": "./dist/alpha/css/*" + "./scss/*": "./dist/scss/*", + "./alpha/css/*": "./dist/alpha/css/*", + "./alpha/scss/*": "./dist/alpha/scss/*" }, "typesVersions": { "*": { @@ -42,7 +46,8 @@ } }, "sideEffects": [ - "**/*.css" + "**/*.css", + "**/*.scss" ], "files": [ "dist" diff --git a/packages/bezier-tokens/scripts/build-scss-index.ts b/packages/bezier-tokens/scripts/build-scss-index.ts new file mode 100644 index 0000000000..d1a8235504 --- /dev/null +++ b/packages/bezier-tokens/scripts/build-scss-index.ts @@ -0,0 +1,35 @@ +import fs from 'fs/promises' +import path from 'path' + +interface BuildScssIndexOptions { + buildPath: string +} + +export async function buildScssIndex({ buildPath }: BuildScssIndexOptions) { + const destination = path.join(buildPath, 'index.scss') + + try { + const files = await fs.readdir(buildPath) + let useStatements = '' + let mapStatements = '$tokens: (\n' + + for (const file of files.filter( + (file) => file.endsWith('.scss') && file !== path.basename(destination) + )) { + const moduleName = path.basename(file, '.scss') + + useStatements += `@use './${moduleName}' as ${moduleName};\n` + mapStatements += ` "${moduleName}": ${moduleName}.$tokens,\n` + } + + mapStatements += ');\n' + + const result = `${useStatements}\n${mapStatements}` + + await fs.writeFile(destination, result) + + console.log(`\n✔︎ Created ${destination}`) + } catch (error) { + throw error + } +} diff --git a/packages/bezier-tokens/scripts/build-tokens.ts b/packages/bezier-tokens/scripts/build-tokens.ts index 74758c1c3e..26c99f5edf 100644 --- a/packages/bezier-tokens/scripts/build-tokens.ts +++ b/packages/bezier-tokens/scripts/build-tokens.ts @@ -6,6 +6,7 @@ import StyleDictionary, { } from 'style-dictionary' import { buildJsIndex } from './build-js-index' +import { buildScssIndex } from './build-scss-index' import { alphaCustomCss, alphaCustomJsCjs, @@ -14,6 +15,7 @@ import { customJsEsm, } from './lib/format' import { CSSTransforms } from './lib/transform' +import { toKebabCase } from './lib/utils' import { mergeCss } from './merge-css' const CustomTransforms = [...Object.values(CSSTransforms)] @@ -24,6 +26,7 @@ const BUILD_PATH = { CJS: 'cjs', ESM: 'esm', CSS: 'css', + SCSS: 'scss', } const TokenBuilder = CustomTransforms.reduce( @@ -125,6 +128,21 @@ function defineConfig({ ], transforms, }), + 'web/scss': defineWebPlatform({ + buildPath: `${basePath}/${BUILD_PATH.SCSS}/`, + files: [ + { + destination: `${toKebabCase(destination)}.scss`, + format: 'scss/map-deep', + filter: ({ filePath }) => + source.some((src) => minimatch(filePath, src)), + options: { + outputReferences: false, + }, + }, + ], + transforms, + }), }, } } @@ -209,6 +227,13 @@ async function main() { await mergeCss(buildPath) } + for (const buildPath of [ + `${BUILD_PATH.BASE}/${BUILD_PATH.SCSS}`, + `${BUILD_PATH.BASE_ALPHA}/${BUILD_PATH.SCSS}`, + ]) { + await buildScssIndex({ buildPath }) + } + for (const options of [ { buildPath: `${BUILD_PATH.BASE}/${BUILD_PATH.CJS}`, isCjs: true }, { buildPath: `${BUILD_PATH.BASE_ALPHA}/${BUILD_PATH.CJS}`, isCjs: true }, diff --git a/packages/bezier-tokens/scripts/lib/utils.ts b/packages/bezier-tokens/scripts/lib/utils.ts index c2ae222ed9..6bc793d20b 100644 --- a/packages/bezier-tokens/scripts/lib/utils.ts +++ b/packages/bezier-tokens/scripts/lib/utils.ts @@ -6,6 +6,9 @@ export const toCamelCase = (str: string) => .toLowerCase() .replace(/[^a-zA-Z0-9]+(.)/g, (_, char) => char.toUpperCase()) +export const toKebabCase = (str: string) => + str.replace(/([A-Z])/g, '-$1').toLowerCase() + export const extractNumber = (str: string) => str.match(/-?\d+(\.\d+)?/g)?.join('')