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("", () => {
it("renders", () => {
- render();
- const button = screen.getByRole("button", { name: "" });
+ render();
+ 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 = () => ;
+const Template: ComponentStory = (args) => ;
export const Default = Template.bind({});
-Default.args = {};
+Default.args = {
+ children: "Amazing button",
+};
diff --git a/packages/react/src/components/Button/index.tsx b/packages/react/src/components/Button/index.tsx
index 52b646a..49b7c34 100644
--- a/packages/react/src/components/Button/index.tsx
+++ b/packages/react/src/components/Button/index.tsx
@@ -1,5 +1,7 @@
-import React from "react";
+import React, { PropsWithChildren } from "react";
-export const Button = () => {
- return ;
+interface Props extends PropsWithChildren {}
+
+export const Button = ({ children }: Props) => {
+ return {children};
};
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,
};
};