✨(react) add tokens.ts files handling
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.
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
dist
|
||||
dist
|
||||
src/cunningham-tokens.ts
|
||||
@@ -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"]}]
|
||||
}
|
||||
}
|
||||
@@ -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()],
|
||||
});
|
||||
},
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
theme: {
|
||||
colors: {
|
||||
primary: 'grey'
|
||||
},
|
||||
},
|
||||
};
|
||||
32
packages/react/cunningham.ts
Normal file
32
packages/react/cunningham.ts
Normal file
@@ -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,
|
||||
};
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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("<Button/>", () => {
|
||||
it("renders", () => {
|
||||
render(<Button />);
|
||||
const button = screen.getByRole("button", { name: "<Button/>" });
|
||||
render(<Button>Test button</Button>);
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,7 +7,9 @@ export default {
|
||||
component: Button,
|
||||
} as ComponentMeta<typeof Button>;
|
||||
|
||||
const Template: ComponentStory<typeof Button> = () => <Button />;
|
||||
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {};
|
||||
Default.args = {
|
||||
children: "Amazing button",
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import React from "react";
|
||||
import React, { PropsWithChildren } from "react";
|
||||
|
||||
export const Button = () => {
|
||||
return <button className="c__button">{"<Button/>"}</button>;
|
||||
interface Props extends PropsWithChildren {}
|
||||
|
||||
export const Button = ({ children }: Props) => {
|
||||
return <button className="c__button">{children}</button>;
|
||||
};
|
||||
|
||||
8
packages/react/src/components/Button/tokens.ts
Normal file
8
packages/react/src/components/Button/tokens.ts
Normal file
@@ -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 + ";",
|
||||
};
|
||||
};
|
||||
7
packages/react/src/cunningham-tokens.css
Normal file
7
packages/react/src/cunningham-tokens.css
Normal file
@@ -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;;
|
||||
}
|
||||
1
packages/react/src/cunningham-tokens.ts
Normal file
1
packages/react/src/cunningham-tokens.ts
Normal file
@@ -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;"}}};
|
||||
@@ -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';
|
||||
33
packages/react/src/tests/Theme.ts
Normal file
33
packages/react/src/tests/Theme.ts
Normal file
@@ -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<void>((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;
|
||||
};
|
||||
8
packages/react/tsconfig.eslint.json
Normal file
8
packages/react/tsconfig.eslint.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": [],
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
]
|
||||
}
|
||||
@@ -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" }]
|
||||
}
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
@@ -12,6 +12,6 @@ export type Tokens = Record<PropertyKey, unknown>;
|
||||
|
||||
export const tokensGenerator = (config: ConfigShape): Tokens => {
|
||||
return {
|
||||
colors: { ...config.theme.colors },
|
||||
...config,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user