Skip to content

Commit 33c1459

Browse files
committed
chore: wip navigation menu
1 parent 39d7002 commit 33c1459

26 files changed

+1956
-11
lines changed

examples/next-ts/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@zag-js/interact-outside": "workspace:*",
5151
"@zag-js/live-region": "workspace:*",
5252
"@zag-js/menu": "workspace:*",
53+
"@zag-js/navigation-menu": "workspace:*",
5354
"@zag-js/number-input": "workspace:*",
5455
"@zag-js/pagination": "workspace:*",
5556
"@zag-js/pin-input": "workspace:*",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
import * as navigationMenu from "@zag-js/navigation-menu"
2+
import { normalizeProps, useMachine } from "@zag-js/react"
3+
import { navigationMenuControls } from "@zag-js/shared"
4+
import { ChevronDown } from "lucide-react"
5+
import { useId } from "react"
6+
import { Presence } from "../components/presence"
7+
import { StateVisualizer } from "../components/state-visualizer"
8+
import { Toolbar } from "../components/toolbar"
9+
import { useControls } from "../hooks/use-controls"
10+
11+
export default function Page() {
12+
const controls = useControls(navigationMenuControls)
13+
14+
const service = useMachine(navigationMenu.machine, {
15+
id: useId(),
16+
...controls.context,
17+
})
18+
19+
const api = navigationMenu.connect(service, normalizeProps)
20+
21+
const renderLinks = (opts: { value: string; items: string[] }) => {
22+
const { value, items } = opts
23+
return items.map((item, index) => (
24+
<a href="#" key={`${value}-${item}-${index}`} {...api.getLinkProps({ value })}>
25+
{item}
26+
</a>
27+
))
28+
}
29+
30+
return (
31+
<>
32+
<main className="navigation-menu viewport">
33+
<Navbar>
34+
<div {...api.getRootProps()}>
35+
<div {...api.getIndicatorTrackProps()}>
36+
<div {...api.getListProps()}>
37+
<div {...api.getItemProps({ value: "products" })}>
38+
<button {...api.getTriggerProps({ value: "products" })}>
39+
Products
40+
<ChevronDown />
41+
</button>
42+
</div>
43+
44+
<div {...api.getItemProps({ value: "company" })}>
45+
<button {...api.getTriggerProps({ value: "company" })}>
46+
Company
47+
<ChevronDown />
48+
</button>
49+
</div>
50+
51+
<div {...api.getItemProps({ value: "developers", disabled: true })}>
52+
<button {...api.getTriggerProps({ value: "developers", disabled: true })}>
53+
Developers
54+
<ChevronDown />
55+
</button>
56+
</div>
57+
58+
<div {...api.getItemProps({ value: "pricing" })}>
59+
<a href="#" {...api.getLinkProps({ value: "pricing" })}>
60+
Pricing
61+
</a>
62+
</div>
63+
64+
<Presence {...api.getIndicatorProps()}>
65+
<div {...api.getArrowProps()} />
66+
</Presence>
67+
</div>
68+
</div>
69+
70+
<div {...api.getViewportPositionerProps()}>
71+
<Presence {...api.getViewportProps()}>
72+
<Presence
73+
{...api.getContentProps({ value: "products" })}
74+
style={{
75+
gridTemplateColumns: "1fr 2fr",
76+
width: 600,
77+
}}
78+
>
79+
{renderLinks({
80+
value: "products",
81+
items: [
82+
"Fusce pellentesque",
83+
"Aliquam porttitor",
84+
"Pellentesque",
85+
"Fusce pellentesque",
86+
"Aliquam porttitor",
87+
"Pellentesque",
88+
],
89+
})}
90+
91+
{renderLinks({
92+
value: "products",
93+
items: ["Fusce pellentesque", "Aliquam porttitor", "Pellentesque"],
94+
})}
95+
</Presence>
96+
97+
<Presence
98+
{...api.getContentProps({ value: "company" })}
99+
style={{
100+
gridTemplateColumns: "1fr 1fr",
101+
width: 450,
102+
}}
103+
>
104+
{renderLinks({
105+
value: "company",
106+
items: ["Fusce pellentesque", "Aliquam porttitor", "Pellentesque", "Aliquam porttitor"],
107+
})}
108+
109+
{renderLinks({
110+
value: "company",
111+
items: ["Fusce pellentesque", "Aliquam porttitor", "Pellentesque"],
112+
})}
113+
</Presence>
114+
115+
<Presence
116+
{...api.getContentProps({ value: "developers" })}
117+
style={{
118+
gridTemplateColumns: "1.6fr 1fr",
119+
width: 650,
120+
}}
121+
>
122+
{renderLinks({
123+
value: "developers",
124+
items: ["Donec quis dui", "Vestibulum", "Fusce pellentesque", "Aliquam porttitor"],
125+
})}
126+
{renderLinks({
127+
value: "developers",
128+
items: ["Fusce pellentesque", "Aliquam porttitor"],
129+
})}
130+
</Presence>
131+
</Presence>
132+
</div>
133+
</div>
134+
</Navbar>
135+
</main>
136+
137+
<Toolbar controls={controls.ui} viz>
138+
<StateVisualizer state={service} context={["value", "previousValue"]} />
139+
</Toolbar>
140+
</>
141+
)
142+
}
143+
144+
const Navbar = ({ children }: { children: React.ReactNode }) => {
145+
return (
146+
<div
147+
style={{
148+
position: "relative",
149+
display: "flex",
150+
boxSizing: "border-box",
151+
alignItems: "center",
152+
padding: "15px 20px",
153+
justifyContent: "space-between",
154+
width: "100%",
155+
backgroundColor: "white",
156+
boxShadow: "0 50px 100px -20px rgba(50,50,93,0.1),0 30px 60px -30px rgba(0,0,0,0.2)",
157+
}}
158+
>
159+
<button>Logo</button>
160+
{children}
161+
<button>Login</button>
162+
</div>
163+
)
164+
}
+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import * as navigationMenu from "@zag-js/navigation-menu"
2+
import { normalizeProps, useMachine } from "@zag-js/react"
3+
import { navigationMenuControls } from "@zag-js/shared"
4+
import { ChevronDown } from "lucide-react"
5+
import { useId } from "react"
6+
import { Presence } from "../components/presence"
7+
import { StateVisualizer } from "../components/state-visualizer"
8+
import { Toolbar } from "../components/toolbar"
9+
import { useControls } from "../hooks/use-controls"
10+
11+
export default function Page() {
12+
const controls = useControls(navigationMenuControls)
13+
14+
const service = useMachine(navigationMenu.machine, {
15+
id: useId(),
16+
...controls.context,
17+
})
18+
19+
const api = navigationMenu.connect(service, normalizeProps)
20+
21+
const renderLinks = (opts: { value: string; items: string[] }) => {
22+
const { value, items } = opts
23+
return items.map((item, index) => (
24+
<a href="#" key={`${value}-${item}-${index}`} {...api.getLinkProps({ value })}>
25+
{item}
26+
</a>
27+
))
28+
}
29+
30+
return (
31+
<>
32+
<main className="navigation-menu basic">
33+
<div {...api.getRootProps()}>
34+
<div {...api.getListProps()}>
35+
<div {...api.getItemProps({ value: "products" })}>
36+
<button {...api.getTriggerProps({ value: "products" })}>
37+
Products
38+
<ChevronDown />
39+
</button>
40+
<Presence {...api.getContentProps({ value: "products" })}>
41+
<Presence {...api.getIndicatorProps()}>
42+
<div {...api.getArrowProps()} />
43+
</Presence>
44+
{renderLinks({
45+
value: "products",
46+
items: [
47+
"Fusce pellentesque",
48+
"Aliquam porttitor",
49+
"Pellentesque",
50+
"Fusce pellentesque",
51+
"Aliquam porttitor",
52+
"Pellentesque",
53+
],
54+
})}
55+
</Presence>
56+
</div>
57+
58+
<div {...api.getItemProps({ value: "company" })}>
59+
<button {...api.getTriggerProps({ value: "company" })}>
60+
Company
61+
<ChevronDown />
62+
</button>
63+
<Presence {...api.getContentProps({ value: "company" })}>
64+
<Presence {...api.getIndicatorProps()}>
65+
<div {...api.getArrowProps()} />
66+
</Presence>
67+
{renderLinks({
68+
value: "company",
69+
items: ["Fusce pellentesque", "Aliquam porttitor", "Pellentesque"],
70+
})}
71+
</Presence>
72+
</div>
73+
74+
<div {...api.getItemProps({ value: "developers", disabled: true })}>
75+
<button {...api.getTriggerProps({ value: "developers", disabled: true })}>
76+
Developers
77+
<ChevronDown />
78+
</button>
79+
<Presence {...api.getContentProps({ value: "developers" })}>
80+
<Presence {...api.getIndicatorProps()}>
81+
<div {...api.getArrowProps()} />
82+
</Presence>
83+
{renderLinks({
84+
value: "developers",
85+
items: ["Donec quis dui", "Vestibulum", "Fusce pellentesque", "Aliquam porttitor"],
86+
})}
87+
</Presence>
88+
</div>
89+
90+
<div {...api.getItemProps({ value: "pricing" })}>
91+
<a href="#" {...api.getLinkProps({ value: "pricing" })}>
92+
Pricing
93+
</a>
94+
</div>
95+
</div>
96+
</div>
97+
</main>
98+
99+
<Toolbar controls={controls.ui} viz>
100+
<StateVisualizer state={service} context={["value", "previousValue"]} />
101+
</Toolbar>
102+
</>
103+
)
104+
}

