@matter-server/dashboard 0.3.2 → 0.3.3

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.
Files changed (104) hide show
  1. package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts +2 -2
  2. package/dist/esm/pages/cluster-commands/base-cluster-commands.d.ts.map +1 -1
  3. package/dist/esm/pages/cluster-commands/base-cluster-commands.js.map +1 -1
  4. package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts +36 -0
  5. package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.d.ts.map +1 -0
  6. package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js +159 -0
  7. package/dist/esm/pages/cluster-commands/clusters/basic-information-commands.js.map +6 -0
  8. package/dist/esm/pages/cluster-commands/index.d.ts +1 -0
  9. package/dist/esm/pages/cluster-commands/index.d.ts.map +1 -1
  10. package/dist/esm/pages/cluster-commands/index.js +1 -0
  11. package/dist/esm/pages/cluster-commands/index.js.map +1 -1
  12. package/dist/esm/pages/components/footer.d.ts.map +1 -1
  13. package/dist/esm/pages/components/footer.js +4 -7
  14. package/dist/esm/pages/components/footer.js.map +1 -1
  15. package/dist/esm/pages/components/header.d.ts +5 -0
  16. package/dist/esm/pages/components/header.d.ts.map +1 -1
  17. package/dist/esm/pages/components/header.js +75 -0
  18. package/dist/esm/pages/components/header.js.map +1 -1
  19. package/dist/esm/pages/components/node-details.js +1 -1
  20. package/dist/esm/pages/components/node-details.js.map +1 -1
  21. package/dist/esm/pages/components/server-details.d.ts.map +1 -1
  22. package/dist/esm/pages/components/server-details.js +0 -1
  23. package/dist/esm/pages/components/server-details.js.map +1 -1
  24. package/dist/esm/pages/matter-dashboard-app.d.ts +12 -0
  25. package/dist/esm/pages/matter-dashboard-app.d.ts.map +1 -1
  26. package/dist/esm/pages/matter-dashboard-app.js +84 -4
  27. package/dist/esm/pages/matter-dashboard-app.js.map +1 -1
  28. package/dist/esm/pages/matter-network-view.d.ts +52 -0
  29. package/dist/esm/pages/matter-network-view.d.ts.map +1 -0
  30. package/dist/esm/pages/matter-network-view.js +309 -0
  31. package/dist/esm/pages/matter-network-view.js.map +6 -0
  32. package/dist/esm/pages/matter-node-view.d.ts.map +1 -1
  33. package/dist/esm/pages/matter-node-view.js +70 -1
  34. package/dist/esm/pages/matter-node-view.js.map +1 -1
  35. package/dist/esm/pages/matter-server-view.d.ts +4 -0
  36. package/dist/esm/pages/matter-server-view.d.ts.map +1 -1
  37. package/dist/esm/pages/matter-server-view.js +16 -1
  38. package/dist/esm/pages/matter-server-view.js.map +1 -1
  39. package/dist/esm/pages/network/base-network-graph.d.ts +74 -0
  40. package/dist/esm/pages/network/base-network-graph.d.ts.map +1 -0
  41. package/dist/esm/pages/network/base-network-graph.js +403 -0
  42. package/dist/esm/pages/network/base-network-graph.js.map +6 -0
  43. package/dist/esm/pages/network/device-icons.d.ts +52 -0
  44. package/dist/esm/pages/network/device-icons.d.ts.map +1 -0
  45. package/dist/esm/pages/network/device-icons.js +197 -0
  46. package/dist/esm/pages/network/device-icons.js.map +6 -0
  47. package/dist/esm/pages/network/device-panel.d.ts +31 -0
  48. package/dist/esm/pages/network/device-panel.d.ts.map +1 -0
  49. package/dist/esm/pages/network/device-panel.js +183 -0
  50. package/dist/esm/pages/network/device-panel.js.map +6 -0
  51. package/dist/esm/pages/network/network-details.d.ts +47 -0
  52. package/dist/esm/pages/network/network-details.d.ts.map +1 -0
  53. package/dist/esm/pages/network/network-details.js +686 -0
  54. package/dist/esm/pages/network/network-details.js.map +6 -0
  55. package/dist/esm/pages/network/network-types.d.ts +153 -0
  56. package/dist/esm/pages/network/network-types.d.ts.map +1 -0
  57. package/dist/esm/pages/network/network-types.js +19 -0
  58. package/dist/esm/pages/network/network-types.js.map +6 -0
  59. package/dist/esm/pages/network/network-utils.d.ts +170 -0
  60. package/dist/esm/pages/network/network-utils.d.ts.map +1 -0
  61. package/dist/esm/pages/network/network-utils.js +472 -0
  62. package/dist/esm/pages/network/network-utils.js.map +6 -0
  63. package/dist/esm/pages/network/thread-graph.d.ts +27 -0
  64. package/dist/esm/pages/network/thread-graph.d.ts.map +1 -0
  65. package/dist/esm/pages/network/thread-graph.js +134 -0
  66. package/dist/esm/pages/network/thread-graph.js.map +6 -0
  67. package/dist/esm/pages/network/wifi-graph.d.ts +27 -0
  68. package/dist/esm/pages/network/wifi-graph.d.ts.map +1 -0
  69. package/dist/esm/pages/network/wifi-graph.js +167 -0
  70. package/dist/esm/pages/network/wifi-graph.js.map +6 -0
  71. package/dist/web/js/{commission-node-dialog-CBSDiqRW.js → commission-node-dialog-B1_khzZb.js} +5 -5
  72. package/dist/web/js/{commission-node-existing-TP6s8Tez.js → commission-node-existing-RpdajrwF.js} +2 -5
  73. package/dist/web/js/{commission-node-thread-DOB8pu6x.js → commission-node-thread-5f2itkTG.js} +2 -5
  74. package/dist/web/js/{commission-node-wifi-tzavmk1j.js → commission-node-wifi-DZ_pWqsa.js} +2 -5
  75. package/dist/web/js/{dialog-box-Dknil_Be.js → dialog-box-DEUxM4B1.js} +2 -2
  76. package/dist/web/js/{fire_event-DRpOSjJR.js → fire_event-BczBMT8E.js} +1 -1
  77. package/dist/web/js/{log-level-dialog-TXkma-7Z.js → log-level-dialog-Cr3PfX1X.js} +2 -3
  78. package/dist/web/js/main.js +1 -1
  79. package/dist/web/js/matter-dashboard-app-BuCe_Jxf.js +29990 -0
  80. package/dist/web/js/{node-binding-dialog-D52FCBFP.js → node-binding-dialog-DMiHNDLA.js} +2 -4
  81. package/dist/web/js/{prevent_default-BPgSQsuY.js → prevent_default-D4FX_PIh.js} +2 -42
  82. package/package.json +5 -4
  83. package/src/pages/cluster-commands/base-cluster-commands.ts +2 -2
  84. package/src/pages/cluster-commands/clusters/basic-information-commands.ts +171 -0
  85. package/src/pages/cluster-commands/index.ts +1 -0
  86. package/src/pages/components/footer.ts +4 -7
  87. package/src/pages/components/header.ts +81 -0
  88. package/src/pages/components/node-details.ts +2 -2
  89. package/src/pages/components/server-details.ts +0 -1
  90. package/src/pages/matter-dashboard-app.ts +105 -5
  91. package/src/pages/matter-network-view.ts +325 -0
  92. package/src/pages/matter-node-view.ts +75 -1
  93. package/src/pages/matter-server-view.ts +17 -1
  94. package/src/pages/network/base-network-graph.ts +463 -0
  95. package/src/pages/network/device-icons.ts +283 -0
  96. package/src/pages/network/device-panel.ts +180 -0
  97. package/src/pages/network/network-details.ts +750 -0
  98. package/src/pages/network/network-types.ts +161 -0
  99. package/src/pages/network/network-utils.ts +752 -0
  100. package/src/pages/network/thread-graph.ts +164 -0
  101. package/src/pages/network/wifi-graph.ts +192 -0
  102. package/dist/web/js/matter-dashboard-app-B7GUghkC.js +0 -17254
  103. package/dist/web/js/outlined-text-field-D1DyKQY-.js +0 -968
  104. package/dist/web/js/validator-C735j770.js +0 -1122
