├── .changeset ├── README.md └── config.json ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ ├── bug.yml │ └── feat.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .vscode ├── extensions.json └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── package.json ├── packages ├── cli │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── bin │ │ └── cli.js │ ├── biome.json │ ├── index.html │ ├── lab.tsx │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── build.ts │ │ ├── check.ts │ │ ├── help.ts │ │ ├── index.ts │ │ ├── init.ts │ │ ├── lab.ts │ │ ├── normalize.ts │ │ ├── shared.ts │ │ └── version.ts │ ├── terrazzo.config.mjs │ ├── test │ │ ├── build.test.ts │ │ ├── check.test.ts │ │ ├── fixtures │ │ │ ├── check-config │ │ │ │ ├── styles │ │ │ │ │ └── tokens.json │ │ │ │ └── terrazzo.config.js │ │ │ ├── check-invalid │ │ │ │ └── tokens.json │ │ │ ├── check-npm │ │ │ │ ├── .gitignore │ │ │ │ ├── node_modules │ │ │ │ │ ├── .package-lock.json │ │ │ │ │ ├── my-other-tokens │ │ │ │ │ └── my-tokens │ │ │ │ ├── package-lock.json │ │ │ │ ├── package.json │ │ │ │ ├── pkg-exports │ │ │ │ │ ├── package.json │ │ │ │ │ └── tokens-xyz.json │ │ │ │ ├── pkg │ │ │ │ │ ├── package.json │ │ │ │ │ └── tokens.json │ │ │ │ └── terrazzo.config.js │ │ │ ├── check-valid │ │ │ │ └── tokens.json │ │ │ ├── error-no-default-export │ │ │ │ └── terrazzo.config.js │ │ │ ├── error-no-tokens │ │ │ │ └── terrazzo.config.js │ │ │ └── normalize │ │ │ │ ├── input.json │ │ │ │ └── want.json │ │ ├── normalize.test.ts │ │ └── version.test.ts │ ├── tokens-example.json │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.config.ts ├── fonts │ ├── FragmentMono-Italic.woff2 │ ├── FragmentMono-Regular.woff2 │ ├── InstrumentSans-Italic-VF.woff2 │ ├── InstrumentSans-VF.woff2 │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── fragment-mono.css │ ├── instrument-sans.css │ └── package.json ├── icons │ ├── .gitignore │ ├── .npmignore │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ └── index.tsx │ └── tsconfig.json ├── parser │ ├── .npmignore │ ├── CHANGELOG.md │ ├── CONTRIBUTING.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── build │ │ │ └── index.ts │ │ ├── config.ts │ │ ├── index.ts │ │ ├── lib │ │ │ └── code-frame.ts │ │ ├── lint │ │ │ ├── index.ts │ │ │ └── plugin-core │ │ │ │ ├── index.ts │ │ │ │ ├── lib │ │ │ │ └── docs.ts │ │ │ │ └── rules │ │ │ │ ├── a11y-min-contrast.ts │ │ │ │ ├── a11y-min-font-size.ts │ │ │ │ ├── colorspace.ts │ │ │ │ ├── consistent-naming.ts │ │ │ │ ├── descriptions.ts │ │ │ │ ├── duplicate-values.ts │ │ │ │ ├── max-gamut.ts │ │ │ │ ├── required-children.ts │ │ │ │ ├── required-modes.ts │ │ │ │ └── required-typography-properties.ts │ │ ├── logger.ts │ │ ├── parse │ │ │ ├── alias.ts │ │ │ ├── index.ts │ │ │ ├── json.ts │ │ │ ├── normalize.ts │ │ │ └── validate.ts │ │ └── types.ts │ ├── test │ │ ├── config.test.ts │ │ ├── lint.test.ts │ │ └── parse.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── plugin-css │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── build │ │ │ ├── index.ts │ │ │ └── utility-css.ts │ │ ├── index.ts │ │ └── lib.ts │ ├── test │ │ ├── cli.test.ts │ │ ├── fixtures │ │ │ ├── base-selector │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── build-default │ │ │ │ └── terrazzo.config.js │ │ │ ├── chained-selector │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── cli-config-outdir │ │ │ │ ├── .gitignore │ │ │ │ ├── styles │ │ │ │ │ ├── out │ │ │ │ │ │ └── want.css │ │ │ │ │ └── tokens.json │ │ │ │ └── terrazzo.config.js │ │ │ ├── cli-skip-build │ │ │ │ ├── terrazzo.config.js │ │ │ │ └── tokens.json │ │ │ ├── cli-watch │ │ │ │ ├── terrazzo.config.js │ │ │ │ └── tokens.json │ │ │ ├── cli-yaml │ │ │ │ ├── terrazzo.config.js │ │ │ │ └── tokens.yaml │ │ │ ├── cli │ │ │ │ ├── terrazzo.config.js │ │ │ │ └── tokens.json │ │ │ ├── ds-adobe-spectrum │ │ │ │ └── want.css │ │ │ ├── ds-apple-hig │ │ │ │ └── want.css │ │ │ ├── ds-figma-sds │ │ │ │ └── want.css │ │ │ ├── ds-github-primer │ │ │ │ └── want.css │ │ │ ├── ds-ibm-carbon │ │ │ │ └── want.css │ │ │ ├── ds-microsoft-fluent │ │ │ │ └── want.css │ │ │ ├── ds-radix │ │ │ │ └── want.css │ │ │ ├── ds-salesforce-lightning │ │ │ │ └── want.css │ │ │ ├── ds-shopify-polaris │ │ │ │ └── want.css │ │ │ ├── hex │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-boolean │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-border │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-color │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-dimension │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-gradient │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-shadow │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-string │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-transition │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ ├── type-typography │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ │ └── utility-css │ │ │ │ ├── tokens.json │ │ │ │ └── want.css │ │ ├── js.test.ts │ │ └── lib.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── plugin-js │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── build.ts │ │ ├── index.ts │ │ └── lib.ts │ ├── test │ │ ├── border │ │ │ ├── tokens.json │ │ │ ├── want.d.ts │ │ │ └── want.js │ │ ├── color │ │ │ ├── tokens.json │ │ │ ├── want.d.ts │ │ │ └── want.js │ │ ├── index.test.ts │ │ ├── shadow │ │ │ ├── tokens.json │ │ │ ├── want.d.ts │ │ │ └── want.js │ │ ├── transition │ │ │ ├── tokens.json │ │ │ ├── want.d.ts │ │ │ └── want.js │ │ └── typography │ │ │ ├── tokens.json │ │ │ ├── want.d.ts │ │ │ └── want.js │ ├── tsconfig.json │ └── vitest.config.ts ├── plugin-sass │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── build.ts │ │ ├── index.ts │ │ └── lib.ts │ ├── test │ │ ├── fixtures │ │ │ └── basic │ │ │ │ ├── tokens.json │ │ │ │ ├── want.css │ │ │ │ └── want.scss │ │ └── index.test.ts │ ├── tsconfig.json │ └── vitest.config.ts ├── plugin-swift │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ └── index.ts │ ├── test │ │ ├── color │ │ │ ├── Want.xcassets │ │ │ │ ├── color.primitive.blue.1.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.10.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.11.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.12.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.2.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.3.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.4.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.5.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.6.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.7.colorset │ │ │ │ │ └── Contents.json │ │ │ │ ├── color.primitive.blue.8.colorset │ │ │ │ │ └── Contents.json │ │ │ │ └── color.primitive.blue.9.colorset │ │ │ │ │ └── Contents.json │ │ │ └── tokens.json │ │ └── index.test.ts │ └── tsconfig.json ├── plugin-tailwind │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── index.ts │ │ └── lib.ts │ ├── test │ │ ├── cli.test.ts │ │ └── fixtures │ │ │ └── primer │ │ │ ├── terrazzo.config.js │ │ │ ├── tokens.json │ │ │ └── want.css │ ├── tsconfig.json │ └── vitest.config.ts ├── react-color-picker │ ├── .npmignore │ ├── .size-limit.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── components │ │ │ ├── ColorChannelSlider.css │ │ │ ├── ColorChannelSlider.tsx │ │ │ ├── ColorPicker.css │ │ │ ├── ColorPicker.tsx │ │ │ ├── HueWheel.tsx │ │ │ └── TrueGradient.tsx │ │ ├── index.ts │ │ ├── lib │ │ │ ├── color.ts │ │ │ ├── oklab.ts │ │ │ ├── rgb.ts │ │ │ └── webgl.ts │ │ └── types.d.ts │ ├── stylelint.config.js │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── vite.config.ts │ └── vitest.setup.ts ├── storybook │ ├── .gitignore │ ├── .storybook │ │ ├── global.css │ │ ├── main.js │ │ └── preview.js │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── src │ │ ├── Button.stories.jsx │ │ ├── ColorChannelSlider.stories.jsx │ │ ├── ColorPicker.stories.jsx │ │ ├── HueWheel.stories.jsx │ │ ├── Icon.stories.jsx │ │ ├── Kbd.stories.jsx │ │ ├── OmniBar.stories.jsx │ │ ├── Select.stories.jsx │ │ ├── Slider.stories.jsx │ │ ├── SubtleInput.stories.jsx │ │ ├── Switch.stories.jsx │ │ ├── TokenType.stories.jsx │ │ ├── TreeGrid.stories.jsx │ │ ├── TrueGradient.stories.jsx │ │ └── components │ │ │ ├── StickerSheet.module.css │ │ │ └── StickerSheet.tsx │ ├── stylelint.config.js │ ├── tsconfig.json │ └── vite.config.ts ├── tiles │ ├── .npmignore │ ├── .size-limit.js │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── Button │ │ │ ├── Button.css │ │ │ └── Button.tsx │ │ ├── ButtonLink │ │ │ └── ButtonLink.tsx │ │ ├── CopyButton │ │ │ ├── CopyButton.css │ │ │ └── CopyButton.tsx │ │ ├── Demo │ │ │ ├── Demo.css │ │ │ └── Demo.tsx │ │ ├── Fieldset │ │ │ ├── Fieldset.css │ │ │ └── Fieldset.tsx │ │ ├── Kbd │ │ │ ├── Kbd.css │ │ │ └── Kbd.tsx │ │ ├── OmniBar │ │ │ ├── OmniBar.css │ │ │ └── OmniBar.tsx │ │ ├── Select │ │ │ ├── Select.css │ │ │ └── Select.tsx │ │ ├── Slider │ │ │ ├── Slider.css │ │ │ └── Slider.tsx │ │ ├── SubtleInput │ │ │ ├── SubtleInput.css │ │ │ └── SubtleInput.tsx │ │ ├── Switch │ │ │ ├── Switch.css │ │ │ └── Switch.tsx │ │ ├── TokenType │ │ │ ├── TokenType.css │ │ │ └── TokenType.tsx │ │ ├── Tooltip │ │ │ ├── Tooltip.css │ │ │ └── Tooltip.tsx │ │ ├── TreeGrid │ │ │ ├── Group.tsx │ │ │ ├── Item.tsx │ │ │ ├── Root.tsx │ │ │ ├── TreeGrid.css │ │ │ └── index.tsx │ │ ├── hooks │ │ │ └── resize-observer.ts │ │ ├── index.ts │ │ ├── lib │ │ │ ├── number.test.ts │ │ │ ├── number.ts │ │ │ └── set.ts │ │ └── types.d.ts │ ├── stylelint.config.js │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── vite.config.ts ├── token-lab │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── biome.json │ ├── index.html │ ├── package.json │ ├── public │ │ ├── assets │ │ │ └── terrazzo-logo.svg │ │ └── favicon.svg │ ├── src │ │ ├── app.tsx │ │ ├── components │ │ │ ├── CodeEditor │ │ │ │ └── CodeEditor.tsx │ │ │ ├── CodePanel │ │ │ │ ├── CodePanel.module.css │ │ │ │ ├── CodePanel.module.d.css.ts │ │ │ │ └── CodePanel.tsx │ │ │ ├── EditableColorToken │ │ │ │ ├── EditableColorToken.module.css │ │ │ │ ├── EditableColorToken.module.d.css.ts │ │ │ │ └── EditableColorToken.tsx │ │ │ ├── EditableToken │ │ │ │ ├── EditableToken.module.css │ │ │ │ ├── EditableToken.module.d.css.ts │ │ │ │ └── EditableToken.tsx │ │ │ ├── MainNav │ │ │ │ ├── MainNav.module.css │ │ │ │ ├── MainNav.module.d.css.ts │ │ │ │ └── MainNav.tsx │ │ │ ├── TokensEditor │ │ │ │ ├── Group.tsx │ │ │ │ ├── TokensEditor.module.css │ │ │ │ ├── TokensEditor.module.d.css.ts │ │ │ │ └── TokensEditor.tsx │ │ │ └── TokensNav │ │ │ │ ├── TokensNav.module.css │ │ │ │ ├── TokensNav.module.d.css.ts │ │ │ │ └── TokensNav.tsx │ │ ├── hooks │ │ │ ├── navigation.ts │ │ │ └── tokens.ts │ │ ├── index.tsx │ │ ├── layouts │ │ │ └── Default │ │ │ │ ├── Default.module.css │ │ │ │ ├── Default.module.d.css.ts │ │ │ │ └── Default.tsx │ │ ├── lib │ │ │ └── indexed-db.ts │ │ ├── styles │ │ │ ├── global.css │ │ │ └── tokens.css │ │ └── types │ │ │ └── react.d.ts │ ├── tsconfig.json │ └── vite.config.ts ├── token-tools │ ├── .npmignore │ ├── CHANGELOG.md │ ├── README.md │ ├── biome.json │ ├── package.json │ ├── rolldown.config.ts │ ├── src │ │ ├── color.ts │ │ ├── css │ │ │ ├── boolean.ts │ │ │ ├── border.ts │ │ │ ├── color.ts │ │ │ ├── css-types.ts │ │ │ ├── cubic-bezier.ts │ │ │ ├── dimension.ts │ │ │ ├── duration.ts │ │ │ ├── font-family.ts │ │ │ ├── font-weight.ts │ │ │ ├── gradient.ts │ │ │ ├── index.ts │ │ │ ├── lib.ts │ │ │ ├── link.ts │ │ │ ├── number.ts │ │ │ ├── shadow.ts │ │ │ ├── string.ts │ │ │ ├── stroke-style.ts │ │ │ ├── transition.ts │ │ │ └── typography.ts │ │ ├── id.ts │ │ ├── index.ts │ │ ├── js │ │ │ └── index.ts │ │ ├── string.ts │ │ ├── transform.ts │ │ └── types.ts │ ├── test │ │ ├── css.test.ts │ │ ├── id.test.ts │ │ ├── js.test.ts │ │ ├── string.test.ts │ │ └── types.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── vitest.config.ts │ └── vitest.config.ts.timestamp-1718599329856-9fd330128a63e.mjs ├── tokens │ ├── .gitignore │ ├── biome.json │ ├── dist │ │ └── index.css │ ├── package.json │ ├── terrazzo.config.js │ └── tokens.json └── use-color │ ├── .npmignore │ ├── .size-limit.js │ ├── CHANGELOG.md │ ├── README.md │ ├── biome.json │ ├── index.d.ts │ ├── index.js │ ├── index.test.tsx │ ├── package.json │ ├── tsconfig.json │ ├── vitest.config.ts │ └── vitest.setup.ts ├── patches └── vite-plugin-sass-dts.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts └── inject-license.js ├── stylelint.config.js ├── tsconfig.base.json ├── tsconfig.react.json ├── turbo.json └── www ├── .gitignore ├── README.md ├── astro.config.ts ├── biome.json ├── package.json ├── public ├── assets │ ├── apple-hig-typography.png │ ├── cli-init.png │ ├── colorpicker-gamut.png │ ├── gamut.svg │ ├── github-themes.png │ ├── logo-figma.svg │ ├── logo-git-butler.svg │ ├── logo-guardian.svg │ ├── logo-lego.svg │ ├── logo-snyk.svg │ ├── radix-colors.png │ ├── social-image.png │ ├── terrazzo-logo.svg │ └── tooltip-dialog-light-dark-mode.png ├── favicon.svg └── robots.txt ├── src ├── components │ ├── SearchBox.css │ ├── SearchBox.tsx │ ├── ThemeSwitcher.tsx │ ├── docs-header-nav.astro │ ├── docs-sidenav.astro │ ├── head-meta.astro │ ├── illo │ │ └── home.astro │ ├── main-footer.astro │ ├── main-nav.astro │ ├── terrazzo.astro │ └── tri.astro ├── env.d.ts ├── layouts │ ├── default.astro │ └── docs.astro ├── lib │ └── navigation.ts ├── pages │ ├── docs │ │ ├── cli │ │ │ ├── api │ │ │ │ ├── js.md │ │ │ │ └── plugin-development.md │ │ │ ├── commands.md │ │ │ ├── config.md │ │ │ ├── index.md │ │ │ ├── integrations │ │ │ │ ├── css.md │ │ │ │ ├── custom.md │ │ │ │ ├── index.md │ │ │ │ ├── js.md │ │ │ │ ├── sass.md │ │ │ │ ├── swift.md │ │ │ │ └── tailwind.md │ │ │ ├── lint.md │ │ │ └── migrating.md │ │ ├── components │ │ │ ├── color-picker.mdx │ │ │ ├── demo.mdx │ │ │ ├── demos │ │ │ │ ├── color-picker-basic.tsx │ │ │ │ ├── demo-button.tsx │ │ │ │ └── slider.tsx │ │ │ ├── slider.mdx │ │ │ └── tiles.md │ │ ├── guides │ │ │ ├── dtcg.md │ │ │ ├── modes.md │ │ │ └── styleguide.md │ │ ├── index.md │ │ ├── reference │ │ │ ├── about.md │ │ │ └── tokens.md │ │ ├── token-lab │ │ │ └── index.md │ │ └── tokens │ │ │ ├── index.md │ │ │ └── modes.md │ ├── index.astro │ └── token-lab │ │ └── index.astro ├── plugins │ ├── rehype-auto-toc.js │ └── remark-vitepress.js ├── scripts │ ├── copy-code.js │ ├── tabs.js │ └── toc.js └── styles │ ├── app.css │ └── docs.css ├── stylelint.config.js └── tsconfig.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.2/schema.json", 3 | "changelog": [ 4 | "@changesets/changelog-github", 5 | { 6 | "repo": "terrazzoapp/terrazzo" 7 | } 8 | ], 9 | "commit": false, 10 | "access": "public", 11 | "baseBranch": "main" 12 | } 13 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Prettier format 2 | 4b07a803ce92376ba2dff577454cf619eb53ed5e 3 | c15567f94a557cbfed32bb3bb51c7e7b98f75d49 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feat.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Propose new functionality or a breaking change 3 | labels: 4 | - enhancement 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Summary 10 | description: Short description of the problem, and why this could be useful. 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: proposal 15 | attributes: 16 | label: Proposal & prior art 17 | description: | 18 | Describe the change in detail. Answer all the following questions: 1) What is syntax? 2) What are some alternate options, along with pros/cons of each? 3) Is this a breaking change (and if so, what is gained/lost)? 4) Any prior art to reference? 19 | validations: 20 | required: true 21 | - type: checkboxes 22 | id: extra 23 | attributes: 24 | label: Extra 25 | options: 26 | - label: I’m willing to open a PR (see [CONTRIBUTING.md](https://github.com/terrazzoapp/terrazzo/blob/main/CONTRIBUTING.md)) 27 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | _What does this PR change? Link to any related issue(s)._ 4 | 5 | ## How to Review 6 | 7 | _How can a reviewer review your changes? What should be kept in mind for this review?_ 8 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | changelog: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/setup-node@v4 13 | with: 14 | node-version: 22 15 | - uses: actions/checkout@v4 16 | - uses: pnpm/action-setup@v4 17 | with: 18 | run_install: true 19 | - run: pnpm run build 20 | - uses: changesets/action@v1 21 | with: 22 | version: pnpm run version 23 | publish: pnpm exec changeset publish 24 | commit: "[ci] release" 25 | title: "[ci] release" 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 28 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | dist 3 | node_modules 4 | package-lock.json 5 | .dccache 6 | .pnpm-debug* 7 | .env 8 | .turbo 9 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.css 2 | *.ts 3 | *.tsx 4 | *.js 5 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["biomejs.biome"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "[css]": { 3 | "editor.defaultFormatter": "biomejs.biome" 4 | }, 5 | "[astro]": { 6 | "editor.defaultFormatter": "biomejs.biome" 7 | }, 8 | "[javascript]": { 9 | "editor.defaultFormatter": "biomejs.biome" 10 | }, 11 | "[javascriptreact]": { 12 | "editor.defaultFormatter": "biomejs.biome" 13 | }, 14 | "[json]": { 15 | "editor.defaultFormatter": "biomejs.biome" 16 | }, 17 | "[markdown]": { 18 | "editor.defaultFormatter": "esbenp.prettier-vscode" 19 | }, 20 | "[typescript]": { 21 | "editor.defaultFormatter": "biomejs.biome" 22 | }, 23 | "[typescriptreact]": { 24 | "editor.defaultFormatter": "biomejs.biome" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ⛋ Terrazzo Monorepo 2 | 3 | This repo serves as the home for: 4 | 5 | - [Terrazzo CLI](https://terrazzo.app/docs/cli): generate code from [DTCG tokens](https://tr.designtokens.org/format/) (formerly known as “Cobalt UI”) 6 | - [CSS](https://terrazzo.app/docs/cli/integrations/css) 7 | - [Sass](https://terrazzo.app/docs/cli/integrations/sass) 8 | - [JS/TS](https://terrazzo.app/docs/cli/integrations/js) 9 | - [Swift](https://terrazzo.app/docs/cli/integrations/swift) 10 | - [Tailwind](https://terrazzo.app/docs/cli/integrations/tailwind) 11 | - [Terrazzo Color Picker](https://terrazzo.app/docs/components/color-picker), a futuristic colorpicker that can handle wide gamut and high bit-depth colors in stunning color reproduction 12 | - [Terrazzo Tiles DS](./packages/tiles/): a design system for _documenting_ design systems 13 | - Token Lab (coming soon): generate design systems from scratch, or start from an existing OSS design system 14 | 15 | ### 🔹 Cobalt UI 16 | 17 | Cobalt UI 2.0 was renamed to Terrazzo (same project, same people). To see the code for Cobalt 1.0, see the [1.x branch](https://github.com/terrazzoapp/terrazzo/tree/1.x). 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/monorepo", 3 | "private": true, 4 | "type": "module", 5 | "packageManager": "pnpm@10.11.0", 6 | "scripts": { 7 | "build": "turbo run build", 8 | "build:apps": "turbo run build:app", 9 | "changeset": "changeset", 10 | "inject-license": "node scripts/inject-license.js", 11 | "dev": "pnpm run -r --parallel --if-present dev", 12 | "lint": "turbo run lint", 13 | "format": "turbo run format", 14 | "test": "turbo run test", 15 | "prepublishOnly": "pnpm run build", 16 | "version": "pnpm run build && changeset version" 17 | }, 18 | "devDependencies": { 19 | "@arethetypeswrong/cli": "^0.18.1", 20 | "@biomejs/biome": "1.9.4", 21 | "@changesets/changelog-github": "^0.5.1", 22 | "@changesets/cli": "^2.29.4", 23 | "@types/node": "^22.15.29", 24 | "execa": "^9.6.0", 25 | "prettier": "^3.5.3", 26 | "rolldown": "1.0.0-beta.10", 27 | "rolldown-plugin-dts": "^0.13.7", 28 | "strip-ansi": "^7.1.0", 29 | "stylelint": "^16.20.0", 30 | "stylelint-config-standard": "^36.0.1", 31 | "stylelint-order": "^6.0.4", 32 | "turbo": "^2.5.4", 33 | "typescript": "^5.8.3", 34 | "vitest": "^3.1.4" 35 | }, 36 | "pnpm": { 37 | "patchedDependencies": { 38 | "vite-plugin-sass-dts": "patches/vite-plugin-sass-dts.patch" 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /packages/cli/.gitignore: -------------------------------------------------------------------------------- 1 | test/fixtures/**/tokens/ 2 | test/fixtures/init/terrazzo.config.js 3 | test/fixtures/**/actual.json 4 | -------------------------------------------------------------------------------- /packages/cli/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/cli/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to @terrazzo/cli 2 | 3 | This document contains developer notes and context for @terrazzo/parser. For general contribution guidelines, see [CONTRIBUTING.md](../../CONTRIBUTING.md) in the root. 4 | 5 | ## What this package does 6 | 7 | - Runs @terrazzo/parser and saves the output to disk using Node.js 8 | - Provide watcher scripts that also do this 9 | 10 | ## What this package DOES NOT do 11 | 12 | - Parse/validate/normalize tokens.json (that’s for @terrazzo/parser) 13 | - Interact with plugins 14 | -------------------------------------------------------------------------------- /packages/cli/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/cli/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ @terrazzo/cli 2 | 3 | The Terrazzo CLI takes DTCG design tokens and generates code using plugins (e.g. [CSS](../plugin-css/), [Sass](../plugin-sass/), [JS/TS](../plugin-js/), and more). You can either run the Terrazzo CLI manually, or as part of your CI process. 4 | 5 | > [!TIP] 6 | > Migrating from Cobalt? Check out the [Migration Guide](/docs/cli/migrating) 7 | 8 | ## Quickstart 9 | 10 | First, install the package using your package manager of choice: 11 | 12 | ```sh 13 | npm i -D @terrazzo/cli 14 | ``` 15 | 16 | And you can create a configuration and install plugins with the `init` command: 17 | 18 | ```sh 19 | npx tz init 20 | ``` 21 | 22 | This will create a `terrazzo.config.js` starter config file, and even start your token system from a popular open source design system. 23 | 24 | [Full documentation](https://terrazzo.app/docs/cli) 25 | -------------------------------------------------------------------------------- /packages/cli/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./bin/", "./src/", "./test/", "*.tsx"] 6 | }, 7 | "linter": { 8 | "rules": { 9 | "suspicious": { 10 | "noConsole": "off" 11 | } 12 | } 13 | }, 14 | "overrides": [ 15 | { 16 | "include": ["./bin/cli.js"], 17 | "linter": { 18 | "rules": { 19 | "suspicious": { 20 | "noConsole": "off" 21 | } 22 | } 23 | } 24 | } 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /packages/cli/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Terrazzo Token Lab 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /packages/cli/lab.tsx: -------------------------------------------------------------------------------- 1 | import TokenLab from '@terrazzo/token-lab'; 2 | import React from 'react'; 3 | import { createRoot } from 'react-dom/client'; 4 | 5 | const response = await fetch('/api/tokens'); 6 | const tokenFile = await response.text(); 7 | const root = createRoot(document.getElementById('app')); 8 | 9 | root.render( 10 | { 13 | const response = await fetch('/api/tokens', { 14 | method: 'POST', 15 | headers: { 16 | 'Content-Type': 'text/plain', 17 | }, 18 | body: updatedTokens, 19 | }); 20 | 21 | if (!response.ok) { 22 | console.error(`Failed to save tokens: ${response.status}`); 23 | } 24 | }} 25 | />, 26 | ); 27 | -------------------------------------------------------------------------------- /packages/cli/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | external: [ 10 | '@clack/prompts', 11 | '@hono/node-server', 12 | '@humanwhocodes/momoa', 13 | '@terrazzo/parser', 14 | '@terrazzo/token-lab', 15 | '@terrazzo/token-tools', 16 | '@types/escodegen', 17 | 'chokidar', 18 | 'detect-package-manager', 19 | 'dotenv', 20 | 'escodegen', 21 | 'merge-anything', 22 | 'meriyah', 23 | 'mime', 24 | 'picocolors', 25 | 'yaml-to-momoa', 26 | ], 27 | output: { 28 | dir: 'dist', 29 | format: 'es', 30 | sourcemap: true, 31 | }, 32 | }); 33 | -------------------------------------------------------------------------------- /packages/cli/src/check.ts: -------------------------------------------------------------------------------- 1 | import { type ConfigInit, type Logger, parse } from '@terrazzo/parser'; 2 | import yamlToMomoa from 'yaml-to-momoa'; 3 | import { loadTokens, printError, printSuccess, resolveTokenPath } from './shared.js'; 4 | 5 | export interface CheckOptions { 6 | /** positional CLI args */ 7 | positionals: string[]; 8 | config: ConfigInit; 9 | logger: Logger; 10 | } 11 | 12 | /** tz check */ 13 | export async function checkCmd({ config, logger, positionals }: CheckOptions) { 14 | try { 15 | const startTime = performance.now(); 16 | const tokenPaths = positionals.slice(1).length 17 | ? positionals.slice(1).map((tokenPath) => resolveTokenPath(tokenPath, { logger })) 18 | : config.tokens; 19 | const sources = await loadTokens(tokenPaths, { logger }); 20 | if (!sources?.length) { 21 | logger.error({ group: 'config', message: 'Couldn’t find any tokens. Run `npx tz init` to create some.' }); 22 | return; 23 | } 24 | await parse(sources, { config, continueOnError: true, logger, yamlToMomoa }); // will throw if errors 25 | printSuccess('No errors', startTime); 26 | } catch (err) { 27 | printError((err as Error).message); 28 | process.exit(1); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/src/help.ts: -------------------------------------------------------------------------------- 1 | /** Show help */ 2 | export function helpCmd() { 3 | console.log(`tz 4 | [commands] 5 | build Build token artifacts from tokens.json 6 | --watch, -w Watch tokens.json for changes and recompile 7 | --no-lint Disable linters running on build 8 | check [path] Check tokens.json for errors and run linters 9 | lint [path] (alias of check) 10 | init Create a starter tokens.json file 11 | lab Manage your tokens with a web interface 12 | 13 | [options] 14 | --help Show this message 15 | --config, -c Path to config (default: ./terrazzo.config.js) 16 | --quiet Suppress warnings 17 | `); 18 | } 19 | -------------------------------------------------------------------------------- /packages/cli/src/version.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | 3 | export function versionCmd() { 4 | const { version } = JSON.parse(fs.readFileSync(new URL('../package.json', import.meta.url), 'utf8')); 5 | console.log(version); 6 | } 7 | -------------------------------------------------------------------------------- /packages/cli/terrazzo.config.mjs: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | 3 | export default defineConfig({ 4 | tokens: ['tokens-example.json'], 5 | outDir: './tokens/', 6 | plugins: [ 7 | /** @see https://terrazzo.app/docs/cli/integrations */ 8 | ], 9 | lint: { 10 | /** @see https://terrazzo.app/docs/cli/lint */ 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-config/styles/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "blue": { 4 | "100": { 5 | "$type": "color", 6 | "$value": { "colorSpace": "srgb", "components": [0, 0.2, 1], "alpha": 1 } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-config/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | tokens: ['./styles/tokens.json'], 3 | }; 4 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-invalid/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "blue": { 4 | "100": { 5 | "$type": "color", 6 | "$value": { "colorSpace": "srgb", "components": "[0, 0.2, 1]", "alpha": 1 } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/.gitignore: -------------------------------------------------------------------------------- 1 | # `npm i` needed for this test for Node resolution. 2 | # No actual node_modules were harmed in the making of this test. 3 | !node_modules 4 | !package-lock.json 5 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/node_modules/.package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/test-cli", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "node_modules/my-other-tokens": { 7 | "resolved": "pkg-exports", 8 | "link": true 9 | }, 10 | "node_modules/my-tokens": { 11 | "resolved": "pkg", 12 | "link": true 13 | }, 14 | "packages/pkg": { 15 | "extraneous": true 16 | }, 17 | "pkg": { 18 | "name": "my-tokens", 19 | "dev": true 20 | }, 21 | "pkg-exports": { 22 | "name": "my-other-tokens", 23 | "dev": true 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/node_modules/my-other-tokens: -------------------------------------------------------------------------------- 1 | ../pkg-exports -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/node_modules/my-tokens: -------------------------------------------------------------------------------- 1 | ../pkg -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/test-cli", 3 | "lockfileVersion": 3, 4 | "requires": true, 5 | "packages": { 6 | "": { 7 | "name": "@terrazzo/test-cli", 8 | "devDependencies": { 9 | "my-other-tokens": "file:./pkg-exports", 10 | "my-tokens": "file:./pkg" 11 | } 12 | }, 13 | "node_modules/my-other-tokens": { 14 | "resolved": "pkg-exports", 15 | "link": true 16 | }, 17 | "node_modules/my-tokens": { 18 | "resolved": "pkg", 19 | "link": true 20 | }, 21 | "packages/pkg": { 22 | "extraneous": true 23 | }, 24 | "pkg": { 25 | "name": "my-tokens", 26 | "dev": true 27 | }, 28 | "pkg-exports": { 29 | "name": "my-other-tokens", 30 | "dev": true 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/test-cli", 3 | "private": true, 4 | "type": "module", 5 | "devDependencies": { 6 | "my-tokens": "file:./pkg", 7 | "my-other-tokens": "file:./pkg-exports" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/pkg-exports/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-other-tokens", 3 | "private": true, 4 | "type": "module", 5 | "exports": { 6 | "./aliased": "./tokens-xyz.json" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/pkg-exports/tokens-xyz.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "blue": { 4 | "$type": "color", 5 | "4": { 6 | "$value": { "colorSpace": "oklch", "components": [0.9381, 0.035, 234.8] } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/pkg/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-tokens", 3 | "private": true, 4 | "type": "module" 5 | } 6 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/pkg/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "blue": { 4 | "$type": "color", 5 | "4": { 6 | "$value": { "colorSpace": "oklch", "components": [0.9381, 0.035, 234.8] } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-npm/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '../../../dist/index.js'; 2 | 3 | export default defineConfig({ 4 | tokens: ['my-tokens/tokens.json', 'my-other-tokens/aliased'], 5 | }); 6 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/check-valid/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "blue": { 4 | "100": { 5 | "$type": "color", 6 | "$value": { "colorSpace": "srgb", "components": [0, 0.2, 1], "alpha": 1 } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/error-no-default-export/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | export const config = {}; 2 | -------------------------------------------------------------------------------- /packages/cli/test/fixtures/error-no-tokens/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '../../../dist/index.js'; 2 | 3 | export default defineConfig({}); 4 | -------------------------------------------------------------------------------- /packages/cli/test/normalize.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { execa } from 'execa'; 4 | import { describe, expect, test } from 'vitest'; 5 | 6 | const cmd = '../../../bin/cli.js'; 7 | 8 | describe('tz normalize', () => { 9 | test('basic', async () => { 10 | const cwd = new URL('./fixtures/normalize/', import.meta.url); 11 | await execa(cmd, ['normalize', 'input.json', '--output', 'actual.json'], { cwd }); 12 | await expect(fs.readFileSync(new URL('./actual.json', cwd), 'utf8')).toMatchFileSnapshot( 13 | fileURLToPath(new URL('./want.json', cwd)), 14 | ); 15 | }); 16 | 17 | test('basic (shortcut)', async () => { 18 | const cwd = new URL('./fixtures/normalize/', import.meta.url); 19 | await execa(cmd, ['normalize', 'input.json', '-o', 'actual.json'], { cwd }); 20 | await expect(fs.readFileSync(new URL('./actual.json', cwd), 'utf8')).toMatchFileSnapshot( 21 | fileURLToPath(new URL('./want.json', cwd)), 22 | ); 23 | }); 24 | 25 | test('missing input', async () => { 26 | const cwd = new URL('./fixtures/normalize/', import.meta.url); 27 | await expect(execa(cmd, ['normalize', '--output', 'actual.json'], { cwd })).rejects.toThrow( 28 | `Command failed with exit code 1: ../../../bin/cli.js normalize --output actual.json 29 | 30 | ✗ [config] Expected input: \`tz normalize -o output.json\``, 31 | ); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /packages/cli/test/version.test.ts: -------------------------------------------------------------------------------- 1 | import { execa } from 'execa'; 2 | import { describe, expect, test } from 'vitest'; 3 | 4 | const cmd = './bin/cli.js'; 5 | 6 | describe('tz --version', () => { 7 | test('returns version', async () => { 8 | const { stdout } = await execa('node', [cmd, '--version']); 9 | expect(stdout).toMatch(/^\d+\.\d+\.\d+/); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /packages/cli/tokens-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "$description": "Color tokens", 4 | "$type": "color", 5 | "black": { 6 | "100": { "$value": "#0c0c0d0d" }, 7 | "200": { "$value": "#0c0c0d1a" }, 8 | "300": { "$value": "#0c0c0d33" }, 9 | "400": { "$value": "#0c0c0456" }, 10 | "500": { "$value": "#0c0c0db2" }, 11 | "600": { "$value": "#0c0c0dcc" }, 12 | "700": { "$value": "#0c0c0dd9" }, 13 | "800": { "$value": "#0c0c0de5" }, 14 | "900": { "$value": "#0c0c0df2" }, 15 | "1000": { "$value": "#0c0c0d" } 16 | } 17 | }, 18 | "border": { 19 | "$description": "Border tokens", 20 | "$type": "border" 21 | }, 22 | "space": { 23 | "$description": "Dimension tokens", 24 | "$type": "dimension" 25 | }, 26 | "typography": { 27 | "$description": "Typography tokens", 28 | "$type": "typography" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /packages/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/", "./test/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/cli/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react-swc'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | build: { 7 | outDir: 'dist/lab', 8 | rollupOptions: { 9 | external: ['@terrazzo/tiles', '@terrazzo/token-lab'], 10 | }, 11 | emptyOutDir: true, 12 | sourcemap: true, 13 | target: 'es2024', 14 | }, 15 | }); 16 | -------------------------------------------------------------------------------- /packages/cli/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | testTimeout: 10_000, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/fonts/FragmentMono-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/packages/fonts/FragmentMono-Italic.woff2 -------------------------------------------------------------------------------- /packages/fonts/FragmentMono-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/packages/fonts/FragmentMono-Regular.woff2 -------------------------------------------------------------------------------- /packages/fonts/InstrumentSans-Italic-VF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/packages/fonts/InstrumentSans-Italic-VF.woff2 -------------------------------------------------------------------------------- /packages/fonts/InstrumentSans-VF.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/packages/fonts/InstrumentSans-VF.woff2 -------------------------------------------------------------------------------- /packages/fonts/README.md: -------------------------------------------------------------------------------- 1 | # Terrazzo Fonts 2 | 3 | Redistributions of SIL open source fonts. All rights reserved by the respective license owners. Not affiliated with this project in any way. 4 | 5 | - [Instrument Sans](https://github.com/Instrument/instrument-sans) 6 | - [Fragment Mono](https://github.com/weiweihuanghuang/fragment-mono). 7 | 8 | ## Setup 9 | 10 | ```sh 11 | pnpm i @terrazzo/fonts 12 | ``` 13 | 14 | In any Vite-powered setup, import CSS in any JS or TS entry file, and the fonts should be loaded automatically. 15 | 16 | ```ts 17 | import '@terrazzo/fonts/fragment-mono.css'; 18 | import '@terrazzo/fonts/instrument-sans.css'; 19 | ``` 20 | -------------------------------------------------------------------------------- /packages/fonts/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/fonts/fragment-mono.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Fragment Mono'; 3 | font-style: normal; 4 | src: url('./FragmentMono-Regular.woff2') format('woff2'); 5 | } 6 | 7 | @font-face { 8 | font-family: 'Fragment Mono'; 9 | font-style: italic; 10 | src: url('./FragmentMono-Italic.woff2') format('woff2'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/fonts/instrument-sans.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Instrument Sans'; 3 | font-style: normal; 4 | src: url('./InstrumentSans-VF.woff2') format('woff2'); 5 | } 6 | 7 | @font-face { 8 | font-family: 'Instrument Sans'; 9 | font-style: italic; 10 | src: url('./InstrumentSans-Italic-VF.woff2') format('woff2'); 11 | } 12 | -------------------------------------------------------------------------------- /packages/fonts/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/fonts", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "license": "OFL-1.1", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/terrazzoapp/terrazzo.git", 9 | "directory": "./packages/fonts/" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/icons/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/icons/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | rollup.* 5 | tsconfig.* 6 | vite.* 7 | vitest.* 8 | -------------------------------------------------------------------------------- /packages/icons/README.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/icons 2 | 3 | Icon set for Terrazzo. Repackaged icons from ['react-icons'](https://react-icons.github.io/react-icons/) because that project isn’t maintained anymore. 4 | -------------------------------------------------------------------------------- /packages/icons/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "linter": { 5 | "rules": { 6 | "a11y": { 7 | "noInteractiveElementToNoninteractiveRole": "off" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/icons/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/icons", 3 | "version": "0.1.0", 4 | "type": "module", 5 | "author": { 6 | "name": "Drew Powers", 7 | "email": "drew@pow.rs" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/terrazzoapp/terrazzo.git", 12 | "directory": "./packages/icons/" 13 | }, 14 | "main": "./dist/index.js", 15 | "scripts": { 16 | "build": "rollup -c rollup.config.js", 17 | "format": "biome check --fix --unsafe src", 18 | "lint": "pnpm --filter @terrazzo/icons run \"/^lint:(js|ts)/\"", 19 | "lint:js": "biome check .", 20 | "lint:ts": "tsc --noEmit" 21 | }, 22 | "peerDependencies": { 23 | "react": "^19.0.0", 24 | "react-dom": "^19.0.0" 25 | }, 26 | "devDependencies": { 27 | "@rollup/plugin-typescript": "^12.1.2", 28 | "@types/react": "^19.1.1", 29 | "@types/react-dom": "^19.1.2", 30 | "react": "19.0.0", 31 | "react-dom": "19.0.0", 32 | "rollup": "^4.40.0", 33 | "rollup-plugin-import-css": "^3.5.8" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/icons/rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from '@rollup/plugin-typescript'; 2 | 3 | /** @type {import("rollup").InputOptions} */ 4 | export default { 5 | plugins: [ts()], 6 | input: 'src/index.tsx', 7 | external: ['*'], 8 | output: { 9 | dir: './dist/', 10 | sourcemap: true, 11 | globals: { 12 | 'react/jsx-runtime': 'jsxRuntime', 13 | 'react-dom/client': 'ReactDOM', 14 | react: 'React', 15 | }, 16 | }, 17 | }; 18 | -------------------------------------------------------------------------------- /packages/icons/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "module": "nodenext", 6 | "outDir": "dist" 7 | }, 8 | "include": ["./src/"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/parser/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | test/ 4 | tsconfig.* 5 | vitest.* 6 | -------------------------------------------------------------------------------- /packages/parser/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to @terrazzo/parser 2 | 3 | This document contains developer notes and context for @terrazzo/parser. For general contribution guidelines, see [CONTRIBUTING.md](../../CONTRIBUTING.md) in the root. 4 | 5 | ## What this package does 6 | 7 | - Parses, validates, and normalizes tokens.json 8 | - Executes plugins and builds output 9 | - Executes linting 10 | 11 | ## What this package DOES NOT do 12 | 13 | - Write files to disk (that’s a job for Node.js; this can be run in a browser) 14 | -------------------------------------------------------------------------------- /packages/parser/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/parser/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./src/", "./test/"] 6 | }, 7 | "linter": { 8 | "rules": { 9 | "suspicious": { 10 | "noExplicitAny": "off" 11 | } 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/parser/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | external: [ 10 | '@humanwhocodes/mamoa', 11 | '@terrazzo/token-tools', 12 | 'culori', 13 | 'merge-anything', 14 | 'picocolors', 15 | 'wildcard-match', 16 | 'yaml-to-momoa', 17 | 'yaml', 18 | ], 19 | output: { 20 | dir: 'dist', 21 | format: 'es', 22 | sourcemap: true, 23 | }, 24 | }); 25 | -------------------------------------------------------------------------------- /packages/parser/src/lint/plugin-core/lib/docs.ts: -------------------------------------------------------------------------------- 1 | export function docsLink(ruleName: string): string { 2 | return `https://terrazzo.app/docs/cli/lint#${ruleName.replaceAll('/', '')}`; 3 | } 4 | -------------------------------------------------------------------------------- /packages/parser/src/lint/plugin-core/rules/descriptions.ts: -------------------------------------------------------------------------------- 1 | import { isTokenMatch } from '@terrazzo/token-tools'; 2 | import type { LintRule } from '../../../types.js'; 3 | import { docsLink } from '../lib/docs.js'; 4 | 5 | export const DESCRIPTIONS = 'core/descriptions'; 6 | 7 | export interface RuleDescriptionsOptions { 8 | /** Token IDs to ignore. Supports globs (`*`). */ 9 | ignore?: string[]; 10 | } 11 | 12 | const ERROR_MISSING_DESCRIPTION = 'MISSING_DESCRIPTION'; 13 | 14 | const rule: LintRule = { 15 | meta: { 16 | messages: { 17 | [ERROR_MISSING_DESCRIPTION]: '{{ id }} missing description', 18 | }, 19 | docs: { 20 | description: 'Enforce tokens have descriptions.', 21 | url: docsLink(DESCRIPTIONS), 22 | }, 23 | }, 24 | defaultOptions: {}, 25 | create({ tokens, options, report }) { 26 | for (const t of Object.values(tokens)) { 27 | if (options.ignore && isTokenMatch(t.id, options.ignore)) { 28 | continue; 29 | } 30 | if (!t.$description) { 31 | report({ 32 | messageId: ERROR_MISSING_DESCRIPTION, 33 | data: { id: t.id }, 34 | node: t.source.node, 35 | }); 36 | } 37 | } 38 | }, 39 | }; 40 | 41 | export default rule; 42 | -------------------------------------------------------------------------------- /packages/parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "noEmit": true 6 | }, 7 | "include": ["./src/", "./test/"], 8 | "exclude": ["node_modules"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/parser/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | restoreMocks: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/plugin-css/.gitignore: -------------------------------------------------------------------------------- 1 | test/fixtures/**/tokens/ 2 | -------------------------------------------------------------------------------- /packages/plugin-css/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/plugin-css/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin-css/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ @terrazzo/plugin-css 2 | 3 | Convert DTCG tokens into CSS variables for use in any web application or native app with webview. Convert your modes into CSS media queries for complete flexibility. 4 | 5 | ## Setup 6 | 7 | Requires [Node.js 20 or later](https://nodejs.org) and [the CLI installed](https://terrazzo.app/docs/cli). With both installed, run: 8 | 9 | ```sh 10 | npm i -D @terrazzo/plugin-css 11 | ``` 12 | 13 | Add a `terrazzo.config.js` to the root of your project: 14 | 15 | ```ts 16 | import { defineConfig } from "@terrazzo/cli"; 17 | import css from "@terrazzo/plugin-css"; 18 | 19 | export default defineConfig({ 20 | outDir: "./tokens/", 21 | plugins: [ 22 | css({ 23 | filename: "tokens.css", 24 | }), 25 | ], 26 | }); 27 | ``` 28 | 29 | Lastly, run: 30 | 31 | ```sh 32 | npx tz build 33 | ``` 34 | 35 | And you’ll see a `./tokens/tokens.css` file generated in your project. 36 | 37 | [Full Documentation](https://terrazzo.app/docs/integrations/css) 38 | -------------------------------------------------------------------------------- /packages/plugin-css/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./src/", "./test/"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-css/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | output: { 10 | dir: 'dist', 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/base-selector/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "foo": { 3 | "$type": "color", 4 | "$value": { "colorSpace": "oklch", "components": [0.6412, 0.239, 254.98] } 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/base-selector/want.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------- 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | :host { 6 | --foo: oklch(0.6333612529106073 0.20110650544801523 253.921996151641); 7 | } 8 | 9 | @media (color-gamut: p3) { 10 | :host { 11 | --foo: oklch(0.6312831744667307 0.21843061935804214 253.28399755832424); 12 | } 13 | } 14 | 15 | @media (color-gamut: rec2020) { 16 | :host { 17 | --foo: oklch(0.6336836232352946 0.2186805283935796 254.20132843068833); 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/build-default/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import pluginCSS from '../../../../plugin-css/dist/index.js'; 2 | import { defineConfig } from '../../../dist/index.js'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | pluginCSS({ 7 | modeSelectors: [ 8 | { mode: 'Light', selectors: ['@media (prefers-color-scheme: light)'] }, 9 | { mode: 'Dark', selectors: ['@media (prefers-color-scheme: dark)'] }, 10 | ], 11 | }), 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/chained-selector/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "$type": "color", 4 | "blue": { 5 | "6": { 6 | "$value": "#0550ae", 7 | "$extensions": { 8 | "mode": { 9 | "light": "#0550ae", 10 | "light-colorblind": "#0550ae", 11 | "light-high-contrast": "#023b95", 12 | "dark": "#1158c7", 13 | "dark-dimmed": "#255ab2", 14 | "dark-high-contrast": "#318bf8", 15 | "dark-colorblind": "#1158c7" 16 | } 17 | } 18 | } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-config-outdir/.gitignore: -------------------------------------------------------------------------------- 1 | styles/out/actual.css 2 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-config-outdir/styles/out/want.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------- 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | :root { 6 | --color-base-gray-10: lab(0.1 0 0); 7 | } 8 | 9 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-config-outdir/styles/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "$type": "color", 4 | "base": { 5 | "gray": { 6 | "10": { "$value": { "colorSpace": "lab", "components": [0.1, 0, 0], "alpha": 1 } } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-config-outdir/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | import css from '../../../dist/index.js'; 3 | 4 | export default defineConfig({ 5 | tokens: ['./styles/tokens.json'], 6 | outDir: './styles/', 7 | plugins: [ 8 | css({ 9 | filename: 'out/actual.css', 10 | }), 11 | ], 12 | }); 13 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-skip-build/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | import css from '../../../dist/index.js'; 3 | 4 | export default defineConfig({ 5 | tokens: ['./tokens.json'], 6 | plugins: [ 7 | css({ 8 | skipBuild: true, 9 | }), 10 | ], 11 | }); 12 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-skip-build/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "color": { 3 | "$type": "color", 4 | "base": { 5 | "gray": { 6 | "10": { "$value": { "colorSpace": "lab", "components": [0.1, 0, 0], "alpha": 1 } } 7 | } 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-watch/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | import pluginCSS from '../../../dist/index.js'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | pluginCSS({ 7 | modeSelectors: [ 8 | { mode: 'Light', selectors: ['@media (prefers-color-scheme: light)'] }, 9 | { mode: 'Dark', selectors: ['@media (prefers-color-scheme: dark)'] }, 10 | ], 11 | }), 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli-yaml/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | import pluginCSS from '../../../dist/index.js'; 3 | 4 | export default defineConfig({ 5 | // expect Terrazzo to automatically pick up on "tokens.yaml" filename 6 | plugins: [ 7 | pluginCSS({ 8 | modeSelectors: [ 9 | { mode: 'Light', selectors: ['@media (prefers-color-scheme: light)'] }, 10 | { mode: 'Dark', selectors: ['@media (prefers-color-scheme: dark)'] }, 11 | ], 12 | }), 13 | ], 14 | }); 15 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/cli/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | import pluginCSS from '../../../dist/index.js'; 3 | 4 | export default defineConfig({ 5 | plugins: [ 6 | pluginCSS({ 7 | modeSelectors: [ 8 | { mode: 'Light', selectors: ['@media (prefers-color-scheme: light)'] }, 9 | { mode: 'Dark', selectors: ['@media (prefers-color-scheme: dark)'] }, 10 | ], 11 | }), 12 | ], 13 | }); 14 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-boolean/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "bool": { 3 | "uh-huh": { 4 | "$type": "boolean", 5 | "$value": true 6 | }, 7 | "nuh-uh": { 8 | "$type": "boolean", 9 | "$value": false 10 | }, 11 | "idk": { 12 | "$value": "{bool.nuh-uh}" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-boolean/want.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------- 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | :root { 6 | --ds-bool-idk: var(--ds-bool-nuh-uh); 7 | --ds-bool-nuh-uh: 0; 8 | --ds-bool-uh-huh: 1; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-dimension/want.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------- 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | :root { 6 | --ds-control-default-size: var(--ds-space-600); 7 | --ds-control-small-size: var(--ds-space-500); 8 | --ds-space-000: 0.125rem; 9 | --ds-space-2x-large: 3rem; 10 | --ds-space-3x-large: 4rem; 11 | --ds-space-100: 0.25rem; 12 | --ds-space-200: 0.5rem; 13 | --ds-space-300: 0.75rem; 14 | --ds-space-400: 1rem; 15 | --ds-space-500: 1.5rem; 16 | --ds-space-600: 2rem; 17 | --ds-space-700: 2.5rem; 18 | --ds-space-800: 3rem; 19 | --ds-space-900: 4rem; 20 | --ds-space-large: 1.5rem; 21 | --ds-space-x-large: 2rem; 22 | } 23 | 24 | @media (width >= 600px) { 25 | :root { 26 | --ds-control-default-size: var(--ds-space-600); 27 | --ds-control-small-size: var(--ds-space-500); 28 | --ds-space-000: 0.25rem; 29 | --ds-space-100: 0.375rem; 30 | --ds-space-200: 0.75rem; 31 | --ds-space-300: 1.25rem; 32 | --ds-space-400: 1.25rem; 33 | --ds-space-500: 1.75rem; 34 | --ds-space-600: 2.5rem; 35 | --ds-space-700: 3.5rem; 36 | --ds-space-800: 4rem; 37 | --ds-space-900: 6rem; 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-gradient/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "gradient": { 3 | "$type": "gradient", 4 | "blue-to-green": { 5 | "$value": [ 6 | { "color": { "colorSpace": "oklch", "components": [0.4559, 0.328, 265.76], "hex": "#051fcf" }, "position": 0 }, 7 | { "color": { "colorSpace": "oklch", "components": [0.8206, 0.286, 156.71], "hex": "#00e089" }, "position": 1 } 8 | ], 9 | "$extensions": { 10 | "mode": { 11 | "light": [ 12 | { 13 | "color": { "colorSpace": "oklch", "components": [0.4559, 0.328, 265.76], "hex": "#051fcf" }, 14 | "position": 0 15 | }, 16 | { 17 | "color": { "colorSpace": "oklch", "components": [0.8206, 0.286, 156.71], "hex": "#00e089" }, 18 | "position": 1 19 | } 20 | ] 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-shadow/want.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------- 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | :root { 6 | --ds-shadow-base: var(--ds-shadow-simple); 7 | --ds-shadow-inset: inset 0 4px 8px 0 color(srgb 0 0 0 / 0.15); 8 | --ds-shadow-layered: 0 1px 1px 0 color(srgb 0 0 0 / 0.12), 0 2px 2px 0 color(srgb 0 0 0 / 0.12), 0 4px 4px 0 color(srgb 0 0 0 / 0.12), 0 8px 8px 0 color(srgb 0 0 0 / 0.12), 0 16px 16px 0 color(srgb 0 0 0 / 0.12); 9 | --ds-shadow-neon: 0 0 0 0.5rem oklch(0.7856631286711945 0.2500998352524085 144.59678318808196); 10 | --ds-shadow-simple: 0 4px 8px 0 color(srgb 0 0 0 / 0.15); 11 | } 12 | 13 | @media (color-gamut: p3) { 14 | :root { 15 | --ds-shadow-neon: 0 0 0 0.5rem oklch(0.7831365401896191 0.34000664092427674 145.64495037017775); 16 | } 17 | } 18 | 19 | @media (color-gamut: rec2020) { 20 | :root { 21 | --ds-shadow-neon: 0 0 0 0.5rem oklch(0.7765 0.3530000000000003 147.18); 22 | } 23 | } 24 | 25 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-string/tokens.json: -------------------------------------------------------------------------------- 1 | { 2 | "string": { 3 | "test": { 4 | "$type": "string", 5 | "$value": "test" 6 | }, 7 | "test-2": { 8 | "$value": "{string.test}" 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/plugin-css/test/fixtures/type-string/want.css: -------------------------------------------------------------------------------- 1 | /* ------------------------------------------- 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | :root { 6 | --ds-string-test: test; 7 | --ds-string-test-2: var(--ds-string-test); 8 | } 9 | 10 | -------------------------------------------------------------------------------- /packages/plugin-css/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/", "./test/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-css/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | mockReset: true, 6 | testTimeout: 30_000, // Only needed for Windows 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/plugin-js/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/plugin-js/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin-js/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ @terrazzo/plugin-js 2 | 3 | Generate JavaScript, TypeScript, and JSON from DTCG tokens. 4 | 5 | ## Setup 6 | 7 | Requires [Node.js 20 or later](https://nodejs.org). With that installed, run: 8 | 9 | ```sh 10 | npm i -D @terrazzo/cli @terrazzo/plugin-js 11 | ``` 12 | 13 | Add a `terrazzo.config.js` to the root of your project with: 14 | 15 | ```ts 16 | import { defineConfig } from "@terrazzo/cli"; 17 | import js from "@terrazzo/plugin-js"; 18 | 19 | export default defineConfig({ 20 | outDir: "./tokens/", 21 | plugins: [ 22 | js({ 23 | js: "index.js", 24 | // json: "tokens.json", 25 | }), 26 | ], 27 | }); 28 | ``` 29 | 30 | Lastly, run: 31 | 32 | ```sh 33 | npx tz build 34 | ``` 35 | 36 | And you’ll see a `./tokens/index.js` file generated in your project. 37 | 38 | [Full Documentation](https://terrazzo.app/docs/integrations/js) 39 | -------------------------------------------------------------------------------- /packages/plugin-js/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./src/", "./test/"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-js/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/plugin-js", 3 | "version": "0.8.0", 4 | "description": "Generate JS, TS, and JSON from your DTCG design tokens JSON.", 5 | "license": "MIT", 6 | "type": "module", 7 | "author": { 8 | "name": "Drew Powers", 9 | "email": "drew@pow.rs" 10 | }, 11 | "keywords": [ 12 | "design tokens", 13 | "design system", 14 | "dtcg", 15 | "w3c", 16 | "ts", 17 | "typescript" 18 | ], 19 | "main": "./dist/index.js", 20 | "exports": { 21 | ".": "./dist/index.js", 22 | "./package.json": "./package.json" 23 | }, 24 | "homepage": "https://terrazzo.app/docs/cli/integrations/js", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/terrazzoapp/terrazzo.git", 28 | "directory": "./packages/plugin-js/" 29 | }, 30 | "scripts": { 31 | "build": "rolldown -c && attw --profile esm-only --pack .", 32 | "dev": "rolldown -c -w", 33 | "format": "biome check --fix --unsafe .", 34 | "lint": "pnpm --filter @terrazzo/plugin-js run \"/^lint:(js|ts)/\"", 35 | "lint:js": "biome check .", 36 | "lint:ts": "tsc --noEmit", 37 | "test": "vitest run" 38 | }, 39 | "peerDependencies": { 40 | "@terrazzo/cli": "^0.8.0" 41 | }, 42 | "dependencies": { 43 | "@terrazzo/token-tools": "workspace:^" 44 | }, 45 | "devDependencies": { 46 | "@terrazzo/cli": "workspace:^", 47 | "@terrazzo/parser": "workspace:^" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/plugin-js/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | output: { 10 | dir: 'dist', 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/plugin-js/test/shadow/want.d.ts: -------------------------------------------------------------------------------- 1 | /** ------------------------------------------ 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | import type { 6 | ShadowTokenNormalized, 7 | } from "@terrazzo/parser"; 8 | 9 | export declare const tokens: { 10 | "shadow.base": Record<".", ShadowTokenNormalized["$value"]>; 11 | "shadow.simple": Record<".", ShadowTokenNormalized["$value"]>; 12 | "shadow.inset": Record<".", ShadowTokenNormalized["$value"]>; 13 | "shadow.layered": Record<".", ShadowTokenNormalized["$value"]>; 14 | }; 15 | 16 | export declare function token(tokenID: K, modeName?: never): (typeof tokens)[K]["."]; 17 | export declare function token(tokenID: K, modeName: M): (typeof tokens)[K][M]; 18 | -------------------------------------------------------------------------------- /packages/plugin-js/test/typography/want.d.ts: -------------------------------------------------------------------------------- 1 | /** ------------------------------------------ 2 | * Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 3 | * ------------------------------------------- */ 4 | 5 | import type { 6 | FontFamilyTokenNormalized, 7 | TypographyTokenNormalized, 8 | } from "@terrazzo/parser"; 9 | 10 | export declare const tokens: { 11 | "typography.family.body": Record<".", FontFamilyTokenNormalized["$value"]>; 12 | "typography.family.heading": Record<".", FontFamilyTokenNormalized["$value"]>; 13 | "typography.largeTitle": Record<"." | "xs" | "s" | "m" | "l" | "xl" | "2xl" | "3xl", TypographyTokenNormalized["$value"]>; 14 | "typography.body": Record<"." | "xs" | "s" | "m" | "l" | "xl" | "2xl" | "3xl", TypographyTokenNormalized["$value"]>; 15 | }; 16 | 17 | export declare function token(tokenID: K, modeName?: never): (typeof tokens)[K]["."]; 18 | export declare function token(tokenID: K, modeName: M): (typeof tokens)[K][M]; 19 | -------------------------------------------------------------------------------- /packages/plugin-js/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/", "./test/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-js/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import os from 'node:os'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | testTimeout: os.platform() === 'win32' ? 10000 : 5000, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/plugin-sass/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/plugin-sass/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin-sass/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ @terrazzo/plugin-sass 2 | 3 | Convert DTCG tokens into Sass for use in any web application or native app with webview. Uses [the CSS plugin](/docs/integrations/css) under the hood that lets you use all of CSS’ features with the typesafety of Sass. 4 | 5 | ## Setup 6 | 7 | Requires [Node.js 20 or later](https://nodejs.org) and [the CLI installed](https://terrazzo.app/docs/cli). With both installed, run: 8 | 9 | ```sh 10 | npm i -D @terrazzo/plugin-css @terrazzo/plugin-sass 11 | ``` 12 | 13 | Add a `terrazzo.config.js` to the root of your project: 14 | 15 | ```ts 16 | import { defineConfig } from "@terrazzo/cli"; 17 | import css from "@terrazzo/plugin-css"; 18 | import sass from "@terrazzo/plugin-sass"; 19 | 20 | export default defineConfig({ 21 | outDir: "./tokens/", 22 | plugins: [ 23 | css({ 24 | filename: "tokens.css", 25 | }), 26 | sass({ 27 | filename: "index.sass", 28 | }), 29 | ], 30 | }); 31 | ``` 32 | 33 | Lastly, run: 34 | 35 | ```sh 36 | npx tz build 37 | ``` 38 | 39 | And you’ll see a `./tokens/index.scss` file generated in your project. 40 | 41 | [Full Documentation](https://terrazzo.app/docs/integrations/sass) 42 | -------------------------------------------------------------------------------- /packages/plugin-sass/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./src/", "./test/"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-sass/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/plugin-sass", 3 | "version": "0.8.0", 4 | "description": "Generate .scss from your DTCG design tokens JSON.", 5 | "license": "MIT", 6 | "type": "module", 7 | "author": { 8 | "name": "Drew Powers", 9 | "email": "drew@pow.rs" 10 | }, 11 | "keywords": [ 12 | "design tokens", 13 | "dtcg", 14 | "w3c", 15 | "css", 16 | "sass" 17 | ], 18 | "main": "./dist/index.js", 19 | "exports": { 20 | ".": "./dist/index.js", 21 | "./package.json": "./package.json" 22 | }, 23 | "homepage": "https://terrazzo.app/docs/cli/integrations/sass", 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/terrazzoapp/terrazzo.git", 27 | "directory": "packages/plugin-sass" 28 | }, 29 | "scripts": { 30 | "build": "rolldown -c && attw --profile esm-only --pack .", 31 | "dev": "rolldown -c -w", 32 | "format": "biome check --fix --unsafe .", 33 | "lint": "pnpm --filter @terrazzo/plugin-sass run \"/^lint:(js|ts)/\"", 34 | "lint:js": "biome check .", 35 | "lint:ts": "tsc --noEmit", 36 | "test": "vitest run" 37 | }, 38 | "peerDependencies": { 39 | "@terrazzo/cli": "^0.8.0", 40 | "@terrazzo/plugin-css": "^0.8.0" 41 | }, 42 | "dependencies": { 43 | "@terrazzo/token-tools": "workspace:^" 44 | }, 45 | "devDependencies": { 46 | "@terrazzo/cli": "workspace:^", 47 | "@terrazzo/parser": "workspace:^", 48 | "@terrazzo/plugin-css": "workspace:^" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /packages/plugin-sass/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | output: { 10 | dir: 'dist', 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/plugin-sass/src/index.ts: -------------------------------------------------------------------------------- 1 | import type { Plugin } from '@terrazzo/parser'; 2 | import build from './build.js'; 3 | import type { SassPluginOptions } from './lib.js'; 4 | 5 | export * from './lib.js'; 6 | 7 | export default function pluginSass(options?: SassPluginOptions): Plugin { 8 | const filename = options?.filename ?? 'index.scss'; 9 | 10 | return { 11 | name: '@terrazzo/plugin-sass', 12 | enforce: 'post', 13 | config(config) { 14 | // plugin-css is required for transforms. throw error 15 | if (!config.plugins.some((p) => p.name === '@terrazzo/plugin-css')) { 16 | throw new Error( 17 | `@terrazzo/plugin-sass relies on @terrazzo/plugin-css. 18 | Please install @terrazzo/plugin-css and follow setup to add to your config.`, 19 | ); 20 | } 21 | }, 22 | async build({ getTransforms, outputFile }) { 23 | const output = build({ getTransforms, options }); 24 | outputFile(filename, output); 25 | }, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /packages/plugin-sass/src/lib.ts: -------------------------------------------------------------------------------- 1 | import type { CSSPluginOptions } from '@terrazzo/plugin-css'; 2 | 3 | export interface SassPluginOptions { 4 | /** Where to output CSS */ 5 | filename?: CSSPluginOptions['filename']; 6 | /** Glob patterns to exclude tokens from output */ 7 | exclude?: CSSPluginOptions['exclude']; 8 | } 9 | 10 | export const FILE_HEADER = `//// 11 | /// Autogenerated by ⛋ Terrazzo. DO NOT EDIT! 12 | //// 13 | 14 | @use "sass:list"; 15 | @use "sass:map";`; 16 | 17 | export const MIXIN_TOKEN = `@function token($tokenName) { 18 | @if map.has-key($__token-values, $tokenName) == false { 19 | @error 'No token named "#{$tokenName}"'; 20 | } 21 | $_token: map.get($__token-values, $tokenName); 22 | @if map.has-key($_token, "__tz-error") { 23 | @error map.get($_token, "__tz-error"); 24 | } 25 | @return map.get($_token); 26 | }`; 27 | 28 | export const MIXIN_TYPOGRAPHY = `@mixin typography($tokenName, $modeName: ".") { 29 | @if map.has-key($__token-typography-mixins, $tokenName) == false { 30 | @error 'No typography mixin named "#{$tokenName}"'; 31 | } 32 | $_mixin: map.get($__token-typography-mixins, $tokenName); 33 | $_properties: map.get($_mixin, "."); 34 | @if map.has-key($_mixin) { 35 | $_properties: map.get($_mixin); 36 | } 37 | @each $_property, $_value in $_properties { 38 | #{$_property}: #{$_value}; 39 | } 40 | }`; 41 | -------------------------------------------------------------------------------- /packages/plugin-sass/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/", "./test/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-sass/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import os from 'node:os'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | testTimeout: os.platform() === 'win32' ? 10000 : 5000, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/plugin-swift/.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | -------------------------------------------------------------------------------- /packages/plugin-swift/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/plugin-swift/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin-swift/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ @terrazzo/plugin-swift 2 | 3 | Generate Swift code from DTCG tokens. 4 | 5 | ## Setup 6 | 7 | Requires [Node.js 20 or later](https://nodejs.org). With that installed, and a `package.json`, run: 8 | 9 | ```sh 10 | npm i -D @terrazzo/cli @terrazzo/plugin-swift 11 | ``` 12 | 13 | Add a `terrazzo.config.js` to the root of your project with: 14 | 15 | ```ts 16 | import { defineConfig } from "@terrazzo/cli"; 17 | import swift from "@terrazzo/plugin-swift"; 18 | 19 | export default defineConfig({ 20 | outDir: "./tokens/", 21 | plugins: [ 22 | swift({ 23 | catalogName: "Tokens", 24 | }), 25 | ], 26 | }); 27 | ``` 28 | 29 | Lastly, run: 30 | 31 | ```sh 32 | npx tz build 33 | ``` 34 | 35 | And you’ll see a `./tokens/Tokens.xcassets` catalog generated in your project. [Import it](https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs) into your project and you’ll have access to all your tokens! 36 | 37 | [Full Documentation](https://terrazzo.app/docs/integrations/swift) 38 | -------------------------------------------------------------------------------- /packages/plugin-swift/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/plugin-swift/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/plugin-swift", 3 | "version": "0.1.0", 4 | "description": "Generate Swift code using DTCG design tokens JSON.", 5 | "license": "MIT", 6 | "type": "module", 7 | "author": { 8 | "name": "Drew Powers", 9 | "email": "drew@pow.rs" 10 | }, 11 | "keywords": [ 12 | "design tokens", 13 | "dtcg", 14 | "dtfm", 15 | "w3c", 16 | "swift", 17 | "ios" 18 | ], 19 | "main": "./dist/index.js", 20 | "exports": { 21 | ".": "./dist/index.js", 22 | "./package.json": "./package.json" 23 | }, 24 | "homepage": "https://terrazzo.app/docs/cli/integrations/swift", 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/terrazzoapp/terrazzo.git", 28 | "directory": "./packages/plugin-swift/" 29 | }, 30 | "scripts": { 31 | "build": "rolldown -c && attw --profile esm-only --pack .", 32 | "dev": "rolldown -c -w", 33 | "format": "biome check --fix --unsafe .", 34 | "lint": "biome check .", 35 | "test": "vitest run" 36 | }, 37 | "peerDependencies": { 38 | "@terrazzo/cli": "^0.8.0" 39 | }, 40 | "dependencies": { 41 | "@terrazzo/token-tools": "workspace:^", 42 | "culori": "^4.0.1" 43 | }, 44 | "devDependencies": { 45 | "@terrazzo/cli": "workspace:*", 46 | "@terrazzo/parser": "workspace:^", 47 | "@types/culori": "^4.0.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/plugin-swift/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | output: { 10 | dir: 'dist', 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.1.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.9857122261910846", 8 | "green": "0.9918978348582416", 9 | "blue": "0.9991694919090187", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.05746783530090841", 26 | "green": "0.08146881860449817", 27 | "blue": "0.12189566404846394", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.10.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.23468314487818098", 8 | "green": "0.525167106120089", 9 | "blue": "0.9119403311366554", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.3435321435041201", 26 | "green": "0.6114011043023767", 27 | "blue": "0.9713324919373404", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.11.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.2038490640365916", 8 | "green": "0.44794576348361775", 9 | "blue": "0.7825620608925641", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.5046395218152211", 26 | "green": "0.7144126403383976", 27 | "blue": "0.9767803153140814", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.12.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.10160032180994383", 8 | "green": "0.1931928893247831", 9 | "blue": "0.3787199032361936", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.7883005117642531", 26 | "green": "0.8977356928858071", 27 | "blue": "0.9898314408312648", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.2.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.9610959140132492", 8 | "green": "0.9796231037551396", 9 | "blue": "0.9978799613348245", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.07303985547217764", 26 | "green": "0.09713934555099096", 27 | "blue": "0.14853779817781101", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.3.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.9120281437931872", 8 | "green": "0.9551068985440067", 9 | "blue": "0.9917926478801694", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.07908044574694958", 26 | "green": "0.15446938825505227", 27 | "blue": "0.26936679657969886", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.4.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.85457584989346", 8 | "green": "0.9341034497224382", 9 | "blue": "0.9931007763338738", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.06909232748693128", 26 | "green": "0.19643835900273826", 27 | "blue": "0.37125592617605824", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.5.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.787471272606805", 8 | "green": "0.8939209890163644", 9 | "blue": "0.9895810628966571", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.09390125109062433", 26 | "green": "0.24670669682719953", 27 | "blue": "0.4401071689219961", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.6.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.7092079991743006", 8 | "green": "0.8420585712777626", 9 | "blue": "0.9745609882360672", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.14022516582440694", 26 | "green": "0.2973263472969356", 27 | "blue": "0.5127819131506204", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.7.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.605510599682131", 8 | "green": "0.7781097681313138", 9 | "blue": "0.9479029174691492", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.19415626909440487", 26 | "green": "0.3596239398578181", 27 | "blue": "0.6008085975749465", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.8.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.4493472380669719", 8 | "green": "0.6863379927169785", 9 | "blue": "0.9161077085265298", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.23803842782286488", 26 | "green": "0.43322463113208826", 27 | "blue": "0.71894276694965", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/color/Want.xcassets/color.primitive.blue.9.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors": [ 3 | { 4 | "color": { 5 | "color-space": "display-p3", 6 | "components": { 7 | "red": "0.2465715501902695", 8 | "green": "0.5560503142085003", 9 | "blue": "0.9689026731386735", 10 | "alpha": "1" 11 | } 12 | }, 13 | "idiom": "universal" 14 | }, 15 | { 16 | "appearances": [ 17 | { 18 | "appearance": "luminosity", 19 | "value": "dark" 20 | } 21 | ], 22 | "color": { 23 | "color-space": "display-p3", 24 | "components": { 25 | "red": "0.2465715501902695", 26 | "green": "0.5560503142085003", 27 | "blue": "0.9689026731386735", 28 | "alpha": "1" 29 | } 30 | }, 31 | "idiom": "universal" 32 | } 33 | ], 34 | "info": { 35 | "author": "Terrazzo", 36 | "version": 1 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/plugin-swift/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { build, defineConfig, parse } from '@terrazzo/parser'; 4 | import { describe, expect, it } from 'vitest'; 5 | import swift from '../src/index.js'; 6 | 7 | describe('@terrazzo/plugin-swift', () => { 8 | it('basic', async () => { 9 | const cwd = new URL('./color/', import.meta.url); 10 | const config = defineConfig( 11 | { 12 | outDir: 'color', 13 | plugins: [ 14 | swift({ 15 | catalogName: 'Want', 16 | }), 17 | ], 18 | }, 19 | { cwd }, 20 | ); 21 | const tokensJSON = new URL('./tokens.json', cwd); 22 | const { tokens, sources } = await parse([{ filename: tokensJSON, src: fs.readFileSync(tokensJSON, 'utf8') }], { 23 | config, 24 | }); 25 | const result = await build(tokens, { config, sources }); 26 | for (const { filename, contents } of result.outputFiles) { 27 | await expect(contents).toMatchFileSnapshot(fileURLToPath(new URL(filename, cwd))); 28 | } 29 | }); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/plugin-swift/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/", "./test/**/*.test.ts"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/.gitignore: -------------------------------------------------------------------------------- 1 | test/fixtures/**/actual.css 2 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./src/", "./test/"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | }, 8 | plugins: [dts()], 9 | output: { 10 | dir: 'dist', 11 | format: 'es', 12 | sourcemap: true, 13 | }, 14 | }); 15 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/src/lib.ts: -------------------------------------------------------------------------------- 1 | export interface TailwindPluginOptions { 2 | /** 3 | * Filename to output. 4 | * @default "tailwind-theme.css" 5 | */ 6 | filename?: string; 7 | /** @see https://tailwindcss.com/docs/theme */ 8 | theme: Record; 9 | /** Array of mapping variants to DTCG modes. */ 10 | modeVariants?: { variant: string; mode: string }[]; 11 | } 12 | 13 | /** Flatten an arbitrarily-nested object */ 14 | export function flattenThemeObj(themeObj: Record): { path: string[]; value: string | string[] }[] { 15 | const result: { path: string[]; value: string | string[] }[] = []; 16 | 17 | function traverse(obj: Record, path: string[]) { 18 | for (const [key, value] of Object.entries(obj)) { 19 | const newPath = [...path, key]; 20 | if (typeof value === 'string' || Array.isArray(value)) { 21 | if (Array.isArray(value) && (value.length === 0 || value.some((v) => typeof v !== 'string'))) { 22 | throw new Error( 23 | `Invalid value at path "${newPath.join('.')}": expected a string or an array of strings, but got ${JSON.stringify(value)}`, 24 | ); 25 | } 26 | result.push({ path: newPath, value }); 27 | } else if (typeof value === 'object' && value !== null) { 28 | traverse(value as Record, newPath); 29 | } 30 | } 31 | } 32 | 33 | traverse(themeObj, []); 34 | return result; 35 | } 36 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/test/cli.test.ts: -------------------------------------------------------------------------------- 1 | import fs from 'node:fs'; 2 | import { fileURLToPath } from 'node:url'; 3 | import { defineConfig } from '@terrazzo/parser'; 4 | import { execa } from 'execa'; 5 | import { describe, expect, test } from 'vitest'; 6 | import tailwind from '../src/index.js'; 7 | 8 | const CMD = '../../../../cli/bin/cli.js'; 9 | 10 | describe('CLI', () => { 11 | const fixtures = ['primer']; 12 | 13 | test.each(fixtures)('%s', async (dir) => { 14 | const cwd = new URL(`./fixtures/${dir}/`, import.meta.url); 15 | await execa('node', [CMD, 'build'], { cwd, stdout: 'inherit' }); 16 | await expect(fs.readFileSync(new URL('./actual.css', cwd), 'utf8')).toMatchFileSnapshot( 17 | fileURLToPath(new URL('./want.css', cwd)), 18 | ); 19 | }); 20 | 21 | describe('errors', () => { 22 | test('plugin-css missing', async () => { 23 | const cwd = new URL('./fixtures/primer/', import.meta.url); 24 | expect(() => defineConfig({ plugins: [tailwind({ theme: {} })] }, { cwd })).toThrowError( 25 | '@terrazzo/plugin-css missing! Please install and add to the plugins array to use the Tailwind plugin.', 26 | ); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/", "./test/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/plugin-tailwind/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | import os from 'node:os'; 3 | 4 | export default defineConfig({ 5 | test: { 6 | testTimeout: os.platform() === 'win32' ? 10000 : 5000, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /packages/react-color-picker/.npmignore: -------------------------------------------------------------------------------- 1 | *.test.* 2 | .size-limit.js 3 | .turbo 4 | biome.* 5 | rollup.* 6 | src/** 7 | stylelint.* 8 | vite.* 9 | vitest.* 10 | tsconfig.* 11 | -------------------------------------------------------------------------------- /packages/react-color-picker/.size-limit.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | ignore: ['@use-gesture/react', '@terrazzo/icons', '@terrazzo/tiles', '@terrazzo/use-color', 'culori', 'react'], 4 | path: './dist/index.js', 5 | limit: '15 kB', 6 | }, 7 | ]; 8 | -------------------------------------------------------------------------------- /packages/react-color-picker/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/react-color-picker 2 | 3 | ## 0.0.7 4 | 5 | ### Patch Changes 6 | 7 | - Bugfixes thanks to [@lilnasy](lilnasy) 8 | -------------------------------------------------------------------------------- /packages/react-color-picker/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/react-color-picker/README.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/react-color-picker 2 | 3 | Pick colors using CSS Color Module 4, wide color gamut (WCG), and all supported web colorspaces using React and WebGL for monitor-accurate colors. Powered by 🌈 [Culori](https://culorijs.org/). 4 | 5 | `2 kB`, enforced by [size-limit](https://www.npmjs.com/package/size-limit) 6 | 7 | ## Setup 8 | 9 | ```sh 10 | pnpm i @terrazzo/react-color-picker 11 | ``` 12 | 13 | ```tsx 14 | import ColorPicker from '@terrazzo/react-color-picker'; 15 | import { useState } from 'react'; 16 | 17 | const [color, setColor] = useState('color(display-p3 0 0.3 1)'); 18 | 19 | ; 20 | ``` 21 | -------------------------------------------------------------------------------- /packages/react-color-picker/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "linter": { 5 | "rules": { 6 | "correctness": { 7 | "useHookAtTopLevel": "off" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/react-color-picker/rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from '@rollup/plugin-typescript'; 2 | import css from 'rollup-plugin-import-css'; 3 | 4 | /** @type {import("rollup").InputOptions} */ 5 | export default { 6 | plugins: [ 7 | css({ 8 | output: 'all-components.css', 9 | }), 10 | ts({ 11 | tsconfig: './tsconfig.build.json', 12 | }), 13 | ], 14 | input: 'src/index.ts', 15 | external: ['*'], 16 | output: { 17 | dir: './dist/', 18 | sourcemap: true, 19 | globals: { 20 | 'react/jsx-runtime': 'jsxRuntime', 21 | 'react-dom/client': 'ReactDOM', 22 | react: 'React', 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/react-color-picker/src/components/HueWheel.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, useEffect, useRef, useState } from 'react'; 2 | import { HueWheel as HueWheelWebGL } from '../lib/webgl.js'; 3 | 4 | export type HueWheelProps = ComponentProps<'canvas'>; 5 | 6 | function HueWheel({ ...rest }: HueWheelProps) { 7 | const canvasEl = useRef(null); 8 | const [webgl, setWebgl] = useState(); 9 | 10 | // initialize 11 | useEffect(() => { 12 | if (webgl || !canvasEl.current) { 13 | return; 14 | } 15 | setWebgl(new HueWheelWebGL({ canvas: canvasEl.current })); 16 | }, [webgl]); 17 | 18 | return ; 19 | } 20 | 21 | export default HueWheel; 22 | -------------------------------------------------------------------------------- /packages/react-color-picker/src/components/TrueGradient.tsx: -------------------------------------------------------------------------------- 1 | import type { Oklab } from '@terrazzo/use-color'; 2 | import { type ComponentProps, useEffect, useRef, useState } from 'react'; 3 | import { GradientOklab } from '../lib/webgl.js'; 4 | 5 | export interface TrueGradientProps extends ComponentProps<'canvas'> { 6 | start: Oklab; 7 | end: Oklab; 8 | } 9 | 10 | function TrueGradient({ start, end, ...props }: TrueGradientProps) { 11 | const canvasEl = useRef(null); 12 | const [webgl, setWebgl] = useState(); 13 | 14 | // initialize 15 | useEffect(() => { 16 | if (webgl || !canvasEl.current) { 17 | return; 18 | } 19 | setWebgl(new GradientOklab({ canvas: canvasEl.current, startColor: start, endColor: end })); 20 | }, [webgl]); 21 | 22 | // update color 23 | useEffect(() => { 24 | if (webgl) { 25 | webgl.setColors(start, end); 26 | } 27 | }, [start, end, webgl]); 28 | 29 | return ; 30 | } 31 | 32 | export default TrueGradient; 33 | -------------------------------------------------------------------------------- /packages/react-color-picker/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './components/ColorChannelSlider.js'; 2 | export { default as ColorChannelSlider } from './components/ColorChannelSlider.js'; 3 | export * from './components/ColorPicker.js'; 4 | export { default } from './components/ColorPicker.js'; 5 | export * from './components/HueWheel.js'; 6 | export { default as HueWheel } from './components/HueWheel.js'; 7 | export * from './components/TrueGradient.js'; 8 | export { default as TrueGradient } from './components/TrueGradient.js'; 9 | export * from './lib/color.js'; 10 | export * from './lib/oklab.js'; 11 | export * from './lib/rgb.js'; 12 | export * from './lib/webgl.js'; 13 | -------------------------------------------------------------------------------- /packages/react-color-picker/src/lib/rgb.ts: -------------------------------------------------------------------------------- 1 | /** Generic implementation of the sRGB transfer function */ 2 | export const LINEAR_RGB = `float srgb_transfer_fn(float a) { 3 | float v = abs(a); 4 | return v <= 0.0031308 ? 12.92 * v : 1.055 * pow(v, (1.0 / 2.4)) - 0.055; 5 | } 6 | 7 | float srgb_inverse_transfer_fn(float a) { 8 | float v = abs(a); 9 | return v <= 0.04045 ? v / 12.92 : pow((v + 0.055) / 1.055, 2.4); 10 | } 11 | 12 | vec4 srgb_to_linear_rgb(vec4 srgb) { 13 | return vec4(srgb_inverse_transfer_fn(srgb.x), srgb_inverse_transfer_fn(srgb.y), srgb_inverse_transfer_fn(srgb.z), srgb.w); 14 | } 15 | 16 | vec4 linear_rgb_to_srgb(vec4 linear_rgb) { 17 | return vec4(srgb_transfer_fn(linear_rgb.x), srgb_transfer_fn(linear_rgb.y), srgb_transfer_fn(linear_rgb.z), linear_rgb.w); 18 | } 19 | 20 | // Blend 2 vec4 colors together 21 | vec4 avg_vec4(vec4 a, vec4 b, float w) { 22 | float _w = 1.0 - w; 23 | return (a * _w) + (b * w); 24 | } 25 | 26 | vec4 blend_srgb(vec4 a, vec4 b, float w) { 27 | vec4 lrgb_a = srgb_to_linear_rgb(a); 28 | vec4 lrgb_b = srgb_to_linear_rgb(b); 29 | vec4 blended = linear_rgb_to_srgb(avg_vec4(a, b, w)); 30 | blended.w = 1.0; 31 | return blended; 32 | } 33 | `; 34 | -------------------------------------------------------------------------------- /packages/react-color-picker/src/types.d.ts: -------------------------------------------------------------------------------- 1 | import 'react'; 2 | 3 | declare module 'react' { 4 | interface CSSProperties { 5 | [key: `--${string}`]: string | number | undefined; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/react-color-picker/stylelint.config.js: -------------------------------------------------------------------------------- 1 | import base from '../../stylelint.config.js'; 2 | 3 | /** @type {import('stylelint').Config} */ 4 | export default { 5 | ...base, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/react-color-picker/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "nodenext" 6 | }, 7 | "exclude": ["**/__test__/**", "**/*.test.*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-color-picker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/react-color-picker/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | test: { 7 | environment: 'jsdom', 8 | environmentOptions: { 9 | jsdom: { 10 | resources: 'usable', 11 | }, 12 | }, 13 | poolOptions: { 14 | threads: { 15 | minThreads: 0, 16 | maxThreads: 1, 17 | }, 18 | }, 19 | restoreMocks: true, 20 | setupFiles: ['./vitest.setup.ts'], 21 | testTimeout: 15000, 22 | }, 23 | }); 24 | -------------------------------------------------------------------------------- /packages/storybook/.gitignore: -------------------------------------------------------------------------------- 1 | storybook/** 2 | -------------------------------------------------------------------------------- /packages/storybook/.storybook/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: var(--tz-font-sans); 3 | font-size: var(--tz-font-body-font-size); 4 | font-variation-settings: var(--tz-font-body-font-variation-settings); 5 | font-weight: var(--tz-font-body-font-weight); 6 | line-height: 1; 7 | margin: 0; 8 | } 9 | 10 | *, 11 | *::before, 12 | *::after { 13 | box-sizing: border-box; 14 | } 15 | 16 | .sb-show-main { 17 | background: color(srgb 1 1 1); 18 | } 19 | 20 | @media (prefers-color-scheme: dark) { 21 | .sb-show-main { 22 | background: color(srgb 0 0 0); 23 | color: color(srgb 1 1 1); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/storybook/.storybook/main.js: -------------------------------------------------------------------------------- 1 | import path from 'node:path'; 2 | 3 | /** @type { import('@storybook/react-vite').StorybookConfig } */ 4 | const config = { 5 | stories: ['../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], 6 | addons: [getAbsolutePath('@storybook/addon-essentials')], 7 | 8 | framework: { 9 | name: getAbsolutePath('@storybook/react-vite'), 10 | options: {}, 11 | disableTelemetry: true, 12 | }, 13 | 14 | docs: { 15 | autodocs: false, 16 | docsMode: false, 17 | }, 18 | 19 | typescript: { 20 | reactDocgen: 'react-docgen-typescript', 21 | }, 22 | }; 23 | export default config; 24 | 25 | function getAbsolutePath(value) { 26 | return path.dirname(require.resolve(path.join(value, 'package.json'))); 27 | } 28 | -------------------------------------------------------------------------------- /packages/storybook/.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import '@terrazzo/tokens/dist/index.css'; 2 | import '@terrazzo/fonts/fragment-mono.css'; 3 | import '@terrazzo/fonts/instrument-sans.css'; 4 | import '@terrazzo/tiles/dist/all-components.css'; 5 | import '@terrazzo/react-color-picker/dist/all-components.css'; 6 | import './global.css'; 7 | 8 | /** @type { import('@storybook/react').Preview } */ 9 | const preview = { 10 | parameters: { 11 | controls: { 12 | matchers: { 13 | color: /(background|color)$/i, 14 | date: /Date$/i, 15 | }, 16 | }, 17 | }, 18 | }; 19 | 20 | export default preview; 21 | -------------------------------------------------------------------------------- /packages/storybook/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/storybook/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ @terrazzo/storybook 2 | 3 | Internal package. 4 | 5 | ## Setup 6 | 7 | ```sh 8 | pnpm i 9 | pnpm run sb 10 | ``` 11 | -------------------------------------------------------------------------------- /packages/storybook/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/storybook/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/storybook", 3 | "version": "0.0.0", 4 | "private": true, 5 | "license": "MIT", 6 | "type": "module", 7 | "scripts": { 8 | "dev": "pnpm run sb", 9 | "build:app": "storybook build -o storybook", 10 | "format": "biome check --fix --unsafe src", 11 | "lint": "pnpm run lint:js && pnpm run lint:css", 12 | "lint:js": "biome check src", 13 | "lint:css": "stylelint \"src/**/*.css\"", 14 | "sb": "storybook dev -p 9009" 15 | }, 16 | "dependencies": { 17 | "culori": "^4.0.1" 18 | }, 19 | "devDependencies": { 20 | "@storybook/addon-essentials": "^8.6.12", 21 | "@storybook/react": "^8.6.12", 22 | "@storybook/react-vite": "^8.6.12", 23 | "@storybook/test": "^8.6.12", 24 | "@terrazzo/fonts": "workspace:^", 25 | "@terrazzo/icons": "workspace:^", 26 | "@terrazzo/react-color-picker": "workspace:^", 27 | "@terrazzo/tiles": "workspace:^", 28 | "@terrazzo/tokens": "workspace:^", 29 | "@terrazzo/use-color": "workspace:^", 30 | "@types/culori": "^4.0.0", 31 | "@types/react": "^19.1.3", 32 | "@types/react-dom": "^19.1.3", 33 | "@vitejs/plugin-react-swc": "^3.9.0", 34 | "react": "19.0.0", 35 | "react-dom": "19.0.0", 36 | "storybook": "^8.6.12", 37 | "vite": "^6.3.5" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /packages/storybook/src/ColorPicker.stories.jsx: -------------------------------------------------------------------------------- 1 | import ColorPicker from '@terrazzo/react-color-picker'; 2 | import useColor from '@terrazzo/use-color'; 3 | 4 | export default { 5 | title: 'Components/Form/ColorPicker', 6 | component: ColorPicker, 7 | parameters: { 8 | layout: 'centered', 9 | }, 10 | }; 11 | 12 | export const Overview = { 13 | args: {}, 14 | render(args) { 15 | const [color, setColor] = useColor('rgb(33% 33% 100%/1)'); 16 | return ; 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/storybook/src/Icon.stories.jsx: -------------------------------------------------------------------------------- 1 | import * as allIcons from '@terrazzo/icons'; 2 | 3 | export default { 4 | title: 'Icons', 5 | }; 6 | 7 | export const Icons = { 8 | render() { 9 | return ( 10 |
20 | {Object.entries(allIcons).map(([name, Icon]) => ( 21 |
32 | 33 | {name} 34 |
35 | ))} 36 |
37 | ); 38 | }, 39 | }; 40 | -------------------------------------------------------------------------------- /packages/storybook/src/Kbd.stories.jsx: -------------------------------------------------------------------------------- 1 | import { Kbd } from '@terrazzo/tiles'; 2 | 3 | /** @type {import("@storybook/react").Meta} */ 4 | export default { 5 | title: 'Components/Display/Kbd', 6 | component: Kbd, 7 | parameters: { 8 | layout: 'centered', 9 | }, 10 | }; 11 | 12 | export const Overview = { 13 | render() { 14 | return F; 15 | }, 16 | }; 17 | -------------------------------------------------------------------------------- /packages/storybook/src/OmniBar.stories.jsx: -------------------------------------------------------------------------------- 1 | import { OmniBar, OmniBarResult } from '@terrazzo/tiles'; 2 | 3 | export default { 4 | title: 'Components/Form/OmniBar', 5 | component: OmniBar, 6 | parameters: { 7 | // layout: 'centered', 8 | }, 9 | }; 10 | 11 | export const Overview = { 12 | render() { 13 | return ( 14 | Results 1–3 of 3.}> 15 | 16 |

Search

17 |

Result blah blah blah

18 |
19 | 20 |

Search

21 |

Result blah blah blah

22 |
23 | 24 |

Search

25 |

Result blah blah blah

26 |
27 |
28 | ); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /packages/storybook/src/Select.stories.jsx: -------------------------------------------------------------------------------- 1 | import { ColorFilterOutline } from '@terrazzo/icons'; 2 | import { Select, SelectGroup, SelectItem } from '@terrazzo/tiles'; 3 | 4 | export default { 5 | title: 'Components/Form/Select', 6 | component: Select, 7 | parameters: { 8 | layout: 'centered', 9 | }, 10 | }; 11 | 12 | export const Overview = { 13 | render() { 14 | return ( 15 | 35 | ); 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /packages/storybook/src/Slider.stories.jsx: -------------------------------------------------------------------------------- 1 | import { Slider } from '@terrazzo/tiles'; 2 | import { useState } from 'react'; 3 | 4 | export default { 5 | title: 'Components/Form/Slider', 6 | component: Slider, 7 | parameters: { 8 | layout: 'centered', 9 | }, 10 | }; 11 | 12 | export const Overview = { 13 | args: { 14 | label: 'My Slider', 15 | orientation: 'horizontal', 16 | min: 0, 17 | max: 100, 18 | step: 1, 19 | defaultValue: 0, 20 | }, 21 | argTypes: { 22 | orientation: { 23 | control: 'select', 24 | options: ['horizontal', 'vertical'], 25 | }, 26 | }, 27 | render({ defaultValue, ...args }) { 28 | const [value, setValue] = useState(defaultValue); 29 | 30 | return ( 31 |
32 | 33 |
34 | ); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/storybook/src/SubtleInput.stories.jsx: -------------------------------------------------------------------------------- 1 | import { SubtleInput } from '@terrazzo/tiles'; 2 | 3 | export default { 4 | title: 'Components/Form/SubtleInput', 5 | component: SubtleInput, 6 | parameters: { 7 | layout: 'centered', 8 | }, 9 | }; 10 | 11 | export const Overview = { 12 | args: { 13 | type: 'number', 14 | defaultValue: 23.45, 15 | }, 16 | render(args) { 17 | return ; 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /packages/storybook/src/Switch.stories.jsx: -------------------------------------------------------------------------------- 1 | import { Switch } from '@terrazzo/tiles'; 2 | 3 | export default { 4 | title: 'Components/Form/Switch', 5 | component: Switch, 6 | parameters: { 7 | layout: 'centered', 8 | }, 9 | }; 10 | 11 | export const Overview = { 12 | args: { 13 | label: 'Rec 2020', 14 | }, 15 | render(args) { 16 | return ; 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /packages/storybook/src/TokenType.stories.jsx: -------------------------------------------------------------------------------- 1 | import { TokenType } from '@terrazzo/tiles'; 2 | 3 | export default { 4 | title: 'Components/Display/TokenType', 5 | component: TokenType, 6 | parameters: { 7 | layout: 'centered', 8 | }, 9 | }; 10 | 11 | export const Overview = { 12 | render(args) { 13 | return ( 14 |
22 | {[ 23 | 'color', 24 | 'dimension', 25 | 'fontFamily', 26 | 'fontWeight', 27 | 'duration', 28 | 'cubicBezier', 29 | 'number', 30 | 'link', 31 | 'strokeStyle', 32 | 'border', 33 | 'transition', 34 | 'shadow', 35 | 'gradient', 36 | 'typography', 37 | ].map((type) => ( 38 | 39 | ))} 40 |
41 | ); 42 | }, 43 | }; 44 | -------------------------------------------------------------------------------- /packages/storybook/src/TrueGradient.stories.jsx: -------------------------------------------------------------------------------- 1 | import { TrueGradient } from '@terrazzo/react-color-picker'; 2 | import useColor from '@terrazzo/use-color'; 3 | 4 | export default { 5 | title: 'Components/Display/TrueGradient', 6 | component: TrueGradient, 7 | parameters: { 8 | layout: 'centered', 9 | }, 10 | }; 11 | 12 | export const Overview = { 13 | args: { 14 | start: 'color(srgb 1 0 0)', 15 | end: 'color(srgb 0 1 0)', 16 | }, 17 | render(args) { 18 | const [start] = useColor(args.start); 19 | const [end] = useColor(args.end); 20 | 21 | return ( 22 |
23 | Good 24 | 25 | Bad 26 |
33 |
34 | ); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /packages/storybook/src/components/StickerSheet.module.css: -------------------------------------------------------------------------------- 1 | .wrapper { 2 | border-collapse: collapse; 3 | } 4 | 5 | .td { 6 | padding: var(--tz-space-300) var(--tz-space-200); 7 | text-align: center; 8 | } 9 | 10 | .th { 11 | color: var(--tz-text-secondary); 12 | font-family: var(--tz-font-mono); 13 | font-size: var(--tz-font-label-small-font-size); 14 | font-weight: var(--tz-font-label-small-font-weight); 15 | letter-spacing: var(--tz-font-label-small-letter-spacing); 16 | line-height: var(--tz-font-label-small-line-height); 17 | padding: var(--tz-space-100) var(--tz-space-200); 18 | 19 | &[scope="row"] { 20 | text-align: left; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /packages/storybook/src/components/StickerSheet.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement } from 'react'; 2 | import c from './StickerSheet.module.css'; 3 | 4 | export interface StickerSheetProps { 5 | columns: string[]; 6 | rows: string[]; 7 | variants: { title: string; component: ReactElement }[]; 8 | } 9 | 10 | export default function StickerSheet({ columns, rows, variants }: StickerSheetProps) { 11 | return ( 12 | 13 | 14 | 15 | 16 | {columns.map((col) => ( 17 | 20 | ))} 21 | 22 | 23 | 24 | {rows.map((row, i) => { 25 | const start = i * columns.length; 26 | const rowVariants = variants.slice(start, start + columns.length); 27 | return ( 28 | 29 | 32 | {rowVariants.map((variant, i) => ( 33 | 36 | ))} 37 | 38 | ); 39 | })} 40 | 41 |
  18 | {col} 19 |
30 | {row} 31 | 34 | {variant} 35 |
42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /packages/storybook/stylelint.config.js: -------------------------------------------------------------------------------- 1 | import base from '../../stylelint.config.js'; 2 | 3 | /** @type {import('stylelint').Config} */ 4 | export default { 5 | ...base, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/storybook/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "noEmit": true 6 | }, 7 | "include": ["./src/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/storybook/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc'; 2 | import { defineConfig } from 'vite'; 3 | 4 | export default defineConfig({ 5 | plugins: [react()], 6 | }); 7 | -------------------------------------------------------------------------------- /packages/tiles/.npmignore: -------------------------------------------------------------------------------- 1 | *.test.* 2 | .size-limit.js 3 | .turbo 4 | biome.* 5 | rollup.* 6 | stylelint.* 7 | src/** 8 | tsconfig.* 9 | vite.* 10 | vitest.* 11 | -------------------------------------------------------------------------------- /packages/tiles/.size-limit.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { ignore: ['@use-gesture/react', 'culori', 'react', 'shiki'], path: './dist/index.js', limit: '35 kB' }, 3 | ]; 4 | -------------------------------------------------------------------------------- /packages/tiles/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/tiles 2 | 3 | ## 0.0.7 4 | 5 | ### Patch Changes 6 | 7 | - Bugfixes thanks to [@lilnasy](lilnasy) 8 | 9 | ## 0.0.3 10 | 11 | ### Patch Changes 12 | 13 | - [#2](https://github.com/terrazzoapp/tiles/pull/2) [`0a7e761`](https://github.com/terrazzoapp/tiles/commit/0a7e7617b820b25c421bef71c2d6d832fa803729) Thanks [@drwpow](https://github.com/drwpow)! - Allow buttons to be links 14 | 15 | ## 0.0.2 16 | 17 | ### Patch Changes 18 | 19 | - [#1](https://github.com/terrazzoapp/tiles/pull/1) [`1edb094`](https://github.com/terrazzoapp/tiles/commit/1edb094f01b17f5b43728ba4fb1b15d7b10fa5a9) Thanks [@drwpow](https://github.com/drwpow)! - Fix Svelte package, export types 20 | 21 | ## 0.0.1 22 | 23 | ### Patch Changes 24 | 25 | - [`934d708`](https://github.com/terrazzoapp/tiles/commit/934d7083768bc60bc21d0ac206f184cf9a91ecf4) Thanks [@drwpow](https://github.com/drwpow)! - Add light and dark mode CSS 26 | 27 | - [`934d708`](https://github.com/terrazzoapp/tiles/commit/934d7083768bc60bc21d0ac206f184cf9a91ecf4) Thanks [@drwpow](https://github.com/drwpow)! - Add button colors, improve CodeBlock copy button 28 | 29 | ## 0.0.0 30 | 31 | Initial Release 32 | -------------------------------------------------------------------------------- /packages/tiles/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Drew Powers 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/tiles/README.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/tiles 2 | 3 | Terrazzo’s component library. Sizes enforced by [size-limit](https://www.npmjs.com/package/size-limit) 4 | 5 | ## Setup 6 | 7 | ```sh 8 | npm i @terrazzo/tiles 9 | ``` 10 | 11 | ```tsx 12 | import { Button, Select, SubtleInput, Switch, TokenType, TrueGradient } from '@terrazzo/tiles'; 13 | ``` 14 | -------------------------------------------------------------------------------- /packages/tiles/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "linter": { 5 | "rules": { 6 | "a11y": { 7 | "useFocusableInteractive": "off", 8 | "noNoninteractiveTabindex": "off" 9 | } 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/tiles/rollup.config.js: -------------------------------------------------------------------------------- 1 | import ts from '@rollup/plugin-typescript'; 2 | import css from 'rollup-plugin-import-css'; 3 | 4 | /** @type {import("rollup").InputOptions} */ 5 | export default { 6 | plugins: [ 7 | css({ 8 | output: 'all-components.css', 9 | }), 10 | ts({ 11 | tsconfig: './tsconfig.build.json', 12 | }), 13 | ], 14 | input: 'src/index.ts', 15 | external: ['*'], 16 | output: { 17 | dir: './dist/', 18 | sourcemap: true, 19 | globals: { 20 | 'react/jsx-runtime': 'jsxRuntime', 21 | 'react-dom/client': 'ReactDOM', 22 | react: 'React', 23 | }, 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/tiles/src/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps } from 'react'; 3 | import './Button.css'; 4 | 5 | export interface ButtonProps extends Omit, 'size'> { 6 | /** default: "m" */ 7 | size?: 's' | 'm'; 8 | /** default: "primary" */ 9 | variant?: 'lime' | 'blue' | 'orange' | 'secondary'; 10 | } 11 | 12 | export default function Button({ 13 | className, 14 | children, 15 | size = 'm', 16 | type = 'button', 17 | variant = 'secondary', 18 | ref, 19 | ...rest 20 | }: ButtonProps) { 21 | return ( 22 | 32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /packages/tiles/src/ButtonLink/ButtonLink.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps } from 'react'; 3 | import '../Button/Button.css'; // note: a component should NEVER import another’s styles, but this is a special case 4 | 5 | export interface ButtonLinkProps extends ComponentProps<'a'> { 6 | /** default: "m" */ 7 | size?: 's' | 'm'; 8 | /** default: "primary" */ 9 | variant?: 'lime' | 'blue' | 'orange' | 'secondary' | 'tertiary'; 10 | } 11 | 12 | export default function ButtonLink({ 13 | className, 14 | children, 15 | size = 'm', 16 | variant = 'secondary', 17 | ref, 18 | ...rest 19 | }: ButtonLinkProps) { 20 | return ( 21 | 22 | {children} 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/tiles/src/CopyButton/CopyButton.css: -------------------------------------------------------------------------------- 1 | .tz-copy-button { 2 | --tz-copy-btn-size: 2rem; 3 | 4 | align-items: center; 5 | background-color: transparent; 6 | border: 1px solid var(--tz-border-1); 7 | border-radius: var(--tz-radius-300); 8 | color: var(--tz-icon-secondary); 9 | cursor: pointer; 10 | display: inline-flex; 11 | height: var(--tz-space-control-m); 12 | justify-content: center; 13 | min-width: var(--tz-space-control-m); 14 | outline: 2px solid transparent; 15 | padding: 0; 16 | transition: outline-color var(--tz-timing-default) var(--tz-ease-linear); 17 | width: var(--tz-space-control-m); 18 | z-index: 2; 19 | 20 | &:focus-visible { 21 | outline-color: var(--tz-nav-primary-highlight); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/tiles/src/CopyButton/CopyButton.tsx: -------------------------------------------------------------------------------- 1 | import { Check, Copy } from '@terrazzo/icons'; 2 | import clsx from 'clsx'; 3 | import { type ComponentProps, useRef, useState } from 'react'; 4 | import './CopyButton.css'; 5 | 6 | export interface CopyButtonProps extends Omit, 'children' | 'onClick'> { 7 | /** The text to copy to the clipboard */ 8 | clipboardText: string; 9 | /** 10 | * Amount of time, in milliseconds, to show the checkmark. 11 | * @default: 1000 12 | */ 13 | timeoutMS?: number; 14 | } 15 | 16 | export default function CopyButton({ className, clipboardText, timeoutMS = 1000, ...rest }: CopyButtonProps) { 17 | const [copied, setCopied] = useState(false); 18 | const copiedTO = useRef(undefined); 19 | 20 | return ( 21 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/tiles/src/Demo/Demo.css: -------------------------------------------------------------------------------- 1 | .tz-demo { 2 | display: grid; 3 | } 4 | 5 | .tz-demo-canvas { 6 | padding: var(--tz-space-400); 7 | } 8 | 9 | .tz-demo-code { 10 | contain: inline-size; 11 | } 12 | 13 | .tz-demo-code-copy-wrapper { 14 | margin: 0; 15 | padding: 0; 16 | position: absolute; 17 | right: var(--tz-space-300); 18 | top: var(--tz-space-200); 19 | z-index: var(--tz-layer-nav); 20 | } 21 | 22 | .tz-demo-code-icon { 23 | opacity: 0.75; 24 | } 25 | 26 | .tz-demo-code-overflow { 27 | overflow-x: auto; 28 | } 29 | -------------------------------------------------------------------------------- /packages/tiles/src/Demo/Demo.tsx: -------------------------------------------------------------------------------- 1 | import { type ComponentProps, useEffect, useState } from 'react'; 2 | import type { CodeOptionsSingleTheme, CodeToHastOptions } from 'shiki'; 3 | import './Demo.css'; 4 | import CopyButton from '../CopyButton/CopyButton.js'; 5 | 6 | const shiki = import('shiki'); 7 | 8 | export interface DemoProps extends Omit, 'lang'> { 9 | /** Code to Highlight */ 10 | code: string; 11 | lang?: CodeToHastOptions['lang']; 12 | theme?: CodeOptionsSingleTheme['theme']; 13 | } 14 | 15 | export default function Demo({ children, code, lang = 'tsx', theme = 'houston', ...rest }: DemoProps) { 16 | const [codeFormatted, setCodeFormatted] = useState(''); 17 | 18 | useEffect(() => { 19 | shiki.then(async ({ codeToHtml }) => { 20 | const formatted = await codeToHtml(code, { lang, theme }); 21 | setCodeFormatted(formatted); 22 | }); 23 | }, [code, lang, theme]); 24 | 25 | return ( 26 |
27 |
{children}
28 |
29 | 30 | 31 | 32 |
37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /packages/tiles/src/Fieldset/Fieldset.css: -------------------------------------------------------------------------------- 1 | .tz-fieldset { 2 | border: none; 3 | display: flex; 4 | flex-direction: column; 5 | gap: var(--tz-space-300); 6 | padding: 0; 7 | } 8 | 9 | .tz-fieldset-legend { 10 | color: var(--tz-text-secondary); 11 | display: flex; 12 | font-family: var(--tz-font-sans); 13 | font-size: var(--tz-font-body-font-size); 14 | letter-spacing: var(--tz-font-body-letter-spacing); 15 | line-height: var(--tz-font-body-line-height); 16 | margin-bottom: var(--tz-space-300); 17 | position: static; 18 | width: 100%; 19 | } 20 | -------------------------------------------------------------------------------- /packages/tiles/src/Fieldset/Fieldset.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps, ReactNode } from 'react'; 3 | import './Fieldset.css'; 4 | 5 | export interface FieldsetProps extends ComponentProps<'fieldset'> { 6 | label: ReactNode; 7 | } 8 | 9 | export default function Fieldset({ children, className, label, ref, ...rest }: FieldsetProps) { 10 | return ( 11 |
12 | {label} 13 | {children} 14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /packages/tiles/src/Kbd/Kbd.css: -------------------------------------------------------------------------------- 1 | .tz-kbd { 2 | align-items: center; 3 | background-color: var(--tz-color-content-bg); 4 | border: 1px solid var(--tz-color-content-border); 5 | border-radius: var(--tz-radius-200); 6 | color: var(--tz-text-primary); 7 | cursor: default; 8 | display: inline-flex; 9 | font-family: var(--tz-font-mono); 10 | font-size: inherit; 11 | font-weight: var(--tz-font-body-font-weight); 12 | height: 1.5em; 13 | justify-content: center; 14 | letter-spacing: var(--tz-font-body-letter-spacing); 15 | line-height: inherit; 16 | padding: 0; 17 | user-select: none; 18 | width: 1.5em; 19 | } 20 | -------------------------------------------------------------------------------- /packages/tiles/src/Kbd/Kbd.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps } from 'react'; 3 | import './Kbd.css'; 4 | 5 | export type KbdProps = ComponentProps<'kbd'>; 6 | 7 | export default function Kbd({ className, children, ...rest }: KbdProps) { 8 | return ( 9 | 10 | {children} 11 | 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/tiles/src/SubtleInput/SubtleInput.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import type { ComponentProps } from 'react'; 3 | import './SubtleInput.css'; 4 | 5 | const INPUT_MODE: Record['inputMode']> = { 6 | text: 'text', 7 | number: 'decimal', 8 | email: 'email', 9 | search: 'search', 10 | tel: 'tel', 11 | url: 'url', 12 | }; 13 | 14 | export interface SubtleInputProps extends ComponentProps<'input'> { 15 | type: 'text' | 'number' | 'email' | 'search' | 'tel' | 'url'; 16 | suffix?: string; 17 | } 18 | 19 | export default function SubtleInput({ className, suffix, ref, ...rest }: SubtleInputProps) { 20 | return ( 21 |
22 | 23 | {suffix} 24 |
25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /packages/tiles/src/Switch/Switch.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import { type ComponentProps, type ReactNode, useId } from 'react'; 3 | import './Switch.css'; 4 | 5 | export interface SwitchProps extends ComponentProps<'input'> { 6 | /** Accessible label for this toggle switch */ 7 | label: ReactNode; 8 | } 9 | 10 | export default function Switch({ className, id: userID, label, ref, ...rest }: SwitchProps) { 11 | const randomID = useId(); 12 | const id = userID ?? randomID; 13 | 14 | return ( 15 |
16 | 25 | 28 |
29 | ); 30 | } 31 | -------------------------------------------------------------------------------- /packages/tiles/src/TokenType/TokenType.css: -------------------------------------------------------------------------------- 1 | .tz-tokentype { 2 | align-items: center; 3 | display: flex; 4 | font-family: var(--tz-font-sans); 5 | font-size: var(--tz-font-body-font-size); 6 | font-weight: var(--tz-font-body-font-weight); 7 | gap: var(--tz-space-300); 8 | letter-spacing: var(--tz-font-body-letter-spacing); 9 | line-height: 1; 10 | } 11 | 12 | .tz-tokentype-icon { 13 | color: var(--tz-icon-secondary); 14 | height: 1em; 15 | width: 1em; 16 | } 17 | -------------------------------------------------------------------------------- /packages/tiles/src/Tooltip/Tooltip.css: -------------------------------------------------------------------------------- 1 | .tz-tooltip-content { 2 | animation-duration: 400ms; 3 | background: var(--tz-color-content-bg-elevated); 4 | border: 1px solid var(--tz-color-content-border-elevated); 5 | border-radius: var(--tz-radius-2); 6 | box-shadow: var(--tz-shadow-300); 7 | color: var(--tz-text-secondary); 8 | font-family: var(--tz-font-sans); 9 | font-size: var(--tz-font-body-font-size); 10 | font-weight: var(--tz-font-body-font-weight); 11 | letter-spacing: var(--tz-font-body-letter-spacing); 12 | line-height: var(--tz-font-body-line-height); 13 | padding: var(--tz-space-300); 14 | user-select: none; 15 | will-change: transform, opacity; 16 | z-index: var(--tz-layer-popover); 17 | 18 | a { 19 | color: var(--tz-text-link-primary); 20 | } 21 | } 22 | 23 | .tz-tooltip-arrow { 24 | fill: var(--tz-color-content-bg-elevated); 25 | } 26 | -------------------------------------------------------------------------------- /packages/tiles/src/TreeGrid/Item.tsx: -------------------------------------------------------------------------------- 1 | import clsx from 'clsx'; 2 | import { type ComponentProps, type ReactNode, useContext, useEffect } from 'react'; 3 | import { NestedContext } from './Group.js'; 4 | import { Context } from './Root.js'; 5 | 6 | export interface ItemProps extends ComponentProps<'tr'> { 7 | /** 8 | * Unique ID for this item 9 | * ⚠️ must not conflict with any other items, or any parent groups! 10 | */ 11 | id: string; 12 | actions?: ReactNode; 13 | hidden?: boolean; 14 | } 15 | 16 | export default function Item({ actions, children, className, hidden, id, ...rest }: ItemProps) { 17 | const { selected, registerID } = useContext(Context); 18 | const { level, isParentExpanded, parentID } = useContext(NestedContext); 19 | 20 | useEffect(() => { 21 | registerID(parentID, id); 22 | }, [parentID, id]); 23 | 24 | return ( 25 | 34 | 35 | {children} 36 | 37 | {actions} 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /packages/tiles/src/TreeGrid/index.tsx: -------------------------------------------------------------------------------- 1 | import './TreeGrid.css'; 2 | 3 | export * from './Group.js'; 4 | export { default as Group } from './Group.js'; 5 | export * from './Item.js'; 6 | export { default as Item } from './Item.js'; 7 | export * from './Root.js'; 8 | export { default as Root } from './Root.js'; 9 | -------------------------------------------------------------------------------- /packages/tiles/src/hooks/resize-observer.ts: -------------------------------------------------------------------------------- 1 | import { type MutableRefObject, useEffect, useState } from 'react'; 2 | 3 | /** Use performant Resize Observer */ 4 | export default function useResizeObserver>(el: T): DOMRect { 5 | const [domRect, setDomRect] = useState({ 6 | width: 0, 7 | height: 0, 8 | x: 0, 9 | y: 0, 10 | left: 0, 11 | top: 0, 12 | right: 0, 13 | bottom: 0, 14 | } as DOMRect); 15 | useEffect(() => { 16 | if (!el.current) { 17 | return; 18 | } 19 | const ro = new ResizeObserver(([entry]) => { 20 | if (entry) { 21 | setDomRect(entry.contentRect); 22 | } 23 | }); 24 | ro.observe(el.current); 25 | return () => { 26 | ro.disconnect(); 27 | }; 28 | }, [el.current]); 29 | return domRect; 30 | } 31 | -------------------------------------------------------------------------------- /packages/tiles/src/lib/number.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest'; 2 | import { clamp, snap } from './number.js'; 3 | 4 | describe('clamp', () => { 5 | it('basic', () => { 6 | expect(clamp(0.5, 0, 1)).toBe(0.5); 7 | expect(clamp(1.5, 0, 1)).toBe(1); 8 | expect(clamp(-1, 0, 1)).toBe(0); 9 | }); 10 | }); 11 | 12 | describe('snap', () => { 13 | it('basic', () => { 14 | expect(snap(0.5, 0.1)).toBe(0.5); 15 | expect(snap(342, 1)).toBe(342); 16 | expect(snap(342.000001, 0.01)).toBe(342); 17 | expect(snap(0.5, 1)).toBe(1); 18 | expect(snap(0.1 + 0.2, 0.1)).toBe(0.3); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/tiles/src/lib/number.ts: -------------------------------------------------------------------------------- 1 | /** Clamp a number between 2 values */ 2 | export function clamp(value: number, min: number, max: number) { 3 | if (typeof value !== 'number') { 4 | return value; 5 | } 6 | return Math.min(Math.max(value, min), max); 7 | } 8 | 9 | /** Snap a number to n decimal places */ 10 | export function snap(value: number, precision: number) { 11 | if (typeof value !== 'number' || precision <= 0 || precision > 1 || value % 1 === 0) { 12 | return value; 13 | } 14 | const p = 1 / precision; 15 | return Math.round(value * p) / p; 16 | } 17 | -------------------------------------------------------------------------------- /packages/tiles/src/lib/set.ts: -------------------------------------------------------------------------------- 1 | export function addToSet(newValue: T) { 2 | return (current: Set) => new Set([...current, newValue]); 3 | } 4 | 5 | export function removeFromSet(oldValue: T) { 6 | return (current: Set) => new Set([...current].filter((v) => v !== oldValue)); 7 | } 8 | -------------------------------------------------------------------------------- /packages/tiles/src/types.d.ts: -------------------------------------------------------------------------------- 1 | import 'react'; 2 | 3 | declare module 'react' { 4 | interface CSSProperties { 5 | [key: `--${string}`]: string | number | undefined; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/tiles/stylelint.config.js: -------------------------------------------------------------------------------- 1 | import base from '../../stylelint.config.js'; 2 | 3 | /** @type {import('stylelint').Config} */ 4 | export default { 5 | ...base, 6 | }; 7 | -------------------------------------------------------------------------------- /packages/tiles/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "NodeNext", 5 | "moduleResolution": "nodenext" 6 | }, 7 | "exclude": ["**/__test__/**", "**/*.test.*"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/tiles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/tiles/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | /** @see https://vitejs.dev/config/ */ 5 | export default defineConfig({ 6 | plugins: [react({ devTarget: 'esnext' })], 7 | test: { 8 | environment: 'jsdom', 9 | restoreMocks: true, 10 | }, 11 | }); 12 | -------------------------------------------------------------------------------- /packages/token-lab/.gitignore: -------------------------------------------------------------------------------- 1 | public/monaco-editor/ 2 | -------------------------------------------------------------------------------- /packages/token-lab/.npmignore: -------------------------------------------------------------------------------- 1 | *.test.* 2 | .size-limit.js 3 | .turbo 4 | biome.* 5 | rollup.* 6 | stylelint.* 7 | src/** 8 | tsconfig.* 9 | vite.* 10 | vitest.* 11 | -------------------------------------------------------------------------------- /packages/token-lab/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/token-lab 2 | -------------------------------------------------------------------------------- /packages/token-lab/README.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/token-lab 2 | 3 | Tokens.json editor and viewer. 4 | 5 | ## Setup 6 | 7 | ```sh 8 | pnpm 9 | ``` 10 | 11 | ```tsx 12 | import TokenLab from '@terrazzo/token-lab'; 13 | import tokens from './tokens.json' with { type: 'json' }; 14 | 15 | ; 18 | ``` 19 | -------------------------------------------------------------------------------- /packages/token-lab/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "linter": { 5 | "rules": { 6 | "a11y": { 7 | "useSemanticElements": "off" 8 | }, 9 | "suspicious": { 10 | "noArrayIndexKey": "off" 11 | } 12 | } 13 | }, 14 | "css": { 15 | "parser": { 16 | "cssModules": true 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /packages/token-lab/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | Token Lab: build, edit, and customize DTCG tokens.json 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /packages/token-lab/public/assets/terrazzo-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/token-lab/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/token-lab/src/app.tsx: -------------------------------------------------------------------------------- 1 | import './styles/tokens.css'; 2 | import '@terrazzo/fonts/fragment-mono.css'; 3 | import '@terrazzo/fonts/instrument-sans.css'; 4 | import '@terrazzo/tiles/dist/all-components.css'; 5 | import '@terrazzo/react-color-picker/dist/all-components.css'; 6 | import './styles/global.css'; 7 | import { Provider as JotaiProvider } from 'jotai'; 8 | import { TokensFileContext } from './hooks/tokens.js'; 9 | import { DefaultLayout } from './layouts/Default/Default.js'; 10 | 11 | export default function App({ tokensFile, onUpdate }: { tokensFile?: string; onUpdate?: (file: string) => unknown }) { 12 | return ( 13 | 14 | 15 | 16 | 17 | 18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/CodePanel/CodePanel.module.css: -------------------------------------------------------------------------------- 1 | .container { 2 | position: relative; 3 | z-index: 10; 4 | } 5 | 6 | .btn { 7 | align-items: center; 8 | background: none; 9 | border: none; 10 | color: inherit; 11 | cursor: pointer; 12 | display: flex; 13 | font-size: 1rem; 14 | height: 3rem; 15 | justify-content: center; 16 | width: 3rem; 17 | 18 | svg { 19 | height: 1.25rem; 20 | width: 1.25rem; 21 | } 22 | } 23 | 24 | .btnClose { 25 | height: 2rem; 26 | width: 2rem; 27 | 28 | svg { 29 | height: 1rem; 30 | width: 1rem; 31 | } 32 | } 33 | 34 | .panel { 35 | background-color: #202020; 36 | border: var(--tz-border-2); 37 | bottom: 0; 38 | display: grid; 39 | grid-template-rows: 2rem auto; 40 | height: 100vh; 41 | position: fixed; 42 | right: 0; 43 | top: 0; 44 | width: 40rem; 45 | 46 | > * { 47 | height: 100%; 48 | width: 100%; 49 | } 50 | 51 | &[hidden] { 52 | display: none; 53 | } 54 | } 55 | 56 | .menu { 57 | align-items: center; 58 | background-color: var(--tz-color-bg-2); 59 | border-bottom: var(--tz-border-2); 60 | display: flex; 61 | justify-content: space-between; 62 | } 63 | 64 | .title { 65 | align-items: center; 66 | display: inline-flex; 67 | font-family: var(--tz-monospace); 68 | font-size: 0.75rem; 69 | margin-right: auto; 70 | padding-inline: 0.5rem; 71 | } 72 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/CodePanel/CodePanel.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly container: 'container'; 3 | readonly btn: 'btn'; 4 | readonly btnClose: 'btnClose'; 5 | readonly panel: 'panel'; 6 | readonly menu: 'menu'; 7 | readonly title: 'title'; 8 | }; 9 | export default classNames; 10 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/EditableColorToken/EditableColorToken.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly container: 'container'; 3 | readonly swatch: 'swatch'; 4 | readonly popover: 'popover'; 5 | readonly components: 'components'; 6 | readonly colorSpaceDropdown: 'colorSpaceDropdown'; 7 | readonly 'tz-select-item-inner': 'tz-select-item-inner'; 8 | readonly 'tz-select-item-icon': 'tz-select-item-icon'; 9 | readonly colorSpaceDropdownItem: 'colorSpaceDropdownItem'; 10 | readonly channel: 'channel'; 11 | readonly alpha: 'alpha'; 12 | }; 13 | export default classNames; 14 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/EditableToken/EditableToken.module.css: -------------------------------------------------------------------------------- 1 | .cell { 2 | border-bottom: var(--tz-border-3); 3 | border-right: var(--tz-border-3); 4 | font-family: var(--tz-font-sans); 5 | font-size: var(--tz-font-body-font-size); 6 | font-variation-settings: var(--tz-font-body-font-variation-settings); 7 | font-weight: var(--tz-font-body-font-weight); 8 | letter-spacing: var(--tz-font-body-letter-spacing); 9 | line-height: var(--tz-font-body-line-height); 10 | padding: 0.5rem; 11 | } 12 | 13 | .code { 14 | font-family: var(--tz-font-mono); 15 | font-size: 0.75rem; 16 | } 17 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/EditableToken/EditableToken.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly cell: 'cell'; 3 | readonly code: 'code'; 4 | }; 5 | export default classNames; 6 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/EditableToken/EditableToken.tsx: -------------------------------------------------------------------------------- 1 | import type { TokenNormalized } from '@terrazzo/token-tools'; 2 | import clsx from 'clsx'; 3 | import EditableColorToken from '../EditableColorToken/EditableColorToken.js'; 4 | import c from './EditableToken.module.css'; 5 | 6 | export interface TokenProps { 7 | token: TokenNormalized; 8 | /** ordered listing of modes ("." is always first, even if omitted) */ 9 | modes?: string[]; 10 | } 11 | 12 | export default function EditableToken({ token, modes = ['.'] }: TokenProps) { 13 | const localName = token.id.replace(`${token.group.id}.`, ''); 14 | 15 | switch (token.$type) { 16 | case 'color': { 17 | return ( 18 | 19 | 20 | {localName} 21 | 22 | {modes.map((mode) => ( 23 | 24 | 25 | 26 | ))} 27 | 28 | ); 29 | } 30 | default: { 31 | return ( 32 | 33 | 34 | {localName} 35 | 36 | {modes.map((mode) => ( 37 | 38 | {mode in token.mode && JSON.stringify(token.mode[mode])} 39 | 40 | ))} 41 | 42 | ); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/MainNav/MainNav.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | align-items: center; 3 | border-bottom: var(--tz-border-3); 4 | display: flex; 5 | height: var(--main-nav-height); 6 | user-select: none; 7 | } 8 | 9 | .logo { 10 | height: 1.25rem; 11 | margin-inline-start: 1rem; 12 | width: 1.25rem; 13 | } 14 | 15 | .codePanel { 16 | margin-inline-start: auto; 17 | } 18 | 19 | .title { 20 | font-size: 1rem; 21 | font-weight: 800; 22 | line-height: 1; 23 | margin: 0; 24 | padding-inline-start: 0.375rem; 25 | } 26 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/MainNav/MainNav.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly header: 'header'; 3 | readonly logo: 'logo'; 4 | readonly codePanel: 'codePanel'; 5 | readonly title: 'title'; 6 | }; 7 | export default classNames; 8 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/TokensEditor/TokensEditor.module.css: -------------------------------------------------------------------------------- 1 | .multipleGroups { 2 | padding: 1.5rem; 3 | } 4 | 5 | .group { 6 | margin-block: 1.5rem; 7 | padding-inline: 1.5rem; 8 | } 9 | 10 | .groupTitle { 11 | font-family: var(--tz-font-sans); 12 | font-size: var(--tz-font-heading4-font-size); 13 | font-variation-settings: var(--tz-font-heading4-font-variation-settings); 14 | font-weight: var(--tz-font-heading4-font-weight); 15 | letter-spacing: var(--tz-font-heading4-letter-spacing); 16 | line-height: var(--tz-font-heading4-line-height); 17 | } 18 | 19 | .tokenGrid { 20 | border-collapse: collapse; 21 | border-left: var(--tz-border-3); 22 | border-top: var(--tz-border-3); 23 | text-align: left; 24 | width: 100%; 25 | } 26 | 27 | .colheader { 28 | border-bottom: var(--tz-border-3); 29 | border-right: var(--tz-border-3); 30 | font-family: var(--tz-font-sans); 31 | font-size: var(--tz-font-body-strong-font-size); 32 | font-variation-settings: var(--tz-font-body-strong-font-variation-settings); 33 | font-weight: var(--tz-font-body-strong-font-weight); 34 | letter-spacing: var(--tz-font-body-strong-letter-spacing); 35 | line-height: var(--tz-font-body-strong-line-height); 36 | padding: 0.5rem; 37 | } 38 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/TokensEditor/TokensEditor.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly multipleGroups: 'multipleGroups'; 3 | readonly group: 'group'; 4 | readonly groupTitle: 'groupTitle'; 5 | readonly tokenGrid: 'tokenGrid'; 6 | readonly colheader: 'colheader'; 7 | }; 8 | export default classNames; 9 | -------------------------------------------------------------------------------- /packages/token-lab/src/components/TokensNav/TokensNav.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly container: 'container'; 3 | readonly tree: 'tree'; 4 | readonly error: 'error'; 5 | readonly search: 'search'; 6 | readonly searchIcon: 'searchIcon'; 7 | readonly tokenIcon: 'tokenIcon'; 8 | }; 9 | export default classNames; 10 | -------------------------------------------------------------------------------- /packages/token-lab/src/hooks/navigation.ts: -------------------------------------------------------------------------------- 1 | import { atom, useAtom } from 'jotai'; 2 | import { useEffect } from 'react'; 3 | 4 | export interface NavState { 5 | selection: string[]; 6 | } 7 | 8 | /** 9 | * Naviation 10 | * The simple version of this app only uses a single route with clientside search params, so we can 11 | * implement routing using a couple Jotai atoms without the need for a heavier router. 12 | */ 13 | export default function useNavigation() { 14 | const [state, setState] = useAtom($state); 15 | 16 | // keep search params synced with state 17 | useEffect(() => { 18 | const params = new URLSearchParams(window.location.search); 19 | params.set('selected', serializeSelected(state.selection)); 20 | window.history.replaceState({}, '', `${window.location.pathname}?${params}`); 21 | }, [state]); 22 | 23 | return [state, setState] as const; 24 | } 25 | 26 | function serializeSelected(selected: string[]) { 27 | return selected.join(','); 28 | } 29 | 30 | function deserializeSelected(selected: string) { 31 | return selected.split(','); 32 | } 33 | 34 | const rawParams = new URLSearchParams(typeof window !== 'undefined' ? window.location.search : ''); 35 | const parsed: NavState = { 36 | selection: deserializeSelected(rawParams.get('selected') || ''), 37 | }; 38 | 39 | const $state = atom(parsed); 40 | -------------------------------------------------------------------------------- /packages/token-lab/src/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './app.js'; 3 | 4 | const root = createRoot(document.getElementById('app')!); 5 | root.render(); 6 | -------------------------------------------------------------------------------- /packages/token-lab/src/layouts/Default/Default.module.css: -------------------------------------------------------------------------------- 1 | .layout { 2 | --main-nav-height: 3rem; 3 | --sidebar-width: 16rem; 4 | 5 | display: grid; 6 | grid-template-rows: var(--main-nav-height) auto; 7 | height: 100svh; 8 | height: 100vh; 9 | overflow: hidden; 10 | width: 100svw; 11 | width: 100vw; 12 | } 13 | 14 | .page { 15 | display: grid; 16 | grid-template-columns: var(--sidebar-width) auto; 17 | } 18 | 19 | .sidebar { 20 | display: flex; 21 | flex-direction: column; 22 | height: 100%; 23 | max-height: calc(100svh - var(--main-nav-height)); 24 | max-height: calc(100vh - var(--main-nav-height)); 25 | } 26 | 27 | .main { 28 | max-height: calc(100svh - var(--main-nav-height)); 29 | max-height: calc(100vh - var(--main-nav-height)); 30 | overflow-y: auto; 31 | } 32 | -------------------------------------------------------------------------------- /packages/token-lab/src/layouts/Default/Default.module.d.css.ts: -------------------------------------------------------------------------------- 1 | declare const classNames: { 2 | readonly layout: 'layout'; 3 | readonly page: 'page'; 4 | readonly sidebar: 'sidebar'; 5 | readonly main: 'main'; 6 | }; 7 | export default classNames; 8 | -------------------------------------------------------------------------------- /packages/token-lab/src/layouts/Default/Default.tsx: -------------------------------------------------------------------------------- 1 | import MainNav from '../../components/MainNav/MainNav.js'; 2 | import TokensEditor from '../../components/TokensEditor/TokensEditor.js'; 3 | import TokensNav from '../../components/TokensNav/TokensNav.js'; 4 | import c from './Default.module.css'; 5 | 6 | export function DefaultLayout() { 7 | return ( 8 |
9 | 10 |
11 | 14 |
15 | 16 |
17 |
18 |
19 | ); 20 | } 21 | -------------------------------------------------------------------------------- /packages/token-lab/src/lib/indexed-db.ts: -------------------------------------------------------------------------------- 1 | export async function getDB( 2 | name: string, 3 | { 4 | version = 1, 5 | onupgradeneeded, 6 | }: { 7 | version?: number; 8 | onupgradeneeded?: NonNullable; 9 | } = {}, 10 | ): Promise { 11 | return await new Promise((resolve, reject) => { 12 | const dbReq = window.indexedDB.open(name, version); 13 | dbReq.onerror = (evt) => { 14 | reject(`IndexedDB not supported. Unable to save tokens!\nError: ${(evt.target as IDBOpenDBRequest).error}`); 15 | }; 16 | if (onupgradeneeded) { 17 | dbReq.onupgradeneeded = onupgradeneeded; 18 | } 19 | dbReq.onsuccess = () => { 20 | resolve(dbReq!.result); 21 | }; 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /packages/token-lab/src/styles/global.css: -------------------------------------------------------------------------------- 1 | html { 2 | height: 100%; 3 | } 4 | 5 | body { 6 | background-color: var(--tz-color-bg-1); 7 | color: var(--tz-color-text-1); 8 | font-family: var(--tz-font-sans); 9 | font-size: 100%; 10 | font-variant-numeric: tabular-nums; 11 | font-variation-settings: var(--tz-font-body-font-variation-settings); 12 | font-weight: var(--tz-font-body-font-weight); 13 | line-height: 1; 14 | margin: 0; 15 | } 16 | 17 | *, 18 | *::before, 19 | *::after { 20 | box-sizing: border-box; 21 | outline: 1px solid transparent; 22 | 23 | &:focus-visible { 24 | outline-color: var(--tz-color-base-lime-800); 25 | } 26 | } 27 | 28 | ::selection { 29 | background-color: var(--tz-color-base-lime-800); 30 | color: var(--tz-color-base-gray-100); 31 | } 32 | 33 | code, 34 | pre { 35 | font-family: var(--tz-font-monospace); 36 | } 37 | -------------------------------------------------------------------------------- /packages/token-lab/src/types/react.d.ts: -------------------------------------------------------------------------------- 1 | import 'react'; 2 | 3 | declare module 'react' { 4 | interface CSSProperties { 5 | [key: `--${string}`]: string | number | undefined; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/token-lab/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "outDir": "dist" 6 | }, 7 | "include": ["./src/"] 8 | } 9 | -------------------------------------------------------------------------------- /packages/token-lab/vite.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc'; 2 | import { defineConfig } from 'vite'; 3 | import sassDts from 'vite-plugin-sass-dts'; 4 | 5 | export default defineConfig({ 6 | plugins: [react(), sassDts({ esmExport: true })], 7 | }); 8 | -------------------------------------------------------------------------------- /packages/token-tools/.npmignore: -------------------------------------------------------------------------------- 1 | .turbo 2 | biome.json 3 | src/** 4 | test/** 5 | tsconfig.* 6 | vitest.* 7 | -------------------------------------------------------------------------------- /packages/token-tools/README.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/token-tools 2 | 3 | ## Install 4 | 5 | ```sh 6 | npm install @terrazzo/token-tools 7 | ``` 8 | -------------------------------------------------------------------------------- /packages/token-tools/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "files": { 5 | "include": ["./src/", "./test/"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/token-tools/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/token-tools", 3 | "version": "0.8.0", 4 | "description": "Various utilities for token types", 5 | "license": "MIT", 6 | "type": "module", 7 | "author": { 8 | "name": "Drew Powers", 9 | "email": "drew@pow.rs" 10 | }, 11 | "keywords": [ 12 | "design tokens", 13 | "dtcg", 14 | "cli", 15 | "w3c", 16 | "design system", 17 | "typescript", 18 | "sass", 19 | "css", 20 | "style tokens", 21 | "style system", 22 | "linting", 23 | "lint" 24 | ], 25 | "repository": { 26 | "type": "git", 27 | "url": "https://github.com/terrazzoapp/terrazzo.git", 28 | "directory": "./packages/token-tools/" 29 | }, 30 | "main": "./dist/index.js", 31 | "exports": { 32 | ".": "./dist/index.js", 33 | "./css": "./dist/css.js", 34 | "./js": "./dist/js.js", 35 | "./package.json": "./package.json" 36 | }, 37 | "scripts": { 38 | "build": "rolldown -c && attw --profile esm-only --pack .", 39 | "dev": "rolldown -c -w", 40 | "lint": "pnpm --filter @terrazzo/token-tools run \"/^lint:(js|ts)/\"", 41 | "lint:js": "biome check .", 42 | "lint:ts": "tsc --noEmit", 43 | "format": "biome check --fix --unsafe .", 44 | "test": "vitest run" 45 | }, 46 | "dependencies": { 47 | "@humanwhocodes/momoa": "^3.3.8", 48 | "culori": "^4.0.1", 49 | "wildcard-match": "^5.1.4" 50 | }, 51 | "devDependencies": { 52 | "@types/culori": "^4.0.0" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/token-tools/rolldown.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rolldown'; 2 | import { dts } from 'rolldown-plugin-dts'; 3 | 4 | export default defineConfig({ 5 | input: { 6 | index: './src/index.ts', 7 | css: './src/css/index.ts', 8 | js: './src/js/index.ts', 9 | }, 10 | plugins: [dts()], 11 | external: ['@humanwhocodes/momoa', 'culori', 'culori/css', 'culori/fn', 'wildcard-match'], 12 | output: { 13 | dir: 'dist', 14 | format: 'es', 15 | sourcemap: true, 16 | }, 17 | }); 18 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/boolean.ts: -------------------------------------------------------------------------------- 1 | import type { BooleanTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert boolean value to CSS string */ 6 | export function transformBoolean( 7 | token: BooleanTokenNormalized, 8 | { tokensSet, transformAlias = defaultAliasTransform }: TransformCSSValueOptions, 9 | ): string { 10 | if (token.aliasChain?.[0]) { 11 | return transformAlias(tokensSet[token.aliasChain[0]]!); 12 | } 13 | return token.$value === true ? '1' : '0'; 14 | } 15 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/css-types.ts: -------------------------------------------------------------------------------- 1 | import type { TokenNormalizedSet } from '../types.js'; 2 | import type { IDGenerator } from './lib.js'; 3 | 4 | export interface TransformCSSValueOptions { 5 | /** Complete set of tokens (needed to resolve full and partial aliases) */ 6 | tokensSet: TokenNormalizedSet; 7 | transformAlias?: IDGenerator; 8 | /** Options for color tokens */ 9 | color?: { 10 | /** Output legacy hex-6 and hex-8 */ 11 | legacyHex?: boolean; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/cubic-bezier.ts: -------------------------------------------------------------------------------- 1 | import type { CubicBezierTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert cubicBezier value to CSS */ 6 | export function transformCubicBezier(token: CubicBezierTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | return `cubic-bezier(${token.$value 12 | .map((v, i) => (token.partialAliasOf?.[i] ? transformAlias(tokensSet[token.partialAliasOf[i]]!) : v)) 13 | .join(', ')})`; 14 | } 15 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/dimension.ts: -------------------------------------------------------------------------------- 1 | import type { DimensionTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert dimension value to CSS */ 6 | export function transformDimension(token: DimensionTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | 12 | return token.$value.value === 0 ? '0' : `${token.$value.value}${token.$value.unit}`; 13 | } 14 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/duration.ts: -------------------------------------------------------------------------------- 1 | import type { DurationTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert duration value to CSS */ 6 | export function transformDuration(token: DurationTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | 12 | return `${token.$value.value}${token.$value.unit}`; 13 | } 14 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/font-family.ts: -------------------------------------------------------------------------------- 1 | import type { FontFamilyTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | const FONT_NAME_KEYWORD = /^[a-z-]+$/; 6 | 7 | export function transformFontFamily(token: FontFamilyTokenNormalized, options: TransformCSSValueOptions): string { 8 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 9 | if (token.aliasChain?.[0]) { 10 | return transformAlias(tokensSet[token.aliasChain[0]]!); 11 | } 12 | return token.$value.map((fontName) => (FONT_NAME_KEYWORD.test(fontName) ? fontName : `"${fontName}"`)).join(', '); 13 | } 14 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/font-weight.ts: -------------------------------------------------------------------------------- 1 | import type { FontWeightTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert fontWeight value to CSS */ 6 | export function transformFontWeight(token: FontWeightTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | return String(token.$value); 12 | } 13 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/link.ts: -------------------------------------------------------------------------------- 1 | import type { LinkTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert link value to CSS */ 6 | export function transformLink(token: LinkTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | return `url("${token.$value}")`; 12 | } 13 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/number.ts: -------------------------------------------------------------------------------- 1 | import type { NumberTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert number value to CSS */ 6 | export function transformNumber(token: NumberTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | return String(token.$value); 12 | } 13 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/string.ts: -------------------------------------------------------------------------------- 1 | import type { StringTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert string value to CSS */ 6 | export function transformString(token: StringTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | // this seems like a useless function—because it is—but this is a placeholder 12 | // that can handle unexpected values in the future should any arise 13 | return String(token.$value); 14 | } 15 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/stroke-style.ts: -------------------------------------------------------------------------------- 1 | import type { StrokeStyleTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { defaultAliasTransform } from './lib.js'; 4 | 5 | /** Convert strokeStyle value to CSS */ 6 | export function transformStrokeStyle(token: StrokeStyleTokenNormalized, options: TransformCSSValueOptions): string { 7 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 8 | if (token.aliasChain?.[0]) { 9 | return transformAlias(tokensSet[token.aliasChain[0]]!); 10 | } 11 | return typeof token.$value === 'string' ? token.$value : 'dashed'; // CSS doesn’t have `dash-array`; it’s just "dashed" 12 | } 13 | -------------------------------------------------------------------------------- /packages/token-tools/src/css/transition.ts: -------------------------------------------------------------------------------- 1 | import type { CubicBezierTokenNormalized, DurationTokenNormalized, TransitionTokenNormalized } from '../types.js'; 2 | import type { TransformCSSValueOptions } from './css-types.js'; 3 | import { transformCubicBezier } from './cubic-bezier.js'; 4 | import { transformDuration } from './duration.js'; 5 | import { defaultAliasTransform } from './lib.js'; 6 | 7 | /** Convert transition value to shorthand */ 8 | export function transformTransition(token: TransitionTokenNormalized, options: TransformCSSValueOptions) { 9 | const { tokensSet, transformAlias = defaultAliasTransform } = options; 10 | if (token.aliasChain?.[0]) { 11 | return transformAlias(tokensSet[token.aliasChain[0]]!); 12 | } 13 | 14 | const duration = token.partialAliasOf?.duration 15 | ? transformAlias(tokensSet[token.partialAliasOf.duration]!) 16 | : transformDuration({ $value: token.$value.duration } as DurationTokenNormalized, options); 17 | const delay = token.partialAliasOf?.delay 18 | ? transformAlias(tokensSet[token.partialAliasOf.delay]!) 19 | : transformDuration({ $value: token.$value.delay } as DurationTokenNormalized, options); 20 | const timingFunction = token.partialAliasOf?.timingFunction 21 | ? transformAlias(tokensSet[token.partialAliasOf.timingFunction]!) 22 | : transformCubicBezier({ $value: token.$value.timingFunction } as CubicBezierTokenNormalized, options); 23 | 24 | return `${duration} ${delay} ${timingFunction}`; 25 | } 26 | -------------------------------------------------------------------------------- /packages/token-tools/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './color.js'; 2 | export * from './id.js'; 3 | export * from './string.js'; 4 | export * from './transform.js'; 5 | export * from './types.js'; 6 | -------------------------------------------------------------------------------- /packages/token-tools/src/js/index.ts: -------------------------------------------------------------------------------- 1 | import type { TokenNormalized } from '../types.js'; 2 | 3 | export interface TransformJSValueOptions { 4 | mode: string; 5 | /** indent space count */ 6 | indent?: number; 7 | /** initial indent level */ 8 | startingIndent?: number; 9 | } 10 | 11 | /** 12 | * Convert token value to a JS string via acorn/astring. 13 | */ 14 | export function transformJSValue( 15 | token: T, 16 | { mode, indent = 2, startingIndent = 0 }: TransformJSValueOptions, 17 | ) { 18 | if (!(mode in token.mode)) { 19 | return; 20 | } 21 | const indentStart = startingIndent > 0 ? ' '.repeat(startingIndent ?? 2) : ''; 22 | 23 | // note: since tokens are JSON-serializable to begin with, using 24 | // JSON.stringify generates the same output as an AST parser/generator would 25 | // but faster and without the added overhead (even acorn/astring leave object 26 | // keys quoted). 27 | 28 | // TODO: use @biomejs/js-api when it’s stable for pretty formatting 29 | 30 | return JSON.stringify(token.mode[mode]!.$value, undefined, indent).replace(/\n/g, `\n${indentStart}`); 31 | } 32 | -------------------------------------------------------------------------------- /packages/token-tools/src/transform.ts: -------------------------------------------------------------------------------- 1 | export interface CustomTransformOptions { 2 | /** Token $type */ 3 | $type: string; 4 | } 5 | 6 | /** Give a user pertinent feedback if they override a transform incorrectly */ 7 | export function validateCustomTransform(value: unknown, { $type }: CustomTransformOptions) { 8 | if (value) { 9 | if ((typeof value !== 'string' && typeof value !== 'object') || Array.isArray(value)) { 10 | throw new Error( 11 | `transform(): expected string or Object of strings, received ${Array.isArray(value) ? 'Array' : typeof value}`, 12 | ); 13 | } 14 | switch ($type) { 15 | case 'typography': { 16 | if (typeof value !== 'object') { 17 | throw new Error('transform(): typography tokens must be an object of keys'); 18 | } 19 | break; 20 | } 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /packages/token-tools/test/types.test.ts: -------------------------------------------------------------------------------- 1 | import { describe, expectTypeOf, it } from 'vitest'; 2 | import type { Group } from '../src/types.js'; 3 | 4 | describe('types', () => { 5 | it('group', () => { 6 | const simpleGroup = { 7 | $description: 'group', 8 | color: { 9 | $type: 'color', 10 | $description: 'token', 11 | $value: '#000000', 12 | }, 13 | } satisfies Group; 14 | 15 | expectTypeOf(simpleGroup).toMatchTypeOf(); 16 | }); 17 | 18 | it('nested groups', () => { 19 | const groupsWithCore = { 20 | $description: 'group', 21 | color: { 22 | $description: 'nested group', 23 | primary: { 24 | $type: 'color', 25 | $description: 'token', 26 | $value: '#000000', 27 | }, 28 | }, 29 | } satisfies Group; 30 | 31 | expectTypeOf(groupsWithCore).toMatchTypeOf(); 32 | 33 | const groupsWithoutCore = { 34 | color: { 35 | primary: { 36 | $type: 'color', 37 | $value: '#000000', 38 | }, 39 | }, 40 | } satisfies Group; 41 | 42 | expectTypeOf(groupsWithoutCore).toMatchTypeOf(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/token-tools/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": ["test"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/token-tools/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "dist" 5 | }, 6 | "include": ["./src/", "./test/"] 7 | } 8 | -------------------------------------------------------------------------------- /packages/token-tools/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vitest/config'; 2 | 3 | export default defineConfig({ 4 | test: { 5 | restoreMocks: true, 6 | }, 7 | }); 8 | -------------------------------------------------------------------------------- /packages/token-tools/vitest.config.ts.timestamp-1718599329856-9fd330128a63e.mjs: -------------------------------------------------------------------------------- 1 | // vitest.config.ts 2 | import { defineConfig } from "file:///Users/drew/Sites/terrazzoapp/terrazzo/node_modules/.pnpm/vitest@1.6.0_@types+node@20.14.2/node_modules/vitest/dist/config.js"; 3 | var vitest_config_default = defineConfig({ 4 | test: { 5 | restoreMocks: true 6 | } 7 | }); 8 | export { 9 | vitest_config_default as default 10 | }; 11 | //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZXN0LmNvbmZpZy50cyJdLAogICJzb3VyY2VzQ29udGVudCI6IFsiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIi9Vc2Vycy9kcmV3L1NpdGVzL3RlcnJhenpvYXBwL3RlcnJhenpvL3BhY2thZ2VzL3Rva2VuLXRvb2xzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCIvVXNlcnMvZHJldy9TaXRlcy90ZXJyYXp6b2FwcC90ZXJyYXp6by9wYWNrYWdlcy90b2tlbi10b29scy92aXRlc3QuY29uZmlnLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9Vc2Vycy9kcmV3L1NpdGVzL3RlcnJhenpvYXBwL3RlcnJhenpvL3BhY2thZ2VzL3Rva2VuLXRvb2xzL3ZpdGVzdC5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlc3QvY29uZmlnJztcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgdGVzdDoge1xuICAgIHJlc3RvcmVNb2NrczogdHJ1ZSxcbiAgfSxcbn0pO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUF1VyxTQUFTLG9CQUFvQjtBQUVwWSxJQUFPLHdCQUFRLGFBQWE7QUFBQSxFQUMxQixNQUFNO0FBQUEsSUFDSixjQUFjO0FBQUEsRUFDaEI7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo= 12 | -------------------------------------------------------------------------------- /packages/tokens/.gitignore: -------------------------------------------------------------------------------- 1 | !dist 2 | -------------------------------------------------------------------------------- /packages/tokens/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/tokens/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/tokens", 3 | "version": "0.1.0", 4 | "description": "Internal design tokens for @terrazzo/tiles", 5 | "type": "module", 6 | "author": { 7 | "name": "Drew Powers", 8 | "email": "drew@pow.rs" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "https://github.com/terrazzoapp/terrazzo.git", 13 | "directory": "./packages/tokens/" 14 | }, 15 | "scripts": { 16 | "build": "tz build", 17 | "lint": "pnpm run lint:tokens", 18 | "lint:tokens": "tz check", 19 | "dev": "tz build --watch" 20 | }, 21 | "devDependencies": { 22 | "@terrazzo/cli": "workspace:^", 23 | "@terrazzo/plugin-css": "workspace:^", 24 | "@terrazzo/token-tools": "workspace:^" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/tokens/terrazzo.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from '@terrazzo/cli'; 2 | import { makeCSSVar } from '@terrazzo/token-tools/css'; 3 | import css from '@terrazzo/plugin-css'; 4 | 5 | export default defineConfig({ 6 | tokens: './tokens.json', 7 | outDir: './dist/', 8 | plugins: [ 9 | css({ 10 | variableName: (token) => makeCSSVar(token.id, { prefix: 'tz' }), 11 | modeSelectors: [ 12 | { 13 | mode: 'light', 14 | selectors: ['@media (prefers-color-scheme: light)', '[data-color-mode="light"][data-color-mode="light"]'], 15 | }, 16 | { 17 | mode: 'dark', 18 | selectors: ['@media (prefers-color-scheme: dark)', '[data-color-mode="dark"][data-color-mode="dark"]'], 19 | }, 20 | ], 21 | }), 22 | ], 23 | }); 24 | -------------------------------------------------------------------------------- /packages/use-color/.npmignore: -------------------------------------------------------------------------------- 1 | *.test.* 2 | .size-limit.js 3 | .turbo 4 | biome.* 5 | vite.* 6 | vitest.* 7 | tsconfig.* 8 | -------------------------------------------------------------------------------- /packages/use-color/.size-limit.js: -------------------------------------------------------------------------------- 1 | export default [{ ignore: ['culori'], path: './index.js', limit: '12 kB' }]; 2 | -------------------------------------------------------------------------------- /packages/use-color/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @terrazzo/use-color 2 | 3 | ## 0.0.7 4 | 5 | ### Patch Changes 6 | 7 | - Bugfixes thanks to [@lilnasy](lilnasy) 8 | -------------------------------------------------------------------------------- /packages/use-color/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../../biome.json"], 4 | "linter": { 5 | "rules": { 6 | "correctness": { 7 | "useHookAtTopLevel": "off" 8 | } 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/use-color/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@terrazzo/use-color", 3 | "version": "0.1.0", 4 | "description": "React hook for memoizing and transforming any web-compatible color. Uses Culori.", 5 | "license": "MIT", 6 | "type": "module", 7 | "keywords": ["color", "color-module-4", "culori", "react", "hooks"], 8 | "author": { 9 | "name": "Drew Powers", 10 | "email": "drew@pow.rs" 11 | }, 12 | "main": "./index.js", 13 | "homepage": "https://terrazzo.app/docs/components/color-picker", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/terrazzoapp/terrazzo.git", 17 | "directory": "./packages/use-color/" 18 | }, 19 | "scripts": { 20 | "build": "tsc --noEmit && size-limit", 21 | "format": "biome check --fix --unsafe .", 22 | "lint": "biome check .", 23 | "test": "vitest run" 24 | }, 25 | "peerDependencies": { 26 | "react": "^19.0.0" 27 | }, 28 | "dependencies": { 29 | "@terrazzo/token-tools": "workspace:^", 30 | "@types/culori": "^4.0.0", 31 | "culori": "^4.0.1" 32 | }, 33 | "devDependencies": { 34 | "@size-limit/preset-small-lib": "^11.2.0", 35 | "@testing-library/jest-dom": "^6.6.3", 36 | "@testing-library/react": "^16.3.0", 37 | "@testing-library/user-event": "^14.6.1", 38 | "@types/react": "^19.1.1", 39 | "@types/react-dom": "^19.1.2", 40 | "@vitejs/plugin-react-swc": "^3.8.1", 41 | "react": "19.0.0", 42 | "react-dom": "19.0.0", 43 | "size-limit": "^11.2.0" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/use-color/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.react.json", 3 | "compilerOptions": { 4 | "baseUrl": "src", 5 | "noEmit": true, 6 | "outDir": "dist" 7 | }, 8 | "include": ["./*.{js,ts}", "./index.test.tsx"] 9 | } 10 | -------------------------------------------------------------------------------- /packages/use-color/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@vitejs/plugin-react-swc'; 2 | import { defineConfig } from 'vitest/config'; 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | test: { 8 | environment: 'jsdom', 9 | restoreMocks: true, 10 | setupFiles: ['./vitest.setup.ts'], 11 | testTimeout: 15000, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /packages/use-color/vitest.setup.ts: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom/vitest'; 2 | import { cleanup } from '@testing-library/react'; 3 | import { afterEach } from 'vitest'; 4 | 5 | // @see https://github.com/vitest-dev/vitest/issues/1430 6 | afterEach(cleanup); 7 | -------------------------------------------------------------------------------- /patches/vite-plugin-sass-dts.patch: -------------------------------------------------------------------------------- 1 | diff --git a/dist/index.js b/dist/index.js 2 | index b4fbd644ea9430b83addd30a1adb9c092fdb3941..ad09b4ad5e8d2d72161a9a3242623134b4900071 100644 3 | --- a/dist/index.js 4 | +++ b/dist/index.js 5 | @@ -360,7 +360,7 @@ var formatWriteFilePath = (file, options) => { 6 | } 7 | return formatWriteFileName(path4); 8 | }; 9 | -var formatWriteFileName = (file) => `${file}${file.endsWith("d.ts") ? "" : ".d.ts"}`; 10 | +var formatWriteFileName = (file) => file.replace(/\.css$/, ".d.css.ts"); 11 | var formatExportTypeFileName = (file) => basename(file.replace(".ts", "")); 12 | var ensureDirectoryExists = async (file) => { 13 | await mkdir(dirname(file), { recursive: true }); 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | shellEmulator: true 2 | packages: 3 | - packages/** 4 | - www 5 | - '!packages/**/test/**' 6 | onlyBuiltDependencies: 7 | - '@biomejs/biome' 8 | - '@swc/core' 9 | - esbuild 10 | - sharp 11 | -------------------------------------------------------------------------------- /scripts/inject-license.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import fs from 'node:fs'; 4 | 5 | // settings 6 | const LB_RE = /\n/g; 7 | const license = fs 8 | .readFileSync(new URL('../LICENSE', import.meta.url), 'utf8') 9 | .trim() 10 | .replace(LB_RE, '\n * '); 11 | 12 | // run 13 | const cwd = new URL(`file://${process.env.INIT_CWD ?? process.cwd()}/`); 14 | const [, , module, targets] = process.argv; 15 | 16 | for (const target of targets.split(',')) { 17 | const filepath = new URL(target, cwd); 18 | const contents = fs.readFileSync(filepath, 'utf8'); 19 | 20 | fs.writeFileSync( 21 | filepath, 22 | `/** 23 | * @module ${module} 24 | * @license ${license} 25 | */ 26 | ${contents}`, 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /stylelint.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('stylelint').Config} */ 2 | export default { 3 | extends: ['stylelint-config-standard'], 4 | plugins: ['stylelint-order'], 5 | ignoreFiles: ['**/*.js', '**/*.ts', '**/*.tsx', '**/test/*'], 6 | rules: { 7 | 'alpha-value-notation': 'number', 8 | 'declaration-block-no-redundant-longhand-properties': null, // this is fine 9 | 'hue-degree-notation': 'number', 10 | 'lightness-notation': 'number', 11 | 'no-descending-specificity': null, // this often leads to unsolvable riddles 12 | 'number-max-precision': null, // in the running for dumbest rule ever created 13 | 'order/properties-alphabetical-order': true, // let me find things 14 | 'property-no-vendor-prefix': null, // we still need this 15 | 'selector-class-pattern': null, 16 | 'selector-pseudo-class-no-unknown': null, // stylelint considers the :global selector unknown 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowArbitraryExtensions": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "esModuleInterop": true, 7 | "lib": ["ESNext"], 8 | "module": "NodeNext", 9 | "moduleResolution": "nodenext", 10 | "noEmitOnError": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "noUncheckedIndexedAccess": true, 13 | "noUnusedLocals": true, 14 | "noUnusedParameters": true, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "sourceMap": true, 18 | "strict": true, 19 | "target": "ESNext", 20 | "verbatimModuleSyntax": true 21 | }, 22 | "exclude": ["**/dist/*", "**/node_modules/*"] 23 | } 24 | -------------------------------------------------------------------------------- /tsconfig.react.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "lib": ["DOM", "ESNext"], 5 | "jsx": "react-jsx", 6 | "jsxImportSource": "react" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "tasks": { 4 | "build": { 5 | "dependsOn": ["^build"] 6 | }, 7 | "build:app": { 8 | "dependsOn": ["build"] 9 | }, 10 | "format": { 11 | "dependsOn": ["^format"] 12 | }, 13 | "lint": { 14 | "inputs": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.css", "**/biome.json"], 15 | "dependsOn": ["build"] 16 | }, 17 | "test": { 18 | "dependsOn": ["build"] 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /www/.gitignore: -------------------------------------------------------------------------------- 1 | /.astro 2 | /.cache 3 | /build 4 | .env 5 | -------------------------------------------------------------------------------- /www/README.md: -------------------------------------------------------------------------------- 1 | # ⛋ www + docs 2 | 3 | ```sh 4 | pnpm i 5 | pnpm run dev 6 | ``` 7 | -------------------------------------------------------------------------------- /www/astro.config.ts: -------------------------------------------------------------------------------- 1 | import react from '@astrojs/react'; 2 | import sitemap from '@astrojs/sitemap'; 3 | import mdx from '@astrojs/mdx'; 4 | import { defineConfig } from 'astro/config'; 5 | import rehypeAutolinkHeadings from 'rehype-autolink-headings'; 6 | import rehypeSlug from 'rehype-slug'; 7 | import remarkDirective from 'remark-directive'; 8 | import rehypeAutoToc from './src/plugins/rehype-auto-toc.js'; 9 | import remarkVitepress from './src/plugins/remark-vitepress.js'; 10 | 11 | // https://astro.build/config 12 | export default defineConfig({ 13 | integrations: [react(), mdx(), sitemap()], 14 | site: 'https://terrazzo.app', 15 | devToolbar: { 16 | enabled: false, 17 | }, 18 | markdown: { 19 | shikiConfig: { 20 | theme: 'ayu-dark', 21 | }, 22 | remarkRehype: { 23 | allowDangerousHtml: true, 24 | }, 25 | remarkPlugins: [remarkDirective, remarkVitepress], 26 | rehypePlugins: [rehypeSlug, rehypeAutolinkHeadings, rehypeAutoToc], 27 | }, 28 | }); 29 | -------------------------------------------------------------------------------- /www/biome.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", 3 | "extends": ["../biome.json"] 4 | } 5 | -------------------------------------------------------------------------------- /www/public/assets/apple-hig-typography.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/apple-hig-typography.png -------------------------------------------------------------------------------- /www/public/assets/cli-init.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/cli-init.png -------------------------------------------------------------------------------- /www/public/assets/colorpicker-gamut.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/colorpicker-gamut.png -------------------------------------------------------------------------------- /www/public/assets/github-themes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/github-themes.png -------------------------------------------------------------------------------- /www/public/assets/radix-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/radix-colors.png -------------------------------------------------------------------------------- /www/public/assets/social-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/social-image.png -------------------------------------------------------------------------------- /www/public/assets/tooltip-dialog-light-dark-mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/terrazzoapp/terrazzo/ac939ed496a483874e2fc6ffb949cc70f647593f/www/public/assets/tooltip-dialog-light-dark-mode.png -------------------------------------------------------------------------------- /www/public/favicon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /www/public/robots.txt: -------------------------------------------------------------------------------- 1 | # Example: Allow all bots to scan and index your site. 2 | # Full syntax: https://developers.google.com/search/docs/advanced/robots/create-robots-txt 3 | User-agent: * 4 | Allow: / 5 | -------------------------------------------------------------------------------- /www/src/components/tri.astro: -------------------------------------------------------------------------------- 1 | --- 2 | export interface Props { 3 | color?: string; 4 | style?: string; 5 | } 6 | 7 | const { color = 'var(--tz-color-base-gray-100)', style } = Astro.props; 8 | --- 9 | 10 | 11 | 12 | 27 | -------------------------------------------------------------------------------- /www/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /www/src/layouts/default.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import HeadMeta from '../components/head-meta.astro'; 3 | import MainFooter from '../components/main-footer.astro'; 4 | import MainNav from '../components/main-nav.astro'; 5 | 6 | const { title, description, socialImage } = Astro.props; 7 | --- 8 | 9 | 10 | 11 | 16 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /www/src/lib/navigation.ts: -------------------------------------------------------------------------------- 1 | export function getAriaCurrent(testPathname: string, currentPathname: string | URL): 'page' | 'location' | undefined { 2 | const currentLocation = new URL(currentPathname); 3 | if (currentLocation.pathname === testPathname) { 4 | return 'page'; 5 | } 6 | if (testPathname !== '/' && currentLocation.pathname.startsWith(testPathname)) { 7 | return 'location'; 8 | } 9 | return undefined; 10 | } 11 | -------------------------------------------------------------------------------- /www/src/pages/docs/cli/integrations/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Custom Integration 3 | layout: ../../../../layouts/docs.astro 4 | --- 5 | 6 | # Custom Integration 7 | 8 | Building a custom integration is easier than you might think! See the [Plugin API docs](/docs/cli/api/plugin-development) for instructions on how to build your own Terrazzo plugin from scratch. 9 | -------------------------------------------------------------------------------- /www/src/pages/docs/cli/integrations/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Integrations 3 | layout: ../../../../layouts/docs.astro 4 | --- 5 | 6 | # Integrations 7 | 8 | - [**CSS**](/docs/cli/integrations/css) 9 | - [**JS/TS**](/docs/cli/integrations/js) 10 | - [**JSON/Native**](/docs/cli/integrations/json) 11 | - [**Sass**](/docs/cli/integrations/sass) 12 | - [**Tailwind**](/docs/cli/integrations/tailwind) 13 | -------------------------------------------------------------------------------- /www/src/pages/docs/cli/integrations/swift.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: CSS 3 | layout: ../../../../layouts/docs.astro 4 | --- 5 | 6 | # Swift 7 | 8 | Generate Swift asset catalogs from DTCG tokens. 9 | 10 | ## Setup 11 | 12 | Requires [Node.js 20 or later](https://nodejs.org). With that installed, and a `package.json`, run: 13 | 14 | ```sh 15 | npm i -D @terrazzo/cli @terrazzo/plugin-swift 16 | ``` 17 | 18 | Add a `terrazzo.config.js` to the root of your project with: 19 | 20 | ```ts 21 | import { defineConfig } from "@terrazzo/cli"; 22 | import swift from "@terrazzo/plugin-swift"; 23 | 24 | export default defineConfig({ 25 | outDir: "./tokens/", 26 | plugins: [ 27 | swift({ 28 | catalogName: "Tokens", 29 | }), 30 | ], 31 | }); 32 | ``` 33 | 34 | Lastly, run: 35 | 36 | ```sh 37 | npx tz build 38 | ``` 39 | 40 | And you’ll see a `./tokens/Tokens.xcassets` catalog generated in your project. [Import it](https://developer.apple.com/documentation/xcode/managing-assets-with-asset-catalogs) into your project and you’ll have access to all your tokens! 41 | -------------------------------------------------------------------------------- /www/src/pages/docs/components/demo.mdx: -------------------------------------------------------------------------------- 1 | --- 2 | title: Demo 3 | layout: ../../../layouts/docs.astro 4 | --- 5 | 6 | import DemoButton from "./demos/demo-button.tsx"; 7 | 8 | # Demo 9 | 10 | A component for showing a component sandbox and displaying a copyable code snippet next to it. 11 | 12 | ## Setup 13 | 14 | :::code-group 15 | 16 | ```sh npm 17 | npm i @terrazzo/tiles 18 | ``` 19 | 20 | ```sh pnpm 21 | pnpm i @terrazzo/tiles 22 | ``` 23 | 24 | ::: 25 | 26 | ## Demo 27 | 28 | 29 | -------------------------------------------------------------------------------- /www/src/pages/docs/components/demos/color-picker-basic.tsx: -------------------------------------------------------------------------------- 1 | import ColorPicker from '@terrazzo/react-color-picker'; 2 | import { Demo } from '@terrazzo/tiles'; 3 | import useColor from '@terrazzo/use-color'; 4 | 5 | export default function ColorPickerDemo() { 6 | const [color, setColor] = useColor('#663399'); 7 | 8 | return ( 9 | 20 | ); 21 | }` 22 | } 23 | > 24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /www/src/pages/docs/components/demos/demo-button.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Demo } from '@terrazzo/tiles'; 2 | 3 | export default function DemoButtonDemo() { 4 | return ( 5 | Save\`}> 15 | 16 | 17 | ); 18 | }` 19 | } 20 | > 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /www/src/pages/docs/components/demos/slider.tsx: -------------------------------------------------------------------------------- 1 | import { Demo, Slider } from '@terrazzo/tiles'; 2 | import { useState } from 'react'; 3 | 4 | export default function ColorPickerDemo() { 5 | const [value, setValue] = useState(50); 6 | 7 | return ( 8 | 17 | ); 18 | }` 19 | } 20 | > 21 | 22 | 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /www/src/pages/docs/components/tiles.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Tiles 3 | layout: ../../../layouts/docs.astro 4 | --- 5 | 6 | # Tiles 7 | 8 | Tiles is Terrazzo’s open source React component library used to build everything you see. Its main goal is to display and showcase an existing design system, so it’s a great fit for any kind of documentation, design-related or not. 9 | 10 | ## Setup 11 | 12 | :::code-group 13 | 14 | ```sh npm 15 | npm i @terrazzo/react-color-picker 16 | ``` 17 | 18 | ```sh pnpm 19 | pnpm i @terrazzo/react-color-picker 20 | ``` 21 | 22 | ::: 23 | 24 | ## Storybook 25 | 26 | [terrazzo-storybook.pages.dev](https://terrazzo-storybook.pages.dev) 27 | -------------------------------------------------------------------------------- /www/src/pages/docs/guides/dtcg.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: DTCG Tokens 3 | layout: ../../../layouts/docs.astro 4 | --- 5 | 6 | # DTCG Tokens 7 | 8 | The DTCG format is a [W3C Community Group specification](https://www.designtokens.org/) started in 2020 and aims to outline a standard, universal design token format that works for all forms of digital design (including web, print, native apps, and beyond). 9 | 10 | DTCG tokens are stored in JSON, and look something like the following: 11 | 12 | ```json 13 | { 14 | "rebeccapurple": { 15 | "$type": "color", 16 | "$value": { 17 | "colorSpace": "srgb", 18 | "components": [0.4, 0.2, 0.6] 19 | } 20 | } 21 | } 22 | ``` 23 | 24 | By storing tokens in a universal format like JSON, you can centrally manage them and generate code for any output target including web and native apps. 25 | 26 | ## Format 27 | 28 | You can [find the official format here](https://tr.designtokens.org/format/). But for convenience, we also have [a list of token types](/docs/reference/tokens) Terrazzo supports. 29 | -------------------------------------------------------------------------------- /www/src/pages/docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Terrazzo (Beta) 3 | layout: ../../layouts/docs.astro 4 | --- 5 | 6 | # Terrazzo Token Tools (Beta) 7 | 8 | Terrazzo is a work-in-progress, but comprises: 9 | 10 | - [**Terrazzo CLI**](/docs/cli) (formerly known as “Cobalt UI”): use [DTCG](https://designtokens.org) tokens 11 | - [**Terrazzo Tiles DS**](/docs/components): Design system for _documenting_ design systems 12 | - **Token Lab** _(Coming Soon)_: generate, manage, and save token sets 13 | -------------------------------------------------------------------------------- /www/src/pages/docs/reference/about.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About Terrazzo 3 | layout: ../../../layouts/docs.astro 4 | --- 5 | 6 | # About Terrazzo 7 | 8 | Terrazzo is a free, MIT-licensed open-source design tool built by @drwpow, @ntassone, and [many other contributors](https://github.com/drwpow/cobalt-ui). 9 | 10 | It started out as “Cobalt” in 2021 as tooling for the [W3C Design Token Community Group (DTCG)](https://designtokens.org) format. It was clear early on that a visual token editor was needed, but Drew found the CLI to be as big of a project as he could handle on his own. So along came Nick to design and envision Token Lab in 2024. 11 | 12 | The project was renamed to “Terrazzo” to better reflect how lots of colorful little shards of tokens fit together to create a beautiful mosaic when all put together. Also because [lots of things are named “Cobalt”](https://github.com/search?q=cobalt&type=repositories) (or similar-sounding names like “[Cobol](https://en.wikipedia.org/wiki/COBOL)”) and it became confusing. 13 | -------------------------------------------------------------------------------- /www/src/pages/docs/token-lab/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Token Lab 3 | layout: ../../../layouts/docs.astro 4 | --- 5 | 6 | # Token Lab 7 | 8 | The token lab is a thing 9 | -------------------------------------------------------------------------------- /www/src/pages/docs/tokens/modes.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Thinking in Modes 3 | layout: ../../../layouts/docs.astro 4 | --- 5 | 6 | # Thinking in Modes 7 | -------------------------------------------------------------------------------- /www/src/pages/token-lab/index.astro: -------------------------------------------------------------------------------- 1 | --- 2 | import DefaultLayout from '../../layouts/default.astro'; 3 | --- 4 | 5 | 6 |

Token Lab

7 |
8 | -------------------------------------------------------------------------------- /www/src/scripts/copy-code.js: -------------------------------------------------------------------------------- 1 | const MESSAGE_DELAY = 1500; 2 | 3 | for (const btn of document.querySelectorAll('button[data-code]')) { 4 | let t; 5 | 6 | btn.addEventListener('click', async () => { 7 | await navigator.clipboard.writeText(btn.getAttribute('data-code')); 8 | const contentBefore = btn.innerHTML; 9 | const labelBefore = btn.getAttribute('aria-label'); 10 | 11 | btn.setAttribute('aria-label', 'Copied!'); 12 | btn.setAttribute('aria-live', 'polite'); 13 | btn.innerHTML = 14 | ''; 15 | 16 | t = setTimeout(() => { 17 | clearTimeout(t); 18 | btn.setAttribute('aria-label', labelBefore); 19 | btn.removeAttribute('aria-live'); 20 | btn.innerHTML = contentBefore; 21 | }, MESSAGE_DELAY); 22 | }); 23 | } 24 | -------------------------------------------------------------------------------- /www/stylelint.config.js: -------------------------------------------------------------------------------- 1 | import base from '../stylelint.config.js'; 2 | 3 | /** @type {import('stylelint').Config} */ 4 | export default { 5 | ...base, 6 | }; 7 | -------------------------------------------------------------------------------- /www/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "astro/tsconfigs/base", 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "baseUrl": "/Users/drew/Sites/terrazzoapp/terrazzo/www", 6 | "esModuleInterop": true, 7 | "forceConsistentCasingInFileNames": true, 8 | "isolatedModules": true, 9 | "jsx": "react-jsx", 10 | "jsxImportSource": "react", 11 | "lib": ["DOM", "DOM.Iterable", "ES2022"], 12 | "module": "NodeNext", 13 | "moduleResolution": "nodenext", 14 | "noEmit": true, 15 | "noEmitOnError": false, 16 | "resolveJsonModule": true, 17 | "types": ["vite/client"] 18 | }, 19 | "include": ["./src./"] 20 | } 21 | --------------------------------------------------------------------------------