From ccdfc419d24755f0f992bc83171274ed9346a43c Mon Sep 17 00:00:00 2001 From: Alexandre Stahmer Date: Wed, 12 Jul 2023 15:26:43 +0200 Subject: [PATCH] refactor: pnpm workspaces, rename fn & files docs: add comments fix: dts generation --- astro.config.mjs | 41 -- ...ground.constants.ts => demo-code-sample.ts | 2 +- package.json | 76 +-- bin.js => packages/tw2panda/bin.js | 0 packages/tw2panda/package.json | 64 ++ .../tw2panda/samples}/button.ts | 0 .../tw2panda/samples}/tailwind.config.ts | 0 {src => packages/tw2panda/src}/cli.ts | 20 +- .../tw2panda/src}/create-project.ts | 0 .../tw2panda/src}/extract-tw-class-list.ts | 14 +- .../tw2panda/src/find-tw-class-candidates.ts | 6 +- packages/tw2panda/src/index.ts | 10 + .../tw2panda/src}/maybe-pretty.ts | 0 packages/tw2panda/src/panda-context.ts | 262 ++++++++ .../tw2panda/src}/panda-map-to-shorthands.ts | 1 + .../tw2panda/src}/postcss-find-rule-props.ts | 0 .../src}/rewrite-tw-file-content-to-panda.ts | 10 +- .../src}/tw-class-list-to-panda-styles.ts | 3 + .../tw2panda/src}/tw-context.ts | 0 .../tw2panda/src}/tw-default-constants.ts | 0 .../tw2panda/src}/tw-eval-theme.ts | 0 .../tw2panda/src}/tw-parser.ts | 2 +- .../tw2panda/src}/tw-to-panda.ts | 4 +- .../tw2panda/src}/tw-types.ts | 0 .../tw2panda/src}/types.ts | 0 .../tw2panda/tests-setup.ts | 0 .../tests}/extract-class-candidates.test.ts | 8 +- .../tests}/extract-tw-class-list.test.ts | 18 +- .../tw2panda/tests}/panda-context.test.ts | 2 +- .../rewrite-tw-file-content-to-panda.test.ts | 18 +- .../tw2panda/tests}/tw-context.test.ts | 6 +- .../tw2panda/tests}/tw-eval-theme.test.ts | 8 +- .../tw2panda/tests}/tw-parser.test.ts | 2 +- packages/tw2panda/tsconfig.json | 10 + .../tw2panda/tsup.config.ts | 4 +- packages/tw2panda/vitest.config.ts | 12 + fs.shim.ts => packages/web/fs.shim.ts | 0 .../web/get-ts-declarations.ts | 0 index.html => packages/web/index.html | 1 + module.shim.ts => packages/web/module.shim.ts | 0 packages/web/package.json | 52 ++ .../web/panda.config.ts | 0 .../web/postcss.config.cjs | 0 packages/web/public/favicon.svg | 39 ++ packages/web/public/github-icon.svg | 1 + react.d.ts => packages/web/react.d.ts | 0 .../web/src}/Playground/Playground.machine.ts | 10 +- .../Playground/Playground.machine.typegen.ts | 0 .../web/src}/Playground/Playground.tsx | 6 +- .../Playground/PlaygroundMachineProvider.ts | 0 .../src}/Playground/PlaygroundWithMachine.tsx | 0 .../web/src}/Playground/ResizeHandle.tsx | 0 .../web/src}/components/color-mode-switch.tsx | 35 +- packages/web/src/components/github-icon.tsx | 14 + packages/web/src/components/icon-button.tsx | 41 ++ {src => packages/web/src}/main.tsx | 2 +- .../web/src/pages}/Home.tsx | 20 +- {src => packages/web/src}/styles.css | 0 packages/web/src/vite-themes/provider.tsx | 338 ++++++++++ .../web/src/vite-themes/vite-themes-types.ts | 0 {theme => packages/web/theme}/preset.ts | 0 .../web/theme}/semantic-tokens.ts | 0 {theme => packages/web/theme}/text-styles.ts | 0 {theme => packages/web/theme}/tokens.ts | 0 packages/web/tsconfig.json | 26 + vite.config.ts => packages/web/vite.config.ts | 5 +- pnpm-lock.yaml | 596 +++++++++--------- pnpm-workspace.yaml | 2 + public/favicon.svg | 13 - src/components/github-icon.tsx | 19 - src/components/vite-theme.tsx | 310 --------- src/converter/get-node-range.ts | 20 - src/converter/panda-context.ts | 50 -- src/env.d.ts | 1 - src/pages/index.astro | 16 - tsconfig.json | 44 +- 76 files changed, 1295 insertions(+), 969 deletions(-) delete mode 100644 astro.config.mjs rename src/components/Playground/Playground.constants.ts => demo-code-sample.ts (97%) rename bin.js => packages/tw2panda/bin.js (100%) mode change 100644 => 100755 create mode 100644 packages/tw2panda/package.json rename {samples => packages/tw2panda/samples}/button.ts (100%) rename {samples => packages/tw2panda/samples}/tailwind.config.ts (100%) rename {src => packages/tw2panda/src}/cli.ts (65%) rename {src/converter => packages/tw2panda/src}/create-project.ts (100%) rename {src/converter => packages/tw2panda/src}/extract-tw-class-list.ts (67%) rename src/converter/extract-class-candidates.ts => packages/tw2panda/src/find-tw-class-candidates.ts (77%) create mode 100644 packages/tw2panda/src/index.ts rename {src/converter => packages/tw2panda/src}/maybe-pretty.ts (100%) create mode 100644 packages/tw2panda/src/panda-context.ts rename {src/converter => packages/tw2panda/src}/panda-map-to-shorthands.ts (86%) rename {src/converter => packages/tw2panda/src}/postcss-find-rule-props.ts (100%) rename {src/converter => packages/tw2panda/src}/rewrite-tw-file-content-to-panda.ts (84%) rename {src/converter => packages/tw2panda/src}/tw-class-list-to-panda-styles.ts (96%) rename {src/converter => packages/tw2panda/src}/tw-context.ts (100%) rename {src/converter => packages/tw2panda/src}/tw-default-constants.ts (100%) rename {src/converter => packages/tw2panda/src}/tw-eval-theme.ts (100%) rename {src/converter => packages/tw2panda/src}/tw-parser.ts (99%) rename {src/converter => packages/tw2panda/src}/tw-to-panda.ts (85%) rename {src/converter => packages/tw2panda/src}/tw-types.ts (100%) rename {src/converter => packages/tw2panda/src}/types.ts (100%) rename tests-setup.ts => packages/tw2panda/tests-setup.ts (100%) rename {tests => packages/tw2panda/tests}/extract-class-candidates.test.ts (89%) rename {tests => packages/tw2panda/tests}/extract-tw-class-list.test.ts (96%) rename {tests => packages/tw2panda/tests}/panda-context.test.ts (91%) rename {tests => packages/tw2panda/tests}/rewrite-tw-file-content-to-panda.test.ts (94%) rename {tests => packages/tw2panda/tests}/tw-context.test.ts (82%) rename {tests => packages/tw2panda/tests}/tw-eval-theme.test.ts (50%) rename {tests => packages/tw2panda/tests}/tw-parser.test.ts (99%) create mode 100644 packages/tw2panda/tsconfig.json rename tsup.config.ts => packages/tw2panda/tsup.config.ts (51%) create mode 100644 packages/tw2panda/vitest.config.ts rename fs.shim.ts => packages/web/fs.shim.ts (100%) rename get-ts-declarations.ts => packages/web/get-ts-declarations.ts (100%) rename index.html => packages/web/index.html (88%) rename module.shim.ts => packages/web/module.shim.ts (100%) create mode 100644 packages/web/package.json rename panda.config.ts => packages/web/panda.config.ts (100%) rename postcss.config.cjs => packages/web/postcss.config.cjs (100%) create mode 100644 packages/web/public/favicon.svg create mode 100644 packages/web/public/github-icon.svg rename react.d.ts => packages/web/react.d.ts (100%) rename {src/components => packages/web/src}/Playground/Playground.machine.ts (92%) rename {src/components => packages/web/src}/Playground/Playground.machine.typegen.ts (100%) rename {src/components => packages/web/src}/Playground/Playground.tsx (98%) rename {src/components => packages/web/src}/Playground/PlaygroundMachineProvider.ts (100%) rename {src/components => packages/web/src}/Playground/PlaygroundWithMachine.tsx (100%) rename {src/components => packages/web/src}/Playground/ResizeHandle.tsx (100%) rename {src => packages/web/src}/components/color-mode-switch.tsx (75%) create mode 100644 packages/web/src/components/github-icon.tsx create mode 100644 packages/web/src/components/icon-button.tsx rename {src => packages/web/src}/main.tsx (84%) rename {src/components => packages/web/src/pages}/Home.tsx (63%) rename {src => packages/web/src}/styles.css (100%) create mode 100644 packages/web/src/vite-themes/provider.tsx rename src/components/vite-theme-types.ts => packages/web/src/vite-themes/vite-themes-types.ts (100%) rename {theme => packages/web/theme}/preset.ts (100%) rename {theme => packages/web/theme}/semantic-tokens.ts (100%) rename {theme => packages/web/theme}/text-styles.ts (100%) rename {theme => packages/web/theme}/tokens.ts (100%) create mode 100644 packages/web/tsconfig.json rename vite.config.ts => packages/web/vite.config.ts (94%) create mode 100644 pnpm-workspace.yaml delete mode 100644 public/favicon.svg delete mode 100644 src/components/github-icon.tsx delete mode 100644 src/components/vite-theme.tsx delete mode 100644 src/converter/get-node-range.ts delete mode 100644 src/converter/panda-context.ts delete mode 100644 src/env.d.ts delete mode 100644 src/pages/index.astro diff --git a/astro.config.mjs b/astro.config.mjs deleted file mode 100644 index d5e6c43..0000000 --- a/astro.config.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import { defineConfig } from "astro/config"; -import react from "@astrojs/react"; - -import { NodeGlobalsPolyfillPlugin } from "@esbuild-plugins/node-globals-polyfill"; -import path from "node:path"; -import * as url from "node:url"; - -const dirname = url.fileURLToPath(new URL(".", import.meta.url)); - -// https://astro.build/config -export default defineConfig({ - integrations: [react()], - vite: { - ssr: { - external: [ - "lodash.merge", - "postcss-nested", - "camelcase-css", - "postcss-discard-duplicates", - "postcss-discard-empty", - "postcss-merge-rules", - "postcss-normalize-whitespace", - "postcss-selector-parser", - ], - }, - optimizeDeps: { - esbuildOptions: { - define: { - global: "globalThis", - }, - plugins: [NodeGlobalsPolyfillPlugin({ process: true })], - }, - }, - resolve: { - alias: { - module: path.join(dirname, "./module.shim.ts"), - crosspath: "path-browserify", - }, - }, - }, -}); diff --git a/src/components/Playground/Playground.constants.ts b/demo-code-sample.ts similarity index 97% rename from src/components/Playground/Playground.constants.ts rename to demo-code-sample.ts index f67a876..aed6737 100644 --- a/src/components/Playground/Playground.constants.ts +++ b/demo-code-sample.ts @@ -36,7 +36,7 @@ export const config = { export const initialInputList = { "tw-App.tsx": defaultCode, - "theme.ts": themeCode, + "tailwind.config.js": themeCode, }; export const initialOutputList = { diff --git a/package.json b/package.json index 527d453..9e310d9 100644 --- a/package.json +++ b/package.json @@ -1,76 +1,18 @@ { - "name": "tailwind-to-panda", + "name": "tw2panda-root", + "private": true, "type": "module", "version": "0.0.1", - "bin": { - "tw2panda": "bin.js" + "repository": { + "type": "git", + "url": "https://github.com/astahmer/tw2panda.git" }, + "author": "Alexandre Stahmer ", + "license": "MIT", "scripts": { - "prepare": "panda && panda codegen && pnpm gen:react-declaration", - "gen:react-declaration": "tsx ./get-ts-declarations.ts", - "cli:start": "node ./dist/cli.cjs", - "cli:dev": "tsup --watch", - "cli:build": "tsup", - "astro:dev": "astro dev", - "astro:start": "astro dev", - "astro:build": "astro build", - "astro:preview": "astro preview", - "dev": "vite", - "build": "vite build", - "preview": "vite preview", - "test": "vitest" - }, - "dependencies": { - "@astrojs/react": "^2.2.1", - "@fontsource/inter": "^5.0.5", - "@monaco-editor/react": "4.5.1", - "@pandacss/config": "^0.6.0", - "@pandacss/core": "0.6.0", - "@pandacss/dev": "0.6.0", - "@pandacss/generator": "^0.6.0", - "@pandacss/parser": "^0.6.0", - "@pandacss/shared": "^0.6.0", - "@pandacss/types": "^0.6.0", - "@xstate/react": "^3.2.2", - "astro": "^2.8.1", - "cac": "^6.7.14", - "esbuild-wasm": "^0.18.11", - "hookable": "^5.5.3", - "lightningcss": "^1.21.5", - "magic-string": "^0.30.1", - "monaco-editor": "0.40.0", - "os-browserify": "^0.3.0", - "pastable": "^2.2.0", - "path-browserify": "^1.0.1", - "postcss": "^8.4.25", - "prettier": "2.8.4", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "react-resizable-panels": "^0.0.53", - "tailwindcss": "^3.3.2", - "ts-morph": "18.0.0", - "ts-pattern": "5.0.1", - "util": "^0.12.5", - "vite-node": "^0.33.0", - "xstate": "^4.38.0" + "format": "prettier --write \"./{src,tests}/**/*\"" }, "devDependencies": { - "@esbuild-plugins/node-globals-polyfill": "^0.2.3", - "@pandacss/preset-base": "^0.6.0", - "@pandacss/preset-panda": "^0.6.0", - "@types/prettier": "2.7.3", - "@types/react": "^18.2.14", - "@types/react-dom": "^18.2.6", - "@vitejs/plugin-react-swc": "^3.3.2", - "rollup": "^3.26.2", - "rollup-plugin-dts": "^5.3.0", - "tsup": "^7.1.0", - "tsx": "^3.12.7", - "type-fest": "^3.13.0", - "typescript": "^5.1.6", - "vite": "^4.4.3", - "vite-plugin-react-click-to-component": "^2.0.0", - "vite-tsconfig-paths": "^4.2.0", - "vitest": "^0.33.0" + "prettier": "^3.0.0" } } diff --git a/bin.js b/packages/tw2panda/bin.js old mode 100644 new mode 100755 similarity index 100% rename from bin.js rename to packages/tw2panda/bin.js diff --git a/packages/tw2panda/package.json b/packages/tw2panda/package.json new file mode 100644 index 0000000..ff98da7 --- /dev/null +++ b/packages/tw2panda/package.json @@ -0,0 +1,64 @@ +{ + "name": "tw2panda", + "type": "module", + "version": "0.0.1", + "main": "dist/index.cjs", + "module": "dist/index.js", + "bin": { + "tw2panda": "bin.js" + }, + "scripts": { + "start": "node ./dist/cli.cjs", + "dev": "tsup --watch", + "build": "tsup", + "test": "vitest", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@pandacss/config": "^0.6.0", + "@pandacss/generator": "^0.6.0", + "@pandacss/parser": "^0.6.0", + "@pandacss/preset-base": "^0.6.0", + "@pandacss/preset-panda": "^0.6.0", + "@pandacss/shared": "^0.6.0", + "@pandacss/types": "^0.6.0", + "cac": "^6.7.14", + "hookable": "^5.5.3", + "magic-string": "^0.30.1", + "pastable": "^2.2.0", + "postcss": "^8.4.25", + "prettier": "2.8.4", + "tailwindcss": "^3.3.2", + "ts-morph": "18.0.0" + }, + "devDependencies": { + "@pandacss/core": "^0.6.0", + "@pandacss/token-dictionary": "^0.6.0", + "@types/node": "^20.4.1", + "@types/prettier": "2.7.3", + "tsup": "^7.1.0", + "typescript": "^5.1.6", + "vite-node": "^0.33.0", + "vitest": "^0.33.0" + }, + "files": [ + "bin.js", + "src", + "dist", + "cli", + "README.md" + ], + "keywords": [ + "panda", + "css", + "pandacss", + "cli", + "tailwind", + "converter", + "migration" + ], + "sideEffects": false, + "publishConfig": { + "access": "public" + } +} diff --git a/samples/button.ts b/packages/tw2panda/samples/button.ts similarity index 100% rename from samples/button.ts rename to packages/tw2panda/samples/button.ts diff --git a/samples/tailwind.config.ts b/packages/tw2panda/samples/tailwind.config.ts similarity index 100% rename from samples/tailwind.config.ts rename to packages/tw2panda/samples/tailwind.config.ts diff --git a/src/cli.ts b/packages/tw2panda/src/cli.ts similarity index 65% rename from src/cli.ts rename to packages/tw2panda/src/cli.ts index da1d7d4..ebba7c3 100644 --- a/src/cli.ts +++ b/packages/tw2panda/src/cli.ts @@ -2,12 +2,12 @@ import { createMergeCss } from "@pandacss/shared"; import { cac } from "cac"; import { readFileSync } from "fs"; import { join } from "path"; -import { initialInputList } from "./components/Playground/Playground.constants"; -import { createPandaContext } from "./converter/panda-context"; -import { createTailwindContext } from "./converter/tw-context"; -import { twClassListToPanda } from "./converter/tw-to-panda"; -import { extractTwFileClassList } from "./converter/extract-tw-class-list"; -import { rewriteTwFileContentToPanda } from "./converter/rewrite-tw-file-content-to-panda"; +import { initialInputList } from "../../../demo-code-sample"; +import { extractTwFileClassList } from "./extract-tw-class-list"; +import { createPandaContext } from "./panda-context"; +import { rewriteTwFileContentToPanda } from "./rewrite-tw-file-content-to-panda"; +import { createTailwindContext } from "./tw-context"; +import { twClassListToPanda } from "./tw-to-panda"; const cli = cac("tw2panda"); @@ -19,7 +19,7 @@ cli .action((file) => { const content = readFileSync(join(process.cwd(), file), "utf-8"); - const tw = createTailwindContext(initialInputList["theme.ts"]); + const tw = createTailwindContext(initialInputList["tailwind.config.js"]); const panda = createPandaContext(); const { mergeCss } = createMergeCss(Object.assign(panda, { hash: false })); @@ -41,11 +41,11 @@ cli console.log({ file }); const content = readFileSync(join(process.cwd(), file), "utf-8"); const panda = createPandaContext(); - const tw = createTailwindContext(initialInputList["theme.ts"]); + const tw = createTailwindContext(initialInputList["tailwind.config.js"]); const { mergeCss } = createMergeCss(Object.assign(panda, { hash: false })); - const result = extractTwFileClassList(content, tw.context, panda, mergeCss); - console.log(result.resultList.map(({ node, ...item }) => item)); + const list = extractTwFileClassList(content, tw.context, panda, mergeCss); + console.log(list.map(({ node, ...item }) => item)); }); cli diff --git a/src/converter/create-project.ts b/packages/tw2panda/src/create-project.ts similarity index 100% rename from src/converter/create-project.ts rename to packages/tw2panda/src/create-project.ts diff --git a/src/converter/extract-tw-class-list.ts b/packages/tw2panda/src/extract-tw-class-list.ts similarity index 67% rename from src/converter/extract-tw-class-list.ts rename to packages/tw2panda/src/extract-tw-class-list.ts index 6770816..14f2c1a 100644 --- a/src/converter/extract-tw-class-list.ts +++ b/packages/tw2panda/src/extract-tw-class-list.ts @@ -1,19 +1,25 @@ -import { extractClassCandidates } from "./extract-class-candidates"; +import { findTwClassCandidates } from "./find-tw-class-candidates"; import { PandaContext } from "./panda-context"; import { mapToShorthands } from "./panda-map-to-shorthands"; import { TailwindContext } from "./tw-types"; import { StyleObject, TwResultItem } from "./types"; import { twClassListToPandaStyles } from "./tw-class-list-to-panda-styles"; +/** + * Returns a list of `TwResultItem`, which is a mapping of: + * - `classList` - the original class list + * - `styles` - the classList converted to a Panda style object + * - `node` - the StringLiteral AST node that contains the class list + */ export const extractTwFileClassList = ( content: string, tailwind: TailwindContext, panda: PandaContext, mergeCss: (...styles: StyleObject[]) => StyleObject ) => { - const { sourceFile, nodes } = extractClassCandidates(content, panda); + const { nodes } = findTwClassCandidates(content, panda); const resultList = [] as TwResultItem[]; - if (!nodes.size) return { sourceFile, nodes, resultList }; + if (!nodes.size) return resultList; nodes.forEach((node) => { const string = node.getLiteralText(); @@ -26,5 +32,5 @@ export const extractTwFileClassList = ( resultList.push({ classList: new Set(classList), styles: merged, node }); }); - return { sourceFile, nodes, resultList }; + return resultList; }; diff --git a/src/converter/extract-class-candidates.ts b/packages/tw2panda/src/find-tw-class-candidates.ts similarity index 77% rename from src/converter/extract-class-candidates.ts rename to packages/tw2panda/src/find-tw-class-candidates.ts index ff4ed0b..28bdd21 100644 --- a/src/converter/extract-class-candidates.ts +++ b/packages/tw2panda/src/find-tw-class-candidates.ts @@ -1,9 +1,10 @@ import { Node, SourceFile, StringLiteral } from "ts-morph"; import { PandaContext } from "./panda-context"; -export function extractClassCandidates(content: string, panda: PandaContext) { +/** Finds all tailwind class candidates in a file + * -> returns the list of all StringLiteral AST nodes */ +export function findTwClassCandidates(content: string, panda: PandaContext) { const nodes = new Set(); - const candidates = new Set(); const sourceFile = panda.project.addSourceFile( "App.tsx", @@ -22,7 +23,6 @@ export function extractClassCandidates(content: string, panda: PandaContext) { if (!string) return; nodes.add(node); - candidates.add(string); } }); diff --git a/packages/tw2panda/src/index.ts b/packages/tw2panda/src/index.ts new file mode 100644 index 0000000..6687bdc --- /dev/null +++ b/packages/tw2panda/src/index.ts @@ -0,0 +1,10 @@ +export * from "./panda-context"; +export * from "./find-tw-class-candidates"; +export * from "./extract-tw-class-list"; +export * from "./rewrite-tw-file-content-to-panda"; +export * from "./tw-class-list-to-panda-styles"; +export * from "./tw-parser"; +export * from "./tw-to-panda"; +export * from "./tw-context"; +export * from "./tw-types"; +export * from "./types"; diff --git a/src/converter/maybe-pretty.ts b/packages/tw2panda/src/maybe-pretty.ts similarity index 100% rename from src/converter/maybe-pretty.ts rename to packages/tw2panda/src/maybe-pretty.ts diff --git a/packages/tw2panda/src/panda-context.ts b/packages/tw2panda/src/panda-context.ts new file mode 100644 index 0000000..0a9e4b4 --- /dev/null +++ b/packages/tw2panda/src/panda-context.ts @@ -0,0 +1,262 @@ +import { createGenerator } from "@pandacss/generator"; +import { mergeConfigs } from "@pandacss/config"; +import { createProject } from "@pandacss/parser"; +import presetBase from "@pandacss/preset-base"; +import presetTheme from "@pandacss/preset-panda"; +import { createHooks } from "hookable"; + +import type { + Artifact, + Config, + ConfigResultWithHooks, + Dict, + PandaHookable, + ParserResultType, + PatternConfig, + PatternHelpers, + RecipeConfig, + RequiredBy, + SystemStyleObject, + TSConfig, +} from "@pandacss/types"; +import type { UnwrapExtend } from "@pandacss/types/dist/shared"; +import type { + Conditions, + Utility, + Recipes, + StylesheetOptions, + Stylesheet, +} from "@pandacss/core"; +import type { TokenDictionary } from "@pandacss/token-dictionary"; +import type { Root } from "postcss"; + +const createContext = (conf: ConfigResultWithHooks) => { + const generator = createGenerator({ + ...conf, + config: mergeConfigs([presetBase, presetTheme as any, conf.config]), + }); + const files = new Map(); + + const project = createProject({ + useInMemoryFileSystem: true, + parserOptions: generator.parserOptions, + getFiles: () => Array.from(files.keys()), + readFile: (file: string) => files.get(file) || "", + hooks: generator.hooks, + }); + + return { + ...(generator as any as Generator), + project: { + ...project, + addSourceFile: (file: string, content: string) => { + files.set(file, content); + return project.addSourceFile(file, content); + }, + }, + }; +}; + +export const createPandaContext = (conf?: Partial) => { + return createContext({ + hooks: createHooks() as any, + dependencies: [], + path: "", + config: { + cwd: "", + include: [], + outdir: "styled-system", + }, + ...conf, + }); +}; +export interface PandaContext extends ReturnType {} + +type Generator = { + getArtifacts: () => Artifact[]; + getCss: (options: { + files: string[]; + resolve?: boolean | undefined; + }) => string; + getParserCss: (result: ParserResultType) => string | undefined; + messages: { + artifactsGenerated: () => string; + configExists: (cmd: string) => string; + thankYou: () => string; + codegenComplete: () => string; + noExtract: () => string; + watch: () => string; + buildComplete: (count: number) => string; + configWatch: () => string; + }; + parserOptions: { + importMap: { + css: string; + recipe: string; + pattern: string; + jsx: string; + }; + jsx: { + factory: string; + isStyleProp: (key: string) => boolean; + nodes: Array; + }; + getRecipesByJsxName: (jsxName: string) => RecipeNode[]; + }; + patterns: { + transform: (name: string, data: Dict) => SystemStyleObject; + nodes: { + type: "pattern"; + name: string; + props: string[]; + baseName: string; + }[]; + patterns: Record; + getConfig: (name: string) => PatternConfig; + getNames: (name: string) => { + name: string; + upperName: string; + dashName: string; + styleFnName: string; + jsxName: string; + }; + details: JsxPatternNode[]; + getFnName: (jsx: string) => string; + isEmpty: () => boolean; + }; + jsx: JsxEngine; + paths: { + get: (file?: string | undefined) => string[]; + root: string[]; + css: string[]; + token: string[]; + types: string[]; + recipe: string[]; + pattern: string[]; + chunk: string[]; + outCss: string[]; + jsx: string[]; + }; + file: { + ext(file: string): string; + import(mod: string, file: string): string; + export(file: string): string; + }; + isTemplateLiteralSyntax: boolean; + studio: { + outdir: string; + logo?: string | undefined; + inject?: + | { + head?: string | undefined; + body?: string | undefined; + } + | undefined; + }; + hash: { + tokens: boolean | undefined; + className: boolean | undefined; + }; + prefix: { + tokens: string | undefined; + className: string | undefined; + }; + tokens: TokenDictionary; + utility: Utility; + properties: string[]; + isValidProperty: (key: string) => boolean; + recipes: Recipes; + conditions: Conditions; + createSheetContext: () => StylesheetContext; + createSheet: ( + options?: Pick | undefined + ) => Stylesheet; + hooks: PandaHookable; + path: string; + config: UnwrapExtend>; + tsconfig?: TSConfig | undefined; + tsconfigFile?: string | undefined; + dependencies: string[]; +}; + +type JsxEngine = { + factoryName: string; + upperName: string; + typeName: string; + componentName: string; + framework: "react" | "solid" | "preact" | "vue" | "qwik" | undefined; +}; + +type JsxRecipeNode = { + type: "recipe"; + name: string; + props: string[]; + baseName: string; + jsx: (string | RegExp)[]; + match: RegExp; +}; + +type JsxPatternNode = { + type: "pattern"; + name: string; + props: string[]; + baseName: string; +}; + +type RecipeNode = { + /** + * The name of the recipe + */ + name: string; + /** + * The keys of the variants + */ + variantKeys: string[]; + /** + * The map of the variant keys to their possible values + */ + variantKeyMap: Record; + /** + * The jsx keys or regex to match the recipe + */ + jsx: (string | RegExp)[]; + /** + * The name of the recipe in upper case + */ + upperName: string; + /** + * The name of the recipe in dash case + */ + dashName: string; + /** + * The name of the recipe in camel case + */ + jsxName: string; + /** + * The regex to match the recipe + */ + match: RegExp; + /** + * The transformed recipe config + */ + config: RecipeConfig; + /** + * The function to split the props + */ + splitProps: (props: Dict) => [Dict, Dict]; +}; + +type StylesheetContext = { + root: Root; + utility: Utility; + conditions: Conditions; + helpers: PatternHelpers; + hash?: boolean; + transform?: AtomicRuleTransform; +}; +type AtomicRuleTransform = (prop: string, value: any) => TransformResult; +type TransformResult = { + layer?: string; + className: string; + styles: Dict; +}; diff --git a/src/converter/panda-map-to-shorthands.ts b/packages/tw2panda/src/panda-map-to-shorthands.ts similarity index 86% rename from src/converter/panda-map-to-shorthands.ts rename to packages/tw2panda/src/panda-map-to-shorthands.ts index 556ff65..0f88691 100644 --- a/src/converter/panda-map-to-shorthands.ts +++ b/packages/tw2panda/src/panda-map-to-shorthands.ts @@ -2,6 +2,7 @@ import { PandaContext } from "./panda-context"; import { walkObject } from "@pandacss/shared"; import { StyleObject } from "./types"; +/** Takes a style object and map each CSS property to its shorthand */ export function mapToShorthands(styles: StyleObject, context: PandaContext) { const utilities = context.config.utilities ?? {}; diff --git a/src/converter/postcss-find-rule-props.ts b/packages/tw2panda/src/postcss-find-rule-props.ts similarity index 100% rename from src/converter/postcss-find-rule-props.ts rename to packages/tw2panda/src/postcss-find-rule-props.ts diff --git a/src/converter/rewrite-tw-file-content-to-panda.ts b/packages/tw2panda/src/rewrite-tw-file-content-to-panda.ts similarity index 84% rename from src/converter/rewrite-tw-file-content-to-panda.ts rename to packages/tw2panda/src/rewrite-tw-file-content-to-panda.ts index 0564777..9add208 100644 --- a/src/converter/rewrite-tw-file-content-to-panda.ts +++ b/packages/tw2panda/src/rewrite-tw-file-content-to-panda.ts @@ -1,20 +1,24 @@ import { PandaContext } from "./panda-context"; import { TailwindContext } from "./tw-types"; import { maybePretty } from "./maybe-pretty"; -import { extractClassCandidates } from "./extract-class-candidates"; +import { findTwClassCandidates } from "./find-tw-class-candidates"; import MagicString from "magic-string"; import { Node } from "ts-morph"; import { StyleObject, TwResultItem } from "./types"; import { twClassListToPandaStyles } from "./tw-class-list-to-panda-styles"; import { mapToShorthands } from "./panda-map-to-shorthands"; +/** + * Parse a file content, replace Tailwind classes with Panda styles object + * and return the new content. (Does not write to disk) + */ export function rewriteTwFileContentToPanda( content: string, tailwind: TailwindContext, panda: PandaContext, mergeCss: (...styles: StyleObject[]) => StyleObject ) { - const { sourceFile, nodes } = extractClassCandidates(content, panda); + const { sourceFile, nodes } = findTwClassCandidates(content, panda); if (!nodes.size) return { sourceFile, nodes, output: content }; @@ -53,5 +57,5 @@ export function rewriteTwFileContentToPanda( trailingComma: "all", }); - return { sourceFile, nodes, output, resultList }; + return { sourceFile, output, resultList }; } diff --git a/src/converter/tw-class-list-to-panda-styles.ts b/packages/tw2panda/src/tw-class-list-to-panda-styles.ts similarity index 96% rename from src/converter/tw-class-list-to-panda-styles.ts rename to packages/tw2panda/src/tw-class-list-to-panda-styles.ts index 6f972d1..a0288b3 100644 --- a/src/converter/tw-class-list-to-panda-styles.ts +++ b/packages/tw2panda/src/tw-class-list-to-panda-styles.ts @@ -6,6 +6,9 @@ import { TailwindContext } from "./tw-types"; import { MatchingToken, StyleObject } from "./types"; import { findRuleProps } from "./postcss-find-rule-props"; +/** + * Takes a list of Tailwind class names and convert them to a list of Panda style objects + */ export const twClassListToPandaStyles = ( classList: Set, tailwind: TailwindContext, diff --git a/src/converter/tw-context.ts b/packages/tw2panda/src/tw-context.ts similarity index 100% rename from src/converter/tw-context.ts rename to packages/tw2panda/src/tw-context.ts diff --git a/src/converter/tw-default-constants.ts b/packages/tw2panda/src/tw-default-constants.ts similarity index 100% rename from src/converter/tw-default-constants.ts rename to packages/tw2panda/src/tw-default-constants.ts diff --git a/src/converter/tw-eval-theme.ts b/packages/tw2panda/src/tw-eval-theme.ts similarity index 100% rename from src/converter/tw-eval-theme.ts rename to packages/tw2panda/src/tw-eval-theme.ts diff --git a/src/converter/tw-parser.ts b/packages/tw2panda/src/tw-parser.ts similarity index 99% rename from src/converter/tw-parser.ts rename to packages/tw2panda/src/tw-parser.ts index 07dac54..4bf30fb 100644 --- a/src/converter/tw-parser.ts +++ b/packages/tw2panda/src/tw-parser.ts @@ -97,7 +97,7 @@ export const parseTwClassName = ( let isAllowed = allowedModifiers.includes(current); if (!isAllowed && current.includes("/")) { - isAllowed = allowedModifiers.includes(current.split("/")[0]); + isAllowed = allowedModifiers.includes(current.split("/")[0]!); } if (isAllowed) { diff --git a/src/converter/tw-to-panda.ts b/packages/tw2panda/src/tw-to-panda.ts similarity index 85% rename from src/converter/tw-to-panda.ts rename to packages/tw2panda/src/tw-to-panda.ts index f8ac104..67c0baa 100644 --- a/src/converter/tw-to-panda.ts +++ b/packages/tw2panda/src/tw-to-panda.ts @@ -1,10 +1,10 @@ import { createMergeCss } from "@pandacss/shared"; -import { initialInputList } from "../components/Playground/Playground.constants"; import { createPandaContext } from "./panda-context"; import { mapToShorthands } from "./panda-map-to-shorthands"; import { createTailwindContext } from "./tw-context"; import { twClassListToPandaStyles } from "./tw-class-list-to-panda-styles"; +import { initialInputList } from "../../../demo-code-sample"; export function twClassListToPanda( classListString: string, @@ -12,7 +12,7 @@ export function twClassListToPanda( ) { const classList = new Set(classListString.split(" ")); - const tw = createTailwindContext(initialInputList["theme.ts"]); + const tw = createTailwindContext(initialInputList["tailwind.config.js"]); const tailwind = tw.context; const panda = createPandaContext(); diff --git a/src/converter/tw-types.ts b/packages/tw2panda/src/tw-types.ts similarity index 100% rename from src/converter/tw-types.ts rename to packages/tw2panda/src/tw-types.ts diff --git a/src/converter/types.ts b/packages/tw2panda/src/types.ts similarity index 100% rename from src/converter/types.ts rename to packages/tw2panda/src/types.ts diff --git a/tests-setup.ts b/packages/tw2panda/tests-setup.ts similarity index 100% rename from tests-setup.ts rename to packages/tw2panda/tests-setup.ts diff --git a/tests/extract-class-candidates.test.ts b/packages/tw2panda/tests/extract-class-candidates.test.ts similarity index 89% rename from tests/extract-class-candidates.test.ts rename to packages/tw2panda/tests/extract-class-candidates.test.ts index 2a48324..f1a6b7e 100644 --- a/tests/extract-class-candidates.test.ts +++ b/packages/tw2panda/tests/extract-class-candidates.test.ts @@ -1,8 +1,9 @@ import { describe, test, expect } from "vitest"; -import { createPandaContext } from "../src/converter/panda-context"; -import { extractClassCandidates } from "../src/converter/extract-class-candidates"; +import { createPandaContext } from "../src/panda-context"; +import { extractClassCandidates } from "../src/extract-class-candidates"; +import { initialInputList } from "../../../demo-code-sample"; +// @ts-expect-error import buttonRaw from "../samples/button?raw"; -import { initialInputList } from "../src/components/Playground/Playground.constants"; describe("extract-class-candidates", () => { test("samples/button.ts", () => { @@ -46,7 +47,6 @@ describe("extract-class-candidates", () => { "md:flex bg-slate-100 rounded-xl p-8 md:p-0 dark:bg-slate-800", "w-24 h-24 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto", "/sarah-dayan.jpg", - "", "384", "512", "pt-6 md:p-8 text-center md:text-left space-y-4", diff --git a/tests/extract-tw-class-list.test.ts b/packages/tw2panda/tests/extract-tw-class-list.test.ts similarity index 96% rename from tests/extract-tw-class-list.test.ts rename to packages/tw2panda/tests/extract-tw-class-list.test.ts index b894ea4..c77dc6f 100644 --- a/tests/extract-tw-class-list.test.ts +++ b/packages/tw2panda/tests/extract-tw-class-list.test.ts @@ -1,15 +1,19 @@ import { createMergeCss } from "@pandacss/shared"; import { describe, expect, test } from "vitest"; -import { extractTwFileClassList } from "../src/converter/extract-tw-class-list"; -import { createPandaContext } from "../src/converter/panda-context"; -import { createTailwindContext } from "../src/converter/tw-context"; -import { initialInputList } from "../src/components/Playground/Playground.constants"; +import { extractTwFileClassList } from "../src/extract-tw-class-list"; +import { createPandaContext } from "../src/panda-context"; +import { createTailwindContext } from "../src/tw-context"; +import { initialInputList } from "../../../demo-code-sample"; + +// @ts-expect-error import buttonRaw from "../samples/button?raw"; import tailwindConfigRaw from "../samples/tailwind.config"; describe("extract-tw-class-list", () => { test("samples/button.ts", () => { - const tailwind = createTailwindContext(initialInputList["theme.ts"]); + const tailwind = createTailwindContext( + initialInputList["tailwind.config.js"] + ); const panda = createPandaContext(); const { mergeCss } = createMergeCss({ utility: panda.utility, @@ -360,7 +364,9 @@ describe("extract-tw-class-list", () => { }); test("Playground defaultCode", () => { - const tailwind = createTailwindContext(initialInputList["theme.ts"]); + const tailwind = createTailwindContext( + initialInputList["tailwind.config.js"] + ); const panda = createPandaContext(); const { mergeCss } = createMergeCss({ utility: panda.utility, diff --git a/tests/panda-context.test.ts b/packages/tw2panda/tests/panda-context.test.ts similarity index 91% rename from tests/panda-context.test.ts rename to packages/tw2panda/tests/panda-context.test.ts index 71f4a86..1c07788 100644 --- a/tests/panda-context.test.ts +++ b/packages/tw2panda/tests/panda-context.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "vitest"; -import { createPandaContext } from "../src/converter/panda-context"; +import { createPandaContext } from "../src/panda-context"; describe("panda-context", () => { test("createPandaContext", () => { diff --git a/tests/rewrite-tw-file-content-to-panda.test.ts b/packages/tw2panda/tests/rewrite-tw-file-content-to-panda.test.ts similarity index 94% rename from tests/rewrite-tw-file-content-to-panda.test.ts rename to packages/tw2panda/tests/rewrite-tw-file-content-to-panda.test.ts index 1ced526..2f1d446 100644 --- a/tests/rewrite-tw-file-content-to-panda.test.ts +++ b/packages/tw2panda/tests/rewrite-tw-file-content-to-panda.test.ts @@ -1,15 +1,19 @@ import { createMergeCss } from "@pandacss/shared"; import { describe, expect, test } from "vitest"; -import { createPandaContext } from "../src/converter/panda-context"; -import { createTailwindContext } from "../src/converter/tw-context"; -import { initialInputList } from "../src/components/Playground/Playground.constants"; -import { rewriteTwFileContentToPanda } from "../src/converter/rewrite-tw-file-content-to-panda"; +import { createPandaContext } from "../src/panda-context"; +import { createTailwindContext } from "../src/tw-context"; +import { rewriteTwFileContentToPanda } from "../src/rewrite-tw-file-content-to-panda"; +import { initialInputList } from "../../../demo-code-sample"; + +// @ts-expect-error import buttonRaw from "../samples/button?raw"; import tailwindConfigRaw from "../samples/tailwind.config"; describe("extract-tw-class-list", () => { test("samples/button.ts", () => { - const tailwind = createTailwindContext(initialInputList["theme.ts"]); + const tailwind = createTailwindContext( + initialInputList["tailwind.config.js"] + ); const panda = createPandaContext(); const { mergeCss } = createMergeCss({ utility: panda.utility, @@ -182,7 +186,9 @@ describe("extract-tw-class-list", () => { }); test("Playground defaultCode", () => { - const tailwind = createTailwindContext(initialInputList["theme.ts"]); + const tailwind = createTailwindContext( + initialInputList["tailwind.config.js"] + ); const panda = createPandaContext(); const { mergeCss } = createMergeCss({ utility: panda.utility, diff --git a/tests/tw-context.test.ts b/packages/tw2panda/tests/tw-context.test.ts similarity index 82% rename from tests/tw-context.test.ts rename to packages/tw2panda/tests/tw-context.test.ts index b37133b..84a689a 100644 --- a/tests/tw-context.test.ts +++ b/packages/tw2panda/tests/tw-context.test.ts @@ -1,10 +1,10 @@ import { describe, expect, test } from "vitest"; -import { initialInputList } from "../src/components/Playground/Playground.constants"; -import { createTailwindContext } from "../src/converter/tw-context"; +import { createTailwindContext } from "../src/tw-context"; +import { initialInputList } from "../../../demo-code-sample"; describe("tw-context", () => { test("createTailwindContext", () => { - const ctx = createTailwindContext(initialInputList["theme.ts"]); + const ctx = createTailwindContext(initialInputList["tailwind.config.js"]); expect(Object.keys(ctx.context)).toMatchInlineSnapshot(` [ "disposables", diff --git a/tests/tw-eval-theme.test.ts b/packages/tw2panda/tests/tw-eval-theme.test.ts similarity index 50% rename from tests/tw-eval-theme.test.ts rename to packages/tw2panda/tests/tw-eval-theme.test.ts index aef5a91..87a0a65 100644 --- a/tests/tw-eval-theme.test.ts +++ b/packages/tw2panda/tests/tw-eval-theme.test.ts @@ -1,10 +1,12 @@ import { describe, expect, test } from "vitest"; -import { initialInputList } from "../src/components/Playground/Playground.constants"; -import { evalTheme } from "../src/converter/tw-eval-theme"; +import { evalTheme } from "../src/tw-eval-theme"; +import { initialInputList } from "../../../demo-code-sample"; describe("tw-eval-theme", () => { test("Playground themeCode", () => { - expect(evalTheme(initialInputList["theme.ts"])).toMatchInlineSnapshot( + expect( + evalTheme(initialInputList["tailwind.config.js"]) + ).toMatchInlineSnapshot( ` { "theme": { diff --git a/tests/tw-parser.test.ts b/packages/tw2panda/tests/tw-parser.test.ts similarity index 99% rename from tests/tw-parser.test.ts rename to packages/tw2panda/tests/tw-parser.test.ts index 549a565..10613ff 100644 --- a/tests/tw-parser.test.ts +++ b/packages/tw2panda/tests/tw-parser.test.ts @@ -1,5 +1,5 @@ import { test, expect } from "vitest"; -import { parseTwClassName } from "../src/converter/tw-parser"; +import { parseTwClassName } from "../src/tw-parser"; const parseTailwindClasses = (classList: string) => classList.split(" ").map((className) => parseTwClassName(className)); diff --git a/packages/tw2panda/tsconfig.json b/packages/tw2panda/tsconfig.json new file mode 100644 index 0000000..d56e67e --- /dev/null +++ b/packages/tw2panda/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ESNext", + "moduleResolution": "Node", + "lib": ["DOM", "ES2021"] + }, + "include": ["src"], + "exclude": ["node_modules", "samples"] +} diff --git a/tsup.config.ts b/packages/tw2panda/tsup.config.ts similarity index 51% rename from tsup.config.ts rename to packages/tw2panda/tsup.config.ts index d7f743e..f6258b6 100644 --- a/tsup.config.ts +++ b/packages/tw2panda/tsup.config.ts @@ -1,6 +1,8 @@ import { defineConfig } from "tsup"; export default defineConfig({ - entryPoints: ["src/cli.ts"], + entryPoints: ["src/cli.ts", "src/index.ts"], outDir: "dist", + dts: true, + format: ["cjs", "esm"], }); diff --git a/packages/tw2panda/vitest.config.ts b/packages/tw2panda/vitest.config.ts new file mode 100644 index 0000000..d5cb901 --- /dev/null +++ b/packages/tw2panda/vitest.config.ts @@ -0,0 +1,12 @@ +/// + +import { defineConfig } from "vitest/config"; + +// https://vitejs.dev/config/ +export default defineConfig({ + test: { + setupFiles: ["tests-setup.ts"], + hideSkippedTests: true, + // snapshotFormat: { indent: 4, escapeString: false }, + }, +}); diff --git a/fs.shim.ts b/packages/web/fs.shim.ts similarity index 100% rename from fs.shim.ts rename to packages/web/fs.shim.ts diff --git a/get-ts-declarations.ts b/packages/web/get-ts-declarations.ts similarity index 100% rename from get-ts-declarations.ts rename to packages/web/get-ts-declarations.ts diff --git a/index.html b/packages/web/index.html similarity index 88% rename from index.html rename to packages/web/index.html index 29dd85a..24f7695 100644 --- a/index.html +++ b/packages/web/index.html @@ -3,6 +3,7 @@ + Tailwind to PandaCSS diff --git a/module.shim.ts b/packages/web/module.shim.ts similarity index 100% rename from module.shim.ts rename to packages/web/module.shim.ts diff --git a/packages/web/package.json b/packages/web/package.json new file mode 100644 index 0000000..9c325b2 --- /dev/null +++ b/packages/web/package.json @@ -0,0 +1,52 @@ +{ + "name": "tw2panda-web", + "private": true, + "type": "module", + "version": "0.0.1", + "scripts": { + "prebuild": "panda && panda codegen && pnpm gen:react-declaration", + "gen:react-declaration": "tsx ./get-ts-declarations.ts", + "dev": "vite", + "build": "pnpm prebuild && vite build", + "preview": "vite preview", + "test": "vitest" + }, + "dependencies": { + "@fontsource/inter": "^5.0.5", + "@monaco-editor/react": "4.5.1", + "@pandacss/dev": "0.6.0", + "@pandacss/shared": "^0.6.0", + "@xstate/react": "^3.2.2", + "esbuild-wasm": "^0.18.11", + "monaco-editor": "0.40.0", + "pastable": "^2.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-resizable-panels": "^0.0.53", + "ts-morph": "18.0.0", + "tw2panda": "workspace:*", + "xstate": "^4.38.0" + }, + "devDependencies": { + "@esbuild-plugins/node-globals-polyfill": "^0.2.3", + "@pandacss/preset-base": "^0.6.0", + "@pandacss/preset-panda": "^0.6.0", + "@types/prettier": "2.7.3", + "@types/react": "^18.2.14", + "@types/react-dom": "^18.2.6", + "@vitejs/plugin-react-swc": "^3.3.2", + "os-browserify": "^0.3.0", + "path-browserify": "^1.0.1", + "rollup": "^3.26.2", + "rollup-plugin-dts": "^5.3.0", + "tsup": "^7.1.0", + "tsx": "^3.12.7", + "type-fest": "^3.13.0", + "typescript": "^5.1.6", + "util": "^0.12.5", + "vite": "^4.4.3", + "vite-plugin-react-click-to-component": "^2.0.0", + "vite-tsconfig-paths": "^4.2.0", + "vitest": "^0.33.0" + } +} diff --git a/panda.config.ts b/packages/web/panda.config.ts similarity index 100% rename from panda.config.ts rename to packages/web/panda.config.ts diff --git a/postcss.config.cjs b/packages/web/postcss.config.cjs similarity index 100% rename from postcss.config.cjs rename to packages/web/postcss.config.cjs diff --git a/packages/web/public/favicon.svg b/packages/web/public/favicon.svg new file mode 100644 index 0000000..c8f309a --- /dev/null +++ b/packages/web/public/favicon.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/web/public/github-icon.svg b/packages/web/public/github-icon.svg new file mode 100644 index 0000000..e547320 --- /dev/null +++ b/packages/web/public/github-icon.svg @@ -0,0 +1 @@ + diff --git a/react.d.ts b/packages/web/react.d.ts similarity index 100% rename from react.d.ts rename to packages/web/react.d.ts diff --git a/src/components/Playground/Playground.machine.ts b/packages/web/src/Playground/Playground.machine.ts similarity index 92% rename from src/components/Playground/Playground.machine.ts rename to packages/web/src/Playground/Playground.machine.ts index c4d25ee..4648c5a 100644 --- a/src/components/Playground/Playground.machine.ts +++ b/packages/web/src/Playground/Playground.machine.ts @@ -3,14 +3,11 @@ import type { editor } from "monaco-editor"; import { Node, SourceFile } from "ts-morph"; import { assign, createMachine } from "xstate"; import { choose } from "xstate/lib/actions"; -import { initialInputList, initialOutputList } from "./Playground.constants"; +import { initialInputList, initialOutputList } from "../../../../demo-code-sample"; import { createMergeCss } from "@pandacss/shared"; -import { createPandaContext } from "../../converter/panda-context"; -import { createTailwindContext } from "../../converter/tw-context"; -import { rewriteTwFileContentToPanda } from "../../converter/rewrite-tw-file-content-to-panda"; -import { TwResultItem } from "../../converter/types"; +import { createPandaContext, createTailwindContext,rewriteTwFileContentToPanda, type TwResultItem } from "tw2panda"; type PlaygroundContext = { monaco: Monaco | null; @@ -51,6 +48,7 @@ const initialContext: PlaygroundContext = { decorations: [], }; +// @ts-ignore globalThis.__dirname = "/"; export const playgroundMachine = createMachine( @@ -131,7 +129,7 @@ export const playgroundMachine = createMachine( (event.type === "Update input" ? event.value : ctx.inputList[ctx.selectedInput]) ?? ""; - const themeContent = ctx.inputList["theme.ts"] ?? "module.exports = {}"; + const themeContent = ctx.inputList["tailwind.config.js"] ?? "module.exports = {}"; const tw = createTailwindContext(themeContent); const tailwind = tw.context; diff --git a/src/components/Playground/Playground.machine.typegen.ts b/packages/web/src/Playground/Playground.machine.typegen.ts similarity index 100% rename from src/components/Playground/Playground.machine.typegen.ts rename to packages/web/src/Playground/Playground.machine.typegen.ts diff --git a/src/components/Playground/Playground.tsx b/packages/web/src/Playground/Playground.tsx similarity index 98% rename from src/components/Playground/Playground.tsx rename to packages/web/src/Playground/Playground.tsx index da98402..51d603a 100644 --- a/src/components/Playground/Playground.tsx +++ b/packages/web/src/Playground/Playground.tsx @@ -5,8 +5,10 @@ import { css } from "panda/css"; import { Flex, styled } from "panda/jsx"; import { usePlaygroundContext } from "./PlaygroundMachineProvider"; import { ResizeHandle } from "./ResizeHandle"; -import ReactDeclaration from "../../../react.d.ts?raw"; -import { useTheme } from "../vite-theme"; +import { useTheme } from "../vite-themes/provider"; + +// @ts-ignore +import ReactDeclaration from "../../react.d.ts?raw"; export const Playground = () => { const service = usePlaygroundContext(); diff --git a/src/components/Playground/PlaygroundMachineProvider.ts b/packages/web/src/Playground/PlaygroundMachineProvider.ts similarity index 100% rename from src/components/Playground/PlaygroundMachineProvider.ts rename to packages/web/src/Playground/PlaygroundMachineProvider.ts diff --git a/src/components/Playground/PlaygroundWithMachine.tsx b/packages/web/src/Playground/PlaygroundWithMachine.tsx similarity index 100% rename from src/components/Playground/PlaygroundWithMachine.tsx rename to packages/web/src/Playground/PlaygroundWithMachine.tsx diff --git a/src/components/Playground/ResizeHandle.tsx b/packages/web/src/Playground/ResizeHandle.tsx similarity index 100% rename from src/components/Playground/ResizeHandle.tsx rename to packages/web/src/Playground/ResizeHandle.tsx diff --git a/src/components/color-mode-switch.tsx b/packages/web/src/components/color-mode-switch.tsx similarity index 75% rename from src/components/color-mode-switch.tsx rename to packages/web/src/components/color-mode-switch.tsx index 658e158..220310f 100644 --- a/src/components/color-mode-switch.tsx +++ b/packages/web/src/components/color-mode-switch.tsx @@ -1,7 +1,8 @@ import { css, cx } from "panda/css"; import { ComponentPropsWithoutRef, useEffect, useState } from "react"; -import { useTheme } from "./vite-theme"; +import { useTheme } from "../vite-themes/provider"; import { styled } from "panda/jsx"; +import { IconButton } from "./icon-button"; export const ColorModeSwitch = () => { const [mounted, setMounted] = useState(false); @@ -25,35 +26,9 @@ export const ColorModeSwitch = () => { const iconText = isDark ? "Dark" : "Light"; return ( - + + + ); }; diff --git a/packages/web/src/components/github-icon.tsx b/packages/web/src/components/github-icon.tsx new file mode 100644 index 0000000..48c6d91 --- /dev/null +++ b/packages/web/src/components/github-icon.tsx @@ -0,0 +1,14 @@ +import { ComponentPropsWithoutRef } from "react"; + +export const GithubIcon = (props: ComponentPropsWithoutRef<"svg">) => ( + + + +); diff --git a/packages/web/src/components/icon-button.tsx b/packages/web/src/components/icon-button.tsx new file mode 100644 index 0000000..78dcd46 --- /dev/null +++ b/packages/web/src/components/icon-button.tsx @@ -0,0 +1,41 @@ +import { css, cx } from "panda/css"; +import { SystemStyleObject } from "panda/types"; + +interface IconButtonProps + extends React.ButtonHTMLAttributes { + children: React.ReactNode; + css?: SystemStyleObject; +} + +export function IconButton(props: IconButtonProps) { + const { children, className, css: cssProp, ...rest } = props; + return ( + + ); +} diff --git a/src/main.tsx b/packages/web/src/main.tsx similarity index 84% rename from src/main.tsx rename to packages/web/src/main.tsx index 55458e4..479ce6f 100644 --- a/src/main.tsx +++ b/packages/web/src/main.tsx @@ -1,7 +1,7 @@ import React from "react"; import ReactDOM from "react-dom/client"; import "./styles.css"; -import { Home } from "./components/Home"; +import { Home } from "./pages/Home"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/src/components/Home.tsx b/packages/web/src/pages/Home.tsx similarity index 63% rename from src/components/Home.tsx rename to packages/web/src/pages/Home.tsx index 9ab1be9..f20f50c 100644 --- a/src/components/Home.tsx +++ b/packages/web/src/pages/Home.tsx @@ -1,12 +1,13 @@ import { Container, Flex, HStack, Stack } from "panda/jsx"; import { styled } from "panda/jsx"; -import PlaygroundWithMachine from "./Playground/PlaygroundWithMachine"; +import PlaygroundWithMachine from "../Playground/PlaygroundWithMachine"; import "../styles.css"; import "@fontsource/inter"; // Defaults to weight 400 -import { ThemeProvider } from "./vite-theme"; -import { ColorModeSwitch } from "./color-mode-switch"; -import { GithubIcon } from "./github-icon"; +import { ThemeProvider } from "../vite-themes/provider"; +import { ColorModeSwitch } from "../components/color-mode-switch"; +import { GithubIcon } from "../components/github-icon"; +import { IconButton } from "../components/icon-button"; export const Home = () => { return ( @@ -17,8 +18,6 @@ export const Home = () => { color={{ base: "cyan.600", _dark: "cyan.200" }} bg={{ base: "whiteAlpha.100", _dark: "whiteAlpha.200" }} fontFamily="Inter" - // color="var(--vscode-editor-foreground)" - // bg="var(--vscode-editor-background)" > @@ -27,8 +26,13 @@ export const Home = () => { Tailwind to Panda (tw2panda) - - + + + + diff --git a/src/styles.css b/packages/web/src/styles.css similarity index 100% rename from src/styles.css rename to packages/web/src/styles.css diff --git a/packages/web/src/vite-themes/provider.tsx b/packages/web/src/vite-themes/provider.tsx new file mode 100644 index 0000000..61cb847 --- /dev/null +++ b/packages/web/src/vite-themes/provider.tsx @@ -0,0 +1,338 @@ +/** Adapted from https://github.com/pacocoursey/next-themes/blob/a385b8d865bbb317ff73a5b6c1319ae566f7d6f1/src/index.tsx */ + +import { + createContext, + Fragment, + memo, + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from "react"; +import type { ThemeProviderProps, UseThemeProps } from "./vite-themes-types"; + +const colorSchemes = ["light", "dark"]; +const MEDIA = "(prefers-color-scheme: dark)"; +const isServer = typeof window === "undefined"; +const ThemeContext = createContext(undefined); +const defaultContext: UseThemeProps = { setTheme: (_) => {}, themes: [] }; + +export const useTheme = () => useContext(ThemeContext) ?? defaultContext; + +export const ThemeProvider: React.FC = (props) => { + const context = useContext(ThemeContext); + + // Ignore nested context providers, just passthrough children + if (context) return {props.children as any}; + return ; +}; + +const defaultThemes = ["light", "dark"]; + +const Theme: React.FC = ({ + forcedTheme, + disableTransitionOnChange = false, + enableSystem = true, + enableColorScheme = true, + storageKey = "theme", + themes = defaultThemes, + defaultTheme = enableSystem ? "system" : "light", + attribute = "data-theme", + value, + children, + nonce, +}) => { + const [theme, setThemeState] = useState(() => + getTheme(storageKey, defaultTheme) + ); + const [resolvedTheme, setResolvedTheme] = useState(() => + getTheme(storageKey) + ); + const attrs = !value ? themes : Object.values(value); + + const applyTheme = useCallback((theme?: string) => { + let resolved = theme; + if (!resolved) return; + + // If theme is system, resolve it before setting theme + if (theme === "system" && enableSystem) { + resolved = getSystemTheme(); + } + + const name = value ? value[resolved] : resolved; + const enable = disableTransitionOnChange ? disableAnimation() : null; + const d = document.documentElement; + + if (attribute === "class") { + d.classList.remove(...attrs); + + if (name) d.classList.add(name); + } else { + if (name) { + d.setAttribute(attribute, name); + } else { + d.removeAttribute(attribute); + } + } + + if (enableColorScheme) { + const fallback = colorSchemes.includes(defaultTheme) + ? defaultTheme + : null; + const colorScheme = colorSchemes.includes(resolved) ? resolved : fallback; + // @ts-ignore + d.style.colorScheme = colorScheme; + } + + enable?.(); + }, []); + + const setTheme = useCallback( + (theme: string) => { + setThemeState(theme); + + // Save to storage + try { + localStorage.setItem(storageKey, theme); + } catch (e) { + // Unsupported + } + }, + [forcedTheme], + ); + + const handleMediaQuery = useCallback( + (e: MediaQueryListEvent | MediaQueryList) => { + const resolved = getSystemTheme(e); + setResolvedTheme(resolved); + + if (theme === "system" && enableSystem && !forcedTheme) { + applyTheme("system"); + } + }, + [theme, forcedTheme], + ); + + // Always listen to System preference + useEffect(() => { + const media = window.matchMedia(MEDIA); + + // Intentionally use deprecated listener methods to support iOS & old browsers + media.addListener(handleMediaQuery); + handleMediaQuery(media); + + return () => media.removeListener(handleMediaQuery); + }, [handleMediaQuery]); + + // localStorage event handling + useEffect(() => { + const handleStorage = (e: StorageEvent) => { + if (e.key !== storageKey) { + return; + } + + // If default theme set, use it if localstorage === null (happens on local storage manual deletion) + const theme = e.newValue || defaultTheme; + setTheme(theme); + }; + + window.addEventListener("storage", handleStorage); + return () => window.removeEventListener("storage", handleStorage); + }, [setTheme]); + + // Whenever theme or forcedTheme changes, apply it + useEffect(() => { + applyTheme(forcedTheme ?? theme); + }, [forcedTheme, theme]); + + const providerValue = useMemo(() => ({ + theme, + setTheme, + forcedTheme, + resolvedTheme: theme === "system" ? resolvedTheme : theme, + themes: enableSystem ? [...themes, "system"] : themes, + systemTheme: (enableSystem ? resolvedTheme : undefined) as + | "light" + | "dark" + | undefined, + }), [theme, setTheme, forcedTheme, resolvedTheme, enableSystem, themes]); + + return ( + + + {children as any} + + ); +}; + +const ThemeScript = memo( + ({ + forcedTheme, + storageKey, + attribute, + enableSystem, + enableColorScheme, + defaultTheme, + value, + attrs, + nonce, + }: ThemeProviderProps & { attrs: string[]; defaultTheme: string }) => { + const defaultSystem = defaultTheme === "system"; + + // Code-golfing the amount of characters in the script + const optimization = (() => { + if (attribute === "class") { + const removeClasses = `c.remove(${ + attrs.map((t: string) => `'${t}'`).join(",") + })`; + + return `var d=document.documentElement,c=d.classList;${removeClasses};`; + } else { + return `var d=document.documentElement,n='${attribute}',s='setAttribute';`; + } + })(); + + const fallbackColorScheme = (() => { + if (!enableColorScheme) { + return ""; + } + + const fallback = colorSchemes.includes(defaultTheme) + ? defaultTheme + : null; + + if (fallback) { + return `if(e==='light'||e==='dark'||!e)d.style.colorScheme=e||'${defaultTheme}'`; + } else { + return `if(e==='light'||e==='dark')d.style.colorScheme=e`; + } + })(); + + const updateDOM = ( + name: string, + literal: boolean = false, + setColorScheme = true, + ) => { + const resolvedName = value ? value[name] : name; + const val = literal ? name + `|| ''` : `'${resolvedName}'`; + let text = ""; + + // MUCH faster to set colorScheme alongside HTML attribute/class + // as it only incurs 1 style recalculation rather than 2 + // This can save over 250ms of work for pages with big DOM + if ( + enableColorScheme && setColorScheme && !literal && + colorSchemes.includes(name) + ) { + text += `d.style.colorScheme = '${name}';`; + } + + if (attribute === "class") { + if (literal || resolvedName) { + text += `c.add(${val})`; + } else { + text += `null`; + } + } else { + if (resolvedName) { + text += `d[s](n,${val})`; + } + } + + return text; + }; + + const scriptSrc = (() => { + if (forcedTheme) { + return `!function(){${optimization}${updateDOM(forcedTheme)}}()`; + } + + if (enableSystem) { + return `!function(){try{${optimization}var e=localStorage.getItem('${storageKey}');if('system'===e||(!e&&${defaultSystem})){var t='${MEDIA}',m=window.matchMedia(t);if(m.media!==t||m.matches){${ + updateDOM( + "dark", + ) + }}else{${updateDOM("light")}}}else if(e){${ + value ? `var x=${JSON.stringify(value)};` : "" + }${updateDOM(value ? `x[e]` : "e", true)}}${ + !defaultSystem + ? `else{` + updateDOM(defaultTheme, false, false) + "}" + : "" + }${fallbackColorScheme}}catch(e){}}()`; + } + + return `!function(){try{${optimization}var e=localStorage.getItem('${storageKey}');if(e){${ + value ? `var x=${JSON.stringify(value)};` : "" + }${updateDOM(value ? `x[e]` : "e", true)}}else{${ + updateDOM( + defaultTheme, + false, + false, + ) + };}${fallbackColorScheme}}catch(t){}}();`; + })(); + + return ( +