@abreen/tada 1.16.9 → 1.16.11
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/build/custom-fonts.ts +3 -3
- package/build/generate-fonts.ts +1 -1
- package/build/material-symbols.ts +0 -10
- package/package.json +1 -1
- package/src/appearance-picker/index.ts +43 -37
- package/src/appearance-picker/style.scss +21 -16
- package/src/header/README.md +5 -3
- package/src/header/_base.scss +86 -9
- package/src/search/style.scss +13 -0
- package/templates/_appearance-picker.html +12 -16
- package/templates/_top.html +6 -0
- package/assets/material-symbols/close_24px.svg +0 -1
- package/assets/material-symbols/menu_24px.svg +0 -1
package/build/custom-fonts.ts
CHANGED
|
@@ -18,8 +18,8 @@ export const CUSTOM_FONT_FAMILY_DEFINITIONS = Object.freeze({
|
|
|
18
18
|
}),
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
type CustomFontFamilyKey = keyof typeof CUSTOM_FONT_FAMILY_DEFINITIONS;
|
|
22
|
+
type CustomFontFaceKey = keyof typeof CUSTOM_FONT_FACE_DEFINITIONS;
|
|
23
23
|
|
|
24
24
|
const FACE_KEYS = Object.freeze(
|
|
25
25
|
Object.keys(CUSTOM_FONT_FACE_DEFINITIONS) as CustomFontFaceKey[],
|
|
@@ -32,7 +32,7 @@ interface ParsedFont {
|
|
|
32
32
|
availableFeatures: readonly string[];
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
interface CustomFontValidationOptions {
|
|
36
36
|
fontOverrides: FontOverrides | undefined;
|
|
37
37
|
publicDir: string;
|
|
38
38
|
publicFiles: ReadonlySet<string>;
|
package/build/generate-fonts.ts
CHANGED
|
@@ -68,16 +68,6 @@ export const MATERIAL_SYMBOLS = Object.freeze({
|
|
|
68
68
|
symbol: 'smart_display',
|
|
69
69
|
opticalSize: 24,
|
|
70
70
|
}),
|
|
71
|
-
headerMenu: Object.freeze({
|
|
72
|
-
cssVariable: '--icon-header-menu',
|
|
73
|
-
symbol: 'menu',
|
|
74
|
-
opticalSize: 24,
|
|
75
|
-
}),
|
|
76
|
-
headerClose: Object.freeze({
|
|
77
|
-
cssVariable: '--icon-header-close',
|
|
78
|
-
symbol: 'close',
|
|
79
|
-
opticalSize: 24,
|
|
80
|
-
}),
|
|
81
71
|
} as const);
|
|
82
72
|
|
|
83
73
|
type MaterialSymbol = (typeof MATERIAL_SYMBOLS)[keyof typeof MATERIAL_SYMBOLS];
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
type FontPreference = 'sans' | 'serif';
|
|
2
|
+
type ContrastPreference = 'standard' | 'high';
|
|
3
3
|
|
|
4
4
|
const FONT_STORAGE_KEY = 'fontPreference';
|
|
5
5
|
const CONTRAST_STORAGE_KEY = 'contrastPreference';
|
|
@@ -25,9 +25,7 @@ function getFontPreferenceLoader(
|
|
|
25
25
|
window: Window,
|
|
26
26
|
): FontPreferenceLoader | undefined {
|
|
27
27
|
return (
|
|
28
|
-
window as Window & {
|
|
29
|
-
__tadaFontPreferenceLoader?: FontPreferenceLoader;
|
|
30
|
-
}
|
|
28
|
+
window as Window & { __tadaFontPreferenceLoader?: FontPreferenceLoader }
|
|
31
29
|
).__tadaFontPreferenceLoader;
|
|
32
30
|
}
|
|
33
31
|
|
|
@@ -171,21 +169,19 @@ export function applyContrastPreference(
|
|
|
171
169
|
}
|
|
172
170
|
}
|
|
173
171
|
|
|
174
|
-
function
|
|
172
|
+
function syncSwitch(
|
|
175
173
|
document: Document,
|
|
176
174
|
selector: string,
|
|
177
|
-
|
|
175
|
+
checked: boolean,
|
|
176
|
+
title: string,
|
|
178
177
|
): void {
|
|
179
|
-
document.
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
),
|
|
187
|
-
);
|
|
188
|
-
});
|
|
178
|
+
const control = document.querySelector<HTMLButtonElement>(selector);
|
|
179
|
+
if (!control) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
control.disabled = false;
|
|
183
|
+
control.setAttribute('aria-checked', String(checked));
|
|
184
|
+
control.title = title;
|
|
189
185
|
}
|
|
190
186
|
|
|
191
187
|
function syncAppearance(
|
|
@@ -193,11 +189,19 @@ function syncAppearance(
|
|
|
193
189
|
fontPreference: FontPreference,
|
|
194
190
|
contrastPreference: ContrastPreference,
|
|
195
191
|
): void {
|
|
196
|
-
|
|
197
|
-
syncControls(
|
|
192
|
+
syncSwitch(
|
|
198
193
|
document,
|
|
199
|
-
'[data-
|
|
200
|
-
|
|
194
|
+
'[data-font-preference-switch]',
|
|
195
|
+
fontPreference === 'serif',
|
|
196
|
+
fontPreference === 'serif' ? 'Use sans-serif fonts' : 'Use serif fonts',
|
|
197
|
+
);
|
|
198
|
+
syncSwitch(
|
|
199
|
+
document,
|
|
200
|
+
'[data-contrast-preference-switch]',
|
|
201
|
+
contrastPreference === 'high',
|
|
202
|
+
contrastPreference === 'high'
|
|
203
|
+
? 'Use standard contrast'
|
|
204
|
+
: 'Use high contrast',
|
|
201
205
|
);
|
|
202
206
|
}
|
|
203
207
|
|
|
@@ -238,7 +242,9 @@ export default function mountAppearancePicker(window: Window): () => void {
|
|
|
238
242
|
|
|
239
243
|
const observedRequests = new Set<number>();
|
|
240
244
|
const observeFontRequest = (request: FontPreferenceRequest) => {
|
|
241
|
-
if (observedRequests.has(request.generation))
|
|
245
|
+
if (observedRequests.has(request.generation)) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
242
248
|
observedRequests.add(request.generation);
|
|
243
249
|
request.promise.then(
|
|
244
250
|
() => {
|
|
@@ -266,26 +272,26 @@ export default function mountAppearancePicker(window: Window): () => void {
|
|
|
266
272
|
|
|
267
273
|
const handleClick = (event: Event) => {
|
|
268
274
|
const button = event.currentTarget as HTMLButtonElement;
|
|
269
|
-
const fontValue = button.dataset.fontPreferenceValue;
|
|
270
|
-
const contrastValue = button.dataset.contrastPreferenceValue;
|
|
271
275
|
|
|
272
|
-
if (
|
|
276
|
+
if (button.dataset.fontPreferenceSwitch !== undefined) {
|
|
273
277
|
fontPreference = getAppliedFontPreference(document);
|
|
274
|
-
if (
|
|
278
|
+
if (fontLoader?.pending) {
|
|
275
279
|
fontLoader?.cancel();
|
|
276
280
|
saveFontPreference(storage, fontPreference, defaultFontPreference);
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
applyFontPreference(document, fontPreference);
|
|
281
|
+
syncAppearance(document, fontPreference, contrastPreference);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const nextFontPreference = fontPreference === 'serif' ? 'sans' : 'serif';
|
|
285
|
+
const request = fontLoader?.request(nextFontPreference) ?? null;
|
|
286
|
+
if (request) {
|
|
287
|
+
observeFontRequest(request);
|
|
288
|
+
return;
|
|
286
289
|
}
|
|
287
|
-
|
|
288
|
-
|
|
290
|
+
fontPreference = nextFontPreference;
|
|
291
|
+
saveFontPreference(storage, fontPreference, defaultFontPreference);
|
|
292
|
+
applyFontPreference(document, fontPreference);
|
|
293
|
+
} else if (button.dataset.contrastPreferenceSwitch !== undefined) {
|
|
294
|
+
contrastPreference = contrastPreference === 'high' ? 'standard' : 'high';
|
|
289
295
|
saveContrastPreference(
|
|
290
296
|
storage,
|
|
291
297
|
contrastPreference,
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
width: fit-content;
|
|
22
22
|
padding: var(--preference-picker-padding);
|
|
23
23
|
background: var(--bg2-color);
|
|
24
|
+
border: 0;
|
|
24
25
|
border-radius: calc(var(--border-radius) + 4px);
|
|
25
26
|
}
|
|
26
27
|
|
|
@@ -46,15 +47,15 @@
|
|
|
46
47
|
transform: translateX(calc(100% + var(--preference-picker-gap)));
|
|
47
48
|
}
|
|
48
49
|
|
|
49
|
-
.preference-picker:
|
|
50
|
-
.preference-picker-option[aria-pressed='true']:is(:hover, :focus)
|
|
51
|
-
)::before {
|
|
50
|
+
.preference-picker:is(:hover, :focus)::before {
|
|
52
51
|
background: var(--bg2-color);
|
|
53
52
|
}
|
|
54
53
|
|
|
55
|
-
.preference-picker:
|
|
56
|
-
|
|
57
|
-
|
|
54
|
+
.preference-picker:active:not(:disabled) {
|
|
55
|
+
background: var(--bg2-color) !important;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.preference-picker:active:not(:disabled)::before {
|
|
58
59
|
background: var(--link-active-color);
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -65,9 +66,8 @@
|
|
|
65
66
|
align-items: center;
|
|
66
67
|
justify-content: center;
|
|
67
68
|
width: var(--preference-picker-option-width);
|
|
68
|
-
height:
|
|
69
|
+
height: 1.8rem;
|
|
69
70
|
padding: 0;
|
|
70
|
-
font-size: var(--font-size-smaller);
|
|
71
71
|
color: var(--fg2-color);
|
|
72
72
|
background: transparent;
|
|
73
73
|
border: 0;
|
|
@@ -100,17 +100,22 @@
|
|
|
100
100
|
font-size-adjust: var(--font-size-adjust);
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
:root:not([data-font-preference='serif']) .font-picker-option-sans,
|
|
104
|
+
:root[data-font-preference='serif'] .font-picker-option-serif,
|
|
105
|
+
:root:not([data-contrast-preference='high']) .contrast-picker-option-standard,
|
|
106
|
+
:root[data-contrast-preference='high'] .contrast-picker-option-high {
|
|
104
107
|
color: var(--fg-color);
|
|
105
|
-
background: transparent;
|
|
106
108
|
}
|
|
107
109
|
|
|
108
|
-
.preference-picker
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
.preference-picker:active:not(:disabled) {
|
|
111
|
+
:root:not([data-font-preference='serif']) & .font-picker-option-sans,
|
|
112
|
+
:root[data-font-preference='serif'] & .font-picker-option-serif,
|
|
113
|
+
:root:not([data-contrast-preference='high'])
|
|
114
|
+
&
|
|
115
|
+
.contrast-picker-option-standard,
|
|
116
|
+
:root[data-contrast-preference='high'] & .contrast-picker-option-high {
|
|
117
|
+
color: var(--bg-color);
|
|
118
|
+
}
|
|
114
119
|
}
|
|
115
120
|
|
|
116
121
|
@media print {
|
package/src/header/README.md
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# The `header` component
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
CSS morphs the header's three-stroke inline SVG between menu and close states
|
|
4
|
+
and, where supported, animates its `<details>` content. The component's
|
|
5
|
+
JavaScript adds dismissal behavior for outside clicks, focus leaving the menu,
|
|
6
|
+
and the Escape key.
|
|
5
7
|
|
|
6
8
|
This component doesn't provide any other functionality related to the header.
|
|
7
9
|
If JavaScript is turned off in the web browser, the `<details>` element is still
|
|
8
|
-
functional
|
|
10
|
+
functional and its disclosure content opens immediately.
|
package/src/header/_base.scss
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
:root {
|
|
4
4
|
--header-collapsed-height: 45px;
|
|
5
|
+
--header-menu-icon-size: 24px;
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
header {
|
|
@@ -15,6 +16,7 @@ header {
|
|
|
15
16
|
grid-template-columns: 1fr;
|
|
16
17
|
width: 100%;
|
|
17
18
|
background: var(--bg-color-translucent);
|
|
19
|
+
border-bottom: 1px solid var(--bg2-color);
|
|
18
20
|
backdrop-filter: var(--backdrop-filter);
|
|
19
21
|
view-transition-name: site-header;
|
|
20
22
|
|
|
@@ -35,6 +37,25 @@ header {
|
|
|
35
37
|
background: var(--bg-color);
|
|
36
38
|
}
|
|
37
39
|
|
|
40
|
+
body > .container::before {
|
|
41
|
+
position: fixed;
|
|
42
|
+
inset: 0;
|
|
43
|
+
z-index: 999;
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
content: '';
|
|
46
|
+
background: var(--bg-color-translucent);
|
|
47
|
+
opacity: 0;
|
|
48
|
+
|
|
49
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
50
|
+
transition: opacity 150ms ease-out;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
body:has(header details[open]) > .container::before {
|
|
55
|
+
pointer-events: auto;
|
|
56
|
+
opacity: 1;
|
|
57
|
+
}
|
|
58
|
+
|
|
38
59
|
header > details {
|
|
39
60
|
min-width: 0;
|
|
40
61
|
}
|
|
@@ -75,21 +96,77 @@ header details summary::marker {
|
|
|
75
96
|
content: '';
|
|
76
97
|
}
|
|
77
98
|
|
|
78
|
-
header summary
|
|
79
|
-
flex: 0 0 var(--
|
|
99
|
+
header summary .menu-icon {
|
|
100
|
+
flex: 0 0 var(--header-menu-icon-size);
|
|
80
101
|
order: -1;
|
|
81
|
-
width: var(--
|
|
82
|
-
height: var(--
|
|
102
|
+
width: var(--header-menu-icon-size);
|
|
103
|
+
height: var(--header-menu-icon-size);
|
|
83
104
|
margin-right: 8px;
|
|
84
105
|
margin-left: var(--gap);
|
|
85
|
-
|
|
106
|
+
overflow: visible;
|
|
107
|
+
pointer-events: none;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
header summary .menu-icon-line {
|
|
111
|
+
transform-origin: center;
|
|
112
|
+
transform-box: fill-box;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
116
|
+
header summary .menu-icon-line {
|
|
117
|
+
transition: transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
header summary .menu-icon-line-middle {
|
|
121
|
+
transition:
|
|
122
|
+
opacity 150ms ease-out,
|
|
123
|
+
transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
header details[open] summary .menu-icon-line-top {
|
|
128
|
+
transform: translateY(6px) rotate(45deg);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
header details[open] summary .menu-icon-line-middle {
|
|
132
|
+
opacity: 0;
|
|
133
|
+
transform: scaleX(0.8);
|
|
134
|
+
}
|
|
86
135
|
|
|
87
|
-
|
|
136
|
+
header details[open] summary .menu-icon-line-bottom {
|
|
137
|
+
transform: translateY(-6px) rotate(-45deg);
|
|
88
138
|
}
|
|
89
139
|
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
|
|
140
|
+
@supports selector(details::details-content) and
|
|
141
|
+
(transition-behavior: allow-discrete) {
|
|
142
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
143
|
+
:root.js header > details {
|
|
144
|
+
display: grid;
|
|
145
|
+
grid-template-rows: var(--header-collapsed-height) 0fr;
|
|
146
|
+
grid-template-columns: minmax(0, 1fr);
|
|
147
|
+
transition: grid-template-rows 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
:root.js header > details[open] {
|
|
151
|
+
grid-template-rows: var(--header-collapsed-height) 1fr;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
:root.js header > details::details-content {
|
|
155
|
+
min-block-size: 0;
|
|
156
|
+
overflow: clip;
|
|
157
|
+
opacity: 0;
|
|
158
|
+
transform: translateY(-4px);
|
|
159
|
+
transition:
|
|
160
|
+
content-visibility 200ms allow-discrete,
|
|
161
|
+
opacity 150ms ease-out,
|
|
162
|
+
transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
:root.js header > details[open]::details-content {
|
|
166
|
+
opacity: 1;
|
|
167
|
+
transform: none;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
93
170
|
}
|
|
94
171
|
|
|
95
172
|
header .content {
|
package/src/search/style.scss
CHANGED
|
@@ -14,6 +14,11 @@ header {
|
|
|
14
14
|
.results-container.is-showing .results {
|
|
15
15
|
pointer-events: all;
|
|
16
16
|
opacity: 1;
|
|
17
|
+
transform: none;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&.loading .results {
|
|
21
|
+
transition: none;
|
|
17
22
|
}
|
|
18
23
|
|
|
19
24
|
@media (hover: hover) {
|
|
@@ -37,6 +42,14 @@ header {
|
|
|
37
42
|
border-radius: var(--border-radius);
|
|
38
43
|
box-shadow: var(--box-shadow);
|
|
39
44
|
opacity: 0;
|
|
45
|
+
transform: translateY(-4px) scale(0.98);
|
|
46
|
+
transform-origin: top center;
|
|
47
|
+
|
|
48
|
+
@media (prefers-reduced-motion: no-preference) {
|
|
49
|
+
transition:
|
|
50
|
+
opacity 150ms ease-out,
|
|
51
|
+
transform 200ms cubic-bezier(0.2, 0, 0, 1);
|
|
52
|
+
}
|
|
40
53
|
|
|
41
54
|
@include user-select(none);
|
|
42
55
|
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
<% if (site.features.pickers) { %>
|
|
2
2
|
<div class="appearance-pickers" data-pagefind-ignore>
|
|
3
|
-
<
|
|
4
|
-
<
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<button type="button" class="preference-picker-option contrast-picker-option-high" data-contrast-preference-value="high" aria-label="Use high contrast" aria-pressed="<%= site.defaultContrast === 'high' %>" disabled>
|
|
16
|
-
<span class="material-symbol-icon material-symbol-icon-contrast-high" aria-hidden="true"></span>
|
|
17
|
-
</button>
|
|
18
|
-
</div>
|
|
3
|
+
<button type="button" class="preference-picker font-picker" role="switch" data-font-preference-switch aria-label="Use serif fonts" aria-checked="<%= site.defaultFont === 'serif' %>" title="<%= site.defaultFont === 'serif' ? 'Use sans-serif fonts' : 'Use serif fonts' %>" disabled>
|
|
4
|
+
<span class="preference-picker-option font-picker-option-sans" data-font-preference-value="sans" aria-hidden="true">Aa</span>
|
|
5
|
+
<span class="preference-picker-option font-picker-option-serif" data-font-preference-value="serif" aria-hidden="true">Aa</span>
|
|
6
|
+
</button>
|
|
7
|
+
<button type="button" class="preference-picker contrast-picker" role="switch" data-contrast-preference-switch aria-label="Use high contrast" aria-checked="<%= site.defaultContrast === 'high' %>" title="<%= site.defaultContrast === 'high' ? 'Use standard contrast' : 'Use high contrast' %>" disabled>
|
|
8
|
+
<span class="preference-picker-option contrast-picker-option-standard" data-contrast-preference-value="standard" aria-hidden="true">
|
|
9
|
+
<span class="material-symbol-icon material-symbol-icon-contrast-standard"></span>
|
|
10
|
+
</span>
|
|
11
|
+
<span class="preference-picker-option contrast-picker-option-high" data-contrast-preference-value="high" aria-hidden="true">
|
|
12
|
+
<span class="material-symbol-icon material-symbol-icon-contrast-high"></span>
|
|
13
|
+
</span>
|
|
14
|
+
</button>
|
|
19
15
|
</div>
|
|
20
16
|
<% } %>
|
package/templates/_top.html
CHANGED
|
@@ -208,6 +208,12 @@
|
|
|
208
208
|
</div>
|
|
209
209
|
<details>
|
|
210
210
|
<summary aria-label="Toggle site navigation">
|
|
211
|
+
<svg class="menu-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false"
|
|
212
|
+
fill="none" stroke="currentColor" stroke-width="1.65" stroke-linecap="round">
|
|
213
|
+
<path class="menu-icon-line menu-icon-line-top" d="M3 6h18" />
|
|
214
|
+
<path class="menu-icon-line menu-icon-line-middle" d="M3 12h18" />
|
|
215
|
+
<path class="menu-icon-line menu-icon-line-bottom" d="M3 18h18" />
|
|
216
|
+
</svg>
|
|
211
217
|
<% if (site.symbol) { %><span class="logo" aria-hidden="true"><%= site.symbol %></span><% } %>
|
|
212
218
|
<span class="site-title" aria-hidden="true"><%= site.title %></span>
|
|
213
219
|
</summary>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M252-203.69 205.69-252l227-228-227-230L252-758.31l229 230 227-230L754.31-710l-227 230 227 228L708-203.69l-227-230-229 230Z"/></svg>
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
<svg xmlns="http://www.w3.org/2000/svg" height="24" viewBox="0 -960 960 960" width="24"><path d="M128-251.23v-66h704v66H128ZM128-448v-66h704v66H128Zm0-196.77v-66h704v66H128Z"/></svg>
|