figma-plugin-utilities 0.4.0 → 0.5.1
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/CHANGELOG.md +41 -1
- package/README.md +134 -26
- package/package.json +27 -2
- package/src/components/CodeExportModal.svelte +98 -0
- package/src/components/ConfirmModal.svelte +66 -0
- package/src/components/DataTable.svelte +349 -0
- package/src/components/FieldGrid.svelte +18 -0
- package/src/components/Footer.svelte +10 -0
- package/src/components/Header.svelte +6 -1
- package/src/components/LadderBadges.svelte +35 -0
- package/src/components/ListItem.svelte +53 -28
- package/src/components/MappingChip.svelte +150 -0
- package/src/components/RampCurve.svelte +383 -0
- package/src/components/Section.svelte +49 -0
- package/src/components/SteppedField.svelte +52 -0
- package/src/components/index.js +9 -0
- package/src/index.js +19 -3
- package/src/lib/colors.ts +99 -0
- package/src/lib/confirm.ts +55 -0
- package/src/lib/errorHandling.js +0 -39
- package/src/lib/figma-frame-builders.ts +2 -2
- package/src/lib/figma-helpers.ts +103 -59
- package/src/lib/figma-variables.ts +151 -0
- package/src/lib/format.ts +19 -0
- package/src/lib/index.js +13 -4
- package/src/lib/scale.ts +147 -0
- package/src/lib/colors.js +0 -80
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
DataTable: named rows with a cell per column, laid out as a table — a
|
|
3
|
+
set's size at each breakpoint, or a style's size and line height before
|
|
4
|
+
and after an update. The name leads each row, with its notes as badges
|
|
5
|
+
after it: the first `maxBadges`, and a count of the rest listed in its
|
|
6
|
+
title. A cell is a badge, a variable chip where it aliases a variable, or
|
|
7
|
+
plain text; badges and text are colored as new, changed or danger.
|
|
8
|
+
|
|
9
|
+
Selectable (the default), rows are buttons, named by their `label`, that
|
|
10
|
+
open the `editor` slot under them, with a trailing button in `action`.
|
|
11
|
+
Not selectable, the table is read-only, with table roles. Removed rows are
|
|
12
|
+
colored and can't be selected either way.
|
|
13
|
+
-->
|
|
14
|
+
<script>
|
|
15
|
+
import { createEventDispatcher } from "svelte";
|
|
16
|
+
import { Badge, Tooltip, VariablePill } from "figma-ui3-kit-svelte";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{
|
|
20
|
+
* label: string,
|
|
21
|
+
* title?: string,
|
|
22
|
+
* width?: string,
|
|
23
|
+
* align?: "start" | "end",
|
|
24
|
+
* }} Column `width` is a grid track, 2.75rem by default; `align` "start"
|
|
25
|
+
* by default, "end" to right-align numbers.
|
|
26
|
+
* @typedef {{
|
|
27
|
+
* text: string | number,
|
|
28
|
+
* tone?: "new" | "changed" | "danger" | "muted" | null,
|
|
29
|
+
* title?: string | null,
|
|
30
|
+
* alias?: string | null,
|
|
31
|
+
* plain?: boolean,
|
|
32
|
+
* }} Cell `alias` names the variable the value aliases, shown as a chip;
|
|
33
|
+
* `plain` shows the text as it is, not as a badge (muted cells are).
|
|
34
|
+
* @typedef {{
|
|
35
|
+
* key: string,
|
|
36
|
+
* name: string,
|
|
37
|
+
* label?: string,
|
|
38
|
+
* cells: Cell[],
|
|
39
|
+
* tone?: "new" | null,
|
|
40
|
+
* removed?: boolean,
|
|
41
|
+
* badges?: { text: string, variant?: string, title?: string }[],
|
|
42
|
+
* [extra: string]: any,
|
|
43
|
+
* }} Row
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/** @type {(Column | string)[]} The columns after the name. */
|
|
47
|
+
export let columns = [];
|
|
48
|
+
/** @type {Row[]} */
|
|
49
|
+
export let rows = [];
|
|
50
|
+
/** The name column's header. */
|
|
51
|
+
export let nameLabel = "Name";
|
|
52
|
+
/** Rows are buttons that open an editor; false makes the table read-only. */
|
|
53
|
+
export let selectable = true;
|
|
54
|
+
/** @type {string | null} The row whose editor is open. */
|
|
55
|
+
export let selectedKey = null;
|
|
56
|
+
/** @type {number | null} A column to mark, header and cells. */
|
|
57
|
+
export let active = null;
|
|
58
|
+
/** Makes the column headers buttons that dispatch `column`. */
|
|
59
|
+
export let selectableColumns = false;
|
|
60
|
+
/**
|
|
61
|
+
* @type {string | null} The container's side padding, such as
|
|
62
|
+
* "var(--size-xsmall)": the rows run through it to the container's edges,
|
|
63
|
+
* their contents still in line with the rest of it.
|
|
64
|
+
*/
|
|
65
|
+
export let inset = null;
|
|
66
|
+
/** How many of a row's badges show before the rest become a count. */
|
|
67
|
+
export let maxBadges = 2;
|
|
68
|
+
/** @type {string | null} Names a read-only table. */
|
|
69
|
+
export let ariaLabel = null;
|
|
70
|
+
|
|
71
|
+
let className = "";
|
|
72
|
+
export { className as class };
|
|
73
|
+
|
|
74
|
+
const dispatch = createEventDispatcher();
|
|
75
|
+
|
|
76
|
+
// The badge a value gets for its tone.
|
|
77
|
+
const TONES = { new: "success", changed: "warning", danger: "danger" };
|
|
78
|
+
|
|
79
|
+
// The limit is passed in, so the markup re-runs when it changes.
|
|
80
|
+
const shown = (row, max) => (row.badges ?? []).slice(0, max);
|
|
81
|
+
const hidden = (row, max) => (row.badges ?? []).slice(max);
|
|
82
|
+
|
|
83
|
+
$: cols = columns.map((c) => (typeof c === "string" ? { label: c } : c));
|
|
84
|
+
$: tracks = cols.map((c) => c.width ?? "2.75rem").join(" ");
|
|
85
|
+
// Read-only, the table has table roles; selectable, its rows are buttons
|
|
86
|
+
// named by their label, and the header is for sight only unless it picks.
|
|
87
|
+
$: roles = !selectable;
|
|
88
|
+
$: headerHidden = !roles && !selectableColumns;
|
|
89
|
+
</script>
|
|
90
|
+
|
|
91
|
+
<div
|
|
92
|
+
class="table {className}"
|
|
93
|
+
class:static={!selectable}
|
|
94
|
+
style:--tracks={tracks}
|
|
95
|
+
style:--inset={inset}
|
|
96
|
+
role={roles ? "table" : undefined}
|
|
97
|
+
aria-label={roles ? ariaLabel : undefined}
|
|
98
|
+
>
|
|
99
|
+
<div class="row-wrap" aria-hidden={headerHidden ? "true" : undefined}>
|
|
100
|
+
<div class="tr th" role={roles ? "row" : undefined}>
|
|
101
|
+
<span
|
|
102
|
+
role={roles ? "columnheader" : undefined}
|
|
103
|
+
aria-hidden={roles ? undefined : "true"}>{nameLabel}</span
|
|
104
|
+
>
|
|
105
|
+
{#each cols as col, c (col.label)}
|
|
106
|
+
{#if selectableColumns}
|
|
107
|
+
<button
|
|
108
|
+
type="button"
|
|
109
|
+
class="num col"
|
|
110
|
+
class:end={col.align === "end"}
|
|
111
|
+
class:active={c === active}
|
|
112
|
+
aria-label="Show {col.label}"
|
|
113
|
+
aria-pressed={c === active}
|
|
114
|
+
title={col.title ?? null}
|
|
115
|
+
on:click={() => dispatch("column", c)}>{col.label}</button
|
|
116
|
+
>
|
|
117
|
+
{:else}
|
|
118
|
+
<span
|
|
119
|
+
class="num"
|
|
120
|
+
class:end={col.align === "end"}
|
|
121
|
+
class:active={c === active}
|
|
122
|
+
role={roles ? "columnheader" : undefined}
|
|
123
|
+
title={col.title ?? null}>{col.label}</span
|
|
124
|
+
>
|
|
125
|
+
{/if}
|
|
126
|
+
{/each}
|
|
127
|
+
</div>
|
|
128
|
+
<span></span>
|
|
129
|
+
</div>
|
|
130
|
+
{#each rows as row (row.key)}
|
|
131
|
+
{@const interactive = selectable && !row.removed}
|
|
132
|
+
{@const selected = interactive && selectedKey === row.key}
|
|
133
|
+
<div class="row-wrap" class:item={interactive} class:selected>
|
|
134
|
+
<svelte:element
|
|
135
|
+
this={interactive ? "button" : "div"}
|
|
136
|
+
type={interactive ? "button" : undefined}
|
|
137
|
+
class="tr"
|
|
138
|
+
class:row={interactive}
|
|
139
|
+
class:removed={row.removed}
|
|
140
|
+
role={roles ? "row" : undefined}
|
|
141
|
+
aria-label={roles ? undefined : row.label}
|
|
142
|
+
aria-expanded={interactive ? selected : undefined}
|
|
143
|
+
on:click={() => interactive && dispatch("select", row)}
|
|
144
|
+
>
|
|
145
|
+
<span class="token" role={roles ? "cell" : undefined}>
|
|
146
|
+
<span class="name" class:is-new={row.tone === "new"}>{row.name}</span>
|
|
147
|
+
<!-- Tooltips, not titles: a plugin's frame may not show a title. -->
|
|
148
|
+
{#each shown(row, maxBadges) as badge, i (i)}
|
|
149
|
+
{#if badge.title}
|
|
150
|
+
<Tooltip label={badge.title} direction="Top">
|
|
151
|
+
<Badge variant={badge.variant ?? "default"} text={badge.text} />
|
|
152
|
+
</Tooltip>
|
|
153
|
+
{:else}
|
|
154
|
+
<Badge variant={badge.variant ?? "default"} text={badge.text} />
|
|
155
|
+
{/if}
|
|
156
|
+
{/each}
|
|
157
|
+
{#if hidden(row, maxBadges).length}
|
|
158
|
+
<Tooltip
|
|
159
|
+
label={hidden(row, maxBadges)
|
|
160
|
+
.map((b) => b.text)
|
|
161
|
+
.join(", ")}
|
|
162
|
+
direction="Top"
|
|
163
|
+
>
|
|
164
|
+
<Badge text="+{hidden(row, maxBadges).length}" />
|
|
165
|
+
</Tooltip>
|
|
166
|
+
{/if}
|
|
167
|
+
</span>
|
|
168
|
+
{#each row.cells as cell, c (c)}
|
|
169
|
+
{@const plain = cell.plain || cell.tone === "muted"}
|
|
170
|
+
<span
|
|
171
|
+
class="num"
|
|
172
|
+
class:end={cols[c]?.align === "end"}
|
|
173
|
+
class:active={c === active}
|
|
174
|
+
class:plain
|
|
175
|
+
class:muted={cell.tone === "muted"}
|
|
176
|
+
class:text-new={plain && cell.tone === "new"}
|
|
177
|
+
class:text-changed={plain && cell.tone === "changed"}
|
|
178
|
+
class:text-danger={plain && cell.tone === "danger"}
|
|
179
|
+
role={roles ? "cell" : undefined}
|
|
180
|
+
title={[cell.alias, cell.title].filter(Boolean).join(", ") || null}
|
|
181
|
+
>
|
|
182
|
+
{#if plain}
|
|
183
|
+
{cell.text}
|
|
184
|
+
{:else if cell.alias && !row.removed}
|
|
185
|
+
<!-- No label, so no title of its own over the cell's. -->
|
|
186
|
+
<VariablePill
|
|
187
|
+
label={null}
|
|
188
|
+
class={cell.tone ? `tone-${cell.tone}` : ""}
|
|
189
|
+
>{cell.text}</VariablePill
|
|
190
|
+
>
|
|
191
|
+
{:else}
|
|
192
|
+
<Badge
|
|
193
|
+
variant={row.removed
|
|
194
|
+
? "danger"
|
|
195
|
+
: (TONES[cell.tone] ?? "default")}
|
|
196
|
+
text={String(cell.text)}
|
|
197
|
+
/>
|
|
198
|
+
{/if}
|
|
199
|
+
</span>
|
|
200
|
+
{/each}
|
|
201
|
+
</svelte:element>
|
|
202
|
+
<span class="action"><slot name="action" {row} /></span>
|
|
203
|
+
</div>
|
|
204
|
+
{#if selected}
|
|
205
|
+
<div class="editor"><slot name="editor" {row} /></div>
|
|
206
|
+
{/if}
|
|
207
|
+
{/each}
|
|
208
|
+
</div>
|
|
209
|
+
<slot name="note" />
|
|
210
|
+
|
|
211
|
+
<style>
|
|
212
|
+
/* Run through the container's padding. Rows keep their own 8px inside,
|
|
213
|
+
so the name lines up with the container's content; the action ends 8px
|
|
214
|
+
from the edge, in line with a Header's icons. */
|
|
215
|
+
.table {
|
|
216
|
+
display: flex;
|
|
217
|
+
flex-direction: column;
|
|
218
|
+
margin-inline: calc(-1 * var(--inset, 0px));
|
|
219
|
+
font-family: var(--font-stack);
|
|
220
|
+
font-size: var(--body-medium-font-size);
|
|
221
|
+
font-weight: var(--body-medium-font-weight);
|
|
222
|
+
line-height: var(--body-medium-line-height);
|
|
223
|
+
letter-spacing: var(--body-medium-letter-spacing);
|
|
224
|
+
}
|
|
225
|
+
.tr {
|
|
226
|
+
display: grid;
|
|
227
|
+
grid-template-columns: minmax(0, 1fr) var(--tracks,);
|
|
228
|
+
gap: 4px;
|
|
229
|
+
align-items: center;
|
|
230
|
+
padding: var(--size-xxxsmall) var(--size-xxsmall);
|
|
231
|
+
border: none;
|
|
232
|
+
background: none;
|
|
233
|
+
color: var(--figma-color-text);
|
|
234
|
+
font: inherit;
|
|
235
|
+
letter-spacing: inherit;
|
|
236
|
+
text-align: left;
|
|
237
|
+
}
|
|
238
|
+
/* A row and its action side by side; the button can't sit in the row,
|
|
239
|
+
which is a button itself. */
|
|
240
|
+
/* 32px rows, header included, their rule counted in. */
|
|
241
|
+
.row-wrap {
|
|
242
|
+
box-sizing: border-box;
|
|
243
|
+
display: grid;
|
|
244
|
+
grid-template-columns: minmax(0, 1fr) var(--size-small);
|
|
245
|
+
column-gap: var(--size-xxxsmall);
|
|
246
|
+
align-items: center;
|
|
247
|
+
min-height: var(--size-medium);
|
|
248
|
+
padding-inline: max(0px, calc(var(--inset, 0px) - var(--size-xxsmall)));
|
|
249
|
+
border-bottom: 1px solid var(--figma-color-border);
|
|
250
|
+
}
|
|
251
|
+
.editor {
|
|
252
|
+
padding-inline: max(0px, calc(var(--inset, 0px) - var(--size-xxsmall)));
|
|
253
|
+
border-bottom: 1px solid var(--figma-color-border);
|
|
254
|
+
}
|
|
255
|
+
.action {
|
|
256
|
+
display: flex;
|
|
257
|
+
}
|
|
258
|
+
/* Hover and the open row share one gray, the action included. */
|
|
259
|
+
.row {
|
|
260
|
+
width: 100%;
|
|
261
|
+
cursor: pointer;
|
|
262
|
+
}
|
|
263
|
+
.item:hover,
|
|
264
|
+
.item.selected {
|
|
265
|
+
background: var(--figma-color-bg-secondary);
|
|
266
|
+
}
|
|
267
|
+
.row:focus-visible,
|
|
268
|
+
.col:focus-visible {
|
|
269
|
+
outline: 1px solid var(--figma-color-border-selected);
|
|
270
|
+
outline-offset: -1px;
|
|
271
|
+
}
|
|
272
|
+
/* Body medium strong, as Text's body-medium-strong; the table already
|
|
273
|
+
sets the body medium size, line height and letter spacing. */
|
|
274
|
+
.th {
|
|
275
|
+
color: var(--figma-color-text);
|
|
276
|
+
font-weight: var(--font-weight-strong);
|
|
277
|
+
}
|
|
278
|
+
.token {
|
|
279
|
+
display: flex;
|
|
280
|
+
align-items: center;
|
|
281
|
+
gap: 6px;
|
|
282
|
+
min-width: 0;
|
|
283
|
+
overflow: hidden;
|
|
284
|
+
white-space: nowrap;
|
|
285
|
+
text-overflow: ellipsis;
|
|
286
|
+
}
|
|
287
|
+
/* Padded and rounded, so the marked column's highlight is a pill inside
|
|
288
|
+
the row; the row keeps its height. */
|
|
289
|
+
.num {
|
|
290
|
+
padding: 3px var(--size-xxxsmall);
|
|
291
|
+
border-radius: var(--border-radius-medium);
|
|
292
|
+
text-align: left;
|
|
293
|
+
}
|
|
294
|
+
/* Tabular figures line up plain numbers in a column; a badge or chip is
|
|
295
|
+
sized to its own text, where they'd only widen a narrow 1. */
|
|
296
|
+
.num.plain {
|
|
297
|
+
font-variant-numeric: tabular-nums;
|
|
298
|
+
}
|
|
299
|
+
.col {
|
|
300
|
+
border: none;
|
|
301
|
+
background: none;
|
|
302
|
+
color: inherit;
|
|
303
|
+
font: inherit;
|
|
304
|
+
letter-spacing: inherit;
|
|
305
|
+
cursor: pointer;
|
|
306
|
+
}
|
|
307
|
+
.col:hover:not(.active) {
|
|
308
|
+
background: var(--figma-color-bg-hover);
|
|
309
|
+
}
|
|
310
|
+
.active {
|
|
311
|
+
background: var(--figma-color-bg-selected-secondary);
|
|
312
|
+
}
|
|
313
|
+
.is-new {
|
|
314
|
+
color: var(--figma-color-text-success);
|
|
315
|
+
}
|
|
316
|
+
.removed {
|
|
317
|
+
color: var(--figma-color-text-danger);
|
|
318
|
+
}
|
|
319
|
+
/* A chip has no colored variants; its text takes the badge's color. */
|
|
320
|
+
.num :global(.variable-pill.tone-new) {
|
|
321
|
+
color: var(--figma-color-text-success);
|
|
322
|
+
}
|
|
323
|
+
.num :global(.variable-pill.tone-changed) {
|
|
324
|
+
color: var(--figma-color-text-warning);
|
|
325
|
+
}
|
|
326
|
+
.muted {
|
|
327
|
+
color: var(--figma-color-text-secondary);
|
|
328
|
+
}
|
|
329
|
+
.text-new {
|
|
330
|
+
color: var(--figma-color-text-success);
|
|
331
|
+
}
|
|
332
|
+
.text-changed {
|
|
333
|
+
color: var(--figma-color-text-warning);
|
|
334
|
+
}
|
|
335
|
+
.text-danger {
|
|
336
|
+
color: var(--figma-color-text-danger);
|
|
337
|
+
}
|
|
338
|
+
/* Columns set to end, for numbers, read from the right. */
|
|
339
|
+
.num.end {
|
|
340
|
+
text-align: right;
|
|
341
|
+
}
|
|
342
|
+
/* Read-only rows have no action; the name runs to the edge. */
|
|
343
|
+
.static .row-wrap {
|
|
344
|
+
grid-template-columns: minmax(0, 1fr);
|
|
345
|
+
}
|
|
346
|
+
.static .action {
|
|
347
|
+
display: none;
|
|
348
|
+
}
|
|
349
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
<!-- Fields side by side in equal columns. -->
|
|
2
|
+
<script>
|
|
3
|
+
export let columns = 2;
|
|
4
|
+
</script>
|
|
5
|
+
|
|
6
|
+
<div class="grid" style:--columns={columns}>
|
|
7
|
+
<slot />
|
|
8
|
+
</div>
|
|
9
|
+
|
|
10
|
+
<style>
|
|
11
|
+
.grid {
|
|
12
|
+
display: grid;
|
|
13
|
+
/* minmax(0, 1fr), not 1fr: a bare 1fr track will not shrink below its
|
|
14
|
+
content, and a field is wider than this panel. */
|
|
15
|
+
grid-template-columns: repeat(var(--columns), minmax(0, 1fr));
|
|
16
|
+
gap: var(--size-xxsmall);
|
|
17
|
+
}
|
|
18
|
+
</style>
|
|
@@ -98,6 +98,16 @@
|
|
|
98
98
|
justify-content: end;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/* A kit Text at the footer's edge sits 16px in: a button's own padding
|
|
102
|
+
insets its label, so buttons sit 8px in. */
|
|
103
|
+
.footer--split .footer__left > :global(.text:first-child) {
|
|
104
|
+
margin-left: var(--size-xxsmall);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.footer__right > :global(.text:last-child) {
|
|
108
|
+
margin-right: var(--size-xxsmall);
|
|
109
|
+
}
|
|
110
|
+
|
|
101
111
|
.footer--full {
|
|
102
112
|
display: flex;
|
|
103
113
|
width: 100%;
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
|
|
10
10
|
/** Remove bottom border */
|
|
11
11
|
export let noBorder = false;
|
|
12
|
+
|
|
13
|
+
/** Heading level of the title: 1 for the plugin's own header, 2 or 3 for a bar inside a panel or modal */
|
|
14
|
+
export let level = 1;
|
|
12
15
|
</script>
|
|
13
16
|
|
|
14
17
|
<header
|
|
@@ -19,7 +22,9 @@
|
|
|
19
22
|
<div class="header__left">
|
|
20
23
|
<slot name="left" />
|
|
21
24
|
{#if title}
|
|
22
|
-
<
|
|
25
|
+
<svelte:element this={`h${level}`} class="header__title"
|
|
26
|
+
>{title}</svelte:element
|
|
27
|
+
>
|
|
23
28
|
{/if}
|
|
24
29
|
</div>
|
|
25
30
|
<div class="header__center">
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
A ladder's sizes as neutral badges: outlined where the size is used,
|
|
3
|
+
disabled (the archived badge) where it isn't. Each badge's title is the
|
|
4
|
+
caller's, so each plugin words its own.
|
|
5
|
+
-->
|
|
6
|
+
<script>
|
|
7
|
+
import { Badge, Tooltip } from "figma-ui3-kit-svelte";
|
|
8
|
+
|
|
9
|
+
/** @type {{ value: number, used: boolean, title: string }[]} */
|
|
10
|
+
export let badges = [];
|
|
11
|
+
/** Names the group, e.g. "Ladder sizes". */
|
|
12
|
+
export let ariaLabel;
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="ladder" role="group" aria-label={ariaLabel}>
|
|
16
|
+
<!-- By index: an imported ladder can repeat a value. Tooltips, not
|
|
17
|
+
titles: a plugin's frame may not show a title. -->
|
|
18
|
+
{#each badges as badge, i (i)}
|
|
19
|
+
<Tooltip label={badge.title} direction="Top">
|
|
20
|
+
<Badge
|
|
21
|
+
variant={badge.used ? "default" : "archived"}
|
|
22
|
+
ariaLabel="{badge.value}px, {badge.used ? 'used' : 'unused'}"
|
|
23
|
+
>{badge.value}</Badge
|
|
24
|
+
>
|
|
25
|
+
</Tooltip>
|
|
26
|
+
{/each}
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
.ladder {
|
|
31
|
+
display: flex;
|
|
32
|
+
flex-wrap: wrap;
|
|
33
|
+
gap: 4px;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
* on:menuSelect={handleMenuAction}
|
|
20
20
|
* >
|
|
21
21
|
* <span>Additional info</span>
|
|
22
|
+
* <Button slot="actions" variant="secondary" on:click={fill}>Fill</Button>
|
|
22
23
|
* </ListItem>
|
|
23
24
|
*/
|
|
24
25
|
|
|
@@ -70,33 +71,41 @@
|
|
|
70
71
|
</script>
|
|
71
72
|
|
|
72
73
|
<div class="list-item-wrapper {className}">
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
<div class="list-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
74
|
+
<!-- The actions sit in the card beside the clickable area, not inside it:
|
|
75
|
+
a button can't be nested in another button -->
|
|
76
|
+
<div class="list-item" class:active>
|
|
77
|
+
<div
|
|
78
|
+
class="list-item__main"
|
|
79
|
+
on:click={handleClick}
|
|
80
|
+
on:keydown={(e) => {
|
|
81
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
82
|
+
e.preventDefault();
|
|
83
|
+
handleClick();
|
|
84
|
+
}
|
|
85
|
+
}}
|
|
86
|
+
role="button"
|
|
87
|
+
tabindex="0"
|
|
88
|
+
aria-pressed={active}
|
|
89
|
+
>
|
|
90
|
+
<div class="list-item__content">
|
|
91
|
+
<div class="list-item__title">{title}</div>
|
|
92
|
+
{#if $$slots.default}
|
|
93
|
+
<div class="list-item__meta">
|
|
94
|
+
<slot />
|
|
95
|
+
</div>
|
|
96
|
+
{/if}
|
|
97
|
+
{#if hasBadge && $$slots.badge}
|
|
98
|
+
<div class="list-item__badge">
|
|
99
|
+
<slot name="badge" />
|
|
100
|
+
</div>
|
|
101
|
+
{/if}
|
|
102
|
+
</div>
|
|
99
103
|
</div>
|
|
104
|
+
{#if $$slots.actions}
|
|
105
|
+
<div class="list-item__actions">
|
|
106
|
+
<slot name="actions" />
|
|
107
|
+
</div>
|
|
108
|
+
{/if}
|
|
100
109
|
</div>
|
|
101
110
|
|
|
102
111
|
{#if menuItems.length > 0}
|
|
@@ -128,11 +137,9 @@
|
|
|
128
137
|
flex: 1;
|
|
129
138
|
display: flex;
|
|
130
139
|
align-items: center;
|
|
131
|
-
padding: var(--size-xxsmall);
|
|
132
140
|
background: var(--figma-color-bg-secondary);
|
|
133
141
|
border: 1px solid transparent;
|
|
134
142
|
border-radius: var(--border-radius-medium);
|
|
135
|
-
cursor: pointer;
|
|
136
143
|
transition: border-color 0.15s ease;
|
|
137
144
|
min-width: 0;
|
|
138
145
|
}
|
|
@@ -146,6 +153,24 @@
|
|
|
146
153
|
background: var(--figma-color-bg-brand-tertiary);
|
|
147
154
|
}
|
|
148
155
|
|
|
156
|
+
.list-item__main {
|
|
157
|
+
flex: 1;
|
|
158
|
+
display: flex;
|
|
159
|
+
align-items: center;
|
|
160
|
+
padding: var(--size-xxsmall);
|
|
161
|
+
border-radius: inherit;
|
|
162
|
+
cursor: pointer;
|
|
163
|
+
min-width: 0;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* The main area's own padding spaces them from the text */
|
|
167
|
+
.list-item__actions {
|
|
168
|
+
display: flex;
|
|
169
|
+
align-items: center;
|
|
170
|
+
gap: var(--size-xxxsmall);
|
|
171
|
+
padding-right: var(--size-xxsmall);
|
|
172
|
+
}
|
|
173
|
+
|
|
149
174
|
.list-item__content {
|
|
150
175
|
flex: 1;
|
|
151
176
|
display: flex;
|