Skip to content

Commit cfe0620

Browse files
authored
Add match package (#760)
2 parents 81a8348 + 075ecce commit cfe0620

File tree

8 files changed

+645
-0
lines changed

8 files changed

+645
-0
lines changed

packages/match/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2021 Solid Primitives Working Group
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.

packages/match/README.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<p>
2+
<img width="100%" src="https://assets.solidjs.com/banner?type=Primitives&background=tiles&project=match" alt="Solid Primitives match">
3+
</p>
4+
5+
# @solid-primitives/match
6+
7+
[![size](https://img.shields.io/bundlephobia/minzip/@solid-primitives/match?style=for-the-badge&label=size)](https://bundlephobia.com/package/@solid-primitives/match)
8+
[![version](https://img.shields.io/npm/v/@solid-primitives/match?style=for-the-badge)](https://www.npmjs.com/package/@solid-primitives/match)
9+
[![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)
10+
11+
Control-flow components for matching discriminated union (tagged union) members and union literals.
12+
13+
## Installation
14+
15+
```bash
16+
npm install @solid-primitives/match
17+
# or
18+
yarn add @solid-primitives/match
19+
# or
20+
pnpm add @solid-primitives/match
21+
```
22+
23+
## `MatchTag`
24+
25+
Control-flow component for matching discriminated union (tagged union) members.
26+
27+
### How to use it
28+
29+
```tsx
30+
type MyUnion = {
31+
type: "foo",
32+
foo: "foo-value",
33+
} | {
34+
type: "bar",
35+
bar: "bar-value",
36+
}
37+
38+
const [value, setValue] = createSignal<MyUnion>({type: "foo", foo: "foo-value"})
39+
40+
<MatchTag on={value()} case={{
41+
foo: v => <>{v().foo}</>,
42+
bar: v => <>{v().bar}</>,
43+
}} />
44+
```
45+
46+
### Changing the tag key
47+
48+
The default tag key is `"type"`, but it can be changed with the `tag` prop:
49+
50+
```tsx
51+
type MyUnion = {
52+
kind: "foo",
53+
foo: "foo-value",
54+
} | {
55+
kind: "bar",
56+
bar: "bar-value",
57+
}
58+
59+
<MatchTag on={value()} tag="kind" case={{
60+
foo: v => <>{v().foo}</>,
61+
bar: v => <>{v().bar}</>,
62+
}} />
63+
```
64+
65+
### Partial matching
66+
67+
Use the `partial` prop to only handle some of the union members:
68+
69+
```tsx
70+
<MatchTag partial on={value()} case={{
71+
foo: v => <>{v().foo}</>,
72+
// bar case is not handled
73+
}} />
74+
```
75+
76+
### Fallback
77+
78+
Provide a fallback element when no match is found or the value is `null`/`undefined`:
79+
80+
```tsx
81+
<MatchTag on={value()} case={{
82+
foo: v => <>{v().foo}</>,
83+
bar: v => <>{v().bar}</>,
84+
}} fallback={<div>No match found</div>} />
85+
```
86+
87+
## `MatchValue`
88+
89+
Control-flow component for matching union literals.
90+
91+
### How to use it
92+
93+
```tsx
94+
type MyUnion = "foo" | "bar";
95+
96+
const [value, setValue] = createSignal<MyUnion>("foo");
97+
98+
<MatchValue on={value()} case={{
99+
foo: () => <p>foo</p>,
100+
bar: () => <p>bar</p>,
101+
}} />
102+
```
103+
104+
### Partial matching
105+
106+
Use the `partial` prop to only handle some of the union members:
107+
108+
```tsx
109+
<MatchValue partial on={value()} case={{
110+
foo: () => <p>foo</p>,
111+
// bar case is not handled
112+
}} />
113+
```
114+
115+
### Fallback
116+
117+
Provide a fallback element when no match is found or the value is `null`/`undefined`:
118+
119+
```tsx
120+
<MatchValue on={value()} case={{
121+
foo: () => <p>foo</p>,
122+
bar: () => <p>bar</p>,
123+
}} fallback={<div>No match found</div>} />
124+
```
125+
126+
## Demo
127+
128+
[Deployed example](https://primitives.solidjs.community/playground/match) | [Source code](https://github.com/solidjs-community/solid-primitives/tree/main/packages/match/dev)
129+
130+
## Changelog
131+
132+
See [CHANGELOG.md](./CHANGELOG.md)

packages/match/dev/index.tsx

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { Component, createSignal } from "solid-js";
2+
import { MatchTag, MatchValue } from "../src/index.js";
3+
4+
type AnimalDog = {type: 'dog', breed: string};
5+
type AnimalCat = {type: 'cat', lives: number};
6+
type AnimalBird = {type: 'bird', canFly: boolean};
7+
8+
type Animal = AnimalDog | AnimalCat | AnimalBird;
9+
10+
const DogDisplay: Component<{ animal: AnimalDog }> = (props) => (
11+
<div class="text-center">
12+
<div class="text-2xl mb-2">🐕</div>
13+
<div class="text-sm">Breed: {props.animal.breed}</div>
14+
</div>
15+
);
16+
17+
const CatDisplay: Component<{ animal: AnimalCat }> = (props) => (
18+
<div class="text-center">
19+
<div class="text-2xl mb-2">🐱</div>
20+
<div class="text-sm">Lives: {props.animal.lives}</div>
21+
</div>
22+
);
23+
24+
const BirdDisplay: Component<{ animal: AnimalBird }> = (props) => (
25+
<div class="text-center">
26+
<div class="text-2xl mb-2">🐦</div>
27+
<div class="text-sm">
28+
{props.animal.canFly ? 'Can fly' : 'Cannot fly'}
29+
</div>
30+
</div>
31+
);
32+
33+
const FallbackDisplay: Component = () => (
34+
<div class="text-center">
35+
<div class="text-2xl mb-2"></div>
36+
<div>Fallback content</div>
37+
</div>
38+
);
39+
40+
const App: Component = () => {
41+
const [animal, setAnimal] = createSignal<Animal | null>(null);
42+
43+
const animals: (Animal | null)[] = [
44+
null,
45+
{ type: 'dog', breed: 'Golden Retriever' },
46+
{ type: 'cat', lives: 9 },
47+
{ type: 'bird', canFly: true },
48+
];
49+
50+
return (
51+
<div class="min-h-screen p-8">
52+
<div class="max-w-4xl mx-auto space-y-8">
53+
<div class="text-center">
54+
<h1 class="text-3xl font-bold mb-2">Match Component Demo</h1>
55+
<p>Control-flow component for matching discriminated union members</p>
56+
</div>
57+
58+
<div class="border border-gray-500 border-2 rounded-lg p-6">
59+
<label class="block text-sm font-medium mb-2">
60+
Select an animal:
61+
</label>
62+
<select
63+
class="w-full p-2 border border-gray-500 border-2 rounded-md"
64+
onChange={e => {setAnimal(animals[parseInt(e.target.value)]!)}}
65+
>
66+
<option value="0">None (null)</option>
67+
<option value="1">Dog</option>
68+
<option value="2">Cat</option>
69+
<option value="3">Bird</option>
70+
</select>
71+
</div>
72+
73+
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
74+
<div class="border border-gray-500 border-2 rounded-lg p-6">
75+
<h2 class="text-xl font-semibold mb-1">Complete Match</h2>
76+
<p class="text-sm mb-4">Handles all union members with fallback</p>
77+
<div class="border border-gray-500 border-2 rounded p-4 min-h-[100px] flex items-center justify-center">
78+
<MatchTag on={animal()} case={{
79+
dog: v => <DogDisplay animal={v()} />,
80+
cat: v => <CatDisplay animal={v()} />,
81+
bird: v => <BirdDisplay animal={v()} />,
82+
}} fallback={<FallbackDisplay />} />
83+
</div>
84+
</div>
85+
86+
<div class="border border-gray-500 border-2 rounded-lg p-6">
87+
<h2 class="text-xl font-semibold mb-1">Partial Match</h2>
88+
<p class="text-sm mb-4">Only handles dogs and cats</p>
89+
<div class="border border-gray-500 border-2 rounded p-4 min-h-[100px] flex items-center justify-center">
90+
<MatchTag partial on={animal()} case={{
91+
dog: v => <DogDisplay animal={v()} />,
92+
cat: v => <CatDisplay animal={v()} />,
93+
}} fallback={<FallbackDisplay />} />
94+
</div>
95+
</div>
96+
</div>
97+
98+
<div class="mt-8 border border-gray-500 border-2 rounded-lg p-6">
99+
<h2 class="text-xl font-semibold mb-1">Value Match</h2>
100+
<p class="text-sm mb-4">Match on union literals</p>
101+
<div class="border border-gray-500 border-2 rounded p-4 min-h-[100px] flex items-center justify-center">
102+
<MatchValue on={animal()?.type} case={{
103+
dog: () => <p>🐕</p>,
104+
cat: () => <p>🐱</p>,
105+
bird: () => <p>🐦</p>,
106+
}} fallback={<FallbackDisplay />} />
107+
</div>
108+
</div>
109+
</div>
110+
</div>
111+
);
112+
};
113+
114+
export default App;

packages/match/package.json

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
{
2+
"name": "@solid-primitives/match",
3+
"version": "0.0.100",
4+
"description": "A template primitive example.",
5+
"author": "Damian Tarnawski <gthetarnav@gmail.com>",
6+
"contributors": [],
7+
"license": "MIT",
8+
"homepage": "https://primitives.solidjs.community/package/match",
9+
"repository": {
10+
"type": "git",
11+
"url": "git+https://github.com/solidjs-community/solid-primitives.git"
12+
},
13+
"bugs": {
14+
"url": "https://github.com/solidjs-community/solid-primitives/issues"
15+
},
16+
"primitive": {
17+
"name": "match",
18+
"stage": 0,
19+
"list": [
20+
"Match"
21+
],
22+
"category": "Control Flow"
23+
},
24+
"keywords": [
25+
"solid",
26+
"primitives",
27+
"union"
28+
],
29+
"private": false,
30+
"sideEffects": false,
31+
"files": [
32+
"dist"
33+
],
34+
"type": "module",
35+
"module": "./dist/index.js",
36+
"types": "./dist/index.d.ts",
37+
"browser": {},
38+
"exports": {
39+
"@solid-primitives/source": "./src/index.ts",
40+
"import": {
41+
"types": "./dist/index.d.ts",
42+
"default": "./dist/index.js"
43+
}
44+
},
45+
"typesVersions": {},
46+
"scripts": {
47+
"dev": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/dev.ts",
48+
"build": "node --import=@nothing-but/node-resolve-ts --experimental-transform-types ../../scripts/build.ts",
49+
"vitest": "vitest -c ../../configs/vitest.config.ts",
50+
"test": "pnpm run vitest",
51+
"test:ssr": "pnpm run vitest --mode ssr"
52+
},
53+
"peerDependencies": {
54+
"solid-js": "^1.6.12"
55+
},
56+
"devDependencies": {
57+
"solid-js": "^1.9.7"
58+
}
59+
}

0 commit comments

Comments
 (0)