Skip to content

Commit b1aa4c7

Browse files
committed
add documentation for utility classes
1 parent afb3e6e commit b1aa4c7

4 files changed

Lines changed: 399 additions & 1 deletion

File tree

packages/circuit-ui/styles/style-mixins.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import * as Stories from './style-mixins.stories';
1111

1212
# Style Mixins
1313

14-
<Status variant="deprecated">Use the `utilClasses` instead.</Status>
14+
<Status variant="deprecated">Use the [`utilClasses`](Features/Utility-Classes/Docs) instead.</Status>
1515

1616
<Intro>
1717
Style mixins are small helper functions to apply common styles to an HTML
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
import {
2+
Meta,
3+
Intro,
4+
Status,
5+
Props,
6+
Story,
7+
} from '../../../.storybook/components';
8+
import * as Stories from './utility.stories.tsx';
9+
10+
# Utility classes
11+
12+
<Intro>
13+
<Intro>
14+
Utility classes provide reusable `className` values for applying some common Circuit UI styling patterns to elements and components.
15+
</Intro></Intro>
16+
17+
## Usage
18+
Import the `utilClasses` object from `@sumup-oss/@circuit-ui` and pass the desired utility class to the `className` prop.
19+
20+
```tsx
21+
import { utilClasses } from '@sumup-oss/circuit-ui';
22+
23+
function MyComponent() {
24+
return <div className={utilClasses['<className>']} />;
25+
}
26+
27+
```
28+
29+
<Meta of={Stories} />
30+
31+
## Center
32+
`utilClasses.center`
33+
34+
35+
Pass the utility class to a parent element to center its children.
36+
37+
<Story of={Stories.Center} />
38+
39+
## Hide Visually
40+
`utilClasses.hideVisually`
41+
42+
Visually hides an element while keeping it accessible to users
43+
who rely on a screen reader.
44+
45+
<Story of={Stories.HideVisually} />
46+
47+
## Hide scrollbar
48+
`utilClasses.hideScrollbar`
49+
50+
Hides the browser scrollbar on a scrollable element, e.g. with overflow.
51+
52+
<Story of={Stories.HideScrollbar} />
53+
54+
55+
## Focus
56+
`utilClasses.focusVisible` or `utilClasses.focusVisibleInset`
57+
58+
59+
Shows a focus indicator when the browser determines that focus should be visible, such as during keyboard navigation.
60+
61+
<Story of={Stories.FocusVisible} />
62+
<Story of={Stories.FocusVisibleInset} />
63+
64+
## Spacing
65+
66+
Applies Circuit-UI spacing values as margins to one or all sides of an element.
67+
68+
<Story of={Stories.Spacing} />
Lines changed: 277 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,277 @@
1+
import { utilClasses } from './utility.js';
2+
import { useState } from 'react';
3+
import { Toggle } from '../components/Toggle/index.js';
4+
import { RadioButtonGroup } from '../components/RadioButtonGroup/index.js';
5+
import { clsx } from './clsx.js';
6+
import styles from './utilityStories.module.css';
7+
import { Body } from '../components/Body/index.js';
8+
import { userEvent } from 'storybook/test';
9+
10+
export default {
11+
title: 'Features/Utility Classes',
12+
tags: ['status:stable'],
13+
parameters: {
14+
chromatic: {
15+
disableSnapshot: true,
16+
},
17+
},
18+
};
19+
20+
async function pressTab() {
21+
await userEvent.tab();
22+
}
23+
24+
export const Center = () => {
25+
const [className, setClassName] = useState<string | undefined>(
26+
utilClasses.center,
27+
);
28+
return (
29+
<>
30+
<div className={clsx(styles.wrapper, className)}>
31+
<Body>{className ? 'Centered' : 'Not centered'}</Body>
32+
</div>
33+
<Toggle
34+
label={'Apply center class name'}
35+
checked={className !== undefined}
36+
className={utilClasses.marginTopGiga}
37+
onChange={() =>
38+
setClassName((current) => (current ? undefined : utilClasses.center))
39+
}
40+
/>
41+
</>
42+
);
43+
};
44+
45+
export const HideVisually = () => {
46+
const [className, setClassName] = useState<string | undefined>(
47+
utilClasses.hideVisually,
48+
);
49+
50+
return (
51+
<>
52+
<Body className={className}>Hidden</Body>
53+
<Toggle
54+
label={'Apply hideVisually class name'}
55+
checked={className === utilClasses.hideVisually}
56+
className={utilClasses.marginTopGiga}
57+
onChange={() =>
58+
setClassName((current) =>
59+
current ? undefined : utilClasses.hideVisually,
60+
)
61+
}
62+
/>
63+
</>
64+
);
65+
};
66+
67+
export const HideScrollbar = () => {
68+
const [className, setClassName] = useState<string | undefined>(
69+
utilClasses.hideScrollbar,
70+
);
71+
72+
return (
73+
<>
74+
<div className={clsx(styles.wrapper, className)}>
75+
<div className={styles.scrollable}>Scroll me</div>
76+
</div>
77+
<Toggle
78+
label={'Apply hideScrollbar class name'}
79+
checked={className === utilClasses.hideScrollbar}
80+
className={utilClasses.marginTopGiga}
81+
onChange={() =>
82+
setClassName((current) =>
83+
current ? undefined : utilClasses.hideScrollbar,
84+
)
85+
}
86+
/>
87+
</>
88+
);
89+
};
90+
91+
export const FocusVisible = () => {
92+
const [className, setClassName] = useState<string | undefined>(
93+
utilClasses.focusVisible,
94+
);
95+
96+
return (
97+
<>
98+
<div
99+
// biome-ignore lint/a11y/noNoninteractiveTabindex: for demo purposes only
100+
tabIndex={0}
101+
className={className}
102+
>
103+
I&apos;m an interactive div
104+
</div>
105+
<Toggle
106+
label={'Apply focusVisible class name'}
107+
checked={className === utilClasses.focusVisible}
108+
className={utilClasses.marginTopGiga}
109+
onChange={() =>
110+
setClassName((current) =>
111+
current ? undefined : utilClasses.focusVisible,
112+
)
113+
}
114+
/>
115+
</>
116+
);
117+
};
118+
FocusVisible.play = pressTab;
119+
120+
export const FocusVisibleInset = () => {
121+
const [className, setClassName] = useState<string | undefined>(
122+
utilClasses.focusVisibleInset,
123+
);
124+
125+
return (
126+
<>
127+
<div
128+
// biome-ignore lint/a11y/noNoninteractiveTabindex: for demo purposes only
129+
tabIndex={0}
130+
className={className}
131+
>
132+
I&apos;m an interactive div
133+
</div>
134+
<Toggle
135+
label={'Apply focusVisibleInset class name'}
136+
checked={className === utilClasses.focusVisibleInset}
137+
className={utilClasses.marginTopGiga}
138+
onChange={() =>
139+
setClassName((current) =>
140+
current ? undefined : utilClasses.focusVisibleInset,
141+
)
142+
}
143+
/>
144+
</>
145+
);
146+
};
147+
FocusVisibleInset.play = pressTab;
148+
149+
const sizes = [
150+
'Bit',
151+
'Byte',
152+
'Kilo',
153+
'Mega',
154+
'Giga',
155+
'Tera',
156+
'Peta',
157+
'Exa',
158+
'Zetta',
159+
'Yotta',
160+
'Ronna',
161+
'Quetta',
162+
] as const;
163+
type Size = (typeof sizes)[number];
164+
165+
export const Spacing = () => {
166+
const sizeOptions = sizes.map((size) => ({ label: size, value: size }));
167+
const [allDirections, setAllDirections] = useState(true);
168+
const [allDirectionsSpacingValue, setAllDirectionsSpacingValue] =
169+
useState<Size>('Bit');
170+
const [spacingTopValue, setSpacingTopValue] = useState<Size>('Bit');
171+
const [spacingBottomValue, setSpacingBottomValue] = useState<Size>('Bit');
172+
const [spacingLeftValue, setSpacingLeftValue] = useState<Size>('Bit');
173+
const [spacingRightValue, setSpacingRightValue] = useState<Size>('Bit');
174+
const className = allDirections
175+
? utilClasses[`margin${allDirectionsSpacingValue}`]
176+
: clsx(
177+
utilClasses[`marginTop${spacingTopValue}`],
178+
utilClasses[`marginBottom${spacingBottomValue}`],
179+
utilClasses[`marginLeft${spacingLeftValue}`],
180+
utilClasses[`marginRight${spacingRightValue}`],
181+
);
182+
183+
return (
184+
<>
185+
<div className={clsx(styles.wrapper, styles.flex)}>
186+
<div
187+
className={clsx(
188+
className,
189+
styles.content,
190+
styles.flex,
191+
utilClasses.center,
192+
)}
193+
>
194+
{allDirections && (
195+
<Body color="on-strong">
196+
className applied: {`margin${allDirectionsSpacingValue}`}
197+
</Body>
198+
)}
199+
{!allDirections && (
200+
<>
201+
<Body
202+
color="on-strong"
203+
className={styles.left}
204+
>{`marginLeft${spacingLeftValue}`}</Body>
205+
206+
<Body
207+
color="on-strong"
208+
className={styles.top}
209+
>{`marginTop${spacingTopValue}`}</Body>
210+
<Body
211+
color="on-strong"
212+
className={styles.bottom}
213+
>{`marginBottom${spacingBottomValue}`}</Body>
214+
215+
<Body color="on-strong" className={styles.right}>
216+
{' '}
217+
{`marginRight${spacingRightValue}`}
218+
</Body>
219+
</>
220+
)}
221+
</div>
222+
</div>
223+
<Toggle
224+
label={'Apply spacing to all directions'}
225+
checked={allDirections}
226+
className={clsx(
227+
utilClasses.marginTopGiga,
228+
utilClasses.marginBottomGiga,
229+
)}
230+
onChange={() => setAllDirections((current) => !current)}
231+
/>
232+
{allDirections ? (
233+
<RadioButtonGroup
234+
value={allDirectionsSpacingValue}
235+
onChange={(event) =>
236+
setAllDirectionsSpacingValue(event.target.value as Size)
237+
}
238+
options={sizeOptions}
239+
label={'Spacing value'}
240+
/>
241+
) : (
242+
<div className={styles.directions}>
243+
<RadioButtonGroup
244+
value={spacingTopValue}
245+
onChange={(event) => setSpacingTopValue(event.target.value as Size)}
246+
options={sizeOptions}
247+
label={'Spacing top value'}
248+
/>
249+
<RadioButtonGroup
250+
value={spacingBottomValue}
251+
onChange={(event) =>
252+
setSpacingBottomValue(event.target.value as Size)
253+
}
254+
options={sizeOptions}
255+
label={'Spacing bottom value'}
256+
/>
257+
<RadioButtonGroup
258+
value={spacingLeftValue}
259+
onChange={(event) =>
260+
setSpacingLeftValue(event.target.value as Size)
261+
}
262+
options={sizeOptions}
263+
label={'Spacing left value'}
264+
/>
265+
<RadioButtonGroup
266+
value={spacingRightValue}
267+
onChange={(event) =>
268+
setSpacingRightValue(event.target.value as Size)
269+
}
270+
options={sizeOptions}
271+
label={'Spacing right value'}
272+
/>
273+
</div>
274+
)}
275+
</>
276+
);
277+
};

0 commit comments

Comments
 (0)