From 80da48ee81a412a2439b854d2db4dc372cddcd52 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 16 Feb 2025 18:31:48 +0100 Subject: [PATCH 1/4] feat: Add match component and package --- packages/match/LICENSE | 21 ++++++++++++ packages/match/README.md | 51 ++++++++++++++++++++++++++++ packages/match/dev/index.tsx | 20 +++++++++++ packages/match/package.json | 54 ++++++++++++++++++++++++++++++ packages/match/src/index.ts | 42 +++++++++++++++++++++++ packages/match/test/index.test.ts | 19 +++++++++++ packages/match/test/server.test.ts | 9 +++++ packages/match/tsconfig.json | 12 +++++++ 8 files changed, 228 insertions(+) create mode 100644 packages/match/LICENSE create mode 100644 packages/match/README.md create mode 100644 packages/match/dev/index.tsx create mode 100644 packages/match/package.json create mode 100644 packages/match/src/index.ts create mode 100644 packages/match/test/index.test.ts create mode 100644 packages/match/test/server.test.ts create mode 100644 packages/match/tsconfig.json diff --git a/packages/match/LICENSE b/packages/match/LICENSE new file mode 100644 index 000000000..38b41d975 --- /dev/null +++ b/packages/match/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Solid Primitives Working Group + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/packages/match/README.md b/packages/match/README.md new file mode 100644 index 000000000..e0f066fab --- /dev/null +++ b/packages/match/README.md @@ -0,0 +1,51 @@ +

+ Solid Primitives match +

+ +# @solid-primitives/match + +[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/match?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/match) +[![version](https://img.shields.io/npm/v/@solid-primitives/match?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/match) +[![stage](https://img.shields.io/endpoint?style=for-the-badge&url=https%3A%2F%2Fraw.githubusercontent.com%2Fsolidjs-community%2Fsolid-primitives%2Fmain%2Fassets%2Fbadges%2Fstage-0.json)](https://github.com/solidjs-community/solid-primitives#contribution-process) + +Control-flow component for matching discriminated union (tagged union) members. + +## Installation + +```bash +npm install @solid-primitives/match +# or +yarn add @solid-primitives/match +# or +pnpm add @solid-primitives/match +``` + +## `Match` + +Control-flow component for matching discriminated union (tagged union) members. + +### How to use it + +```tsx +type MyUnion = { + kind: 'foo', + foo: 'foo-value', +} | { + kind: 'bar', + bar: 'bar-value', +} + +const [value, setValue] = s.createSignal({kind: 'foo', foo: 'foo-value'}) + + <>{v().foo}, + bar: v => <>{v().bar}, +}} /> + +## Demo + +[Deployed example](https://primitives.solidjs.community/playground/match) | [Source code](https://github.com/solidjs-community/solid-primitives/tree/main/packages/match/dev) + +## Changelog + +See [CHANGELOG.md](./CHANGELOG.md) diff --git a/packages/match/dev/index.tsx b/packages/match/dev/index.tsx new file mode 100644 index 000000000..e2e5113e6 --- /dev/null +++ b/packages/match/dev/index.tsx @@ -0,0 +1,20 @@ +import { Component, createSignal } from "solid-js"; + +const App: Component = () => { + const [count, setCount] = createSignal(0); + const increment = () => setCount(count() + 1); + + return ( +
+
+

Counter component

+

it's very important...

