├── src ├── postcss.ts ├── runtime │ ├── server │ │ ├── utils.ts │ │ └── api │ │ │ └── tokens │ │ │ ├── generate.ts │ │ │ ├── index.ts │ │ │ └── config.ts │ ├── composables │ │ └── tokens.ts │ └── assets │ │ └── reset.css ├── config │ ├── index.ts │ ├── load.ts │ ├── generate.ts │ └── define.ts ├── palette.ts ├── formats │ ├── tokens-helpers.ts │ ├── references.ts │ ├── types-strings.ts │ └── index.ts ├── index.ts ├── utils │ ├── types.ts │ ├── vue.ts │ └── index.ts ├── transform │ ├── css.ts │ ├── resolvers.ts │ └── index.ts ├── css │ └── index.ts └── module.ts ├── .eslintignore ├── volar ├── package.json └── index.js ├── docs ├── tsconfig.json ├── public │ ├── icon.png │ ├── cover.jpg │ └── favicon.ico ├── renovate.json ├── .gitignore ├── .env.example ├── components │ ├── content │ │ ├── Highlight.vue │ │ └── Ellipsis.vue │ └── Logo.vue ├── README.md ├── nuxt.config.ts ├── content │ ├── 0.index.md │ └── 1.guide │ │ ├── 1.introduction.md │ │ ├── 2.get-started.md │ │ └── 3.API.md └── theme.config.ts ├── renovate.json ├── .github ├── FUNDING.yml ├── scripts │ ├── example.sh │ ├── clean.sh │ ├── test.sh │ ├── release-edge.sh │ └── bump-edge.ts ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature-request.md │ └── bug-report.yml ├── workflows │ └── ci.yml └── PULL_REQUEST_TEMPLATE.md ├── playground ├── public │ └── favicon.ico ├── tsconfig.json ├── tokens.config.ts ├── nuxt.config.ts ├── tailwind.config.js ├── theme │ ├── nuxt.config.ts │ └── tokens.config.ts ├── components │ └── Block.vue └── pages │ └── index.vue ├── test ├── fixtures │ └── basic │ │ ├── nuxt.config.ts │ │ └── pages │ │ └── index.vue └── basic.test.ts ├── .eslintrc ├── README.md ├── LICENSE ├── .gitignore ├── tsconfig.json └── package.json /src/postcss.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /volar/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "volar-plugin" 3 | } 4 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | } 4 | -------------------------------------------------------------------------------- /src/runtime/server/utils.ts: -------------------------------------------------------------------------------- 1 | export { generateTokens } from '../../config/generate' 2 | -------------------------------------------------------------------------------- /docs/public/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-modules/design-tokens/HEAD/docs/public/icon.png -------------------------------------------------------------------------------- /docs/public/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-modules/design-tokens/HEAD/docs/public/cover.jpg -------------------------------------------------------------------------------- /docs/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-modules/design-tokens/HEAD/docs/public/favicon.ico -------------------------------------------------------------------------------- /src/config/index.ts: -------------------------------------------------------------------------------- 1 | export * from './define' 2 | export * from './generate' 3 | export * from './load' 4 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@nuxtjs"], 3 | "lockFileMaintenance": { 4 | "enabled": true 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [nuxt] 4 | open_collective: nuxtjs 5 | -------------------------------------------------------------------------------- /playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nuxt-modules/design-tokens/HEAD/playground/public/favicon.ico -------------------------------------------------------------------------------- /docs/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@nuxtjs" 4 | ], 5 | "lockFileMaintenance": { 6 | "enabled": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.iml 3 | .idea 4 | *.log* 5 | .nuxt 6 | .vscode 7 | .DS_Store 8 | coverage 9 | dist 10 | sw.* 11 | .env 12 | .output 13 | -------------------------------------------------------------------------------- /playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./.nuxt/tsconfig.json", 3 | "vueCompilerOptions": { 4 | "plugins": [ 5 | "../volar" 6 | ] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/.env.example: -------------------------------------------------------------------------------- 1 | # Create one with no scope selected on https://github.com/settings/tokens/new 2 | # This token is used for fetching the repository releases. 3 | GITHUB_TOKEN= -------------------------------------------------------------------------------- /test/fixtures/basic/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt' 2 | import themeModule from '../../../src/module' 3 | 4 | export default defineNuxtConfig({ 5 | modules: [themeModule] 6 | }) 7 | -------------------------------------------------------------------------------- /playground/tokens.config.ts: -------------------------------------------------------------------------------- 1 | import { defineTokens } from '../src' 2 | 3 | /** 4 | * Design Tokens options 5 | * Powered by Style Dictionary 6 | */ 7 | 8 | export default defineTokens({ 9 | }) 10 | -------------------------------------------------------------------------------- /.github/scripts/example.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EXAMPLE_PATH=examples/$1 4 | 5 | if [[ ! -d "$EXAMPLE_PATH/node_modules" ]] ; then 6 | (cd $EXAMPLE_PATH && yarn install) 7 | fi 8 | 9 | (cd $EXAMPLE_PATH && yarn dev) 10 | -------------------------------------------------------------------------------- /test/fixtures/basic/pages/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["@nuxtjs/eslint-config-typescript"], 4 | "rules": { 5 | "vue/multi-word-component-names": "off", 6 | "vue/no-multiple-template-root": "off", 7 | "no-redeclare": "off", 8 | "import/named": "off" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docs/components/content/Highlight.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | [![nuxt-content](/docs/public/cover_dark.png "@nuxt/content image")](https://content.nuxtjs.org) 2 | 3 | # Documentation 4 | 5 | This documentation uses [Docus](https://github.com/nuxtlabs/docus). 6 | 7 | ## 💻 Development 8 | 9 | - Install dependencies using `yarn install` 10 | - Start using `yarn dev` 11 | - Build using `yarn build` 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: 📚 Documentation 3 | url: https://nuxt-design-tokens.netlify.app 4 | about: Check documentation for usage 5 | - name: 💬 Discussions 6 | url: https://github.com/nuxt-community/design-tokens-module/discussions 7 | about: Use discussions if you have an idea for improvement and asking questions 8 | -------------------------------------------------------------------------------- /src/runtime/composables/tokens.ts: -------------------------------------------------------------------------------- 1 | import type { NuxtThemeTokens } from '../../index' 2 | import { useNuxtApp } from '#imports' 3 | 4 | export const useTokens = () => { 5 | const { $tokens } = useNuxtApp() 6 | 7 | return { 8 | $tokens, 9 | $dt: $tokens, 10 | fetch: async (): Promise => await $fetch('/api/_design-tokens/tokens') 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /.github/scripts/clean.sh: -------------------------------------------------------------------------------- 1 | # Docs 2 | rm -rf docs/.nuxt 3 | rm -rf docs/.output 4 | rm -rf docs/dist 5 | 6 | # Playground 7 | rm -rf playground/.nuxt 8 | rm -rf playground/.output 9 | rm -rf playground/dist 10 | 11 | # Fixture 12 | rm -rf test/fixtures/basic/.nuxt 13 | rm -rf test/fixtures/basic/.output 14 | rm -rf test/fixtures/basic/dist 15 | 16 | # Base 17 | rm -rf yarn.lock 18 | rm -rf node_modules 19 | rm -rf dist 20 | -------------------------------------------------------------------------------- /.github/scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CWD=$(pwd) 4 | ARG1=${1} 5 | 6 | # Remove all .nuxt directories in the test/fixtures directory 7 | for d in $(find $CWD/test/fixtures -maxdepth 1 -mindepth 1 -type d); do 8 | cd $d 9 | rm -rf .nuxt 10 | npx nuxi prepare 11 | cd $CWD 12 | done 13 | 14 | if [[ $ARG1 ]] 15 | then 16 | echo "npx vitest run -t $ARG1" 17 | (npx vitest run -t $ARG1.test) 18 | else 19 | echo "npx vitest run" 20 | (npx vitest run) 21 | fi 22 | -------------------------------------------------------------------------------- /docs/components/content/Ellipsis.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | -------------------------------------------------------------------------------- /src/runtime/server/api/tokens/generate.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler } from 'h3' 2 | // @ts-ignore 3 | import { generateTokens } from '#design-tokens' 4 | // @ts-ignore 5 | import { useRuntimeConfig, useStorage } from '#imports' 6 | 7 | export default defineEventHandler(async () => { 8 | const runtimeConfig = useRuntimeConfig() 9 | 10 | const { tokensDir } = runtimeConfig?.style 11 | 12 | const storage = useStorage() 13 | 14 | const tokens = await storage.getItem('cache:design-tokens:tokens.json') 15 | 16 | await generateTokens(tokens, tokensDir) 17 | }) 18 | -------------------------------------------------------------------------------- /playground/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { defineNuxtConfig } from 'nuxt' 3 | import { resolve } from 'pathe' 4 | 5 | const themeDir = fileURLToPath(new URL('./', import.meta.url)) 6 | const resolveThemeDir = (path: string) => resolve(themeDir, path) 7 | 8 | export default defineNuxtConfig({ 9 | alias: { 10 | '@nuxtjs/design-tokens': resolveThemeDir('../src/index.ts') 11 | }, 12 | 13 | extends: [resolveThemeDir('./theme')], 14 | 15 | nitro: { 16 | externals: { 17 | inline: ['recast', 'ast-types'] 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @nuxtjs/style 2 | 3 | Use [Design Tokens](https://amzn.github.io/style-dictionary/#/tokens) in your Nuxt project. 4 | 5 | ## Features 6 | 7 | - 🚀 Powered by [Style Dictionary](https://amzn.github.io/style-dictionary) 8 | - ✨ Lets you configure the appeareance of your Nuxt app easily 9 | - 📦 Handles `tokens.config.ts` file 10 | - 👨‍🎨 Goes nicely with [@nuxt-themes/config](https://themes.nuxtjs.org) 11 | - 🎨 Offers `useTokens`, `$tokens` and `$dt` typed APIs 12 | 13 | ## Development 14 | 15 | - Clone repository 16 | - Install dependencies using `yarn install`` 17 | - Try playground using `yarn dev` 18 | -------------------------------------------------------------------------------- /playground/tailwind.config.js: -------------------------------------------------------------------------------- 1 | import { $dt } from '../src' 2 | 3 | export default { 4 | theme: { 5 | extend: { 6 | colors: { 7 | primary: $dt('colors.primary'), 8 | 9 | black: { 10 | DEFAULT: $dt('colors.black') 11 | }, 12 | 13 | grape: { 14 | DEFAULT: $dt('colors.grape') 15 | }, 16 | 17 | lila: { 18 | DEFAULT: $dt('colors.lila') 19 | }, 20 | 21 | grey: { 22 | DEFAULT: $dt('colors.grey') 23 | }, 24 | 25 | lavender: { 26 | DEFAULT: $dt('colors.lavender') 27 | } 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | import { defineNuxtConfig } from 'nuxt' 2 | // @ts-ignore 3 | import colors from 'tailwindcss/colors.js' 4 | 5 | export default defineNuxtConfig({ 6 | extends: [ 7 | (process.env.DOCUS_THEME_PATH || '@nuxt-themes/docus') 8 | ], 9 | components: [ 10 | { 11 | path: '~/components', 12 | prefix: '', 13 | global: true 14 | } 15 | ], 16 | tailwindcss: { 17 | config: { 18 | theme: { 19 | extend: { 20 | colors: { 21 | primary: colors.pink 22 | } 23 | } 24 | } 25 | } 26 | }, 27 | colorMode: { 28 | preference: 'dark' 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /docs/content/0.index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Design System made easy" 3 | description: "Fully-typed design tokens integration for making and maintaining a design system." 4 | navigation: false 5 | layout: fluid 6 | --- 7 | 8 | ::block-hero 9 | --- 10 | cta: 11 | - Get Started 12 | - /guide/introduction 13 | secondary: 14 | - Star on GitHub 15 | - https://github.com/nuxt-community/design-tokens 16 | snippet: npm install @nuxtjs/design-tokens --save-dev 17 | --- 18 | 19 | #top 20 | :ellipsis 21 | 22 | #title 23 | Nuxt :highlight[Style Tokens] 24 | 25 | #description 26 | Fully-typed design tokens integration for making and maintaining a design system. 27 | :: 28 | -------------------------------------------------------------------------------- /.github/scripts/release-edge.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Temporary forked from nuxt/framework 4 | 5 | set -xe 6 | 7 | # Restore all git changes 8 | git restore -s@ -SW -- . 9 | 10 | # Bump versions to edge 11 | yarn jiti ./.github/scripts/bump-edge 12 | 13 | # Build 14 | yarn build 15 | 16 | # Update token 17 | if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then 18 | echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc 19 | echo "registry=https://registry.npmjs.org/" >> ~/.npmrc 20 | echo "always-auth=true" >> ~/.npmrc 21 | echo "npmAuthToken: ${NODE_AUTH_TOKEN}" >> ~/.yarnrc.yml 22 | npm whoami 23 | fi 24 | 25 | # Release packages 26 | echo "Publishing package..." 27 | npm publish --access public --tolerate-republish 28 | -------------------------------------------------------------------------------- /playground/theme/nuxt.config.ts: -------------------------------------------------------------------------------- 1 | // import { fileURLToPath } from 'url' 2 | import { defineNuxtConfig } from 'nuxt' 3 | // import { resolve } from 'pathe' 4 | import localModule from '../../src/module' 5 | 6 | // const themeDir = fileURLToPath(new URL('./', import.meta.url)) 7 | // const resolveThemeDir = (path: string) => resolve(themeDir, path) 8 | 9 | export default defineNuxtConfig({ 10 | // @ts-ignore 11 | modules: [localModule], 12 | 13 | theme: { 14 | meta: { 15 | name: 'Playground Theme', 16 | description: 'Just a basic Playground Theme', 17 | url: 'https://nuxt-design-tokens.netlify.app', 18 | author: 'NuxtLabs', 19 | motd: true 20 | } 21 | }, 22 | 23 | tailwindcss: { 24 | viewer: false 25 | } 26 | }) 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "\U0001F4A1 Feature Request" 3 | about: Suggest an idea or enhancement for the module. 4 | title: '' 5 | labels: 'enhancement' 6 | assignees: '' 7 | --- 8 | 9 | ### Is your feature request related to a problem? Please describe 10 | 11 | 12 | ### Describe the solution you'd like 13 | 14 | 15 | ### Describe alternatives you've considered 16 | 17 | 18 | ### Additional context 19 | 20 | -------------------------------------------------------------------------------- /src/runtime/server/api/tokens/index.ts: -------------------------------------------------------------------------------- 1 | import { defineEventHandler, isMethod, useBody } from 'h3' 2 | // @ts-ignore 3 | import { useStorage, useRuntimeConfig } from '#imports' 4 | 5 | export default defineEventHandler(async (event) => { 6 | const storage = useStorage() 7 | const runtimeConfig = useRuntimeConfig() 8 | const { server } = runtimeConfig?.style || {} 9 | 10 | if (server && isMethod(event, 'POST')) { 11 | try { 12 | const { tokens } = await useBody(event) 13 | 14 | if (tokens) { 15 | await storage.setItem('cache:design-tokens:tokens.json', tokens) 16 | await $fetch('/api/_design-tokens/tokens/generate') 17 | } 18 | } catch (_) {} 19 | } 20 | 21 | return await storage.getItem('cache:design-tokens:tokens.json') 22 | }) 23 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | ci: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | - uses: actions/setup-node@v3 17 | with: 18 | node-version: '16' 19 | cache: yarn 20 | - run: yarn install 21 | - run: yarn dev:prepare 22 | - run: yarn lint 23 | - run: yarn build 24 | # - run: yarn test 25 | - name: Release Edge 26 | if: | 27 | github.event_name == 'push' && 28 | !contains(github.event.head_commit.message, '[skip-release]') 29 | run: ./.github/scripts/release-edge.sh 30 | env: 31 | NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} 32 | -------------------------------------------------------------------------------- /playground/theme/tokens.config.ts: -------------------------------------------------------------------------------- 1 | import { defineTokens, palette } from '../../src' 2 | 3 | export default defineTokens({ 4 | fonts: { 5 | primary: { 6 | value: 'Inter, sans-serif' 7 | } 8 | }, 9 | colors: { 10 | primary: palette('rgb(49, 52, 66)'), 11 | black: { 12 | value: '#1C1D21' 13 | }, 14 | grape: { 15 | value: '#A288A6' 16 | }, 17 | lila: { 18 | value: '#BB9BB0' 19 | }, 20 | grey: { 21 | value: '#CCBCBC' 22 | }, 23 | lavender: { 24 | value: '#F1E3E4' 25 | }, 26 | velvet: { 27 | value: '#502274' 28 | } 29 | }, 30 | 31 | screens: { 32 | sm: { value: '640px' }, 33 | md: { value: '768px' }, 34 | lg: { value: '1024px' }, 35 | xl: { value: '1280px' }, 36 | '2xl': { value: '1536px' } 37 | } 38 | }) 39 | -------------------------------------------------------------------------------- /test/basic.test.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'url' 2 | import { describe, test, expect } from 'vitest' 3 | import { $fetch, setup } from '@nuxt/test-utils' 4 | import { generateTokens } from '../src/runtime/server/utils' 5 | 6 | const fixturePath = fileURLToPath(new URL('./fixtures/basic', import.meta.url)) 7 | 8 | describe('Basic usage', async () => { 9 | await setup({ 10 | rootDir: fixturePath, 11 | server: true, 12 | dev: true 13 | }) 14 | 15 | await generateTokens( 16 | { 17 | colors: { 18 | red: { 19 | value: 100 20 | } 21 | } 22 | }, 23 | fixturePath + '/.nuxt/tokens/' 24 | ) 25 | 26 | test('Fetch index', async () => { 27 | const content = await $fetch('/') 28 | 29 | // Normal Prop 30 | expect(content).includes('Hello World') 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /docs/theme.config.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | title: '@nuxtjs/style', 3 | description: 'Vue-native zero-runtime CSS-in-JS solution.', 4 | url: 'https://design-tokens.nuxtjs.org', 5 | socials: { 6 | twitter: '@yaeeelglx', 7 | github: 'nuxt-community/design-tokens-module' 8 | }, 9 | github: { 10 | root: 'docs/content', 11 | edit: true, 12 | releases: true 13 | }, 14 | aside: { 15 | level: 0 16 | }, 17 | cover: { 18 | src: '/cover.jpg', 19 | alt: '@nuxtjs/style' 20 | }, 21 | footer: { 22 | credits: { 23 | icon: 'IconDocus', 24 | text: 'Powered by Docus', 25 | href: 'https://docus.com' 26 | }, 27 | icons: [ 28 | { 29 | label: 'Nuxt', 30 | href: 'https://v3.nuxtjs.org', 31 | component: 'IconNuxt' 32 | }, 33 | { 34 | label: 'Vue Telescope', 35 | href: 'https://vuetelescope.com', 36 | component: 'IconVueTelescope' 37 | } 38 | ] 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /docs/content/1.guide/1.introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | description: What makes Design Tokens so useful. 4 | --- 5 | 6 | > The Design Tokens Community Group's goal is to provide standards upon which products and design tools can rely for sharing stylistic pieces of a design system at scale. 7 | > _[Source](https://www.w3.org/community/design-tokens)_ 8 | > :alert[Learn more about [Design Tokens](https://backlight.dev/blog/design-tokens)] 9 | 10 | **@nuxtjs/design-tokens** offers a straightforward way to use Design Tokens definition inside your Nuxt app. 11 | 12 | It is powered by [Style Dictionary](https://amzn.github.io/style-dictionary), a powerful library by Amazon. 13 | 14 | Users can define a set of Design Tokens as they would define a set of configurations options for their app. 15 | 16 | Then, these tokens get processed by Style Dictionary on each file save, and gets injected into your Nuxt app. 17 | 18 | This allows global usage of tokens anywhere in the app, even in external files like `tailwind.config`... 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Nuxt Project 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/palette.ts: -------------------------------------------------------------------------------- 1 | import chroma, { Color } from 'chroma-js' 2 | // @ts-ignore 3 | import type { NuxtDesignTokens } from '#design-tokens/types' 4 | 5 | export const palette = ( 6 | color: string, 7 | suffixes: Array = [50, 100, 200, 300, 400, 500, 600, 700, 800, 900], 8 | padding: number = 0.1 9 | ): NuxtDesignTokens => { 10 | if (!color || typeof color !== 'string') { 11 | throw new Error('Please provide a valid "color" string parameter') 12 | } 13 | 14 | function scalePalette (baseColor: Color | string, _suffixes: Array = suffixes, _padding: number = padding) { 15 | const colorscale = chroma.scale(['white', baseColor, 'black']).padding(padding).colors(suffixes.length) 16 | 17 | const colorRange = {} 18 | 19 | suffixes.forEach( 20 | (suffix, index) => ( 21 | colorRange[suffix] = { 22 | value: colorscale[index] 23 | } 24 | ) 25 | ) 26 | 27 | colorRange[500] = { 28 | value: baseColor 29 | } 30 | 31 | return colorRange 32 | } 33 | 34 | return { 35 | ...scalePalette(color), 36 | // @ts-ignore 37 | palette: true 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules 3 | jspm_packages 4 | 5 | package-lock.json 6 | # */**/yarn.lock 7 | 8 | examples/*/**/yarn.lock 9 | 10 | # Logs 11 | *.log 12 | 13 | # Temp directories 14 | .temp 15 | .tmp 16 | .cache 17 | 18 | # Yarn 19 | **/.yarn/cache 20 | **/.yarn/*state* 21 | 22 | # Generated dirs 23 | dist 24 | .nuxt 25 | .nuxt-* 26 | .output 27 | .gen 28 | nuxt.d.ts 29 | 30 | # Junit reports 31 | reports 32 | 33 | # Coverage reports 34 | coverage 35 | *.lcov 36 | .nyc_output 37 | 38 | # VSCode 39 | .vscode 40 | 41 | # Intellij idea 42 | *.iml 43 | .idea 44 | 45 | # OSX 46 | .DS_Store 47 | .AppleDouble 48 | .LSOverride 49 | 50 | # Files that might appear in the root of a volume 51 | .DocumentRevisions-V100 52 | .fseventsd 53 | .Spotlight-V100 54 | .TemporaryItems 55 | .Trashes 56 | .VolumeIcon.icns 57 | .com.apple.timemachine.donotpresent 58 | 59 | # Directories potentially created on remote AFP share 60 | .AppleDB 61 | .AppleDesktop 62 | Network Trash Folder 63 | Temporary Items 64 | .apdisk 65 | 66 | .vercel_build_output 67 | .build-* 68 | .env 69 | .netlify 70 | 71 | # Cypress 72 | cypress/videos 73 | cypress/screenshots 74 | 75 | # Fake contents in test directories 76 | fake 77 | -------------------------------------------------------------------------------- /src/formats/tokens-helpers.ts: -------------------------------------------------------------------------------- 1 | export const tokensHelper = (ts: boolean = false) => { 2 | return `const defaultTokensHelperOptions${ts ? ': TokensHelperOptions' : ''} = { 3 | key: 'variable', 4 | flatten: true, 5 | silent: false 6 | } 7 | 8 | /** 9 | * Get a theme token by its path 10 | */ 11 | export const $tokens = (path${ts ? ': NuxtThemeTokensPaths' : ''} = undefined, options${ts ? ': TokensHelperOptions' : ''} = {}) => { 12 | const { key, flatten } = Object.assign(defaultTokensHelperOptions, options) 13 | 14 | if (!path) return themeTokens 15 | 16 | if (key === 'variable' && tokensAliases[path]) { 17 | return tokensAliases[path] 18 | } 19 | 20 | const token = get(themeTokens, path) 21 | 22 | if (key && token?.[key]) { return token[key] } 23 | 24 | if (key && flatten && typeof token === 'object') { 25 | const flattened = {} 26 | 27 | const flattenTokens = (obj) => { 28 | Object.entries(obj).forEach(([objectKey, value]) => { 29 | if (value[key]) { 30 | flattened[objectKey] = value[key] 31 | return 32 | } 33 | 34 | flattenTokens(value) 35 | }) 36 | } 37 | 38 | flattenTokens(token) 39 | 40 | return flattened 41 | } 42 | 43 | return token 44 | }\n\n` 45 | } 46 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | ### 🔗 Linked issue 6 | 7 | ### ❓ Type of change 8 | 9 | 10 | 11 | - [ ] 📖 Documentation (updates to the documentation or readme) 12 | - [ ] 🐞 Bug fix (a non-breaking change that fixes an issue) 13 | - [ ] 👌 Enhancement (improving an existing functionality like performance) 14 | - [ ] ✨ New feature (a non-breaking change that adds functionality) 15 | - [ ] ⚠️ Breaking change (fix or feature that would cause existing functionality to change) 16 | 17 | ### 📚 Description 18 | 19 | 20 | 21 | 22 | 23 | ### 📝 Checklist 24 | 25 | 26 | 27 | 28 | 29 | - [ ] I have linked an issue or discussion. 30 | - [ ] I have updated the documentation accordingly. 31 | -------------------------------------------------------------------------------- /src/formats/references.ts: -------------------------------------------------------------------------------- 1 | import { resolveVariableFromPath } from '../utils' 2 | 3 | export const referencesRegex = new RegExp( 4 | '\\' + 5 | '{' + 6 | '([^' + 7 | '}' + 8 | ']+)' + 9 | '\\' + 10 | '}', 'g' 11 | ) 12 | 13 | export const walkTokens = (obj, typing: boolean = true, aliased = {}) => { 14 | let type = {} 15 | 16 | if (obj.value) { 17 | const _path = obj.path.join('.') 18 | 19 | // Resolve aliased properties 20 | const keyRegex = /{(.*)}/g 21 | const testOriginal = obj.original.value.match(referencesRegex) 22 | if (testOriginal?.[0] && testOriginal[0] === obj.original.value) { 23 | obj.value = (obj.original.value as string).replace(keyRegex, (_, tokenPath) => { 24 | aliased[_path] = resolveVariableFromPath(tokenPath) 25 | return aliased[_path] 26 | }) 27 | } 28 | 29 | // Transform name to CSS variable name 30 | obj.variable = aliased?.[_path] ? aliased[_path] : `var(--${obj.name})` 31 | 32 | // Toggle between type declaration and value 33 | type = typing ? 'DesignToken' : obj 34 | } else { 35 | for (const k in obj) { 36 | if (obj[k] && typeof obj[k] === 'object') { 37 | type[k] = walkTokens(obj[k], typing, aliased).type 38 | } 39 | } 40 | } 41 | 42 | return { type, aliased } 43 | } 44 | -------------------------------------------------------------------------------- /playground/components/Block.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 16 | 17 | 64 | -------------------------------------------------------------------------------- /docs/content/1.guide/2.get-started.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: "Get Started" 3 | toc: false 4 | description: "Create and use Design Tokens from your Nuxt app in a few simple steps." 5 | --- 6 | 7 | 🚀. [**Add `@nuxtjs/design-tokens` to your project.**]{.text-xl} 8 | 9 | ::code-group 10 | 11 | ```bash [NPM] 12 | npm install @nuxtjs/design-tokens --save-dev 13 | ``` 14 | 15 | ```bash [Yarn] 16 | yarn add --dev @nuxtjs/design-tokens 17 | ``` 18 | 19 | :: 20 | 21 | ```ts [nuxt.config.ts] 22 | export default defineNuxtConfig({ 23 | modules: [ 24 | '@nuxtjs/design-tokens' 25 | ] 26 | }) 27 | ``` 28 | 29 | ::alert 30 | :icon{name="noto:information" .inline-block .mr-1} As `@nuxtjs/design-tokens` provides integrations with other **Nuxt modules**, you might want to add it as the first module of the list. 31 | :: 32 | 33 | 👩‍🎨. [**Define your design tokens.**]{.text-xl} 34 | 35 | ```ts [style.config.ts] 36 | import { defineTokens } from '@nuxtjs/design-tokens' 37 | 38 | export default defineTokens({ 39 | colors: { 40 | primary: { 41 | value: 'green' 42 | }, 43 | secondary: { 44 | value: 'yellow' 45 | }, 46 | } 47 | }) 48 | ``` 49 | 50 | 🎨. [**Use your tokens!**]{.text-xl} 51 | 52 | ```vue [layout/default.vue] 53 | 61 | 62 | 68 | ``` 69 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { resolveModule } from '@nuxt/kit' 2 | import jiti from 'jiti' 3 | import { palette } from './palette' 4 | import * as Generated from '#design-tokens/types' 5 | 6 | export * from './config' 7 | 8 | export * from './css' 9 | 10 | export interface ModulePrivateRuntimeConfig { 11 | tokensDir?: string 12 | server?: boolean 13 | tokensFilePaths?: Array 14 | } 15 | 16 | export { palette } 17 | 18 | export const $tokens = ( 19 | path: Generated.NuxtThemeTokensPaths, 20 | options: Generated.TokenHelperOptions = { 21 | key: 'variable', 22 | flatten: true, 23 | silent: false 24 | } 25 | ) => { 26 | const module = resolveModule(`${globalThis.__NuxtThemeTokensBuildDir__}index.ts`) 27 | 28 | const { $dt } = jiti(import.meta.url, { cache: false, requireCache: false, v8cache: false })(module) 29 | 30 | return $dt(path, options) 31 | } 32 | 33 | export type DtFunctionType = typeof $tokens 34 | 35 | export const $dt = $tokens 36 | 37 | export interface NuxtStyleConfig { 38 | server?: boolean 39 | tokens?: Generated.NuxtThemeTokens | boolean 40 | } 41 | 42 | export interface ModuleOptions extends NuxtStyleConfig {} 43 | 44 | export interface ModuleHooks {} 45 | 46 | export interface ModulePublicRuntimeConfig {} 47 | 48 | declare module '@nuxt/schema' { 49 | interface PublicRuntimeConfig { 50 | // @ts-ignore 51 | style?: ModulePublicRuntimeConfig; 52 | } 53 | 54 | interface RuntimeConfig { 55 | // @ts-ignore 56 | style?: ModulePrivateRuntimeConfig; 57 | } 58 | 59 | interface NuxtConfig { 60 | // @ts-ignore 61 | style?: Partial 62 | } 63 | 64 | interface NuxtOptions { 65 | // @ts-ignore 66 | style?: ModuleOptions 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/formats/types-strings.ts: -------------------------------------------------------------------------------- 1 | import { get } from '../utils' 2 | 3 | export const getFunction = `const get = ${get.toString()}` 4 | 5 | export const DesignTokenType = 6 | `interface DesignToken { 7 | /* The raw value you specified in your token declaration. */ 8 | value?: T; 9 | /* CSS Variable reference that gets generated. */ 10 | variable?: string; 11 | name?: string; 12 | comment?: string; 13 | themeable?: boolean; 14 | property?: string; 15 | /* Is the property using compose() */ 16 | composed?: boolean; 17 | /* Is the property using palette() */ 18 | palette?: boolean 19 | /* Is the property nested in a selector */ 20 | nested?: string; 21 | /* Is the property nested in variant */ 22 | variant?: string; 23 | attributes?: { 24 | category?: string; 25 | type?: string; 26 | item?: string; 27 | subitem?: string; 28 | state?: string; 29 | [key: string]: any; 30 | }; 31 | [key: string]: any; 32 | }` 33 | 34 | export const NuxtDesignTokensType = 35 | `interface NuxtDesignTokens { 36 | [key: string]: NuxtDesignTokens | DesignToken; 37 | }` 38 | 39 | export const TokenHelperOptionsType = 40 | `interface TokenHelperOptions { 41 | /** 42 | * The key that will be unwrapped from the design token object. 43 | * @default variable 44 | */ 45 | key?: string 46 | /** 47 | * Toggle logging if requesting an unknown token. 48 | * @default false 49 | */ 50 | silent?: boolean 51 | /** 52 | * Toggle deep flattening of the design token object to the requested key. 53 | * 54 | * If you query an token path containing mutliple design tokens and want a flat \`key: value\` object, this option will help you do that. 55 | */ 56 | flatten?: boolean 57 | }` 58 | -------------------------------------------------------------------------------- /src/utils/types.ts: -------------------------------------------------------------------------------- 1 | export type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial; } : T; 2 | 3 | export type WrapKey< 4 | TKey, 5 | TPrefix extends string = '', 6 | TSuffix extends string = '' 7 | > = TKey extends string 8 | ? `${TPrefix}${TKey}${TSuffix}` 9 | : never; 10 | 11 | export type UnwrapKey< 12 | TWrappedKey, 13 | TPrefix extends string = '', 14 | TSuffix extends string = '' 15 | > = TWrappedKey extends WrapKey 16 | ? TKey 17 | : ''; 18 | 19 | export type WrappedValue< 20 | TObject extends object, 21 | TWrappedKey extends string, 22 | TPrefix extends string = '', 23 | TSuffix extends string = '' 24 | > = TObject extends {[K in UnwrapKey]: infer TValue} 25 | ? TValue 26 | : never; 27 | 28 | export type PrefixObjectKeys< 29 | TObject extends object, 30 | TPrefix extends string 31 | > = { 32 | [K in WrapKey]: WrappedValue 33 | } 34 | 35 | export type SuffixObjectKeys< 36 | TObject extends object, 37 | TSuffix extends string 38 | > = { 39 | [K in WrapKey]: WrappedValue 40 | } 41 | 42 | export type WrapObjectKeys< 43 | TObject extends object, 44 | TPrefix extends string, 45 | TSuffix extends string 46 | > = { 47 | [K in WrapKey]: WrappedValue 48 | } 49 | 50 | export type NestedKeyOf = 51 | {[Key in keyof TObject & (string | number)]: TObject[Key] extends object 52 | ? `${Key}` | `${Key}.${NestedKeyOf}` 53 | : `${Key}` 54 | }[keyof TObject & (string | number)]; 55 | 56 | export type WrapUnion< 57 | TObject extends string, 58 | TPrefix extends string, 59 | TSuffix extends string 60 | > = keyof { 61 | [K in WrapKey]: any 62 | } 63 | -------------------------------------------------------------------------------- /src/config/load.ts: -------------------------------------------------------------------------------- 1 | import { mkdir } from 'fs/promises' 2 | import { existsSync } from 'fs' 3 | import { requireModule } from '@nuxt/kit' 4 | import { resolve } from 'pathe' 5 | import { defu } from 'defu' 6 | import { MODULE_DEFAULTS, NuxtLayer } from '../utils' 7 | import type { NuxtStyleTheme } from '../index' 8 | 9 | export const resolveConfig = (layer: NuxtLayer, key: string, configFile = `${key}.config`) => { 10 | const value = layer.config?.style?.[key] || MODULE_DEFAULTS[key] 11 | let config = {} 12 | 13 | let filePath: string 14 | 15 | if (typeof value === 'boolean') { 16 | filePath = resolve(layer.cwd, configFile) 17 | } else if (typeof value === 'string') { 18 | filePath = resolve(layer.cwd, value) 19 | } else if (typeof value === 'object') { 20 | config = value 21 | } 22 | 23 | if (filePath) { 24 | try { 25 | const _file = requireModule(filePath, { clearCache: true }) 26 | if (_file) { config = _file } 27 | } catch (_) {} 28 | } 29 | 30 | return { filePath, config } 31 | } 32 | 33 | /** 34 | * Resolve `tokens` config layers from `extends` layers and merge them via `defu()`. 35 | */ 36 | export const resolveConfigTokens = (layers: NuxtLayer[]) => { 37 | const tokensFilePaths: string[] = [] 38 | let tokens = {} as NuxtStyleTheme 39 | 40 | const splitLayer = (layer: NuxtLayer) => { 41 | // Deeply merge tokens 42 | // In opposition to defaults, here arrays should also be merged. 43 | if (layer.config?.style?.tokens || MODULE_DEFAULTS.tokens) { 44 | const { config: layerTokens, filePath: _layerTokensFilePath } = resolveConfig(layer, 'tokens', 'tokens.config') 45 | 46 | if (_layerTokensFilePath) { tokensFilePaths.push(_layerTokensFilePath) } 47 | 48 | tokens = defu(tokens, layerTokens) 49 | } 50 | } 51 | 52 | for (const layer of layers) { splitLayer(layer) } 53 | 54 | return { tokensFilePaths, tokens } 55 | } 56 | 57 | export const createTokensDir = async (path: string) => { 58 | if (!existsSync(path)) { 59 | await mkdir(path, { recursive: true }) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /playground/pages/index.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 35 | 36 | 100 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F41E Bug Report" 2 | description: Create a report to help us improve Nuxt 3 | labels: ["pending triage"] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Please carefully read the contribution docs before creating a bug report 9 | 👉 https://v3.nuxtjs.org/community/reporting-bugs 10 | Please use the template below to create a minimal reproduction 11 | 👉 https://stackblitz.com/github/nuxt/starter/tree/content 12 | - type: textarea 13 | id: bug-env 14 | attributes: 15 | label: Environment 16 | description: You can use `npx nuxi info` to fill this section 17 | placeholder: Environment 18 | validations: 19 | required: true 20 | - type: textarea 21 | id: reproduction 22 | attributes: 23 | label: Reproduction 24 | description: Please provide a link to a repo that can reproduce the problem you ran into. A [**minimal reproduction**](https://v3.nuxtjs.org/community/reporting-bugs#create-a-minimal-reproduction) is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "need reproduction" label. If no reproduction is provided we might close it. 25 | placeholder: Reproduction 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: bug-description 30 | attributes: 31 | label: Describe the bug 32 | description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! 33 | placeholder: Bug description 34 | validations: 35 | required: true 36 | - type: textarea 37 | id: additonal 38 | attributes: 39 | label: Additional context 40 | description: If applicable, add any other context about the problem here` 41 | - type: textarea 42 | id: logs 43 | attributes: 44 | label: Logs 45 | description: | 46 | Optional if provided reproduction. Please try not to insert an image but copy paste the log text. 47 | render: shell 48 | -------------------------------------------------------------------------------- /src/utils/vue.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Vue SFC Query, forked from the below: 3 | * - original repository url: https://github.com/vitejs/vite/tree/main/packages/plugin-vue 4 | * - code url: https://github.com/vitejs/vite/blob/main/packages/plugin-vue/src/utils/query.ts 5 | * - author: Evan You (https://github.com/yyx990803) 6 | * - license: MIT 7 | * 8 | * - Copied from: https://github.com/intlify/bundle-tools/blob/37ae3acde9e65bf55f5e820b1653b5fddb7ff0cc/packages/unplugin-vue-i18n/src/query.ts#L1 9 | */ 10 | 11 | export interface VueQuery { 12 | vue?: boolean 13 | src?: boolean 14 | global?: boolean 15 | type?: 'script' | 'template' | 'style' | 'custom' 16 | blockType?: string 17 | index?: number 18 | locale?: string 19 | lang?: string 20 | raw?: boolean 21 | scoped?: string 22 | transformed?: boolean 23 | issuerPath?: string 24 | } 25 | 26 | export function parseVueRequest (id: string) { 27 | const [filename, rawQuery] = id.split('?', 2) 28 | const params = new URLSearchParams(rawQuery) 29 | const ret = {} as VueQuery 30 | const langPart = Object.keys(Object.fromEntries(params)).find(key => /lang\./i.test(key)) 31 | ret.vue = params.has('vue') || id.endsWith('.vue') 32 | ret.global = params.has('global') 33 | ret.src = params.has('src') 34 | ret.raw = params.has('raw') 35 | if (params.has('type')) { 36 | ret.type = params.get('type') as VueQuery['type'] 37 | } 38 | if (params.has('blockType')) { 39 | ret.blockType = params.get('blockType') as VueQuery['blockType'] 40 | } 41 | if (params.has('index')) { 42 | ret.index = Number(params.get('index')) 43 | } 44 | if (params.has('data-v-transformed')) { 45 | ret.transformed = Boolean(params.get('data-v-transformed')) 46 | } 47 | if (params.has('scoped')) { 48 | ret.scoped = String(params.get('scoped')) 49 | } 50 | if (langPart) { 51 | const [, lang] = langPart.split('.') 52 | ret.lang = lang 53 | } else if (params.has('lang')) { 54 | ret.lang = params.get('lang') as VueQuery['lang'] 55 | } 56 | if (params.has('issuerPath')) { 57 | ret.issuerPath = params.get('issuerPath') as VueQuery['issuerPath'] 58 | } 59 | // console.log({ filename, rawQuery, params, ret }) 60 | return { 61 | id, 62 | filename, 63 | query: ret 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "preserve", 4 | "target": "ESNext", 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "skipLibCheck": true, 8 | "strict": false, 9 | "allowJs": true, 10 | "noEmit": true, 11 | "resolveJsonModule": true, 12 | "allowSyntheticDefaultImports": true, 13 | "types": [ 14 | "node" 15 | ], 16 | "baseUrl": ".", 17 | "paths": { 18 | "~~": [ 19 | "./playground" 20 | ], 21 | "~~/*": [ 22 | "./playground/*" 23 | ], 24 | "@@": [ 25 | "./playground" 26 | ], 27 | "@@/*": [ 28 | "./playground/*" 29 | ], 30 | "~": [ 31 | "./playground" 32 | ], 33 | "~/*": [ 34 | "./playground/*" 35 | ], 36 | "@": [ 37 | "./playground" 38 | ], 39 | "@/*": [ 40 | "./playground/*" 41 | ], 42 | "assets": [ 43 | "./playground/assets" 44 | ], 45 | "public": [ 46 | "./playground/public" 47 | ], 48 | "public/*": [ 49 | "./playground/public/*" 50 | ], 51 | "@nuxtjs/design-tokens": [ 52 | "./src/index" 53 | ], 54 | "#app": [ 55 | "./node_modules/nuxt/dist/app" 56 | ], 57 | "#app/*": [ 58 | "./node_modules/nuxt/dist/app/*" 59 | ], 60 | "vue-demi": [ 61 | "./node_modules/nuxt/dist/app/compat/vue-demi" 62 | ], 63 | "#design-tokens": [ 64 | "./playground/.nuxt/tokens/index" 65 | ], 66 | "#design-tokens/style": [ 67 | "./playground/.nuxt/tokens/tokens" 68 | ], 69 | "#design-tokens/types": [ 70 | "./playground/.nuxt/tokens/types" 71 | ], 72 | "#head": [ 73 | "./node_modules/nuxt/dist/head/runtime" 74 | ], 75 | "#head/*": [ 76 | "./node_modules/nuxt/dist/head/runtime/*" 77 | ], 78 | "#imports": [ 79 | "./playground/.nuxt/imports" 80 | ], 81 | "#build": [ 82 | "./playground/.nuxt" 83 | ], 84 | "#build/*": [ 85 | "./playground/.nuxt/*" 86 | ], 87 | "#components": [ 88 | "./playground/.nuxt/components" 89 | ] 90 | } 91 | }, 92 | "include": [ 93 | "./nuxt.d.ts", 94 | "./src/**/*" 95 | ] 96 | } 97 | -------------------------------------------------------------------------------- /src/utils/index.ts: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk' 2 | import { kebabCase } from 'scule' 3 | import consola from 'consola' 4 | import { name, version } from '../../package.json' 5 | import type { ModuleOptions } from '../index' 6 | import type { NuxtThemeTokensPaths } from '#design-tokens/types' 7 | 8 | export interface NuxtLayer { 9 | config: any 10 | configFile: string 11 | cwd: string 12 | } 13 | 14 | export * from './types' 15 | 16 | // Default options 17 | export const MODULE_DEFAULTS: ModuleOptions = { 18 | tokens: true, 19 | server: false 20 | } 21 | 22 | // Logging 23 | // Do not import @nuxt/kit here 24 | const _logger = consola 25 | function useLogger (scope) { 26 | return scope ? _logger.withScope(scope) : logger 27 | } 28 | export const logger = useLogger('design-tokens') 29 | 30 | // Package datas 31 | export { name, version } 32 | export const pkgName = chalk.magentaBright(name) 33 | 34 | /** 35 | * Make a list of `get()` compatible paths for any object 36 | */ 37 | export const objectPaths = (data: any) => { 38 | const output: any = [] 39 | function step (obj: any, prev?: string) { 40 | Object.keys(obj).forEach((key) => { 41 | const value = obj[key] 42 | const isarray = Array.isArray(value) 43 | const type = Object.prototype.toString.call(value) 44 | const isobject = 45 | type === '[object Object]' || 46 | type === '[object Array]' 47 | 48 | const newKey = prev 49 | ? `${prev}.${key}` 50 | : key 51 | 52 | if (!output.includes(newKey)) { output.push(newKey) } 53 | 54 | if (!isarray && isobject && Object.keys(value).length) { return step(value, newKey) } 55 | }) 56 | } 57 | step(data) 58 | return output 59 | } 60 | 61 | /** 62 | * Resolve a `var(--token)` value from a token path. 63 | */ 64 | export const resolveVariableFromPath = (path: NuxtThemeTokensPaths): string => `var(--${path.split('.').map(key => kebabCase(key)).join('-')})` 65 | 66 | /** 67 | * Get a key from an object with a dotted syntax. 68 | * @example get({ foot: { bar: 'baz' } }, 'foo.bar') // 'baz' 69 | */ 70 | export const get = (obj, path, defValue = undefined) => { 71 | if (!path) { return undefined } 72 | const pathArray = Array.isArray(path) ? path : path.match(/([^[.\]])+/g) 73 | const result = pathArray.reduce( 74 | (prevObj, key) => prevObj && prevObj[key], 75 | obj 76 | ) 77 | return result === undefined ? defValue : result 78 | } 79 | -------------------------------------------------------------------------------- /src/formats/index.ts: -------------------------------------------------------------------------------- 1 | import { Dictionary } from 'browser-style-dictionary' 2 | import { objectPaths } from '../utils' 3 | import { walkTokens } from './references' 4 | import { tokensHelper } from './tokens-helpers' 5 | import { NuxtDesignTokensType, DesignTokenType, TokenHelperOptionsType, getFunction } from './types-strings' 6 | 7 | export { walkTokens } 8 | 9 | /** 10 | * Formats 11 | */ 12 | 13 | export const tsTypesDeclaration = ({ tokens }: Dictionary) => { 14 | const { type } = walkTokens(tokens) 15 | 16 | let result = 'import type { Ref } from \'vue\'\n\n' 17 | 18 | result = 'import { GlobalTokens } from \'@nuxtjs/design-tokens\'\n\n' 19 | 20 | result = result + `export ${DesignTokenType}\n\n` 21 | 22 | result = result + `export ${NuxtDesignTokensType}\n\n` 23 | 24 | result = result + `export ${TokenHelperOptionsType}\n\n` 25 | 26 | result = result + `export interface NuxtThemeTokens extends GlobalTokens ${JSON.stringify(type, null, 2)}\n\n` 27 | 28 | const tokensPaths = objectPaths(type) 29 | 30 | if (tokensPaths.length) { 31 | result = result + `export type NuxtThemeTokensPaths = ${tokensPaths.map(path => (`'${path}'`)).join(' | \n')}\n\n` 32 | } else { 33 | result = result + 'export type NuxtThemeTokensPaths = \'no.tokens\'\n\n' 34 | } 35 | 36 | result = result.replace(/"DesignToken"/g, 'DesignToken') 37 | 38 | return result 39 | } 40 | 41 | export const tsFull = ({ tokens }: Dictionary) => { 42 | const { type, aliased } = walkTokens(tokens, false) 43 | 44 | let result = 'import type { NuxtThemeTokens, NuxtThemeTokensPaths, TokenHelperOptions } from \'./types.d\'\n\n' 45 | 46 | result = result + 'export * from \'./types.d\'\n\n' 47 | 48 | result = result + `${getFunction}\n\n` 49 | 50 | result = result + `export const tokensAliases = ${JSON.stringify(aliased, null, 2)} as const\n\n` 51 | 52 | result = result + `export const themeTokens: NuxtThemeTokens = ${JSON.stringify(type, null, 2)}\n\n` 53 | 54 | result = result + tokensHelper(true) 55 | 56 | result = result + 'export const $dt = $tokens\n\n' 57 | 58 | return result 59 | } 60 | 61 | export const jsFull = ({ tokens }: Dictionary) => { 62 | const { type, aliased } = walkTokens(tokens, false) 63 | 64 | let result = `${getFunction}\n\n` 65 | 66 | result = result + `export const tokensAliases = ${JSON.stringify(aliased, null, 2)}\n\n` 67 | 68 | result = result + `export const themeTokens = ${JSON.stringify(type, null, 2)}\n\n` 69 | 70 | result = result + tokensHelper(false) 71 | 72 | result = result + 'export const $dt = $tokens\n\n' 73 | 74 | return result 75 | } 76 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@nuxtjs/design-tokens", 3 | "version": "0.0.1", 4 | "description": "'Vue-native zero-runtime CSS-in-JS solution.", 5 | "keywords": [ 6 | "nuxt", 7 | "theme", 8 | "kit" 9 | ], 10 | "repository": "nuxt-community/design-tokens-module", 11 | "license": "MIT", 12 | "type": "module", 13 | "exports": { 14 | ".": "./dist/index.mjs", 15 | "./module": { 16 | "import": "./dist/module.mjs", 17 | "require": "./dist/module.cjs" 18 | }, 19 | "./formats": "./dist/formats.mjs", 20 | "./generate": "./dist/generate.mjs", 21 | "./volar": "./volar/index.js" 22 | }, 23 | "main": "./dist/index.mjs", 24 | "module": "./dist/index.mjs", 25 | "types": "./dist/index.d.ts", 26 | "files": [ 27 | "dist", 28 | "volar" 29 | ], 30 | "scripts": { 31 | "build": "nuxt-module-build", 32 | "dev": "nuxi dev playground", 33 | "build:dev": "nuxi build playground", 34 | "generate:dev": "nuxi generate playground", 35 | "dev:build": "nuxi build playground", 36 | "dev:prepare": "nuxi prepare playground && nuxt-module-build --stub", 37 | "dev:docs": "nuxi dev docs", 38 | "build:docs": "nuxi generate docs", 39 | "lint": "eslint --ext .js,.ts,.vue .", 40 | "prepack": "yarn build", 41 | "test:coverage": "vitest --coverage", 42 | "test:types": "tsc --build tsconfig.json", 43 | "test": "nuxi prepare test/fixtures/basic && vitest run", 44 | "clean": "./.github/scripts/clean.sh", 45 | "prepare": "yarn dev:prepare" 46 | }, 47 | "dependencies": { 48 | "@nuxt/kit": "^3.0.0-rc.8", 49 | "@stitches/stringify": "^1.2.8", 50 | "browser-style-dictionary": "^3.1.1-browser.1", 51 | "chroma-js": "^2.4.2", 52 | "csstype": "^3.1.0", 53 | "json5": "^2.2.1", 54 | "paneer": "^0.0.1", 55 | "postcss-custom-properties": "^12.1.8", 56 | "postcss-easing-gradients": "^3.0.1", 57 | "postcss-nested": "^5.0.6", 58 | "to-ast": "^1.0.0", 59 | "unplugin-ast": "^0.5.5", 60 | "untyped": "^0.4.7" 61 | }, 62 | "devDependencies": { 63 | "@nuxt-themes/docus": "npm:@nuxt-themes/docus-edge@latest", 64 | "@nuxt/module-builder": "^0.1.7", 65 | "@nuxt/schema": "^3.0.0-rc.8", 66 | "@nuxt/test-utils": "npm:@nuxt/test-utils-edge@latest", 67 | "@nuxtjs/eslint-config-typescript": "latest", 68 | "@nuxtjs/tailwindcss": "^5.3.2", 69 | "@volar/vue-language-core": "^0.40.4", 70 | "browser-style-dictionary": "^3.1.1-browser.1", 71 | "c8": "^7.12.0", 72 | "eslint": "^8.23.0", 73 | "globby": "^13.1.2", 74 | "husky": "^8.0.1", 75 | "jiti": "^1.14.0", 76 | "lint-staged": "^13.0.3", 77 | "lodash": "^4.17.21", 78 | "nuxt": "npm:nuxt3@latest", 79 | "pathe": "^0.3.5", 80 | "vitest": "^0.22.1" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/transform/css.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line import/default 2 | import json5 from 'json5' 3 | import { stringify } from '@stitches/stringify' 4 | import { referencesRegex } from '../formats/references' 5 | import { $tokens } from '../index' 6 | 7 | const cssContentRegex = /css\(({.*?\})\)/mgs 8 | 9 | export const resolveStyleTs = (code: string = '', variantsProps = {}) => { 10 | const resolveVariantProps = (property: string, value: any) => { 11 | variantsProps[property] = Object.entries(value).reduce( 12 | (acc, [key, _]) => { 13 | acc[key] = { 14 | type: Boolean, 15 | required: false, 16 | default: false 17 | } 18 | return acc 19 | }, 20 | {} 21 | ) 22 | } 23 | 24 | code = code.replace( 25 | cssContentRegex, 26 | (...cssFunctionMatch) => { 27 | // Parse css({}) content 28 | const declaration = json5.parse(cssFunctionMatch[1]) 29 | 30 | const style = stringify( 31 | declaration, 32 | (property, value) => { 33 | // Match reserved directives (@screen, @dark, @light) 34 | if (property.startsWith('@')) { 35 | const DARK = '@dark' 36 | const LIGHT = '@light' 37 | const SCREEN = /@screen:(.*)/ 38 | const screenMatches = property.match(SCREEN) 39 | if (property === DARK) { 40 | return { 41 | '@media (prefers-color-scheme: dark)': value 42 | } 43 | } 44 | if (property === LIGHT) { 45 | return { 46 | '@media (prefers-color-scheme: light)': value 47 | } 48 | } 49 | if (screenMatches) { 50 | const screenToken = $tokens(`screens.${screenMatches[1]}` as any, { flatten: false, key: undefined, silent: true }) 51 | return { 52 | [`@media (min-width: ${screenToken.original.value})`]: value 53 | } 54 | } 55 | } 56 | 57 | // Push variants to variantsProps 58 | if (value.variants) { 59 | resolveVariantProps(property, value.variants) 60 | } 61 | 62 | // Transform variants to nested selectors 63 | if (property === 'variants') { 64 | return Object.entries(value).reduce( 65 | (acc, [key, value]) => { 66 | acc['&.' + key] = value 67 | return acc 68 | }, 69 | {} 70 | ) 71 | } 72 | 73 | if (typeof value === 'string') { 74 | value = value.replace( 75 | referencesRegex, 76 | (...parts) => { 77 | const [, tokenPath] = parts 78 | 79 | const token = $tokens(tokenPath) 80 | 81 | return token 82 | } 83 | ) 84 | } 85 | 86 | return { 87 | [property]: value 88 | } 89 | } 90 | ) 91 | 92 | return style 93 | } 94 | ) 95 | 96 | return code 97 | } 98 | -------------------------------------------------------------------------------- /docs/content/1.guide/3.API.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: API 3 | description: Discover how to create and use your tokens with @nuxtjs/design-tokens API. 4 | --- 5 | 6 | `tokens` are a [Design Tokens format](https://design-tokens.github.io/community-group/format/) compatible object definition that gets processed by [Style Dictionary](https://amzn.github.io/style-dictionary). 7 | 8 | This allows the options defined by the theme author in his `style.tokens` key to be type-safe for the theme user that will configure his theme via the same key or a `tokens.config` file. 9 | 10 | ### Defining theme tokens 11 | 12 | There is two ways to define theme tokens: 13 | 14 | - Via the `style.tokens` key in the `nuxt.config` file. 15 | - Via the `style.config.{js|ts}` file at the root of your project. 16 | 17 | Both of these options will be merged in the end. 18 | 19 | These two ways will both work for theme authors and theme users as they will get processed in order of priority (user configuration > theme defaults). 20 | 21 | ::code-group 22 | 23 | ```ts [tokens.config.ts] 24 | import { defineTokens } from '@nuxtjs/style' 25 | 26 | export default defineTokens({ 27 | colors: { 28 | primary: { 29 | value: 'green' 30 | }, 31 | secondary: { 32 | value: 'yellow' 33 | }, 34 | } 35 | }) 36 | ``` 37 | 38 | ```ts [nuxt.config.ts] 39 | import { defineNuxtConfig } from 'nuxt' 40 | 41 | export default defineNuxtConfig({ 42 | style: { 43 | tokens: { 44 | colors: { 45 | primary: { 46 | value: 'green' 47 | }, 48 | secondary: { 49 | value: 'yellow' 50 | }, 51 | }, 52 | } 53 | } 54 | }) 55 | ``` 56 | 57 | :: 58 | 59 | ### Consuming theme tokens 60 | 61 | Theme tokens gets processed by [Style Dictionary](https://amzn.github.io/style-dictionary) and generates build targets that are globally accessible in your Nuxt project. 62 | 63 | - `.nuxt/theme/` 64 | - `tokens.css` global CSS variables injected to your Nuxt ``. 65 | - `tokens.scss` for `scss` contexts. 66 | - `tokens.json` if you want to import it from a JSON context. 67 | - `index.ts` to import it from runtime or from any TypeScript context. 68 | - `index.js` to import it from runtime or from any JavaScript context. 69 | - `types.d.ts` for global type inference (`$dt()`, `$tokens()`, `useTokens()`, `defineTokens`, `nuxt.config.style.tokens`). 70 | 71 | - **Composable usage** 72 | ```ts 73 | const { $dt } = useTokens() 74 | 75 | const primaryColor = $dt('colors.primary') 76 | ``` 77 | 78 | - **`