Skip to content

Commit 932564a

Browse files
committed
Add pattern-library documentation for Tab and TabList
1 parent f37af3a commit 932564a

File tree

2 files changed

+397
-0
lines changed

2 files changed

+397
-0
lines changed
Lines changed: 390 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,390 @@
1+
import classnames from 'classnames';
2+
import { useState } from 'preact/hooks';
3+
4+
import {
5+
Card,
6+
EmailIcon,
7+
ProfileIcon,
8+
SettingsIcon,
9+
Tab,
10+
TabList,
11+
} from '../../../../next';
12+
import Library from '../../Library';
13+
import Next from '../../LibraryNext';
14+
15+
export default function TabPage() {
16+
const [prefPanel, setPrefPanel] = useState('notifications');
17+
const [sidebarPanel, setSidebarPanel] = useState('annotations');
18+
const [sidebarPanel2, setSidebarPanel2] = useState('annotations');
19+
const [sidebarPanel3, setSidebarPanel3] = useState('annotations');
20+
const [verticalPanel, setVerticalPanel] = useState('notifications');
21+
return (
22+
<Library.Page
23+
title="Tabs"
24+
intro={
25+
<p>
26+
<code>Tab</code> and <code>TabList</code> are presentational
27+
components for rendering accessible tabs.
28+
</p>
29+
}
30+
>
31+
<Library.Section
32+
title="Tab"
33+
intro={
34+
<p>
35+
<code>Tab</code> generates a button with appropriate ARIA
36+
attributes.
37+
</p>
38+
}
39+
>
40+
<Library.Pattern title="Status">
41+
<p>
42+
<code>Tab</code> is a new component.
43+
</p>
44+
</Library.Pattern>
45+
46+
<Library.Pattern title="Usage">
47+
<Next.Usage componentName="Tab" />
48+
<Library.Example>
49+
<Library.Demo title="Basic (non-interactive) example" withSource>
50+
<div role="tablist" className="gap-x-6 flex">
51+
<Tab>
52+
Annotations
53+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
54+
{52}
55+
</span>
56+
</Tab>
57+
<Tab selected>
58+
Page Notes
59+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
60+
{4}
61+
</span>
62+
</Tab>
63+
<Tab>
64+
Orphans
65+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
66+
{2}
67+
</span>
68+
</Tab>
69+
</div>
70+
</Library.Demo>
71+
</Library.Example>
72+
73+
<ul>
74+
<li>
75+
<code>Tab</code>s <em>must</em> be direct children of an element
76+
with <code>role={'"tablist"'}</code> (or use the{' '}
77+
<code>TabList</code> component).
78+
</li>
79+
<li>
80+
You <em>should</em> provide an{' '}
81+
<a href="https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/Attributes/aria-controls">
82+
<code>aria-controls</code>
83+
</a>{' '}
84+
attribute to each <code>Tab</code>. This is not always feasible in
85+
our applications.
86+
</li>
87+
</ul>
88+
</Library.Pattern>
89+
90+
<Library.Pattern title="Props">
91+
<Library.Example title="aria-controls">
92+
An element with <code>{'role="tab"'}</code> should set an{' '}
93+
<code>aria-controls</code> attribute when possible. See the full{' '}
94+
<code>TabList</code> example below.
95+
</Library.Example>
96+
97+
<Library.Example title="icon">
98+
<code>Tab</code>s may have icons. The icon will be displayed on the
99+
left and sized proportionally to the inherited font size.
100+
<Library.Demo title="Tabs with icon" withSource>
101+
<div role="tablist">
102+
<Tab icon={ProfileIcon}>Profile</Tab>
103+
<Tab classes="text-lg" icon={ProfileIcon}>
104+
Profile
105+
</Tab>
106+
<Tab classes="text-xl" icon={ProfileIcon}>
107+
Profile
108+
</Tab>
109+
</div>
110+
</Library.Demo>
111+
</Library.Example>
112+
113+
<Library.Example title="selected">
114+
This boolean property asserts that the <code>Tab</code> is currently
115+
selected and the <code>tabpanel</code> it controls (where relevant)
116+
is active and visible.
117+
</Library.Example>
118+
119+
<Library.Example title="textContent">
120+
<p>
121+
Bolding is used in our design patterns to indicate a selected tab.
122+
Without any intervention, textual tabs will jiggle around when
123+
they are selected. This has a simple cause: bold text takes up
124+
more room.
125+
</p>
126+
<p>
127+
<code>textContent</code> is a string representing the text content
128+
of the tab when selected.{' '}
129+
<strong>
130+
Setting <code>textContent</code> can help prevent jiggle in
131+
selected tabs
132+
</strong>
133+
. The size of the tab will accommodate this string rendered in
134+
bold text.
135+
</p>
136+
<Library.Demo
137+
title="Tabs without textContent (jiggle when selected)"
138+
withSource
139+
>
140+
<TabList classes="gap-x-6">
141+
<Tab
142+
selected={sidebarPanel === 'annotations'}
143+
onClick={() => setSidebarPanel('annotations')}
144+
>
145+
Annotations
146+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
147+
{52}
148+
</span>
149+
</Tab>
150+
<Tab
151+
selected={sidebarPanel === 'pageNotes'}
152+
onClick={() => setSidebarPanel('pageNotes')}
153+
>
154+
Page Notes
155+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
156+
{48}
157+
</span>
158+
</Tab>
159+
<Tab
160+
selected={sidebarPanel === 'orphans'}
161+
onClick={() => setSidebarPanel('orphans')}
162+
>
163+
Orphans
164+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
165+
{4}
166+
</span>
167+
</Tab>
168+
</TabList>
169+
</Library.Demo>
170+
<p>
171+
For tabs that have a simple text label, setting{' '}
172+
<code>textContent</code> to that string avoids the jiggle. Text
173+
will still change size (bold text is larger) but the tabs
174+
themselves do not move.
175+
</p>
176+
<Library.Demo title="Tabs with textContent (no jiggle)" withSource>
177+
<TabList classes="gap-x-6">
178+
<Tab
179+
selected={sidebarPanel2 === 'annotations'}
180+
onClick={() => setSidebarPanel2('annotations')}
181+
textContent="Annotations"
182+
>
183+
Annotations
184+
</Tab>
185+
<Tab
186+
selected={sidebarPanel2 === 'pageNotes'}
187+
onClick={() => setSidebarPanel2('pageNotes')}
188+
textContent="Page Notes"
189+
>
190+
Page Notes
191+
</Tab>
192+
<Tab
193+
selected={sidebarPanel2 === 'orphans'}
194+
onClick={() => setSidebarPanel2('orphans')}
195+
textContent="Orphans"
196+
>
197+
Orphans
198+
</Tab>
199+
</TabList>
200+
</Library.Demo>
201+
<p>
202+
For tabs with styled or dynamic content, <code>textContent</code>{' '}
203+
can be set to an estimated {'"widest-possible-text-content"'}{' '}
204+
value. A trial and error approach worked here:
205+
</p>
206+
<Library.Demo
207+
title="Tabs with estimated widest-value textContent"
208+
withSource
209+
>
210+
<TabList classes="gap-x-6">
211+
<Tab
212+
selected={sidebarPanel3 === 'annotations'}
213+
onClick={() => setSidebarPanel3('annotations')}
214+
textContent="Annotations##"
215+
>
216+
Annotations
217+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
218+
{52}
219+
</span>
220+
</Tab>
221+
<Tab
222+
selected={sidebarPanel3 === 'pageNotes'}
223+
onClick={() => setSidebarPanel3('pageNotes')}
224+
textContent="Page Notes##"
225+
>
226+
Page Notes
227+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
228+
{56}
229+
</span>
230+
</Tab>
231+
<Tab
232+
selected={sidebarPanel3 === 'orphans'}
233+
onClick={() => setSidebarPanel3('orphans')}
234+
textContent="Orphans##"
235+
>
236+
Orphans
237+
<span className="relative bottom-[3px] left-[2px] text-[10px]">
238+
{2}
239+
</span>
240+
</Tab>
241+
</TabList>
242+
</Library.Demo>
243+
</Library.Example>
244+
</Library.Pattern>
245+
</Library.Section>
246+
<Library.Section
247+
title="TabList"
248+
intro={
249+
<p>
250+
<code>TabList</code> is a presentational component that provides a{' '}
251+
<code>{'role="tablist"'}</code> container and arrow-key navigation
252+
as outlined by{' '}
253+
<a href="https://www.w3.org/WAI/ARIA/apg/patterns/tabpanel/">
254+
WAI-ARIA authoring practices
255+
</a>
256+
.
257+
</p>
258+
}
259+
>
260+
<Library.Pattern title="Status">
261+
<p>
262+
<code>TabList</code> is a new component.
263+
</p>
264+
</Library.Pattern>
265+
<Library.Pattern title="Usage">
266+
<Next.Usage componentName="TabList" />
267+
<Library.Example>
268+
<p>
269+
This example demonstrates a full Tab pattern with{' '}
270+
<code>TabList</code>, <code>Tab</code> and some tabpanels. The
271+
tabpanels have been made focusable here as they contain no
272+
focusable elements: pressing <kbd>tab</kbd> when in the tablist
273+
will move focus to the active tabpanel. Tabs may be navigated with
274+
the left and right arrows.
275+
</p>
276+
<Library.Demo withSource title="Full Tab pattern example">
277+
<div>
278+
<TabList classes="w-[400px] gap-x-6 my-4">
279+
<Tab
280+
aria-controls="profile-panel"
281+
textContent="Profile"
282+
selected={prefPanel === 'profile'}
283+
onClick={() => setPrefPanel('profile')}
284+
>
285+
Profile
286+
</Tab>
287+
<Tab
288+
aria-controls="notifications-panel"
289+
textContent="Notifications"
290+
selected={prefPanel === 'notifications'}
291+
onClick={() => setPrefPanel('notifications')}
292+
>
293+
Notifications
294+
</Tab>
295+
<Tab
296+
aria-controls="account-panel"
297+
textContent="Account"
298+
selected={prefPanel === 'account'}
299+
onClick={() => setPrefPanel('account')}
300+
>
301+
Account
302+
</Tab>
303+
</TabList>
304+
<Card
305+
classes={classnames(
306+
{ hidden: prefPanel !== 'profile' },
307+
'p-2 focus-visible-ring'
308+
)}
309+
id="profile-panel"
310+
role="tabpanel"
311+
tabIndex={0}
312+
variant="flat"
313+
>
314+
Profile
315+
</Card>
316+
<Card
317+
classes={classnames(
318+
{ hidden: prefPanel !== 'notifications' },
319+
'p-2 focus-visible-ring'
320+
)}
321+
id="notifications-panel"
322+
role="tabpanel"
323+
tabIndex={0}
324+
variant="flat"
325+
>
326+
Notifications
327+
</Card>
328+
<Card
329+
classes={classnames(
330+
{ hidden: prefPanel !== 'account' },
331+
'p-2 focus-visible-ring'
332+
)}
333+
id="account-panel"
334+
role="tabpanel"
335+
tabIndex={0}
336+
variant="flat"
337+
>
338+
Account
339+
</Card>
340+
</div>
341+
</Library.Demo>
342+
</Library.Example>
343+
</Library.Pattern>
344+
345+
<Library.Pattern title="Props">
346+
<Library.Example title="vertical">
347+
<p>
348+
By default, <code>TabList</code> layout is horizontal. Set the
349+
boolean <code>vertical</code> prop for a vertical layout. This
350+
will also enable arrow-key navigation using the up and down
351+
arrows.
352+
</p>
353+
<p>
354+
The following example demonstrates vertical layout and up/down
355+
keyboard navigation.
356+
</p>
357+
<Library.Demo withSource title="Vertical TabList">
358+
<TabList classes="gap-y-2" vertical>
359+
<Tab
360+
icon={ProfileIcon}
361+
onClick={() => setVerticalPanel('profile')}
362+
selected={verticalPanel === 'profile'}
363+
textContent="Profile"
364+
>
365+
Profile
366+
</Tab>
367+
<Tab
368+
icon={EmailIcon}
369+
onClick={() => setVerticalPanel('notifications')}
370+
selected={verticalPanel === 'notifications'}
371+
textContent="Notifications"
372+
>
373+
Notifications
374+
</Tab>
375+
<Tab
376+
icon={SettingsIcon}
377+
onClick={() => setVerticalPanel('account')}
378+
selected={verticalPanel === 'account'}
379+
textContent="Account"
380+
>
381+
Account
382+
</Tab>
383+
</TabList>
384+
</Library.Demo>
385+
</Library.Example>
386+
</Library.Pattern>
387+
</Library.Section>
388+
</Library.Page>
389+
);
390+
}

0 commit comments

Comments
 (0)