├── src ├── utils │ ├── reflow.ts │ ├── hasNonOverlayScrollbarY.ts │ └── onTransitionend.ts ├── entry-client.tsx ├── entry-server.tsx ├── editor │ ├── plugins │ │ ├── cursor-style.ts │ │ ├── line-numbers-style.ts │ │ ├── selection-style.ts │ │ ├── autocomplete-style.ts │ │ └── highlight-style.ts │ ├── editorBaseTheme.ts │ ├── theme │ │ ├── light.ts │ │ └── dark.ts │ └── defineEditorTheme.ts ├── components │ ├── Icons │ │ ├── ChevronDownIcon.tsx │ │ ├── XIcon.tsx │ │ ├── SplitPanelColumns.tsx │ │ ├── ArrowLeftIcon.tsx │ │ ├── CopyIcon.tsx │ │ ├── TrashIcon.tsx │ │ ├── HalfSun.tsx │ │ ├── GithubIcon.tsx │ │ ├── SplitPanelRows.tsx │ │ └── SettingsIcon.tsx │ ├── ConfigPanel │ │ ├── Toggle.tsx │ │ ├── Select.tsx │ │ ├── Input.tsx │ │ └── ConfigPanel.tsx │ ├── Editors │ │ ├── SplitEditor.tsx │ │ ├── HTMLEditor.tsx │ │ └── JSXEditor.tsx │ ├── CopyJSXButton.tsx │ ├── Header │ │ ├── Header.tsx │ │ └── ThemeBtn.tsx │ ├── SettingsPanel │ │ ├── SettingsPanel.tsx │ │ └── TogglePanelButton.tsx │ ├── SolidLogo.tsx │ ├── ActionsPanel │ │ ├── TogglePanelButton.tsx │ │ └── ActionsPanel.tsx │ └── MobileScrollDVH.tsx ├── routes │ └── index.tsx ├── store.ts ├── root.css ├── root.tsx └── lib │ └── html-to-jsx.ts ├── public ├── og.png ├── fonts │ ├── .DS_Store │ └── Gordita │ │ ├── Gordita-Black.otf │ │ ├── Gordita-Black.woff │ │ ├── Gordita-Bold.eot │ │ ├── Gordita-Bold.otf │ │ ├── Gordita-Bold.ttf │ │ ├── Gordita-Bold.woff │ │ ├── Gordita-Bold.woff2 │ │ ├── Gordita-Light.otf │ │ ├── Gordita-Light.woff │ │ ├── Gordita-Medium.eot │ │ ├── Gordita-Medium.otf │ │ ├── Gordita-Medium.ttf │ │ ├── Gordita-Thin.eot │ │ ├── Gordita-Thin.otf │ │ ├── Gordita-Thin.ttf │ │ ├── Gordita-Thin.woff │ │ ├── Gordita-Thin.woff2 │ │ ├── Gordita-Ultra.otf │ │ ├── Gordita-Ultra.woff │ │ ├── Gordita-Black.woff2 │ │ ├── Gordita-Light.woff2 │ │ ├── Gordita-Medium.woff │ │ ├── Gordita-Medium.woff2 │ │ ├── Gordita-Regular.eot │ │ ├── Gordita-Regular.otf │ │ ├── Gordita-Regular.ttf │ │ ├── Gordita-Regular.woff │ │ ├── Gordita-Ultra.woff2 │ │ ├── Gordita-Bold-italic.otf │ │ ├── Gordita-Regular.woff2 │ │ ├── Gordita-Thin-Italic.otf │ │ ├── Gordita-Black-Italic.otf │ │ ├── Gordita-Black-Italic.woff │ │ ├── Gordita-Bold-Italic.woff │ │ ├── Gordita-Bold-italic.woff2 │ │ ├── Gordita-Light-Italic.otf │ │ ├── Gordita-Light-Italic.woff │ │ ├── Gordita-Medium-Italic.otf │ │ ├── Gordita-Thin-Italic.woff │ │ ├── Gordita-Thin-Italic.woff2 │ │ ├── Gordita-Ultra-Italic.otf │ │ ├── Gordita-Ultra-Italic.woff │ │ ├── Gordita-Black-Italic.woff2 │ │ ├── Gordita-Light-Italic.woff2 │ │ ├── Gordita-Medium-Italic.woff │ │ ├── Gordita-Medium-Italic.woff2 │ │ ├── Gordita-Regular-Italic.otf │ │ ├── Gordita-Regular-Italic.woff │ │ ├── Gordita-Regular-Italic.woff2 │ │ └── Gordita-Ultra-Italic.woff2 └── favicons │ ├── favicon.ico │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── icon-192x192.png │ ├── icon-256x256.png │ ├── icon-384x384.png │ ├── icon-512x512.png │ ├── mstile-150x150.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── browserconfig.xml ├── .prettierrc ├── tsconfig.json ├── vite.config.ts ├── .github └── workflows │ └── main.yaml ├── LICENSE ├── package.json ├── unocss.config.ts ├── README.md └── .gitignore /src/utils/reflow.ts: -------------------------------------------------------------------------------- 1 | export const reflow = () => document.body.clientWidth; 2 | -------------------------------------------------------------------------------- /public/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/og.png -------------------------------------------------------------------------------- /public/fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/.DS_Store -------------------------------------------------------------------------------- /public/favicons/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/favicon.ico -------------------------------------------------------------------------------- /public/favicons/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicons/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/icon-192x192.png -------------------------------------------------------------------------------- /public/favicons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/icon-256x256.png -------------------------------------------------------------------------------- /public/favicons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/icon-384x384.png -------------------------------------------------------------------------------- /public/favicons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/icon-512x512.png -------------------------------------------------------------------------------- /public/favicons/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/mstile-150x150.png -------------------------------------------------------------------------------- /src/entry-client.tsx: -------------------------------------------------------------------------------- 1 | import { mount, StartClient } from "solid-start/entry-client"; 2 | 3 | mount(() => , document); 4 | -------------------------------------------------------------------------------- /public/favicons/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/apple-touch-icon.png -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Black.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Black.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Black.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold.eot -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Light.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Light.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium.eot -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin.eot -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin.ttf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Ultra.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Ultra.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Ultra.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Ultra.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Black.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Black.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Light.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular.eot -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Ultra.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Ultra.woff2 -------------------------------------------------------------------------------- /public/favicons/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/favicons/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/favicons/android-chrome-512x512.png -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold-italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold-italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin-Italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Black-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Black-Italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Black-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Black-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Bold-italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Light-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Light-Italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Light-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Light-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium-Italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Thin-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Thin-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Ultra-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Ultra-Italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Ultra-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Ultra-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Black-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Black-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Light-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Light-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Medium-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Medium-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular-Italic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular-Italic.otf -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular-Italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular-Italic.woff -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Regular-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Regular-Italic.woff2 -------------------------------------------------------------------------------- /public/fonts/Gordita/Gordita-Ultra-Italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/solidjs-community/html-to-solidjsx/HEAD/public/fonts/Gordita/Gordita-Ultra-Italic.woff2 -------------------------------------------------------------------------------- /src/entry-server.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createHandler, 3 | renderAsync, 4 | StartServer, 5 | } from "solid-start/entry-server"; 6 | 7 | export default createHandler( 8 | renderAsync((event) => ) 9 | ); 10 | -------------------------------------------------------------------------------- /src/utils/hasNonOverlayScrollbarY.ts: -------------------------------------------------------------------------------- 1 | export const hasNonOverlayScrollbarY = (el: HTMLElement) => { 2 | const { offsetWidth, clientWidth } = el; 3 | if (clientWidth === 0) return false; 4 | 5 | return clientWidth !== offsetWidth; 6 | }; 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "tabWidth": 2, 4 | "printWidth": 100, 5 | "semi": true, 6 | "singleQuote": false, 7 | "useTabs": false, 8 | "arrowParens": "always", 9 | "bracketSpacing": true, 10 | "endOfLine": "lf" 11 | } 12 | -------------------------------------------------------------------------------- /src/utils/onTransitionend.ts: -------------------------------------------------------------------------------- 1 | export const onTransitionend = (el: HTMLElement, cb: () => void) => { 2 | el.addEventListener( 3 | "transitionend", 4 | (e) => { 5 | if (e.currentTarget !== e.target) return; 6 | cb(); 7 | }, 8 | { once: true } 9 | ); 10 | }; 11 | -------------------------------------------------------------------------------- /public/favicons/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #da532c 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/editor/plugins/cursor-style.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | 3 | export interface StyledCursorOptions { 4 | color: string; 5 | } 6 | 7 | export function styledCursor(options: StyledCursorOptions) { 8 | return EditorView.theme({ 9 | ".cm-cursor, .cm-dropCursor": { borderLeftColor: options.color }, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /src/editor/plugins/line-numbers-style.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | 3 | export interface StyledLineNumbersOptions { 4 | color: string; 5 | } 6 | 7 | export function styledLineNumbers(options: StyledLineNumbersOptions) { 8 | return EditorView.theme({ 9 | ".cm-lineNumbers .cm-gutterElement": { color: options.color }, 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "esModuleInterop": true, 5 | "target": "ESNext", 6 | "module": "ESNext", 7 | "moduleResolution": "node", 8 | "jsxImportSource": "solid-js", 9 | "jsx": "preserve", 10 | "strict": true, 11 | "types": ["solid-start/env"], 12 | "baseUrl": "./", 13 | "paths": { 14 | "~/*": ["./src/*"] 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import solid from "solid-start/vite"; 2 | import { defineConfig } from "vite"; 3 | import UnoCss from "unocss/vite"; 4 | 5 | export default defineConfig({ 6 | base: "/html-to-solidjsx/", 7 | optimizeDeps: { 8 | // Add both @codemirror/state and @codemirror/view to included deps to optimize 9 | include: ["@codemirror/state", "@codemirror/view"], 10 | }, 11 | plugins: [ 12 | solid({ 13 | adapter: "solid-start-static", 14 | }), 15 | UnoCss(), 16 | ], 17 | }); 18 | -------------------------------------------------------------------------------- /src/components/Icons/ChevronDownIcon.tsx: -------------------------------------------------------------------------------- 1 | const ChevronDownIcon = (props: any) => { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default ChevronDownIcon; 21 | -------------------------------------------------------------------------------- /src/components/Icons/XIcon.tsx: -------------------------------------------------------------------------------- 1 | const XIcon = (props: any) => { 2 | return ( 3 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default XIcon; 22 | -------------------------------------------------------------------------------- /src/components/Icons/SplitPanelColumns.tsx: -------------------------------------------------------------------------------- 1 | const SplitPanelColumns = () => { 2 | return ( 3 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | }; 18 | 19 | export default SplitPanelColumns; 20 | -------------------------------------------------------------------------------- /src/components/Icons/ArrowLeftIcon.tsx: -------------------------------------------------------------------------------- 1 | const ArrowLeftIcon = (props: any) => { 2 | return ( 3 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default ArrowLeftIcon; 22 | -------------------------------------------------------------------------------- /src/editor/plugins/selection-style.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | 3 | export interface StyledSelectionOptions { 4 | backgroundColor: string; 5 | color: string; 6 | activeLine?: string; 7 | } 8 | 9 | export function styledSelection(options: StyledSelectionOptions) { 10 | return EditorView.theme({ 11 | "&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": 12 | { 13 | background: options.backgroundColor, 14 | }, 15 | ".cm-activeLine": { 16 | backgroundColor: options.activeLine ?? "unset", 17 | }, 18 | }); 19 | } 20 | -------------------------------------------------------------------------------- /src/components/Icons/CopyIcon.tsx: -------------------------------------------------------------------------------- 1 | const CopyIcon = (props: any) => { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default CopyIcon; 22 | -------------------------------------------------------------------------------- /src/components/Icons/TrashIcon.tsx: -------------------------------------------------------------------------------- 1 | const TrashIcon = (props: any) => { 2 | return ( 3 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default TrashIcon; 22 | -------------------------------------------------------------------------------- /src/components/Icons/HalfSun.tsx: -------------------------------------------------------------------------------- 1 | const HalfSun = () => { 2 | return ( 3 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | 25 | export default HalfSun; 26 | -------------------------------------------------------------------------------- /src/editor/editorBaseTheme.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | 3 | export const editorBaseTheme = (props: { backgroundColor?: string } = {}) => 4 | EditorView.theme({ 5 | "&": { 6 | textAlign: "left", 7 | // fontSize: "13px", 8 | background: "transparent", 9 | }, 10 | ".cm-activeLineGutter": { 11 | backgroundColor: "transparent", 12 | }, 13 | ".cm-gutters": { 14 | backgroundColor: props.backgroundColor ?? "", 15 | border: "none", 16 | }, 17 | ".cm-line": { 18 | padding: "0 2px 6px 16px", 19 | }, 20 | ".cm-content *": { 21 | fontFamily: `monospace`, 22 | fontWeight: 400, 23 | fontVariantLigatures: "normal", 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | name: Deploy site 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | workflow_dispatch: 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | 15 | - uses: pnpm/action-setup@v2.2.4 16 | 17 | - name: Setup Node.js environment 18 | 19 | uses: actions/setup-node@v3 20 | with: 21 | node-version: 18 22 | cache: pnpm 23 | 24 | - name: Install dependencies 25 | run: pnpm install 26 | 27 | - name: Build 28 | run: pnpm build 29 | 30 | - name: deploy pages 31 | uses: JamesIves/github-pages-deploy-action@v4.4.1 32 | with: 33 | branch: gh-pages 34 | folder: dist/public 35 | -------------------------------------------------------------------------------- /src/components/Icons/GithubIcon.tsx: -------------------------------------------------------------------------------- 1 | const GithubIcon = () => { 2 | return ( 3 | 15 | 16 | 17 | ); 18 | }; 19 | 20 | export default GithubIcon; 21 | -------------------------------------------------------------------------------- /src/components/Icons/SplitPanelRows.tsx: -------------------------------------------------------------------------------- 1 | const SplitPanelRows = () => { 2 | return ( 3 | 10 | 11 | 12 | 19 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default SplitPanelRows; 32 | -------------------------------------------------------------------------------- /src/components/ConfigPanel/Toggle.tsx: -------------------------------------------------------------------------------- 1 | import { Component } from "solid-js"; 2 | 3 | const Toggle: Component<{ 4 | name: string; 5 | value: boolean; 6 | disabled?: boolean; 7 | onChange: (props: { checked: boolean }) => void; 8 | }> = (props) => { 9 | return ( 10 | 25 | ); 26 | }; 27 | 28 | export default Toggle; 29 | -------------------------------------------------------------------------------- /src/routes/index.tsx: -------------------------------------------------------------------------------- 1 | import ActionsPanel from "~/components/ActionsPanel/ActionsPanel"; 2 | import SplitEditor from "~/components/Editors/SplitEditor"; 3 | import Header from "~/components/Header/Header"; 4 | import MobileScrollDVH from "~/components/MobileScrollDVH"; 5 | import SettingsPanel from "~/components/SettingsPanel/SettingsPanel"; 6 | 7 | export default function Home() { 8 | return ( 9 | <> 10 |
11 | 12 |
16 |
20 | 21 | 22 |
23 | 24 |
25 |
26 | 27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 SolidJS Community 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/editor/plugins/autocomplete-style.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | 3 | export interface StyledAutoCompleteOptions { 4 | background: string; 5 | border?: string; 6 | selectedBackground: string; 7 | selectedColor?: string; 8 | } 9 | 10 | export function styledAutocomplete(options: StyledAutoCompleteOptions) { 11 | return EditorView.theme({ 12 | ".cm-tooltip": { 13 | border: options.border ? `1px solid ${options.border}` : "none", 14 | backgroundColor: options.background, 15 | borderRadius: "6px", 16 | }, 17 | 18 | ".cm-tooltip .cm-tooltip-arrow:before": { 19 | borderTopColor: "transparent", 20 | borderBottomColor: "transparent", 21 | }, 22 | ".cm-tooltip .cm-tooltip-arrow:after": { 23 | borderTopColor: options.background, 24 | borderBottomColor: options.background, 25 | }, 26 | 27 | ".cm-tooltip-autocomplete": { 28 | "& > ul > li": { 29 | padding: "6px !important", 30 | }, 31 | "& > ul > li[aria-selected]": { 32 | backgroundColor: options.selectedBackground, 33 | color: options.selectedColor ?? "inherit", 34 | }, 35 | }, 36 | }); 37 | } 38 | -------------------------------------------------------------------------------- /src/editor/theme/light.ts: -------------------------------------------------------------------------------- 1 | import { defineEditorTheme } from "../defineEditorTheme"; 2 | 3 | export const githubLight = [ 4 | defineEditorTheme({ 5 | darkMode: false, 6 | cursor: { 7 | color: "#24292f", 8 | }, 9 | lineNumbers: { 10 | color: "#8493a1", 11 | }, 12 | selection: { 13 | activeLine: "rgba(234,238,242,0.5)", 14 | backgroundColor: "rgba(84,174,255,0.4)", 15 | color: "#24292f", 16 | }, 17 | autocomplete: { 18 | background: "#32383f", 19 | border: "#424a53", 20 | selectedBackground: "#424a53", 21 | selectedColor: "#f6f8fa", 22 | }, 23 | highlight: { 24 | base: "#24292f", 25 | background: "#ffffff", 26 | tag: "#116329", 27 | delimiters: "#6e7781", 28 | numbers: "#0a3069", 29 | punctuation: "#bf8700", 30 | className: "#953800", 31 | brackets: "#bf8700", 32 | keywords: "#cf222e", 33 | strings: "#0a3069", 34 | propertyName: "#0a3069", 35 | variableName: "#953800", 36 | regexp: "#116329", 37 | comments: "#6e7781", 38 | attrName: "#0550ae", 39 | function: "#8250df", 40 | typeName: "#0550ae", 41 | }, 42 | }), 43 | ]; 44 | -------------------------------------------------------------------------------- /src/editor/defineEditorTheme.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | import { styledAutocomplete } from "./plugins/autocomplete-style"; 3 | import { styledCursor } from "./plugins/cursor-style"; 4 | import { styledHighlight } from "./plugins/highlight-style"; 5 | import { styledLineNumbers } from "./plugins/line-numbers-style"; 6 | import { styledSelection } from "./plugins/selection-style"; 7 | 8 | export const defineEditorTheme = (theme: any) => { 9 | const { darkMode, highlight, selection, autocomplete, cursor, lineNumbers } = 10 | theme; 11 | 12 | const base = EditorView.theme({ 13 | "&": { 14 | color: highlight.base, 15 | }, 16 | }); 17 | 18 | return [ 19 | base, 20 | styledCursor({ 21 | color: cursor?.color ?? (darkMode ? "#FFF" : "#000"), 22 | }), 23 | lineNumbers?.color 24 | ? styledLineNumbers({ 25 | color: lineNumbers?.color ?? (darkMode ? "#FFF" : "#000"), 26 | }) 27 | : [], 28 | styledSelection({ 29 | backgroundColor: selection?.backgroundColor ?? `${highlight.keywords}50`, 30 | color: selection?.color ?? "inherit", 31 | }), 32 | styledAutocomplete(autocomplete), 33 | styledHighlight(highlight), 34 | ]; 35 | }; 36 | -------------------------------------------------------------------------------- /src/components/Editors/SplitEditor.tsx: -------------------------------------------------------------------------------- 1 | import { store } from "../../store"; 2 | import HTMLEditor from "./HTMLEditor"; 3 | import JSXEditor from "./JSXEditor"; 4 | 5 | const SplitEditor = () => { 6 | return ( 7 |
16 |
24 | 25 |
26 |
34 | 35 |
36 |
37 | ); 38 | }; 39 | 40 | export default SplitEditor; 41 | -------------------------------------------------------------------------------- /src/components/Icons/SettingsIcon.tsx: -------------------------------------------------------------------------------- 1 | const SettingsIcon = (props: any) => { 2 | return ( 3 | 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default SettingsIcon; 22 | -------------------------------------------------------------------------------- /src/components/CopyJSXButton.tsx: -------------------------------------------------------------------------------- 1 | import { writeClipboard } from "@solid-primitives/clipboard"; 2 | import { debounce } from "@solid-primitives/scheduled"; 3 | import { createSignal } from "solid-js"; 4 | import { store } from "../store"; 5 | import CopyIcon from "./Icons/CopyIcon"; 6 | 7 | const CopyJSXButton = () => { 8 | const [hasCopied, setHasCopied] = createSignal(false); 9 | 10 | const setHasCopiedDebounced = debounce(() => setHasCopied(false), 1500); 11 | 12 | const copyToClipboard = async () => { 13 | try { 14 | await writeClipboard(store.jsxText.trim()); 15 | setHasCopied(true); 16 | setHasCopiedDebounced(); 17 | } catch (err) {} 18 | }; 19 | 20 | const onCopyClick = () => { 21 | copyToClipboard(); 22 | }; 23 | return ( 24 | 33 | ); 34 | }; 35 | 36 | export default CopyJSXButton; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html-to-solidjsx", 3 | "scripts": { 4 | "dev": "solid-start dev", 5 | "build": "solid-start build", 6 | "start": "solid-start start" 7 | }, 8 | "type": "module", 9 | "devDependencies": { 10 | "@types/node": "^18.11.18", 11 | "esbuild": "^0.14.54", 12 | "postcss": "^8.4.21", 13 | "typescript": "^4.9.4", 14 | "vite": "^4.0.3" 15 | }, 16 | "dependencies": { 17 | "@codemirror/lang-html": "^6.4.0", 18 | "@codemirror/lang-javascript": "^6.1.2", 19 | "@codemirror/language": "^6.3.1", 20 | "@codemirror/state": "^6.1.4", 21 | "@codemirror/view": "^6.7.1", 22 | "@floating-ui/dom": "^1.1.1", 23 | "@lezer/highlight": "^1.1.3", 24 | "@solid-primitives/clipboard": "^1.5.0", 25 | "@solid-primitives/media": "^2.0.6", 26 | "@solid-primitives/platform": "^0.0.103", 27 | "@solid-primitives/resize-observer": "^2.0.9", 28 | "@solid-primitives/scheduled": "^1.2.1", 29 | "@solidjs/meta": "^0.28.6", 30 | "@solidjs/router": "^0.8.3", 31 | "@unocss/preset-wind": "^0.49.2", 32 | "@unocss/reset": "^0.49.2", 33 | "@unocss/transformer-variant-group": "^0.49.4", 34 | "codemirror": "^6.0.1", 35 | "solid-codemirror": "^2.2.1", 36 | "solid-dismiss": "^1.6.5", 37 | "solid-floating-ui": "^0.2.0", 38 | "solid-icons": "^1.0.4", 39 | "solid-js": "^1.7.12", 40 | "solid-start": "^0.3.6", 41 | "solid-start-static": "^0.3.6", 42 | "undici": "^5.15.1", 43 | "unocss": "0.54.3" 44 | }, 45 | "packageManager": "pnpm@8.6.0", 46 | "engines": { 47 | "node": ">=18.0.0", 48 | "pnpm": ">=8.6.0" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /unocss.config.ts: -------------------------------------------------------------------------------- 1 | import { presetWind, theme } from "@unocss/preset-wind"; 2 | import { 3 | transformerDirectives, 4 | defineConfig, 5 | transformerVariantGroup, 6 | } from "unocss"; 7 | 8 | const parseValue = (value: string) => { 9 | return value.replace(/_/g, " "); 10 | }; 11 | // mask-image-[linear-gradient(135deg,#000_calc(50%_-_1px),transparent_calc(50%_-_1px))] 12 | export default defineConfig({ 13 | rules: [ 14 | [ 15 | /^bg-image-\[(.+)\]$/, 16 | ([_, d]) => ({ "background-image": parseValue(d) }), 17 | ], 18 | [/^transition-prop-\[(.+)\]$/, ([_, d]) => ({ transition: parseValue(d) })], 19 | [ 20 | /^mask-image-\[(.+)\]$/, 21 | ([_, d]) => ({ 22 | "-webkit-mask-image": parseValue(d), 23 | "mask-image": parseValue(d), 24 | }), 25 | ], 26 | ], 27 | theme: { 28 | breakpoints: { 29 | ...theme.breakpoints, 30 | md: "850px", 31 | }, 32 | colors: { 33 | brand: { 34 | default: "#2c4f7c", 35 | dark: "#335d92", 36 | medium: "#446b9e", 37 | light: "#4f88c6", 38 | }, 39 | solid: { 40 | default: "#2c4f7c", 41 | darkbg: "#222222", 42 | darkLighterBg: "#444444", 43 | darkdefault: "#b8d7ff", 44 | darkgray: "#252525", 45 | gray: "#414042", 46 | mediumgray: "#9d9d9d", 47 | lightgray: "#f3f5f7", 48 | dark: "#07254A", 49 | medium: "#446b9e", 50 | light: "#4f88c6", 51 | accent: "#0cdc73", 52 | secondaccent: "#0dfc85", 53 | }, 54 | other: "#1e1e1e", 55 | }, 56 | fontFamily: { 57 | sans: "Gordita, " + theme.fontFamily!.sans, 58 | }, 59 | }, 60 | presets: [presetWind()], 61 | transformers: [transformerDirectives(), transformerVariantGroup()], 62 | }); 63 | -------------------------------------------------------------------------------- /src/components/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import GithubIcon from "../Icons/GithubIcon"; 2 | import { panelSize } from "../SettingsPanel/SettingsPanel"; 3 | import SolidLogo from "../SolidLogo"; 4 | import ThemeBtn from "./ThemeBtn"; 5 | 6 | const Header = () => { 7 | return ( 8 | 42 | ); 43 | }; 44 | 45 | export default Header; 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | HTML to Solid JSX 3 |

4 | 5 | # HTML to SolidJSX 6 | 7 | This is the source code of the [HTML to Solid JSX](https://solidjs-community.github.io/html-to-solidjsx) website. 8 | 9 | ## Purpose 10 | 11 | Existing HTML to JSX online transformers aren't compatible for SolidJS, it transforms to JSX that is suited for React templates. 12 | 13 | 1. Replaces standard HTML attributes such as `class` and `for` to `className` and `htmlFor`. 14 | 2. Incorrectly changes css variables names inside style attribute. 15 | 16 | Solid attempts to stay as close to HTML standards as possible, allowing copy and paste from answers on Stack Overflow or from template builders from your designers. This [site](https://solidjs-community.github.io/html-to-solidjsx) brings that goal even closer by converting void elements(`
`) to self-closing(`
`), while also providing customizations such as the option to camelCase attributes or having style attribute value set as a CSS object or string. 17 | 18 | ## Credits / Technologies used 19 | 20 | - [solid-start](https://start.solidjs.com/): The meta-framework 21 | - [solid-js](https://github.com/solidjs/solid/): The view library 22 | - [codemirror](https://codemirror.net/): The in-browser code editor 23 | - [solid-codemirror](https://github.com/riccardoperra/solid-codemirror): The SolidJS bindings for codemirror 24 | - [htmltojsx](https://github.com/reactjs/react-magic/blob/master/README-htmltojsx.md)(modified for Solid JSX compatiblity): The HTML to JSX converter 25 | - [UnoCSS](https://uno.antfu.me/): The CSS framework 26 | - [solid-primitives](https://github.com/solidjs-community/solid-primitives): The utilities/primitives 27 | - [vite](https://vitejs.dev/): The module bundler 28 | - [pnpm](https://pnpm.js.org/): The package manager 29 | -------------------------------------------------------------------------------- /src/components/SettingsPanel/SettingsPanel.tsx: -------------------------------------------------------------------------------- 1 | import { onMount } from "solid-js"; 2 | import { setStore, store } from "../../store"; 3 | import ConfigPanel from "../ConfigPanel/ConfigPanel"; 4 | import TogglePanelButton from "./TogglePanelButton"; 5 | 6 | export const panelSize = "w-full md:w-35vw md:min-w-300px md:max-w-450px"; 7 | 8 | const SettingsPanel = () => { 9 | onMount(() => { 10 | try { 11 | const lineWrap = JSON.parse(localStorage.lineWrap) as boolean; 12 | 13 | requestAnimationFrame(() => { 14 | setStore("lineWrap", lineWrap); 15 | }); 16 | } catch (err) {} 17 | }); 18 | return ( 19 |
23 |
24 | 46 |
47 | 48 |
49 |
50 |
51 | ); 52 | }; 53 | 54 | export default SettingsPanel; 55 | -------------------------------------------------------------------------------- /src/editor/theme/dark.ts: -------------------------------------------------------------------------------- 1 | import { EditorView } from "@codemirror/view"; 2 | import { defineEditorTheme } from "../defineEditorTheme"; 3 | 4 | export const background = "#262335"; 5 | const foreground = "hsl(204, 3%, 98%)"; 6 | const identifier = "hsl(204, 98%, 80%)"; 7 | const selection = "hsl(210, 52%, 31%)"; 8 | const selectionMatch = "hsl(210, 12%, 22%)"; 9 | const matchingBracket = "hsla(204, 3%, 80%, 0.5)"; 10 | const lineNumber = "hsl(204, 3%, 50%)"; 11 | const activeLine = "hsla(204, 3%, 20%, 0.4)"; 12 | const keyword = "hsl(207, 65%, 59%)"; 13 | const comment = "hsl(101, 33%, 47%)"; 14 | const number = "hsl(99, 28%, 73%)"; 15 | const string = "hsl(17, 60%, 64%)"; 16 | const func = "hsl(60,42%,76%)"; 17 | const regex = "hsl(0, 60%, 62%)"; 18 | const tag = "hsl(168, 60%, 55%)"; 19 | const purple = "#C586C0"; 20 | const yellow = "#DBD700"; 21 | 22 | export const vsCodeDark = [ 23 | defineEditorTheme({ 24 | selection: { 25 | backgroundColor: selection, 26 | activeLine: activeLine, 27 | }, 28 | lineNumbers: { 29 | color: lineNumber, 30 | }, 31 | cursor: { 32 | color: foreground, 33 | }, 34 | highlight: { 35 | base: identifier, 36 | tag, 37 | keywords: keyword, 38 | comments: comment, 39 | numbers: number, 40 | strings: string, 41 | function: func, 42 | regexp: regex, 43 | boolean: number, 44 | propertyName: identifier, 45 | variableName: identifier, 46 | punctuation: foreground, 47 | attrValue: string, 48 | className: tag, 49 | delimiters: foreground, 50 | annotation: string, 51 | moduleKeyword: purple, 52 | brackets: yellow, 53 | paren: yellow, 54 | typeName: tag, 55 | }, 56 | darkMode: true, 57 | autocomplete: { 58 | background: "#1E1E1E", 59 | selectedBackground: "#191818", 60 | }, 61 | }), 62 | EditorView.theme({ 63 | ".cm-matchingBracket": { 64 | backgroundColor: selectionMatch, 65 | outline: `1px solid ${matchingBracket}`, 66 | }, 67 | ".cm-content": { 68 | caretColor: "#fff", 69 | }, 70 | }), 71 | ]; 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # solid-start 2 | .solid 3 | 4 | .output 5 | 6 | # Mac 7 | .DS_Store 8 | 9 | # Logs 10 | logs 11 | *.log 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | lerna-debug.log* 16 | 17 | # Diagnostic reports (https://nodejs.org/api/report.html) 18 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 19 | 20 | # Runtime data 21 | pids 22 | *.pid 23 | *.seed 24 | *.pid.lock 25 | 26 | # Directory for instrumented libs generated by jscoverage/JSCover 27 | lib-cov 28 | 29 | # Coverage directory used by tools like istanbul 30 | coverage 31 | *.lcov 32 | 33 | # nyc test coverage 34 | .nyc_output 35 | 36 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 37 | .grunt 38 | 39 | # Bower dependency directory (https://bower.io/) 40 | bower_components 41 | 42 | # node-waf configuration 43 | .lock-wscript 44 | 45 | # Compiled binary addons (https://nodejs.org/api/addons.html) 46 | build/Release 47 | 48 | # Dependency directories 49 | node_modules/ 50 | jspm_packages/ 51 | 52 | # TypeScript v1 declaration files 53 | typings/ 54 | 55 | # TypeScript cache 56 | *.tsbuildinfo 57 | 58 | # Optional npm cache directory 59 | .npm 60 | 61 | # Optional eslint cache 62 | .eslintcache 63 | 64 | # Microbundle cache 65 | .rpt2_cache/ 66 | .rts2_cache_cjs/ 67 | .rts2_cache_es/ 68 | .rts2_cache_umd/ 69 | 70 | # Optional REPL history 71 | .node_repl_history 72 | 73 | # Output of 'npm pack' 74 | *.tgz 75 | 76 | # Yarn Integrity file 77 | .yarn-integrity 78 | 79 | # dotenv environment variables file 80 | .env 81 | .env.test 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | 86 | # Next.js build output 87 | .next 88 | 89 | # Nuxt.js build / generate output 90 | .nuxt 91 | dist 92 | 93 | # Gatsby files 94 | .cache/ 95 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 96 | # https://nextjs.org/blog/next-9-1#public-directory-support 97 | # public 98 | 99 | # vuepress build output 100 | .vuepress/dist 101 | 102 | # Serverless directories 103 | .serverless/ 104 | 105 | # FuseBox cache 106 | .fusebox/ 107 | 108 | # DynamoDB Local files 109 | .dynamodb/ 110 | 111 | # TernJS port file 112 | .tern-port 113 | -------------------------------------------------------------------------------- /src/store.ts: -------------------------------------------------------------------------------- 1 | import { createStore } from "solid-js/store"; 2 | import { isServer } from "solid-js/web"; 3 | import { HTMLtoJSXConfig } from "./lib/html-to-jsx"; 4 | export type TJSXConfig = HTMLtoJSXConfig & { prefixSVGIds?: string }; 5 | type TStore = { 6 | config: TJSXConfig; 7 | htmlText: string; 8 | jsxText: string; 9 | layout: "columns" | "rows" | "jsx" | "html"; 10 | lineWrap: boolean; 11 | }; 12 | export const defaultConfig: TJSXConfig = { 13 | attributeValueString: true, 14 | camelCaseAttributes: false, 15 | component: "none", 16 | wrapperNode: "none", 17 | componentName: "SolidComponent", 18 | indent: " ", 19 | preTagWrapTemplateLiterals: false, 20 | styleAttribute: "css-object", 21 | styleTagAttributeInnerHTML: false, 22 | stripStyleTag: false, 23 | stripComment: false, 24 | prefixSVGIds: "", 25 | exportComponent: false, 26 | }; 27 | 28 | export type ConfigKey = keyof HTMLtoJSXConfig; 29 | export const [store, setStore] = createStore({ 30 | config: { ...defaultConfig }, 31 | htmlText: getHTMLText().trimStart(), 32 | jsxText: getJSXText().trimStart(), 33 | layout: "rows", 34 | lineWrap: true, 35 | }); 36 | 37 | function getHTMLText() { 38 | return ` 39 | 40 |

SolidJSX

41 |
42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 56 | `; 57 | } 58 | 59 | function getJSXText() { 60 | return ` 61 | {/* Solid is solid */} 62 |

SolidJSX

63 |
64 |
65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 80 | `; 81 | } 82 | -------------------------------------------------------------------------------- /src/components/SolidLogo.tsx: -------------------------------------------------------------------------------- 1 | const SolidLogo = () => { 2 | return ( 3 | 4 | 5 | 13 | 14 | 15 | 16 | 17 | 25 | 26 | 27 | 28 | 29 | 37 | 38 | 39 | 40 | 41 | 49 | 50 | 51 | 52 | 53 | 54 | 58 | 63 | 64 | 69 | 73 | 77 | 78 | ); 79 | }; 80 | 81 | export default SolidLogo; 82 | -------------------------------------------------------------------------------- /src/root.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: "Gordita"; 3 | src: url("/fonts/Gordita/Gordita-Regular.woff2") format("woff2"); 4 | font-weight: 400; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: "Gordita"; 10 | src: url("/fonts/Gordita/Gordita-Bold.woff2") format("woff2"); 11 | font-weight: 700; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: "Gordita"; 17 | src: url("/fonts/Gordita/Gordita-Medium.woff2") format("woff2"); 18 | font-weight: 500; 19 | font-style: normal; 20 | } 21 | 22 | html, 23 | body, 24 | #root, 25 | main { 26 | height: 100%; 27 | /* TODO: does overflow on body prevent native swipdown reloading */ 28 | /* overflow: clip; */ 29 | } 30 | 31 | .dark { 32 | color-scheme: dark; 33 | } 34 | 35 | .dark body { 36 | @apply bg-dark; 37 | } 38 | 39 | /* CodeMirror */ 40 | .cm-gutter { 41 | @apply bg-white dark:bg-dark; 42 | } 43 | .cm-gutters:after { 44 | @apply absolute content-empty block top-0 -right-12px bottom-0 w-12px bg-white dark:bg-dark; 45 | } 46 | .cm-editor { 47 | @apply h-full; 48 | } 49 | .cm-content { 50 | @apply text-13px md:text-14px; 51 | } 52 | .cm-activeLineGutter { 53 | @apply text-#000! dark:text-light!; 54 | } 55 | 56 | .select { 57 | border: 2px solid #000; 58 | border-radius: 6px; 59 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23adb5bd' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m2 5 6 6 6-6'/%3e%3c/svg%3e"); 60 | background-repeat: no-repeat; 61 | background-position: right 0.75rem center; 62 | background-size: 16px 12px; 63 | -webkit-appearance: none; 64 | -moz-appearance: none; 65 | appearance: none; 66 | padding: 0.375rem 2.25rem 0.375rem 0.75rem; 67 | } 68 | 69 | .switch { 70 | /* @apply bg-#888 dark:bg-#666; */ 71 | border: 1px solid #666; 72 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3.333' fill='%23666'/%3e%3c/svg%3e"); 73 | font-size: 20px; 74 | background-position: left center; 75 | border-radius: 2em; 76 | width: 2em; 77 | height: 1em; 78 | margin-top: 0.1em; 79 | vertical-align: top; 80 | background-repeat: no-repeat; 81 | -webkit-appearance: none; 82 | -moz-appearance: none; 83 | appearance: none; 84 | -webkit-print-color-adjust: exact; 85 | color-adjust: exact; 86 | print-color-adjust: exact; 87 | transition: background-position 0.15s ease-in-out; 88 | } 89 | 90 | .dark .switch { 91 | border: 1px solid #888; 92 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3.333' fill='%23888'/%3e%3c/svg%3e"); 93 | } 94 | 95 | .switch:checked { 96 | @apply bg-solid-light border-solid-light; 97 | background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3.333' fill='%23fff'/%3e%3c/svg%3e"); 98 | background-position: right center; 99 | } 100 | -------------------------------------------------------------------------------- /src/root.tsx: -------------------------------------------------------------------------------- 1 | // @refresh reload 2 | import { Suspense } from "solid-js"; 3 | import { 4 | A, 5 | Body, 6 | ErrorBoundary, 7 | FileRoutes, 8 | Head, 9 | Html, 10 | Link, 11 | Meta, 12 | Routes, 13 | Scripts, 14 | Title, 15 | } from "solid-start"; 16 | import "@unocss/reset/tailwind.css"; 17 | import "virtual:uno.css"; 18 | import "./root.css"; 19 | 20 | const ghHandle = "solidjs-community"; 21 | const ghRepoName = "html-to-solidjsx"; 22 | const url = `https://${ghHandle}.github.io/${ghRepoName}`; 23 | 24 | export default function Root() { 25 | return ( 26 | 27 | 28 | HTML To SolidJSX 29 | 30 | 31 | 32 | 33 | 34 | 38 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 56 | 61 | 67 | 73 | 77 | 78 | 79 | 80 | 81 | 86 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | ); 106 | } 107 | -------------------------------------------------------------------------------- /src/editor/plugins/highlight-style.ts: -------------------------------------------------------------------------------- 1 | import { HighlightStyle, syntaxHighlighting } from "@codemirror/language"; 2 | import { tags as t } from "@lezer/highlight"; 3 | 4 | export interface StyledHighlightOptions { 5 | base: string; 6 | delimiters: string; 7 | function?: string; 8 | comments: string; 9 | numbers: string; 10 | regexp: string; 11 | tag: string; 12 | variableName: string; 13 | variableNameSpecial?: string; 14 | keywords: string; 15 | strings: string; 16 | boolean?: string; 17 | background?: string; 18 | quote?: string; 19 | meta?: string; 20 | punctuation?: string; 21 | paren?: string; 22 | brackets?: string; 23 | moduleKeyword?: string; 24 | propertyName?: string; 25 | annotation?: string; 26 | attrName?: string; 27 | attrValue?: string; 28 | className?: string; 29 | operators?: string; 30 | self?: string; 31 | typeName?: string; 32 | atom?: string; 33 | } 34 | 35 | export function styledHighlight(h: StyledHighlightOptions) { 36 | return syntaxHighlighting( 37 | HighlightStyle.define([ 38 | // Base 39 | { 40 | tag: [t.emphasis], 41 | fontStyle: "italic", 42 | }, 43 | { 44 | tag: [t.strong], 45 | fontStyle: "bold", 46 | }, 47 | { 48 | tag: [t.link], 49 | color: h.delimiters, 50 | }, 51 | { tag: t.local(t.variableName), color: h.variableName }, 52 | { tag: t.definition(t.propertyName), color: h.propertyName }, 53 | { tag: t.special(t.variableName), color: h.variableNameSpecial }, 54 | // Keywords 55 | { 56 | tag: [t.moduleKeyword], 57 | color: h.moduleKeyword ?? h.keywords, 58 | }, 59 | { 60 | tag: [t.keyword], 61 | color: h.keywords, 62 | }, 63 | { 64 | tag: [t.typeName, t.typeOperator], 65 | color: h.typeName ?? h.keywords, 66 | }, 67 | { 68 | tag: [t.changed, t.annotation, t.modifier, t.namespace], 69 | color: h.keywords, 70 | }, 71 | // Operators 72 | { 73 | tag: [t.operator, t.special(t.string)], 74 | color: h.operators ?? h.delimiters, 75 | }, 76 | // Type 77 | { 78 | tag: [t.bool], 79 | color: h.boolean ?? h.strings, 80 | }, 81 | { 82 | tag: [t.number], 83 | color: h.numbers, 84 | }, 85 | { 86 | tag: [t.string, t.processingInstruction, t.inserted], 87 | color: h.strings, 88 | }, 89 | // Classes 90 | { 91 | tag: [t.className, t.namespace], 92 | color: h.className ?? h.function ?? h.base, 93 | }, 94 | { 95 | tag: [t.self], 96 | color: h.self ?? h.keywords, 97 | }, 98 | // Function 99 | { 100 | // Function name with @codemirror/language 0.20 101 | tag: [t.color, t.constant(t.name), t.standard(t.name)], 102 | color: h.function, 103 | }, 104 | { 105 | tag: [t.function(t.variableName), t.function(t.propertyName)], 106 | color: h.function, 107 | }, 108 | // Meta 109 | { tag: [t.annotation], color: h.annotation ?? h.keywords }, 110 | { tag: [t.punctuation], color: h.punctuation ?? h.delimiters }, 111 | { tag: [t.paren], color: h.paren ?? h.punctuation }, 112 | { 113 | tag: [t.squareBracket, t.bracket, t.angleBracket], 114 | color: h.brackets ?? h.delimiters, 115 | }, 116 | { tag: [t.meta], color: h.meta ?? h.keywords }, 117 | // Other 118 | { tag: [t.comment], color: h.comments }, 119 | { tag: [t.regexp], color: h.regexp }, 120 | { tag: [t.tagName], color: h.tag }, 121 | { 122 | tag: [t.atom], 123 | color: h.atom, 124 | }, 125 | { 126 | tag: [t.attributeValue], 127 | color: h.attrValue ?? h.strings, 128 | }, 129 | { 130 | tag: [t.attributeName], 131 | color: h.attrName ?? h.base, 132 | }, 133 | // Markdown 134 | { tag: [t.heading], color: h.keywords, fontWeight: "bold" }, 135 | { tag: [t.quote], color: h.quote }, 136 | ]) 137 | ); 138 | } 139 | -------------------------------------------------------------------------------- /src/components/Editors/HTMLEditor.tsx: -------------------------------------------------------------------------------- 1 | import { Extension } from "@codemirror/state"; 2 | import { html } from "@codemirror/lang-html"; 3 | import { EditorView, lineNumbers, highlightActiveLineGutter } from "@codemirror/view"; 4 | import { createCodeMirror, createEditorControlledValue } from "solid-codemirror"; 5 | import { createEffect, on, onMount } from "solid-js"; 6 | import { setStore, store } from "../../store"; 7 | import { vsCodeDark } from "../../editor/theme/dark"; 8 | import { isDarkTheme } from "../Header/ThemeBtn"; 9 | import { githubLight } from "../../editor/theme/light"; 10 | import { editorBaseTheme } from "../../editor/editorBaseTheme"; 11 | import { isFirefox, isMobile } from "@solid-primitives/platform"; 12 | import { useWindowSize } from "@solid-primitives/resize-observer"; 13 | import { createMediaQuery } from "@solid-primitives/media"; 14 | import TrashIcon from "../Icons/TrashIcon"; 15 | 16 | const HTMLEditor = () => { 17 | const { 18 | editorView, 19 | ref: setEditorRef, 20 | createExtension, 21 | } = createCodeMirror({ 22 | onValueChange, 23 | }); 24 | createEditorControlledValue(editorView, () => store.htmlText); 25 | const isPortrait = createMediaQuery("(orientation: portrait)"); 26 | const size = useWindowSize(); 27 | let vhHeight = 0; 28 | 29 | function onValueChange(value: string) { 30 | setStore("htmlText", value); 31 | } 32 | 33 | const onClear = () => { 34 | setStore("htmlText", ""); 35 | editorView().focus(); 36 | }; 37 | 38 | const extensions = (): Extension => { 39 | return [ 40 | editorBaseTheme(), 41 | isDarkTheme() ? vsCodeDark : githubLight, 42 | lineNumbers(), 43 | highlightActiveLineGutter(), 44 | store.lineWrap ? EditorView.lineWrapping : [], 45 | EditorView.contentAttributes.of({ 46 | "aria-label": "HTML code input textbox", 47 | }), 48 | html(), 49 | ]; 50 | }; 51 | 52 | const reconfigure = createExtension(extensions()); 53 | 54 | onMount(() => { 55 | setTimeout(() => { 56 | const { scrollDOM } = editorView(); 57 | 58 | scrollDOM.scrollTo({ top: 0 }); 59 | }); 60 | }); 61 | 62 | createEffect(on(extensions, (extensions) => reconfigure(extensions))); 63 | 64 | // prevent resizing viewport height on firefox mobile when virtual keyboard opened 65 | if (isFirefox && isMobile) { 66 | createEffect( 67 | on(isPortrait, () => { 68 | const editor = editorView(); 69 | if (editor && document.activeElement === editor.contentDOM) return; 70 | 71 | vhHeight = document.documentElement.clientHeight; 72 | }), 73 | ); 74 | 75 | createEffect( 76 | on( 77 | () => size.height, 78 | (height) => { 79 | if (height !== vhHeight) { 80 | document.documentElement.style.height = `${vhHeight}px`; 81 | } else { 82 | document.documentElement.style.height = ""; 83 | } 84 | }, 85 | { defer: true }, 86 | ), 87 | ); 88 | } 89 | 90 | return ( 91 |
92 |
93 |
94 | HTML Input 95 |
96 | 106 |
107 |
108 |
{ 111 | onMount(() => { 112 | setEditorRef(el); 113 | }); 114 | }} 115 | /> 116 |
117 |
118 | ); 119 | }; 120 | 121 | export default HTMLEditor; 122 | -------------------------------------------------------------------------------- /src/components/Header/ThemeBtn.tsx: -------------------------------------------------------------------------------- 1 | import { usePrefersDark } from "@solid-primitives/media"; 2 | import { FiMoon, FiSun } from "solid-icons/fi"; 3 | import { 4 | createComputed, 5 | createEffect, 6 | createSignal, 7 | Match, 8 | on, 9 | onMount, 10 | Switch, 11 | } from "solid-js"; 12 | import { isServer } from "solid-js/web"; 13 | import HalfSun from "../Icons/HalfSun"; 14 | 15 | const [_currentTheme, setCurrentTheme] = createSignal<"light" | "dark" | "os">( 16 | !isServer ? localStorage.theme || "os" : "os" 17 | ); 18 | 19 | export const isDarkTheme = () => { 20 | const _prefersDark = usePrefersDark(); 21 | const prefersDark = _prefersDark(); 22 | const currentTheme = _currentTheme(); 23 | 24 | if (currentTheme === "os") { 25 | // solid-codemirror probably fails to update theme because it initializes after usePrefersDark updates. 26 | // so this redundant matchMedia is to get correct dark matches value on initial load 27 | if (!isServer) { 28 | return window.matchMedia("(prefers-color-scheme: dark)").matches; 29 | } 30 | 31 | return prefersDark; 32 | } 33 | return currentTheme === "dark"; 34 | }; 35 | 36 | const ThemeBtn = () => { 37 | const [theme, setTheme] = createSignal<"light" | "dark" | "os">("os"); 38 | const prefersDark = usePrefersDark(); 39 | 40 | const onClickTheme = () => { 41 | switch (theme()) { 42 | // 1. OS (default dark) 43 | // 2. user light 44 | // 3. user dark 45 | // 4. OS (default dark) 46 | case "dark": 47 | { 48 | if (prefersDark()) { 49 | document.documentElement.classList.add("dark"); 50 | localStorage.removeItem("theme"); 51 | setTheme("os"); 52 | } else { 53 | document.documentElement.classList.remove("dark"); 54 | localStorage.theme = "light"; 55 | setTheme("light"); 56 | } 57 | } 58 | break; 59 | // 1. OS (default light) 60 | // 2. user dark 61 | // 3. user light 62 | // 4. OS (default light) 63 | case "light": 64 | { 65 | if (prefersDark()) { 66 | document.documentElement.classList.add("dark"); 67 | localStorage.theme = "dark"; 68 | setTheme("dark"); 69 | } else { 70 | localStorage.removeItem("theme"); 71 | setTheme("os"); 72 | } 73 | } 74 | break; 75 | default: { 76 | if (!prefersDark()) { 77 | document.documentElement.classList.add("dark"); 78 | localStorage.theme = "dark"; 79 | setTheme("dark"); 80 | } else { 81 | document.documentElement.classList.remove("dark"); 82 | localStorage.theme = "light"; 83 | setTheme("light"); 84 | } 85 | } 86 | } 87 | }; 88 | 89 | createComputed( 90 | on( 91 | theme, 92 | () => { 93 | setCurrentTheme(theme()); 94 | }, 95 | { defer: true } 96 | ) 97 | ); 98 | 99 | createEffect( 100 | on( 101 | prefersDark, 102 | (prefersDark) => { 103 | if (theme() !== "os") return; 104 | 105 | if (prefersDark) { 106 | document.documentElement.classList.add("dark"); 107 | } else { 108 | document.documentElement.classList.remove("dark"); 109 | } 110 | }, 111 | { defer: true } 112 | ) 113 | ); 114 | 115 | onMount(() => { 116 | let lsTheme = localStorage.theme; 117 | if (!lsTheme) lsTheme = "os"; 118 | 119 | setTheme(lsTheme); 120 | }); 121 | 122 | return ( 123 | 140 | ); 141 | }; 142 | 143 | export default ThemeBtn; 144 | -------------------------------------------------------------------------------- /src/components/ConfigPanel/Select.tsx: -------------------------------------------------------------------------------- 1 | import { autoUpdate, flip, offset, shift } from "@floating-ui/dom"; 2 | import { createResizeObserver } from "@solid-primitives/resize-observer"; 3 | import Dismiss from "solid-dismiss"; 4 | import { useFloating } from "solid-floating-ui"; 5 | import { Component, createSignal, For } from "solid-js"; 6 | import ChevronDownIcon from "../Icons/ChevronDownIcon"; 7 | 8 | const Select: Component<{ 9 | name: string; 10 | id?: string; 11 | options: string[]; 12 | selected: string; 13 | disabled?: boolean; 14 | onOpen?: (open: boolean) => void; 15 | onChange: (props: { value: string; idx: number }) => void; 16 | }> = (props) => { 17 | const [reference, setReference] = createSignal(); 18 | const [floating, setFloating] = createSignal(); 19 | const [open, setOpen] = createSignal(false); 20 | let floatingUl!: HTMLUListElement; 21 | createResizeObserver(reference as any, ({ width, height }, el) => { 22 | if (!floating()) return; 23 | if (el !== reference()) return; 24 | setFloatingUlWidthSameSizeAsButton(); 25 | }); 26 | 27 | const setFloatingUlWidthSameSizeAsButton = () => { 28 | floatingUl.style.width = `${reference()!.clientWidth}px`; 29 | }; 30 | const position = useFloating(reference, floating, { 31 | whileElementsMounted: autoUpdate, 32 | middleware: [offset(4), flip(), shift()], 33 | }); 34 | 35 | return ( 36 |
40 |
{props.name}
41 | 50 | { 55 | if (open) { 56 | setFloatingUlWidthSameSizeAsButton(); 57 | } 58 | props.onOpen && props.onOpen(open); 59 | }} 60 | focusElementOnOpen={`[data-selected="true"]`} 61 | mount="body" 62 | cursorKeys 63 | animation={{ 64 | enterClass: "opacity-0", 65 | enterToClass: "opacity-100 transition", 66 | exitClass: "opacity-100", 67 | exitToClass: "opacity-0 transition-200", 68 | }} 69 | > 70 |
79 |
    80 | 81 | {(item, idx) => ( 82 |
  • { 87 | setOpen(false); 88 | props.onChange({ value: item, idx: idx() }); 89 | }} 90 | onKeyDown={(e) => { 91 | if (e.key !== "Enter") return; 92 | setOpen(false); 93 | props.onChange({ value: item, idx: idx() }); 94 | }} 95 | > 96 | 103 | {item} 104 | 105 |
  • 106 | )} 107 |
    108 |
