@justeattakeaway/pie-list 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +238 -21
- package/custom-elements.json +111 -13
- package/dist/{PieElement-ab_PEQpW.js → PieElement-DQQMEr70.js} +1 -1
- package/dist/index-BH08tWcm.js +339 -0
- package/dist/index.js +1 -1
- package/dist/pie-list-item/defs.d.ts +28 -9
- package/dist/pie-list-item/defs.d.ts.map +1 -1
- package/dist/pie-list-item/index.d.ts +40 -11
- package/dist/pie-list-item/index.d.ts.map +1 -1
- package/dist/pie-list-item/index.js +4 -3
- package/dist/pie-list-item/react.js +8 -8
- package/package.json +3 -3
- package/src/pie-list-item/defs.ts +30 -10
- package/src/pie-list-item/index.ts +147 -23
- package/src/pie-list-item/list-item.scss +82 -1
- package/dist/index-mqipXuiX.js +0 -265
package/README.md
CHANGED
|
@@ -21,7 +21,25 @@
|
|
|
21
21
|
- [CSS Variables](#css-variables)
|
|
22
22
|
- [Events](#events)
|
|
23
23
|
- [Accessibility](#accessibility)
|
|
24
|
+
- [You never set ARIA on a list item or its slotted content](#you-never-set-aria-on-a-list-item-or-its-slotted-content)
|
|
25
|
+
- [How the naming is derived](#how-the-naming-is-derived)
|
|
24
26
|
- [Usage Examples](#usage-examples)
|
|
27
|
+
- [Basic list](#basic-list)
|
|
28
|
+
- [Leading and trailing content](#leading-and-trailing-content)
|
|
29
|
+
- [Trailing tag](#trailing-tag)
|
|
30
|
+
- [Meta text](#meta-text)
|
|
31
|
+
- [Bold primary text](#bold-primary-text)
|
|
32
|
+
- [Compact list](#compact-list)
|
|
33
|
+
- [Media](#media)
|
|
34
|
+
- [Overriding alignment and padding](#overriding-alignment-and-padding)
|
|
35
|
+
- [Selectable lists](#selectable-lists)
|
|
36
|
+
- [Single-select (radios)](#single-select-radios)
|
|
37
|
+
- [Multi-select (checkboxes)](#multi-select-checkboxes)
|
|
38
|
+
- [Independent toggles (switches)](#independent-toggles-switches)
|
|
39
|
+
- [Link items](#link-items)
|
|
40
|
+
- [Indicating the current page](#indicating-the-current-page)
|
|
41
|
+
- [Button items](#button-items)
|
|
42
|
+
- [Framework variants](#framework-variants)
|
|
25
43
|
- [Usage Notes and Rules](#usage-notes-and-rules)
|
|
26
44
|
- [Questions and Support](#questions-and-support)
|
|
27
45
|
- [Contributing](#contributing)
|
|
@@ -55,8 +73,9 @@ Ideally, you should install the component using the **`@justeattakeaway/pie-webc
|
|
|
55
73
|
| `isCompact` | `true`, `false` | Decreases the item height to save vertical space. See the [rules](#usage-notes-and-rules) below. | `false` |
|
|
56
74
|
| `isBold` | `true`, `false` | Sets the primary text to a bold font-weight. | `false` |
|
|
57
75
|
| `hasMedia` | `true`, `false` | **Required whenever you slot a media element (e.g. `pie-thumbnail`) into the item.** Reduces the block padding so single-line media sits correctly (this padding adjustment has no effect when `secondaryText` is set, but you should still set `hasMedia`). | `false` |
|
|
58
|
-
| `
|
|
76
|
+
| `interactionType` | `"none"`, `"radio"`, `"checkbox"`, `"switch"`, `"link"`, `"button"` | Declares how the **whole row** behaves, and drives its role, accessible naming, click forwarding and interactive states from this one prop. `"none"` is a static item; `"radio"`/`"checkbox"`/`"switch"` host the matching control in the `leading`/`trailing` slot; `"link"` turns the row into a single navigation link (slot an empty `<a slot="link" href="...">`); `"button"` turns the row into a single button for an in-page action (the item renders the button for you - listen for `click`). See [Selectable lists](#selectable-lists), [Link items](#link-items) and [Button items](#button-items). | `"none"` |
|
|
59
77
|
| `disabled` | `true`, `false` | Marks the row as disabled: it takes the disabled styling and stops forwarding row clicks to its control. Set it alongside the slotted control's own `disabled` (the control still governs its own interactivity). No visible effect on a non-selectable (static) item. | `false` |
|
|
78
|
+
| `aria` | `{ button?: { haspopup?: "menu" \| "listbox" \| "tree" \| "grid" \| "dialog" \| "true" } }` | Additional ARIA properties that the item cannot derive from its text props. `button.haspopup` only applies when `interactionType="button"` and is forwarded to the internal `<button>` element. Set it when the button row triggers a popup such as a dialog or menu. | `undefined` |
|
|
60
79
|
|
|
61
80
|
### Slots
|
|
62
81
|
|
|
@@ -66,6 +85,7 @@ Slots are provided by `pie-list-item`.
|
|
|
66
85
|
|---|---|
|
|
67
86
|
| `leading` | Content displayed at the start of the item, before the text. Intended for a small icon or a media element (e.g. `pie-thumbnail`). If slotting `pie-thumbnail`, it MUST use `size="40"`; this is the only size that fits the list-item layout correctly. |
|
|
68
87
|
| `trailing` | Content displayed at the end of the item, after the text. Intended for a small icon, a `pie-tag`, etc. Not rendered when `metaText` is set. |
|
|
88
|
+
| `link` | Only rendered when `interactionType="link"`. Slot a single **empty** anchor (`<a slot="link" href="...">`) here. It is stretched over the whole row so the entire item becomes the navigation target. The item supplies the anchor's accessible name and description from its text, so the anchor must contain no text of its own; apply any native anchor attributes you need (`href`, `target`, `rel`, and so on). See [Link items](#link-items). |
|
|
69
89
|
|
|
70
90
|
The permitted slotted elements are: a PIE WEBC icon, `pie-tag`, `pie-thumbnail`, `pie-avatar`*, `pie-switch`, and native HTML radio/checkbox inputs.
|
|
71
91
|
Some slotted content is designed with specific properties being used. So please read the entire readme to understand correct slot usage.
|
|
@@ -116,9 +136,36 @@ This component does not emit any custom events. To listen for interactions, trea
|
|
|
116
136
|
</pie-list>
|
|
117
137
|
```
|
|
118
138
|
|
|
139
|
+
### You never set ARIA on a list item or its slotted content
|
|
140
|
+
|
|
141
|
+
> [!IMPORTANT]
|
|
142
|
+
> You do **not** add any ARIA attributes to a `pie-list-item`, nor to the control, anchor, icon or media you slot into it. The item manages all of that for you, driven entirely by the `primaryText`, `secondaryText` and `metaText` props (and the `interactionType`). Just provide the text and slot the element in.
|
|
143
|
+
|
|
144
|
+
Specifically, for **every** interaction type the item takes care of:
|
|
145
|
+
|
|
146
|
+
- the **role** on the item itself (`listitem`, or `presentation` for radio/checkbox rows so the group owns the control directly) - you never write `role="..."`;
|
|
147
|
+
- the **accessible name and description** of whatever it hosts - you never write `aria-label`, `aria-labelledby`, `aria-description` or `aria-describedby` on the control, anchor or button;
|
|
148
|
+
- **hiding the duplicated visible text** from assistive technology (the item `aria-hidden`s its own text once the name/description have been mirrored onto the interactive element), so nothing is announced twice.
|
|
149
|
+
|
|
150
|
+
The one accessibility attribute you **do** own is the accessible name of the `pie-list` container itself (`aria-label` / `aria-labelledby`), because only you know what the list represents. See the note above.
|
|
151
|
+
|
|
152
|
+
### How the naming is derived
|
|
153
|
+
|
|
154
|
+
When a `pie-list-item` hosts an interactive element (a slotted control set via `interactionType="radio" | "checkbox" | "switch"`, a slotted link anchor via `interactionType="link"`, or the button it renders for `interactionType="button"`) it generates that element's accessibility naming from its own text:
|
|
155
|
+
|
|
156
|
+
- `primaryText` becomes the element's **accessible name**;
|
|
157
|
+
- `secondaryText` and `metaText` become its **accessible description** (combined with a full stop when both are present);
|
|
158
|
+
- the visible text rendered inside the item is `aria-hidden`, so a screen reader announces the name and description once (via the interactive element) rather than twice.
|
|
159
|
+
|
|
160
|
+
How the naming reaches the element differs by type, but this is entirely internal - you never do it yourself. Selection controls receive it through a shared context and each applies it to the element that carries its role: `pie-radio` names its host, while `pie-checkbox` and `pie-switch` name their internal `input` (their host is role-less). A link anchor is a plain light-DOM element you slot (not a PIE control), so `pie-list-item` sets `aria-label` and `aria-description` on it directly. A `button` row's button is one the item renders itself, so it is named the same way from within the item.
|
|
161
|
+
|
|
162
|
+
**For slotted elements, anything you set yourself wins.** If a slotted control already has its own name (its `label` or `aria` prop), or the slotted link anchor already has an `aria-label`/`aria-labelledby` (or `aria-description`/`aria-describedby`), the item leaves it untouched and does not overwrite it. This is an escape hatch for the rare case that needs it, not something you normally reach for. (A `button` row's button is rendered and named by the item, so there is nothing for you to set.)
|
|
163
|
+
|
|
119
164
|
## Usage Examples
|
|
120
165
|
|
|
121
|
-
|
|
166
|
+
The examples below use HTML. For framework-specific syntax, see [Framework variants](#framework-variants).
|
|
167
|
+
|
|
168
|
+
### Basic list
|
|
122
169
|
|
|
123
170
|
```js
|
|
124
171
|
// import as modules into a js file e.g. main.js
|
|
@@ -142,7 +189,7 @@ A basic list with primary, secondary and meta text (note the required `aria-labe
|
|
|
142
189
|
<script type="module" src="/main.js"></script>
|
|
143
190
|
```
|
|
144
191
|
|
|
145
|
-
|
|
192
|
+
### Leading and trailing content
|
|
146
193
|
|
|
147
194
|
```js
|
|
148
195
|
import '@justeattakeaway/pie-icons-webc/dist/IconChevronRight.js';
|
|
@@ -157,7 +204,7 @@ import '@justeattakeaway/pie-icons-webc/dist/IconChevronRight.js';
|
|
|
157
204
|
</pie-list>
|
|
158
205
|
```
|
|
159
206
|
|
|
160
|
-
|
|
207
|
+
### Trailing tag
|
|
161
208
|
|
|
162
209
|
```js
|
|
163
210
|
import '@justeattakeaway/pie-webc/components/tag.js';
|
|
@@ -171,7 +218,9 @@ import '@justeattakeaway/pie-webc/components/tag.js';
|
|
|
171
218
|
</pie-list>
|
|
172
219
|
```
|
|
173
220
|
|
|
174
|
-
|
|
221
|
+
### Meta text
|
|
222
|
+
|
|
223
|
+
Renders as trailing text; do not combine with the `trailing` slot:
|
|
175
224
|
|
|
176
225
|
```html
|
|
177
226
|
<pie-list>
|
|
@@ -182,7 +231,7 @@ import '@justeattakeaway/pie-webc/components/tag.js';
|
|
|
182
231
|
</pie-list>
|
|
183
232
|
```
|
|
184
233
|
|
|
185
|
-
|
|
234
|
+
### Bold primary text
|
|
186
235
|
|
|
187
236
|
```html
|
|
188
237
|
<pie-list>
|
|
@@ -190,7 +239,9 @@ import '@justeattakeaway/pie-webc/components/tag.js';
|
|
|
190
239
|
</pie-list>
|
|
191
240
|
```
|
|
192
241
|
|
|
193
|
-
|
|
242
|
+
### Compact list
|
|
243
|
+
|
|
244
|
+
Reduced height, see [rules](#usage-notes-and-rules):
|
|
194
245
|
|
|
195
246
|
```html
|
|
196
247
|
<pie-list>
|
|
@@ -203,7 +254,9 @@ import '@justeattakeaway/pie-webc/components/tag.js';
|
|
|
203
254
|
</pie-list>
|
|
204
255
|
```
|
|
205
256
|
|
|
206
|
-
|
|
257
|
+
### Media
|
|
258
|
+
|
|
259
|
+
Slot larger media such as `pie-thumbnail` into the `leading` slot:
|
|
207
260
|
|
|
208
261
|
```js
|
|
209
262
|
import '@justeattakeaway/pie-webc/components/thumbnail.js';
|
|
@@ -227,7 +280,9 @@ import '@justeattakeaway/pie-webc/components/thumbnail.js';
|
|
|
227
280
|
</pie-list>
|
|
228
281
|
```
|
|
229
282
|
|
|
230
|
-
|
|
283
|
+
### Overriding alignment and padding
|
|
284
|
+
|
|
285
|
+
Via CSS variables:
|
|
231
286
|
|
|
232
287
|
```html
|
|
233
288
|
<!-- Vertically centre the content of the items. `--list-item-alignment` must be set on the
|
|
@@ -248,20 +303,22 @@ import '@justeattakeaway/pie-webc/components/thumbnail.js';
|
|
|
248
303
|
|
|
249
304
|
### Selectable lists
|
|
250
305
|
|
|
251
|
-
`pie-list` itself is a static container with no selection or keyboard behaviour. To build a **selectable** list, do **not** put the controls in `pie-list`. Instead, place `pie-list-item`s inside a [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) (single-select) or a [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) (multi-select), set each item's `
|
|
306
|
+
`pie-list` itself is a static container with no selection or keyboard behaviour. To build a **selectable** list, do **not** put the controls in `pie-list`. Instead, place `pie-list-item`s inside a [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) (single-select) or a [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) (multi-select), set each item's `interactionType`, and slot the control into the item's `leading` (or `trailing`) slot. Switches have no group, so `interactionType="switch"` rows go directly in a `pie-list`.
|
|
252
307
|
|
|
253
|
-
#### The `
|
|
308
|
+
#### The `interactionType` prop
|
|
254
309
|
|
|
255
|
-
`
|
|
310
|
+
`interactionType` (set to `"radio"`, `"checkbox"` or `"switch"`) is what makes a row selectable. Setting it drives the item to:
|
|
256
311
|
|
|
257
312
|
- take the correct **role** — `presentation` for `radio`/`checkbox` (so the group owns the controls directly), `listitem` for `switch` and `none`;
|
|
258
313
|
- provide its `primaryText`, `secondaryText` and `metaText` as the slotted control's **accessible name and description** (and `aria-hidden` the now-duplicated visible text);
|
|
259
314
|
- **forward a click** anywhere on the row to the control, so the whole row is a hit target;
|
|
260
|
-
- show **hover and active** states on the row (suppressed when the control, or
|
|
315
|
+
- show **hover and active** states on the row (suppressed when the control is disabled, or when its group is disabled; switches have no group, so only per-row and per-switch disable applies).
|
|
261
316
|
|
|
262
|
-
Provide the label through the item's `primaryText` — not as the control's own content. The group still owns the group-level semantics (its own role, shared `name`, selection coordination and group-disable); `
|
|
317
|
+
Provide the label through the item's `primaryText` — not as the control's own content. The group still owns the group-level semantics (its own role, shared `name`, selection coordination and group-disable); `interactionType` only governs the item. See the [`pie-radio-group`](https://webc.pie.design/?path=/docs/components-radio-group--overview) and [`pie-checkbox-group`](https://webc.pie.design/?path=/docs/components-checkbox-group--overview) docs for the group behaviour.
|
|
263
318
|
|
|
264
|
-
|
|
319
|
+
You do not add any ARIA to the item or the slotted control - `pie-list-item` derives the control's accessible name and description from your `primaryText`, `secondaryText` and `metaText`. See [Accessibility](#accessibility).
|
|
320
|
+
|
|
321
|
+
#### Single-select (radios)
|
|
265
322
|
|
|
266
323
|
```js
|
|
267
324
|
import '@justeattakeaway/pie-webc/components/radio-group.js';
|
|
@@ -270,19 +327,19 @@ import '@justeattakeaway/pie-webc/components/list-item.js';
|
|
|
270
327
|
```
|
|
271
328
|
|
|
272
329
|
```html
|
|
273
|
-
<!-- `
|
|
330
|
+
<!-- `interactionType="radio"` makes each row selectable and the group lays them out as a divided
|
|
274
331
|
list. `value` on the group selects the matching radio (here, Express). -->
|
|
275
332
|
<pie-radio-group name="delivery" value="express">
|
|
276
|
-
<pie-list-item
|
|
333
|
+
<pie-list-item interactionType="radio" primaryText="Standard delivery" secondaryText="3 to 5 working days" metaText="Free">
|
|
277
334
|
<pie-radio slot="leading" value="standard"></pie-radio>
|
|
278
335
|
</pie-list-item>
|
|
279
|
-
<pie-list-item
|
|
336
|
+
<pie-list-item interactionType="radio" primaryText="Express delivery" secondaryText="Next working day" metaText="£4.99">
|
|
280
337
|
<pie-radio slot="leading" value="express"></pie-radio>
|
|
281
338
|
</pie-list-item>
|
|
282
339
|
</pie-radio-group>
|
|
283
340
|
```
|
|
284
341
|
|
|
285
|
-
Multi-select (checkboxes)
|
|
342
|
+
#### Multi-select (checkboxes)
|
|
286
343
|
|
|
287
344
|
```js
|
|
288
345
|
import '@justeattakeaway/pie-webc/components/checkbox-group.js';
|
|
@@ -292,15 +349,175 @@ import '@justeattakeaway/pie-webc/components/list-item.js';
|
|
|
292
349
|
|
|
293
350
|
```html
|
|
294
351
|
<pie-checkbox-group>
|
|
295
|
-
<pie-list-item
|
|
352
|
+
<pie-list-item interactionType="checkbox" primaryText="Cheese" secondaryText="Extra mature" metaText="Free">
|
|
296
353
|
<pie-checkbox slot="leading" name="cheese"></pie-checkbox>
|
|
297
354
|
</pie-list-item>
|
|
298
|
-
<pie-list-item
|
|
355
|
+
<pie-list-item interactionType="checkbox" primaryText="Pepperoni" secondaryText="Spicy">
|
|
299
356
|
<pie-checkbox slot="leading" name="pepperoni"></pie-checkbox>
|
|
300
357
|
</pie-list-item>
|
|
301
358
|
</pie-checkbox-group>
|
|
302
359
|
```
|
|
303
360
|
|
|
361
|
+
#### Independent toggles (switches)
|
|
362
|
+
|
|
363
|
+
There is no group, so the rows go in a `pie-list` (which needs its own accessible name), and the switch sits in the `trailing` slot. Set `disabled` on the `pie-list-item` alongside any disabled `pie-switch`:
|
|
364
|
+
|
|
365
|
+
```js
|
|
366
|
+
import '@justeattakeaway/pie-webc/components/list.js';
|
|
367
|
+
import '@justeattakeaway/pie-webc/components/list-item.js';
|
|
368
|
+
import '@justeattakeaway/pie-webc/components/switch.js';
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
```html
|
|
372
|
+
<pie-list aria-label="Notification settings">
|
|
373
|
+
<pie-list-item interactionType="switch" primaryText="Email" secondaryText="Order updates and receipts">
|
|
374
|
+
<pie-switch slot="trailing"></pie-switch>
|
|
375
|
+
</pie-list-item>
|
|
376
|
+
<pie-list-item interactionType="switch" primaryText="Push notifications">
|
|
377
|
+
<pie-switch slot="trailing"></pie-switch>
|
|
378
|
+
</pie-list-item>
|
|
379
|
+
<pie-list-item interactionType="switch" primaryText="SMS" disabled>
|
|
380
|
+
<pie-switch slot="trailing" disabled></pie-switch>
|
|
381
|
+
</pie-list-item>
|
|
382
|
+
</pie-list>
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Each `pie-switch` manages its own state and emits a native `change` event when toggled. Listen on the switch itself, or on the `pie-list` since the event bubbles. Set a switch's initial state with its `checked` prop. There is no group coordinating them, so each switch is independent.
|
|
386
|
+
|
|
387
|
+
### Link items
|
|
388
|
+
|
|
389
|
+
Set `interactionType="link"` and slot a single **empty** anchor into the `link` slot to turn the whole row into one navigation link. The anchor is stretched over the entire item (`position: absolute; inset: 0`), so a click or tap anywhere on the row follows the link, and keyboard focus lands on the row as a whole (the focus ring hugs the row).
|
|
390
|
+
|
|
391
|
+
Because the anchor is empty, `pie-list-item` names it from its own text: `primaryText` becomes the anchor's `aria-label` and `secondaryText`/`metaText` its `aria-description`, and the visible text is hidden from assistive technology so nothing is announced twice (see [You never set ARIA on a list item or its slotted content](#you-never-set-aria-on-a-list-item-or-its-slotted-content)). If you set your own `aria-label`/`aria-labelledby` (or `aria-description`/`aria-describedby`) on the anchor, that wins and the item leaves it untouched. Apply whatever native anchor attributes you need (`href`, `target`, `rel`, `download`, and so on); the only requirement is that the anchor **contains no text** (it must stay empty for the naming to work). Give the `pie-list` its own accessible name (e.g. `aria-label`).
|
|
392
|
+
|
|
393
|
+
```js
|
|
394
|
+
import '@justeattakeaway/pie-webc/components/list.js';
|
|
395
|
+
import '@justeattakeaway/pie-webc/components/list-item.js';
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
```html
|
|
399
|
+
<pie-list aria-label="Manage your restaurant">
|
|
400
|
+
<pie-list-item interactionType="link" primaryText="Orders" secondaryText="View and manage live orders" metaText="12 active">
|
|
401
|
+
<a slot="link" href="/orders"></a>
|
|
402
|
+
</pie-list-item>
|
|
403
|
+
<pie-list-item interactionType="link" primaryText="Menu" secondaryText="Edit items, prices and photos">
|
|
404
|
+
<a slot="link" href="/menu"></a>
|
|
405
|
+
</pie-list-item>
|
|
406
|
+
</pie-list>
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
You are not limited to a raw `<a>`. Any component that renders an anchor and forwards the `slot` attribute works the same way, so in **Next.js you can slot the `next/link` `<Link>` component** (and in Nuxt, `<NuxtLink>`). Slot it into `link`, keep it empty, and `pie-list-item` names it exactly as it names a raw anchor:
|
|
410
|
+
|
|
411
|
+
```jsx
|
|
412
|
+
// Next.js
|
|
413
|
+
import Link from 'next/link';
|
|
414
|
+
import { PieListItem } from '@justeattakeaway/pie-webc/react/list-item.js';
|
|
415
|
+
|
|
416
|
+
<PieListItem interactionType="link" primaryText="Orders" secondaryText="View and manage live orders">
|
|
417
|
+
<Link slot="link" href="/orders" />
|
|
418
|
+
</PieListItem>
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
#### Indicating the current page
|
|
422
|
+
|
|
423
|
+
To highlight the active link in a navigation list, combine `isBold` on the `pie-list-item` with `aria-current="page"` on the slotted anchor. `isBold` gives the active item a heavier weight visually; `aria-current="page"` communicates the current page to assistive technology. `pie-list-item` only manages the anchor's `aria-label` and `aria-description`, so `aria-current` does not conflict with the item's naming logic and is safe to set yourself.
|
|
424
|
+
|
|
425
|
+
You can also slot a tick icon into the `trailing` slot of the active item as an additional visual indicator. PIE icons render with `role="presentation"` on their SVG, so they are already hidden from assistive technology and no extra `aria-hidden` is needed.
|
|
426
|
+
|
|
427
|
+
```js
|
|
428
|
+
import '@justeattakeaway/pie-icons-webc/dist/IconCheck.js';
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
```html
|
|
432
|
+
<pie-list aria-label="Account navigation">
|
|
433
|
+
<pie-list-item interactionType="link" primaryText="Overview">
|
|
434
|
+
<a slot="link" href="/account"></a>
|
|
435
|
+
</pie-list-item>
|
|
436
|
+
<pie-list-item interactionType="link" isBold primaryText="Orders">
|
|
437
|
+
<a slot="link" href="/account/orders" aria-current="page"></a>
|
|
438
|
+
<icon-check slot="trailing"></icon-check>
|
|
439
|
+
</pie-list-item>
|
|
440
|
+
<pie-list-item interactionType="link" primaryText="Payment methods">
|
|
441
|
+
<a slot="link" href="/account/payment"></a>
|
|
442
|
+
</pie-list-item>
|
|
443
|
+
<pie-list-item interactionType="link" primaryText="Addresses">
|
|
444
|
+
<a slot="link" href="/account/addresses"></a>
|
|
445
|
+
</pie-list-item>
|
|
446
|
+
</pie-list>
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
In React, pass `isBold` as a prop and `aria-current="page"` directly to the `<Link>` component (or raw anchor):
|
|
450
|
+
|
|
451
|
+
```jsx
|
|
452
|
+
import { IconCheck } from '@justeattakeaway/pie-icons-webc/dist/IconCheck.js';
|
|
453
|
+
|
|
454
|
+
<PieListItem interactionType="link" isBold primaryText="Orders">
|
|
455
|
+
<Link slot="link" href="/account/orders" aria-current="page" />
|
|
456
|
+
<IconCheck slot="trailing" />
|
|
457
|
+
</PieListItem>
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
### Button items
|
|
461
|
+
|
|
462
|
+
Set `interactionType="button"` to turn the whole row into one button. Use this for an **in-page action** (opening a dialog, running a command); use [`link`](#link-items) when the row navigates to another page. Unlike a link, **you do not slot anything** - the item renders the interactive element for you: an invisible, row-sized native `<button type="button">` stretched over the row. A click or tap anywhere on the row activates it, and keyboard focus lands on the row as a whole (the focus ring hugs the row). It is just an action trigger, **not a form control** - it is `type="button"`, so it never submits a form, and it carries no value.
|
|
463
|
+
|
|
464
|
+
The item names the button from its own text: `primaryText` becomes the button's `aria-label` and `secondaryText`/`metaText` its `aria-description`, and the visible text is hidden from assistive technology so nothing is announced twice (see [You never set ARIA on a list item or its slotted content](#you-never-set-aria-on-a-list-item-or-its-slotted-content)).
|
|
465
|
+
|
|
466
|
+
**Handle activation by listening for `click`** on the `pie-list-item` (or on the `pie-list`, since the event bubbles). Because it is a real `<button>`, activation is native: both pointer taps and keyboard (Enter, and Space) fire a `click`, so a single `click` listener covers every input. The row shows a pressed tint while it is pressed - by pointer, or while Space is held on the focused row - and the focus ring on keyboard focus. Give the `pie-list` its own accessible name (e.g. `aria-label`).
|
|
467
|
+
|
|
468
|
+
**When the button opens a popup (dialog, menu, etc.)**, pass the `aria` prop with `button.haspopup` set to the popup type. This is forwarded to the internal `<button>` so assistive technology announces it correctly:
|
|
469
|
+
|
|
470
|
+
```html
|
|
471
|
+
<!-- A row that opens a dialog -->
|
|
472
|
+
<pie-list-item
|
|
473
|
+
interactionType="button"
|
|
474
|
+
primaryText="Delete account"
|
|
475
|
+
secondaryText="Permanently remove your account"
|
|
476
|
+
.aria=${{ button: { haspopup: 'dialog' } }}>
|
|
477
|
+
</pie-list-item>
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
In React:
|
|
481
|
+
|
|
482
|
+
```jsx
|
|
483
|
+
<PieListItem
|
|
484
|
+
interactionType="button"
|
|
485
|
+
primaryText="Delete account"
|
|
486
|
+
secondaryText="Permanently remove your account"
|
|
487
|
+
aria={{ button: { haspopup: 'dialog' } }}
|
|
488
|
+
onClick={openConfirmDialog}
|
|
489
|
+
/>
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
```js
|
|
493
|
+
import '@justeattakeaway/pie-webc/components/list.js';
|
|
494
|
+
import '@justeattakeaway/pie-webc/components/list-item.js';
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
```html
|
|
498
|
+
<pie-list aria-label="Account actions">
|
|
499
|
+
<pie-list-item interactionType="button" primaryText="Edit profile" secondaryText="Update your name and photo"></pie-list-item>
|
|
500
|
+
<pie-list-item interactionType="button" primaryText="Sign out" secondaryText="End your session on this device"></pie-list-item>
|
|
501
|
+
</pie-list>
|
|
502
|
+
|
|
503
|
+
<script type="module">
|
|
504
|
+
document.querySelector('pie-list').addEventListener('click', (event) => {
|
|
505
|
+
const item = event.target.closest('pie-list-item');
|
|
506
|
+
if (item) runActionFor(item.primaryText);
|
|
507
|
+
});
|
|
508
|
+
</script>
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
In React, attach an `onClick` to the item:
|
|
512
|
+
|
|
513
|
+
```jsx
|
|
514
|
+
import { PieListItem } from '@justeattakeaway/pie-webc/react/list-item.js';
|
|
515
|
+
|
|
516
|
+
<PieListItem interactionType="button" primaryText="Edit profile" secondaryText="Update your name and photo" onClick={openProfileEditor} />
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
### Framework variants
|
|
520
|
+
|
|
304
521
|
**For Native JS Applications, Vue, Angular, Svelte etc.:**
|
|
305
522
|
|
|
306
523
|
```js
|
package/custom-elements.json
CHANGED
|
@@ -68,11 +68,11 @@
|
|
|
68
68
|
"declarations": [
|
|
69
69
|
{
|
|
70
70
|
"kind": "variable",
|
|
71
|
-
"name": "
|
|
71
|
+
"name": "interactionTypes",
|
|
72
72
|
"type": {
|
|
73
|
-
"text": "['none', 'radio', 'checkbox', 'switch']"
|
|
73
|
+
"text": "['none', 'radio', 'checkbox', 'switch', 'link', 'button']"
|
|
74
74
|
},
|
|
75
|
-
"default": "['none', 'radio', 'checkbox', 'switch']"
|
|
75
|
+
"default": "['none', 'radio', 'checkbox', 'switch', 'link', 'button']"
|
|
76
76
|
},
|
|
77
77
|
{
|
|
78
78
|
"kind": "variable",
|
|
@@ -80,15 +80,15 @@
|
|
|
80
80
|
"type": {
|
|
81
81
|
"text": "DefaultProps"
|
|
82
82
|
},
|
|
83
|
-
"default": "{ isCompact: false, isBold: false, hasMedia: false,
|
|
83
|
+
"default": "{ isCompact: false, isBold: false, hasMedia: false, interactionType: 'none', disabled: false, }"
|
|
84
84
|
}
|
|
85
85
|
],
|
|
86
86
|
"exports": [
|
|
87
87
|
{
|
|
88
88
|
"kind": "js",
|
|
89
|
-
"name": "
|
|
89
|
+
"name": "interactionTypes",
|
|
90
90
|
"declaration": {
|
|
91
|
-
"name": "
|
|
91
|
+
"name": "interactionTypes",
|
|
92
92
|
"module": "src/pie-list-item/defs.js"
|
|
93
93
|
}
|
|
94
94
|
},
|
|
@@ -118,6 +118,10 @@
|
|
|
118
118
|
{
|
|
119
119
|
"description": "Content shown at the end of the item. Mutually exclusive with `metaText`.",
|
|
120
120
|
"name": "trailing"
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"description": "Rendered only when `interactionType=\"link\"`. Slot one empty anchor (e.g. `<a slot=\"link\" href=\"/x\">`) stretched over the whole row as the link target. Must contain no text.",
|
|
124
|
+
"name": "link"
|
|
121
125
|
}
|
|
122
126
|
],
|
|
123
127
|
"members": [
|
|
@@ -162,14 +166,22 @@
|
|
|
162
166
|
},
|
|
163
167
|
{
|
|
164
168
|
"kind": "field",
|
|
165
|
-
"name": "
|
|
166
|
-
"attribute": "
|
|
169
|
+
"name": "interactionType",
|
|
170
|
+
"attribute": "interactionType"
|
|
167
171
|
},
|
|
168
172
|
{
|
|
169
173
|
"kind": "field",
|
|
170
174
|
"name": "disabled",
|
|
171
175
|
"attribute": "disabled"
|
|
172
176
|
},
|
|
177
|
+
{
|
|
178
|
+
"kind": "field",
|
|
179
|
+
"name": "aria",
|
|
180
|
+
"type": {
|
|
181
|
+
"text": "ListItemProps['aria']"
|
|
182
|
+
},
|
|
183
|
+
"attribute": "aria"
|
|
184
|
+
},
|
|
173
185
|
{
|
|
174
186
|
"kind": "field",
|
|
175
187
|
"name": "_parentDisabled",
|
|
@@ -194,7 +206,34 @@
|
|
|
194
206
|
},
|
|
195
207
|
{
|
|
196
208
|
"kind": "field",
|
|
197
|
-
"name": "
|
|
209
|
+
"name": "_managedRole",
|
|
210
|
+
"type": {
|
|
211
|
+
"text": "string | null"
|
|
212
|
+
},
|
|
213
|
+
"privacy": "private",
|
|
214
|
+
"default": "null"
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
"kind": "field",
|
|
218
|
+
"name": "_linkAriaCaptured",
|
|
219
|
+
"type": {
|
|
220
|
+
"text": "boolean"
|
|
221
|
+
},
|
|
222
|
+
"privacy": "private",
|
|
223
|
+
"default": "false"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"kind": "field",
|
|
227
|
+
"name": "_consumerNamedLink",
|
|
228
|
+
"type": {
|
|
229
|
+
"text": "boolean"
|
|
230
|
+
},
|
|
231
|
+
"privacy": "private",
|
|
232
|
+
"default": "false"
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
"kind": "field",
|
|
236
|
+
"name": "_consumerDescribedLink",
|
|
198
237
|
"type": {
|
|
199
238
|
"text": "boolean"
|
|
200
239
|
},
|
|
@@ -210,6 +249,42 @@
|
|
|
210
249
|
"privacy": "private",
|
|
211
250
|
"readonly": true
|
|
212
251
|
},
|
|
252
|
+
{
|
|
253
|
+
"kind": "field",
|
|
254
|
+
"name": "_isLinkRow",
|
|
255
|
+
"type": {
|
|
256
|
+
"text": "boolean"
|
|
257
|
+
},
|
|
258
|
+
"privacy": "private",
|
|
259
|
+
"readonly": true
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"kind": "field",
|
|
263
|
+
"name": "_isButtonRow",
|
|
264
|
+
"type": {
|
|
265
|
+
"text": "boolean"
|
|
266
|
+
},
|
|
267
|
+
"privacy": "private",
|
|
268
|
+
"readonly": true
|
|
269
|
+
},
|
|
270
|
+
{
|
|
271
|
+
"kind": "field",
|
|
272
|
+
"name": "_isOverlayRow",
|
|
273
|
+
"type": {
|
|
274
|
+
"text": "boolean"
|
|
275
|
+
},
|
|
276
|
+
"privacy": "private",
|
|
277
|
+
"readonly": true
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
"kind": "field",
|
|
281
|
+
"name": "_providesAria",
|
|
282
|
+
"type": {
|
|
283
|
+
"text": "boolean"
|
|
284
|
+
},
|
|
285
|
+
"privacy": "private",
|
|
286
|
+
"readonly": true
|
|
287
|
+
},
|
|
213
288
|
{
|
|
214
289
|
"kind": "field",
|
|
215
290
|
"name": "_ownedByGroup",
|
|
@@ -246,7 +321,7 @@
|
|
|
246
321
|
"text": "void"
|
|
247
322
|
}
|
|
248
323
|
},
|
|
249
|
-
"description": "Sets the item's role from `
|
|
324
|
+
"description": "Sets the item's role from `interactionType`: `presentation` for radio/checkbox (so the group\nowns those controls directly), otherwise `listitem` (static items, switches, links and\nbuttons). A role set explicitly by the consumer is left untouched, even if it is added or\nremoved dynamically after the element connects."
|
|
250
325
|
},
|
|
251
326
|
{
|
|
252
327
|
"kind": "field",
|
|
@@ -255,7 +330,7 @@
|
|
|
255
330
|
"text": "ContextualAria | undefined"
|
|
256
331
|
},
|
|
257
332
|
"privacy": "private",
|
|
258
|
-
"description": "The accessible name and description this item provides to its
|
|
333
|
+
"description": "The accessible name and description this item provides to its interactive element, stitched\nfrom its text. Selection controls consume it via `ariaContext` (each applying it to the\nelement that carries its role); the action button of a `button` row takes it directly as\n`aria-label`/`aria-description` in the template; a link anchor takes it via `_applyLinkAria`.",
|
|
259
334
|
"readonly": true
|
|
260
335
|
},
|
|
261
336
|
{
|
|
@@ -264,6 +339,17 @@
|
|
|
264
339
|
"privacy": "private",
|
|
265
340
|
"description": "Forwards a click anywhere on the row to the slotted control, so the whole item is a hit\ntarget. Skips clicks that already landed on the control, to avoid a double toggle."
|
|
266
341
|
},
|
|
342
|
+
{
|
|
343
|
+
"kind": "method",
|
|
344
|
+
"name": "_applyLinkAria",
|
|
345
|
+
"privacy": "private",
|
|
346
|
+
"return": {
|
|
347
|
+
"type": {
|
|
348
|
+
"text": "void"
|
|
349
|
+
}
|
|
350
|
+
},
|
|
351
|
+
"description": "On a link row, names the slotted (empty) anchor from the item's own text: the parent owns the\nchild's aria. The anchor is plain light-DOM, so it cannot reference the shadow-DOM text via\n`aria-labelledby`; and it is not a PIE control that consumes `ariaContext`, so we set the\nattributes imperatively. The visible text is `aria-hidden` in the template, so nothing is\nannounced twice. (A `button` row names its own rendered element in the template instead.)\n\nA consumer-provided name/description always wins: we capture (once) whether the anchor already\ncarries `aria-label`/`aria-labelledby` or `aria-description`/`aria-describedby` and, if so,\nleave that attribute untouched. Otherwise we keep our generated value in sync with the text."
|
|
352
|
+
},
|
|
267
353
|
{
|
|
268
354
|
"kind": "method",
|
|
269
355
|
"name": "_renderSecondaryText"
|
|
@@ -271,6 +357,11 @@
|
|
|
271
357
|
{
|
|
272
358
|
"kind": "method",
|
|
273
359
|
"name": "_renderTrailingContent"
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
"kind": "method",
|
|
363
|
+
"name": "_renderActionButton",
|
|
364
|
+
"description": "The interactive target for a `button` row: an empty, native `<button>` stretched invisibly\nover the whole row (see the SCSS). The item renders and owns it - rather than the consumer\nslotting one - so it can name it from its own text and keep its attributes private (it is just\nan action box, not a configurable control). Being a real button, activation is native: pointer\nclicks and keyboard (Enter/Space) both fire a `click` that bubbles out, so consumers listen for\n`click` on the `pie-list-item`. It is `type=\"button\"`, so it never submits a form."
|
|
274
365
|
}
|
|
275
366
|
],
|
|
276
367
|
"attributes": [
|
|
@@ -308,12 +399,19 @@
|
|
|
308
399
|
"fieldName": "hasMedia"
|
|
309
400
|
},
|
|
310
401
|
{
|
|
311
|
-
"name": "
|
|
312
|
-
"fieldName": "
|
|
402
|
+
"name": "interactionType",
|
|
403
|
+
"fieldName": "interactionType"
|
|
313
404
|
},
|
|
314
405
|
{
|
|
315
406
|
"name": "disabled",
|
|
316
407
|
"fieldName": "disabled"
|
|
408
|
+
},
|
|
409
|
+
{
|
|
410
|
+
"name": "aria",
|
|
411
|
+
"type": {
|
|
412
|
+
"text": "ListItemProps['aria']"
|
|
413
|
+
},
|
|
414
|
+
"fieldName": "aria"
|
|
317
415
|
}
|
|
318
416
|
],
|
|
319
417
|
"superclass": {
|