Skip to content

Commit 56728d7

Browse files
committed
feat: marquee
1 parent 81337e5 commit 56728d7

File tree

40 files changed

+2082
-14
lines changed

40 files changed

+2082
-14
lines changed

.changeset/fine-oranges-smash.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@zag-js/marquee": patch
3+
---
4+
5+
Initial release of marquee component for continuously scrolling content

examples/next-ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@zag-js/json-tree-utils": "workspace:*",
5454
"@zag-js/listbox": "workspace:*",
5555
"@zag-js/live-region": "workspace:*",
56+
"@zag-js/marquee": "workspace:*",
5657
"@zag-js/menu": "workspace:*",
5758
"@zag-js/navigation-menu": "workspace:*",
5859
"@zag-js/number-input": "workspace:*",
@@ -115,4 +116,4 @@
115116
"typescript": "5.9.3"
116117
},
117118
"license": "MIT"
118-
}
119+
}

examples/next-ts/pages/marquee.tsx

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import * as marquee from "@zag-js/marquee"
2+
import { normalizeProps, useMachine } from "@zag-js/react"
3+
import { marqueeControls, marqueeData } from "@zag-js/shared"
4+
import { useId } from "react"
5+
import { StateVisualizer } from "../components/state-visualizer"
6+
import { Toolbar } from "../components/toolbar"
7+
import { useControls } from "../hooks/use-controls"
8+
9+
export default function Page() {
10+
const controls = useControls(marqueeControls)
11+
12+
const service = useMachine(marquee.machine, {
13+
id: useId(),
14+
spacing: "2rem",
15+
...controls.context,
16+
})
17+
18+
const api = marquee.connect(service, normalizeProps)
19+
20+
return (
21+
<>
22+
<main className="marquee">
23+
<div {...api.getRootProps()}>
24+
<div {...api.getEdgeProps({ side: "start" })} />
25+
26+
<div {...api.getViewportProps()}>
27+
{Array.from({ length: api.contentCount }).map((_, index) => (
28+
<div key={index} {...api.getContentProps({ index })}>
29+
{marqueeData.map((item, i) => (
30+
<div key={i} className="marquee-item">
31+
<span className="marquee-logo">{item.logo}</span>
32+
<span className="marquee-name">{item.name}</span>
33+
</div>
34+
))}
35+
</div>
36+
))}
37+
</div>
38+
39+
<div {...api.getEdgeProps({ side: "end" })} />
40+
</div>
41+
42+
<div className="controls">
43+
<button onClick={() => api.pause()}>Pause</button>
44+
<button onClick={() => api.resume()}>Resume</button>
45+
<button onClick={() => api.togglePause()}>Toggle</button>
46+
<span>Status: {api.paused ? "Paused" : "Playing"}</span>
47+
</div>
48+
</main>
49+
50+
<Toolbar controls={controls.ui}>
51+
<StateVisualizer state={service} />
52+
</Toolbar>
53+
</>
54+
)
55+
}

examples/nuxt-ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
"@zag-js/json-tree-utils": "workspace:*",
5050
"@zag-js/listbox": "workspace:*",
5151
"@zag-js/live-region": "workspace:*",
52+
"@zag-js/marquee": "workspace:*",
5253
"@zag-js/menu": "workspace:*",
5354
"@zag-js/navigation-menu": "workspace:*",
5455
"@zag-js/number-input": "workspace:*",
@@ -100,4 +101,4 @@
100101
"@types/node": "24.7.0",
101102
"nuxt": "4.1.3"
102103
}
103-
}
104+
}

examples/nuxt-ts/pages/marquee.vue

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<script setup lang="ts">
2+
import * as marquee from "@zag-js/marquee"
3+
import { marqueeControls, marqueeData } from "@zag-js/shared"
4+
import { normalizeProps, useMachine } from "@zag-js/vue"
5+
6+
const controls = useControls(marqueeControls)
7+
8+
const service = useMachine(
9+
marquee.machine,
10+
computed(() => ({
11+
id: useId(),
12+
spacing: "2rem",
13+
...controls.value.context,
14+
})),
15+
)
16+
17+
const api = computed(() => marquee.connect(service, normalizeProps))
18+
</script>
19+
20+
<template>
21+
<main class="marquee">
22+
<div v-bind="api.getRootProps()">
23+
<div v-bind="api.getEdgeProps({ side: 'start' })" />
24+
25+
<div v-bind="api.getViewportProps()">
26+
<div v-for="index in api.contentCount" :key="index - 1" v-bind="api.getContentProps({ index: index - 1 })">
27+
<div v-for="(item, i) in marqueeData" :key="i" class="marquee-item">
28+
<span class="marquee-logo">{{ item.logo }}</span>
29+
<span class="marquee-name">{{ item.name }}</span>
30+
</div>
31+
</div>
32+
</div>
33+
34+
<div v-bind="api.getEdgeProps({ side: 'end' })" />
35+
</div>
36+
37+
<div class="controls">
38+
<button @click="api.pause()">Pause</button>
39+
<button @click="api.resume()">Resume</button>
40+
<button @click="api.togglePause()">Toggle</button>
41+
<span>Status: {{ api.paused ? "Paused" : "Playing" }}</span>
42+
</div>
43+
</main>
44+
45+
<Toolbar :controls="controls.ui">
46+
<StateVisualizer :state="service" />
47+
</Toolbar>
48+
</template>

