From 1c8a1d60c1e33c9421d28800e594acfb80522735 Mon Sep 17 00:00:00 2001 From: Alexis Munsayac Date: Sun, 26 Sep 2021 15:37:14 +0800 Subject: [PATCH] Add `TailwindCheckbox` --- examples/checkbox-example/.eslintrc.js | 12 + examples/checkbox-example/.gitignore | 5 + examples/checkbox-example/favicon.svg | 15 ++ examples/checkbox-example/index.html | 13 ++ examples/checkbox-example/package.json | 28 +++ examples/checkbox-example/postcss.config.js | 6 + examples/checkbox-example/src/App.tsx | 113 ++++++++++ examples/checkbox-example/src/main.tsx | 20 ++ examples/checkbox-example/src/style.css | 3 + examples/checkbox-example/src/vite-env.d.ts | 1 + examples/checkbox-example/tailwind.config.js | 19 ++ .../checkbox-example/tsconfig.eslint.json | 19 ++ examples/checkbox-example/tsconfig.json | 19 ++ examples/checkbox-example/vite.config.js | 6 + packages/solid-headless/src/index.ts | 10 + .../solid-headless/src/tailwind/Checkbox.tsx | 205 ++++++++++++++++++ 16 files changed, 494 insertions(+) create mode 100644 examples/checkbox-example/.eslintrc.js create mode 100644 examples/checkbox-example/.gitignore create mode 100644 examples/checkbox-example/favicon.svg create mode 100644 examples/checkbox-example/index.html create mode 100644 examples/checkbox-example/package.json create mode 100644 examples/checkbox-example/postcss.config.js create mode 100644 examples/checkbox-example/src/App.tsx create mode 100644 examples/checkbox-example/src/main.tsx create mode 100644 examples/checkbox-example/src/style.css create mode 100644 examples/checkbox-example/src/vite-env.d.ts create mode 100644 examples/checkbox-example/tailwind.config.js create mode 100644 examples/checkbox-example/tsconfig.eslint.json create mode 100644 examples/checkbox-example/tsconfig.json create mode 100644 examples/checkbox-example/vite.config.js create mode 100644 packages/solid-headless/src/tailwind/Checkbox.tsx diff --git a/examples/checkbox-example/.eslintrc.js b/examples/checkbox-example/.eslintrc.js new file mode 100644 index 00000000..8430608b --- /dev/null +++ b/examples/checkbox-example/.eslintrc.js @@ -0,0 +1,12 @@ +module.exports = { + "root": true, + "extends": [ + "lxsmnsyc/typescript/react" + ], + "parserOptions": { + "project": "./tsconfig.eslint.json", + "tsconfigRootDir": __dirname, + }, + "rules": { + } +}; diff --git a/examples/checkbox-example/.gitignore b/examples/checkbox-example/.gitignore new file mode 100644 index 00000000..53f7466a --- /dev/null +++ b/examples/checkbox-example/.gitignore @@ -0,0 +1,5 @@ +node_modules +.DS_Store +dist +dist-ssr +*.local \ No newline at end of file diff --git a/examples/checkbox-example/favicon.svg b/examples/checkbox-example/favicon.svg new file mode 100644 index 00000000..de4aeddc --- /dev/null +++ b/examples/checkbox-example/favicon.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/examples/checkbox-example/index.html b/examples/checkbox-example/index.html new file mode 100644 index 00000000..78ed111d --- /dev/null +++ b/examples/checkbox-example/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite App + + +
+ + + diff --git a/examples/checkbox-example/package.json b/examples/checkbox-example/package.json new file mode 100644 index 00000000..cc75b585 --- /dev/null +++ b/examples/checkbox-example/package.json @@ -0,0 +1,28 @@ +{ + "name": "checkbox-example", + "version": "0.6.4", + "scripts": { + "dev": "vite", + "build": "vite build", + "serve": "vite preview" + }, + "devDependencies": { + "autoprefixer": "^10.2.6", + "babel-preset-solid": "^1.1.1", + "eslint": "^7.32.0", + "eslint-config-lxsmnsyc": "^0.2.3", + "postcss": "^8.3.5", + "typescript": "^4.3.2", + "vite": "^2.4.4", + "vite-plugin-solid": "^2.0.1" + }, + "dependencies": { + "solid-headless": "0.6.4", + "solid-js": "^1.1.0", + "tailwindcss": "^2.2.4" + }, + "private": true, + "publishConfig": { + "access": "restricted" + } +} diff --git a/examples/checkbox-example/postcss.config.js b/examples/checkbox-example/postcss.config.js new file mode 100644 index 00000000..3a75af87 --- /dev/null +++ b/examples/checkbox-example/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + } +}; \ No newline at end of file diff --git a/examples/checkbox-example/src/App.tsx b/examples/checkbox-example/src/App.tsx new file mode 100644 index 00000000..29f0f3fd --- /dev/null +++ b/examples/checkbox-example/src/App.tsx @@ -0,0 +1,113 @@ +import { + TailwindDialog, + TailwindDialogPanel, + TailwindDialogTitle, + TailwindTransition, + TailwindTransitionChild, + TailwindDialogOverlay, + TailwindCheckbox, + TailwindCheckboxIndicator, + TailwindCheckboxLabel, + TailwindCheckboxDescription, +} from 'solid-headless'; +import { createSignal, JSX, Switch, Match } from 'solid-js'; + +function CheckIcon(props: JSX.IntrinsicElements['svg']): JSX.Element { + return ( + + + + ); +} + +function CloseIcon(props: JSX.IntrinsicElements['svg']): JSX.Element { + return ( + + + + ); +} + +function UndefinedIcon(props: JSX.IntrinsicElements['svg']): JSX.Element { + return ( + + + + ); +} + +export default function App(): JSX.Element { + const [checked, setChecked] = createSignal(); + + return ( + <> +
+ + + + + Mixed + + + + Checked + + + + Unchecked + + + + +
+ + This is a checkbox label + + + This is a checkbox description + +
+
+
+ + ); +} diff --git a/examples/checkbox-example/src/main.tsx b/examples/checkbox-example/src/main.tsx new file mode 100644 index 00000000..933e785e --- /dev/null +++ b/examples/checkbox-example/src/main.tsx @@ -0,0 +1,20 @@ +import { render } from 'solid-js/web'; +import App from './App'; + +import './style.css'; + +function Root() { + return ( +
+
+ +
+
+ ); +} + +const app = document.getElementById('app'); + +if (app) { + render(() => , app); +} diff --git a/examples/checkbox-example/src/style.css b/examples/checkbox-example/src/style.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/examples/checkbox-example/src/style.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/examples/checkbox-example/src/vite-env.d.ts b/examples/checkbox-example/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/examples/checkbox-example/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/examples/checkbox-example/tailwind.config.js b/examples/checkbox-example/tailwind.config.js new file mode 100644 index 00000000..4ff1450e --- /dev/null +++ b/examples/checkbox-example/tailwind.config.js @@ -0,0 +1,19 @@ +const colors = require('tailwindcss/colors'); + +module.exports = { + mode: 'jit', + purge: [ + './src/**/*.tsx', + ], + darkMode: 'class', // or 'media' or 'class' + theme: { + extend: { + colors: { + ...colors, + }, + }, + }, + variants: {}, + plugins: [ + ], +}; \ No newline at end of file diff --git a/examples/checkbox-example/tsconfig.eslint.json b/examples/checkbox-example/tsconfig.eslint.json new file mode 100644 index 00000000..7b3b25a2 --- /dev/null +++ b/examples/checkbox-example/tsconfig.eslint.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + }, + "include": ["./src"] +} diff --git a/examples/checkbox-example/tsconfig.json b/examples/checkbox-example/tsconfig.json new file mode 100644 index 00000000..7b3b25a2 --- /dev/null +++ b/examples/checkbox-example/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ESNext", + "module": "ESNext", + "lib": ["ESNext", "DOM"], + "moduleResolution": "Node", + "strict": true, + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "noEmit": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "jsx": "preserve", + "jsxImportSource": "solid-js", + }, + "include": ["./src"] +} diff --git a/examples/checkbox-example/vite.config.js b/examples/checkbox-example/vite.config.js new file mode 100644 index 00000000..ea2923ba --- /dev/null +++ b/examples/checkbox-example/vite.config.js @@ -0,0 +1,6 @@ +import { defineConfig } from 'vite'; +import solidPlugin from 'vite-plugin-solid'; + +export default defineConfig({ + plugins: [solidPlugin()], +}); diff --git a/packages/solid-headless/src/index.ts b/packages/solid-headless/src/index.ts index a0db8f48..4ade7857 100644 --- a/packages/solid-headless/src/index.ts +++ b/packages/solid-headless/src/index.ts @@ -162,3 +162,13 @@ export { ToasterListener, useToaster, } from './tailwind/Toast'; +export { + TailwindCheckbox, + TailwindCheckboxDescription, + TailwindCheckboxDescriptionProps, + TailwindCheckboxIndicator, + TailwindCheckboxIndicatorProps, + TailwindCheckboxLabel, + TailwindCheckboxLabelProps, + TailwindCheckboxProps, +} from './tailwind/Checkbox'; diff --git a/packages/solid-headless/src/tailwind/Checkbox.tsx b/packages/solid-headless/src/tailwind/Checkbox.tsx new file mode 100644 index 00000000..caf3d53f --- /dev/null +++ b/packages/solid-headless/src/tailwind/Checkbox.tsx @@ -0,0 +1,205 @@ +import { JSX } from 'solid-js/jsx-runtime'; +import { + createContext, + createEffect, + createSignal, + createUniqueId, + onCleanup, + useContext, +} from 'solid-js'; +import { + Dynamic, +} from 'solid-js/web'; +import { + HeadlessToggleChild, + HeadlessToggleChildProps, + HeadlessToggleRoot, + HeadlessToggleRootProps, + useHeadlessToggleChild, +} from '../headless/Toggle'; +import { DynamicProps, ValidConstructor } from '../utils/dynamic-prop'; +import { excludeProps } from '../utils/exclude-props'; +import Fragment from '../utils/Fragment'; + +interface TailwindCheckboxContext { + ownerID: string; + labelID: string; + indicatorID: string; + descriptionID: string; +} + +const TailwindCheckboxContext = createContext(); + +function useTailwindCheckboxContext(componentName: string): TailwindCheckboxContext { + const context = useContext(TailwindCheckboxContext); + + if (context) { + return context; + } + throw new Error(`<${componentName}> must be used inside a `); +} + +export type TailwindCheckboxProps = { + as?: T; +} & HeadlessToggleRootProps + & Omit, keyof HeadlessToggleRootProps>; + +export function TailwindCheckbox( + props: TailwindCheckboxProps, +): JSX.Element { + const ownerID = createUniqueId(); + const labelID = createUniqueId(); + const indicatorID = createUniqueId(); + const descriptionID = createUniqueId(); + + return ( + + + + {props.children} + + + + ); +} + +export type TailwindCheckboxIndicatorProps = { + as?: T; +} & HeadlessToggleChildProps + & Omit, keyof HeadlessToggleChildProps>; + +export function TailwindCheckboxIndicator( + props: TailwindCheckboxIndicatorProps, +): JSX.Element { + const context = useTailwindCheckboxContext('TailwindCheckboxIndicator'); + const state = useHeadlessToggleChild(); + + const [internalRef, setInternalRef] = createSignal(); + + createEffect(() => { + const ref = internalRef(); + + if (ref) { + const toggle = () => { + state.setState(!state.checked()); + }; + + const onKeyDown = (e: KeyboardEvent) => { + if (e.key === ' ') { + toggle(); + } + }; + + ref.addEventListener('click', toggle); + ref.addEventListener('keydown', onKeyDown); + onCleanup(() => { + ref.removeEventListener('click', toggle); + ref.removeEventListener('keydown', onKeyDown); + }); + } + }); + + return ( + { + const outerRef = props.ref; + if (typeof outerRef === 'function') { + outerRef(e); + } else { + props.ref = e; + } + setInternalRef(e); + }} + > + + {props.children} + + + ); +} + +export type TailwindCheckboxLabelProps = { + as?: T; +} & HeadlessToggleChildProps + & Omit, keyof HeadlessToggleChildProps>; + +export function TailwindCheckboxLabel( + props: TailwindCheckboxLabelProps, +): JSX.Element { + const context = useTailwindCheckboxContext('TailwindCheckboxLabel'); + return ( + + {props.children} + + ); +} + +export type TailwindCheckboxDescriptionProps = { + as?: T; +} & HeadlessToggleChildProps + & Omit, keyof HeadlessToggleChildProps>; + +export function TailwindCheckboxDescription( + props: TailwindCheckboxDescriptionProps, +): JSX.Element { + const context = useTailwindCheckboxContext('TailwindCheckboxDescription'); + + return ( + + + {props.children} + + + ); +}