@juspay/svelte-ui-components 2.93.0 → 2.95.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/dist/Table/BuiltinCell.svelte +59 -14
- package/dist/Table/Table.svelte +20 -6
- package/dist/Table/cellData.d.ts +7 -0
- package/dist/Table/cellData.js +53 -8
- package/dist/Table/properties.d.ts +43 -3
- package/dist/assets/sort-ascending.svg +4 -0
- package/dist/assets/sort-default.svg +3 -3
- package/dist/assets/sort-descending.svg +4 -0
- package/package.json +1 -1
|
@@ -305,19 +305,41 @@
|
|
|
305
305
|
{:else if column.type === 'button'}
|
|
306
306
|
{@const buttonData = asButtonCellData(value)}
|
|
307
307
|
{#if buttonData}
|
|
308
|
+
<!-- The icon <img> is always decorative (alt=""): an icon-only button is
|
|
309
|
+
named by the required ariaLabel on the Button, and a text-bearing
|
|
310
|
+
button is named by its visible text — the image never carries the
|
|
311
|
+
accessible name itself. -->
|
|
312
|
+
{#snippet buttonCellIcon()}
|
|
313
|
+
<img class="builtin-button-icon" src={buttonData.iconUrl} alt="" />
|
|
314
|
+
{/snippet}
|
|
308
315
|
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
309
316
|
<span
|
|
310
317
|
class="builtin-interactive"
|
|
318
|
+
class:builtin-icon-button={buttonData.iconUrl && !buttonData.text}
|
|
311
319
|
onclick={stopClickPropagation}
|
|
312
320
|
onkeydown={stopKeydownPropagation}
|
|
313
321
|
>
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
322
|
+
{#if buttonData.iconUrl}
|
|
323
|
+
<Button
|
|
324
|
+
text={buttonData.text}
|
|
325
|
+
icon={buttonCellIcon}
|
|
326
|
+
iconOnly={!buttonData.text}
|
|
327
|
+
ariaLabel={buttonData.ariaLabel}
|
|
328
|
+
disabled={buttonData.disabled ?? false}
|
|
329
|
+
classes={buttonData.classes ?? ''}
|
|
330
|
+
testId={buttonData.testId}
|
|
331
|
+
onclick={() => column.onButtonClick?.(rowIndex, originalIndex)}
|
|
332
|
+
/>
|
|
333
|
+
{:else}
|
|
334
|
+
<Button
|
|
335
|
+
text={buttonData.text}
|
|
336
|
+
ariaLabel={buttonData.ariaLabel}
|
|
337
|
+
disabled={buttonData.disabled ?? false}
|
|
338
|
+
classes={buttonData.classes ?? ''}
|
|
339
|
+
testId={buttonData.testId}
|
|
340
|
+
onclick={() => column.onButtonClick?.(rowIndex, originalIndex)}
|
|
341
|
+
/>
|
|
342
|
+
{/if}
|
|
321
343
|
</span>
|
|
322
344
|
{:else}
|
|
323
345
|
{cellValueToText(value)}
|
|
@@ -333,13 +355,30 @@
|
|
|
333
355
|
>
|
|
334
356
|
{#if actionData.primaryButton}
|
|
335
357
|
{@const primary = actionData.primaryButton}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
358
|
+
{#snippet primaryButtonIcon()}
|
|
359
|
+
<img class="builtin-button-icon" src={primary.iconUrl} alt="" />
|
|
360
|
+
{/snippet}
|
|
361
|
+
{#if primary.iconUrl}
|
|
362
|
+
<Button
|
|
363
|
+
text={primary.text}
|
|
364
|
+
icon={primaryButtonIcon}
|
|
365
|
+
iconOnly={!primary.text}
|
|
366
|
+
ariaLabel={primary.ariaLabel}
|
|
367
|
+
disabled={primary.disabled ?? false}
|
|
368
|
+
classes={primary.classes ?? ''}
|
|
369
|
+
testId={primary.testId}
|
|
370
|
+
onclick={() => column.onPrimaryAction?.(rowIndex, originalIndex)}
|
|
371
|
+
/>
|
|
372
|
+
{:else}
|
|
373
|
+
<Button
|
|
374
|
+
text={primary.text}
|
|
375
|
+
ariaLabel={primary.ariaLabel}
|
|
376
|
+
disabled={primary.disabled ?? false}
|
|
377
|
+
classes={primary.classes ?? ''}
|
|
378
|
+
testId={primary.testId}
|
|
379
|
+
onclick={() => column.onPrimaryAction?.(rowIndex, originalIndex)}
|
|
380
|
+
/>
|
|
381
|
+
{/if}
|
|
343
382
|
{/if}
|
|
344
383
|
{#if actionData.menuItems && actionData.menuItems.length > 0}
|
|
345
384
|
<Menu
|
|
@@ -600,6 +639,12 @@
|
|
|
600
639
|
height: var(--table-cell-input-icon-size, 16px);
|
|
601
640
|
}
|
|
602
641
|
|
|
642
|
+
.builtin-button-icon {
|
|
643
|
+
display: block;
|
|
644
|
+
width: var(--table-cell-icon-size, 16px);
|
|
645
|
+
height: var(--table-cell-icon-size, 16px);
|
|
646
|
+
}
|
|
647
|
+
|
|
603
648
|
.builtin-action-group {
|
|
604
649
|
gap: var(--table-cell-inline-gap, 8px);
|
|
605
650
|
}
|
package/dist/Table/Table.svelte
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import Pagination from '../Pagination/Pagination.svelte';
|
|
11
11
|
import Select from '../Select/Select.svelte';
|
|
12
12
|
import chevronDownSmSvg from '../assets/chevron-down-sm.svg?raw';
|
|
13
|
-
import
|
|
14
|
-
import
|
|
13
|
+
import sortAscendingSvg from '../assets/sort-ascending.svg?raw';
|
|
14
|
+
import sortDescendingSvg from '../assets/sort-descending.svg?raw';
|
|
15
15
|
import sortDefaultSvg from '../assets/sort-default.svg?raw';
|
|
16
16
|
import searchSvg from '../assets/search.svg?raw';
|
|
17
17
|
import closeSvg from '../assets/close.svg?raw';
|
|
@@ -745,7 +745,7 @@
|
|
|
745
745
|
{:else}
|
|
746
746
|
<span class="sort-icon">
|
|
747
747
|
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
748
|
-
{@html
|
|
748
|
+
{@html sortAscendingSvg}
|
|
749
749
|
</span>
|
|
750
750
|
{/if}
|
|
751
751
|
{:else if sortColumn === colIndex && sortDirection === 'desc'}
|
|
@@ -754,7 +754,7 @@
|
|
|
754
754
|
{:else}
|
|
755
755
|
<span class="sort-icon">
|
|
756
756
|
<!-- eslint-disable svelte/no-at-html-tags -->
|
|
757
|
-
{@html
|
|
757
|
+
{@html sortDescendingSvg}
|
|
758
758
|
</span>
|
|
759
759
|
{/if}
|
|
760
760
|
{:else if typeof sortDefaultIcon === 'function'}
|
|
@@ -1324,18 +1324,32 @@
|
|
|
1324
1324
|
align-items: center;
|
|
1325
1325
|
}
|
|
1326
1326
|
|
|
1327
|
+
/* The sorted-direction half paints via currentColor; without an explicit
|
|
1328
|
+
color it inherits the Button's text color (white on the default button
|
|
1329
|
+
theme), which disappears on light headers. Default to the design
|
|
1330
|
+
system's active blue. */
|
|
1331
|
+
.sort-button :global(.sort-icon:not(.sort-icon-idle)) {
|
|
1332
|
+
color: var(--table-sort-active-color, #1b85ff);
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1327
1335
|
.sort-button :global(.sort-icon svg) {
|
|
1328
1336
|
width: var(--table-sort-icon-size, 14px);
|
|
1329
1337
|
height: var(--table-sort-icon-size, 14px);
|
|
1330
1338
|
display: block;
|
|
1331
1339
|
}
|
|
1332
1340
|
|
|
1341
|
+
/* The design system draws every sort state fully opaque — faintness comes
|
|
1342
|
+
from the glyph fills (inactive halves #C7C7C7, active direction
|
|
1343
|
+
currentColor), never from transparency. The opacity vars remain for
|
|
1344
|
+
consumers that prefer a fade but now default to solid; hover steps the
|
|
1345
|
+
inactive halves to the design system's hover gray. */
|
|
1333
1346
|
.sort-button :global(.sort-icon-idle) {
|
|
1334
|
-
opacity: var(--table-sort-idle-opacity,
|
|
1347
|
+
opacity: var(--table-sort-idle-opacity, 1);
|
|
1335
1348
|
}
|
|
1336
1349
|
|
|
1337
1350
|
.sort-button:hover :global(.sort-icon-idle) {
|
|
1338
|
-
opacity: var(--table-sort-idle-hover-opacity,
|
|
1351
|
+
opacity: var(--table-sort-idle-hover-opacity, 1);
|
|
1352
|
+
--table-sort-inactive-color: var(--table-sort-hover-color, #797979);
|
|
1339
1353
|
}
|
|
1340
1354
|
|
|
1341
1355
|
/* ── Empty ──────────────────────────────────────────────────────────────── */
|
package/dist/Table/cellData.d.ts
CHANGED
|
@@ -12,6 +12,13 @@ import type { TableActionGroupCellData, TableAvatarStackCellData, TableButtonCel
|
|
|
12
12
|
export declare const asJsonObject: (value: TableCellValue) => {
|
|
13
13
|
[key: string]: JSONValue;
|
|
14
14
|
} | null;
|
|
15
|
+
/**
|
|
16
|
+
* Accepts an icon URL only when its scheme is safe to place in an `<img src>`
|
|
17
|
+
* — http(s), a `data:image/*` payload, or a scheme-less (relative) path. Cell
|
|
18
|
+
* data is consumer-supplied JSON, so `javascript:`/`vbscript:`/non-image
|
|
19
|
+
* `data:` URIs must never reach the DOM.
|
|
20
|
+
*/
|
|
21
|
+
export declare const asSafeIconUrl: (value: TableCellValue) => string | null;
|
|
15
22
|
export declare const asTagCellData: (value: TableCellValue) => TableTagCellData | null;
|
|
16
23
|
export declare const asTagArrayItems: (value: TableCellValue) => TableTagArrayCellItem[] | null;
|
|
17
24
|
export declare const asAvatarStackData: (value: TableCellValue) => TableAvatarStackCellData | null;
|
package/dist/Table/cellData.js
CHANGED
|
@@ -11,6 +11,32 @@ export const asJsonObject = (value) => {
|
|
|
11
11
|
}
|
|
12
12
|
return null;
|
|
13
13
|
};
|
|
14
|
+
/**
|
|
15
|
+
* Accepts an icon URL only when its scheme is safe to place in an `<img src>`
|
|
16
|
+
* — http(s), a `data:image/*` payload, or a scheme-less (relative) path. Cell
|
|
17
|
+
* data is consumer-supplied JSON, so `javascript:`/`vbscript:`/non-image
|
|
18
|
+
* `data:` URIs must never reach the DOM.
|
|
19
|
+
*/
|
|
20
|
+
export const asSafeIconUrl = (value) => {
|
|
21
|
+
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
const candidate = value.trim();
|
|
25
|
+
const schemeMatch = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(candidate);
|
|
26
|
+
if (schemeMatch === null) {
|
|
27
|
+
return candidate;
|
|
28
|
+
}
|
|
29
|
+
const scheme = schemeMatch[1].toLowerCase();
|
|
30
|
+
if (scheme === 'http' || scheme === 'https') {
|
|
31
|
+
return candidate;
|
|
32
|
+
}
|
|
33
|
+
// Image data URIs only, from an explicit subtype allowlist — a data: URI
|
|
34
|
+
// continues with ';' (parameters like base64) or ',' (payload).
|
|
35
|
+
if (scheme === 'data' && /^data:image\/(svg\+xml|png|jpeg|jpg|gif|webp)[;,]/i.test(candidate)) {
|
|
36
|
+
return candidate;
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
};
|
|
14
40
|
export const asTagCellData = (value) => {
|
|
15
41
|
const record = asJsonObject(value);
|
|
16
42
|
if (record === null || typeof record.text !== 'string') {
|
|
@@ -160,8 +186,9 @@ export const asInputCellData = (value) => {
|
|
|
160
186
|
if (typeof record.ariaLabel === 'string') {
|
|
161
187
|
inputData.ariaLabel = record.ariaLabel;
|
|
162
188
|
}
|
|
163
|
-
|
|
164
|
-
|
|
189
|
+
const inputIconUrl = asSafeIconUrl(record.iconUrl ?? null);
|
|
190
|
+
if (inputIconUrl !== null) {
|
|
191
|
+
inputData.iconUrl = inputIconUrl;
|
|
165
192
|
}
|
|
166
193
|
if (typeof record.dataType === 'string') {
|
|
167
194
|
inputData.dataType = record.dataType;
|
|
@@ -192,20 +219,38 @@ export const asInputDataType = (value) => {
|
|
|
192
219
|
};
|
|
193
220
|
export const asButtonCellData = (value) => {
|
|
194
221
|
const record = asJsonObject(value);
|
|
195
|
-
if (record === null
|
|
222
|
+
if (record === null) {
|
|
196
223
|
return null;
|
|
197
224
|
}
|
|
198
|
-
const
|
|
225
|
+
const buttonIconUrl = asSafeIconUrl(record.iconUrl ?? null);
|
|
226
|
+
const buttonAriaLabel = typeof record.ariaLabel === 'string' ? record.ariaLabel : null;
|
|
227
|
+
const common = {};
|
|
199
228
|
if (typeof record.disabled === 'boolean') {
|
|
200
|
-
|
|
229
|
+
common.disabled = record.disabled;
|
|
201
230
|
}
|
|
202
231
|
if (typeof record.classes === 'string') {
|
|
203
|
-
|
|
232
|
+
common.classes = record.classes;
|
|
204
233
|
}
|
|
205
234
|
if (typeof record.testId === 'string') {
|
|
206
|
-
|
|
235
|
+
common.testId = record.testId;
|
|
236
|
+
}
|
|
237
|
+
if (typeof record.text === 'string') {
|
|
238
|
+
const textButton = { text: record.text, ...common };
|
|
239
|
+
if (buttonIconUrl !== null) {
|
|
240
|
+
textButton.iconUrl = buttonIconUrl;
|
|
241
|
+
}
|
|
242
|
+
if (buttonAriaLabel !== null) {
|
|
243
|
+
textButton.ariaLabel = buttonAriaLabel;
|
|
244
|
+
}
|
|
245
|
+
return textButton;
|
|
207
246
|
}
|
|
208
|
-
|
|
247
|
+
// Icon-only buttons require BOTH a safe icon and an accessible name — a
|
|
248
|
+
// button with no visible text and no aria-label is a WCAG 4.1.2 failure,
|
|
249
|
+
// so such data fails narrowing and the cell falls back to plain text.
|
|
250
|
+
if (buttonIconUrl !== null && buttonAriaLabel !== null) {
|
|
251
|
+
return { iconUrl: buttonIconUrl, ariaLabel: buttonAriaLabel, ...common };
|
|
252
|
+
}
|
|
253
|
+
return null;
|
|
209
254
|
};
|
|
210
255
|
const asMenuItemsData = (rawItems) => {
|
|
211
256
|
if (!Array.isArray(rawItems)) {
|
|
@@ -116,6 +116,8 @@ export type TableInputCellData = {
|
|
|
116
116
|
/**
|
|
117
117
|
* URL of a passive leading icon rendered inside the input field (e.g. a
|
|
118
118
|
* currency glyph). Sized via `--table-cell-input-icon-size` (default 16px).
|
|
119
|
+
* Scheme-validated at narrowing time: only http(s), `data:image/*`, and
|
|
120
|
+
* relative URLs are accepted; anything else is dropped.
|
|
119
121
|
*/
|
|
120
122
|
iconUrl?: string;
|
|
121
123
|
/** Input dataType forwarded to the rendered Input ('text' | 'tel' | 'number' | …). */
|
|
@@ -128,14 +130,52 @@ export type TableInputCellData = {
|
|
|
128
130
|
/** Inline error message shown when the validation pattern rejects the value. */
|
|
129
131
|
onErrorMessage?: string;
|
|
130
132
|
};
|
|
131
|
-
/**
|
|
132
|
-
export type
|
|
133
|
-
text: string;
|
|
133
|
+
/** Options shared by both button cell variants. */
|
|
134
|
+
export type TableButtonCellCommonData = {
|
|
134
135
|
disabled?: boolean;
|
|
135
136
|
/** CSS classes forwarded to the Button (the consumer owns variant mapping). */
|
|
136
137
|
classes?: string;
|
|
137
138
|
testId?: string;
|
|
138
139
|
};
|
|
140
|
+
/**
|
|
141
|
+
* Text-bearing button cell — `text` stays required exactly as before this
|
|
142
|
+
* type became a union, so existing consumers are unaffected. An optional
|
|
143
|
+
* `iconUrl` renders a leading icon; `ariaLabel` may override the accessible
|
|
144
|
+
* name (the visible text already names the button).
|
|
145
|
+
*/
|
|
146
|
+
export type TableTextButtonCellData = TableButtonCellCommonData & {
|
|
147
|
+
text: string;
|
|
148
|
+
/**
|
|
149
|
+
* URL of a leading icon rendered next to the text. Sized via
|
|
150
|
+
* `--table-cell-icon-size` (default 16px). Scheme-validated at narrowing
|
|
151
|
+
* time: only http(s), `data:image/*`, and relative URLs are accepted;
|
|
152
|
+
* anything else is dropped.
|
|
153
|
+
*/
|
|
154
|
+
iconUrl?: string;
|
|
155
|
+
ariaLabel?: string;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Icon-only button cell — renders as a bare ghost control. `ariaLabel` is
|
|
159
|
+
* required (the button has no visible text, so it MUST carry an accessible
|
|
160
|
+
* name); the decoder enforces the same rule at runtime for untyped data.
|
|
161
|
+
*/
|
|
162
|
+
export type TableIconOnlyButtonCellData = TableButtonCellCommonData & {
|
|
163
|
+
text?: never;
|
|
164
|
+
/**
|
|
165
|
+
* URL of the button's icon. Sized via `--table-cell-icon-size` (default
|
|
166
|
+
* 16px). Scheme-validated at narrowing time: only http(s), `data:image/*`,
|
|
167
|
+
* and relative URLs are accepted; anything else is dropped (the cell then
|
|
168
|
+
* fails narrowing and falls back to plain text of the raw value).
|
|
169
|
+
*/
|
|
170
|
+
iconUrl: string;
|
|
171
|
+
ariaLabel: string;
|
|
172
|
+
};
|
|
173
|
+
/**
|
|
174
|
+
* Cell shape for `type: 'button'` — the handler lives on the column. A
|
|
175
|
+
* discriminated union: text-bearing buttons keep `text` required (unchanged
|
|
176
|
+
* public contract), icon-only buttons require `iconUrl` + `ariaLabel`.
|
|
177
|
+
*/
|
|
178
|
+
export type TableButtonCellData = TableTextButtonCellData | TableIconOnlyButtonCellData;
|
|
139
179
|
/** One overflow-menu entry for action-group / popup-menu cells (JSON-safe). */
|
|
140
180
|
export type TableMenuItemData = {
|
|
141
181
|
id: string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.64645 1.64645C5.84171 1.45118 6.15829 1.45118 6.35355 1.64645L8.85355 4.14645C9.04882 4.34171 9.04882 4.65829 8.85355 4.85355C8.65829 5.04882 8.34171 5.04882 8.14645 4.85355L6 2.70711L3.85355 4.85355C3.65829 5.04882 3.34171 5.04882 3.14645 4.85355C2.95118 4.65829 2.95118 4.34171 3.14645 4.14645L5.64645 1.64645Z" fill="currentColor"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14645 7.14645C3.34171 6.95118 3.65829 6.95118 3.85355 7.14645L6 9.29289L8.14645 7.14645C8.34171 6.95118 8.65829 6.95118 8.85355 7.14645C9.04882 7.34171 9.04882 7.65829 8.85355 7.85355L6.35355 10.3536C6.15829 10.5488 5.84171 10.5488 5.64645 10.3536L3.14645 7.85355C2.95118 7.65829 2.95118 7.34171 3.14645 7.14645Z" fill="var(--table-sort-inactive-color, #C7C7C7)"/>
|
|
4
|
+
</svg>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<svg width="
|
|
2
|
-
<
|
|
3
|
-
<
|
|
1
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.64645 1.64645C5.84171 1.45118 6.15829 1.45118 6.35355 1.64645L8.85355 4.14645C9.04882 4.34171 9.04882 4.65829 8.85355 4.85355C8.65829 5.04882 8.34171 5.04882 8.14645 4.85355L6 2.70711L3.85355 4.85355C3.65829 5.04882 3.34171 5.04882 3.14645 4.85355C2.95118 4.65829 2.95118 4.34171 3.14645 4.14645L5.64645 1.64645Z" fill="var(--table-sort-inactive-color, #C7C7C7)"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14645 7.14645C3.34171 6.95118 3.65829 6.95118 3.85355 7.14645L6 9.29289L8.14645 7.14645C8.34171 6.95118 8.65829 6.95118 8.85355 7.14645C9.04882 7.34171 9.04882 7.65829 8.85355 7.85355L6.35355 10.3536C6.15829 10.5488 5.84171 10.5488 5.64645 10.3536L3.14645 7.85355C2.95118 7.65829 2.95118 7.34171 3.14645 7.14645Z" fill="var(--table-sort-inactive-color, #C7C7C7)"/>
|
|
4
4
|
</svg>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.64645 1.64645C5.84171 1.45118 6.15829 1.45118 6.35355 1.64645L8.85355 4.14645C9.04882 4.34171 9.04882 4.65829 8.85355 4.85355C8.65829 5.04882 8.34171 5.04882 8.14645 4.85355L6 2.70711L3.85355 4.85355C3.65829 5.04882 3.34171 5.04882 3.14645 4.85355C2.95118 4.65829 2.95118 4.34171 3.14645 4.14645L5.64645 1.64645Z" fill="var(--table-sort-inactive-color, #C7C7C7)"/>
|
|
3
|
+
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.14645 7.14645C3.34171 6.95118 3.65829 6.95118 3.85355 7.14645L6 9.29289L8.14645 7.14645C8.34171 6.95118 8.65829 6.95118 8.85355 7.14645C9.04882 7.34171 9.04882 7.65829 8.85355 7.85355L6.35355 10.3536C6.15829 10.5488 5.84171 10.5488 5.64645 10.3536L3.14645 7.85355C2.95118 7.65829 2.95118 7.34171 3.14645 7.14645Z" fill="currentColor"/>
|
|
4
|
+
</svg>
|