diff --git a/.changeset/chatty-shoes-taste.md b/.changeset/chatty-shoes-taste.md new file mode 100644 index 00000000..ecda8600 --- /dev/null +++ b/.changeset/chatty-shoes-taste.md @@ -0,0 +1,5 @@ +--- +'@cobalt-ui/core': patch +--- + +Fix strokeStyle object values diff --git a/.changeset/fresh-buses-lick.md b/.changeset/fresh-buses-lick.md new file mode 100644 index 00000000..3d25f9fd --- /dev/null +++ b/.changeset/fresh-buses-lick.md @@ -0,0 +1,5 @@ +--- +'@cobalt-ui/cli': patch +--- + +Call all config() callbacks before running builds diff --git a/.changeset/grumpy-snakes-type.md b/.changeset/grumpy-snakes-type.md new file mode 100644 index 00000000..eff236df --- /dev/null +++ b/.changeset/grumpy-snakes-type.md @@ -0,0 +1,5 @@ +--- +'@cobalt-ui/plugin-css': patch +--- + +Allow generateName() to return `undefined` or `null` to fall back to default name generation diff --git a/docs/.env.example b/docs/.env.example deleted file mode 100644 index dc23c6cb..00000000 --- a/docs/.env.example +++ /dev/null @@ -1 +0,0 @@ -FIGMA_API_KEY= diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..b7330346 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +.vitepress/cache +.vitepress/dist diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts new file mode 100644 index 00000000..605f63cb --- /dev/null +++ b/docs/.vitepress/config.ts @@ -0,0 +1,88 @@ +import {defineConfig} from 'vitepress'; +import packageJSON from '../../packages/cli/package.json'; + +/** @see https://vitepress.dev/reference/site-config */ +export default defineConfig({ + title: 'Cobalt', + description: 'Tooling to use DTFM Design Tokens anywhere', + cleanUrls: true, + /** @see https://vitepress.dev/reference/default-theme-config */ + themeConfig: { + logo: '/images/cobalt-icon-solid.svg', + nav: [ + { + text: `v${packageJSON.version}`, + items: [{text: 'Changelog', link: 'https://github.com/drwpow/cobalt-ui/blob/main/packages/cli/CHANGELOG.md'}], + }, + ], + sidebar: [ + { + text: 'Guides', + collapsed: false, + items: [ + {text: 'Getting Started', link: '/guides/getting-started'}, + {text: 'tokens.json', link: '/guides/tokens'}, + {text: 'CLI', link: '/guides/cli'}, + {text: 'Modes', link: '/guides/modes'}, + ], + }, + { + text: 'Tokens', + collapsed: true, + items: [ + {text: 'Color', link: '/tokens/color'}, + {text: 'Dimension', link: '/tokens/dimension'}, + {text: 'Font Family', link: '/tokens/font-family'}, + {text: 'Font Weight', link: '/tokens/font-weight'}, + {text: 'Duration', link: '/tokens/duration'}, + {text: 'Cubic Bézier', link: '/tokens/cubic-bezier'}, + {text: 'Number', link: '/tokens/number'}, + {text: 'Link (ext)', link: '/tokens/link'}, + {text: 'Stroke Style', link: '/tokens/stroke-style'}, + {text: 'Border', link: '/tokens/border'}, + {text: 'Transition', link: '/tokens/transition'}, + {text: 'Shadow', link: '/tokens/shadow'}, + {text: 'Gradient', link: '/tokens/gradient'}, + {text: 'Typography', link: '/tokens/typography'}, + {text: 'Group', link: '/tokens/group'}, + {text: 'Alias', link: '/tokens/alias'}, + {text: 'Custom Tokens', link: '/tokens/custom'}, + ], + }, + { + text: 'Integrations', + collapsed: false, + items: [ + {text: 'CSS', link: '/integrations/css'}, + {text: 'Figma', link: '/integrations/figma'}, + {text: 'JS/TS', link: '/integrations/js'}, + {text: 'JSON/Native', link: '/integrations/json'}, + {text: 'Sass', link: '/integrations/sass'}, + {text: 'Style Dictionary', link: '/integrations/style-dictionary'}, + {text: 'Tailwind', link: '/integrations/tailwind'}, + {text: 'Other', link: '/integrations/other'}, + ], + }, + { + text: 'Advanced', + collapsed: true, + items: [ + {text: 'Config', link: '/advanced/config'}, + {text: 'Node.js API', link: '/advanced/node'}, + {text: 'Plugin API', link: '/advanced/plugin-api'}, + {text: 'CI', link: '/advanced/ci'}, + {text: 'About', link: '/advanced/about'}, + ], + }, + ], + search: { + provider: 'algolia', + options: { + appId: '2U13I82HTZ', + apiKey: 'b67c6e8504f5721bb7c0875d044bfddb', + indexName: 'cobalt-ui', + }, + }, + socialLinks: [{icon: 'github', link: 'https://github.com/drwpow/cobalt-ui'}], + }, +}); diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 00000000..fa1ef9a8 --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,17 @@ +// https://vitepress.dev/guide/custom-theme +import {h} from 'vue'; +import type {Theme} from 'vitepress'; +import DefaultTheme from 'vitepress/theme'; +import './style.css'; + +export default { + extends: DefaultTheme, + Layout: () => { + return h(DefaultTheme.Layout, null, { + // https://vitepress.dev/guide/extending-default-theme#layout-slots + }); + }, + enhanceApp({app, router, siteData}) { + // ... + }, +} satisfies Theme; diff --git a/docs/src/styles/_fonts.scss b/docs/.vitepress/theme/style.css similarity index 59% rename from docs/src/styles/_fonts.scss rename to docs/.vitepress/theme/style.css index 2143bdda..ef409e66 100644 --- a/docs/src/styles/_fonts.scss +++ b/docs/.vitepress/theme/style.css @@ -1,5 +1,7 @@ -@use 'sass:map'; -@use '../../tokens/' as *; +/** + * Customize default theme styling by overriding CSS variables: + * https://github.com/vuejs/vitepress/blob/main/src/client/theme-default/styles/vars.css + */ @font-face { font-family: 'Neue Montreal'; @@ -204,3 +206,165 @@ src: url(/fonts/redhatmono-v10-300-normal-latin.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; } + +/** + * Typography + * -------------------------------------------------------------------------- */ + +:root { + --vp-font-family-base: 'Neue Montreal', -apple-system, 'system-ui', 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji'; + --vp-font-family-mono: 'Red Hat Mono', ui-monospace, monospace; +} + +em, +i { + font-variation-settings: 'ital' 900; + font-style: normal; +} + +/** + * Colors + * + * Each colors have exact same color scale system with 3 levels of solid + * colors with different brightness, and 1 soft color. + * + * - `XXX-1`: The most solid color used mainly for colored text. It must + * satisfy the contrast ratio against when used on top of `XXX-soft`. + * + * - `XXX-2`: The color used mainly for hover state of the button. + * + * - `XXX-3`: The color for solid background, such as bg color of the button. + * It must satisfy the contrast ratio with pure white (#ffffff) text on + * top of it. + * + * - `XXX-soft`: The color used for subtle background such as custom container + * or badges. It must satisfy the contrast ratio when putting `XXX-1` colors + * on top of it. + * + * The soft color must be semi transparent alpha channel. This is crucial + * because it allows adding multiple "soft" colors on top of each other + * to create a accent, such as when having inline code block inside + * custom containers. + * + * - `default`: The color used purely for subtle indication without any + * special meanings attched to it such as bg color for menu hover state. + * + * - `brand`: Used for primary brand colors, such as link text, button with + * brand theme, etc. + * + * - `tip`: Used to indicate useful information. The default theme uses the + * brand color for this by default. + * + * - `warning`: Used to indicate warning to the users. Used in custom + * container, badges, etc. + * + * - `danger`: Used to show error, or dangerous message to the users. Used + * in custom container, badges, etc. + * -------------------------------------------------------------------------- */ + +:root { + --vp-c-default-1: var(--vp-c-gray-1); + --vp-c-default-2: var(--vp-c-gray-2); + --vp-c-default-3: var(--vp-c-gray-3); + --vp-c-default-soft: var(--vp-c-gray-soft); + + --vp-c-brand-1: oklch(60% 0.216564 269); + --vp-c-brand-2: oklch(50% 0.216583 268); + --vp-c-brand-3: oklch(60% 0.216564 269); + --vp-c-brand-soft: oklch(60% 0.216564 269/0.2); + + --vp-c-tip-1: var(--vp-c-brand-1); + --vp-c-tip-2: var(--vp-c-brand-2); + --vp-c-tip-3: var(--vp-c-brand-3); + --vp-c-tip-soft: var(--vp-c-brand-soft); + + --vp-c-warning-1: var(--vp-c-yellow-1); + --vp-c-warning-2: var(--vp-c-yellow-2); + --vp-c-warning-3: var(--vp-c-yellow-3); + --vp-c-warning-soft: var(--vp-c-yellow-soft); + + --vp-c-danger-1: var(--vp-c-red-1); + --vp-c-danger-2: var(--vp-c-red-2); + --vp-c-danger-3: var(--vp-c-red-3); + --vp-c-danger-soft: var(--vp-c-red-soft); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: transparent; + --vp-button-brand-text: var(--vp-c-white); + --vp-button-brand-bg: var(--vp-c-brand-3); + --vp-button-brand-hover-border: transparent; + --vp-button-brand-hover-text: var(--vp-c-white); + --vp-button-brand-hover-bg: var(--vp-c-brand-2); + --vp-button-brand-active-border: transparent; + --vp-button-brand-active-text: var(--vp-c-white); + --vp-button-brand-active-bg: var(--vp-c-brand-1); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: -webkit-linear-gradient(120deg, oklch(60% 0.216564 269) 30%, oklch(50% 0.216583 268)); + --vp-home-hero-image-background-image: linear-gradient(-45deg, #bd34fe 50%, #47caff 50%); + --vp-home-hero-image-filter: blur(44px); +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(68px); + } +} + +/** + * Component: Custom Block + * -------------------------------------------------------------------------- */ + +:root { + --vp-custom-block-tip-border: transparent; + --vp-custom-block-tip-text: var(--vp-c-text-1); + --vp-custom-block-tip-bg: var(--vp-c-brand-soft); + --vp-custom-block-tip-code-bg: var(--vp-c-brand-soft); +} + +/** + * Component: Algolia + * -------------------------------------------------------------------------- */ + +.DocSearch { + --docsearch-primary-color: var(--vp-c-brand-1) !important; +} + +/** + * Page: Home + * -------------------------------------------------------------------------- */ + +.VPHome { + background-image: url('/images/home-bg.png'); + background-repeat: no-repeat; + background-position: center 3rem; + background-size: 200% auto; +} + +.dark .VPHome { + background-image: url('/images/home-bg-dark.png'); +} + +@media (min-width: 800px) { + .VPHome { + background-size: 100% auto; + background-position: 100% 4rem; + } +} diff --git a/docs/src/pages/docs/reference/about.md b/docs/advanced/about.md similarity index 52% rename from docs/src/pages/docs/reference/about.md rename to docs/advanced/about.md index b5db13f3..a46d6a90 100644 --- a/docs/src/pages/docs/reference/about.md +++ b/docs/advanced/about.md @@ -1,23 +1,24 @@ --- -title: About Cobalt -layout: ../../../layouts/docs.astro +title: About --- -# About Cobalt +# About + +Cobalt was created to support the [Design Tokens Format Module (DTFM)](https://designtokens.org) and provide a pluggable, extensible interface for generating code for any platform. ## Project Goals -1. Support the complete and full [Design Tokens Format Module](https://design-tokens.github.io/community-group/format) spec -2. Support a pluggable and configurable architecture, enabling users and the community to write their own plugins to generate any format -3. Make syncing from Figma easy and automatable ([docs](/docs/integrations/tokens-studio)) +1. Support the complete and full [Design Tokens Format Module](https://design-tokens.github.io/community-group/format) spec. +1. Offer the widest compatiblity possible for any platform. +1. Support a pluggable and configurable architecture, enabling users to write their own plugins to generate any format. -## Why the name “Cobalt”? +## Why the name “Cobalt?” The name **Cobalt** has three meanings: -1. Cobalt [the element](https://en.wikipedia.org/wiki/Cobalt) is a nod to design tokens being the “atoms” of your design system
+1. Cobalt [the element](https://en.wikipedia.org/wiki/Cobalt) is a nod to design tokens being the “atoms” of your design system.
_Fun fact: the animated token icons on this docs site are a nod to Cobalt’s hexagonal atomic structure_ -2. Cobalt [the pigment](https://artsartistsartwork.com/history-of-the-colour-blue-in-art/) is a nod to blue being the last missing color in art history, just as design tokens are the last missing piece of design systems
+2. Cobalt [the pigment](https://artsartistsartwork.com/history-of-the-colour-blue-in-art) is a nod to blue being the last missing color in art history, just as design tokens are the last missing piece of design systems.
_Fun fact: Van Gogh once said “Cobalt is a divine colour and there is nothing so beautiful for putting atmosphere around things”_ -3. Cobalt the color is a nod to [blueprints](https://en.wikipedia.org/wiki/Blueprint)
+3. Cobalt the color is a nod to [blueprints](https://en.wikipedia.org/wiki/Blueprint).
_Fun fact: blueprints originally were colored with Prussian Blue (ferrous ferrocyanide), not Cobalt, for cost reasons_ diff --git a/docs/advanced/ci.md b/docs/advanced/ci.md new file mode 100644 index 00000000..06cb3390 --- /dev/null +++ b/docs/advanced/ci.md @@ -0,0 +1,57 @@ +--- +title: CI +--- + +# CI + +Using your preferred CI stack, here’s an example of how you could add Cobalt to your CI. First, we’ll take a `package.json` that had an existing `npm run build` command, and add `co build` script to it: + +::: code-group + +```json [package.json] +{ + "scripts": { + "build": "npm run build:app", // [!code --] + "build": "npm run build:tokens && npm run build:app", // [!code ++] + "build:app": "vite build", + "build:tokens": "co build" // [!code ++] + } +} +``` + +::: + +This is just a generic example. The important part is that `co build` is run somehow during the build. + +## GitHub Actions + +```yaml +name: CI + +on: + push: + branches: + - main + pull_request: + +concurrency: + group: ci-\${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 20 + - run: npm i + - run: npm run build +``` + +This will run `co build` on every code change, which validates your `tokens.json` and runs all plugins to make sure they’re all working as expected. + +## Publishing to npm + +You could then take the additional step of using a package versioning tool like [Changesets](https://github.com/changesets/changesets) to release npm packages from CI ([Cobalt does this!](https://github.com/drwpow/cobalt-ui/blob/main/.github/workflows/release.yml)). diff --git a/docs/advanced/config.md b/docs/advanced/config.md new file mode 100644 index 00000000..0b5fc48b --- /dev/null +++ b/docs/advanced/config.md @@ -0,0 +1,147 @@ +--- +title: Config +--- + +# Config + +Customizing Cobalt and managing plugins requires you to add a `tokens.config.mjs` file in the root of your project. Here’s an example configuration with all settings and defaults: + +::: code-group + +```js [tokens.config.mjs] +import pluginJS from '@cobalt-ui/plugin-js'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [pluginJS()], + + // token type options +}; +``` + +::: + +## `tokens` + +The `tokens` property is where you tell Cobalt your `tokens.json` file lives ([or files](#multiple-schemas)). By default this is set to `./tokens.json`, so this option can be omitted if your tokens manifest lives there. + +### YAML + +Cobalt supports `tokens.json` as YAML as well: + +::: code-group + +```js [tokens.config.mjs] +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.yaml', +}; +``` + +::: + +::: info +The file extension must be `.yml` or `.yaml` to be parsed as YAML +::: + +### Remote schemas + +Cobalt can load tokens from any **publicly-available** URL: + +::: code-group + +```js [tokens.config.mjs] +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: 'https://my-bucket.s3.amazonaws.com/tokens.json', +}; +``` + +::: + +### npm + +To load tokens from an npm package, update `config.tokens` to point to the **full JSON path** (not merely the root package): + +::: code-group + +```js [tokens.config.mjs] +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: '@my-scope/my-tokens', // [!code --] + tokens: '@my-scope/my-tokens/tokens.json', // [!code ++] +}; +``` + +::: + +### Multiple schemas + +Cobalt supports loading multiple tokens schemas by passing an array: + +::: code-group + +```js [tokens.config.mjs] +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: ['./base.json', './theme.json', './overrides.json'], +}; +``` + +::: + +Cobalt will flatten these schemas in order, with the latter entries overriding the former if there are any conflicts. The final result of all the combined schemas **must** result in a valid tokens.json. + +::: info +All aliases must refer to the same document. For example, instead of `{./theme.json#/color.action.50}`, omit the filepath and use `{color.action.50}` as if it were in the same file. +::: + +## `outdir` + +`outdir` is the **output directory** where all plugins will generate files to. This can only be set to a directory, not a file, since you’ll almost always use multiple plugins and generate multiple output files. By default this is `./tokens/`, but you can move this anywhere. Note that each plugin decides where to generate its file(s), but you can usually configure additional options per-plugin. + +## `plugins` + +The following official plugins are available. Refer to each’s documentation to learn all it can do as well as all the options available: + +- [@cobalt-ui/plugin-css](/integrations/css): Generate CSS variables, and optionally utility CSS +- [@cobalt-ui/plugin-js](/integrations/js): Generate JavaScript + TypeScript +- [@cobalt-ui/plugin-js](/integrations/json): Generate JSON for universal usage +- [@cobalt-ui/plugin-sass](/integrations/sass): Generate Sass (compatible with the CSS plugin) +- [@cobalt-ui/plugin-tailwind](/integrations/tailwind): Generate a Tailwind CSS theme (compatible with the CSS plugin) +- @cobalt-ui/plugin-img: TODO +- @cobalt-ui/plugin-php: TODO +- @cobalt-ui/plugin-python: TODO +- @cobalt-ui/plugin-ruby: TODO +- @cobalt-ui/plugin-elixir: TODO + +_If you’ve created a Cobalt plugin of your own, please [suggest yours](https://github.com/drwpow/cobalt-ui)!_ + +### Custom Plugins + +Creating custom plugins is designed to be easy. Please [view the plugin guide](/advanced/plugin-api) to learn how to create your own. + +## Token Type Options + +Some token types allow for extra configuration. + +## Color + +::: code-group + +```js [tokens.config.mjs] +/** @type import('@cobalt-ui/core').Config */ +export default { + color: { + convertToHex: false, // Convert all colors to sRGB hexadecimal (default: false). By default, colors are kept in their formats + }, +}; +``` + +::: + +| Name | Type | Description | +| :------------------- | :-------: | :--------------------------------------------------------------------------------------------------------------- | +| `color.convertToHex` | `boolean` | Convert this color to sRGB hexadecimal. By default, colors are kept in the original formats they’re authored in. | diff --git a/docs/advanced/node.md b/docs/advanced/node.md new file mode 100644 index 00000000..25c398b4 --- /dev/null +++ b/docs/advanced/node.md @@ -0,0 +1,38 @@ +--- +title: Node.js API +--- + +# Node.js API + +Cobalt’s Node.js API is for parsing and validating the [Design Tokens Format Module](https://designtokens.org) (DTFM) standard. It can’t output code like the [CLI](/guides/cli) can, but it is a lightweight and fast parser/validator for the DTFM spec that could even be used in client code if desired. + +## Setup + +```sh +npm install @cobalt-ui/core +``` + +## Usage + +Parse a `tokens.json` file into a JS object + + +```js +import co from '@cobalt-ui/core'; + +const designTokens = { + color: { + red: {$type: 'color', $value: '#e34850'}, + green: {$type: 'color', $value: '#2d9d78'}, + blue: {$type: 'color', $value: '#2680eb'}, + }, +}; + +const {errors, warnings, result} = co.parse(designTokens); +``` + +| Name | Type | Description | +| :--------- | :------------------------ | :------------------------------------------------------------------------------------------------------------------------------------------ | +| `result` | Token[] | Flattened array of all parsed tokens in the schema (this may be incomplete if `errors` present) | +| `errors` | `string[]` \| `undefined` | If present, unrecoverable errors were encountered (you should probably `throw` with these messages). | +| `warnings` | `string[]` \| `undefined` | If present, the parser found schema issues that are likely undesirable, but the schema is still usable (you should probably show the user). | diff --git a/docs/advanced/plugin-api.md b/docs/advanced/plugin-api.md new file mode 100644 index 00000000..5ed2318e --- /dev/null +++ b/docs/advanced/plugin-api.md @@ -0,0 +1,208 @@ +--- +title: Plugin API +--- + +# Plugin API + +Creating your own Cobalt plugins is easy if you’re comfortable with JavaScript. This guide is for creating a custom plugin yourself; if you’re looking for instructions on how to use existing plugins, [see the Getting Started guide](/guides/getting-started#next-steps). + +## Plugin Format + +A Cobalt plugin is designed similarly to a Rollup or Vite plugin, if you’re familiar with those (no worries if you’re not). A plugin is essentially **any function that returns an object with the following keys**: + +| Key | Type | Description | +| :------- | :--------: | :----------------------------------------------------------- | +| `name` | `string` | **Required.** The name of your plugin (shown on errors) | +| `config` | `function` | (Optional) Read the user’s config, and optionally modify it. | +| `build` | `function` | **Required.** The build output of your plugin. | + +_Note: the following examples will be using TypeScript, but JavaScript will work just as well if you prefer!_ + +```ts +import type {Plugin} from '@cobalt-ui/core'; + +export default function myPlugin(): Plugin { + return { + name: 'my-plugin', + config(config) { + // read final user config + }, + async build({tokens, metadata, rawSchema}) { + // (your plugin code here) + + return [ + { + filename: 'my-filename.json', + contents: tokens, + }, + ]; + }, + }; +} +``` + +### Accepting Options + +Your plugin can accept options as the parameters of your main function. The structure is up to you and what makes sense of your plugin. Here’s an example of letting a user configure the `filename`: + +```ts +import type {Plugin} from '@cobalt-ui/core'; + +export interface MyPluginOptions { + /** (Optional) Set the output filename */ + filename?: string; + // add more options here! +} + +export default function myPlugin(options: MyPluginOptions = {}): Plugin { + const filename = options.filename || 'default-filename.json'; // be sure to always set a default! + return { + name: 'my-plugin', + async build({tokens, rawSchema}) { + // (your plugin code here) + + return [ + { + filename, + contents: tokens, + }, + ]; + }, + }; +} +``` + +You’d then pass any options into `tokens.config.mjs`: + +```js +import myPlugin from './my-plugin.js'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + plugins: [ + myPlugin({ + filename: 'custom.json', + }), + ], +}; +``` + +You can then expand `options` to be whatever shape you need it to be. + +## `name` + +Naming your plugin helps identify it in case something goes wrong during the build. You can name your plugin anything. + +## `config()` + +The `config()` function is an optional callback that can read the final user config or modify it. Use it if you need to read a user’s setting. Though you _can_ mutate the config, don’t do so unless absolutely necessary! + +```ts +import type {Plugin} from '@cobalt-ui/core'; + +export default function myPlugin(): Plugin { + let outDir: URL | undefined; + return { + name: 'my-plugin', + config(config) { + outDir = config.outDir; // read the user’s outDir from the config, and save it + // return nothing to leave config unaltered + }, + async build({tokens, rawSchema}) { + console.log(outDir); // now config info is accessible within the build() function + + // (your plugin code here) + + return [{filename: 'my-filename.json', contents: tokens}]; + }, + }; +} +``` + +`config()` will be fired _after_ the user’s config has been fully loaded and all plugins are instantiated, and _before_ any build happens. + +## `build()` + +The `build()` function takes one parameter object with 3 keys: + +| Name | Type | Description | +| :---------- | :-------------------: | :---------------------------------------------------------- | +| `tokens` | `Token[]` | An array of tokens with metadata ([docs](#token-structure)) | +| `rawSchema` | `DTFM JSON` | The original `tokens.json` file, unedited. | +| `metadata` | `Record` | (currently unused) | + +After running, and formatting your output, the `build()` function should return an array of objects with the following properties: + +| Name | Type | Description | +| :--------- | :------------------: | :------------------------------------------------------------------ | +| `filename` | `string` | Filename (relative to user’s `outDir` setting, default `./tokens/`) | +| `contents` | `string` \| `Buffer` | File contents to be written to disk. | + +```ts +export default function myPlugin(): Plugin { + return { + name: 'my-plugin', + async build({tokens, rawSchema}) { + // (your plugin code here) + + return [ + {filename: './output-1.json', contents: jsonContents}, + {filename: './output-2.svg', contents: svgContents}, + ]; + }, + }; +} +``` + +### Token structure + +Cobalt gives you more context when dealing with tokens. Inspecting each individual token will yield the following: + +```js +{ + id: 'color.brand.green', // the full ID of the token + $type: 'color', // the original $type + $value: '#40c362', // the normalized $value + $extensions: { + mode: {…} // normalized modes + }, + _group: {…} // metadata about the token’s parent group + _original: {…} // the original node untouched from tokens.json (including unresolved aliases, etc.) +} +``` + +### Tips + +- For `build()`, return a **relative filename**. That way it respects the user’s `outDir` setting. +- Returning only one file is normal! Most plugins only output one file. +- Use JSDoc comments as much as possible! They go a long way in good DX of your plugin. +- For the full ID of the token, a dot (`.`) always represents a group. So for `color.brand.green`, you’re looking at the `green` token, inside the `brand` group, inside the `color` group. Groups aren’t allowed to have dots in their names. +- Cobalt will always resolve `$value` to the final value, even for aliased tokens. To see the original alias name, see `_original.$value`. + +## Lifecycle + +Cobalt executes in the following order: + +1. **Plugin instantiation.** All plugins are loaded, in array order, in the user’s config. +2. **Config.** `config()` is called on every plugin (if present), also in array order. Note that if any plugin modifies the config, the changes will only be picked up by plugins that appear later in the array. +3. **Build.** `build()` is called on every plugin, in parallel. +4. **Write.** Cobalt writes each plugin’s file(s) to disk after it’s done. This also happens in parallel, and happens as soon as each plugin finishes. + +## Testing + +To test your plugin working on your design tokens, add it to your `tokens.config.mjs`: + +```js +import myPlugin from './my-plugin.js'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + plugins: [myPlugin()], +}; +``` + +Now when you run `co build`, your plugin will run and you can see its output. + +## Examples + +Examples of plugins may be found [in the original source repo](https://github.com/drwpow/cobalt-ui/tree/main/packages). diff --git a/docs/astro.config.js b/docs/astro.config.js deleted file mode 100644 index 9cf9332f..00000000 --- a/docs/astro.config.js +++ /dev/null @@ -1,5 +0,0 @@ -import {defineConfig} from 'astro/config'; - -export default defineConfig({ - site: `https://cobalt-ui.pages.dev`, -}); diff --git a/docs/guides/cli.md b/docs/guides/cli.md new file mode 100644 index 00000000..7921aa1b --- /dev/null +++ b/docs/guides/cli.md @@ -0,0 +1,79 @@ +--- +title: CLI +--- + +# CLI + +The Cobalt CLI is the primary way to turn your Design Token Format Module (DTFM) design tokens into code. To install it to your project, run: + +```sh +npm i -D @cobalt-ui/cli +``` + +## Build + +Most of the time you’ll be running the **build** command: + +```sh +npx co build +``` + +This first **validates** your schema (and will error on any schema errors), then it generates code using your installed [plugins](/guides/getting-started#next-steps). + +::: info +If you end up with stale assets in `outDir` from old tokens or plugins no longer used, there’s not an option to clear out this directory on build (Cobalt doesn’t assume it owns this folder). However, there are easy-to-use tools such as [del-cli](https://www.npmjs.com/package/del-cli) you can run before each build. +::: + +### Watch mode + +To build your tokens as you work, add the `--watch` flag: + +```sh +npx co build --watch +``` + +This watches for any changes in your `tokens.json` file, and automatically runs a build on any change. Useful for developing locally! + +## Validate + +To only validate your `tokens.json` schema without loading plugins, run the **check** command: + +```sh +npx co check [path] +``` + +This will show any errors and warnings in your schema. `[path]` can be ommitted if you only want to validate the file(s) set to [`token`](/advanced/config#token) in your [config file](/advanced/config). + +## Bundle + +To combine multiple `tokens.json` files into one, use the `bundle` command: + +```sh +npx co bundle --out [path] +``` + +Be sure to specify a `[path]`! + +## Convert + +The **convert** comand is useful for converting a foreign format to DTFM. Currently only converting from the [Style Dictionary token format](https://amzn.github.io/style-dictionary) format is supported. + +### Style Dictionary Format + +To convert from the [Style Dictionary token format](https://amzn.github.io/style-dictionary) to DTFM, run: + +```sh +npx co convert [input] --out [output] +``` + +[See full guide](/integrations/style-dictionary) + +## Init + +You can initialize a placeholder `tokens.json` file with the **init** command: + +```sh +npx co init +``` + +There’s not much here, but it can at least save a little typing. diff --git a/docs/guides/getting-started.md b/docs/guides/getting-started.md new file mode 100644 index 00000000..8795f713 --- /dev/null +++ b/docs/guides/getting-started.md @@ -0,0 +1,212 @@ +--- +title: Getting Started +--- + +# Getting Started + +Cobalt turns your [design tokens](/guides/design-tokens) into code using a CLI or Node.js. Cobalt is configurable and pluggable, and can generate [JavaScript](/integrations/js), [TypeScript](/integrations/js), +[JSON](/integrations/json), [CSS](/integrations/css), and [Sass](/integrations/sass) via the official plugins, and even [integrate with Tailwind CSS](/integrations/tailwind). + +You can also create your own plugin to turn your design tokens into anything using [the plugin API](/advanced/plugin-api). + +## Setup + +With [the latest version of Node installed](https://nodejs.org), install the Cobalt CLI using npm, along with any plugins you’d like (for our example we’ll install the JS and CSS plugins, but you can install fewer or more depending on what you’d like): + +```sh +npm i -D @cobalt-ui/cli @cobalt-ui/plugin-css @cobalt-ui/plugin-js +``` + +Next, we’ll create a `tokens.json` file in the root of our project (or `tokens.yaml`). This is where we’ll put all our [tokens](/guides/tokens): + +::: code-group + +```json [JSON] +{ + "color": { + "$type": "color", + "base": { + "gray": { + "0": {"$value": "#f6f8fa"}, + "1": {"$value": "#eaeef2"}, + "2": {"$value": "#d0d7de"}, + "3": {"$value": "#afb8c1"}, + "4": {"$value": "#8c959f"}, + "5": {"$value": "#6e7781"}, + "6": {"$value": "#57606a"}, + "7": {"$value": "#424a53"}, + "8": {"$value": "#32383f"}, + "9": {"$value": "#24292f"} + }, + "blue": { + "0": {"$value": "#ddf4ff"}, + "1": {"$value": "#b6e3ff"}, + "2": {"$value": "#80ccff"}, + "3": {"$value": "#54aeff"}, + "4": {"$value": "#218bff"}, + "5": {"$value": "#0969da"}, + "6": {"$value": "#0550ae"}, + "7": {"$value": "#033d8b"}, + "8": {"$value": "#0a3069"}, + "9": {"$value": "#002155"} + } + }, + "semantic": { + "action": "{color.base.blue.5}", + "textColor": "{color.base.gray.9}" + } + }, + "fontStack": { + "$type": "fontFamily", + "sansSerif": { + "$value": ["-apple-system", "BlinkMacSystemFont", "Segoe UI", "Noto Sans", "Helvetica", "Arial", "sans-serif", "Apple Color Emoji", "Segoe UI Emoji"] + } + }, + "space": { + "$type": "dimension", + "xxsmall": {"$value": "2px"}, + "xsmall": {"$value": "4px"}, + "small": {"$value": "6px"}, + "medium": {"$value": "8px"}, + "large": {"$value": "12px"}, + "xlarge": {"$value": "16px"} + } +} +``` + +```yaml [YAML] +color: + $type: color + base: + gray: + '0': + $value: '#f6f8fa' + '1': + $value: '#eaeef2' + '2': + $value: '#d0d7de' + '3': + $value: '#afb8c1' + '4': + $value: '#8c959f' + '5': + $value: '#6e7781' + '6': + $value: '#57606a' + '7': + $value: '#424a53' + '8': + $value: '#32383f' + '9': + $value: '#24292f' + blue: + '0': + $value: '#ddf4ff' + '1': + $value: '#b6e3ff' + '2': + $value: '#80ccff' + '3': + $value: '#54aeff' + '4': + $value: '#218bff' + '5': + $value: '#0969da' + '6': + $value: '#0550ae' + '7': + $value: '#033d8b' + '8': + $value: '#0a3069' + '9': + $value: '#002155' + semantic: + action: '{color.base.blue.5}' + textColor: '{color.base.gray.9}' +fontStack: + $type: fontFamily + sansSerif: + $value: + - -apple-system + - BlinkMacSystemFont + - Segoe UI + - Noto Sans + - Helvetica + - Arial + - sans-serif + - Apple Color Emoji + - Segoe UI Emoji +space: + $type: dimension + xxsmall: + $value: 2px + xsmall: + $value: 4px + small: + $value: 6px + medium: + $value: 8px + large: + $value: 12px + xlarge: + $value: 16px +``` + +::: + +Then we’ll configure our plugins. Create a `tokens.config.mjs` file ([docs](/advanced/config)) also in the root of your project: + +::: code-group + +```js [tokens.config.mjs] +import pluginCSS from '@cobalt-ui/plugin-css'; +import pluginJS from '@cobalt-ui/plugin-js'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [pluginCSS(/* options */), pluginJS(/* options */)], +}; +``` + +::: + +Lastly, run the following command to generate all code from your tokens: + +```sh +npx co build +``` + +You should see a new `/tokens/` folder with your newly-generated tokens: + +``` +├── package.json +├── tokens/ <-- ✨ New ✨ +│ ├── index.css +│ ├── index.d.ts +│ ├── index.js +├── tokens.config.mjs +└── tokens.json +``` + +You can now use the generated CSS and/or JS in your project! + +You can also change any settings in `tokens.config.mjs` ([docs](/advanced/config)) such as the name of `tokens.json` and the output folder, as well as configure plugins individually (be sure to read guides on all each plugin can do—some can do quite a bit!). + +## Next Steps + +This covers the basics, but there’s a lot more you can do with your design tokens: + +- [Learn about the DTFM format](/guides/tokens) +- [Learn about Modes](/guides/modes) (unique to Cobalt!) +- See additional integrations: + - [CSS](/integrations/css) + - [JavaScript/TypeScript](/integrations/js) + - [JSON/Native](/integrations/json) + - [Sass](/integrations/sass) + - [Tailwind CSS](/integrations/tailwind) +- View advanced guides + - [All Config Options](/advanced/config) + - [Plugin API](/advanced/plugin-api) for making your own plugins easily + - [Integrating with CI](/advanced/ci) diff --git a/docs/guides/modes.md b/docs/guides/modes.md new file mode 100644 index 00000000..42be14d7 --- /dev/null +++ b/docs/guides/modes.md @@ -0,0 +1,352 @@ +--- +title: Modes +--- + +# Modes + +Modes are **alternate forms of a token** that can be triggered to activate in different contexts. They allow your design system to account for different states or contexts that allow some values to change while others remain the same. + +To explain this concept, we’ll explore 2 common usages: **color** and **typography**. + +## Examples + +### Example 1: Color modes + +![GitHub’s theme panel](/images/mode-github.png) + +In this screenshot of GitHub’s dashboard you’ll find 5 color themes: _Light default_, _Light high contrast_, _Dark default_, _Dark high contrast_, and _Dark dimmed_ (shown above). How might that be +represented in tokens? + +::: info + +This is an older screenshot that is missing some of GitHub’s newer color modes, but the core idea hasn’t changed. + +::: + +#### Without Modes + +Consider the `red` and `white` colors in the system. Whereas `red` has a different value for each mode, `white` is an absolute value that doesn’t change. A (wrong) first attempt may look +something like: + +::: code-group + +```json [JSON] +{ + "color": { + "$type": "color", + "red": {"$value": "#fa4549"}, + "red-light": {"$value": "#fa4549"}, + "red-lightHighContrast": {"$value": "#d5232c"}, + "red-dark": {"$value": "#f85149"}, + "red-darkHighContrast": {"$value": "#ff6a69"}, + "red-darkDimmed": {"$value": "#f47067"}, + "white": {"$value": "#ffffff"} + } +} +``` + +```yaml [YAML] +color: + $type: color + red: + $value: '#fa4549' + red-light: + $value: '#fa4549' + red-lightHighContrast: + $value: '#d5232c' + red-dark: + $value: '#f85149' + red-darkHighContrast: + $value: '#ff6a69' + red-darkDimmed: + $value: '#f47067' + white: + $value: '#ffffff' +``` + +::: + +But off the bat we have some problems: + +- Color themes are scattered between our original colors +- Token names now carry implicit context +- There’s not a clear abstraction of color themes +- It’s unclear when `[color]-[mode]` exists, and when it doesn’t +- Strict naming guidelines must be enforced for this to work long-term +- Updating/managing color modes can become a precarious game of find-and-replace +- What if `red-darker` was added in the future—do we now have `red-darker`, `red-darker-dark`, and `red-darker-light`? + +#### With Modes + +**Modes** exist to solve these problems by decoupling token names from context and state. This is how it can be represented with modes (using the `$extensions` property from the token syntax): + +::: code-group + +```json [JSON] {7-13} +{ + "color": { + "$type": "color", + "red": { + "$value": "#fa4549", + "$extensions": { + "mode": { + "light": "#fa4549", + "lightHighContrast": "#d5232c", + "dark": "#f85149", + "darkHighContrast": "#ff6a69", + "darkDimmed": "#f85149" + } + } + } + } +} +``` + +```yaml [YAML] {6-11} +color: + $type: color + red: + $value: '#fa4549' + $extensions: + mode: + light: '#fa4549' + lightHighContrast: '#d5232c' + dark: '#f85149' + darkHighContrast: '#c38000' + darkDimmed: '#f85149' +``` + +::: + +Our tokens are vastly improved by having clear colors, and clear color modes. And color modes can be easily modified without affecting any names. Colors can optionally have mode variations, or not. And best of all, application-specific +context isn’t affecting your token names. + +This simplifies your application code, too, as, you can simply refer to `color.red` and the mode can be inferred based on the global context. +below to see the “how”). + +#### With @cobalt/plugin-css + +If using [@cobalt/plugin-css](/integrations/css), you could generate CSS to handle these modes. That would look something like: + +```js +import pluginCSS from '@cobalt-ui/plugin-css'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [ + pluginCSS({ + modeSelectors: [ + {mode: 'light', selectors: ['@media (prefers-color-scheme: light)', 'body[data-color-mode="light"]']}, + {mode: 'lightHighContrast', selectors: ['@media (prefers-color-scheme: light) and (prefers-contrast: more)', 'body[data-color-mode="lightHighContrast"]']}, + {mode: 'dark', selectors: ['@media (prefers-color-scheme: dark)', 'body[data-color-mode="dark"]']}, + {mode: 'darkHighContrast', selectors: ['@media (prefers-color-scheme: dark) and (prefers-contrast: more)', 'body[data-color-mode="darkHighContrast"]']}, + {mode: 'darkDimmed', selectors: ['body[data-color-mode="darkDimmed"]']}, + ], + }), + ], +}; +``` + +Then in your CSS, the correct color mode would apply automatically in most instances, but you could also set `` to override it. Also note there aren’t browser-global colorblind +preferences, so if you added a colorblind color mode, it would have to be initialized manually (i.e. user preference). + +### Example 2: Text size modes + +![Apple’s Human Interface Guidelines recommended text sizes](/images/mode-apple.png) + +_Apple’s dynamic text sizes use modes to control multiple type scales._ + +#### Without Modes + +Another common example is **text size**. If users need to make the text bigger or smaller, they can adjust to their taste. But trying to have this context exist in the token names can result in pretty long values (note this is just **ONE** text size): + +::: code-group + +```json [JSON] +{ + "typography": { + "size": { + "title1-xSmall": {"$value": "25px"}, + "title1-Small": {"$value": "26px"}, + "title1-Medium": {"$value": "27px"}, + "title1-Large": {"$value": "28px"}, + "title1-xLarge": {"$value": "30px"}, + "title1-xxLarge": {"$value": "32px"}, + "title1-xxxLarge": {"$value": "32px"} + } + } +} +``` + +```yaml [YAML] +typography: + size: + title1-xSmall: + $value: 25px + title1-Small: + $value: 26px + title1-Medium: + $value: 27px + title1-Large: + $value: 28px + title1-xLarge: + $value: 30px + title1-xxLarge: + $value: 32px + title1-xxxLarge: + $value: 32px +``` + +::: + +Naming a font size token as `typography.size.title1-Medium` or `typography.size.title2-Medium` is a bad idea, because then every level of your application must be aware of the user’s current preference settings. +And if values ever change, now your entire application must be updated everywhere. + +### With Modes + +Instead, by declaring font sizes as modes, the value becomes much more portable: `typography.size.title1`. + +::: code-group + +```json [JSON] {9-17} +{ + "typography": { + "size": { + "title1": { + "$name": "Title 1", + "$type": "dimension", + "$value": "28px", + "$extensions": { + "mode": { + "xSmall": "25px", + "Small": "26px", + "Medium": "27px", + "Large": "28px", + "xLarge": "30px", + "xxLarge": "32px", + "xxxLarge": "32px" + } + } + } + } + } +} +``` + +```yaml [YAML] {8-15} +typography: + size: + title1: + $name: Title 1 + $type: dimension + $value: 28px + $extensions: + mode: + xSmall: 25px + Small: 26px + Medium: 27px + Large: 28px + xLarge: 30px + xxLarge: 32px + xxxLarge: 32px +``` + +::: + +Now the user preferences only have to be dealt with in the global context, and the rest of your code will adapt. + +### Additional Examples + +To see how to use modes in specific languages, see the following plugin docs: + +- [@cobalt-ui/plugin-css](/integrations/css#mode-selectors") +- [@cobalt-ui/plugin-js](/integrations/js) +- [@cobalt-ui/plugin-sass](/integrations/sass) + +## Best practices + +A mode is best used for **2 variations that are never used together.** + +Back to the color example, if a user has requested high contrast colors, we’d never want to show them the default (non-high contrast) green; we’d want to preserve their preferences. + +So following that, here are some common scenarios for when modes should—or shouldn’t—be used. + +::: tip ✅ Do + +Do use modes for when a user can’t be in 2 contexts on the same page: + +- User preferences (e.g. text size, reduced motion, colorblind mode) +- Device (e.g. mobile or desktop) +- Region/language +- Product/application area (e.g. marketing site vs dashboard UI) + +::: + +::: danger ❌ Don’t + +Don’t use modes for things that can be used on the same page: + +- Semantic color (e.g _success_ or _error_) +- Localized state (e.g. _disabled_ or _active_) +- Color shades/hues +- Components (e.g. _card_ or _button_) + +::: + +## Advanced + +### Validation + +To enforce all modes exist for a group. You can assert typechecking with `$extensions.requiredModes`: + +::: code-group + +```json [JSON] {3-5} +{ + "color": { + "$extensions": { + "requiredModes": ["light", "lightHighContrast", "dark", "darkHighContrast"] + }, + "red": { + "4": { + "$type": "color", + "$value": "#fa4549", + "$extensions": { + "mode": { + "light": "#fa4549", + "lightHighContrast": "#d5232c", + "dark": "#f85149" + } + } + } + } + } +} +``` + +```yaml [YAML] {2-9} +color: + $extensions: + requiredModes: + - light + - lightHighContrast + - dark + - darkHighContrast + red: + '4': + $type: color + $value: '#fa4549' + $extensions: + mode: + light: '#fa4549' + lightHighContrast: '#d5232c' + dark: '#f85149' +``` + +::: + +In the above example, we’d have an error on `color.red.4` because the `darkHighContrast` mode is missing. This helps ensure you’re not accidentally getting fallback values you intended to set. + +`requiredModes` can be enforced at any level. And it will require all children to have every mode present (regardless of `$type`). diff --git a/docs/guides/tokens.md b/docs/guides/tokens.md new file mode 100644 index 00000000..c5f676d9 --- /dev/null +++ b/docs/guides/tokens.md @@ -0,0 +1,155 @@ +--- +title: tokens.json +--- + +# tokens.json + +Your `tokens.json` (or `tokens.yaml`) file is a complete manifest of all your design tokens. It follows the [Design Token Format Module (DTFM)](https://www.w3.org/community/design-tokens/), an official standard for describing design tokens. + +The format is currently in draft, and being developed by hundreds of design leaders that work at Figma, Adobe, Salesforce, Google, Amazon, Microsoft, Zeplin, Supernova, and more! But despite it still being a draft, it’s still a robust format and is becoming the de-facto standard for design tokens, and many popular design systems [like GitHub Primer](https://primer.style/) either have switched to DTFM or are planning to in the near future. + +## DTFM Format + +The basic design token consists of a simple JSON object with `$type` and `$value` (required), as well as an optional `$description` (highly-recommended to use to describe the token’s purpose, as well as usage instructions). + +::: code-group + +```json [JSON] +{ + "tokenName": { + "$description": "(optional) A description of this token", + "$type": "[token type]", + "$value": "[token value - different shape depending on $type]", + "$extensions": "(optional) Used by third-party tools" + } +} +``` + +```yaml [YAML] +tokenName: + $description: (optional) A description of this token + $type: '[token type]' + $value: '[token value - different shape depending on $type]' + $extensions: (optional) Used by third-party tools +``` + +::: + +Tokens can also be nested infinitely within other groups. A group counts as any arbitrary object wrapping the token (note that `$` prefixes are reserved for tokens, to prevent name conflicts): + +::: code-group + +```json [JSON] +{ + "groupA": { + "groupB": { + "token": { + "$type": "color", + "$value": "#000" + } + } + } +} +``` + +```yaml [YAML] +groupA: + groupB: + token: + $type: color + $value: '#000' +``` + +::: + +### Managing `tokens.json` + +Currently, the only way to manage `tokens.json` is manually. But there are tools to make editing friendlier, such as [JSON Hero](https://jsonhero.io/). + +::: tip + +The Cobalt team is working on a visual GUI for editing/managing tokens, but it’s not ready yet. We’re planning on announcing it in early 2024. + +::: + +### Token Types + +Within your `tokens.json`, you can use all of the following types of tokens: + +- [Color](/tokens/color) +- [Dimension](/tokens/dimension) (can be used for spacing, font size, border width, etc.) +- [Font Family](/tokens/font-family) +- [Font Weight](/tokens/font-weight) +- [Duration](/tokens/duration) +- [Cubic bézier](/tokens/cubic-bezier) +- [Number](/tokens/number) +- [Link](/tokens/link) (for files, extension provided by Cobalt) +- [Stroke Style](/tokens/stroke-style) +- [Border](/tokens/border) +- [Transition](/tokens/transition) +- [Shadow](/tokens/shadow) +- [Gradient](/tokens/gradient) +- [Typography](/tokens/typography) + +And in addition to these, you can also [group tokens](/tokens/groups) in any hierarchy you’d like, as well as create your own [custom tokens](/tokens/custom). + +### Cobalt extensions + +Cobalt **is NOT** its own format; it is an implementation of DTFM as close to the spec as possible. However, just for quality of life, Cobalt supports a superset to DTFM, allowing: + +- [Addition of a Link token for assets](/tokens/link) +- YAML is supported in addition to JSON +- Wider support for more values are expected, such as: + - Color: any CSS-valid color is accepted, including [Oklab/Oklch](https://oklch.com) (the spec requires sRGB Hexadecimal) + - Shadow: arrays of shadows are accepted (the spec only allows one shadow) + - Typography: any CSS text property is accepted (the spec doesn’t allow things like `font-variant`). +- [Modes](/guides/mode) are allowed. + +Any other deviations are considered unintentional. Please [file an issue](https://github.com/drwpow/cobalt-ui/issues) if you run across any! + +## JSON or YAML? + +Though the original [DTFM spec](https://design-tokens.github.io/community-group/format/) (and most examples) use JSON, Cobalt supports YAML equally well since it’s a 1:1 translation. But since YAML is an easier format to read and write, you may prefer it (the Cobalt maintainers do!). Wherever you see mention of `tokens.json`, know that Cobalt supports `tokens.yaml` equally well; the former is just used as the common term for simplicity. + +_Tip: [Boop](https://boop.okat.best/) is a simple, secure tool to convert JSON to YAML in a snap._ + +## Combining multiple `tokens.json` files + +With Cobalt, you can organize your tokens into separate files if you’d like, and they can all be flattened together. Pass in an array to `tokens` in your config: + +::: code-group + +```js [tokens.config.mjs] +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: ['./token-src/colors.json', './token-src/typography.json', './token-src/icons.json', './token-src/spacing.json'], +}; +``` + +::: + +Note that this will flatten all tokens into one manifest, so you’ll have to handle conflicts if multiple tokens are named the same thing (including their groups). + +## Naming / organization + +Organization is completely up to you! The DTFM spec allows infinite nesting, and lets you name tokens anything, with the following exceptions: + +- Token names or group names can’t … + - … contain dots (`.`). These are reserved for shorthand IDs (e.g. `color.base.blue.500`). + - … contain curly braces (`{}`). These are reserved for aliases (e.g. `{color.base.blue.500}`). + - … start with `$`. These are reserved properties (e.g. `$type`). +- Tokens can’t contain sub-tokens (an object is either a token or a group, but never both). + +### Best practices + +Based on looking at dozens of design systems, here are a few tips and common patterns that we’ve seen: + +- **Favor predictability.** For naming colors, `color.red` is better than `color.crimson`. Expand into more “creative” only after you’ve exhausted the predictable names. +- **Use aliasing.** You can create as many aliases as you’d like. For example, you could set `color.brand.primary`, `color.semantic.action`, and `color.semantic.info` all to `{color.base.blue.500}` and only have to manage one color instead of multiple. +- **Group tokens by type.** Most DSs contain obvious top-level groups such as `color.*`, `border.*`, `font.*`, and `space.*`. As you develop more semantic groups, you can always alias back to these tokens. +- **Dedupe types.** Groups let you set the default `$type` that apply to all children. So set `"$type": "color"` on your `color.*` group once to save typing on all child tokens (this also encourages good organization!). +- **Set descriptions.** Tokens and groups all support adding `$description`s. Take the time to describe how this token should (or shouldn’t) be used! + +## Further Reading + +- [The official DTFM specification](https://design-tokens.github.io/community-group/format/) diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..0390909d --- /dev/null +++ b/docs/index.md @@ -0,0 +1,23 @@ +--- +# https://vitepress.dev/reference/default-theme-home-page +layout: home + +hero: + name: Cobalt + text: CI for Design Tokens + tagline: Use Design Tokens Format Module tokens to generate CSS, Sass, JS/TS, universal JSON, and more. + actions: + - theme: brand + text: Get Started + link: /guides/getting-started + - theme: alt + text: View on GitHub + link: https://github.com/drwpow/cobalt-ui +features: + - title: Supports Design Tokens Format Module + details: Use the universal design token standard for the widest compatibility and no vendor lock-in + - title: Pluggable & customizable + details: Written from the ground-up to power your own custom design tooling + - title: Community-powered + details: Open source, MIT-licensed, and open to community contributions +--- diff --git a/docs/src/pages/docs/integrations/css.md b/docs/integrations/css.md similarity index 71% rename from docs/src/pages/docs/integrations/css.md rename to docs/integrations/css.md index d8a6c00b..d50184d3 100644 --- a/docs/src/pages/docs/integrations/css.md +++ b/docs/integrations/css.md @@ -1,40 +1,55 @@ --- -title: CSS Plugin for Cobalt -layout: ../../../layouts/docs.astro +title: CSS --- -# @cobalt-ui/plugin-css +# CSS -Generate CSS from your design tokens using [Cobalt](https://cobalt-ui.pages.dev). +Generate CSS variables from your Design Tokens Format Module (DTFM) tokens. -**Features** - -- ✅ 🌈 Automatic [P3 color](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/color-gamut) enhancement -- ✅ Automatic mode inheritance (e.g. light/dark mode) +This plugin generates CSS variables for dynamic, flexible theming that supports modes and gives you the full range of what CSS can do. ## Setup -```bash +::: tip + +Make sure you have the [Cobalt CLI](/guides/cli) installed! + +::: + +Install the plugin: + +```sh npm i -D @cobalt-ui/plugin-css ``` -```js -// tokens.config.mjs -import pluginCSS from '@cobalt-ui/plugin-css'; +Then add to your `tokens.config.mjs` file: + +::: code-group + +```js [tokens.config.mjs] +import pluginCSS from '@cobalt-ui/plugin-css'; // [!code ++] /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', outDir: './tokens/', - plugins: [pluginCSS()], + plugins: [pluginCSS()], // [!code ++] }; ``` -Generates: +::: -```css -/* tokens/tokens.css */ +And run: +```sh +npx co build +``` + +You’ll then get a `./tokens/tokens.css` file with [CSS variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) for you to use anywhere in your app: + +::: code-group + +```css [./tokens/tokens.css] :root { --color-blue: #0969da; --color-green: #2da44e; @@ -44,20 +59,15 @@ Generates: } ``` -You can then use these anywhere in your app. - -## Usage - -Running `npx co build` with the plugin set up will generate a `tokens/tokens.css` file. Inspect that, and import where desired and use the [CSS Custom Properties](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) as desired ([docs](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties)). +::: -## Options - -### All Options +## Config Here are all plugin options, along with their default values -```js -// tokens.config.mjs +::: code-group + +```js [tokens.config.mjs] import pluginCSS from '@cobalt-ui/plugin-css'; /** @type import('@cobalt-ui/core').Config */ @@ -89,11 +99,49 @@ export default { }; ``` -### Embed Files +::: + +## Color tokens + +::: code-group + +```js [tokens.config.mjs] {5} +/** @type import('@cobalt-ui/core').Config */ +export default { + plugins: [ + pluginCSS({ + colorFormat: 'oklch', + }), + ], +}; +``` + +::: + +By specifying a `colorFormat`, you can transform all your colors to [any browser-supported colorspace](https://www.w3.org/TR/css-color-4/). Any of the following colorspaces are accepted: + +- [hex](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) (default) +- [rgb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb) +- [hsl](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) +- [hwb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb) +- [lab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab) +- [lch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch) +- [oklab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab) +- [oklch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) +- [p3](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color) +- [srgb-linear](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) +- [xyz-d50](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) +- [xyz-d65](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) + +If you are unfamiliar with these colorspaces, the default `hex` value is best for most users (though [you should use OKLCH to define your colors](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)). + +## Link tokens -Say you have `link` tokens in your `tokens.json`: +Say you have [Link tokens](/tokens/link) in your `tokens.json`: -```json +::: code-group + +```json [JSON] { "icon": { "alert": { @@ -104,40 +152,102 @@ Say you have `link` tokens in your `tokens.json`: } ``` +```yaml [YAML] +icon: + alert: + $type: link + value: ./icon/alert.svg +``` + +::: + By default, consuming those will print values as-is: ```css -.icon-alert { - background-image: var(--icon-alert); +:root { + --icon-alert: url('./icon/alert.svg'); } -/* Becomes … */ .icon-alert { - background-image: url('./icon/alert.svg'); + background-image: var(--icon-alert); } ``` In some scenarios this is preferable, but in others, this may result in too many requests and may result in degraded performance. You can set `embedFiles: true` to generate the following instead: ```css -.icon-alert { - background-image: var(--icon-alert); +:root { + --icon-alert: url('image/svg+xml;utf8,'); } -/* Becomes … */ .icon-alert { - background-image: url('image/svg+xml;utf8,'); + background-image: var(--icon-alert); } ``` -[Read more](https://css-tricks.com/data-uris/) +::: tip + +The CSS plugin uses [SVGO](https://github.com/svg/svgo) to optimize SVGs at lossless quality. However, raster images won’t be optimized so quality isn’t degraded. + +::: + +[Read more about the advantages to inlining files](https://css-tricks.com/data-uris/) + +## Generate name + +Use the `generateName()` option to customize the naming of CSS tokens, such as adding prefixes/suffixes, or just changing how the default variable naming works in general. + +### Default naming -### Mode Selectors +By default, Cobalt takes your dot-separated token IDs and… + +- Removes leading and trailing whitespace from each group or token name in an ID +- camelCases any group or token name that has a space in the middle of it +- Joins the normalized segments together with a single dashes + +### Custom naming + +To override specific or all CSS variable names yourself, use the `generateName()` option: + +::: code-group + +```js [tokens.config.mjs] +import pluginCSS from '@cobalt-ui/plugin-css'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [ + pluginCSS({ + generateName(variableId, token) { + if (variableId === 'my.special.token') { + return 'SUPER_IMPORTANT_VARIABLE'; + } + // if nothing returned, fall back to default behavior + }, + }), + ], +}; +``` + +::: + +A couple things to be aware of: + +- `token` can be `undefined` in rare cases + - This occurs when a token references another token that is not defined. Currently, this is not explicitly disallowed by the design tokens specification. +- `variableId` may not be a 1:1 match with the `token.id` + - For example, each property in a composite token will have its own variable generated, so those `variableId`s will include the property name. In most cases you should use `variableId` rather than `token.id`. +- The string returned does not need to be prefixed with `--`, Cobalt will take care of that for you + +## Modes To generate CSS for Modes, add a `modeSelectors` array to your config that specifies the **mode** you’d like to target and which **CSS selectors** should activate those modes (can either be one or multiple). You may optionally also decide to include or exclude certain tokens (e.g. `color.*` will only target the tokens that begin with `color.`). -```js -// tokens.config.mjs +::: code-group + +```js [tokens.config.mjs] import css from '@cobalt-ui/plugin-css'; /** @type import('@cobalt-ui/core').Config */ @@ -167,6 +277,8 @@ export default { }; ``` +::: + This would generate the following CSS: ```css @@ -206,8 +318,9 @@ In our example the `@media` selectors would automatically pick up whether a user Further, any valid CSS selector can be used (that’s why it’s called `modeSelectors` and not `modeClasses`)! You could also generate CSS if your `typography.size` group had `desktop` and `mobile` sizes: -```js -// tokens.config.mjs +::: code-group + +```js [tokens.config.mjs] import css from '@cobalt-ui/plugin-css'; /** @type import('@cobalt-ui/core').Config */ @@ -225,6 +338,8 @@ export default { }; ``` +::: + That will generate the following: ```css @@ -245,38 +360,15 @@ That will generate the following: } ``` -[Learn more about modes](https://cobalt-ui.pages.dev/docs/guides/modes/) - -### Color Format +[Learn more about modes](/guides/modes) -```js -pluginCSS({ - colorFormat: 'oklch', -}), -``` - -By specifying a `colorFormat`, you can transform all your colors to [any browser-supported colorspace](https://www.w3.org/TR/css-color-4/). Any of the following colorspaces are accepted: - -- [hex](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) (default) -- [rgb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb) -- [hsl](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) -- [hwb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb) -- [lab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab) -- [lch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch) -- [oklab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab) -- [oklch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) -- [p3](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color) -- [srgb-linear](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) -- [xyz-d50](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) -- [xyz-d65](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) - -If you are unfamiliar with these colorspaces, the default `hex` value is best for most users (though [you should use OKLCH to define your colors](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)). - -### Transform +## Transform Inside plugin options, you can specify an optional `transform()` function. -```js +::: code-group + +```js [tokens.config.mjs] {7-13} /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', @@ -295,13 +387,17 @@ export default { }; ``` +::: + Your transform will only take place if you return a truthy value, otherwise the default transformer will take place. -#### Custom tokens +### Custom tokens If you have your own custom token type, e.g. `my-custom-type`, you’ll have to handle it within `transform()`: -```js +::: code-group + +```js [tokens.config.mjs] {8-13} /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', @@ -321,73 +417,34 @@ export default { }; ``` -### Generate Name - -Because token IDs are a dot-separated series of JSON keys, they cannot be trusted to be valid CSS variable names. By default, Cobalt generates CSS variable names using the `defaultNameGenerator` function which... - -- Removes leading and trailing whitespace from each group or token name in an ID -- camelCases any group or token name that has a space in the middle of it -- Joins the normalized segments together with a single dashes - -If you wish to customize this behavior, you can specify your own name generator with the plugin's `generateName` option. It accepts a function with the following signature. +::: -```ts -type CustomNameGenerator = (variableId: string, token?: ParsedToken) => string; -``` +## Sass interop -A couple things to be aware of: +If you’re using Sass in your project, you can load this plugin through [@cobalt-ui/plugin-sass](/integrations/sass), which gives you all the benefits of this plugin plus Sass’ typechecking (the Sass plugin’s normal Sass vars will be swapped for CSS vars, but it will still error on any mistyped tokens). -- `token` can be undefined in rare cases - - This occurs when a token references another token that is not defined. Currently, this is not explicitly disallowed by the design tokens specification. -- `variableId` may not be a 1:1 match with the `token.id` - - For example, each property in a composite token will have its own variable generated, so those `variableId`s will include the property name. In most cases you should use `variableId` rather than `token.id`. -- The string returned does not need to be prefixed with `--`, Cobalt will take care of that for you +To use this, replace this plugin with @cobalt-ui/plugin-sass in `tokens.config.mjs` and move your options into the `pluginCSS: {}` option: -If you wish, you can use the `defaultNameGenerator` in your custom name generator. This is handy if you only want to modify the behavior of a special case. +::: code-group -```js -// tokens.config.mjs -import pluginCSS, { defaultNameGenerator } from '@cobalt-ui/plugin-css'; + +```js [tokens.config.mjs] +import pluginCSS from '@cobalt-ui/plugin-css'; // [!code --] +import pluginSass from '@cobalt-ui/plugin-sass'; // [!code ++] /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', outDir: './tokens/', plugins: [ - pluginCSS({ - generateName: (variableId, token) { - if (variableId === 'my.special.token') { - return "SUPER_IMPORTANT_VARIABLE"; - } - - return defaultNameGenerator(variableId); - }, - }), + pluginCSS({filename: 'tokens.css'}), // [!code --] + pluginSass({ // [!code ++] + pluginCSS: {filename: 'tokens.css'}, // [!code ++] + }), // [!code ++] ], }; ``` -### Usage with @cobalt-ui/plugin-sass - -If you’re using Sass in your project, you can load this plugin through [@cobalt-ui/plugin-sass](https://cobalt-ui.pages.dev/docs/integrations/sass/), which lets you use CSS vars while letting Sass typecheck everything and making sure your stylesheet references everything correctly. - -To use this, replace this plugin with @cobalt-ui/plugin-sass in `tokens.config.mjs` and pass all options into `pluginCSS: {}`: - -```diff -- import pluginCSS from '@cobalt-ui/plugin-css'; -+ import pluginSass from '@cobalt-ui/plugin-sass'; - - /** @type import('@cobalt-ui/core').Config */ - export default { - tokens: './tokens.json', - outDir: './tokens/', - plugins: [ -- pluginCSS({ filename: 'tokens.css }), -+ pluginSass({ -+ pluginCSS: { filename: 'tokens.css' }, -+ }), - ], - }; -``` +::: -This changes `token('color.blue')` to return CSS vars rather than the original values. To learn more, [read the docs](https://cobalt-ui.pages.dev/docs/integrations/sass/). +To learn more, [read the docs](/integrations/sass). diff --git a/docs/integrations/figma.md b/docs/integrations/figma.md new file mode 100644 index 00000000..76e10abf --- /dev/null +++ b/docs/integrations/figma.md @@ -0,0 +1,54 @@ +--- +title: Figma +--- + +# Figma + +Because Figma doesn’t have a way to export the [Design Tokens Format Module (DTFM)](https://designtokens.org) directly, you’ll need a plugin to export your styles to the DTFM format. + +The plugin we recommend for now is [Tokens Studio for Figma](https://tokens.studio). Though it doesn’t support DTFM directly either, it does allow you to export your design tokens in a format Cobalt can read. + +::: info + +This only allows syncing _from_ Figma. Syncing _to_ Figma isn’t possible today, but the Cobalt team is actively building something to make this possible. Stay tuned! 📺 + +::: + +## Exporting from Tokens Studio + +Once your design tokens are in Tokens Studio ([docs](https://docs.tokens.studio/tokens/creating-tokens)), use [any of the approved sync methods](https://docs.tokens.studio/sync/sync) to export a `tokens.json` file. Then use Cobalt as you would normally: + +```js +import pluginCSS from '@cobalt-ui/plugin-css'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [pluginCSS()], +}; +``` + +Once your sync method is set up, it should be a snap to re-export that `tokens.json` file every time something updates. + +## Support + +| Tokens Studio Type | Supported | Notes | +| :-------------------------------------------------------------------------------- | :-------: | :----------------------------------------------------------------------------------------------------------------------------------------------------- | +| [Sizing](https://docs.tokens.studio/available-tokens/sizing-tokens) | ✅ | Converted to [Dimension](/tokens/dimension). | +| [Spacing](https://docs.tokens.studio/available-tokens/spacing-tokens) | ✅ | Converted to [Dimension](/tokens/dimension). | +| [Color](https://docs.tokens.studio/available-tokens/color-tokens) | ✅ | Flat colors are kept as [Color](/tokens/color) while gradients are converted to [Gradient](/tokens/gradient). Modifiers aren’t supported. | +| [Border radius](https://docs.tokens.studio/available-tokens/border-radius-tokens) | ✅ | Converted to [Dimension](/tokens/dimension). Multiple values are expanded into 4 tokens (`*TopLeft`, `*TopRight`, `*BottomLeft`, `*BottomRight`). | +| [Border width](https://docs.tokens.studio/available-tokens/border-width-tokens) | ✅ | Converted to [Dimension](/tokens/dimension). | +| [Shadow](https://docs.tokens.studio/available-tokens/shadow-tokens) | ✅ | Basically equivalent to [Shadow](/tokens/shadow). | +| [Opacity](https://docs.tokens.studio/available-tokens/opacity-tokens) | ✅ | Converted to [Number](/tokens/number) | +| [Typography](https://docs.tokens.studio/available-tokens/typography-tokens) | ✅ | Basically equivalent to [Typography](/tokens/typography). **Text decoration** and **Text Case** must be flattened as there is no DTFM spec equivalent. | +| [Asset](https://docs.tokens.studio/available-tokens/asset-tokens) | ❌ | TODO. Cobalt supports [Link](/tokens/link), which should be an equivalent. | +| [Composition](https://docs.tokens.studio/available-tokens/composition-tokens) | ❌ | Unsupported because this is a paid feature. | +| [Dimension](https://docs.tokens.studio/available-tokens/dimension-tokens) | ✅ | Direct equivalent to [Dimension](/tokens/dimension). | +| [Border](https://docs.tokens.studio/available-tokens/border-tokens) | ✅ | Direct equivalent to [Border](/tokens/border). | + +#### Notes + +- **Duration** and **Cubic Bézier** types aren’t supported by Tokens Studio (because Figma currently doesn’t support animations). So to use those types you’ll need to convert your tokens into DTFM. +- Though Cobalt preserves your [Token Sets](https://docs.tokens.studio/themes/token-sets), which means most aliases will work, Token Studio’s [Advanced Themes](https://docs.tokens.studio/themes/themes-pro) is a paid feature and is therefore not supported. Though you could manually upconvert Token Studio themes to [modes](/tokens/modes). diff --git a/docs/src/pages/docs/integrations/js.md b/docs/integrations/js.md similarity index 56% rename from docs/src/pages/docs/integrations/js.md rename to docs/integrations/js.md index bcedb33e..3ee14449 100644 --- a/docs/src/pages/docs/integrations/js.md +++ b/docs/integrations/js.md @@ -1,52 +1,57 @@ --- -title: JS/TS/JSON Plugin for Cobalt -layout: ../../../layouts/docs.astro +title: JS / TS --- -# @cobalt-ui/plugin-js +# JavaScript + TypeScript -Generate JSON and JS (with TypeScript types) from your design tokens using [Cobalt](https://cobalt-ui.pages.dev). +Generate JavaScript (with TypeScript declarations) from your Design Tokens Format Module (DTFM) tokens. -**Features** +## Setup -- ✅ Access all your design tokens safely and programatically in any frontend or backend setup -- ✅ Full support for token modes (e.g. light/dark mode) -- ✅ Automatic TypeScript types for strong typechecking (never have a broken style) +::: tip -## Setup +Make sure you have the [Cobalt CLI](/guides/cli) installed! + +::: + +Install the plugin from npm: ```bash npm i -D @cobalt-ui/plugin-js ``` -```js -// tokens.config.mjs -import pluginJS from '@cobalt-ui/plugin-js'; +Then add to your `tokens.config.mjs` file: + +::: code-group + + +```js [tokens.config.mjs] +import pluginJS from '@cobalt-ui/plugin-js'; // [!code ++] /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', outDir: './tokens/', plugins: [ - pluginJS({ - /** output JS (with TS types)? boolean or filename (default: true) */ - js: true, - /** output JSON? boolean or filename (default: false) */ - json: false, - }), + pluginJS({ // [!code ++] + /** output JS (with TS types)? boolean or filename (default: true) */ // [!code ++] + js: true, // [!code ++] + }), // [!code ++] ], }; ``` -_Note: the default plugin exports a `.d.ts` file alongside the `.js`, which means the same file can either be used in JS or TS._ +::: -## Usage +And run: -### JS +```sh +npx co build +``` -To use a token, import the `token()` function and reference it by its full ID: +You’ll then get generated JS with a `token()` function you can use to grab token values: -```ts +```js import {token} from './tokens/index.js'; // get default token @@ -60,9 +65,15 @@ import BezierEasing from 'bezier-easing'; const easing = BezierEasing(...token('ease.cubic-in-out')); ``` -You’ll also be able to see any `$description`s specified in your IDE in the form of JSDoc. If using TypeScript, `token()` is statically typed as it‘s only a thin wrapper around the `tokens` and `modes` exports. +::: info + +The default plugin exports a plain `.js` with invisible `.d.ts` TypeScript declarations alongside it, which means you don’t have to configure anything whether using TypeScript or not. + +::: -In addition, you’ll also find the following named exports: +## API + +In addition to `token()`, you’ll also find the following named exports: | Name | Type | Description | | :------- | :------- | :-------------------------------------------------------------------------------------------------- | @@ -70,24 +81,13 @@ In addition, you’ll also find the following named exports: | `meta` | `object` | Object of token ID → metadata (`$type`, `$description`, etc.) | | `modes` | `object` | Object of token ID → mode → values (note: tokens without any modes will be missing from the object) | -### JSON - -This plugin’s JSON output has the same shape as the JS output. This format is preferable if you’re preparing tokens for an API, or for native apps (iOS or Android). - -Note that even if your tokens started off as a `tokens.json` file, this output JSON differs in the following ways: - -- It flattens deeply-nested structures into a single depth (e.g. `color.core.blue.500` becomes `tokens["color.core.blue.500"]`) -- It resolves all aliases (so you don’t have to) -- It normalizes token values (especially helpful when the original spec is loose in what’s accepted) - -## Options - -### All Options +## Config Here are all plugin options, along with their default values: -```js -// tokens.config.mjs +::: code-group + +```js [tokens.config.mjs] import pluginJS from '@cobalt-ui/plugin-js'; /** @type import('@cobalt-ui/core').Config */ @@ -107,7 +107,9 @@ export default { }; ``` -### Transform +::: + +## Transform Inside plugin options, you can specify an optional `transform()` function. diff --git a/docs/integrations/json.md b/docs/integrations/json.md new file mode 100644 index 00000000..3c3fc02b --- /dev/null +++ b/docs/integrations/json.md @@ -0,0 +1,72 @@ +--- +title: JSON +--- + +# JSON + Native Apps + +Generate universal JSON from your Design Tokens Format Module (DTFM) tokens. This is usable by any platform, any language (provided you do a small amount of JSON parsing). + +## Setup + +::: tip + +Make sure you have the [Cobalt CLI](/guides/cli) installed! + +::: + +This uses the [JS plugin](/integrations/js), which we’ll install from npm: + +```bash +npm i -D @cobalt-ui/plugin-js +``` + +Then add to your `tokens.config.mjs` file: + +::: code-group + + +```js [tokens.config.mjs] +import pluginJS from '@cobalt-ui/plugin-js'; // [!code ++] + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [ + pluginJS({ // [!code ++] + /** output JSON? boolean or filename (default: false) */ // [!code ++] + json: true, // [!code ++] + }), // [!code ++] + ], +}; +``` + +::: + +And run: + +```sh +npx co build +``` + +You’ll get a generated `./tokens/tokens.json` file with the following structure: + +| Name | Type | Description | +| :------- | :------- | :-------------------------------------------------------------------------------------------------- | +| `tokens` | `object` | Object of token ID → value (all aliases resolved & all transformations applied) | +| `meta` | `object` | Object of token ID → metadata (`$type`, `$description`, etc.) | +| `modes` | `object` | Object of token ID → mode → values (note: tokens without any modes will be missing from the object) | + +## Usage + +Usage will vary depending on the platform and language, but here are a few examples: + +- [Simplifying iOS Apps Design with Design Tokens](https://blogs.halodoc.io/simplifying-ios-app-design-with-design-tokens/) (this blog post uses Style Dictonary JSON, but the same ideas apply to DTFM JSON) + +## Config + +The config options [are the same as the JS plugin](/integrations/js#config). + +## Transform + +Likewise, the transform API is [also the same as the JS plugin](/integrations/js#transform). diff --git a/docs/integrations/other.md b/docs/integrations/other.md new file mode 100644 index 00000000..b90c5ca5 --- /dev/null +++ b/docs/integrations/other.md @@ -0,0 +1,24 @@ +--- +title: Other +--- + +# Other Integrations + +The following integrations are planned, but aren’t ready yet: + +- Elixir +- PHP +- Python +- Ruby + +## Consuming JSON + +If your integration isn’t supported (or isn’t planned), you can use the [JSON plugin](/integrations/json) to pull the tokens into your project. JSON is a universal language, and can be easily parsed in just about every language. + +Even though [`tokens.json` manifests](/guides/tokens) are written in JSON, Cobalt’s JSON plugin performs extra layers of work to make tokens even easier to consume: + +- Syntax errors are caught +- All aliases are resolved +- Token values are normalized wherever possible (e.g. colors converted to hex) + +[Read JSON plugin docs](/integrations/json) diff --git a/docs/src/pages/docs/integrations/sass.md b/docs/integrations/sass.md similarity index 58% rename from docs/src/pages/docs/integrations/sass.md rename to docs/integrations/sass.md index 1fad8be5..2cffdda9 100644 --- a/docs/src/pages/docs/integrations/sass.md +++ b/docs/integrations/sass.md @@ -1,56 +1,70 @@ --- -title: Sass Plugin for Cobalt -layout: ../../../layouts/docs.astro +title: Sass --- -# @cobalt-ui/plugin-sass +# Sass -Generate `.scss` and `.sass` output from your design tokens using [Cobalt](https://cobalt-ui.pages.dev). +Generate `.scss` and `.sass` from your Design Tokens Format Module (DTFM) tokens. -**Features** +## Setup -- ✅ Supports all features of the [CSS plugin](https://cobalt-ui.pages.dev/integrations/css) -- ✅ Strong typechecking with Sass to never have broken styles +::: tip -## Setup +Make sure you have the [Cobalt CLI](/guides/cli) installed! + +::: + +Install the plugin (and its dependency) from npm: -```bash +```sh npm i -D @cobalt-ui/plugin-sass @cobalt-ui/plugin-css ``` -```js -// tokens.config.mjs -import pluginSass from '@cobalt-ui/plugin-sass'; +Then add to your `tokens.config.mjs` file: + +::: code-group + +```js [tokens.config.mjs] +import pluginSass from '@cobalt-ui/plugin-sass'; // [!code ++] /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', outDir: './tokens/', - plugins: [ - pluginSass({ - /** set the filename inside outDir */ - filename: './index.scss', - /** output CSS vars generated by @cobalt-ui/plugin-css? */ - pluginCSS: undefined, - /** use indented syntax? (.sass format) */ - indentedSyntax: false, - /** embed file tokens? */ - embedFiles: false, - /** handle specific token types */ - transform(token, mode) { - // Replace "sans-serif" with "Brand Sans" for font tokens - if (token.$type === 'fontFamily') { - return token.$value.replace('sans-serif', 'Brand Sans'); - } - }, - }), - ], + plugins: [pluginSass()], // [!code ++] }; ``` +::: + +And run: + +```sh +npx co build +``` + +You’ll then generate a `./tokens/index.scss` file that exports a `token()` function you can use to grab tokens: + +```scss +@use '../tokens' as *; // update '../tokens' to match your location of tokens/index.scss + +.heading { + color: token('color.blue'); + font-size: token('typography.size.xxl'); +} +``` + ## Usage -Use the provided `token()` function to get a token by its ID (separated by dots): +The generated Sass outputs the following helpers: + +- [`token()`](#token) +- [`typography()`](#typography) +- [`listModes()`](#list-modes) + +### `token()` + +The main way you’ll use the token is by importing the `token()` function to grab a token by its ID (separated by dots): ```scss @use '../tokens' as *; // update '../tokens' to match your location of tokens/index.scss @@ -71,47 +85,53 @@ Note that a function has a few advantages over plain Sass variables: - ✅ You can programmatically pull values (which is more difficult to do with Sass vars) - ✅ Use the same function to access [modes](#modes) -### CSS Variable Mode (recommended) +### typography() -By default, the Sass plugin only loads your raw token values to Sass. This is a good basic usage, but leaves all the automatic mode inheritance and advanced features of the CSS plugin on the table. To get all the features of the CSS plugin, you can load it through the Sass plugin. In other words: +[Sass mixin](https://sass-lang.com/documentation/at-rules/mixin/) to inject all styles from a [typography](https://cobalt-ui.pages.dev/docs/tokens/#typography) token. Optionally provide the **mode** as the 2nd param. -```sass -color: token('color.blue'); +```scss +@include typography($tokenID, [$mode]); ``` -Becomes: +```scss +@use '../tokens' as *; + +h2 { + @include typography('typography.heading-2'); -```diff -- color: #506fff; -+ color: var(--color-blue); + font-size: token('typography.size.xxl'); // overrides can still be applied after the mixin! +} ``` -_“Why would I want to do this?”_ you may ask. _“Why not just type CSS variables directly?”_ +Note that you can override any individual property so long as it comes _after_ the mixin. -The answer is that **CSS variables have no typechecking.** If, say, your tokens were renamed, or you made a typo, you would never know! You would just have broken styles. However, **using the Sass plugin in CSS variable mode** gives you all the advantages of CSS variables but with the typechecking of Sass so that you’ll never have a single broken style. +### listModes() -**Pros** +The `listModes()` function lists all modes a token has defined. This returns a [Sass list](https://sass-lang.com/documentation/values/lists/). This can be used to generate styles for specific modes: + +```scss +@use '../tokens' as *; -- Get automatic mode inheritance from CSS variables (such as light/dark mode) -- Get dynamic style inheritance from your app -- Get P3 Color enhancement (provided by `@cobalt-ui/plugin-css`) +@for $mode in listModes('color.blue') { + [data-color-mode='#{$mode}'] { + color: token('color.blue', $mode); + } +} +``` -**Cons** +## CSS Variable Mode -- None, really! You may have to just change how you write CSS. +By default, this plugin converts tokens to pure Sass variables. But if you’d like to take advantage of dynamic CSS variables (which support dynamic [modes](/integrations/css#modes)), you can use in conjunction with the [CSS plugin](/integrations/css). This gives you all the flexibility and benefits of modern CSS while still keeping the typechecking properties of Sass. -#### Setup +To use CSS variables instead of Sass variables, set `cssVars: true` and set the `pluginCSS` option: -In your `tokens.config.mjs` file, opt in by adding a `pluginCSS` option. That will automatically load @cobalt-ui/plugin-css and will pass all options to it (all options are supported): +::: code-group -```js -// tokens.config.mjs +```js [tokens.config.mjs] {7-14} import pluginSass from '@cobalt-ui/plugin-sass'; /** @type import('@cobalt-ui/core').Config */ export default { - tokens: './tokens.json', - outDir: './tokens/', plugins: [ pluginSass({ cssVars: true, @@ -123,54 +143,130 @@ export default { ], }, }), - ], -}; ``` -
+::: -⚠️ Don’t load another instance of @cobalt-ui/plugin-css, otherwise they may conflict! +From here you can set [any option the CSS plugin allows](/integrations/css). -
+::: tip -Lastly, you’ll need to make sure the new `tokens.css` file is loaded in your app somehow (otherwise the variables won’t be defined): +Don’t forget to import the `./tokens/tokens.css` file into your app as well so those variables are defined! -```diff - // src/app.ts -+ import '../tokens/tokens.css'; -``` +::: + +::: warning + +Don’t load another instance of @cobalt-ui/plugin-css, otherwise they may conflict! -#### Usage +::: -Here’s one example of how you may need to adjust your code with CSS vars. For example, **opacity** can be achieved with the [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) function: +### Tips -```diff -- color: rgba(token('color.ui.foreground'), 0.75); // ❌ rgba(var(--color-ui-foreground), 0.75) -+ color: color-mix(in oklab, #{token('color.ui.foreground')}, 25% transparent); // ✅ var(--color-ui-foreground) at 75% opacity +Though CSS variable mode is recommended, there may be some caveats to be aware of. One example is that you’ll lose the ability for Sass to change opacity, however, you can achieve the same results with the [color-mix()](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color-mix) function: + +```scss +.text { + color: rgba(token('color.ui.foreground'), 0.75); // [!code --] + color: color-mix(in oklab, #{token('color.ui.foreground')}, 25% transparent); // [!code ++] +} ``` -Or perhaps you want to do some calculations off your tokens. CSS’ `calc()` can do that the same: +You’ll also lose Sass’ ability to perform math on the values, however, CSS’ built-in `calc()` can do the same: -```diff -- margin-left: -0.5 * token('space.sm'); // ❌ Error: Undefined operation "-0.5 * var(--space-sm)" -+ margin-left: calc(-0.5 * #{token('space.ms')}); // ✅ calc(-0.5 * var(--color-ui-foreground)); +```scss +.nav { + margin-left: -0.5 * token('space.sm'); // [!code --] + margin-left: calc(-0.5 * #{token('space.ms')}); // [!code ++] +} ``` In either case, letting the browser do the work is better, especially considering CSS variables are dynamic and can be modified on-the-fly. -
+::: tip -✨ **Tip**: Always use `in oklab` as the default colorspace for `color-mix()`. It usually outperforms other blending methods ([comparison](https://better-color-tools.pages.dev/mix)). +Always use `in oklab` as the default colorspace for `color-mix()`. It usually outperforms other blending methods ([comparison](https://better-color-tools.pages.dev/mix)). -
+::: ## Config -### Embed Files +Here are all plugin options, along with their default values: + +:::code-group -Say you have `link` tokens in your `tokens.json`: +```js [tokens.config.mjs] +import pluginSass from '@cobalt-ui/plugin-sass'; -```json +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [ + pluginSass({ + /** set the filename inside outDir */ + filename: './index.scss', + /** output CSS vars generated by @cobalt-ui/plugin-css? */ + pluginCSS: undefined, + /** use indented syntax? (.sass format) */ + indentedSyntax: false, + /** embed file tokens? */ + embedFiles: false, + /** handle specific token types */ + transform(token, mode) { + // Replace "sans-serif" with "Brand Sans" for font tokens + if (token.$type === 'fontFamily') { + return token.$value.replace('sans-serif', 'Brand Sans'); + } + }, + }), + ], +}; +``` + +::: + +## Color tokens + +::: code-group + +```js [tokens.config.mjs] {5} +/** @type import('@cobalt-ui/core').Config */ +export default { + plugins: [ + pluginSass({ + colorFormat: 'oklch', + }), + ], +}; +``` + +::: + +By specifying a `colorFormat`, you can transform all your colors to [any browser-supported colorspace](https://www.w3.org/TR/css-color-4/). Any of the following colorspaces are accepted: + +- [hex](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) (default) +- [rgb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb) +- [hsl](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) +- [hwb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb) +- [lab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab) +- [lch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch) +- [oklab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab) +- [oklch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) +- [p3](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color) +- [srgb-linear](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) +- [xyz-d50](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) +- [xyz-d65](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) + +If you are unfamiliar with these colorspaces, the default `hex` value is best for most users (though [you should use OKLCH to define your colors](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)). + +## Link tokens + +Say you have [link tokens](/tokens/link) in your `tokens.json`: + +::: code-group + +```json [JSON] { "icon": { "alert": { @@ -181,16 +277,20 @@ Say you have `link` tokens in your `tokens.json`: } ``` +```yaml [YAML] +icon: + alert: + $type: link + value: ./icon/alert.svg +``` + +::: + By default, consuming those will print values as-is: ```scss .icon-alert { - background-image: token('icon.alert'); -} - -// Becomes … -.icon-alert { - background-image: url('./icon/alert.svg'); + background-image: token('icon.alert'); // url('./icon/alert.svg') } ``` @@ -198,47 +298,25 @@ In some scenarios this is preferable, but in others, this may result in too many ```scss .icon-alert { - background-image: token('icon.alert'); -} - -// Becomes … -.icon-alert { - background-image: url('image/svg+xml;utf8,'); + background-image: token('icon.alert'); // url('image/svg+xml;utf8,'); } ``` -[Read more](https://css-tricks.com/data-uris/) +::: tip -### Color Format +The Sass plugin uses [SVGO](https://github.com/svg/svgo) to optimize SVGs at lossless quality. However, raster images won’t be optimized so quality isn’t degraded. -```js -pluginSass({ - colorFormat: 'oklch', -}), -``` +::: -By specifying a `colorFormat`, you can transform all your colors to [any browser-supported colorspace](https://www.w3.org/TR/css-color-4/). Any of the following colorspaces are accepted: +[Read more about the advantages to inlining files](https://css-tricks.com/data-uris/) -- [hex](https://developer.mozilla.org/en-US/docs/Web/CSS/hex-color) (default) -- [rgb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/rgb) -- [hsl](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hsl) -- [hwb](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/hwb) -- [lab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lab) -- [lch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/lch) -- [oklab](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklab) -- [oklch](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) -- [p3](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/color) -- [srgb-linear](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) -- [xyz-d50](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) -- [xyz-d65](https://developer.mozilla.org/en-US/docs/Web/CSS/color-interpolation-method) - -If you are unfamiliar with these colorspaces, the default `hex` value is best for most users (though [you should use OKLCH to define your colors](https://evilmartians.com/chronicles/oklch-in-css-why-quit-rgb-hsl)). - -### Transform +## Transform Inside plugin options, you can specify an optional `transform()` function: -```js +::: code-group + +```js [tokens.config.mjs] {7-13} /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', @@ -257,13 +335,17 @@ export default { }; ``` +::: + Your transform will only take place if you return a truthy value, otherwise the default transformer will take place. -#### Custom tokens +### Custom tokens If you have your own custom token type, e.g. `my-custom-type`, you’ll have to handle it within `transform()`: -```js +::: code-group + +```js [tokens.config.mjs] {8-13} /** @type import('@cobalt-ui/core').Config */ export default { tokens: './tokens.json', @@ -282,49 +364,3 @@ export default { ], }; ``` - -## API - -All available methods in the Sass plugin. - -### listModes() - -List all modes a token has defined. This returns a [Sass list](https://sass-lang.com/documentation/values/lists/). This can be used to generate styles for specific modes: - -```scss -@use '../tokens' as *; - -@for $mode in listModes('color.blue') { - [data-color-mode='#{$mode}'] { - color: token('color.blue', $mode); - } -} -``` - -### token() - -Retrieve a token by its ID. Optionally provide the **mode** as the 2nd param. - -```scss -token($tokenID, [$mode]); -``` - -### typography() - -[Sass mixin](https://sass-lang.com/documentation/at-rules/mixin/) to inject all styles from a [typography](https://cobalt-ui.pages.dev/docs/tokens/#typography) token. Optionally provide the **mode** as the 2nd param. - -```scss -@include typography($tokenID, [$mode]); -``` - -```scss -@use '../tokens' as *; - -h2 { - @include typography('typography.heading-2'); - - font-size: token('typography.size.xxl'); // overrides can still be applied after the mixin! -} -``` - -Note that you can override any individual property so long as it comes _after_ the mixin. diff --git a/docs/integrations/style-dictionary.md b/docs/integrations/style-dictionary.md new file mode 100644 index 00000000..7e8c83de --- /dev/null +++ b/docs/integrations/style-dictionary.md @@ -0,0 +1,31 @@ +--- +title: Style Dictionary +--- + +# Style Dictionary + +You can migrate your [Style Dictionary](https://amzn.github.io/style-dictionary) tokens to the Design Tokens Format Module (DTFM) standard by running the following command (granted you have [the CLI installed](/docs/reference/cli)): + +```bash +npx co convert style-dictionary-tokens.json --out tokens.json +``` + +After running `npx co convert` it’s not recommended to keep using the Style Dictionary format. + +::: warning +This is **NOT** a perfect conversion. This is only meant to do most of the work of migrating to DTFM, but you’ll still have to do some clean up and migrate the parts that weren’t able to be converted. +::: + +## Why convert to DTFM? + +::: tip + +Only you can decide what’s best, and don’t fix your design tooling if it isn’t broken! Only switch to DTFM if it makes sense for your project. + +::: + +There are reasons to switch from Style Dictionary to DTFM. While Style Dictionary is a powerful and flexible tool, it also requires more configuration and maintenance than DTFM does. For example, Style Dictionary requires you place all your colors underneath a top-level `color` group. If you want to reference colors elsewhere, you’ll have to configure all your transformers to look for them. The same applies for `size` and `time` tokens. + +Further, Style Dictionary is missing more advanced features like `gradient`, `typography`, and `shadow` tokens from the DTFM spec, to name a few (and adding them results in nonstandard usage that would be improved by opting for a standard that supports them out-of-the-box). + +While Style Dictionary was a significant trailblazer in managing design tokens and was the first mature library to accomplish this elegantly, the new DTFM spec is being designed to replace the Style Dictionary format by improving on its flaws. In fact, DTFM is more influenced by Style Dictionary than any other format, so rest assured that while migrating can be hard work, the goal of DTFM is to support all of Style Dictionary’s functionality and then some. diff --git a/docs/integrations/tailwind.md b/docs/integrations/tailwind.md new file mode 100644 index 00000000..8f989d67 --- /dev/null +++ b/docs/integrations/tailwind.md @@ -0,0 +1,159 @@ +--- +title: Tailwind CSS +--- + +# Tailwind CSS + +Generate a [Tailwind CSS](https://tailwindcss.com/) preset from your design tokens. + +## Setup + +::: tip +Make sure you have the [Cobalt CLI](/guides/cli) installed! +::: + +Install the plugin from npm + +```sh +npm i -D @cobalt-ui/plugin-tailwind +``` + +Then add to your `tokens.config.mjs` file, configuring [theme](https://tailwindcss.com/docs/configuration#theme) as you would normally, except replacing the values with token IDs: + +::: code-group + +```js [tokens.config.mjs] +import pluginTailwind from '@cobalt-ui/plugin-tailwind'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + plugins: [ + pluginTailwind({ + /** (optional) the path to the Tailwind preset */ + output?: './tailwind-tokens.js', + /** (optional) module format to use (to match your Tailwind config) */ + outputFormat?: 'esm' | 'cjs', + tailwind: { + theme: { + /** @see https://tailwindcss.com/docs/configuration#theme */ + colors: { + blue: { + 100: 'color.blue.100', + 200: 'color.blue.200', + // … + }, + }, + fontFamily: { + sans: 'typography.family.base', + // … + }, + extend: { + spacing: { + 1: 'token.size.s.space', + 2: 'token.size.m.space', + // … + }, + borderRadius: { + m: 'token.size.m.borderRadius', + // … + }, + }, + }, + }, + }), + ], +}; +``` + +::: + +Then run: + +```sh +npx co build +``` + +And you’ll generate a `./tokens/tailwind-tokens.js` file. Add it to your Tailwind config [`presets`](https://tailwindcss.com/docs/configuration#presets) and your Tailwind theme now pulls directly from your design tokens: + +::: code-group + + +```js [tailwind.config.js] +import tailwindTokens from './tokens/tailwind-tokens.js'; // [!code ++] + +/** @type {import('tailwindcss').Config} */ +export default { + presets: [ // [!code ++] + tailwindTokens, // [!code ++] + ], // [!code ++] +}; +``` + +::: + +::: tip +Be sure to rerun `co build` to rebuild your Tailwind preset, or run `co build --watch` to rebuild your tokens every time they change! + +::: + +## Automated mapping + +Because the Tailwind config is “just JS,” you can automate the mapping by using JS: + +::: code-group + +```js [tokens.config.mjs] +import pluginTailwind from '@cobalt-ui/plugin-tailwind'; + +function makeColor(colorName) { + const output = {}; + for (const step of [50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 950]) { + output[step] = [colorName, step].join('.'); // e.g. `color.blue.50` + } + return output; +} + +/** @type import('@cobalt-ui/core').Config */ +export default { + tokens: './tokens.json', + outDir: './tokens/', + plugins: [ + pluginTailwind({ + tailwind: { + theme: { + colors: { + blue: makeColor('color.blue'), // { 50: 'color.blue.50', 100: 'color.blue.100', … } + green: makeColor('color.green'), // { 50: 'color.green.50', 100: 'color.green.100', … } + // … + }, + }, + }, + }), + ], +}; +``` + +::: + +Use this to avoid having to repeat yourself when mapping between dozens (if not hundreds) of your tokens. + +## CommonJS + +If you’re still using CommonJS (using `require("…")` rather than `import "…"`), make sure to change the `outputFormat` setting to `cjs`: + +::: code-group + +```js [tokens.config.mjs] {7} +import pluginTailwind from '@cobalt-ui/plugin-tailwind'; + +/** @type import('@cobalt-ui/core').Config */ +export default { + plugins: [ + pluginTailwind({ + outputFormat: 'cjs', + }), + ], +}; +``` + +::: diff --git a/docs/package.json b/docs/package.json index 150bd240..290765cd 100644 --- a/docs/package.json +++ b/docs/package.json @@ -1,31 +1,18 @@ { - "name": "cobalt-docs", - "version": "0.0.0", + "name": "@cobalt-ui/docs", "private": true, + "version": "0.0.0", "type": "module", "scripts": { - "build": "npm run build:tokens && npm run build:readme && npm run build:static", - "build:tokens": "co build", - "build:readme": "node ./scripts/update-readmes.js", - "build:static": "astro build", - "sync": "co sync", - "dev": "run-p dev:*", - "dev:tokens": "co build -w", - "dev:serve": "astro dev" + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview" }, "dependencies": { - "js-yaml": "^4.1.0", - "nanoid": "^4.0.2" + "vue": "^3.3.7" }, "devDependencies": { - "@cobalt-ui/cli": "workspace:*", - "@cobalt-ui/core": "workspace:*", - "@cobalt-ui/plugin-css": "workspace:*", - "@cobalt-ui/plugin-sass": "workspace:*", - "astro": "^3.3.2", - "npm-run-all": "^4.1.5", - "sass": "^1.69.4", - "shiki": "^0.14.5", - "vite": "^4.5.0" + "vite": "^4.5.0", + "vitepress": "1.0.0-rc.24" } } diff --git a/docs/public/arrow-r.svg b/docs/public/arrow-r.svg deleted file mode 100644 index 7b8a0971..00000000 --- a/docs/public/arrow-r.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/public/github.svg b/docs/public/github.svg deleted file mode 100644 index 52c363b1..00000000 --- a/docs/public/github.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/public/icons/cloud--download.svg b/docs/public/icons/cloud--download.svg deleted file mode 100644 index f7700926..00000000 --- a/docs/public/icons/cloud--download.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/cloud--upload.svg b/docs/public/icons/cloud--upload.svg deleted file mode 100644 index 5b08fbe2..00000000 --- a/docs/public/icons/cloud--upload.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/crop.svg b/docs/public/icons/crop.svg deleted file mode 100644 index 12dab3fd..00000000 --- a/docs/public/icons/crop.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/delete.svg b/docs/public/icons/delete.svg deleted file mode 100644 index a8d1441d..00000000 --- a/docs/public/icons/delete.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/do-not--02.svg b/docs/public/icons/do-not--02.svg deleted file mode 100644 index 7439dc00..00000000 --- a/docs/public/icons/do-not--02.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/do-not.svg b/docs/public/icons/do-not.svg deleted file mode 100644 index a60a39ba..00000000 --- a/docs/public/icons/do-not.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/download--01.svg b/docs/public/icons/download--01.svg deleted file mode 100644 index 727798df..00000000 --- a/docs/public/icons/download--01.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/download--02.svg b/docs/public/icons/download--02.svg deleted file mode 100644 index c09632d9..00000000 --- a/docs/public/icons/download--02.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/embed.svg b/docs/public/icons/embed.svg deleted file mode 100644 index b3688515..00000000 --- a/docs/public/icons/embed.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/export--01.svg b/docs/public/icons/export--01.svg deleted file mode 100644 index 77b72944..00000000 --- a/docs/public/icons/export--01.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/export--02.svg b/docs/public/icons/export--02.svg deleted file mode 100644 index 6e94f768..00000000 --- a/docs/public/icons/export--02.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/launch.svg b/docs/public/icons/launch.svg deleted file mode 100644 index eb3490c3..00000000 --- a/docs/public/icons/launch.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/love.svg b/docs/public/icons/love.svg deleted file mode 100644 index 956a5196..00000000 --- a/docs/public/icons/love.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/minimize.svg b/docs/public/icons/minimize.svg deleted file mode 100644 index 7b30ba26..00000000 --- a/docs/public/icons/minimize.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/paperclip.svg b/docs/public/icons/paperclip.svg deleted file mode 100644 index 80e7456d..00000000 --- a/docs/public/icons/paperclip.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/player--flow.svg b/docs/public/icons/player--flow.svg deleted file mode 100644 index 6f6ec5d8..00000000 --- a/docs/public/icons/player--flow.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/renew.svg b/docs/public/icons/renew.svg deleted file mode 100644 index 267a8c4a..00000000 --- a/docs/public/icons/renew.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/repeat.svg b/docs/public/icons/repeat.svg deleted file mode 100644 index 6b0514d8..00000000 --- a/docs/public/icons/repeat.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/reset.svg b/docs/public/icons/reset.svg deleted file mode 100644 index e824d6f9..00000000 --- a/docs/public/icons/reset.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/docs/public/icons/trash.svg b/docs/public/icons/trash.svg deleted file mode 100644 index 758aa56e..00000000 --- a/docs/public/icons/trash.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/upload--01.svg b/docs/public/icons/upload--01.svg deleted file mode 100644 index c9a11c36..00000000 --- a/docs/public/icons/upload--01.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/icons/upload--02.svg b/docs/public/icons/upload--02.svg deleted file mode 100644 index 5faa79d7..00000000 --- a/docs/public/icons/upload--02.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/docs/public/images/.DS_Store b/docs/public/images/.DS_Store new file mode 100644 index 00000000..5008ddfc Binary files /dev/null and b/docs/public/images/.DS_Store differ diff --git a/docs/public/images/cobalt-icon-solid.svg b/docs/public/images/cobalt-icon-solid.svg new file mode 100644 index 00000000..3d840d3d --- /dev/null +++ b/docs/public/images/cobalt-icon-solid.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/docs/public/images/cobalt-icon.svg b/docs/public/images/cobalt-icon.svg new file mode 100644 index 00000000..751f719f --- /dev/null +++ b/docs/public/images/cobalt-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/public/images/figma-colors.png b/docs/public/images/figma-colors.png deleted file mode 100644 index efbf0e3c..00000000 Binary files a/docs/public/images/figma-colors.png and /dev/null differ diff --git a/docs/public/images/figma-icons.png b/docs/public/images/figma-icons.png deleted file mode 100644 index c61875f7..00000000 Binary files a/docs/public/images/figma-icons.png and /dev/null differ diff --git a/docs/public/images/figma-typography.png b/docs/public/images/figma-typography.png deleted file mode 100644 index 010fc955..00000000 Binary files a/docs/public/images/figma-typography.png and /dev/null differ diff --git a/docs/public/images/home-bg-dark.png b/docs/public/images/home-bg-dark.png new file mode 100644 index 00000000..caf6ab19 Binary files /dev/null and b/docs/public/images/home-bg-dark.png differ diff --git a/docs/public/images/home-bg.png b/docs/public/images/home-bg.png new file mode 100644 index 00000000..a3045bc8 Binary files /dev/null and b/docs/public/images/home-bg.png differ diff --git a/docs/public/images/tokens-studio-for-figma.png b/docs/public/images/tokens-studio-for-figma.png deleted file mode 100644 index 8d16e8b5..00000000 Binary files a/docs/public/images/tokens-studio-for-figma.png and /dev/null differ diff --git a/docs/public/token-hex.svg b/docs/public/token-hex.svg deleted file mode 100644 index 2b09fca5..00000000 --- a/docs/public/token-hex.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/docs/scripts/update-readmes.js b/docs/scripts/update-readmes.js deleted file mode 100644 index 0e9659b4..00000000 --- a/docs/scripts/update-readmes.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Update READMEs - * - * For the docs pages that are just duplicates of other markdown files, automate them - */ - -import fs from 'node:fs'; -import {URL} from 'node:url'; -const FRONTMATTER_RE = /^---/gm; - -const updates = { - '../../packages/plugin-css/README.md': '../src/pages/docs/integrations/css.md', - '../../packages/plugin-sass/README.md': '../src/pages/docs/integrations/sass.md', - '../../packages/plugin-js/README.md': '../src/pages/docs/integrations/js.md', -}; - -const urlRewrites = { - '../plugin-css/': './css', - '../plugin-js/': './js', - '../plugin-sass/': './sass', -}; - -for (const [input, output] of Object.entries(updates)) { - let src = fs.readFileSync(new URL(input, import.meta.url), 'utf8'); - for (const [find, replace] of Object.entries(urlRewrites)) { - src = src.replace(new RegExp(`\\(${find}\\)`, 'g'), `(${replace})`); - } - - const dest = fs.readFileSync(new URL(output, import.meta.url), 'utf8'); - const parts = dest.split(FRONTMATTER_RE); - parts[parts.length - 1] = `\n\n${src}`; - fs.writeFileSync(new URL(output, import.meta.url), parts.join('---')); -} diff --git a/docs/src/components/CobaltSm.astro b/docs/src/components/CobaltSm.astro deleted file mode 100644 index ddaa34c9..00000000 --- a/docs/src/components/CobaltSm.astro +++ /dev/null @@ -1,24 +0,0 @@ - - -
Co
diff --git a/docs/src/components/DocsNav.astro b/docs/src/components/DocsNav.astro deleted file mode 100644 index bc3d6cb1..00000000 --- a/docs/src/components/DocsNav.astro +++ /dev/null @@ -1,103 +0,0 @@ ---- -import manifest from '../docs-manifest.json'; - -const subPageRE = /^\/docs\/[^/]+\//; -const docsSection = Astro.url.pathname.split('/').slice(0, 3).join('/'); - -function showSubPage(url) { - if (subPageRE.test(url)) { - if (Astro.url.pathname === '/') return false; - return docsSection && url.startsWith(docsSection); - } - return true; -} ---- - - - - diff --git a/docs/src/components/DocsPagination.astro b/docs/src/components/DocsPagination.astro deleted file mode 100644 index 95318152..00000000 --- a/docs/src/components/DocsPagination.astro +++ /dev/null @@ -1,107 +0,0 @@ ---- -import pages from '../docs-manifest.json'; - -let prev; -let next; -for (let n = 0; n < pages.pages.length; n++) { - if (Astro.url.pathname.startsWith(pages.pages[n].pathname)) { - if (pages.pages[n - 1]) prev = pages.pages[n - 1]; - if (pages.pages[n + 1]) next = pages.pages[n + 1]; - break; - } -} ---- - - - - diff --git a/docs/src/components/Footer.astro b/docs/src/components/Footer.astro deleted file mode 100644 index d194f331..00000000 --- a/docs/src/components/Footer.astro +++ /dev/null @@ -1,59 +0,0 @@ ---- -import CobaltSm from './CobaltSm.astro'; ---- - - - -
-
-
-
- - Cobalt -
-
- Cobalt is open source and is MIT-Licensed. -
-
-
-
diff --git a/docs/src/components/Head.astro b/docs/src/components/Head.astro deleted file mode 100644 index 2b695bb5..00000000 --- a/docs/src/components/Head.astro +++ /dev/null @@ -1,13 +0,0 @@ ---- -import '../styles/app.scss'; - -const {title = 'Cobalt design tokens'} = Astro.props; ---- - - - - - - -{title} - diff --git a/docs/src/components/Icons.astro b/docs/src/components/Icons.astro deleted file mode 100644 index a70c3ca3..00000000 --- a/docs/src/components/Icons.astro +++ /dev/null @@ -1,9 +0,0 @@ - - - GitHub - - - - - - diff --git a/docs/src/components/JSONYaml.astro b/docs/src/components/JSONYaml.astro deleted file mode 100644 index f8aaf460..00000000 --- a/docs/src/components/JSONYaml.astro +++ /dev/null @@ -1,152 +0,0 @@ ---- -import {Code} from 'astro/components'; -import {nanoid} from 'nanoid'; -import yaml from 'js-yaml'; - -interface Props { - code: string; -} -const {code} = Astro.props; - -const uuid = nanoid(); - -const yamlCode = yaml.dump(JSON.parse(code)).trim(); ---- - -
- - - - -
- - -
- -
- - - - diff --git a/docs/src/components/Nav.astro b/docs/src/components/Nav.astro deleted file mode 100644 index fa969c6d..00000000 --- a/docs/src/components/Nav.astro +++ /dev/null @@ -1,90 +0,0 @@ ---- -import CobaltSm from './CobaltSm.astro'; -import Icons from './Icons.astro'; -let {title = 'Cobalt'} = Astro.props; ---- - - - - -
- - -

{title}

-
- -
diff --git a/docs/src/components/SkipToMain.astro b/docs/src/components/SkipToMain.astro deleted file mode 100644 index 8037eec6..00000000 --- a/docs/src/components/SkipToMain.astro +++ /dev/null @@ -1,19 +0,0 @@ - - - diff --git a/docs/src/components/Token.astro b/docs/src/components/Token.astro deleted file mode 100644 index 821ab1f2..00000000 --- a/docs/src/components/Token.astro +++ /dev/null @@ -1,535 +0,0 @@ ---- -const {type: tokenType} = Astro.props; ---- - -
- { - ['color', 'border', 'gradient', 'transition', 'shadow'].includes(tokenType) && [ - - - , - - - , - ] - } - { - ['fontFamily', 'fontWeight', 'typography'].includes(tokenType) && [ - - - , -
Aa
, - ] - } - { - ['dimension', 'duration'].includes(tokenType) && [ - - - , -
, - ] - } - { - tokenType === 'number' && [ - - - , -
-
-
-
-
-
-
-
-
-
-
, - ] - } - { - tokenType === 'link' && [ - - - , -
-
-
-
-
-
, - ] - } - { - tokenType === 'cubic-bezier' && [ - - - , - - - , - ] - } - { - tokenType === 'stroke-style' && ( - - - - ) - } -
- - diff --git a/docs/src/docs-manifest.json b/docs/src/docs-manifest.json deleted file mode 100644 index 89d7da61..00000000 --- a/docs/src/docs-manifest.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "pages": [ - {"title": "Getting Started", "pathname": "/docs/getting-started"}, - {"title": "Tokens", "pathname": "/docs/tokens"}, - {"title": "Color", "pathname": "/docs/tokens/#color"}, - {"title": "Dimension", "pathname": "/docs/tokens/#dimension"}, - {"title": "Font Family", "pathname": "/docs/tokens/#font-family"}, - {"title": "Font Weight", "pathname": "/docs/tokens/#font-weight"}, - {"title": "Duration", "pathname": "/docs/tokens/#duration"}, - {"title": "Cubic Bézier", "pathname": "/docs/tokens/#cubic-bezier"}, - {"title": "Number", "pathname": "/docs/tokens/#number"}, - {"title": "Link", "pathname": "/docs/tokens/#link"}, - {"title": "Stroke style", "pathname": "/docs/tokens/#stroke-style"}, - {"title": "Border", "pathname": "/docs/tokens/#border"}, - {"title": "Transition", "pathname": "/docs/tokens/#transition"}, - {"title": "Shadow", "pathname": "/docs/tokens/#shadow"}, - {"title": "Gradient", "pathname": "/docs/tokens/#gradient"}, - {"title": "Typography", "pathname": "/docs/tokens/#typography"}, - {"title": "Groups", "pathname": "/docs/tokens/#groups"}, - {"title": "Custom", "pathname": "/docs/tokens/#custom"}, - {"title": "Aliasing", "pathname": "/docs/tokens/#aliasing"}, - {"title": "Modes", "pathname": "/docs/tokens/#modes"}, - {"title": "Integrations", "pathname": "/docs/integrations"}, - {"title": "CSS", "pathname": "/docs/integrations/css"}, - {"title": "Sass", "pathname": "/docs/integrations/sass"}, - {"title": "Tailwind", "pathname": "/docs/integrations/tailwind"}, - {"title": "JSON/JS/TS", "pathname": "/docs/integrations/js"}, - {"title": "Style Dictionary", "pathname": "/docs/integrations/style-dictionary"}, - {"title": "Tokens Studio", "pathname": "/docs/integrations/tokens-studio"}, - {"title": "Custom Plugins", "pathname": "/docs/integrations/custom-plugins"}, - {"title": "Guides", "pathname": "/docs/guides"}, - {"title": "What are Design Tokens?", "pathname": "/docs/guides/design-tokens"}, - {"title": "Best Practices", "pathname": "/docs/guides/best-practices"}, - {"title": "Modes", "pathname": "/docs/guides/modes"}, - {"title": "Reference", "pathname": "/docs/reference"}, - {"title": "CLI", "pathname": "/docs/reference/cli"}, - {"title": "Config API", "pathname": "/docs/reference/config"}, - {"title": "About", "pathname": "/docs/reference/about"} - ] -} diff --git a/docs/src/env.d.ts b/docs/src/env.d.ts deleted file mode 100644 index 8c34fb45..00000000 --- a/docs/src/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// \ No newline at end of file diff --git a/docs/src/layouts/docs.astro b/docs/src/layouts/docs.astro deleted file mode 100644 index 269c9500..00000000 --- a/docs/src/layouts/docs.astro +++ /dev/null @@ -1,56 +0,0 @@ ---- -import DocsNav from '../components/DocsNav.astro'; -import Footer from '../components/Footer.astro'; -import Head from '../components/Head.astro'; -import Nav from '../components/Nav.astro'; -import SkipToMain from '../components/SkipToMain.astro'; - -const {content = {}} = Astro.props; ---- - - - - - - - - - -