Skip to content

Commit 6398d76

Browse files
committed
chore(docs): added SpinButton docs
1 parent 4179921 commit 6398d76

File tree

10 files changed

+244
-0
lines changed

10 files changed

+244
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
"use client";
2+
3+
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
4+
import { type SpinButtonValue } from "@react-md/core/spinbutton/types";
5+
import { Typography } from "@react-md/core/typography/Typography";
6+
import { type ReactElement, useState } from "react";
7+
8+
export default function ControllingTheValueExample(): ReactElement {
9+
const [value, setValue] = useState<SpinButtonValue>(null);
10+
return (
11+
<>
12+
<SpinButton
13+
aria-label="Example"
14+
value={value}
15+
min={0}
16+
max={10}
17+
minDigits={2}
18+
onValueChange={({ value }) => setValue(value)}
19+
/>
20+
<Typography>Current value: {`"${value}"`}</Typography>
21+
</>
22+
);
23+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
3+
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
4+
import { type SpinButtonValue } from "@react-md/core/spinbutton/types";
5+
import { Typography } from "@react-md/core/typography/Typography";
6+
import { type ReactElement, useState } from "react";
7+
8+
export default function OnValueChangeCallbackExample(): ReactElement {
9+
const [value, setValue] = useState<SpinButtonValue>(0);
10+
11+
return (
12+
<>
13+
<Typography>Current value: {`"${value}"`}</Typography>
14+
<SpinButton
15+
aria-label="Minutes"
16+
min={0}
17+
max={59}
18+
fallback="MM"
19+
defaultValue={0}
20+
onValueChange={(options) => {
21+
const { reason, value } = options;
22+
if (reason !== "type") {
23+
setValue(value);
24+
}
25+
}}
26+
/>
27+
</>
28+
);
29+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
2+
import { type ReactElement } from "react";
3+
4+
export default function RangedSpinButtonExample(): ReactElement {
5+
return <SpinButton aria-label="Range Example" min={0} max={10} />;
6+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
2+
import { type ReactElement } from "react";
3+
4+
export default function SimpleExample(): ReactElement {
5+
return <SpinButton aria-label="Simple Example" />;
6+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
"use client";
2+
3+
import { Box } from "@react-md/core/box/Box";
4+
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
5+
import { SpinButtonGroupProvider } from "@react-md/core/spinbutton/SpinButtonGroupProvider";
6+
import { useSpinButtonGroupProvider } from "@react-md/core/spinbutton/useSpinButtonGroupProvider";
7+
import { type ReactElement } from "react";
8+
9+
export default function SpinButtonGroupExample(): ReactElement {
10+
const { movementProps, movementContext } = useSpinButtonGroupProvider();
11+
return (
12+
<Box {...movementProps}>
13+
<SpinButtonGroupProvider value={movementContext}>
14+
<SpinButton aria-label="Hours" min={1} max={12} fallback="hh" />:
15+
<SpinButton aria-label="Minutes" min={0} max={59} fallback="mm" />:
16+
<SpinButton aria-label="Seconds" min={0} max={59} fallback="ss" />
17+
</SpinButtonGroupProvider>
18+
</Box>
19+
);
20+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { SpinButton } from "@react-md/core/spinbutton/SpinButton";
2+
import { type ReactElement } from "react";
3+
4+
export default function UsingAFallbackExample(): ReactElement {
5+
return (
6+
<>
7+
<SpinButton aria-label="Fallback Example" min={1} />
8+
<SpinButton
9+
aria-label="Fallback Example"
10+
min={1}
11+
max={12}
12+
minDigits={2}
13+
/>
14+
<SpinButton
15+
aria-label="Custom Fallback Example"
16+
fallback="HH"
17+
min={1}
18+
max={12}
19+
/>
20+
</>
21+
);
22+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
@use "everything";
2+
3+
div[role="spinbutton"] {
4+
@include everything.divider-border-style;
5+
@include everything.interaction-outline;
6+
7+
align-items: center;
8+
display: inline-flex;
9+
justify-content: center;
10+
min-height: 3rem;
11+
min-width: 3rem;
12+
padding: 0.5rem;
13+
14+
&:focus-visible {
15+
@include everything.interaction-focus-styles;
16+
}
17+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { type ReactElement, type ReactNode } from "react";
2+
3+
import "./layout.scss";
4+
5+
export interface LayoutProps {
6+
children: ReactNode;
7+
}
8+
9+
export default function Layout({
10+
children,
11+
}: Readonly<LayoutProps>): ReactElement {
12+
return <>{children}</>;
13+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
---
2+
title: Spin Button
3+
description: The SpinButton component can be used to implement the spinbutton role when a <TextField type="number" /> cannot be used.
4+
docType: Demo
5+
docGroup: Components
6+
group: Inputs
7+
components: [SpinButton, SpinButtonGroup, SpinButtonGroupProvider]
8+
hooks: [useSpinButton, useSpinButtonProvider]
9+
---
10+
11+
# Spin Button
12+
13+
> !Info! In most cases, use the [NumberField](/components/text-field#number) instead.
14+
15+
The `SpinButton` component can be used to implement the
16+
[`spinbutton` role](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Reference/Roles/spinbutton_role).
17+
when a `<TextField type="number" />` cannot be used. The `SpinButton` does not have styles
18+
by default and will need to be provided manually.
19+
20+
To help with the demos on this page, the following styles have been applied:
21+
22+
```import source="./layout.scss"
23+
24+
```
25+
26+
## Simple SpinButton
27+
28+
A `SpinButton` has no styles by default and requires an `aria-label` or
29+
`aria-labelledby` for accessibility. The value can be changed by:
30+
31+
- using the `ArrowUp` or `ArrowDown` keys
32+
- typing a number
33+
- clearing the field with `Backspace` or `Delete`
34+
35+
Any non-digit keys will be ignored.
36+
37+
> !Info! All `SpinButton` on this page will be updated to have minimal styles
38+
> to help show the component.
39+
40+
```demo source="./SimpleExample.tsx"
41+
42+
```
43+
44+
### Ranged SpinButton
45+
46+
It is recommended to provide the `min` and `max` props to improve accessibility
47+
and limit the range. This also adds support for updating the value using the
48+
`Home` and `End` keys and will prevent the user from typing values outside of
49+
this range.
50+
51+
```demo source="./RangedSpinButtonExample.tsx"
52+
53+
```
54+
55+
## Using a Fallback
56+
57+
In the previous examples, the `SpinButton` content was either empty, a hyphen,
58+
or the current value. When there is no value, the `SpinButton` has a few
59+
default text content:
60+
61+
- if `minDigits` is provided, return `-` for each digit
62+
- if `min` is provided, return `-` for each digit
63+
- if `fallback` is provided, use that value
64+
- otherwise, return an empty string
65+
66+
```demo source="./UsingAFallbackExample.tsx"
67+
68+
```
69+
70+
## Controlling the value
71+
72+
The value can be controlled by providing a `value` and `onValueChange`. The
73+
`onValueChange` callback provides an object with:
74+
75+
- `event` - either a `KeyboardEvent`, `FormEvent`, or `FocusEvent`
76+
- `reason` - either `"type"`, `"cleared"`, `"typed-to-completion"`, or `"change"`
77+
- `value` - the next value
78+
79+
```demo source="./ControllingTheValueExample.tsx"
80+
81+
```
82+
83+
### Setting a default value
84+
85+
An alternative to controlling the value is to use a `defaultValue` instead.
86+
This can be useful alongside the `onValueChange` callback to only update a
87+
local state value when a specific change reason occurs.
88+
89+
```demo source="./OnValueChangeCallbackExample.tsx"
90+
91+
```
92+
93+
## SpinButtonGroup Example
94+
95+
When multiple `SpinButton` should be used together to create a single value,
96+
use the `SpinButtonGroupProvider` and `useSpinButtonGroupProvider` as a wrapper
97+
for the `SpinButton` components. When the user triggers the
98+
`"typed-to-completion"` event, the next `SpinButton` will automatically be
99+
focused.
100+
101+
```demo source="./SpinButtonGroupExample.tsx"
102+
103+
```

apps/docs/src/components/MainLayout/navItems.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ export const navItems: readonly NavigationItem[] = [
123123
href: "/slider",
124124
children: "Slider",
125125
},
126+
{
127+
type: "route",
128+
href: "/spin-button",
129+
children: "SpinButton",
130+
},
126131
{
127132
type: "route",
128133
href: "/switch",

0 commit comments

Comments
 (0)