diff --git a/README.md b/README.md index 1c652b88583efe7db3848a7ff9f036266f299896..02f43211a4ab5fb574f428bf8561f27da2580958 100644 --- a/README.md +++ b/README.md @@ -18,9 +18,11 @@ Import the [@sikt/sds-core](./packages/core/) package. It contains, among other @import url("@sikt/sds-core"); ``` -**Note** This package may be required whether you use the React components or not. Unless you import design tokens directly and make the setup yourself. - -**Note** Design Tokens can be imported in the needed format directly from [@sikt/sds-tokens](./packages/tokens/). +> **Note** +> +> - @sikt/sds-core is required wheter or not you use the React components. Unless you import design tokens directly and make the setup yourself. +> - Design Tokens can be imported in the needed format directly from [@sikt/sds-tokens](./packages/tokens/). +> - Design Tokens can also be used via Tailwind. [Click here for more information](?path=/docs/tailwind-readme--docs) ### React @@ -31,15 +33,16 @@ import { PrimaryButton } from "@sikt/sds-button"; import "@sikt/sds-button/dist/index.css"; return ( - <PrimaryButton>Hello, World!</PrimaryButton>; +<PrimaryButton>Hello, World!</PrimaryButton>; ); + ``` ### Vue -See custom markup below or go to the [Vue component example](./docs/VUE.md). +See custom markup below, [Tailwind](?path=/docs/tailwind-readme--docs), or go to the [Vue component example](./docs/VUE.md). -### Stylesheets & custom markup +#### Stylesheets & custom markup If you are not able to use the React components, for example if you are using Vue, Angular, Svelte or even PHP 😱. You can still benefit by using the stylesheets and building your own markup and components. @@ -57,6 +60,10 @@ Create custom markup: </button> ``` +### Tailwind + +You can import a Tailwind preset CSS Tokens from the Design System. [Click here for more information](?path=/docs/tailwind-readme--docs) + ## Accessibility [Accessibility](./docs/A11Y.md) diff --git a/package-lock.json b/package-lock.json index 5afbb7252e2665e40dd70ef716bca4310fce2406..e712e6205d3dfac1829766f53edeee9ca57c8ac5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "prettier": "^3.3.3", "rimraf": "^6.0.1", "stylelint": "^16.8.1", + "tailwindcss": "^3.4.9", "terser": "^5.31.5", "ts-jest": "^29.2.4", "tsup": "^8.2.4", @@ -130,6 +131,19 @@ "dev": true, "license": "MIT" }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.0", "license": "Apache-2.0", @@ -9537,6 +9551,10 @@ "resolved": "packages/tabs", "link": true }, + "node_modules/@sikt/sds-tailwind": { + "resolved": "packages/tailwind", + "link": true + }, "node_modules/@sikt/sds-toggle": { "resolved": "packages/toggle", "link": true @@ -15020,6 +15038,16 @@ "node": ">=6" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/camelcase-keys": { "version": "6.2.2", "license": "MIT", @@ -17976,6 +18004,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/diff": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", @@ -18014,6 +18049,13 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, "node_modules/doctrine": { "version": "3.0.0", "license": "Apache-2.0", @@ -32801,6 +32843,16 @@ "node": ">=0.10.0" } }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.1", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", @@ -34255,6 +34307,26 @@ "postcss": "^8.0.0" } }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, "node_modules/postcss-load-config": { "version": "3.1.4", "dev": true, @@ -40022,6 +40094,124 @@ "node": ">=8" } }, + "node_modules/tailwindcss": { + "version": "3.4.9", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.9.tgz", + "integrity": "sha512-1SEOvRr6sSdV5IDf9iC+NU4dhwdqzF4zKKq3sAbasUWHEM6lsMhX+eNN5gkPx1BvLFEnZQEUFbXnGj8Qlp83Pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/tailwindcss/node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/tailwindcss/node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/tapable": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", @@ -43377,6 +43567,22 @@ "react-dom": "^18.0.0" } }, + "packages/tailwind": { + "name": "@sikt/sds-tailwind", + "version": "0.1.0", + "license": "UNLICENSED", + "dependencies": { + "@sikt/sds-core": "^3.0.0" + }, + "peerDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "clsx": "^1.0.0 || ^2.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "tailwindcss": "^3.0.0" + } + }, "packages/toggle": { "name": "@sikt/sds-toggle", "version": "3.0.2", diff --git a/package.json b/package.json index d3d0aa4c9523c96f32950ab6d0e9b2edf280efb4..5ca9279bedaf24dff41ed1ed910d7590ed924ed8 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "prettier": "^3.3.3", "rimraf": "^6.0.1", "stylelint": "^16.8.1", + "tailwindcss": "^3.4.9", "terser": "^5.31.5", "ts-jest": "^29.2.4", "tsup": "^8.2.4", diff --git a/packages/tailwind/CHANGELOG.md b/packages/tailwind/CHANGELOG.md new file mode 100644 index 0000000000000000000000000000000000000000..825c32f0d03d98995ebe3e6d797f14daf2df51d9 --- /dev/null +++ b/packages/tailwind/CHANGELOG.md @@ -0,0 +1 @@ +# Changelog diff --git a/packages/tailwind/README.md b/packages/tailwind/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a01246c41bced81b9f2d07a1c3601b2298a5c2c1 --- /dev/null +++ b/packages/tailwind/README.md @@ -0,0 +1,38 @@ +# `@sikt/sds-tailwind` + +## About + +All tokens are configured as a preset to import into existing tailwind configurations. + +## Prerequisites + +A working Tailwind installation and configuration. + +## Consume + +```sh +npm i -s @sikt/sds-tailwind +``` + +### tailwind.config.ts + +```ts +import * as tailwind from "@sikt/sds-tailwind"; +import { Config } from "tailwindcss"; + +const config: Config = { + presets: [tailwind], + content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"], +}; + +export default config; +``` + +### React + +```jsx +<div className="flex flex-row gap-xs m-xl hover:bg-brand-primary-strong text-brand-accent-strong"> + <div>foo</div> + <div>bar</div> +</div> +``` diff --git a/packages/tailwind/index.ts b/packages/tailwind/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..4048f941a9c1f7cf35b580c1c828757c118e0d32 --- /dev/null +++ b/packages/tailwind/index.ts @@ -0,0 +1,3 @@ +import * as config from "./src/tailwind.config"; + +module.exports = { ...config }; diff --git a/packages/tailwind/package.json b/packages/tailwind/package.json new file mode 100644 index 0000000000000000000000000000000000000000..cd08b55b3486b64e89c69ca96ac642d66611f8ce --- /dev/null +++ b/packages/tailwind/package.json @@ -0,0 +1,41 @@ +{ + "name": "@sikt/sds-tailwind", + "version": "0.1.0", + "license": "UNLICENSED", + "type": "commonjs", + "main": "dist/index.js", + "module": "dist/index.mjs", + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "./dist/*": "./dist/*" + }, + "types": "dist/index.d.ts", + "files": [ + "CHANGELOG.md", + "dist", + "README.md" + ], + "scripts": { + "build": "tsup" + }, + "dependencies": { + "@sikt/sds-core": "^3.0.0" + }, + "peerDependencies": { + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "clsx": "^1.0.0 || ^2.0.0", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "tailwindcss": "^3.0.0" + } +} diff --git a/packages/tailwind/src/tailwind.config.ts b/packages/tailwind/src/tailwind.config.ts new file mode 100644 index 0000000000000000000000000000000000000000..ce5c332cabdb5d527c0a7164dee81bd36f5933fe --- /dev/null +++ b/packages/tailwind/src/tailwind.config.ts @@ -0,0 +1,231 @@ +import { default as dark } from "@sikt/sds-tokens/dist/js/color.dark"; +import * as tokens from "@sikt/sds-tokens/dist/js/tokens"; +import { Config } from "tailwindcss"; + +export type TailwindType = typeof tailwindConfig; + +declare interface DesignToken { + value?: any; + type?: string; + comment?: string; + name?: string; + themeable?: boolean; + attributes?: Record<string, unknown>; + [key: string]: any; +} + +const extractTokenValues = (input: DesignToken) => { + const values: { [key: string]: any } = {}; + + const extractValues = (obj: DesignToken) => { + if (obj && typeof obj === "object" && "value" in obj) { + return obj.value; + } + + const result: { [key: string]: any } = {}; + Object.keys(obj).forEach((key) => { + result[key] = extractValues(obj[key]); + }); + return result; + }; + + Object.keys(input).forEach((key) => { + values[key] = extractValues(input[key]); + }); + + return values; +}; + +const tailwindConfig: Config = { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx,mdx}"], + theme: { + extend: { + colors: { + "page-default-light": "#f7f7f7", + overlay: "#0b013225", + ...extractTokenValues(tokens.default.color.layout.background), + sikt: extractTokenValues(tokens.default.color.text), + brand: extractTokenValues(tokens.default.color.brand), + support: extractTokenValues(tokens.default.color.support), + page: extractTokenValues(tokens.default.color.layout.page), + focus: extractTokenValues(tokens.default.color.layout.focus), + divider: extractTokenValues(tokens.default.color.layout.divider), + interaction: extractTokenValues(tokens.default.color.interaction), + dark: { + ...extractTokenValues(dark.color.layout.background), + sikt: extractTokenValues(dark.color.text), + brand: extractTokenValues(dark.color.brand), + support: extractTokenValues(dark.color.support), + page: extractTokenValues(dark.color.layout.page), + focus: extractTokenValues(dark.color.layout.focus), + divider: extractTokenValues(dark.color.layout.divider), + interaction: extractTokenValues(dark.color.interaction), + }, + }, + spacing: { + ...extractTokenValues(tokens.default.base.size), + relative: extractTokenValues( + tokens.default.base["size-relative"], + ) as never as string, + }, + screens: { + ...extractTokenValues(tokens.default.base.breakpoint), + }, + padding: { + "mobile-container": + "var(--mobile-container-padding)" /* This should be defined in the custom stylesheets */, + menu: "var(--menu-padding)", + minimal: "var(--sds-space-padding-minimal)" /* 4px (0.25rem) */, + tiny: "var(--sds-space-padding-tiny)" /* 8px (0.5rem) */, + small: "var(--sds-space-padding-small)" /* 12px (0.75rem) */, + medium: + "var(--sds-space-padding-medium)" /* 12px (0.75rem), 16px (1rem), 24px (1.5rem) */, + large: + "var(--sds-space-padding-large)" /* 24px (1.5rem), 32px (2rem), 48px */, + huge: "var(--sds-space-padding-huge)" /* 32px (2rem), 48px (3rem), 96px (6rem) */, + }, + + gap: { + ...extractTokenValues(tokens.default.space.gap), + }, + borderRadius: { + ...extractTokenValues(tokens.default.space.border.radius), + }, + borderWidth: { + ...extractTokenValues(tokens.default.space.border.weight), + }, + zIndex: { + ...extractTokenValues(tokens.default.base.zindex), + }, + boxShadow: { + ...extractTokenValues(tokens.default.effect.shadow), + small: "0px 2px 6px 0px rgba(0,0,0,0.05)", + }, + animation: { + ...extractTokenValues(tokens.default.effect.animation), + }, + transitionDuration: { + ...extractTokenValues(tokens.default.effect.animation), + }, + /* SDS Typography */ + fontFamily: { + custom: [ + "Haffer", + "Helvetica Neue", + "Helvetica", + "Arial", + "sans-serif", + ], + }, + fontWeight: { + normal: "var(--sds-typography-weight-regular)", + bold: "var(--sds-typography-weight-bold)", + }, + fontSize: { + /* Heading */ + "heading-huge": [ + "var(--sds-typography-heading-fontsize-huge)", + { + lineHeight: "var(--sds-typography-heading-lineheight-huge)", + }, + ], + "heading-xlarge": [ + "var(--sds-typography-heading-fontsize-xlarge)", + { + lineHeight: "var(--sds-typography-heading-lineheight-xlarge)", + }, + ], + "heading-large": [ + "var(--sds-typography-heading-fontsize-large)", + { + lineHeight: "var(--sds-typography-heading-lineheight-large)", + }, + ], + "heading-medium": [ + "var(--sds-typography-heading-fontsize-medium)", + { + lineHeight: "var(--sds-typography-heading-lineheight-medium)", + }, + ], + "heading-small": [ + "var(--sds-typography-heading-fontsize-small)", + { + lineHeight: "var(--sds-typography-heading-lineheight-small)", + }, + ], + "heading-paragraph": [ + "var(--sds-typography-heading-fontsize-paragraph)", + { + lineHeight: "var(--sds-typography-heading-lineheight-paragraph)", + }, + ], + "heading-overline": [ + "var(--sds-typography-heading-fontsize-overline)", + { + lineHeight: "var(--sds-typography-heading-lineheight-overline)", + }, + ], + + /* Body */ + "body-lead": [ + "var(--sds-typography-body-fontsize-lead)", + { + lineHeight: "var(--sds-typography-body-lineheight-lead)", + }, + ], + "body-large": [ + "var(--sds-typography-body-fontsize-large)", + { + lineHeight: "var(--sds-typography-body-lineheight-large)", + }, + ], + "body-regular": [ + "var(--sds-typography-body-fontsize-regular)", + { + lineHeight: "var(--sds-typography-body-lineheight-regular)", + }, + ], + "body-small": [ + "var(--sds-typography-body-fontsize-small)", + { + lineHeight: "var(--sds-typography-body-lineheight-small)", + }, + ], + "body-emphasis": [ + "var(--sds-typography-body-fontsize-emphasis)", + { + lineHeight: "var(--sds-typography-body-lineheight-regular)", + }, + ], + "body-strong": [ + "var(--sds-typography-body-fontsize-strong)", + { + lineHeight: "var(--sds-typography-body-lineheight-regular)", + }, + ], + "body-quote": [ + "var(--sds-typography-body-fontsize-quote)", + { + lineHeight: "var(--sds-typography-body-lineheight-regular)", + }, + ], + "body-code": [ + "var(--sds-typography-body-fontsize-code)", + { + lineHeight: "var(--sds-typography-body-lineheight-regular)", + }, + ], + }, + + transitionTimingFunction: { + default: "var(--sds-effect-animation-easing-default)" /* ease-in-out */, + "emphasized-entry": + "cubic-bezier(0.05, 0.7, 0.1, 1)" /* From Material 3 */, + "emphasized-exit": "cubic-bezier:(0.3, 0, 0.8, 0.15)", + }, + }, + }, + plugins: [], +}; + +export default tailwindConfig; diff --git a/packages/tailwind/stories/Changelog.mdx b/packages/tailwind/stories/Changelog.mdx new file mode 100644 index 0000000000000000000000000000000000000000..1f951bda7e4fe675b132fd53135e80b0bd6d6683 --- /dev/null +++ b/packages/tailwind/stories/Changelog.mdx @@ -0,0 +1,6 @@ +import { Markdown, Meta } from "@storybook/blocks"; +import Changelog from "../CHANGELOG.md?raw"; + +<Meta title="Tailwind/Changelog" /> + +<Markdown>{Changelog}</Markdown> diff --git a/packages/tailwind/stories/Readme.mdx b/packages/tailwind/stories/Readme.mdx new file mode 100644 index 0000000000000000000000000000000000000000..e1e1c234f25dbbacae404873b2d1c6d831d8b17e --- /dev/null +++ b/packages/tailwind/stories/Readme.mdx @@ -0,0 +1,6 @@ +import { Markdown, Meta } from "@storybook/blocks"; +import Readme from "../README.md?raw"; + +<Meta title="Tailwind/Readme" /> + +<Markdown>{Readme}</Markdown>