-
Notifications
You must be signed in to change notification settings - Fork 47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement bezier-tokens package #1685
Changes from all commits
5d5481a
64f7bc8
581ee18
093107c
7dbe77e
5f57722
93a7ff2
e0ae091
584e557
1139742
fe6765d
aef1e31
b4578f2
e8221d6
c681297
c0277b4
83086ac
d979b80
85b5d75
1df6dbf
6d1048a
925555d
11e5a01
a95ff35
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@channel.io/bezier-tokens": minor | ||
--- | ||
|
||
First release |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
node_modules | ||
dist |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
module.exports = { | ||
root: true, | ||
extends: ['bezier'], | ||
parserOptions: { | ||
tsconfigRootDir: __dirname, | ||
project: './tsconfig.eslint.json', | ||
}, | ||
rules: { | ||
'no-restricted-imports': 'off', | ||
'sort-imports': [ | ||
'error', | ||
{ | ||
ignoreDeclarationSort: true, | ||
}, | ||
], | ||
'import/order': [ | ||
'error', | ||
{ | ||
'newlines-between': 'always', | ||
alphabetize: { order: 'asc' }, | ||
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index'], | ||
}, | ||
], | ||
'@typescript-eslint/naming-convention': 'off', | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
# Bezier Tokens | ||
|
||
Bezier Tokens is a design tokens library that implements Bezier design system. | ||
|
||
## Installation | ||
|
||
```bash | ||
npm i -D @channel.io/bezier-tokens | ||
``` | ||
|
||
## Usage | ||
|
||
### JavaScript | ||
|
||
You can access and use values by token group. | ||
|
||
```ts | ||
import { tokens } from '@channel.io/bezier-tokens' | ||
|
||
console.log(tokens.global['blue-300']) // "#..." | ||
console.log(tokens.lightTheme['bg-black-dark']) // "#..." | ||
``` | ||
|
||
### CSS | ||
|
||
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 '@channel.io/bezier-tokens/css/global.css' | ||
import '@channel.io/bezier-tokens/css/light-theme.css' | ||
import '@channel.io/bezier-tokens/css/dark-theme.css' | ||
|
||
div { | ||
background: var(--bg-black-dark); | ||
} | ||
``` | ||
|
||
## Contributing | ||
|
||
See [contribution guide](https://github.com/channel-io/bezier-react/wiki/Contribute). | ||
|
||
## Maintainers | ||
|
||
This package is mainly contributed by Channel Corp. Although feel free to contribution, or raise concerns! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
{ | ||
"name": "@channel.io/bezier-tokens", | ||
"version": "0.1.0", | ||
"description": "Design tokens for Bezier design system.", | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/channel-io/bezier-react", | ||
"directory": "packages/bezier-tokens" | ||
}, | ||
"main": "dist/cjs/index.js", | ||
"module": "dist/esm/index.mjs", | ||
"types": "dist/types/cjs/index.d.ts", | ||
"exports": { | ||
".": { | ||
"require": { | ||
"types": "./dist/types/cjs/index.d.ts", | ||
"default": "./dist/cjs/index.js" | ||
}, | ||
"import": { | ||
"types": "./dist/types/esm/index.d.mts", | ||
"default": "./dist/esm/index.mjs" | ||
} | ||
}, | ||
"./css/*": "./dist/css/*" | ||
}, | ||
"sideEffects": [ | ||
"**/*.css" | ||
], | ||
"files": [ | ||
"dist" | ||
], | ||
"scripts": { | ||
"build": "run-s clean:build build:tokens build:js-index build:types", | ||
"build:tokens": "ts-node-esm scripts/build-tokens.ts", | ||
"build:js-index": "ts-node-esm scripts/build-js-index.ts", | ||
"build:types": "tsc -p tsconfig.build.json", | ||
"lint": "TIMING=1 eslint --cache .", | ||
"typecheck": "tsc --noEmit", | ||
"clean": "run-s 'clean:*'", | ||
"clean:build": "rm -rf dist", | ||
"clean:cache": "rm -rf node_modules .turbo .eslintcache" | ||
}, | ||
"keywords": [ | ||
"channel", | ||
"design", | ||
"tokens", | ||
"design tokens" | ||
], | ||
"author": "Channel Corp.", | ||
"license": "Apache-2.0", | ||
"devDependencies": { | ||
"eslint-config-bezier": "workspace:*", | ||
"style-dictionary": "^3.9.0", | ||
"tsconfig": "workspace:*" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import fs from 'fs' | ||
import path from 'path' | ||
|
||
interface BuildJsIndexFileOptions { | ||
buildPath: string | ||
isCjs: boolean | ||
} | ||
|
||
const getFileExtensionByModuleSystem = (isCjs: boolean) => | ||
(isCjs ? '.js' : '.mjs') | ||
|
||
function buildJsIndexFile({ buildPath, isCjs }: BuildJsIndexFileOptions) { | ||
const fileExtension = getFileExtensionByModuleSystem(isCjs) | ||
const indexFile = `index${fileExtension}` | ||
let exportStatements = '' | ||
|
||
if (!fs.existsSync(buildPath)) { | ||
// eslint-disable-next-line no-console | ||
console.log(`Directory not found: ${buildPath}`) | ||
return | ||
} | ||
|
||
const files = fs.readdirSync(buildPath) | ||
// eslint-disable-next-line no-console | ||
console.log(`Reading files in ${buildPath}:`, files) | ||
|
||
files.forEach((file) => { | ||
if (file.endsWith(fileExtension) && file !== indexFile) { | ||
const moduleName = file.replace(fileExtension, '') | ||
if (!isCjs) { | ||
exportStatements += `import ${moduleName} from './${file}';\n` | ||
} | ||
} | ||
}) | ||
|
||
if (isCjs) { | ||
exportStatements += 'exports.tokens = Object.freeze({\n' | ||
} else { | ||
exportStatements += '\nexport const tokens = Object.freeze({\n' | ||
} | ||
|
||
files.forEach((file) => { | ||
if (file.endsWith(fileExtension) && file !== indexFile) { | ||
const moduleName = file.replace(fileExtension, '') | ||
if (isCjs) { | ||
exportStatements += ` ${moduleName}: require('./${moduleName}'),\n` | ||
} else { | ||
exportStatements += ` ${moduleName},\n` | ||
} | ||
} | ||
}) | ||
|
||
exportStatements += '});\n' | ||
|
||
fs.writeFileSync(path.join(buildPath, indexFile), exportStatements) | ||
// eslint-disable-next-line no-console | ||
console.log(`✅ Created ${indexFile} in ${buildPath}`) | ||
} | ||
|
||
function main() { | ||
[ | ||
{ | ||
buildPath: 'dist/cjs', | ||
isCjs: true, | ||
}, | ||
{ | ||
buildPath: 'dist/esm', | ||
isCjs: false, | ||
}, | ||
].forEach(buildJsIndexFile) | ||
} | ||
|
||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
import StyleDictionary, { type Config } from 'style-dictionary' | ||
|
||
import { | ||
customJsCjs, | ||
customJsEsm, | ||
} from './lib/format' | ||
import { customFontPxToRem } from './lib/transform' | ||
import { toCamelCase } from './lib/utils' | ||
|
||
const TokenBuilder = StyleDictionary.registerTransform(customFontPxToRem) | ||
.registerFormat(customJsCjs) | ||
.registerFormat(customJsEsm) | ||
|
||
const COMMON_WEB_TRANSFORMS = [ | ||
'attribute/cti', | ||
'name/cti/kebab', | ||
'size/rem', | ||
'color/css', | ||
customFontPxToRem.name, | ||
] | ||
|
||
interface DefineConfigOptions { | ||
source: string[] | ||
destination: string | ||
options?: { | ||
cssSelector: string | ||
} | ||
} | ||
|
||
function defineConfig({ | ||
source, | ||
destination, | ||
options, | ||
}: DefineConfigOptions): Config { | ||
return { | ||
source, | ||
platforms: { | ||
'js/cjs': { | ||
transforms: COMMON_WEB_TRANSFORMS, | ||
buildPath: 'dist/cjs/', | ||
files: [ | ||
{ | ||
destination: `${toCamelCase(destination)}.js`, | ||
format: customJsCjs.name, | ||
filter: (token) => token.filePath.includes(destination), | ||
}, | ||
], | ||
}, | ||
'js/esm': { | ||
transforms: COMMON_WEB_TRANSFORMS, | ||
buildPath: 'dist/esm/', | ||
files: [ | ||
{ | ||
destination: `${toCamelCase(destination)}.mjs`, | ||
format: customJsEsm.name, | ||
filter: (token) => token.filePath.includes(destination), | ||
}, | ||
], | ||
}, | ||
css: { | ||
transforms: COMMON_WEB_TRANSFORMS, | ||
basePxFontSize: 10, | ||
buildPath: 'dist/css/', | ||
files: [ | ||
{ | ||
destination: `${destination}.css`, | ||
format: 'css/variables', | ||
filter: (token) => token.filePath.includes(destination), | ||
options: { | ||
selector: options?.cssSelector, | ||
outputReferences: true, | ||
}, | ||
}, | ||
], | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
function main() { | ||
[ | ||
TokenBuilder.extend( | ||
defineConfig({ | ||
source: ['src/global/*.json'], | ||
destination: 'global', | ||
options: { cssSelector: ':where(:root, :host)' }, | ||
}), | ||
), | ||
TokenBuilder.extend( | ||
defineConfig({ | ||
source: ['src/global/*.json', 'src/semantic/light-theme/*.json'], | ||
destination: 'light-theme', | ||
options: { | ||
cssSelector: ':where(:root, :host), [data-bezier-theme="light"]', | ||
}, | ||
}), | ||
), | ||
TokenBuilder.extend( | ||
defineConfig({ | ||
source: ['src/global/*.json', 'src/semantic/dark-theme/*.json'], | ||
destination: 'dark-theme', | ||
options: { cssSelector: '[data-bezier-theme="dark"]' }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 여기는 :root selector 를 안줘도 되는 것인가요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 기본 테마로 라이트 테마가 설정되고, 다크 테마는 data 속성으로 필요한 경우에 뗐다 붙였다 하는 걸 기대했어요 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (#1690 예시처럼) |
||
}), | ||
), | ||
].forEach((builder) => builder.buildAllPlatforms()) | ||
} | ||
|
||
main() |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { | ||
type Format, | ||
type Named, | ||
formatHelpers, | ||
} from 'style-dictionary' | ||
|
||
type CustomFormat = Named<Format> | ||
|
||
const { fileHeader } = formatHelpers | ||
|
||
export const customJsCjs: CustomFormat = { | ||
name: 'custom/js/cjs', | ||
formatter({ dictionary, file }) { | ||
return ( | ||
`${fileHeader({ file }) | ||
}module.exports = {` + | ||
`\n${ | ||
dictionary.allTokens | ||
.map((token) => ` "${token.name}": ${JSON.stringify(token.value)},`) | ||
.join('\n') | ||
}\n` + | ||
'}' | ||
) | ||
}, | ||
} | ||
|
||
export const customJsEsm: CustomFormat = { | ||
name: 'custom/js/esm', | ||
formatter({ dictionary, file }) { | ||
return ( | ||
`${fileHeader({ file }) | ||
}export default {` + | ||
`\n${ | ||
dictionary.allTokens | ||
.map((token) => ` "${token.name}": ${JSON.stringify(token.value)},`) | ||
.join('\n') | ||
}\n` + | ||
'}' | ||
) | ||
}, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
semantic 토큰이 global 토큰을 참조하고 있기 때문에, 빌드 에러를 방지하기 위해선 source 배열에 global 토큰이 포함되어야 합니다.