109 |
110 |
111 |
112 | ); 113 | }; 114 | 115 | export default Select; 116 | -------------------------------------------------------------------------------- /src/components/ActionsPanel/TogglePanelButton.tsx: -------------------------------------------------------------------------------- 1 | import { createMediaQuery } from "@solid-primitives/media"; 2 | import { createEffect, createSignal, on } from "solid-js"; 3 | import { onTransitionend } from "../../utils/onTransitionend"; 4 | import XIcon from "../Icons/XIcon"; 5 | import SettingsIcon from "../Icons/SettingsIcon"; 6 | 7 | const TogglePanelButton = () => { 8 | const vwMax850px = createMediaQuery("(max-width:850px)"); 9 | const [close, setClose] = createSignal(false); 10 | let toggleBtnXIconEl!: SVGSVGElement; 11 | let toggleBtnSettingsIconEl!: HTMLDivElement; 12 | 13 | const queryEls = () => { 14 | const actionsPanelEl = document.getElementById("actions-panel")!; 15 | const settingsPanelContainerEl = document.getElementById("settings-panel-container")!; 16 | const configContainerEl = document.getElementById("config-container")!; 17 | 18 | return { 19 | actionsPanelEl, 20 | settingsPanelContainerEl, 21 | configContainerEl, 22 | }; 23 | }; 24 | 25 | const openPanel = () => { 26 | const { settingsPanelContainerEl, configContainerEl } = queryEls(); 27 | configContainerEl.style.display = ""; 28 | settingsPanelContainerEl.style.height = ""; 29 | settingsPanelContainerEl.style.transition = "height 150ms"; 30 | toggleBtnXIconEl.style.transform = "scale(1)"; 31 | toggleBtnXIconEl.style.transition = "transform 350ms"; 32 | toggleBtnSettingsIconEl.style.transform = "scale(0)"; 33 | toggleBtnSettingsIconEl.style.transition = "transform 350ms"; 34 | 35 | onTransitionend(settingsPanelContainerEl, () => { 36 | settingsPanelContainerEl.style.minHeight = ""; 37 | settingsPanelContainerEl.style.transition = ""; 38 | }); 39 | }; 40 | 41 | const closePanel = () => { 42 | const { actionsPanelEl, settingsPanelContainerEl, configContainerEl } = queryEls(); 43 | const actionsPanelHeight = actionsPanelEl.getBoundingClientRect().height; 44 | settingsPanelContainerEl.style.height = `${actionsPanelHeight}px`; 45 | settingsPanelContainerEl.style.minHeight = "auto"; 46 | settingsPanelContainerEl.style.transition = "height 150ms"; 47 | toggleBtnXIconEl.style.transform = "scale(0)"; 48 | toggleBtnXIconEl.style.transition = "transform 350ms"; 49 | toggleBtnSettingsIconEl.style.opacity = "1"; 50 | toggleBtnSettingsIconEl.style.transform = "scale(0)"; 51 | requestAnimationFrame(() => { 52 | requestAnimationFrame(() => { 53 | toggleBtnSettingsIconEl.style.transform = "scale(1)"; 54 | toggleBtnSettingsIconEl.style.transition = "transform 350ms"; 55 | }); 56 | }); 57 | 58 | onTransitionend(settingsPanelContainerEl, () => { 59 | settingsPanelContainerEl.style.transition = ""; 60 | configContainerEl.style.display = "none"; 61 | }); 62 | }; 63 | 64 | createEffect( 65 | on( 66 | close, 67 | (close) => { 68 | if (close) { 69 | closePanel(); 70 | return; 71 | } 72 | openPanel(); 73 | }, 74 | { defer: true }, 75 | ), 76 | ); 77 | 78 | createEffect( 79 | on( 80 | vwMax850px, 81 | (vwMax850px) => { 82 | if (!close()) { 83 | return; 84 | } 85 | 86 | const run = () => { 87 | const { actionsPanelEl, settingsPanelContainerEl, configContainerEl } = queryEls(); 88 | 89 | const actionsPanelHeight = actionsPanelEl.getBoundingClientRect().height; 90 | 91 | if (vwMax850px) { 92 | settingsPanelContainerEl.style.height = `${actionsPanelHeight}px`; 93 | settingsPanelContainerEl.style.minHeight = "auto"; 94 | configContainerEl.style.display = "none"; 95 | } else { 96 | settingsPanelContainerEl.style.height = ""; 97 | settingsPanelContainerEl.style.minHeight = ""; 98 | configContainerEl.style.display = ""; 99 | } 100 | }; 101 | 102 | setTimeout(run, vwMax850px ? 100 : 0); 103 | }, 104 | { defer: true }, 105 | ), 106 | ); 107 | 108 | return ( 109 | 122 | ); 123 | }; 124 | 125 | export default TogglePanelButton; 126 | -------------------------------------------------------------------------------- /src/components/ActionsPanel/ActionsPanel.tsx: -------------------------------------------------------------------------------- 1 | import { autoUpdate, flip, offset, shift } from "@floating-ui/dom"; 2 | import Dismiss from "solid-dismiss"; 3 | import { useFloating } from "solid-floating-ui"; 4 | import { createMemo, createSignal, For, JSX, ParentComponent } from "solid-js"; 5 | import { Dynamic } from "solid-js/web"; 6 | import { setStore, store } from "../../store"; 7 | import CopyJSXButton from "../CopyJSXButton"; 8 | import SplitPanelColumns from "../Icons/SplitPanelColumns"; 9 | import SplitPanelRows from "../Icons/SplitPanelRows"; 10 | import TogglePanelButton from "./TogglePanelButton"; 11 | 12 | const ActionsPanel = () => { 13 | const list: { 14 | id: string; 15 | text: string; 16 | Icon?: () => JSX.Element; 17 | }[] = [ 18 | { 19 | id: "rows", 20 | text: "HTML and JSX rows", 21 | Icon: SplitPanelRows, 22 | }, 23 | { 24 | id: "columns", 25 | text: "HTML and JSX columns", 26 | Icon: SplitPanelColumns, 27 | }, 28 | { 29 | id: "html", 30 | text: "HTML only", 31 | Icon: () => HTML, 32 | }, 33 | { 34 | id: "jsx", 35 | text: "JSX only", 36 | Icon: () => JSX, 37 | }, 38 | ]; 39 | 40 | const [reference, setReference] = createSignal(); 41 | const [floating, setFloating] = createSignal(); 42 | const [open, setOpen] = createSignal(false); 43 | let floatingUl!: HTMLUListElement; 44 | const position = useFloating(reference, floating, { 45 | whileElementsMounted: autoUpdate, 46 | placement: "bottom-start", 47 | middleware: [offset(4), flip(), shift()], 48 | }); 49 | 50 | const selectedItemFromList = createMemo(() => { 51 | const item = list.find(({ id }) => id === store.layout); 52 | return item; 53 | }); 54 | 55 | return ( 56 |
60 | 61 |
62 | 75 | 89 |
98 |
    99 | 100 | {({ id, text, Icon }) => ( 101 |
  • { 106 | setOpen(false); 107 | setStore("layout", id as "jsx"); 108 | }} 109 | onKeyDown={(e) => { 110 | if (e.key !== "Enter") return; 111 | setOpen(false); 112 | setStore("layout", id as "jsx"); 113 | }} 114 | > 115 |
    122 |
    {Icon && }
    123 | {text} 124 |
    125 |
  • 126 | )} 127 |
    128 |
129 |
130 |
131 |
132 | 142 |
143 |
144 | 145 |
146 |
147 |
148 | ); 149 | }; 150 | 151 | const IconText: ParentComponent = (props) => { 152 | return ( 153 |
154 | 155 | {props.children} 156 | 157 |
158 | ); 159 | }; 160 | 161 | export default ActionsPanel; 162 | -------------------------------------------------------------------------------- /src/components/ConfigPanel/Input.tsx: -------------------------------------------------------------------------------- 1 | import { arrow, autoUpdate, computePosition, flip, offset } from "@floating-ui/dom"; 2 | import Dismiss from "solid-dismiss"; 3 | import { useFloating } from "solid-floating-ui"; 4 | import { Component, createSignal, Show } from "solid-js"; 5 | 6 | const Input: Component<{ 7 | name: string; 8 | value: string; 9 | maskType: "character-count"; 10 | disabled?: boolean; 11 | filter?: RegExp; 12 | placeholder?: string; 13 | tooltip?: boolean; 14 | onInput: (value: string) => void; 15 | }> = (props) => { 16 | const [reference, setReference] = createSignal(); 17 | const [floating, setFloating] = createSignal(); 18 | const [open, setOpen] = createSignal(false); 19 | // todo fix dismiss 20 | const [clickedClose, setClickedClose] = createSignal(false); 21 | const [show, setShow] = createSignal(true); 22 | let arrowEl!: HTMLDivElement; 23 | 24 | const countWhiteSpace = () => { 25 | const value = props.value; 26 | const group: { char: string; count: number }[] = []; 27 | if (value[0] != null) { 28 | group.push({ char: value[0], count: 0 }); 29 | } 30 | value.split("").forEach((item) => { 31 | const groupItem = group.at(-1)!; 32 | if (groupItem.char === item) { 33 | groupItem.count = groupItem.count + 1; 34 | } else { 35 | group.push({ char: item, count: 1 }); 36 | } 37 | }); 38 | 39 | setShow(true); 40 | 41 | return group 42 | .map(({ count, char }) => { 43 | if (char === "\t") char = "\\t"; 44 | if (char.match(/\s/)) char = "\\s"; 45 | 46 | return `${char}${count > 1 ? `(x${count})` : ""}`; 47 | }) 48 | .reduce((a, c) => a + " " + c, ""); 49 | }; 50 | 51 | const onClickReplaceTab = () => { 52 | const value = props.value; 53 | setClickedClose(true); 54 | setOpen(false); 55 | props.onInput("\t"); 56 | }; 57 | 58 | useFloating(reference, floating, { 59 | whileElementsMounted: (reference, floating) => 60 | autoUpdate(reference, floating, () => { 61 | const arrowLen = arrowEl.offsetWidth; 62 | const floatingOffset = Math.sqrt(2 * arrowLen ** 2) / 2; 63 | computePosition(reference, floating, { 64 | placement: "bottom", 65 | middleware: [offset(floatingOffset), flip(), arrow({ element: arrowEl })], 66 | }).then(({ middlewareData, placement, x, y }) => { 67 | const side = placement.split("-")[0]; 68 | 69 | Object.assign(floating.style, { 70 | left: `${x}px`, 71 | top: `${y}px`, 72 | }); 73 | 74 | const staticSide = { 75 | top: "bottom", 76 | right: "left", 77 | bottom: "top", 78 | left: "right", 79 | }[side]; 80 | 81 | if (middlewareData.arrow) { 82 | const { x, y } = middlewareData.arrow; 83 | const isTop = placement === "top"; 84 | const maskImage = `linear-gradient(${isTop ? "315" : "135"}deg, #000 calc(50% - ${ 85 | isTop ? "1" : "0.5" 86 | }px),transparent calc(50% - ${isTop ? "1" : "0.5"}px))`; 87 | 88 | Object.assign(arrowEl!.style, { 89 | left: x != null ? `${x}px` : "", 90 | top: y != null ? `${y}px` : "", 91 | // Ensure the static side gets unset when 92 | // flipping to other placements' axes. 93 | right: "", 94 | bottom: "", 95 | [staticSide!]: `${-arrowLen / 2 + 2}px`, 96 | transform: `rotate(45deg)`, 97 | WebkitMaskImage: maskImage, 98 | }); 99 | } 100 | }); 101 | }), 102 | }); 103 | 104 | return ( 105 |