+ +
+
+ ); +}; + +export default App; diff --git a/packages/match/package.json b/packages/match/package.json new file mode 100644 index 000000000..c87540183 --- /dev/null +++ b/packages/match/package.json @@ -0,0 +1,54 @@ +{ + "name": "@solid-primitives/match", + "version": "0.0.100", + "description": "A template primitive example.", + "author": "Damian Tarnawski ", + "contributors": [], + "license": "MIT", + "homepage": "https://primitives.solidjs.community/package/match", + "repository": { + "type": "git", + "url": "git+https://github.com/solidjs-community/solid-primitives.git" + }, + "bugs": { + "url": "https://github.com/solidjs-community/solid-primitives/issues" + }, + "primitive": { + "name": "match", + "stage": 0, + "list": [ + "createPrimitiveTemplate" + ], + "category": "Display & Media" + }, + "keywords": [ + "solid", + "primitives" + ], + "private": false, + "sideEffects": false, + "files": [ + "dist" + ], + "type": "module", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "browser": {}, + "exports": { + "@solid-primitives/source": "./src/index.ts", + "import": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + }, + "typesVersions": {}, + "scripts": { + "dev": "tsx ../../scripts/dev.ts", + "vitest": "vitest -c ../../configs/vitest.config.ts", + "test": "pnpm run vitest", + "test:ssr": "pnpm run vitest --mode ssr" + }, + "peerDependencies": { + "solid-js": "^1.6.12" + } +} diff --git a/packages/match/src/index.ts b/packages/match/src/index.ts new file mode 100644 index 000000000..54030acd3 --- /dev/null +++ b/packages/match/src/index.ts @@ -0,0 +1,42 @@ +import { type Accessor, type JSX, createMemo } from "solid-js" + +/** + * Control-flow component for matching discriminated union (tagged union) members. + * + * @example + * ```tsx + * type MyUnion = { + * kind: 'foo', + * foo: 'foo-value', + * } | { + * kind: 'bar', + * bar: 'bar-value', + * } + * + * const [value, setValue] = s.createSignal({kind: 'foo', foo: 'foo-value'}) + * + * <>{v().foo}, + * bar: v => <>{v().bar}, + * }} /> + * ``` + */ +export function Match< + T extends {[k in Tag]: PropertyKey}, + Tag extends PropertyKey, +>(props: { + on: T, + tag: Tag, + case: {[K in T[Tag]]: (v: Accessor) => JSX.Element}, +}): JSX.Element +export function Match< + T extends {kind: PropertyKey}, +>(props: { + on: T, + case: {[K in T['kind']]: (v: Accessor) => JSX.Element}, +}): JSX.Element +export function Match(props: any): any { + const kind = createMemo(() => props.on[props.tag ?? 'kind']) + return createMemo(() => props.case[kind()](() => props.on)) +} + diff --git a/packages/match/test/index.test.ts b/packages/match/test/index.test.ts new file mode 100644 index 000000000..2f1ef641e --- /dev/null +++ b/packages/match/test/index.test.ts @@ -0,0 +1,19 @@ +import { describe, test, expect } from "vitest"; +import { createRoot } from "solid-js"; +import { createPrimitiveTemplate } from "../src/index.js"; + +describe("createPrimitiveTemplate", () => { + test("createPrimitiveTemplate return values", () => { + const { value, setValue, dispose } = createRoot(dispose => { + const [value, setValue] = createPrimitiveTemplate(true); + expect(value(), "initial value should be true").toBe(true); + + return { value, setValue, dispose }; + }); + + setValue(false); + expect(value(), "value after change should be false").toBe(false); + + dispose(); + }); +}); diff --git a/packages/match/test/server.test.ts b/packages/match/test/server.test.ts new file mode 100644 index 000000000..d0ba586ee --- /dev/null +++ b/packages/match/test/server.test.ts @@ -0,0 +1,9 @@ +import { describe, test, expect } from "vitest"; +import { createPrimitiveTemplate } from "../src/index.js"; + +describe("createPrimitiveTemplate", () => { + test("doesn't break in SSR", () => { + const [value, setValue] = createPrimitiveTemplate(true); + expect(value(), "initial value should be true").toBe(true); + }); +}); diff --git a/packages/match/tsconfig.json b/packages/match/tsconfig.json new file mode 100644 index 000000000..38c71ce71 --- /dev/null +++ b/packages/match/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "src" + }, + "references": [], + "include": [ + "src" + ] +} \ No newline at end of file From 209d630faef927aa47565e04ced73715d747d831 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 16 Feb 2025 18:32:45 +0100 Subject: [PATCH 2/4] Fix readme --- packages/match/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/match/README.md b/packages/match/README.md index e0f066fab..3a113a506 100644 --- a/packages/match/README.md +++ b/packages/match/README.md @@ -41,6 +41,7 @@ const [value, setValue] = s.createSignal({kind: 'foo', foo: 'foo-value' foo: v => <>{v().foo}, bar: v => <>{v().bar}, }} /> +``` ## Demo From 96adc342e534db213c5e356aa035093baf7e48d9 Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 16 Feb 2025 18:34:53 +0100 Subject: [PATCH 3/4] Update readme --- packages/match/package.json | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/match/package.json b/packages/match/package.json index c87540183..c529a83d4 100644 --- a/packages/match/package.json +++ b/packages/match/package.json @@ -17,13 +17,14 @@ "name": "match", "stage": 0, "list": [ - "createPrimitiveTemplate" + "Match" ], - "category": "Display & Media" + "category": "Control Flow" }, "keywords": [ "solid", - "primitives" + "primitives", + "union" ], "private": false, "sideEffects": false, From 8be2d574f7c8a12caa5c8ca279c1573dd587be0f Mon Sep 17 00:00:00 2001 From: Damian Tarnawski Date: Sun, 16 Feb 2025 18:39:29 +0100 Subject: [PATCH 4/4] fix: Update README and add example for using `tag` --- packages/match/README.md | 29 ++++++++++++++++++++++++----- packages/match/src/index.ts | 2 +- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/packages/match/README.md b/packages/match/README.md index 3a113a506..9ef60d4f2 100644 --- a/packages/match/README.md +++ b/packages/match/README.md @@ -28,14 +28,14 @@ Control-flow component for matching discriminated union (tagged union) members. ```tsx type MyUnion = { - kind: 'foo', - foo: 'foo-value', + kind: "foo", + foo: "foo-value", } | { - kind: 'bar', - bar: 'bar-value', + kind: "bar", + bar: "bar-value", } -const [value, setValue] = s.createSignal({kind: 'foo', foo: 'foo-value'}) +const [value, setValue] = createSignal({kind: "foo", foo: "foo-value"}) <>{v().foo}, @@ -43,6 +43,25 @@ const [value, setValue] = s.createSignal({kind: 'foo', foo: 'foo-value' }} /> ``` +### Changing the tag key + +The default tag key is `"kind"`, but it can be changed with the `tag` prop: + +```tsx +type MyUnion = { + type: "foo", + foo: "foo-value", +} | { + type: "bar", + bar: "bar-value", +} + + <>{v().foo}, + bar: v => <>{v().bar}, +}} /> +``` + ## Demo [Deployed example](https://primitives.solidjs.community/playground/match) | [Source code](https://github.com/solidjs-community/solid-primitives/tree/main/packages/match/dev) diff --git a/packages/match/src/index.ts b/packages/match/src/index.ts index 54030acd3..ba8075428 100644 --- a/packages/match/src/index.ts +++ b/packages/match/src/index.ts @@ -13,7 +13,7 @@ import { type Accessor, type JSX, createMemo } from "solid-js" * bar: 'bar-value', * } * - * const [value, setValue] = s.createSignal({kind: 'foo', foo: 'foo-value'}) + * const [value, setValue] = createSignal({kind: 'foo', foo: 'foo-value'}) * * <>{v().foo},