examples/preact-ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@zag-js/json-tree-utils": "workspace:*",
4949
"@zag-js/listbox": "workspace:*",
5050
"@zag-js/live-region": "workspace:*",
51+
"@zag-js/marquee": "workspace:*",
5152
"@zag-js/menu": "workspace:*",
5253
"@zag-js/navigation-menu": "workspace:*",
5354
"@zag-js/number-input": "workspace:*",
@@ -99,4 +100,4 @@
99100
"typescript": "5.9.3",
100101
"vite": "7.1.9"
101102
}
102-
}
103+
}

examples/solid-ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
"@zag-js/json-tree-utils": "workspace:*",
5353
"@zag-js/listbox": "workspace:*",
5454
"@zag-js/live-region": "workspace:*",
55+
"@zag-js/marquee": "workspace:*",
5556
"@zag-js/menu": "workspace:*",
5657
"@zag-js/navigation-menu": "workspace:*",
5758
"@zag-js/number-input": "workspace:*",
@@ -99,4 +100,4 @@
99100
"engines": {
100101
"node": ">=18"
101102
}
102-
}
103+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as marquee from "@zag-js/marquee"
2+
import { normalizeProps, useMachine } from "@zag-js/solid"
3+
import { createMemo, createUniqueId, For, Index } from "solid-js"
4+
import { marqueeControls, marqueeData } from "@zag-js/shared"
5+
import { StateVisualizer } from "../components/state-visualizer"
6+
import { Toolbar } from "../components/toolbar"
7+
import { useControls } from "../hooks/use-controls"
8+
9+
export default function Page() {
10+
const controls = useControls(marqueeControls)
11+
12+
const service = useMachine(
13+
marquee.machine,
14+
controls.mergeProps({
15+
id: createUniqueId(),
16+
spacing: "2rem",
17+
}),
18+
)
19+
20+
const api = createMemo(() => marquee.connect(service, normalizeProps))
21+
22+
return (
23+
<>
24+
<main class="marquee">
25+
<div {...api().getRootProps()}>
26+
<div {...api().getEdgeProps({ side: "start" })} />
27+
28+
<div {...api().getViewportProps()}>
29+
<Index each={Array.from({ length: api().contentCount })}>
30+
{(_, index) => (
31+
<div {...api().getContentProps({ index })}>
32+
<For each={marqueeData}>
33+
{(item) => (
34+
<div class="marquee-item">
35+
<span class="marquee-logo">{item.logo}</span>
36+
<span class="marquee-name">{item.name}</span>
37+
</div>
38+
)}
39+
</For>
40+
</div>
41+
)}
42+
</Index>
43+
</div>
44+
45+
<div {...api().getEdgeProps({ side: "end" })} />
46+
</div>
47+
48+
<div class="controls">
49+
<button onClick={() => api().pause()}>Pause</button>
50+
<button onClick={() => api().resume()}>Resume</button>
51+
<button onClick={() => api().togglePause()}>Toggle</button>
52+
<span>Status: {api().paused ? "Paused" : "Playing"}</span>
53+
</div>
54+
</main>
55+
56+
<Toolbar controls={controls}>
57+
<StateVisualizer state={service} />
58+
</Toolbar>
59+
</>
60+
)
61+
}

examples/svelte-ts/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
"@zag-js/json-tree-utils": "workspace:*",
5454
"@zag-js/listbox": "workspace:*",
5555
"@zag-js/live-region": "workspace:*",
56+
"@zag-js/marquee": "workspace:*",
5657
"@zag-js/menu": "workspace:*",
5758
"@zag-js/navigation-menu": "workspace:*",
5859
"@zag-js/number-input": "workspace:*",
@@ -108,4 +109,4 @@
108109
"vite": "7.1.9",
109110
"vite-tsconfig-paths": "5.1.4"
110111
}
111-
}
112+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<script lang="ts">
2+
import StateVisualizer from "$lib/components/state-visualizer.svelte"
3+
import Toolbar from "$lib/components/toolbar.svelte"
4+
import { useControls } from "$lib/use-controls.svelte"
5+
import * as marquee from "@zag-js/marquee"
6+
import { marqueeControls, marqueeData } from "@zag-js/shared"
7+
import { normalizeProps, useMachine } from "@zag-js/svelte"
8+
9+
const controls = useControls(marqueeControls)
10+
11+
const id = $props.id()
12+
const service = useMachine(
13+
marquee.machine,
14+
controls.mergeProps<marquee.Props>({
15+
id,
16+
spacing: "2rem",
17+
}),
18+
)
19+
20+
const api = $derived(marquee.connect(service, normalizeProps))
21+
</script>
22+
23+
<main class="marquee">
24+
<div {...api.getRootProps()}>
25+
<div {...api.getEdgeProps({ side: "start" })}></div>
26+
27+
<div {...api.getViewportProps()}>
28+
{#each Array.from({ length: api.contentCount }) as _, index}
29+
<div {...api.getContentProps({ index })}>
30+
{#each marqueeData as item}
31+
<div class="marquee-item">
32+
<span class="marquee-logo">{item.logo}</span>
33+
<span class="marquee-name">{item.name}</span>
34+
</div>
35+
{/each}
36+
</div>
37+
{/each}
38+
</div>
39+
40+
<div {...api.getEdgeProps({ side: "end" })}></div>
41+
</div>
42+
43+
<div class="controls">
44+
<button onclick={() => api.pause()}>Pause</button>
45+
<button onclick={() => api.resume()}>Resume</button>
46+
<button onclick={() => api.togglePause()}>Toggle</button>
47+
<span>Status: {api.paused ? "Paused" : "Playing"}</span>
48+
</div>
49+
</main>
50+
51+
<Toolbar {controls}>
52+
<StateVisualizer state={service} />
53+
</Toolbar>

0 commit comments

Comments
 (0)