Skip to content

Commit db15a87

Browse files
committed
feat: implement radio group indicator disabled
1 parent e115051 commit db15a87

File tree

9 files changed

+84
-24
lines changed

9 files changed

+84
-24
lines changed

examples/next-ts/pages/segment-control.tsx

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import * as radio from "@zag-js/radio-group"
22
import { normalizeProps, useMachine } from "@zag-js/react"
3-
import { radioControls, radioData } from "@zag-js/shared"
3+
import { radioControls, segmentControlData } from "@zag-js/shared"
44
import { useId } from "react"
55
import { StateVisualizer } from "../components/state-visualizer"
66
import { Toolbar } from "../components/toolbar"
@@ -13,6 +13,7 @@ export default function Page() {
1313
id: useId(),
1414
name: "fruit",
1515
orientation: "horizontal",
16+
defaultValue: "orange",
1617
...controls.context,
1718
})
1819

@@ -23,16 +24,31 @@ export default function Page() {
2324
<main className="segmented-control">
2425
<div {...api.getRootProps()}>
2526
<div {...api.getIndicatorProps()} />
26-
{radioData.map((opt) => (
27-
<label key={opt.id} data-testid={`radio-${opt.id}`} {...api.getItemProps({ value: opt.id })}>
28-
<span data-testid={`label-${opt.id}`} {...api.getItemTextProps({ value: opt.id })}>
27+
{segmentControlData.map((opt) => (
28+
<label
29+
key={opt.id}
30+
data-testid={`radio-${opt.id}`}
31+
{...api.getItemProps({ value: opt.id, disabled: opt.disabled })}
32+
>
33+
<span
34+
data-testid={`label-${opt.id}`}
35+
{...api.getItemTextProps({ value: opt.id, disabled: opt.disabled })}
36+
>
2937
{opt.label}
3038
</span>
31-
<input data-testid={`input-${opt.id}`} {...api.getItemHiddenInputProps({ value: opt.id })} />
39+
<input
40+
data-testid={`input-${opt.id}`}
41+
{...api.getItemHiddenInputProps({ value: opt.id, disabled: opt.disabled })}
42+
/>
3243
</label>
3344
))}
3445
</div>
35-
<button onClick={api.clearValue}>reset</button>
46+
47+
<div style={{ display: "flex", flexDirection: "row" }}>
48+
<button onClick={api.clearValue}>Reset</button>
49+
<button onClick={() => api.clearValue()}>Clear</button>
50+
<button onClick={() => api.setValue("orange")}>Set to Oranges</button>
51+
</div>
3652
</main>
3753

3854
<Toolbar controls={controls.ui}>

packages/machines/radio-group/src/radio-group.connect.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,14 @@ export function connect<T extends PropTypes>(
196196

197197
getIndicatorProps() {
198198
const rect = context.get("indicatorRect")
199+
const disabled = context.get("indicatorDisabled")
200+
199201
return normalize.element({
200202
id: dom.getIndicatorId(scope),
201203
...parts.indicator.attrs,
202204
dir: prop("dir"),
203205
hidden: context.get("value") == null,
204-
"data-disabled": dataAttr(groupDisabled),
206+
"data-disabled": dataAttr(groupDisabled) || dataAttr(disabled),
205207
"data-orientation": prop("orientation"),
206208
style: {
207209
"--transition-property": "left, top, width, height",

packages/machines/radio-group/src/radio-group.dom.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,8 @@ export const resolveRect = (rect: Record<"width" | "height" | "left" | "top", nu
5252
left: `${rect.left}px`,
5353
top: `${rect.top}px`,
5454
})
55+
56+
export const getIsTriggerDisabled = (ctx: Scope, value: string) => {
57+
const el = ctx.getById(getItemId(ctx, value))
58+
return el?.getAttribute("data-disabled") === ""
59+
}

packages/machines/radio-group/src/radio-group.machine.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ export const machine = createMachine<RadioGroupSchema>({
4040
indicatorRect: bindable<Partial<IndicatorRect>>(() => ({
4141
defaultValue: {},
4242
})),
43+
indicatorDisabled: bindable(() => ({ defaultValue: false })),
4344
canIndicatorTransition: bindable<boolean>(() => ({
4445
defaultValue: false,
4546
})),
@@ -62,15 +63,15 @@ export const machine = createMachine<RadioGroupSchema>({
6263
isDisabled: ({ prop, context }) => !!prop("disabled") || context.get("fieldsetDisabled"),
6364
},
6465

65-
entry: ["syncIndicatorRect", "syncSsr"],
66+
entry: ["syncIndicatorDisabled", "syncIndicatorRect", "syncSsr"],
6667

6768
exit: ["cleanupObserver"],
6869

6970
effects: ["trackFormControlState", "trackFocusVisible"],
7071

7172
watch({ track, action, context }) {
7273
track([() => context.get("value")], () => {
73-
action(["setIndicatorTransition", "syncIndicatorRect", "syncInputElements"])
74+
action(["setIndicatorTransition", "syncIndicatorDisabled", "syncIndicatorRect", "syncInputElements"])
7475
})
7576
},
7677

@@ -148,6 +149,12 @@ export const machine = createMachine<RadioGroupSchema>({
148149
syncSsr({ context }) {
149150
context.set("ssr", false)
150151
},
152+
syncIndicatorDisabled({ context, scope }) {
153+
const value = context.get("value")
154+
if (value) {
155+
context.set("indicatorDisabled", dom.getIsTriggerDisabled(scope, value))
156+
}
157+
},
151158
syncIndicatorRect({ context, scope, refs }) {
152159
refs.get("indicatorCleanup")?.()
153160

packages/machines/radio-group/src/radio-group.types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ interface PrivateContext {
8888
* The active tab indicator's dom rect
8989
*/
9090
indicatorRect: Partial<IndicatorRect>
91+
/**
92+
* Whether the active tab indicator is disabled
93+
*/
94+
indicatorDisabled: boolean
9195
/**
9296
* Whether the active tab indicator's rect can transition
9397
*/

packages/machines/tabs/src/tabs.connect.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,14 +185,14 @@ export function connect<T extends PropTypes>(service: Service<TabsSchema>, norma
185185
getIndicatorProps() {
186186
const indicatorRect = context.get("indicatorRect")
187187
const indicatorTransition = context.get("indicatorTransition")
188-
const disabled = context.get("indicatorDisabled")
188+
const indicatorDisabled = context.get("indicatorDisabled")
189189

190190
return normalize.element({
191191
id: dom.getIndicatorId(scope),
192192
...parts.indicator.attrs,
193193
dir: prop("dir"),
194194
"data-orientation": prop("orientation"),
195-
"data-disabled": dataAttr(disabled),
195+
"data-disabled": dataAttr(indicatorDisabled),
196196
style: {
197197
"--transition-property": "left, right, top, bottom, width, height",
198198
"--left": indicatorRect.left,

packages/machines/tabs/src/tabs.machine.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,13 @@ export const machine = createMachine({
5252

5353
watch({ context, prop, track, action }) {
5454
track([() => context.get("value")], () => {
55-
action(["allowIndicatorTransition", "syncIndicatorRect", "syncTabIndex", "navigateIfNeeded"])
55+
action([
56+
"allowIndicatorTransition",
57+
"syncIndicatorDisabled",
58+
"syncIndicatorRect",
59+
"syncTabIndex",
60+
"navigateIfNeeded",
61+
])
5662
})
5763
track([() => prop("dir"), () => prop("orientation")], () => {
5864
action(["syncIndicatorRect"])
@@ -74,7 +80,7 @@ export const machine = createMachine({
7480
},
7581
},
7682

77-
entry: ["syncIndicatorRect", "syncTabIndex", "syncSsr"],
83+
entry: ["syncIndicatorDisabled", "syncIndicatorRect", "syncTabIndex", "syncSsr"],
7884

7985
exit: ["cleanupObserver"],
8086

@@ -261,7 +267,6 @@ export const machine = createMachine({
261267
if (!triggerEl) return
262268

263269
context.set("indicatorRect", dom.getRectById(scope, value))
264-
context.set("indicatorDisabled", dom.getIsTriggerDisabled(scope, value))
265270

266271
nextTick(() => {
267272
context.set("indicatorTransition", false)
@@ -270,6 +275,12 @@ export const machine = createMachine({
270275
syncSsr({ context }) {
271276
context.set("ssr", false)
272277
},
278+
syncIndicatorDisabled({ context, scope }) {
279+
const value = context.get("value")
280+
if (value) {
281+
context.set("indicatorDisabled", dom.getIsTriggerDisabled(scope, value))
282+
}
283+
},
273284
syncIndicatorRect({ context, refs, scope }) {
274285
const cleanup = refs.get("indicatorCleanup")
275286
if (cleanup) cleanup()
@@ -291,7 +302,6 @@ export const machine = createMachine({
291302
onEntry({ rects }) {
292303
const [rect] = rects
293304
context.set("indicatorRect", dom.resolveRect(rect))
294-
context.set("indicatorDisabled", dom.getIsTriggerDisabled(scope, value))
295305
},
296306
})
297307

shared/src/css/segmented-control.css

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,18 @@
1111
padding: 8px;
1212
width: 100%;
1313
text-align: center;
14+
15+
&[data-disabled] {
16+
opacity: 0.5;
17+
cursor: not-allowed;
18+
}
1419
}
1520

1621
.segmented-control [data-part="indicator"] {
1722
width: var(--width);
1823
height: var(--height);
24+
25+
&[data-disabled] {
26+
opacity: 0.5;
27+
}
1928
}

shared/src/data.ts

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ export const comboboxData = [
5858
]
5959

6060
export const tabsData = [
61+
{
62+
id: "nils",
63+
label: "Nils Frahm",
64+
content: `
65+
Nils Frahm is a German musician, composer and record producer based in Berlin. He is known for combining
66+
classical and electronic music and for an unconventional approach to the piano in which he mixes a grand
67+
piano, upright piano, Roland Juno-60, Rhodes piano, drum machine, and Moog Taurus.
68+
`,
69+
},
6170
{
6271
id: "evelyn",
6372
label: "Evelyn Glennie",
@@ -69,15 +78,6 @@ export const tabsData = [
6978
`,
7079
disabled: true,
7180
},
72-
{
73-
id: "nils",
74-
label: "Nils Frahm",
75-
content: `
76-
Nils Frahm is a German musician, composer and record producer based in Berlin. He is known for combining
77-
classical and electronic music and for an unconventional approach to the piano in which he mixes a grand
78-
piano, upright piano, Roland Juno-60, Rhodes piano, drum machine, and Moog Taurus.
79-
`,
80-
},
8181
{
8282
id: "agnes",
8383
label: "Agnes Obel",
@@ -140,6 +140,13 @@ export const radioData = [
140140
{ id: "grape", label: "Grapes" },
141141
]
142142

143+
export const segmentControlData = [
144+
{ id: "apple", label: "Apples" },
145+
{ id: "orange", label: "Oranges", disabled: true },
146+
{ id: "mango", label: "Mangoes" },
147+
{ id: "grape", label: "Grapes" },
148+
]
149+
143150
export const carouselData = [
144151
"https://picsum.photos/seed/a/500/300",
145152
"https://picsum.photos/seed/b/500/300",

0 commit comments

Comments
 (0)