Skip to content

Commit

Permalink
feat: tailwind plugin POC
Browse files Browse the repository at this point in the history
  • Loading branch information
aversini committed Jan 9, 2024
1 parent 7b8ca37 commit 14d1057
Show file tree
Hide file tree
Showing 14 changed files with 781 additions and 1 deletion.
28 changes: 28 additions & 0 deletions packages/ui-plugins/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
root: true,
env: { browser: true, es2020: true },
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:react-hooks/recommended",
"plugin:react/recommended",
"plugin:react/jsx-runtime",
"prettier",
"../../configuration/eslint-rules/best-practices.cjs",
"../../configuration/eslint-rules/possible-errors.cjs",
"../../configuration/eslint-rules/variables.cjs",
],
ignorePatterns: ["dist", ".eslintrc.cjs"],
parser: "@typescript-eslint/parser",
plugins: ["react", "react-refresh", "simple-import-sort"],
rules: {
"@typescript-eslint/no-explicit-any": "off",
"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],
"simple-import-sort/imports": "error",
"simple-import-sort/exports": "error",
"react/jsx-no-useless-fragment": "error",
},
};
27 changes: 27 additions & 0 deletions packages/ui-plugins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
```

- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list
Empty file.
6 changes: 6 additions & 0 deletions packages/ui-plugins/go.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env node

import { convert } from "css-to-js";

const res = convert(`.myClass { color: 'red' }`);
console.log("==> ", res);
51 changes: 51 additions & 0 deletions packages/ui-plugins/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
{
"name": "@versini/ui-plugins",
"version": "0.0.0",
"license": "MIT",
"author": "Arno Versini",
"publishConfig": {
"access": "public"
},
"homepage": "https://github.com/aversini/ui-components/packages/ui-plugins",
"repository": {
"type": "git",
"url": "[email protected]:aversini/ui-components.git"
},
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build:check": "tsc",
"build:js": "vite build",
"build:types": "tsup",
"build": "npm-run-all --serial clean build:check build:js build:types",
"clean": "rimraf dist",
"dev:js": "vite build --watch --mode development",
"dev:types": "tsup --watch src",
"dev": "npm-run-all clean --parallel dev:js dev:types",
"lint": "eslint src --ext ts,tsx --report-unused-disable-directives --max-warnings 0 --fix --color",
"start": "static-server dist --port 5173",
"test:coverage:ui": "vitest --coverage --ui",
"test:coverage": "vitest run --coverage",
"test:watch": "vitest",
"test": "vitest run"
},
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/culori": "2.0.4",
"jss-cli": "6.0.2",
"react": "18.2.0",
"react-dom": "18.2.0"
},
"dependencies": {
"@tailwindcss/typography": "0.5.10",
"culori": "3.3.0",
"tailwindcss": "3.4.1"
}
}
2 changes: 2 additions & 0 deletions packages/ui-plugins/src/plugins/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { uiComponents } from "./tailwind/tailwindPlugin";
export { uiComponents };
149 changes: 149 additions & 0 deletions packages/ui-plugins/src/plugins/tailwind/tailwindPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import typography from "@tailwindcss/typography";
import { converter } from "culori";
import plugin from "tailwindcss/plugin";

import { tokens } from "../../../../ui-components/lib/tokens";

const parse = converter("rgb");

const dynamicColors = () => {
const result: { [key: string]: string } = {};
Object.entries(tokens.colors).forEach(([name, color]) => {
const rgb = parse(color);
const variable = `--av-${name}`;
const fallbackValue = rgb
? `${rgb.r * 255} ${rgb.g * 255} ${rgb.b * 255}`
: "0 0 0";
result[name] = `var(${variable}, rgb(${fallbackValue} / <alpha-value>))`;
});
return result;
};

const dynamicColorsClasses = () => {
const result: string[] = [];
Object.entries(tokens.colors).forEach(([name]) => {
if (name.startsWith("action-")) {
result.push(`bg-${name}`);
result.push(`hover:bg-${name}`);
result.push(`active:bg-${name}`);
}
if (name.startsWith("surface-")) {
result.push(`bg-${name}`);
}
if (name.startsWith("copy-")) {
result.push(`text-${name}`);
result.push(`hover:text-${name}`);
result.push(`active:text-${name}`);
}
if (name.startsWith("border-")) {
result.push(`border-${name}`);
}
if (name.startsWith("focus-")) {
result.push(`ring-${name}`);
result.push(`focus:ring-${name}`);
}
});

return result;
};

const dynamicMargins = () => {
const allowed = [
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 16, 20, 24, 28, 32, 36, 44,
48, 52, 56, 60, 64, 72, 80, 96,
];
const margins: string[] = [];
allowed.forEach((num) => {
margins.push(`mt-${num}`);
margins.push(`mr-${num}`);
margins.push(`mb-${num}`);
margins.push(`ml-${num}`);
});
return margins;
};

const myComponentLibraryConfig = {
theme: {
extend: {
colors: dynamicColors(),
typography: {
DEFAULT: {
css: {
blockquote: {
borderLeftColor: "var(--tw-prose-blockquote-border-color)",
},
"ul > li::marker": {
color: "var(--tw-prose-counter)",
},
},
},
},
},
},
};

const uiStyles = {
[`.av-text-input-wrapper label[aria-hidden="true"],
.av-text-area-wrapper label[aria-hidden="true"]`]: {
/* move the label inline */
transform: "translate(18px, 0) scale(1)",
transformOrigin: "top left",
transition: "var(--av-text-area-wrapper-transition, all 0.2s ease-out)",
},
'.av-text-input:focus + label[aria-hidden="true"],\n\t.av-text-input:not(:placeholder-shown) + label[aria-hidden="true"],\n\t.av-text-area:focus + label[aria-hidden="true"],\n\t.av-text-area:not(:placeholder-shown) + label[aria-hidden="true"]':
{
transform:
"translate(18px, var(--av-text-area-label, -25px)) scale(0.75)",
},
".av-text-input-helper-text,\n\t.av-text-area-helper-text": {
transform:
"translate(18px, var(--av-text-area-helper-text, 32px))\n\t\t\tscale(0.75)",
transformOrigin: "top left",
},
".av-text-input__control--right,\n\t.av-text-area__control--right": {
right: "18px",
},
"@keyframes blink": { "50%": { fill: "transparent" } },
".av-spinner__dot": { animation: "1s blink infinite" },
".av-spinner__dot:nth-child(2)": { animationDelay: "250ms" },
".av-spinner__dot:nth-child(3)": { animationDelay: "500ms" },
};

const contentPath = (__dirname + "/**/*.{js,ts,jsx,tsx}").replace(
"ui-plugins",
"ui-components",
);

export const uiComponents = {
content: contentPath,
safelist: [...dynamicMargins(), ...dynamicColorsClasses()],
plugin: plugin(function ({ addUtilities }) {
addUtilities(uiStyles);
}, myComponentLibraryConfig),

tailwindMerge: (config: any) => {
const safelist = [...dynamicMargins(), ...dynamicColorsClasses()];
const content = [contentPath];
const plugins = [typography];

if (config?.safelist?.length) {
config.safelist.push(...safelist);
} else {
config.safelist = safelist;
}

if (config?.content?.length) {
config.content.push(...content);
} else {
config.content = content;
}

if (config?.plugins?.length) {
config.plugins.push(...plugins);
} else {
config.plugins = plugins;
}

return config;
},
};
26 changes: 26 additions & 0 deletions packages/ui-plugins/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"types": ["vitest/globals", "@testing-library/jest-dom"],

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
10 changes: 10 additions & 0 deletions packages/ui-plugins/tsconfig.node.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["./vite.config.ts", "./vitest.setup.ts"]
}
9 changes: 9 additions & 0 deletions packages/ui-plugins/tsup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { defineConfig } from "tsup";

export default defineConfig({
format: "esm",
entry: ["src/plugins/index.ts"],
dts: {
only: true,
},
});
67 changes: 67 additions & 0 deletions packages/ui-plugins/vite.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { resolve } from "node:path";

import fs from "fs-extra";
import { defineConfig } from "vite";

import { externalDependencies } from "../../configuration/vite.common";

const packageJson = fs.readJSONSync("package.json");
const copyrightYear = new Date(Date.now()).getFullYear();
const buildTime = new Date()
.toLocaleString("en-US", {
timeZone: "America/New_York",
timeZoneName: "short",
year: "numeric",
month: "2-digit",
day: "2-digit",
hour: "2-digit",
minute: "2-digit",
})
.replace(/,/g, "");
const banner = `/*!
${packageJson.name} v${packageJson.version}
© ${copyrightYear} gizmette.com
*/
`;

export default defineConfig(() => {
return {
build: {
copyPublicDir: false,
lib: {
entry: resolve(__dirname, "src/plugins/index.ts"),
formats: ["es"],
name: "UIPlugins",
},
rollupOptions: {
input: {
index: resolve(__dirname, "src/plugins/index.ts"),
},
treeshake: "smallest",
external: externalDependencies,
output: {
compact: true,
minifyInternalExports: false,
assetFileNames: "style[extname]",
entryFileNames: "[name].js",
chunkFileNames: "chunks/[name].[hash].js",
banner: (module) => {
if (module?.facadeModuleId?.endsWith("src/plugins/index.ts")) {
return banner;
}
},
},
},
},
esbuild: {
supported: {
"top-level-await": true,
},
},
define: {
"import.meta.env.BUILDTIME": JSON.stringify(buildTime),
"import.meta.env.BUILDVERSION": JSON.stringify(packageJson.version),
},
plugins: [],
};
});
Loading

0 comments on commit 14d1057

Please sign in to comment.