@celestia-island/hikari 0.55.122 → 0.55.123
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/package.json
CHANGED
|
@@ -168,6 +168,135 @@
|
|
|
168
168
|
width: 100%;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
/* ---- Responsive label collapse ---- */
|
|
172
|
+
/* A `collapseLabel` button carries icon + text at wide viewports and
|
|
173
|
+
collapses to the SAME icon-only square below the chosen breakpoint
|
|
174
|
+
(chest 2026-10-05 direction: the ad-hoc `hidden sm:inline` label-span
|
|
175
|
+
pattern left the button as a padded icon rectangle on phones — the
|
|
176
|
+
exact squat shape the icon-only contract exists to prevent). The
|
|
177
|
+
geometry mirrors the icon-only ladder above per size; the breakpoint
|
|
178
|
+
px values mirror the runtime useBreakpoint scale (sm 640 / md 768 /
|
|
179
|
+
lg 1024 / xl 1280) — a media query cannot read the TS constant, keep
|
|
180
|
+
the two tables in sync. The TS side suppresses the collapse class
|
|
181
|
+
together with `block` (a full-width button has nothing to collapse
|
|
182
|
+
into), so no block override is needed here. */
|
|
183
|
+
.hk-btn-label {
|
|
184
|
+
/* Inline-flex so multi-node labels (text + inline marks) stay on one
|
|
185
|
+
row and center against the glyph box. */
|
|
186
|
+
display: inline-flex;
|
|
187
|
+
align-items: center;
|
|
188
|
+
min-width: 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@mixin hk-btn-collapse-geometry($square) {
|
|
192
|
+
.hk-btn-label {
|
|
193
|
+
display: none;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/* The exact icon-only geometry (all-side padding zero, width locked),
|
|
197
|
+
mirrored per size from the ladder above. */
|
|
198
|
+
padding: 0;
|
|
199
|
+
box-sizing: border-box;
|
|
200
|
+
width: $square;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.hk-btn-label-collapse-sm.hk-btn-xs {
|
|
204
|
+
@media (max-width: 639.98px) {
|
|
205
|
+
@include hk-btn-collapse-geometry(1.375rem);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
.hk-btn-label-collapse-sm.hk-btn-sm {
|
|
210
|
+
@media (max-width: 639.98px) {
|
|
211
|
+
@include hk-btn-collapse-geometry(1.75rem);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
.hk-btn-label-collapse-sm.hk-btn-md {
|
|
216
|
+
@media (max-width: 639.98px) {
|
|
217
|
+
@include hk-btn-collapse-geometry(2.5rem);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.hk-btn-label-collapse-sm.hk-btn-lg {
|
|
222
|
+
@media (max-width: 639.98px) {
|
|
223
|
+
@include hk-btn-collapse-geometry(2.75rem);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.hk-btn-label-collapse-md.hk-btn-xs {
|
|
228
|
+
@media (max-width: 767.98px) {
|
|
229
|
+
@include hk-btn-collapse-geometry(1.375rem);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.hk-btn-label-collapse-md.hk-btn-sm {
|
|
234
|
+
@media (max-width: 767.98px) {
|
|
235
|
+
@include hk-btn-collapse-geometry(1.75rem);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.hk-btn-label-collapse-md.hk-btn-md {
|
|
240
|
+
@media (max-width: 767.98px) {
|
|
241
|
+
@include hk-btn-collapse-geometry(2.5rem);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.hk-btn-label-collapse-md.hk-btn-lg {
|
|
246
|
+
@media (max-width: 767.98px) {
|
|
247
|
+
@include hk-btn-collapse-geometry(2.75rem);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.hk-btn-label-collapse-lg.hk-btn-xs {
|
|
252
|
+
@media (max-width: 1023.98px) {
|
|
253
|
+
@include hk-btn-collapse-geometry(1.375rem);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.hk-btn-label-collapse-lg.hk-btn-sm {
|
|
258
|
+
@media (max-width: 1023.98px) {
|
|
259
|
+
@include hk-btn-collapse-geometry(1.75rem);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.hk-btn-label-collapse-lg.hk-btn-md {
|
|
264
|
+
@media (max-width: 1023.98px) {
|
|
265
|
+
@include hk-btn-collapse-geometry(2.5rem);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
.hk-btn-label-collapse-lg.hk-btn-lg {
|
|
270
|
+
@media (max-width: 1023.98px) {
|
|
271
|
+
@include hk-btn-collapse-geometry(2.75rem);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
.hk-btn-label-collapse-xl.hk-btn-xs {
|
|
276
|
+
@media (max-width: 1279.98px) {
|
|
277
|
+
@include hk-btn-collapse-geometry(1.375rem);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
.hk-btn-label-collapse-xl.hk-btn-sm {
|
|
282
|
+
@media (max-width: 1279.98px) {
|
|
283
|
+
@include hk-btn-collapse-geometry(1.75rem);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.hk-btn-label-collapse-xl.hk-btn-md {
|
|
288
|
+
@media (max-width: 1279.98px) {
|
|
289
|
+
@include hk-btn-collapse-geometry(2.5rem);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.hk-btn-label-collapse-xl.hk-btn-lg {
|
|
294
|
+
@media (max-width: 1279.98px) {
|
|
295
|
+
@include hk-btn-collapse-geometry(2.75rem);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
|
|
171
300
|
/* ---- Primary ---- */
|
|
172
301
|
|
|
173
302
|
.hk-btn-primary {
|
|
@@ -255,3 +255,134 @@ describe("HkButton attr fallthrough (inheritAttrs: false)", () => {
|
|
|
255
255
|
expect(cls).toContain("extra-class");
|
|
256
256
|
});
|
|
257
257
|
});
|
|
258
|
+
|
|
259
|
+
describe("HkButton label prop + responsive collapse contract", () => {
|
|
260
|
+
it("renders the label prop in a .hk-btn-label span and keeps the button textual", () => {
|
|
261
|
+
const c = mount(h(HkButton, { icon: "gauge", label: "Test all" }));
|
|
262
|
+
const btn = button(c);
|
|
263
|
+
const label = c.querySelector(".hk-btn-label");
|
|
264
|
+
expect(label).not.toBeNull();
|
|
265
|
+
expect(label!.textContent).toBe("Test all");
|
|
266
|
+
// icon + label = a text button at wide viewports, never icon-only.
|
|
267
|
+
expect(btn.className).not.toContain("hk-btn-icon-only");
|
|
268
|
+
// The label prop is the accessible name unless one is given.
|
|
269
|
+
expect(btn.getAttribute("aria-label")).toBe("Test all");
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("prefers the label prop over the default slot and an explicit ariaLabel over the label", () => {
|
|
273
|
+
const c = mount(h(HkButton, { label: "From prop" }, () => "From slot"));
|
|
274
|
+
expect(c.querySelector(".hk-btn-label")!.textContent).toBe("From prop");
|
|
275
|
+
expect(c.textContent).not.toContain("From slot");
|
|
276
|
+
|
|
277
|
+
const named = mount(h(HkButton, { label: "Visible", ariaLabel: "Custom" }));
|
|
278
|
+
expect(button(named).getAttribute("aria-label")).toBe("Custom");
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("emits the collapse modifier class from collapseLabel", () => {
|
|
282
|
+
const c = mount(h(HkButton, { icon: "gauge", label: "Probe", collapseLabel: "sm" }));
|
|
283
|
+
expect(button(c).className).toContain("hk-btn-label-collapse-sm");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("suppresses the collapse class with block or shortcut (nothing to collapse into)", () => {
|
|
287
|
+
const blocked = mount(
|
|
288
|
+
h(HkButton, { icon: "gauge", label: "Probe", collapseLabel: "sm", block: true }),
|
|
289
|
+
);
|
|
290
|
+
expect(button(blocked).className).not.toContain("hk-btn-label-collapse");
|
|
291
|
+
|
|
292
|
+
const withShortcut = mount(
|
|
293
|
+
h(HkButton, { icon: "gauge", label: "Probe", collapseLabel: "sm", shortcut: "Ctrl+B" }),
|
|
294
|
+
);
|
|
295
|
+
expect(button(withShortcut).className).not.toContain("hk-btn-label-collapse");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("keeps label-prop buttons out of the icon-only square class", () => {
|
|
299
|
+
// The narrow-end square is the SCSS media query's job; the class
|
|
300
|
+
// must not flip at mount time or the wide viewport would square a
|
|
301
|
+
// labeled button.
|
|
302
|
+
const c = mount(h(HkButton, { icon: "gauge", label: "Probe", collapseLabel: "sm" }));
|
|
303
|
+
expect(button(c).className).not.toContain("hk-btn-icon-only");
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// SCSS geometry contract for the responsive collapse (same textual style
|
|
308
|
+
// as the icon-only contract above — happy-dom does not lay out): every
|
|
309
|
+
// collapse breakpoint × size pair must exist, must collapse below the
|
|
310
|
+
// useBreakpoint px scale (sm 640 / md 768 / lg 1024 / xl 1280), and must
|
|
311
|
+
// land on the exact icon-only square width of its size.
|
|
312
|
+
describe("HkButton label-collapse SCSS contract", () => {
|
|
313
|
+
const scss = readFileSync(join(here, "HkButton.scss"), "utf-8");
|
|
314
|
+
|
|
315
|
+
const breakpoints: Array<[string, string]> = [
|
|
316
|
+
["sm", "639.98px"],
|
|
317
|
+
["md", "767.98px"],
|
|
318
|
+
["lg", "1023.98px"],
|
|
319
|
+
["xl", "1279.98px"],
|
|
320
|
+
];
|
|
321
|
+
const squares: Array<[string, string]> = [
|
|
322
|
+
["xs", "1.375rem"],
|
|
323
|
+
["sm", "1.75rem"],
|
|
324
|
+
["md", "2.5rem"],
|
|
325
|
+
["lg", "2.75rem"],
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
it("collapses every breakpoint × size pair to the icon-only square", () => {
|
|
329
|
+
for (const [bp, query] of breakpoints) {
|
|
330
|
+
for (const [size, square] of squares) {
|
|
331
|
+
const rule = scss.match(
|
|
332
|
+
new RegExp(
|
|
333
|
+
`\\.hk-btn-label-collapse-${bp}\\.hk-btn-${size}\\s*{\\s*@media \\(max-width: ${query}\\) {`,
|
|
334
|
+
),
|
|
335
|
+
)?.[0];
|
|
336
|
+
expect(rule, `${bp}×${size} collapse rule must exist`).toBeTruthy();
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
it("hides the label and lands on the exact square inside each media block", () => {
|
|
342
|
+
// The per-pair rules @include one shared mixin; the geometry lives in
|
|
343
|
+
// its definition (the compiled CSS materializes it per media block).
|
|
344
|
+
const start = scss.indexOf("@mixin hk-btn-collapse-geometry($square) {");
|
|
345
|
+
expect(start, "collapse mixin must exist").toBeGreaterThan(-1);
|
|
346
|
+
// Slice to the mixin's closing brace (nested rules make a [^}] scan
|
|
347
|
+
// stop early, so index from the header to the documented tail line).
|
|
348
|
+
const end = scss.indexOf("width: $square;", start);
|
|
349
|
+
const mixin = scss.slice(start, end + "width: $square;".length);
|
|
350
|
+
expect(mixin).toContain(".hk-btn-label");
|
|
351
|
+
expect(mixin).toContain("display: none;");
|
|
352
|
+
expect(mixin).toContain("padding: 0;");
|
|
353
|
+
expect(mixin).toContain("box-sizing: border-box;");
|
|
354
|
+
expect(mixin).toContain("width: $square");
|
|
355
|
+
|
|
356
|
+
const smBlock = scss.match(
|
|
357
|
+
/\.hk-btn-label-collapse-sm\.hk-btn-sm\s*{\s*@media \(max-width: 639\.98px\) {([^}]*)}/,
|
|
358
|
+
)?.[1];
|
|
359
|
+
expect(smBlock, "sm×sm collapse body must exist").toBeTruthy();
|
|
360
|
+
expect(smBlock).toContain("@include hk-btn-collapse-geometry(1.75rem)");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("mirrors the useBreakpoint scale breakpoints in order", () => {
|
|
364
|
+
// Keep the SCSS table honest against the runtime scale document —
|
|
365
|
+
// the px values appear in ascending breakpoint order.
|
|
366
|
+
const queries = [...scss.matchAll(/@media \(max-width: (\d+(?:\.\d+)?px)\)/g)].map(
|
|
367
|
+
(m) => m[1],
|
|
368
|
+
);
|
|
369
|
+
expect(queries).toEqual([
|
|
370
|
+
"639.98px",
|
|
371
|
+
"639.98px",
|
|
372
|
+
"639.98px",
|
|
373
|
+
"639.98px",
|
|
374
|
+
"767.98px",
|
|
375
|
+
"767.98px",
|
|
376
|
+
"767.98px",
|
|
377
|
+
"767.98px",
|
|
378
|
+
"1023.98px",
|
|
379
|
+
"1023.98px",
|
|
380
|
+
"1023.98px",
|
|
381
|
+
"1023.98px",
|
|
382
|
+
"1279.98px",
|
|
383
|
+
"1279.98px",
|
|
384
|
+
"1279.98px",
|
|
385
|
+
"1279.98px",
|
|
386
|
+
]);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
@@ -13,6 +13,14 @@ export type ButtonVariant =
|
|
|
13
13
|
| "outline";
|
|
14
14
|
export type ButtonSize = "xs" | "sm" | "md" | "lg";
|
|
15
15
|
|
|
16
|
+
/** Breakpoints below which a `collapseLabel` button hides its label and
|
|
17
|
+
* collapses to the icon-only square. Mirrors the runtime useBreakpoint
|
|
18
|
+
* scale (sm 640 / md 768 / lg 1024 / xl 1280) — the SCSS side repeats
|
|
19
|
+
* the px values because a media query cannot read a TS constant; keep
|
|
20
|
+
* the two in sync. "xs" is deliberately absent: collapse-at-xs means
|
|
21
|
+
* "always collapsed", which is just the icon prop + label-less button. */
|
|
22
|
+
export type ButtonLabelCollapse = "sm" | "md" | "lg" | "xl";
|
|
23
|
+
|
|
16
24
|
export default defineComponent({
|
|
17
25
|
name: "HkButton",
|
|
18
26
|
inheritAttrs: false,
|
|
@@ -26,6 +34,24 @@ export default defineComponent({
|
|
|
26
34
|
shortcut: { type: String, default: undefined },
|
|
27
35
|
icon: { type: String, default: undefined },
|
|
28
36
|
suffix: { type: String, default: undefined },
|
|
37
|
+
/** Plain-text label, rendered in a `.hk-btn-label` span after the
|
|
38
|
+
* glyph. Pairs with `collapseLabel`: at wide viewports the button
|
|
39
|
+
* reads icon + text; below the chosen breakpoint the label hides
|
|
40
|
+
* and the button collapses to the icon-only square. When set, the
|
|
41
|
+
* default slot is ignored (one label source per button) and the
|
|
42
|
+
* accessible name falls back to this text, so the button keeps its
|
|
43
|
+
* name at breakpoints where the visible label is display:none. */
|
|
44
|
+
label: { type: String, default: undefined },
|
|
45
|
+
/** Breakpoint below which the label hides and the button collapses
|
|
46
|
+
* to the icon-only square (see `ButtonLabelCollapse`). Requires
|
|
47
|
+
* `label`; ignored together with `block` (a full-width button has
|
|
48
|
+
* nothing to collapse into) and with `shortcut` (the shortcut chip
|
|
49
|
+
* is an extra child the square width would visibly clip — the same
|
|
50
|
+
* exclusion the icon-only contract applies). */
|
|
51
|
+
collapseLabel: {
|
|
52
|
+
type: String as PropType<ButtonLabelCollapse>,
|
|
53
|
+
default: undefined,
|
|
54
|
+
},
|
|
29
55
|
},
|
|
30
56
|
emits: {
|
|
31
57
|
click: (_e: MouseEvent) => true,
|
|
@@ -40,6 +66,9 @@ export default defineComponent({
|
|
|
40
66
|
props.block ? "hk-btn-block" : "",
|
|
41
67
|
props.loading ? "hk-btn-loading" : "",
|
|
42
68
|
props.shortcut ? "hk-btn-has-shortcut" : "",
|
|
69
|
+
props.collapseLabel && props.label && !props.block && !props.shortcut
|
|
70
|
+
? `hk-btn-label-collapse-${props.collapseLabel}`
|
|
71
|
+
: "",
|
|
43
72
|
]);
|
|
44
73
|
|
|
45
74
|
return () => {
|
|
@@ -51,9 +80,13 @@ export default defineComponent({
|
|
|
51
80
|
// reactive, so a cached flag would go stale when a parent toggles
|
|
52
81
|
// the label slot without touching the icon props. A shortcut chip
|
|
53
82
|
// is excluded: it renders as an extra flex child the fixed square
|
|
54
|
-
// width would visibly clip.
|
|
83
|
+
// width would visibly clip. A `label` prop counts as text (the
|
|
84
|
+
// responsive collapse handles the narrow end via CSS instead).
|
|
55
85
|
const iconOnly =
|
|
56
|
-
!slots.default &&
|
|
86
|
+
!slots.default &&
|
|
87
|
+
props.label === undefined &&
|
|
88
|
+
Boolean(props.icon || props.suffix) &&
|
|
89
|
+
!props.shortcut;
|
|
57
90
|
|
|
58
91
|
return (
|
|
59
92
|
<button
|
|
@@ -62,7 +95,7 @@ export default defineComponent({
|
|
|
62
95
|
disabled={props.disabled || props.loading}
|
|
63
96
|
class={[buttonClass.value, iconOnly ? "hk-btn-icon-only" : "", attrs.class]}
|
|
64
97
|
style={attrs.style || undefined}
|
|
65
|
-
aria-label={props.ariaLabel}
|
|
98
|
+
aria-label={props.ariaLabel ?? props.label}
|
|
66
99
|
aria-busy={props.loading || undefined}
|
|
67
100
|
onClick={(e) => emit("click", e)}
|
|
68
101
|
>
|
|
@@ -116,7 +149,11 @@ export default defineComponent({
|
|
|
116
149
|
<HIcon name={props.icon} size={16} />
|
|
117
150
|
</span>
|
|
118
151
|
) : null}
|
|
119
|
-
{
|
|
152
|
+
{props.label !== undefined && props.label !== "" ? (
|
|
153
|
+
<span class="hk-btn-label">{props.label}</span>
|
|
154
|
+
) : (
|
|
155
|
+
slots.default?.()
|
|
156
|
+
)}
|
|
120
157
|
{props.suffix ? (
|
|
121
158
|
<span class="hk-btn-suffix">
|
|
122
159
|
<HIcon name={props.suffix} size={16} />
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// badges, nav tags) resolve without the wildcard import. Unknown names fall
|
|
9
9
|
// back to `Info`, matching the previous `|| LucideIcons.Info` behavior.
|
|
10
10
|
|
|
11
|
-
import { Activity, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Ban, Battery, Bell, Bot, Box, Braces, Brain, Cable, Calendar, Camera, ChartBarBig as BarChart3, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CircleAlert as AlertCircle, CircleCheck as CheckCircle, CircleCheckBig as CheckCircle2, CircleDot, Clock, Copy, Cpu, Database, Dot, Download, Ellipsis as MoreHorizontal, ExternalLink, Eye, FileArchive, FileCode, FileImage, FileText, Filter, Flag, Flame, FolderOpen, Gauge, Globe, HardDrive, History, Image, Info, Kanban, Key, KeyRound, Layers, LayoutDashboard, LoaderCircle as Loader2, LogOut, Lock, Maximize, Maximize2, MessageSquare, Mic, MicOff, Minimize, Minus, Monitor, Network, Package, Pause, Pencil, Play, Plug, Plus, Radio, RefreshCw, RotateCw, ScanSearch, Save, Search, Send, Server, Settings, Share2, Shield, Sparkles, Star, Table, Tag, Thermometer, Trash2, TriangleAlert as AlertTriangle, Upload, Video, Volume2, VolumeX, Webhook, Wind, Workflow, Wrench, X, Zap, ZoomIn, ZoomOut } from "lucide-vue-next";
|
|
11
|
+
import { Activity, ArrowDown, ArrowLeft, ArrowRight, ArrowUp, Ban, Battery, Bell, Bot, Box, Braces, Brain, Cable, Calendar, Camera, ChartBarBig as BarChart3, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, Circle, CircleAlert as AlertCircle, CircleCheck as CheckCircle, CircleCheckBig as CheckCircle2, CircleDot, Clock, Copy, Cpu, Database, Dot, Download, Ellipsis as MoreHorizontal, ExternalLink, Eye, FileArchive, FileCode, FileImage, FileText, Filter, Flag, Flame, FolderOpen, Gauge, Globe, HardDrive, History, Image, Info, Kanban, Key, KeyRound, Layers, LayoutDashboard, LoaderCircle as Loader2, LogOut, Lock, Maximize, Maximize2, MessageSquare, Mic, MicOff, Minimize, Minus, Monitor, Network, Package, Pause, Pencil, Play, Plug, Plus, Radio, RefreshCw, RotateCw, ScanSearch, Save, Search, Send, Server, Settings, Share2, Shield, SlidersHorizontal, Sparkles, Star, Table, Tag, Thermometer, Trash2, TriangleAlert as AlertTriangle, Upload, Video, Volume2, VolumeX, Webhook, Wind, Workflow, Wrench, X, Zap, ZoomIn, ZoomOut } from "lucide-vue-next";
|
|
12
12
|
import type { Component } from "vue";
|
|
13
13
|
|
|
14
14
|
|
|
@@ -109,7 +109,7 @@ const REGISTRY: Record<string, Component> = {
|
|
|
109
109
|
Network, Package, Pause, Pencil, Play, Plug, Plus, Radio, RefreshCw,
|
|
110
110
|
RotateCw,
|
|
111
111
|
ScanSearch, Save, Search, Send, Server, Settings, Share2, Shield,
|
|
112
|
-
Sparkles, Star, Table, Tag, Thermometer, Trash2, Upload, Video, Volume2,
|
|
112
|
+
SlidersHorizontal, Sparkles, Star, Table, Tag, Thermometer, Trash2, Upload, Video, Volume2,
|
|
113
113
|
VolumeX, Webhook, Wind, Workflow, Wrench, X, Zap, ZoomIn, ZoomOut,
|
|
114
114
|
};
|
|
115
115
|
|