From be1c9d000b84688b53ec44059063fed560ac5c30 Mon Sep 17 00:00:00 2001 From: Nathan Vasse Date: Wed, 4 Jan 2023 15:52:24 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(react)=20add=20tokens.ts=20files=20ha?= =?UTF-8?q?ndling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These files will be used to define the custom design tokens per components. They are automatically aggregated by the packages/react/cunningham.ts file, this is why handling typescript config file was important. --- packages/react/.eslintignore | 3 +- packages/react/.eslintrc.json | 7 ++-- packages/react/.storybook/main.cjs | 12 ++++++- packages/react/cunningham.cjs | 7 ---- packages/react/cunningham.ts | 32 ++++++++++++++++++ packages/react/package.json | 9 +++-- .../react/src/components/Button/index.scss | 6 ++-- .../src/components/Button/index.spec.tsx | 12 +++++-- .../src/components/Button/index.stories.tsx | 6 ++-- .../react/src/components/Button/index.tsx | 8 +++-- .../react/src/components/Button/tokens.ts | 8 +++++ packages/react/src/cunningham-tokens.css | 7 ++++ packages/react/src/cunningham-tokens.ts | 1 + packages/react/src/index.scss | 1 + packages/react/src/tests/Theme.ts | 33 +++++++++++++++++++ packages/react/tsconfig.eslint.json | 8 +++++ packages/react/tsconfig.json | 5 +-- packages/react/vite.config.ts | 9 ++++- packages/tokens/src/bin/TokensGenerator.ts | 2 +- 19 files changed, 147 insertions(+), 29 deletions(-) delete mode 100644 packages/react/cunningham.cjs create mode 100644 packages/react/cunningham.ts create mode 100644 packages/react/src/components/Button/tokens.ts create mode 100644 packages/react/src/cunningham-tokens.css create mode 100644 packages/react/src/cunningham-tokens.ts create mode 100644 packages/react/src/tests/Theme.ts create mode 100644 packages/react/tsconfig.eslint.json diff --git a/packages/react/.eslintignore b/packages/react/.eslintignore index 76add87..7dba084 100644 --- a/packages/react/.eslintignore +++ b/packages/react/.eslintignore @@ -1,2 +1,3 @@ node_modules -dist \ No newline at end of file +dist +src/cunningham-tokens.ts \ No newline at end of file diff --git a/packages/react/.eslintrc.json b/packages/react/.eslintrc.json index a1273cc..8b99351 100644 --- a/packages/react/.eslintrc.json +++ b/packages/react/.eslintrc.json @@ -4,12 +4,9 @@ "custom" ], "parserOptions": { - "project": [ - "./tsconfig.json", - "./tsconfig.node.json" - ] + "project": "./tsconfig.eslint.json" }, "rules": { - "import/no-extraneous-dependencies": ["error", {"devDependencies": ["vite.config.ts", "**/*.stories.tsx", "**/*.spec.tsx"]}] + "import/no-extraneous-dependencies": ["error", {"devDependencies": ["vite.config.ts", "cunningham.ts","**/*.stories.tsx", "**/*.spec.tsx"]}] } } \ No newline at end of file diff --git a/packages/react/.storybook/main.cjs b/packages/react/.storybook/main.cjs index 8d48ff4..a89af0f 100644 --- a/packages/react/.storybook/main.cjs +++ b/packages/react/.storybook/main.cjs @@ -1,3 +1,8 @@ +const viteTsconfig = require('vite-tsconfig-paths'); +const tsconfigPaths = viteTsconfig.default; + +const { mergeConfig } = require('vite'); + module.exports = { "stories": [ "../src/**/*.stories.mdx", @@ -18,5 +23,10 @@ module.exports = { ], "features": { "storyStoreV7": true - } + }, + async viteFinal(config) { + return mergeConfig(config, { + plugins: [tsconfigPaths()], + }); + }, } \ No newline at end of file diff --git a/packages/react/cunningham.cjs b/packages/react/cunningham.cjs deleted file mode 100644 index 80d7028..0000000 --- a/packages/react/cunningham.cjs +++ /dev/null @@ -1,7 +0,0 @@ -module.exports = { - theme: { - colors: { - primary: 'grey' - }, - }, -}; diff --git a/packages/react/cunningham.ts b/packages/react/cunningham.ts new file mode 100644 index 0000000..985a8c7 --- /dev/null +++ b/packages/react/cunningham.ts @@ -0,0 +1,32 @@ +import { glob } from "glob"; +import { defaultTokens } from "@openfun/cunningham-tokens"; + +/** + * This scripts dynamically imports all tokens.ts files from components and load them in a single object that will be + * exported as the local configuration of cunningham under the `components` key. + * + * Hence, any consumers of this package will be able to customize the tokens of the components they use by overriding + * them in their own local configuration file. ( cunningham.ts|js ) + */ +const components: any = {}; +const files = glob.sync("src/components/**/tokens.ts"); +files.forEach((file) => { + const importPath = "./" + file.replace(/\.ts$/, ""); + const matches = /^.+components\/(.+)\/tokens$/gm.exec(importPath); + let componentName = matches && matches[1]; + if (!componentName) { + throw new Error("Could not find component name from file path " + file); + } + componentName = componentName.toLowerCase(); + + const res = require(importPath); + if (!res.tokens) { + throw new Error("Tokens file does not export tokens " + file); + } + + components[componentName] = res.tokens(defaultTokens); +}); + +export default { + components, +}; diff --git a/packages/react/package.json b/packages/react/package.json index 3ae69f1..8cfcd85 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -20,8 +20,9 @@ ], "scripts": { "lint": "eslint . 'src/**/*.{ts,tsx}'", - "dev": "yarn storybook & nodemon --watch src --ext '*' --exec npm run build", - "build": "tsc && vite build", + "dev": "yarn storybook & nodemon --watch src --ext '*' --ignore src/cunningham-tokens.ts --ignore src/cunningham-tokens.css --exec npm run build", + "build": "tsc && yarn build-theme && vite build", + "build-theme": "cunningham -o src -s html -g css,ts", "preview": "vite preview", "test": "FORCE_COLOR=1 vitest run", "test-watch": "vitest", @@ -39,6 +40,7 @@ }, "devDependencies": { "@babel/core": "7.20.7", + "@openfun/cunningham-tokens": "*", "@openfun/typescript-configs": "*", "@storybook/addon-actions": "6.5.15", "@storybook/addon-essentials": "6.5.15", @@ -53,9 +55,11 @@ "@types/react": "18.0.26", "@types/react-dom": "18.0.10", "@vitejs/plugin-react": "3.0.0", + "@vitest/coverage-c8": "0.26.3", "@vitest/ui": "0.26.2", "babel-loader": "9.1.0", "css-loader": "6.7.3", + "glob": "8.0.3", "jsdom": "20.0.3", "sass": "1.57.1", "sass-loader": "13.2.0", @@ -63,6 +67,7 @@ "typescript": "4.9.4", "vite": "4.0.3", "vite-plugin-dts": "1.7.1", + "vite-tsconfig-paths": "4.0.3", "vitest": "0.26.2" } } diff --git a/packages/react/src/components/Button/index.scss b/packages/react/src/components/Button/index.scss index 6ed37ea..268d9ea 100644 --- a/packages/react/src/components/Button/index.scss +++ b/packages/react/src/components/Button/index.scss @@ -1,8 +1,10 @@ .c__button { - background-color: var(--c--colors--primary); + background-image: var(--c--theme--colors--primary-gradient); padding: 8px 30px; - border-radius: 6px; + border-radius: var(--c--components--button--border-radius); border: none; + box-shadow: var(--c--components--button--shadow); color: white; font-weight: bold; + cursor: pointer; } \ No newline at end of file diff --git a/packages/react/src/components/Button/index.spec.tsx b/packages/react/src/components/Button/index.spec.tsx index 4f3c68a..00edb04 100644 --- a/packages/react/src/components/Button/index.spec.tsx +++ b/packages/react/src/components/Button/index.spec.tsx @@ -1,12 +1,20 @@ import { describe, expect, it } from "vitest"; import { render, screen } from "@testing-library/react"; import React from "react"; +import { buildTheme, loadTokens } from "tests/Theme"; import { Button } from "./index"; describe("); + const button = screen.getByRole("button", { name: "Test button" }); expect(button.classList.contains("c__button")).toBe(true); }); + + it("uses custom token", async () => { + await buildTheme(); + const tokens = await loadTokens(); + expect(tokens.components.button["border-radius"]).toBeDefined(); + expect(tokens.components.button.shadow).toBeDefined(); + }); }); diff --git a/packages/react/src/components/Button/index.stories.tsx b/packages/react/src/components/Button/index.stories.tsx index 3a59f00..daed279 100644 --- a/packages/react/src/components/Button/index.stories.tsx +++ b/packages/react/src/components/Button/index.stories.tsx @@ -7,7 +7,9 @@ export default { component: Button, } as ComponentMeta; -const Template: ComponentStory = () => ; +interface Props extends PropsWithChildren {} + +export const Button = ({ children }: Props) => { + return ; }; diff --git a/packages/react/src/components/Button/tokens.ts b/packages/react/src/components/Button/tokens.ts new file mode 100644 index 0000000..32cb2e5 --- /dev/null +++ b/packages/react/src/components/Button/tokens.ts @@ -0,0 +1,8 @@ +import { DefaultTokens } from "@openfun/cunningham-tokens"; + +export const tokens = (defaults: DefaultTokens) => { + return { + "border-radius": "5px", + shadow: "0px 0px 10px 1px " + defaults.theme.colors.primary + ";", + }; +}; diff --git a/packages/react/src/cunningham-tokens.css b/packages/react/src/cunningham-tokens.css new file mode 100644 index 0000000..7424e1f --- /dev/null +++ b/packages/react/src/cunningham-tokens.css @@ -0,0 +1,7 @@ +html { + --c--theme--colors--primary: #002d7f; + --c--theme--colors--primary-gradient: linear-gradient(90deg,#002d7f,#0069b3); + --c--theme--colors--secondary: #DA0000; + --c--components--button--border-radius: 5px; + --c--components--button--shadow: 0px 0px 10px 1px #002d7f;; +} \ No newline at end of file diff --git a/packages/react/src/cunningham-tokens.ts b/packages/react/src/cunningham-tokens.ts new file mode 100644 index 0000000..0dbd3b1 --- /dev/null +++ b/packages/react/src/cunningham-tokens.ts @@ -0,0 +1 @@ +export const tokens = {"theme":{"colors":{"primary":"#002d7f","primary-gradient":"linear-gradient(90deg,#002d7f,#0069b3)","secondary":"#DA0000"}},"components":{"button":{"border-radius":"5px","shadow":"0px 0px 10px 1px #002d7f;"}}}; \ No newline at end of file diff --git a/packages/react/src/index.scss b/packages/react/src/index.scss index 83b7b4c..756302f 100644 --- a/packages/react/src/index.scss +++ b/packages/react/src/index.scss @@ -2,5 +2,6 @@ @import "@fontsource/roboto/400"; @import "@fontsource/roboto/700"; @import "@fontsource/roboto/900"; +@import "cunningham-tokens"; @import '@openfun/cunningham-tokens/default-tokens'; @import './components/Button'; \ No newline at end of file diff --git a/packages/react/src/tests/Theme.ts b/packages/react/src/tests/Theme.ts new file mode 100644 index 0000000..be297f7 --- /dev/null +++ b/packages/react/src/tests/Theme.ts @@ -0,0 +1,33 @@ +import child_process from "child_process"; +import path from "path"; + +/** + * Run the NPM script 'build-theme' in order to generate the tokens files ( cunningham-token.ts|css ). + * ( The purpose is mainly to generate the tokens files and then verify in tests that custom tokens defined in + * tokens.ts files are correctly taken into account. ) + */ +export const buildTheme = (debug?: boolean) => { + const child = child_process.exec( + "cd " + path.join(__dirname, "..", "..") + " && yarn build-theme" + ); + return new Promise((resolve) => { + child.stdout?.on("data", (data) => { + // eslint-disable-next-line no-console + if (debug) console.log("stdout: " + data); + }); + child.stderr?.on("data", (data) => { + // eslint-disable-next-line no-console + if (debug) console.log("stderr: " + data); + }); + child.on("close", (code) => { + // eslint-disable-next-line no-console + if (debug) console.log("closing code: " + code); + resolve(); + }); + }); +}; + +export const loadTokens = async () => { + const module = await import("../cunningham-tokens"); + return module.tokens; +}; diff --git a/packages/react/tsconfig.eslint.json b/packages/react/tsconfig.eslint.json new file mode 100644 index 0000000..824f560 --- /dev/null +++ b/packages/react/tsconfig.eslint.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.json", + "exclude": [], + "include": [ + "**/*.ts", + "**/*.tsx" + ] +} \ No newline at end of file diff --git a/packages/react/tsconfig.json b/packages/react/tsconfig.json index 88c9b8c..65f4503 100644 --- a/packages/react/tsconfig.json +++ b/packages/react/tsconfig.json @@ -2,8 +2,9 @@ "extends": "@openfun/typescript-configs/react.json", "compilerOptions": { "noEmit": true, + "baseUrl": "./src" }, - "include": ["src"], - "exclude": ["node_modules","dist"], + "include": ["src", "cunningham.ts"], + "exclude": ["node_modules","dist", "**/tokens.ts"], "references": [{ "path": "./tsconfig.node.json" }] } diff --git a/packages/react/vite.config.ts b/packages/react/vite.config.ts index fd0b02b..e66d60c 100644 --- a/packages/react/vite.config.ts +++ b/packages/react/vite.config.ts @@ -1,6 +1,7 @@ import { defineConfig } from "vitest/config"; import react from "@vitejs/plugin-react"; import dts from "vite-plugin-dts"; +import tsconfigPaths from "vite-tsconfig-paths"; // https://vitejs.dev/config/ export default defineConfig({ @@ -24,9 +25,15 @@ export default defineConfig({ }, }, }, - plugins: [dts(), react()], + plugins: [tsconfigPaths(), dts(), react()], test: { environment: "jsdom", + reporters: "verbose", globals: true, + coverage: { + all: true, + include: ["src/**/*.{ts,tsx}"], + exclude: ["**/*.stories.tsx", "**/*.spec.tsx"], + }, }, }); diff --git a/packages/tokens/src/bin/TokensGenerator.ts b/packages/tokens/src/bin/TokensGenerator.ts index dc66c12..0a0d7de 100644 --- a/packages/tokens/src/bin/TokensGenerator.ts +++ b/packages/tokens/src/bin/TokensGenerator.ts @@ -12,6 +12,6 @@ export type Tokens = Record; export const tokensGenerator = (config: ConfigShape): Tokens => { return { - colors: { ...config.theme.colors }, + ...config, }; };