examples/solid-ts/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@zag-js/interact-outside": "workspace:*",
5050
"@zag-js/live-region": "workspace:*",
5151
"@zag-js/menu": "workspace:*",
52+
"@zag-js/navigation-menu": "workspace:*",
5253
"@zag-js/number-input": "workspace:*",
5354
"@zag-js/pagination": "workspace:*",
5455
"@zag-js/pin-input": "workspace:*",

examples/svelte-ts/package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@zag-js/interact-outside": "workspace:*",
4848
"@zag-js/live-region": "workspace:*",
4949
"@zag-js/menu": "workspace:*",
50+
"@zag-js/navigation-menu": "workspace:*",
5051
"@zag-js/number-input": "workspace:*",
5152
"@zag-js/pagination": "workspace:*",
5253
"@zag-js/pin-input": "workspace:*",
@@ -98,4 +99,4 @@
9899
"vite": "6.1.1",
99100
"vite-tsconfig-paths": "5.1.4"
100101
}
101-
}
102+
}

examples/vanilla-ts/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"@zag-js/interact-outside": "workspace:*",
5151
"@zag-js/live-region": "workspace:*",
5252
"@zag-js/menu": "workspace:*",
53+
"@zag-js/navigation-menu": "workspace:*",
5354
"@zag-js/number-input": "workspace:*",
5455
"@zag-js/pagination": "workspace:*",
5556
"@zag-js/pin-input": "workspace:*",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# @zag-js/navigation-menu
2+
3+
Core logic for the navigation-menu widget implemented as a state machine
4+
5+
## Installation
6+
7+
```sh
8+
yarn add @zag-js/navigation-menu
9+
# or
10+
npm i @zag-js/navigation-menu
11+
```
12+
13+
## Contribution
14+
15+
Yes please! See the [contributing guidelines](https://github.com/chakra-ui/zag/blob/main/CONTRIBUTING.md) for details.
16+
17+
## Licence
18+
19+
This project is licensed under the terms of the [MIT license](https://github.com/chakra-ui/zag/blob/main/LICENSE).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"name": "@zag-js/navigation-menu",
3+
"version": "0.0.0",
4+
"description": "Core logic for the navigation-menu widget implemented as a state machine",
5+
"keywords": [
6+
"js",
7+
"machine",
8+
"xstate",
9+
"statechart",
10+
"component",
11+
"chakra-ui",
12+
"navigation-menu"
13+
],
14+
"private": true,
15+
"author": "Segun Adebayo <[email protected]>",
16+
"homepage": "https://github.com/chakra-ui/zag#readme",
17+
"license": "MIT",
18+
"main": "src/index.ts",
19+
"repository": "https://github.com/chakra-ui/zag/tree/main/packages/navigation-menu",
20+
"sideEffects": false,
21+
"files": [
22+
"dist",
23+
"src"
24+
],
25+
"scripts": {
26+
"build": "tsup",
27+
"lint": "eslint src",
28+
"typecheck": "tsc --noEmit",
29+
"prepack": "clean-package",
30+
"postpack": "clean-package restore"
31+
},
32+
"publishConfig": {
33+
"access": "public"
34+
},
35+
"bugs": {
36+
"url": "https://github.com/chakra-ui/zag/issues"
37+
},
38+
"dependencies": {
39+
"@zag-js/anatomy": "workspace:*",
40+
"@zag-js/interact-outside": "workspace:*",
41+
"@zag-js/core": "workspace:*",
42+
"@zag-js/dom-query": "workspace:*",
43+
"@zag-js/utils": "workspace:*",
44+
"@zag-js/types": "workspace:*"
45+
},
46+
"devDependencies": {
47+
"clean-package": "2.2.0"
48+
},
49+
"clean-package": "../../../clean-package.config.json"
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
export { anatomy } from "./navigation-menu.anatomy"
2+
export { connect } from "./navigation-menu.connect"
3+
export { machine } from "./navigation-menu.machine"
4+
export * from "./navigation-menu.props"
5+
export type {
6+
NavigationMenuApi as Api,
7+
ArrowProps,
8+
ElementIds,
9+
ItemProps,
10+
LinkProps,
11+
NavigationMenuMachine as Machine,
12+
NavigationMenuProps as Props,
13+
NavigationMenuService as Service,
14+
ValueChangeDetails,
15+
} from "./navigation-menu.types"

0 commit comments

Comments
 (0)