@@ -0,0 +1,463 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2025-2026 Open Home Foundation
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+
7
+ import type { MatterNode } from "@matter-server/ws-client";
8
+ import { LitElement, css } from "lit";
9
+ import { property, state } from "lit/decorators.js";
10
+ // @ts-expect-error vis-network doesn't have proper type declarations for standalone export
11
+ import { DataSet, Network } from "vis-network/standalone";
12
+ import { ThemeService } from "../../util/theme-service.js";
13
+ import type { NetworkGraphEdge, NetworkGraphNode } from "./network-types.js";
14
+
15
+ /**
16
+ * Base class for network graph components (Thread and WiFi).
17
+ * Provides shared vis.js network initialization, highlighting, and theme support.
18
+ */
19
+ export abstract class BaseNetworkGraph extends LitElement {
20
+ @property({ type: Object })
21
+ public nodes: Record<string, MatterNode> = {};
22
+
23
+ @state()
24
+ protected _selectedNodeId: number | string | null = null;
25
+
26
+ protected _network?: Network;
27
+ protected _nodesDataSet?: DataSet<NetworkGraphNode>;
28
+ protected _edgesDataSet?: DataSet<NetworkGraphEdge>;
29
+ protected _container?: HTMLDivElement;
30
+ protected _resizeObserver?: ResizeObserver;
31
+ protected _themeUnsubscribe?: () => void;
32
+ protected _updateDebounceTimer?: ReturnType<typeof setTimeout>;
33
+
34
+ /** Store original edge colors for restoration after highlighting */
35
+ private _originalEdgeColors: Map<string, { color: string; highlight: string }> = new Map();
36
+
37
+ protected _getFontColor(): string {
38
+ return ThemeService.effectiveTheme === "dark" ? "#e0e0e0" : "#333333";
39
+ }
40
+
41
+ protected _getDimmedEdgeColor(): string {
42
+ return ThemeService.effectiveTheme === "dark" ? "#555555" : "#cccccc";
43
+ }
44
+
45
+ /**
46
+ * Returns physics options for the network. Override in subclasses for different behavior.
47
+ */
48
+ protected _getPhysicsOptions(): any {
49
+ return {
50
+ enabled: true,
51
+ solver: "forceAtlas2Based",
52
+ forceAtlas2Based: {
53
+ gravitationalConstant: -70,
54
+ centralGravity: 0.005,
55
+ springLength: 130,
56
+ springConstant: 0.08,
57
+ damping: 0.4,
58
+ avoidOverlap: 0.6,
59
+ },
60
+ stabilization: {
61
+ enabled: true,
62
+ iterations: 250,
63
+ updateInterval: 25,
64
+ },
65
+ };
66
+ }
67
+
68
+ override updated(changedProperties: Map<string, unknown>): void {
69
+ super.updated(changedProperties);
70
+
71
+ // If container wasn't found in firstUpdated (empty state was rendered),
72
+ // try to find and observe it now that it might have appeared
73
+ if (!this._container && !this._resizeObserver) {
74
+ this._tryAttachContainer();
75
+ }
76
+
77
+ if (changedProperties.has("nodes")) {
78
+ this._debouncedUpdateGraph();
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Try to find and attach the graph container if it wasn't available before.
84
+ * This handles the case where empty state was rendered initially.
85
+ */
86
+ private _tryAttachContainer(): void {
87
+ const container = this.shadowRoot?.querySelector(".graph-container") as HTMLDivElement;
88
+ if (container) {
89
+ this._container = container;
90
+ this._resizeObserver = new ResizeObserver(entries => {
91
+ const entry = entries[0];
92
+ if (entry && entry.contentRect.width > 0 && entry.contentRect.height > 0) {
93
+ if (!this._network) {
94
+ this._initializeNetwork();
95
+ } else {
96
+ this._network.setSize(`${entry.contentRect.width}px`, `${entry.contentRect.height}px`);
97
+ this._network.redraw();
98
+ }
99
+ }
100
+ });
101
+ this._resizeObserver.observe(this._container);
102
+ }
103
+ }
104
+
105
+ /** Debounced graph update to avoid excessive redraws */
106
+ protected _debouncedUpdateGraph(): void {
107
+ if (this._updateDebounceTimer) {
108
+ clearTimeout(this._updateDebounceTimer);
109
+ }
110
+ this._updateDebounceTimer = setTimeout(() => {
111
+ this._updateGraph();
112
+ }, 100);
113
+ }
114
+
115
+ override firstUpdated(): void {
116
+ this._container = this.shadowRoot?.querySelector(".graph-container") as HTMLDivElement;
117
+ if (this._container) {
118
+ // Wait for container to have proper dimensions before initializing
119
+ this._resizeObserver = new ResizeObserver(entries => {
120
+ const entry = entries[0];
121
+ if (entry && entry.contentRect.width > 0 && entry.contentRect.height > 0) {
122
+ if (!this._network) {
123
+ this._initializeNetwork();
124
+ } else {
125
+ // Resize existing network
126
+ this._network.setSize(`${entry.contentRect.width}px`, `${entry.contentRect.height}px`);
127
+ this._network.redraw();
128
+ }
129
+ }
130
+ });
131
+ this._resizeObserver.observe(this._container);
132
+ }
133
+
134
+ // Subscribe to theme changes - refresh entire graph to update colors
135
+ this._themeUnsubscribe = ThemeService.subscribe(() => {
136
+ if (this._network) {
137
+ // Update font colors
138
+ this._network.setOptions({
139
+ nodes: {
140
+ font: {
141
+ color: this._getFontColor(),
142
+ },
143
+ },
144
+ });
145
+ // Regenerate node icons and edges with new theme colors
146
+ this._updateGraph();
147
+ }
148
+ });
149
+ }
150
+
151
+ override disconnectedCallback(): void {
152
+ super.disconnectedCallback();
153
+ this._resizeObserver?.disconnect();
154
+ this._themeUnsubscribe?.();
155
+ if (this._updateDebounceTimer) {
156
+ clearTimeout(this._updateDebounceTimer);
157
+ }
158
+ this._nodesDataSet?.clear();
159
+ this._edgesDataSet?.clear();
160
+ this._network?.destroy();
161
+ this._network = undefined;
162
+ this._nodesDataSet = undefined;
163
+ this._edgesDataSet = undefined;
164
+ this._originalEdgeColors.clear();
165
+ }
166
+
167
+ protected _initializeNetwork(): void {
168
+ if (!this._container) return;
169
+
170
+ // Get actual dimensions
171
+ const rect = this._container.getBoundingClientRect();
172
+ if (rect.width === 0 || rect.height === 0) {
173
+ return; // Wait for proper dimensions
174
+ }
175
+
176
+ this._nodesDataSet = new DataSet<NetworkGraphNode>();
177
+ this._edgesDataSet = new DataSet<NetworkGraphEdge>();
178
+
179
+ const options = {
180
+ width: `${rect.width}px`,
181
+ height: `${rect.height}px`,
182
+ autoResize: false, // We handle resize manually
183
+ nodes: {
184
+ shape: "image",
185
+ size: 30,
186
+ font: {
187
+ size: 12,
188
+ color: this._getFontColor(),
189
+ },
190
+ borderWidth: 2,
191
+ borderWidthSelected: 3,
192
+ },
193
+ edges: {
194
+ width: 2,
195
+ smooth: {
196
+ type: "continuous",
197
+ roundness: 0.5,
198
+ },
199
+ },
200
+ physics: this._getPhysicsOptions(),
201
+ interaction: {
202
+ hover: true,
203
+ tooltipDelay: 200,
204
+ hideEdgesOnDrag: true,
205
+ },
206
+ };
207
+
208
+ this._network = new Network(
209
+ this._container,
210
+ {
211
+ nodes: this._nodesDataSet,
212
+ edges: this._edgesDataSet,
213
+ },
214
+ options,
215
+ );
216
+
217
+ // Handle node selection
218
+ this._network.on("selectNode", (params: { nodes: (number | string)[] }) => {
219
+ if (params.nodes.length > 0) {
220
+ this._selectedNodeId = params.nodes[0];
221
+ this._highlightConnections(this._selectedNodeId);
222
+ this._dispatchNodeSelected(this._selectedNodeId);
223
+ }
224
+ });
225
+
226
+ this._network.on("deselectNode", () => {
227
+ this._selectedNodeId = null;
228
+ this._clearHighlights();
229
+ this._dispatchNodeSelected(null);
230
+ });
231
+
232
+ // Auto-fit after stabilization completes
233
+ this._network.on("stabilizationIterationsDone", () => {
234
+ // Fit with padding to keep nodes away from edges
235
+ this._network?.fit({
236
+ animation: {
237
+ duration: 500,
238
+ easingFunction: "easeInOutQuad",
239
+ },
240
+ });
241
+ });
242
+
243
+ // Also fit when physics fully stops (catches any drift after initial stabilization)
244
+ this._network.on("stabilized", () => {
245
+ this._network?.fit({
246
+ animation: {
247
+ duration: 300,
248
+ easingFunction: "easeInOutQuad",
249
+ },
250
+ });
251
+ });
252
+
253
+ this._updateGraph();
254
+ }
255
+
256
+ /**
257
+ * Returns true if the graph is initialized and ready for interaction.
258
+ */
259
+ public isReady(): boolean {
260
+ return this._network !== undefined && this._nodesDataSet !== undefined;
261
+ }
262
+
263
+ /**
264
+ * Fits all nodes into view.
265
+ */
266
+ public fit(): void {
267
+ this._network?.fit({
268
+ animation: {
269
+ duration: 300,
270
+ easingFunction: "easeInOutQuad",
271
+ },
272
+ });
273
+ }
274
+
275
+ /**
276
+ * Selects a node by ID and focuses on it.
277
+ */
278
+ public selectNode(nodeId: number | string): void {
279
+ if (!this._network) return;
280
+
281
+ this._selectedNodeId = nodeId;
282
+ this._network.selectNodes([nodeId]);
283
+ this._highlightConnections(nodeId);
284
+ this._dispatchNodeSelected(nodeId);
285
+
286
+ // Focus on the selected node
287
+ this._network.focus(nodeId, {
288
+ scale: 1.2,
289
+ animation: {
290
+ duration: 500,
291
+ easingFunction: "easeInOutQuad",
292
+ },
293
+ });
294
+ }
295
+
296
+ protected _dispatchNodeSelected(nodeId: number | string | null): void {
297
+ this.dispatchEvent(
298
+ new CustomEvent("node-selected", {
299
+ detail: { nodeId },
300
+ bubbles: true,
301
+ composed: true,
302
+ }),
303
+ );
304
+ }
305
+
306
+ /**
307
+ * Highlights edges connected to the selected node and makes neighbor nodes more prominent.
308
+ */
309
+ protected _highlightConnections(nodeId: number | string): void {
310
+ if (!this._edgesDataSet || !this._nodesDataSet) return;
311
+
312
+ // Store original colors before highlighting
313
+ const allEdges = this._edgesDataSet.get();
314
+ for (const edge of allEdges) {
315
+ const edgeId = String(edge.id);
316
+ if (!this._originalEdgeColors.has(edgeId)) {
317
+ const colorObj = edge.color as { color: string; highlight: string };
318
+ this._originalEdgeColors.set(edgeId, {
319
+ color: colorObj?.color ?? "#999999",
320
+ highlight: colorObj?.highlight ?? "#999999",
321
+ });
322
+ }
323
+ }
324
+
325
+ // Find all edges connected to this node
326
+ const connectedEdges = this._edgesDataSet.get({
327
+ filter: (edge: NetworkGraphEdge) => edge.from === nodeId || edge.to === nodeId,
328
+ });
329
+
330
+ // Get neighbor node IDs
331
+ const neighborIds = new Set<number | string>();
332
+ for (const edge of connectedEdges) {
333
+ if (edge.from === nodeId) {
334
+ neighborIds.add(edge.to);
335
+ } else {
336
+ neighborIds.add(edge.from);
337
+ }
338
+ }
339
+
340
+ // Update edges - make connected ones thicker
341
+ const dimmedColor = this._getDimmedEdgeColor();
342
+ const edgeUpdates = allEdges.map((edge: NetworkGraphEdge) => {
343
+ const isConnected = edge.from === nodeId || edge.to === nodeId;
344
+ const originalColor = this._originalEdgeColors.get(String(edge.id));
345
+ return {
346
+ id: edge.id,
347
+ width: isConnected ? 3 : 1,
348
+ // Dim non-connected edges
349
+ color: isConnected
350
+ ? { color: originalColor?.color, highlight: originalColor?.highlight }
351
+ : {
352
+ color: dimmedColor,
353
+ highlight: dimmedColor,
354
+ },
355
+ };
356
+ });
357
+ this._edgesDataSet.update(edgeUpdates);
358
+
359
+ // Update nodes - make neighbors more prominent
360
+ const allNodes = this._nodesDataSet.get();
361
+ const nodeUpdates = allNodes.map((node: NetworkGraphNode) => {
362
+ const isNeighbor = neighborIds.has(node.id);
363
+ const isSelected = node.id === nodeId;
364
+ return {
365
+ id: node.id,
366
+ size: isSelected ? 40 : isNeighbor ? 35 : 25,
367
+ font: {
368
+ size: isSelected ? 14 : isNeighbor ? 13 : 11,
369
+ color: this._getFontColor(),
370
+ bold: isSelected || isNeighbor ? { color: this._getFontColor() } : undefined,
371
+ },
372
+ opacity: isSelected || isNeighbor ? 1 : 0.5,
373
+ };
374
+ });
375
+ this._nodesDataSet.update(nodeUpdates);
376
+ }
377
+
378
+ /**
379
+ * Clears all highlights and restores default styling.
380
+ */
381
+ protected _clearHighlights(): void {
382
+ if (!this._edgesDataSet || !this._nodesDataSet) return;
383
+
384
+ // Restore edge widths and original colors
385
+ const allEdges = this._edgesDataSet.get();
386
+ const edgeUpdates = allEdges.map((edge: NetworkGraphEdge) => {
387
+ const originalColor = this._originalEdgeColors.get(String(edge.id));
388
+ return {
389
+ id: edge.id,
390
+ width: 2,
391
+ color: originalColor ?? { color: "#999999", highlight: "#999999" },
392
+ };
393
+ });
394
+ this._edgesDataSet.update(edgeUpdates);
395
+
396
+ // Restore node sizes
397
+ const allNodes = this._nodesDataSet.get();
398
+ const nodeUpdates = allNodes.map((node: NetworkGraphNode) => ({
399
+ id: node.id,
400
+ size: 30,
401
+ font: {
402
+ size: 12,
403
+ color: this._getFontColor(),
404
+ bold: undefined,
405
+ },
406
+ opacity: 1,
407
+ }));
408
+ this._nodesDataSet.update(nodeUpdates);
409
+ }
410
+
411
+ /**
412
+ * Clear stored edge colors when graph is updated (edges are recreated).
413
+ */
414
+ protected _clearOriginalEdgeColors(): void {
415
+ this._originalEdgeColors.clear();
416
+ }
417
+
418
+ /**
419
+ * Abstract method to be implemented by subclasses for graph-specific updates.
420
+ */
421
+ protected abstract _updateGraph(): void;
422
+
423
+ static override styles = css`
424
+ :host {
425
+ display: block;
426
+ width: 100%;
427
+ height: 100%;
428
+ min-height: 0;
429
+ }
430
+
431
+ .graph-container {
432
+ width: 100%;
433
+ height: 100%;
434
+ background-color: var(--md-sys-color-surface, #fff);
435
+ border-radius: 8px;
436
+ border: 1px solid var(--md-sys-color-outline-variant, #ccc);
437
+ }
438
+
439
+ .empty-state {
440
+ display: flex;
441
+ flex-direction: column;
442
+ align-items: center;
443
+ justify-content: center;
444
+ height: 100%;
445
+ color: var(--md-sys-color-on-surface-variant, #666);
446
+ text-align: center;
447
+ padding: 24px;
448
+ box-sizing: border-box;
449
+ background-color: var(--md-sys-color-surface, #fff);
450
+ border-radius: 8px;
451
+ border: 1px solid var(--md-sys-color-outline-variant, #ccc);
452
+ }
453
+
454
+ .empty-state p {
455
+ margin: 8px 0;
456
+ }
457
+
458
+ .empty-state .hint {
459
+ font-size: 0.875rem;
460
+ opacity: 0.7;
461
+ }
462
+ `;
463
+ }