Skip to content

Commit e269068

Browse files
committed
chore: add example components for Hono JSX
1 parent 0eaedfb commit e269068

File tree

12 files changed

+701
-6
lines changed

12 files changed

+701
-6
lines changed
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as accordion from "@zag-js/accordion"
2+
import { normalizeProps, useMachine } from "@zag-js/hono-jsx"
3+
import { useId } from "hono/jsx"
4+
5+
const items = [
6+
{
7+
title: "Watercraft",
8+
desc: "Yacht, Boats and Dinghies",
9+
content: "Sample accordion content",
10+
},
11+
{
12+
title: "Automobiles",
13+
desc: "Cars, Trucks and Vans",
14+
content: "Sample accordion content",
15+
},
16+
{
17+
title: "Aircraft",
18+
desc: "Airplanes, Helicopters and Rockets",
19+
content: "Sample accordion content",
20+
},
21+
]
22+
23+
type AccordionProps = Omit<accordion.Props, "id">
24+
25+
export default function Accordion(props: AccordionProps) {
26+
const service = useMachine(accordion.machine, {
27+
id: useId(),
28+
defaultValue: ["Aircraft"],
29+
...props,
30+
})
31+
32+
const api = accordion.connect(service, normalizeProps)
33+
34+
return (
35+
<div {...api.getRootProps()}>
36+
{items.map((item) => (
37+
<div key={item.title} {...api.getItemProps({ value: item.title })}>
38+
<h3>
39+
<button {...api.getItemTriggerProps({ value: item.title })}>
40+
<div>{item.title}</div>
41+
<div>{item.desc}</div>
42+
</button>
43+
</h3>
44+
<div {...api.getItemContentProps({ value: item.title })}>{item.content}</div>
45+
</div>
46+
))}
47+
</div>
48+
)
49+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createRoute } from "honox/factory"
2+
import Accordion from "./$Accordion"
3+
4+
export default createRoute((c) => {
5+
return c.render(
6+
<div class="py-8 text-center">
7+
<title>Accordion</title>
8+
<h1 class="text-3xl font-bold">Accordion</h1>
9+
<Accordion collapsible={true} multiple={false} />
10+
</div>,
11+
)
12+
})
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import * as avatar from "@zag-js/avatar"
2+
import { normalizeProps, useMachine } from "@zag-js/hono-jsx"
3+
import { useId } from "hono/jsx"
4+
5+
interface AvatarProps extends Omit<avatar.Props, "id"> {
6+
src: string
7+
name: string
8+
}
9+
10+
export default function Avatar(props: AvatarProps) {
11+
const [avatarProps, restProps] = avatar.splitProps(props)
12+
const { src, name } = restProps
13+
14+
const service = useMachine(avatar.machine, {
15+
id: useId(),
16+
...avatarProps,
17+
})
18+
19+
const api = avatar.connect(service, normalizeProps)
20+
21+
const initial = name
22+
.split(" ")
23+
.map((s) => s[0])
24+
.join("")
25+
26+
return (
27+
<>
28+
<main className="avatar">
29+
<div {...api.getRootProps()}>
30+
<div {...api.getFallbackProps()}>
31+
<div>{initial}</div>
32+
</div>
33+
<img alt={name} referrerPolicy="no-referrer" src={src} {...api.getImageProps()} />
34+
</div>
35+
</main>
36+
</>
37+
)
38+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { createRoute } from "honox/factory"
2+
import Avatar from "./$Avatar"
3+
4+
export default createRoute((c) => {
5+
return c.render(
6+
<div class="py-8 text-center">
7+
<title>Avatar</title>
8+
<h1 class="text-3xl font-bold">Avatar</h1>
9+
<Avatar
10+
name={"Segun Adebayo"}
11+
src={
12+
"https://static.wikia.nocookie.net/naruto/images/d/d6/Naruto_Part_I.png/revision/latest/scale-to-width-down/300?cb=20210223094656"
13+
}
14+
/>
15+
</div>,
16+
)
17+
})
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import * as checkbox from "@zag-js/checkbox"
2+
import { normalizeProps, useMachine } from "@zag-js/hono-jsx"
3+
import { useId } from "hono/jsx"
4+
5+
interface CheckboxProps extends Omit<checkbox.Props, "id"> {}
6+
7+
export default function Checkbox(props: CheckboxProps) {
8+
const service = useMachine(checkbox.machine, {
9+
id: useId(),
10+
...props,
11+
})
12+
13+
const api = checkbox.connect(service, normalizeProps)
14+
15+
return (
16+
<div>
17+
<label {...api.getRootProps()}>
18+
<span {...api.getLabelProps()}>Checkbox Input</span>
19+
<input data-peer {...api.getHiddenInputProps()} />
20+
<div {...api.getControlProps()}>
21+
{api.checkedState === true && (
22+
<svg viewBox="0 0 24 24" fill="currentColor" transform="scale(0.7)">
23+
<path d="M20.285 2l-11.285 11.567-5.286-5.011-3.714 3.716 9 8.728 15-15.285z" />
24+
</svg>
25+
)}
26+
{api.checkedState === "indeterminate" && (
27+
<svg viewBox="0 0 24 24" stroke="currentColor" strokeWidth="4">
28+
<line x1="21" x2="3" y1="12" y2="12" />
29+
</svg>
30+
)}
31+
</div>
32+
</label>
33+
</div>
34+
)
35+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { createRoute } from "honox/factory"
2+
import Checkbox from "./$Checkbox"
3+
4+
export default createRoute((c) => {
5+
return c.render(
6+
<div class="py-8 text-center">
7+
<title>Checkbox</title>
8+
<h1 class="text-3xl font-bold">Checkbox</h1>
9+
<Checkbox disabled={false} invalid={false} />
10+
</div>,
11+
)
12+
})
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import * as combobox from "@zag-js/combobox"
2+
import { Portal, mergeProps, normalizeProps, useMachine } from "@zag-js/hono-jsx"
3+
import { useId, useMemo, useState } from "hono/jsx"
4+
import { createFilter } from "@zag-js/i18n-utils"
5+
6+
interface ComboboxProps extends Omit<combobox.Props, "id" | "collection"> {}
7+
8+
export default function Combobox(props: ComboboxProps) {
9+
const [options, setOptions] = useState(comboboxData)
10+
11+
const filter = createFilter({ sensitivity: "base" })
12+
13+
const collection = useMemo(
14+
() =>
15+
combobox.collection({
16+
items: options,
17+
itemToValue: (item) => item.code,
18+
itemToString: (item) => item.label,
19+
}),
20+
[options],
21+
)
22+
23+
const service = useMachine(combobox.machine, {
24+
id: useId(),
25+
collection,
26+
onInputValueChange({ inputValue }) {
27+
const filtered = comboboxData.filter((item) => filter.contains(item.label, inputValue))
28+
setOptions(filtered.length > 0 ? filtered : comboboxData)
29+
},
30+
placeholder: "Type or select country",
31+
...props,
32+
})
33+
34+
const api = combobox.connect(service, normalizeProps)
35+
36+
const triggerProps = mergeProps(api.getTriggerProps(), {
37+
onClick() {
38+
setOptions(comboboxData)
39+
},
40+
})
41+
42+
return (
43+
<div>
44+
<div {...api.getRootProps()}>
45+
<label {...api.getLabelProps()}>Nationality</label>
46+
<div {...api.getControlProps()}>
47+
<input {...api.getInputProps()} />
48+
<button {...triggerProps}>
49+
<CaretIcon />
50+
</button>
51+
</div>
52+
</div>
53+
<Portal>
54+
<div {...api.getPositionerProps()}>
55+
{options.length > 0 && (
56+
<ul {...api.getContentProps()}>
57+
{options.map((item, index) => (
58+
<li key={`${item.code}:${index}`} {...api.getItemProps({ item })}>
59+
{item.label}
60+
</li>
61+
))}
62+
</ul>
63+
)}
64+
</div>
65+
</Portal>
66+
</div>
67+
)
68+
}
69+
70+
const comboboxData = [
71+
{ label: "Zambia", code: "ZA" },
72+
{ label: "Benin", code: "BN" },
73+
{ label: "Canada", code: "CA" },
74+
{ label: "United States", code: "US" },
75+
{ label: "Japan", code: "JP" },
76+
{ label: "Nigeria", code: "NG" },
77+
{ label: "Albania", code: "AL" },
78+
{ label: "Algeria", code: "DZ" },
79+
{ label: "American Samoa", code: "AS" },
80+
{ label: "Andorra", code: "AD" },
81+
{ label: "Angola", code: "AO" },
82+
{ label: "Anguilla", code: "AI" },
83+
{ label: "Antarctica", code: "AQ" },
84+
{ label: "Australia", code: "AU" },
85+
{ label: "Austria", code: "AT" },
86+
{ label: "Azerbaijan", code: "AZ" },
87+
{ label: "Bahamas", code: "BS" },
88+
{ label: "Bahrain", code: "BH" },
89+
{ label: "Madagascar", code: "MG" },
90+
{ label: "Malawi", code: "MW" },
91+
{ label: "Malaysia", code: "MY" },
92+
{ label: "Maldives", code: "MV" },
93+
{ label: "Mali", code: "ML" },
94+
{ label: "Malta", code: "MT" },
95+
{ label: "Togo", code: "TG" },
96+
{ label: "Tokelau", code: "TK" },
97+
{ label: "Tonga", code: "TO" },
98+
{ label: "Trinidad and Tobago", code: "TT" },
99+
{ label: "Tunisia", code: "TN" },
100+
]
101+
102+
const CaretIcon = () => (
103+
<svg
104+
stroke="currentColor"
105+
fill="currentColor"
106+
strokeWidth="0"
107+
viewBox="0 0 1024 1024"
108+
height="1em"
109+
width="1em"
110+
xmlns="http://www.w3.org/2000/svg"
111+
>
112+
<path d="M840.4 300H183.6c-19.7 0-30.7 20.8-18.5 35l328.4 380.8c9.4 10.9 27.5 10.9 37 0L858.9 335c12.2-14.2 1.2-35-18.5-35z"></path>
113+
</svg>
114+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { createRoute } from "honox/factory"
2+
import Combobox from "./$Combobox"
3+
4+
export default createRoute((c) => {
5+
return c.render(
6+
<div class="py-8 text-center">
7+
<title>Combobox</title>
8+
<h1 class="text-3xl font-bold">Combobox</h1>
9+
<Combobox
10+
disabled={false}
11+
readOnly={false}
12+
loopFocus={false}
13+
inputBehavior={"autohighlight"}
14+
selectionBehavior={"replace"}
15+
/>
16+
</div>,
17+
)
18+
})

examples/hono-jsx-ts/app/routes/index.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,41 @@ import { createRoute } from "honox/factory"
22
import Counter from "../islands/counter"
33

44
export default createRoute((c) => {
5-
const name = c.req.query("name") ?? "Hono"
65
return c.render(
7-
<div class="py-8 text-center">
8-
<title>{name}</title>
9-
<h1 class="text-3xl font-bold">Hello, {name}!</h1>
10-
<Counter />
6+
<div>
7+
<div class="py-8 text-center">
8+
<title>Hono + Zag</title>
9+
<h1 class="text-3xl font-bold">Hello, Hono!</h1>
10+
<Counter />
11+
</div>
12+
<div class="py-8 text-center">
13+
<h2 class="text-2xl font-bold">Examples</h2>
14+
<p>
15+
<a href="/accordion" class="text-blue-500 hover:underline">
16+
Accordion
17+
</a>
18+
</p>
19+
<p>
20+
<a href="/avatar" class="text-blue-500 hover:underline">
21+
Avatar
22+
</a>
23+
</p>
24+
<p>
25+
<a href="/checkbox" class="text-blue-500 hover:underline">
26+
Checkbox
27+
</a>
28+
</p>
29+
<p>
30+
<a href="/combobox" class="text-blue-500 hover:underline">
31+
Combobox
32+
</a>
33+
</p>
34+
<p>
35+
<a href="/popover" class="text-blue-500 hover:underline">
36+
Popover
37+
</a>
38+
</p>
39+
</div>
1140
</div>,
1241
)
1342
})
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import * as popover from "@zag-js/popover"
2+
import { normalizeProps, useMachine, Portal } from "@zag-js/hono-jsx"
3+
import { useId, Fragment } from "hono/jsx"
4+
5+
interface PopoverProps extends Omit<popover.Props, "id"> {}
6+
7+
export default function Popover(props: PopoverProps) {
8+
const service = useMachine(popover.machine, {
9+
id: useId(),
10+
...props,
11+
})
12+
13+
const api = popover.connect(service, normalizeProps)
14+
15+
const Wrapper = api.portalled ? Portal : Fragment
16+
17+
return (
18+
<div>
19+
<button {...api.getTriggerProps()}>Click me</button>
20+
<Wrapper>
21+
<div {...api.getPositionerProps()}>
22+
<div {...api.getContentProps()}>
23+
<div {...api.getArrowProps()}>
24+
<div {...api.getArrowTipProps()} />
25+
</div>
26+
27+
<div>
28+
<div {...api.getTitleProps()}>
29+
<b>About Tabs</b>
30+
</div>
31+
<div {...api.getDescriptionProps()}>
32+
Tabs are used to organize and group content into sections that the user can navigate between.
33+
</div>
34+
<button>Action Button</button>
35+
</div>
36+
37+
<button {...api.getCloseTriggerProps()}>
38+
<svg viewBox="0 0 24 24" fill="currentColor" width="24" height="24">
39+
<line x1="18" y1="6" x2="6" y2="18" stroke="currentColor" strokeWidth="2" />
40+
<line x1="6" y1="6" x2="18" y2="18" stroke="currentColor" strokeWidth="2" />
41+
</svg>
42+
</button>
43+
</div>
44+
</div>
45+
</Wrapper>
46+
</div>
47+
)
48+
}

0 commit comments

Comments
 (0)