@visns-studio/visns-components 6.17.1 → 6.20.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.
Files changed (84) hide show
  1. package/package.json +10 -7
  2. package/src/components/AssociationManager.jsx +344 -177
  3. package/src/components/AsyncSelect.jsx +2 -2
  4. package/src/components/Autocomplete.jsx +57 -12
  5. package/src/components/Breadcrumb.jsx +23 -4
  6. package/src/components/ClientAssociationManager.jsx +321 -145
  7. package/src/components/DataGrid.jsx +625 -20
  8. package/src/components/DropZone.jsx +4 -1
  9. package/src/components/Form.jsx +233 -6
  10. package/src/components/MultiSelect.jsx +2 -2
  11. package/src/components/Navigation.jsx +45 -25
  12. package/src/components/TableFilter.jsx +172 -12
  13. package/src/components/UniversalSearch.jsx +390 -0
  14. package/src/components/auth/Login.jsx +164 -8
  15. package/src/components/auth/Profile.jsx +209 -22
  16. package/src/components/auth/authEndpoints.js +4 -0
  17. package/src/components/auth/webauthn.js +194 -0
  18. package/src/components/columns/ColumnRenderers.jsx +6 -1
  19. package/src/components/controls/DataGridSearch.jsx +11 -5
  20. package/src/components/generic/GenericAuth.jsx +6 -0
  21. package/src/components/generic/GenericDashboard.jsx +93 -36
  22. package/src/components/generic/GenericDetail.jsx +146 -61
  23. package/src/components/generic/GenericIndex.jsx +134 -38
  24. package/src/components/generic/GenericSettings.jsx +220 -0
  25. package/src/components/generic/StandardModal.jsx +165 -8
  26. package/src/components/generic/dashboardChartDefaults.js +230 -0
  27. package/src/components/generic/shared/indexLayout.js +38 -0
  28. package/src/components/navActive.js +25 -0
  29. package/src/components/settings/IntegrationsManager.jsx +474 -0
  30. package/src/components/sms/SmsLineSettings.jsx +16 -12
  31. package/src/components/styles/AssociationManager.module.scss +4 -363
  32. package/src/components/styles/AuditLog.module.scss +6 -6
  33. package/src/components/styles/AuditLogs.module.scss +3 -3
  34. package/src/components/styles/Autocomplete.module.scss +7 -7
  35. package/src/components/styles/BusinessCardOcr.module.scss +6 -1
  36. package/src/components/styles/CallQueueSettings.module.scss +4 -1
  37. package/src/components/styles/CameraPlacement.module.scss +19 -19
  38. package/src/components/styles/CategorizedDropZone.module.scss +6 -1
  39. package/src/components/styles/ClientAssociationManager.module.scss +5 -289
  40. package/src/components/styles/ClientAuth.module.scss +1 -1
  41. package/src/components/styles/DataGrid.module.scss +455 -8
  42. package/src/components/styles/Field.module.scss +32 -24
  43. package/src/components/styles/Form.module.scss +123 -3
  44. package/src/components/styles/GenericDashboard.module.scss +612 -825
  45. package/src/components/styles/GenericDetail.module.scss +406 -61
  46. package/src/components/styles/GenericDynamic.module.scss +25 -25
  47. package/src/components/styles/GenericEditableTable.module.scss +33 -33
  48. package/src/components/styles/GenericFormBuilder.module.scss +119 -119
  49. package/src/components/styles/GenericIndex.module.scss +483 -38
  50. package/src/components/styles/GenericMain.module.scss +6 -3
  51. package/src/components/styles/GenericQuote.module.scss +399 -81
  52. package/src/components/styles/GenericReport.module.scss +8 -8
  53. package/src/components/styles/GenericSettings.module.scss +209 -0
  54. package/src/components/styles/GenericSort.module.scss +9 -9
  55. package/src/components/styles/Integrations.module.scss +321 -0
  56. package/src/components/styles/List.module.scss +4 -4
  57. package/src/components/styles/Login.module.scss +57 -0
  58. package/src/components/styles/MultiSelect.module.scss +4 -4
  59. package/src/components/styles/Navigation.module.scss +12 -12
  60. package/src/components/styles/Notification.module.scss +12 -8
  61. package/src/components/styles/NotificationList.module.scss +5 -5
  62. package/src/components/styles/Profile.module.scss +5 -5
  63. package/src/components/styles/QrCode.module.scss +4 -4
  64. package/src/components/styles/QuickAction.module.scss +20 -20
  65. package/src/components/styles/SortableQuoteItems.module.scss +4 -4
  66. package/src/components/styles/TableFilter.module.scss +128 -17
  67. package/src/components/styles/TicketConversation.module.scss +497 -0
  68. package/src/components/styles/UniversalSearch.module.scss +229 -0
  69. package/src/components/styles/Vault.module.scss +413 -0
  70. package/src/components/styles/_associationManager.scss +467 -0
  71. package/src/components/styles/_authBrandPanel.scss +15 -9
  72. package/src/components/styles/_controls.scss +43 -39
  73. package/src/components/styles/global-datagrid.css +196 -0
  74. package/src/components/styles/global.css +168 -17
  75. package/src/components/tickets/TicketConversation.jsx +455 -0
  76. package/src/components/utils/rowActionSettings.js +117 -0
  77. package/src/components/vault/VaultClientPicker.jsx +183 -0
  78. package/src/components/vault/VaultEntryForm.jsx +23 -0
  79. package/src/components/vault/VaultManager.jsx +382 -34
  80. package/src/components/vault/VaultShareModal.jsx +454 -0
  81. package/src/components/vault/vaultEndpoints.js +16 -2
  82. package/src/components/vault/vaultShares.js +247 -0
  83. package/src/index.js +35 -0
  84. package/src/utils/fetchUtil.js +38 -14
@@ -1,971 +1,758 @@
1
- /* New grid container */
1
+ // ============================================================================
2
+ // GenericDashboard — 2026
3
+ // ----------------------------------------------------------------------------
4
+ // Rebuilt from the 2021-era sheet this replaces. What changed and why:
5
+ //
6
+ // * The old card wore a 1px border, a hard 4px brand-coloured bottom rule and
7
+ // an underline beneath every title. Three separating devices doing one job,
8
+ // so a grid of six cards arrived as a grid of six competing boxes. The card
9
+ // is now one quiet surface with a hairline and a soft shadow, and the
10
+ // heading is the only thing that separates itself.
11
+ // * `--dash-*` tokens throughout, each with a fallback, so a host that
12
+ // declares nothing still renders. Dark mode is a token swap, not a filter.
13
+ // * Stat tiles follow the figure contract: label, value, optional delta and
14
+ // sparkline. The value uses PROPORTIONAL figures - tabular-nums gives every
15
+ // digit the width of a zero, which makes a standalone "121" look loose at
16
+ // display sizes. Tabular is reserved for the table widget's columns.
17
+ // * The chart palette is validated, not chosen by eye: six categorical hues
18
+ // that clear the CVD and normal-vision floors in both modes (light worst
19
+ // adjacent ΔE 9.2 deutan / 27.6 normal). Slot 1 is the brand green.
20
+ // Three light-mode hues sit under 3:1 on white, so every chart ships a
21
+ // legend and direct labels - the relief rule.
22
+ //
23
+ // The class names are unchanged: this is a restyle, and every consumer's
24
+ // dashboard.json keeps working untouched.
25
+ // ============================================================================
26
+
2
27
  .grid {
28
+ // -- surfaces ---------------------------------------------------------
29
+ --dash-bg: var(--bg-color, #f5f3ef);
30
+ --dash-surface: var(--background-color, #fff);
31
+ --dash-ink: var(--paragraph-color, #2b2b2b);
32
+ --dash-heading: var(--heading-color, var(--primary-color, #1b3933));
33
+ --dash-primary: var(--primary-color, #1b3933);
34
+ --dash-accent: var(--highlight-color, #3cbf7d);
35
+
36
+ --dash-line: color-mix(in srgb, var(--dash-ink) 12%, transparent);
37
+ --dash-muted: color-mix(in srgb, var(--dash-ink) 62%, transparent);
38
+ --dash-quiet: color-mix(in srgb, var(--dash-ink) 4%, var(--dash-surface));
39
+ --dash-hover: color-mix(in srgb, var(--dash-primary) 5%, var(--dash-surface));
40
+
41
+ --dash-radius: var(--dash-card-radius, 14px);
42
+ --dash-gap: var(--dash-card-gap, 1rem);
43
+ --dash-pad: var(--dash-card-padding, 1.25rem);
44
+
45
+ // Two stops rather than one: a single large blur reads as a smudge, a
46
+ // tight contact shadow plus a wide ambient one reads as a raised surface.
47
+ --dash-shadow:
48
+ 0 1px 2px color-mix(in srgb, var(--dash-primary) 8%, transparent),
49
+ 0 8px 24px -12px color-mix(in srgb, var(--dash-primary) 18%, transparent);
50
+ --dash-shadow-lift:
51
+ 0 2px 4px color-mix(in srgb, var(--dash-primary) 10%, transparent),
52
+ 0 16px 32px -14px color-mix(in srgb, var(--dash-primary) 26%, transparent);
53
+
54
+ // -- status (reserved; never reused as a series colour) ----------------
55
+ --dash-good: #1f7a47;
56
+ --dash-warn: #a36a14;
57
+ --dash-bad: #be3e3e;
58
+
59
+ // -- categorical series (validated; see the header note) ---------------
60
+ --dash-series-1: #1baf7a;
61
+ --dash-series-2: #eb6834;
62
+ --dash-series-3: #2a78d6;
63
+ --dash-series-4: #eda100;
64
+ --dash-series-5: #4a3aa7;
65
+ --dash-series-6: #e87ba4;
66
+
67
+ --dash-motion: var(--speed, 160ms) var(--ease, cubic-bezier(0.4, 0, 0.2, 1));
68
+
69
+ display: grid;
70
+ // Twelve columns, because the config vocabulary is thirds and halves and
71
+ // nothing else divides both. An auto-fit track cannot express "a third":
72
+ // 1/3 and 1/2 collapse to the same width and the row order drifts with the
73
+ // viewport.
74
+ grid-template-columns: repeat(12, minmax(0, 1fr));
75
+ gap: var(--dash-gap);
76
+ align-items: start;
77
+ width: 100%;
78
+ color: var(--dash-ink);
79
+ }
80
+
81
+ // Dark mode is a set of chosen steps, not an inversion: the series hues below
82
+ // are the same six re-stepped for a dark surface and validated against it.
83
+ // The app ships a single light theme. These dark overrides used to fire
84
+ // straight off the OS setting, which left the app half-dark at night: only 4
85
+ // of 72 stylesheets carry dark rules, so dark surfaces ended up wearing
86
+ // light-theme text. They now apply only when `data-theme="dark"` is opted
87
+ // into explicitly, so the work survives for a real theme toggle later.
88
+ :root[data-theme='dark'] {
89
+ .grid {
90
+ --dash-bg: #101312;
91
+ --dash-surface: #171b1a;
92
+ --dash-ink: #e6e9e8;
93
+ --dash-heading: #f2f5f4;
94
+ --dash-series-1: #199e70;
95
+ --dash-series-2: #d95926;
96
+ --dash-series-3: #3987e5;
97
+ --dash-series-4: #c98500;
98
+ --dash-series-5: #9085e9;
99
+ --dash-series-6: #d55181;
100
+ --dash-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
101
+ --dash-shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.55), 0 16px 32px -14px rgba(0, 0, 0, 0.7);
102
+ }
103
+ }
104
+
105
+ :root[data-theme='dark'] .grid {
106
+ --dash-bg: #101312;
107
+ --dash-surface: #171b1a;
108
+ --dash-ink: #e6e9e8;
109
+ --dash-heading: #f2f5f4;
110
+ --dash-series-1: #199e70;
111
+ --dash-series-2: #d95926;
112
+ --dash-series-3: #3987e5;
113
+ --dash-series-4: #c98500;
114
+ --dash-series-5: #9085e9;
115
+ --dash-series-6: #d55181;
116
+ --dash-shadow: 0 1px 2px rgba(0, 0, 0, 0.5), 0 8px 24px -12px rgba(0, 0, 0, 0.6);
117
+ --dash-shadow-lift: 0 2px 4px rgba(0, 0, 0, 0.55), 0 16px 32px -14px rgba(0, 0, 0, 0.7);
118
+ }
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Spans. The old sheet set percentage widths on floats; these are column spans
122
+ // on the auto-fit grid, clamped so a "full" widget cannot demand more columns
123
+ // than the viewport actually has.
124
+ // ---------------------------------------------------------------------------
125
+
126
+ .grid__full { grid-column: span 12; }
127
+ .grid__twothird { grid-column: span 8; }
128
+ .grid__half { grid-column: span 6; }
129
+ .grid__three { grid-column: span 4; }
130
+
131
+ // Below 1100px a third is too narrow for a table or a chart, so thirds become
132
+ // halves and everything wider becomes full width.
133
+ @media (max-width: 1100px) {
134
+ .grid__twothird { grid-column: span 12; }
135
+ .grid__half { grid-column: span 12; }
136
+ .grid__three { grid-column: span 6; }
137
+ }
138
+
139
+ @media (max-width: 700px) {
140
+ .grid__full,
141
+ .grid__twothird,
142
+ .grid__half,
143
+ .grid__three { grid-column: span 12; }
144
+ }
145
+
146
+ // ---------------------------------------------------------------------------
147
+ // The card
148
+ // ---------------------------------------------------------------------------
149
+
150
+ .dashwidget,
151
+ .dashwidget--highlight {
152
+ position: relative;
3
153
  display: flex;
4
- flex-wrap: wrap;
5
- gap: 1rem;
154
+ flex-direction: column;
155
+ min-width: 0; // or a wide table stretches the track
156
+ background: var(--dash-surface);
157
+ border: 1px solid var(--dash-line);
158
+ border-radius: var(--dash-radius);
159
+ box-shadow: var(--dash-shadow);
160
+ transition: box-shadow var(--dash-motion), transform var(--dash-motion);
6
161
  }
7
162
 
8
- .grid__three {
9
- flex: 0 0 calc(33.33% - 0.67rem);
10
- background: var(--item-color);
11
- border-radius: var(--br);
12
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
13
- box-sizing: border-box;
14
- overflow: visible;
15
- position: relative;
16
- padding: 20px;
17
- margin: 8px 0;
18
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
163
+ .dashwidget:hover,
164
+ .dashwidget--highlight:hover {
165
+ box-shadow: var(--dash-shadow-lift);
19
166
  }
20
167
 
21
- .grid__half {
22
- flex: 0 0 calc(50% - 0.5rem);
23
- background: var(--item-color);
24
- border-radius: var(--br);
25
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
26
- box-sizing: border-box;
27
- overflow: hidden;
28
- position: relative;
29
- padding: 20px;
30
- margin: 8px 0;
31
- box-shadow: 0 4px 0 rgba(var(--primary-color), 0.05);
168
+ // The highlighted card. The old one filled itself with the brand green and put
169
+ // white type on it, which made one card shout across the whole grid. Now it
170
+ // keeps the same surface and earns its emphasis from a 3px accent edge and a
171
+ // faint tint - present at a glance, not dominant.
172
+ .dashwidget--highlight {
173
+ background:
174
+ linear-gradient(
175
+ to bottom right,
176
+ color-mix(in srgb, var(--dash-accent) 8%, var(--dash-surface)),
177
+ var(--dash-surface) 60%
178
+ );
179
+ border-color: color-mix(in srgb, var(--dash-accent) 40%, var(--dash-line));
32
180
  }
33
181
 
34
- .grid__full {
35
- flex: 0 0 100%;
36
- background: var(--item-color);
37
- border-radius: var(--br);
38
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
39
- box-sizing: border-box;
40
- overflow: hidden;
41
- position: relative;
42
- padding: 20px;
43
- margin: 8px 0;
44
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
182
+ .dashwidget--highlight::before {
183
+ content: '';
184
+ position: absolute;
185
+ inset: 0 auto 0 0;
186
+ width: 3px;
187
+ border-radius: var(--dash-radius) 0 0 var(--dash-radius);
188
+ background: var(--dash-accent);
45
189
  }
46
190
 
47
- .grid__twothird {
48
- flex: 0 0 calc(66.66% - 0.67rem);
49
- background: var(--item-color);
50
- border-radius: var(--br);
51
- border: 1px solid rgba(var(--item-color-rgb), 1.15);
52
- box-sizing: border-box;
53
- overflow: visible;
54
- position: relative;
55
- padding: 20px;
56
- margin: 8px 0;
57
- box-shadow: 0 10px 50px rgba(var(--primary-rgb), 0.05);
191
+ .widgetItem {
192
+ display: flex;
193
+ flex-direction: column;
194
+ gap: 0.75rem;
195
+ padding: var(--dash-pad);
196
+ min-width: 0;
197
+ height: 100%;
58
198
  }
59
199
 
60
- /* The remaining CSS remains largely unchanged */
200
+ // ---------------------------------------------------------------------------
201
+ // Heading
202
+ // ---------------------------------------------------------------------------
203
+
61
204
  .widgetTitle {
62
- width: 100%;
63
- height: auto;
64
- padding: 0;
65
- margin-bottom: 0.85rem;
66
205
  display: flex;
67
- justify-content: space-between;
68
206
  align-items: center;
207
+ justify-content: space-between;
208
+ gap: 0.75rem;
209
+ min-height: 1.5rem;
69
210
  }
211
+
70
212
  .widgetTitle h2 {
71
- font-size: 1rem;
72
213
  margin: 0;
73
- padding: 0;
74
- line-height: 1;
75
- text-transform: capitalize;
76
- text-align: left;
77
- color: var(--primary-color);
214
+ // 0.8125rem/600 with a little tracking: a card heading is a label for what
215
+ // is below it, not a page title competing with the value.
216
+ font-size: var(--dash-title-size, 0.8125rem);
217
+ font-weight: 600;
218
+ letter-spacing: 0.02em;
219
+ line-height: 1.3;
220
+ color: var(--dash-muted);
221
+ text-transform: uppercase;
78
222
  }
79
223
 
80
- .dashList {
81
- width: 100%;
224
+ .autoRefreshControl {
82
225
  display: flex;
83
- flex-wrap: wrap;
84
- list-style: none;
85
- padding: 0;
86
- margin: 0;
87
- font-size: 0.875rem;
88
-
89
- li {
90
- width: 100%;
91
- padding: 0;
92
- margin: 0;
93
-
94
- &:first-child {
95
- button {
96
- border-top-left-radius: var(--br);
97
- border-top-right-radius: var(--br);
98
- }
99
- }
100
-
101
- &:last-child {
102
- margin-bottom: 0;
103
-
104
- button {
105
- border-bottom-left-radius: var(--br);
106
- border-bottom-right-radius: var(--br);
107
- border-bottom: 1px solid #dadce0;
108
- }
109
- }
110
-
111
- a {
112
- text-decoration: none;
113
-
114
- &:hover {
115
- background: rgba(var(--secondary-rgb), 0.05);
116
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
117
- }
118
- }
119
-
120
- button {
121
- width: 100%;
122
- display: block;
123
- background: white;
124
- border: 1px solid #dadce0;
125
- border-bottom: none;
126
- outline: none;
127
- padding: 0.35rem 1rem;
128
- color: var(--paragraph-color);
129
- text-align: left;
130
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
131
- letter-spacing: 0.018em;
132
-
133
- &:hover {
134
- background: rgba(var(--secondary-rgb), 0.05);
135
- transition: all 0.25s cubic-bezier(0.25, 0.8, 0.25, 1);
136
- }
137
-
138
- strong {
139
- color: var(--paragraph-color);
140
- }
141
-
142
- span {
143
- color: rgba(var(--secondary-rgb), 1.1);
144
- float: right;
145
- }
146
- }
147
- }
226
+ align-items: center;
227
+ font-size: 0.75rem;
228
+ color: var(--dash-muted);
148
229
  }
149
230
 
150
- .dmore {
151
- width: 100%;
152
- margin: 0.65em 0 0 0;
153
- padding: 5px;
231
+ .autoRefreshControl input[type='checkbox'] {
232
+ inline-size: 14px;
233
+ block-size: 14px;
234
+ padding: 0;
235
+ border: 0;
236
+ border-radius: 0;
237
+ appearance: auto;
238
+ accent-color: var(--dash-primary);
154
239
  }
155
240
 
156
- .dashwidget {
157
- // background: var(--primary-color) !important;
158
- border-top: 1px solid transparent !important;
159
- border-left: 1px solid transparent !important;
160
- border-right: 1px solid transparent !important;
161
- border-bottom: 2px solid rgba(var(--secondary-rgb), 0.9) !important;
241
+ // ---------------------------------------------------------------------------
242
+ // Stat tile
243
+ // ---------------------------------------------------------------------------
162
244
 
163
- h2 {
164
- color: var(--highlight-color) !important;
165
- }
245
+ .widgetNo,
246
+ .widgetNo-alt {
247
+ display: flex;
248
+ align-items: baseline;
249
+ gap: 0.5rem;
250
+ min-height: 3rem;
251
+ }
166
252
 
167
- .widgetTitle {
168
- padding-bottom: 10px;
169
- border-bottom: 2px solid rgba(var(--secondary-rgb), 0.5);
170
- }
253
+ .widgetNo span,
254
+ .widgetNo-alt span {
255
+ // Proportional figures deliberately - see the header note.
256
+ font-size: var(--dash-stat-size, clamp(2rem, 1.4rem + 2vw, 3rem));
257
+ font-weight: 600;
258
+ line-height: 1;
259
+ letter-spacing: -0.02em;
260
+ color: var(--dash-heading);
261
+ font-variant-numeric: proportional-nums;
171
262
  }
172
263
 
173
- .dashwidget--highlight {
174
- background: var(--primary-color) !important;
175
- border-top: 1px solid transparent !important;
176
- border-left: 1px solid transparent !important;
177
- border-right: 1px solid transparent !important;
178
- border-bottom: 2px solid rgba(var(--primary-rgb), 0.9) !important;
179
-
180
- h2 {
181
- color: var(--tertiary-color) !important;
182
- }
264
+ .widgetNo-alt span { color: var(--dash-primary); }
183
265
 
184
- .widgetTitle {
185
- padding-bottom: 10px;
186
- border-bottom: 2px solid rgba(var(--tertiary-color-rgb), 0.5);
187
- }
266
+ // Optional delta beside the value: signed, against a named period. Colour is
267
+ // direction x whether up is good, so a widget can say a rise is bad.
268
+ .statDelta {
269
+ display: inline-flex;
270
+ align-items: center;
271
+ gap: 0.25rem;
272
+ padding: 0.15rem 0.45rem;
273
+ border-radius: var(--radius-pill, 999px);
274
+ font-size: 0.75rem;
275
+ font-weight: 600;
276
+ line-height: 1.4;
277
+ white-space: nowrap;
188
278
  }
189
279
 
190
- .widgetNo {
191
- width: 100%;
192
- height: auto;
193
- padding: 0 0 2rem 0;
194
- box-sizing: border-box;
195
-
196
- span {
197
- width: 100%;
198
- display: block;
199
- text-align: center;
200
- font-weight: 400;
201
- font-size: 3em;
202
- color: var(--primary-color);
203
- }
280
+ .statDeltaGood {
281
+ color: var(--dash-good);
282
+ background: color-mix(in srgb, var(--dash-good) 12%, transparent);
204
283
  }
205
284
 
206
- .widgetNo-alt {
207
- width: 100%;
208
- height: auto;
209
- padding: 0 0 2rem 0;
210
- box-sizing: border-box;
211
-
212
- span {
213
- width: 100%;
214
- display: block;
215
- text-align: center;
216
- font-weight: 400;
217
- font-size: 3em;
218
- color: var(--tertiary-color);
219
- }
285
+ .statDeltaBad {
286
+ color: var(--dash-bad);
287
+ background: color-mix(in srgb, var(--dash-bad) 12%, transparent);
220
288
  }
221
289
 
222
- .widgetLink {
223
- width: 100%;
224
- display: block;
225
-
226
- .btn {
227
- width: max-content;
228
- margin: 0 auto;
229
- display: block;
230
- padding: 0.25em 3em;
231
- background: none;
232
- border-color: rgba(var(--primary-rgb), 0.05);
233
- border-radius: 5px;
234
-
235
- &:hover {
236
- background: none;
237
- border-color: rgba(var(--primary-rgb), 0.5);
238
- }
239
- }
240
-
241
- .btn-alt {
242
- width: max-content;
243
- margin: 0 auto;
244
- display: block;
245
- padding: 0.25em 3em;
246
- background: #00294a;
247
- border-color: rgba(var(--tertiary-color-rgb), 0.05);
248
- color: #fff;
249
- border-radius: 5px;
250
-
251
- &:hover {
252
- background: #00294a;
253
- color: var(--tertiary-color);
254
- border-color: rgba(var(--tertiary-color-rgb), 0.5);
255
- color: #fff;
256
- }
257
- }
290
+ .statDeltaFlat {
291
+ color: var(--dash-muted);
292
+ background: var(--dash-quiet);
258
293
  }
259
294
 
260
- .filter-container {
261
- padding: 1rem;
295
+ .statPeriod {
296
+ font-size: 0.75rem;
297
+ color: var(--dash-muted);
262
298
  }
263
299
 
264
- .filter-header {
265
- text-align: left;
266
- margin-bottom: 1rem;
267
-
268
- h3 {
269
- font-size: 1.25rem;
270
- font-weight: 600;
271
- color: #333;
272
- margin: 0;
273
- }
300
+ .statTrend {
301
+ display: block;
302
+ width: 100%;
303
+ height: 34px;
304
+ margin-top: 0.25rem;
305
+ overflow: visible;
274
306
  }
275
307
 
276
- .filter-fields {
277
- display: flex;
278
- flex-wrap: wrap;
279
- gap: 1rem; // Gap between fields
280
- align-items: flex-end; // Align fields vertically
281
- }
308
+ // ---------------------------------------------------------------------------
309
+ // List widget
310
+ // ---------------------------------------------------------------------------
282
311
 
283
- .filter-field {
284
- flex: 1; // Ensures equal spacing for each field
312
+ .dashList {
285
313
  display: flex;
286
314
  flex-direction: column;
287
- min-width: 200px; // Sets a minimum width for smaller screens
288
-
289
- label {
290
- font-size: 0.875rem;
291
- font-weight: 600;
292
- margin-bottom: 0.5rem;
293
- color: #555;
294
- }
295
-
296
- input,
297
- select {
298
- border: 1px solid #dadce0;
299
- border-radius: 5px;
300
- padding: 0.5rem 0.75rem;
301
- font-size: 0.875rem;
302
- color: #333;
303
- outline: none;
304
- background-color: #f9f9f9;
305
- width: 100%; // Ensures full width within the container
306
-
307
- &:focus {
308
- border-color: #007bff;
309
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); // Focus effect
310
- }
311
- }
315
+ margin: 0;
316
+ padding: 0;
317
+ list-style: none;
312
318
  }
313
319
 
314
- .filter-field-highlight {
315
- flex: 1; // Ensures equal spacing for each field
320
+ .dashList > li {
316
321
  display: flex;
317
- flex-direction: column;
318
- min-width: 200px; // Sets a minimum width for smaller screens
319
-
320
- label {
321
- font-size: 0.875rem;
322
- font-weight: 600;
323
- margin-bottom: 0.5rem;
324
- color: var(--tertiary-color);
325
- }
326
-
327
- input,
328
- select {
329
- border: 1px solid #dadce0;
330
- border-radius: 5px;
331
- padding: 0.5rem 0.75rem;
332
- font-size: 0.875rem;
333
- color: #333;
334
- outline: none;
335
- background-color: #f9f9f9;
336
- width: 100%; // Ensures full width within the container
337
-
338
- &:focus {
339
- border-color: #007bff;
340
- box-shadow: 0 0 0 2px rgba(0, 123, 255, 0.25); // Focus effect
341
- }
342
- }
322
+ align-items: center;
323
+ justify-content: space-between;
324
+ gap: 0.75rem;
325
+ margin: 0;
326
+ padding: 0.6rem 0.5rem;
327
+ border-radius: var(--radius-sm, 6px);
328
+ border-bottom: 1px solid var(--dash-line);
329
+ font-size: 0.875rem;
330
+ line-height: 1.4;
331
+ transition: background var(--dash-motion);
343
332
  }
344
333
 
345
- .filter-actions {
346
- display: flex;
347
- gap: 0.75rem; // Gap between buttons
348
-
349
- .btn {
350
- padding: 0.5rem 1rem;
351
- border-radius: 5px;
352
- font-size: 0.875rem;
353
- font-weight: 600;
354
- text-transform: uppercase;
355
- cursor: pointer;
356
- transition: background-color 0.2s;
357
- height: 51px;
358
-
359
- &.btn-primary {
360
- background-color: var(--primary-color);
361
- color: var(--tertiary-color);
362
- border: none;
363
-
364
- &:hover {
365
- background-color: #0056b3;
366
- }
367
- }
368
-
369
- &.btn-secondary {
370
- background-color: #f8f9fa;
371
- color: #333;
372
- border: 1px solid #ccc;
373
-
374
- &:hover {
375
- background-color: #e2e6ea;
376
- }
377
- }
378
- }
379
- }
334
+ .dashList > li:last-child { border-bottom: 0; }
380
335
 
381
- .table-scroll {
382
- overflow-x: auto;
383
- overflow-y: hidden;
384
- width: 100%;
385
- display: block;
386
- }
336
+ .dashList > li:hover { background: var(--dash-hover); }
387
337
 
388
- .table-container {
389
- margin-top: 20px;
390
- border: 1px solid #dadce0;
391
- border-radius: 8px;
392
- overflow: hidden;
393
- width: 100%;
394
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
395
- }
338
+ // The host styles bare `a`/`button` inside lists - borders, a pill radius, a
339
+ // fixed height - so every row arrived as an outlined chip. Restated here
340
+ // rather than inherited, the same reasoning as the vault and sms modules.
341
+ .dashList a { display: block; width: 100%; }
396
342
 
397
- .table-container table {
343
+ .dashList button {
344
+ display: flex;
345
+ align-items: baseline;
346
+ justify-content: space-between;
347
+ gap: 0.75rem;
398
348
  width: 100%;
399
- border-collapse: collapse;
400
- text-align: left;
401
- background-color: #ffffff;
402
349
  }
403
350
 
404
- .table-container th {
405
- background-color: #f6f8fa;
406
- color: #333;
407
- font-weight: bold;
408
- padding: 12px 15px;
409
- border-bottom: 1px solid #dadce0;
410
- text-transform: capitalize;
411
- font-size: 14px;
412
- }
413
-
414
- .table-container td {
415
- padding: 10px 15px;
416
- border-bottom: 1px solid #eaeaea;
417
- font-size: 13px;
418
- color: #555;
419
- text-align: center;
420
- vertical-align: middle;
421
- max-width: 200px;
422
- word-wrap: break-word;
423
- }
424
-
425
- .table-container th {
426
- text-align: center;
427
- }
428
-
429
- .table-container tbody tr:hover {
430
- background-color: #f9f9f9;
351
+ .dashList strong {
352
+ min-width: 0;
353
+ overflow: hidden;
354
+ text-overflow: ellipsis;
355
+ white-space: nowrap;
356
+ font-weight: 500;
431
357
  }
432
358
 
433
- .table-container tbody tr:nth-child(even) {
434
- background-color: #f7f7f7;
359
+ // The date/meta at the end of a row: fixed width, quiet, and tabular so a
360
+ // column of them lines up.
361
+ .dashList button > span {
362
+ flex: 0 0 auto;
363
+ color: var(--dash-muted);
364
+ font-size: 0.75rem;
365
+ font-variant-numeric: tabular-nums;
366
+ white-space: nowrap;
435
367
  }
436
368
 
437
- .table-container th:first-child,
438
- .table-container td:first-child {
369
+ .dashList a,
370
+ .dashList button {
371
+ min-width: 0;
372
+ max-width: 100%;
373
+ height: auto;
374
+ margin: 0;
375
+ padding: 0;
376
+ border: 0;
377
+ border-radius: 0;
378
+ background: none;
379
+ color: inherit;
380
+ font: inherit;
381
+ font-weight: 500;
439
382
  text-align: left;
440
- font-weight: bold;
441
- padding-left: 20px;
442
- width: 15%;
443
- min-width: 120px;
383
+ text-decoration: none;
384
+ overflow: hidden;
385
+ text-overflow: ellipsis;
386
+ white-space: nowrap;
387
+ cursor: pointer;
444
388
  }
445
389
 
446
- // Template column styling
447
- .table-container th:nth-child(2),
448
- .table-container td:nth-child(2) {
449
- width: 20%;
450
- min-width: 140px;
451
- }
390
+ .dashList a:hover { color: var(--dash-primary); text-decoration: underline; }
452
391
 
453
- // Processed At column styling
454
- .table-container th:nth-child(3),
455
- .table-container td:nth-child(3) {
456
- width: 20%;
457
- min-width: 160px;
458
- font-family: monospace;
459
- font-size: 12px;
392
+ .dmore {
393
+ align-self: flex-start;
394
+ margin-top: 0.5rem;
395
+ font-size: 0.8125rem;
396
+ font-weight: 600;
397
+ color: var(--dash-primary);
398
+ text-decoration: none;
460
399
  }
461
400
 
462
- // Status column styling
463
- .table-container th:nth-child(4),
464
- .table-container td:nth-child(4) {
465
- width: 20%;
466
- min-width: 180px;
467
- }
401
+ .dmore:hover { text-decoration: underline; }
468
402
 
469
- // File size column styling
470
- .table-container th:nth-child(5),
471
- .table-container td:nth-child(5) {
472
- width: 15%;
473
- min-width: 100px;
474
- }
403
+ // ---------------------------------------------------------------------------
404
+ // Table widget
405
+ // ---------------------------------------------------------------------------
475
406
 
476
- // Actions column styling
477
- .table-container th:nth-child(6),
478
- .table-container td:nth-child(6) {
479
- width: 15%;
480
- min-width: 120px;
481
- text-align: center;
482
- }
407
+ .table-container { min-width: 0; }
483
408
 
484
- .table-container td:last-child {
485
- border-right: none;
409
+ .table-scroll {
410
+ overflow-x: auto;
411
+ // The card must never make the page scroll sideways; a wide table scrolls
412
+ // inside its own container instead.
413
+ max-width: 100%;
414
+ border-radius: var(--radius-sm, 6px);
486
415
  }
487
416
 
488
- .table-container th:last-child {
489
- border-right: none;
417
+ .table-scroll table {
418
+ width: 100%;
419
+ border-collapse: collapse;
420
+ font-size: 0.8125rem;
421
+ // Columns of numbers DO get tabular figures, so they align vertically.
422
+ font-variant-numeric: tabular-nums;
423
+ }
424
+
425
+ .table-scroll th {
426
+ position: sticky;
427
+ top: 0;
428
+ z-index: 1;
429
+ padding: 0.5rem 0.75rem;
430
+ background: var(--dash-quiet);
431
+ border-bottom: 1px solid var(--dash-line);
432
+ color: var(--dash-muted);
433
+ font-size: 0.6875rem;
434
+ font-weight: 600;
435
+ letter-spacing: 0.04em;
436
+ text-align: left;
437
+ text-transform: uppercase;
438
+ white-space: nowrap;
490
439
  }
491
440
 
492
- // Additional status column styling
493
- .table-container td:nth-child(4) {
494
- font-style: italic;
495
- color: #666;
441
+ .table-scroll td {
442
+ padding: 0.55rem 0.75rem;
443
+ border-bottom: 1px solid var(--dash-line);
444
+ color: var(--dash-ink);
496
445
  }
497
446
 
498
- // Additional file size column styling
499
- .table-container td:nth-child(5) {
500
- font-family: monospace;
501
- color: #888;
502
- text-align: right;
503
- padding-right: 20px;
504
- }
447
+ .table-scroll tbody tr:last-child td { border-bottom: 0; }
448
+ .table-scroll tbody tr:hover td { background: var(--dash-hover); }
505
449
 
506
- // Additional actions column styling
507
- .table-container td:nth-child(6) {
508
- text-align: center;
509
- padding: 8px;
510
- }
450
+ // ---------------------------------------------------------------------------
451
+ // Buttons
452
+ // ---------------------------------------------------------------------------
511
453
 
512
- // Improve responsive behavior
513
- @media (max-width: 768px) {
514
- .table-container {
515
- font-size: 12px;
516
- }
517
-
518
- .table-container th,
519
- .table-container td {
520
- padding: 8px 10px;
521
- }
522
-
523
- .table-container td {
524
- max-width: 120px;
525
- }
526
- }
454
+ .widgetLink { margin-top: auto; padding-top: 0.5rem; }
527
455
 
528
- // Indicates that the widget is in edit mode with a glowing dashed border.
529
- .editModeWidget {
530
- border: 2px dashed var(--primary-color);
531
- box-shadow: 0 0 10px var(--primary-color);
532
- }
533
-
534
- // Global modal styling improvements (for Popup component modals)
535
- :global(.modalwrap) {
536
- position: fixed !important;
537
- top: 0 !important;
538
- left: 0 !important;
539
- right: 0 !important;
540
- bottom: 0 !important;
541
- width: 100vw !important;
542
- height: 100vh !important;
543
- background: rgba(var(--dark-rgb, 0, 0, 0), 0.75) !important;
544
- backdrop-filter: blur(6px) !important;
545
- z-index: 10000 !important;
546
- display: flex !important;
547
- align-items: center !important;
548
- justify-content: center !important;
549
- padding: 20px !important;
550
- box-sizing: border-box !important;
551
-
552
- .modal {
553
- background: var(--background-color, #ffffff) !important;
554
- border-radius: 16px !important;
555
- box-shadow: 0 25px 80px rgba(var(--dark-rgb, 0, 0, 0), 0.4) !important;
556
- max-width: 95vw !important;
557
- max-height: 90vh !important;
558
- width: 1200px !important;
559
- display: flex !important;
560
- flex-direction: column !important;
561
- overflow: hidden !important;
562
- animation: modalFadeIn 0.4s cubic-bezier(0.4, 0, 0.2, 1) !important;
563
- margin: 0 !important;
564
- position: relative !important;
565
- border: 1px solid rgba(var(--primary-rgb, 0, 123, 255), 0.1) !important;
566
- }
456
+ .btn,
457
+ .btn-alt,
458
+ .btn-primary,
459
+ .btn-secondary {
460
+ display: inline-flex;
461
+ align-items: center;
462
+ justify-content: center;
463
+ gap: 0.4rem;
464
+ height: 34px;
465
+ padding: 0 0.9rem;
466
+ border: 1px solid var(--dash-line);
467
+ border-radius: var(--btn-br, 8px);
468
+ background: var(--dash-surface);
469
+ color: var(--dash-primary);
470
+ font-family: inherit;
471
+ font-size: 0.8125rem;
472
+ font-weight: 600;
473
+ line-height: 1;
474
+ text-transform: none; // was uppercase; a button is not a label
475
+ cursor: pointer;
476
+ transition: background var(--dash-motion), border-color var(--dash-motion);
567
477
  }
568
478
 
569
- :global(.modal__header) {
570
- background: linear-gradient(135deg, var(--primary-color, #007bff) 0%, var(--secondary-color, #6c757d) 100%) !important;
571
- color: var(--light-color, #ffffff) !important;
572
- padding: 24px 28px !important;
573
- border-radius: 16px 16px 0 0 !important;
574
- display: flex !important;
575
- justify-content: space-between !important;
576
- align-items: center !important;
577
- border-bottom: 1px solid rgba(var(--light-rgb, 255, 255, 255), 0.1) !important;
578
-
579
- h1 {
580
- margin: 0 !important;
581
- font-size: 20px !important;
582
- font-weight: 600 !important;
583
- color: var(--light-color, #ffffff) !important;
584
- text-shadow: 0 1px 2px rgba(var(--dark-rgb, 0, 0, 0), 0.1) !important;
585
- }
479
+ .btn:hover,
480
+ .btn-alt:hover,
481
+ .btn-secondary:hover {
482
+ background: var(--dash-hover);
483
+ border-color: color-mix(in srgb, var(--dash-primary) 35%, var(--dash-line));
586
484
  }
587
485
 
588
- :global(.modal__close) {
589
- background: rgba(var(--light-rgb, 255, 255, 255), 0.2) !important;
590
- border: none !important;
591
- color: var(--light-color, #ffffff) !important;
592
- width: 32px !important;
593
- height: 32px !important;
594
- border-radius: 50% !important;
595
- display: flex !important;
596
- align-items: center !important;
597
- justify-content: center !important;
598
- cursor: pointer !important;
599
- transition: background-color 0.2s !important;
600
-
601
- &:hover {
602
- background: rgba(var(--light-rgb, 255, 255, 255), 0.3) !important;
603
- }
486
+ .btn-primary {
487
+ background: var(--dash-primary);
488
+ border-color: var(--dash-primary);
489
+ color: #fff;
604
490
  }
605
491
 
606
- :global(.modal__content) {
607
- max-height: 70vh !important;
608
- overflow-y: auto !important;
609
- padding: 25px !important;
610
- flex: 1 !important;
611
-
612
- .table-container {
613
- margin-top: 0 !important;
614
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1) !important;
615
-
616
- table {
617
- font-size: 13px !important;
618
- }
619
-
620
- th {
621
- background: var(--item-color, #f8f9fa) !important;
622
- color: var(--paragraph-color, #333) !important;
623
- font-weight: 600 !important;
624
- padding: 12px 15px !important;
625
- font-size: 12px !important;
626
- text-transform: uppercase !important;
627
- letter-spacing: 0.5px !important;
628
- border-bottom: 1px solid rgba(var(--primary-rgb, 0, 123, 255), 0.1) !important;
629
- }
630
-
631
- td {
632
- padding: 12px 15px !important;
633
- vertical-align: middle !important;
634
- color: var(--paragraph-color, #555) !important;
635
- border-bottom: 1px solid rgba(var(--item-color-rgb, 234, 236, 240), 0.5) !important;
636
- }
637
-
638
- tbody tr {
639
- transition: background-color 0.15s ease !important;
640
-
641
- &:hover {
642
- background-color: rgba(var(--primary-rgb, 0, 123, 255), 0.05) !important;
643
- }
644
- }
645
- }
492
+ .btn-primary:hover {
493
+ background: color-mix(in srgb, #000 10%, var(--dash-primary));
646
494
  }
647
495
 
648
- @keyframes modalFadeIn {
649
- from {
650
- opacity: 0;
651
- transform: scale(0.9) translateY(-20px);
652
- }
653
- to {
654
- opacity: 1;
655
- transform: scale(1) translateY(0);
656
- }
496
+ .btn:focus-visible,
497
+ .btn-alt:focus-visible,
498
+ .btn-primary:focus-visible,
499
+ .btn-secondary:focus-visible {
500
+ outline: 2px solid var(--dash-accent);
501
+ outline-offset: 2px;
657
502
  }
658
503
 
659
- // Responsive modal behavior
660
- @media (max-width: 768px) {
661
- .modalwrap {
662
- padding: 10px;
663
-
664
- .modal {
665
- width: 100%;
666
- max-height: 95vh;
667
-
668
- .modal__header {
669
- padding: 15px 20px;
670
-
671
- h1 {
672
- font-size: 18px;
673
- }
674
- }
675
-
676
- .modal__content {
677
- padding: 20px;
678
-
679
- .table-container {
680
- font-size: 12px;
681
-
682
- th, td {
683
- padding: 8px 10px;
684
- }
685
- }
686
- }
687
- }
688
- }
689
- }
504
+ // ---------------------------------------------------------------------------
505
+ // Filters
506
+ // ---------------------------------------------------------------------------
690
507
 
691
- // Container for the edit mode toggle button.
692
- .editButtonContainer {
693
- position: fixed;
694
- bottom: 1rem;
695
- right: 1rem;
696
- z-index: 1000; // Ensures the button stays on top of other elements
508
+ .filter-container {
509
+ padding: 0.75rem;
510
+ background: var(--dash-quiet);
511
+ border: 1px solid var(--dash-line);
512
+ border-radius: var(--radius-sm, 8px);
697
513
  }
698
514
 
699
- .editButtonContainer .btn {
700
- padding: 0.5rem 1rem;
701
- background: var(--primary-color);
702
- color: var(--tertiary-color);
703
- border: none;
704
- border-radius: var(--br);
705
- font-size: 0.875rem;
515
+ .filter-header {
516
+ margin-bottom: 0.5rem;
517
+ color: var(--dash-muted);
518
+ font-size: 0.6875rem;
706
519
  font-weight: 600;
520
+ letter-spacing: 0.04em;
707
521
  text-transform: uppercase;
708
- cursor: pointer;
709
- transition: background-color 0.25s ease;
710
- }
711
-
712
- .editButtonContainer .btn:hover {
713
- background-color: var(--secondary-color);
714
522
  }
715
523
 
716
- // Normal edit mode style for non-highlighted widgets
717
- .editModeWidgetNormal {
718
- border: 2px dashed var(--primary-color);
719
- box-shadow: 0 0 10px var(--primary-color);
720
-
721
- // Use the default resize tools positioning for normal widgets.
722
- .resizeTools {
723
- top: -5px;
724
- right: 0px;
725
- }
726
- }
727
-
728
- // Edit mode style for highlighted widgets (using a contrasting color)
729
- .editModeWidgetHighlight {
730
- border: 2px dashed var(--secondary-color);
731
- box-shadow: 0 0 10px var(--secondary-color);
732
-
733
- // Position resize tools further up to avoid blending with the background.
734
- .resizeTools {
735
- top: -5px; // Adjust this value as needed
736
- right: 0px;
737
- }
738
- }
739
-
740
- // Resizing tool container (common styling)
741
- .resizeTools {
742
- position: absolute;
524
+ // One row of controls above the chart, wrapping - not a stacked column.
525
+ .filter-fields {
743
526
  display: flex;
744
- gap: 5px;
745
-
746
- button {
747
- background: var(--primary-color);
748
- color: var(--tertiary-color);
749
- border: none;
750
- border-radius: 3px;
751
- padding: 0.25rem 0.5rem;
752
- cursor: pointer;
753
- font-size: 0.75rem;
754
- transition: background-color 0.2s;
755
-
756
- &:hover {
757
- background-color: rgba(var(--primary-rgb), 0.8);
758
- }
759
- }
527
+ flex-wrap: wrap;
528
+ align-items: flex-end;
529
+ gap: 0.5rem;
760
530
  }
761
531
 
762
- // Override the resize button for highlighted widgets
763
- .editModeWidgetHighlight .resizeTools button {
764
- background: var(--tertiary-color); // Use a contrasting background
765
- color: var(--primary-color); // Ensure the text is legible
766
-
767
- &:hover {
768
- background-color: rgba(var(--tertiary-color-rgb), 0.8);
769
- }
532
+ .filter-field,
533
+ .filter-field-highlight {
534
+ display: flex;
535
+ flex-direction: column;
536
+ gap: 0.25rem;
537
+ min-width: 8rem;
538
+ flex: 1 1 8rem;
539
+ font-size: 0.75rem;
540
+ color: var(--dash-muted);
770
541
  }
771
542
 
772
- .widgetItem {
773
- position: relative;
543
+ .filter-field input,
544
+ .filter-field select,
545
+ .filter-field-highlight input,
546
+ .filter-field-highlight select {
547
+ height: 34px;
548
+ padding: 0 0.6rem;
549
+ border: 1px solid var(--dash-line);
550
+ border-radius: var(--radius-sm, 6px);
551
+ background: var(--dash-surface);
552
+ color: var(--dash-ink);
553
+ font-family: inherit;
554
+ font-size: 0.8125rem;
774
555
  }
775
556
 
776
- /* Styles for the Add Widget Popup */
777
- .addWidgetSelect {
778
- width: 100%;
779
- padding: 10px;
780
- margin-bottom: 1rem;
781
- border: 1px solid #dadce0;
782
- border-radius: 5px;
783
- font-size: 1rem;
557
+ .filter-field input:focus,
558
+ .filter-field select:focus,
559
+ .filter-field-highlight input:focus,
560
+ .filter-field-highlight select:focus {
561
+ outline: 2px solid var(--dash-accent);
562
+ outline-offset: 1px;
784
563
  }
785
564
 
786
- .addWidgetButton {
787
- width: 100%;
788
- padding: 10px;
789
- background: var(--primary-color);
790
- color: var(--tertiary-color);
791
- border: none;
792
- border-radius: 5px;
793
- font-size: 1rem;
794
- font-weight: 600;
795
- text-transform: uppercase;
796
- cursor: pointer;
797
- transition: background-color 0.25s ease;
565
+ .filter-actions {
566
+ display: flex;
567
+ gap: 0.5rem;
568
+ align-items: flex-end;
798
569
  }
799
570
 
800
- .addWidgetButton:hover {
801
- background: var(--secondary-color);
802
- }
571
+ // ---------------------------------------------------------------------------
572
+ // Empty & loading
573
+ // ---------------------------------------------------------------------------
803
574
 
575
+ .noData,
804
576
  .noWidgetsMessage {
577
+ display: flex;
578
+ align-items: center;
579
+ justify-content: center;
580
+ min-height: 6rem;
581
+ padding: 1.5rem 1rem;
582
+ color: var(--dash-muted);
583
+ font-size: 0.875rem;
805
584
  text-align: center;
806
- font-size: 1rem;
807
- color: #888;
808
- padding: 20px;
809
- }
810
-
811
- .noData {
812
- text-align: center;
813
- font-size: 1rem;
814
- color: #888;
815
- padding: 20px;
816
- }
817
-
818
- /* ---------------------------------------------------------------------------
819
- Loading skeletons
820
- Neutral greys only (no brand colours) so every tenant gets a safe default.
821
- Keyframes are uniquely prefixed to avoid colliding with host app globals.
822
- --------------------------------------------------------------------------- */
823
- @keyframes visns-dash-skeleton-sweep {
824
- 0% {
825
- background-position: 150% 0;
826
- }
827
- 100% {
828
- background-position: -150% 0;
829
- }
830
585
  }
831
586
 
832
- .skeleton {
833
- width: 100%;
834
- box-sizing: border-box;
835
- }
836
-
837
- .skeletonBlock {
838
- display: block;
839
- box-sizing: border-box;
840
- border-radius: var(--br, 5px);
841
- background-color: rgba(0, 0, 0, 0.06);
842
- background-image: linear-gradient(
587
+ .noWidgetsMessage {
588
+ grid-column: 1 / -1;
589
+ background: var(--dash-surface);
590
+ border: 1px dashed var(--dash-line);
591
+ border-radius: var(--dash-radius);
592
+ }
593
+
594
+ // A shimmer that respects reduced-motion, which the old one did not.
595
+ .skeleton,
596
+ .skeletonBlock,
597
+ .skeletonButton,
598
+ .skeletonCircle,
599
+ .skeletonCounterValue,
600
+ .skeletonListRow,
601
+ .skeletonStrip,
602
+ .skeletonTableHeader,
603
+ .skeletonTableRow {
604
+ background: linear-gradient(
843
605
  90deg,
844
- rgba(0, 0, 0, 0) 0%,
845
- rgba(0, 0, 0, 0.05) 50%,
846
- rgba(0, 0, 0, 0) 100%
606
+ var(--dash-quiet) 25%,
607
+ color-mix(in srgb, var(--dash-ink) 8%, var(--dash-surface)) 37%,
608
+ var(--dash-quiet) 63%
847
609
  );
848
- background-repeat: no-repeat;
849
- background-size: 150% 100%;
850
- animation: visns-dash-skeleton-sweep 1.4s ease-in-out infinite;
851
- }
852
-
853
- /* Counter: matches .widgetNo (2rem bottom padding, 3em value) */
854
- .skeletonCounter {
855
- width: 100%;
856
- padding: 0 0 2rem 0;
857
- box-sizing: border-box;
610
+ background-size: 400% 100%;
611
+ animation: dashShimmer 1.4s ease-in-out infinite;
612
+ border-radius: var(--radius-sm, 6px);
858
613
  }
859
614
 
860
- .skeletonCounterValue {
861
- width: 55%;
862
- max-width: 220px;
863
- height: 3.6rem;
864
- margin: 0 auto;
615
+ @keyframes dashShimmer {
616
+ 0% { background-position: 100% 50%; }
617
+ 100% { background-position: 0 50%; }
865
618
  }
866
619
 
867
- /* Counter button: matches .widgetLink .btn */
868
- .skeletonButtonRow {
869
- width: 100%;
870
- display: block;
620
+ @media (prefers-reduced-motion: reduce) {
621
+ .skeleton,
622
+ .skeletonBlock,
623
+ .skeletonButton,
624
+ .skeletonCircle,
625
+ .skeletonCounterValue,
626
+ .skeletonListRow,
627
+ .skeletonStrip,
628
+ .skeletonTableHeader,
629
+ .skeletonTableRow {
630
+ animation: none;
631
+ }
871
632
  }
872
633
 
873
- .skeletonButton {
874
- width: 180px;
875
- max-width: 100%;
876
- height: 34px;
877
- margin: 0 auto;
878
- }
634
+ .skeleton { min-height: 1rem; }
635
+ .skeletonBlock { height: 1rem; }
636
+ .skeletonButton { height: 34px; width: 8rem; }
637
+ .skeletonCircle { width: 9rem; height: 9rem; border-radius: 50%; margin: 0 auto; }
638
+ .skeletonCounter { display: flex; flex-direction: column; gap: 0.75rem; }
639
+ .skeletonCounterValue { height: 3rem; width: 60%; }
640
+ .skeletonList { display: flex; flex-direction: column; gap: 0.5rem; }
641
+ .skeletonListRow { height: 2.25rem; }
642
+ .skeletonTable { display: flex; flex-direction: column; gap: 0.4rem; }
643
+ .skeletonTableHeader { height: 2rem; }
644
+ .skeletonTableRow { height: 1.75rem; }
645
+ .skeletonTimeline { display: flex; flex-direction: column; gap: 0.75rem; }
646
+ .skeletonGeneric { display: flex; flex-direction: column; gap: 0.75rem; }
647
+ .skeletonButtonRow { display: flex; gap: 0.5rem; }
648
+ .skeletonChart { display: flex; align-items: flex-end; gap: 0.5rem; height: 12rem; }
649
+ .skeletonChartStrips { display: flex; align-items: flex-end; gap: 0.5rem; width: 100%; height: 100%; }
650
+ .skeletonStrip { flex: 1; }
651
+
652
+ // ---------------------------------------------------------------------------
653
+ // Edit mode
654
+ // ---------------------------------------------------------------------------
879
655
 
880
- /* Charts: the wrapper takes widget.height (or the 600px chart default) inline */
881
- .skeletonChart {
882
- width: 100%;
656
+ .editButtonContainer {
883
657
  display: flex;
884
- flex-direction: column;
658
+ flex-wrap: wrap;
885
659
  align-items: center;
886
- justify-content: center;
887
- box-sizing: border-box;
888
- padding: 20px 0;
660
+ gap: 0.5rem;
661
+ margin-bottom: var(--dash-gap);
889
662
  }
890
663
 
891
- .skeletonChartStrips {
892
- align-items: flex-start;
893
- gap: 14px;
664
+ .addWidgetSelect,
665
+ .addWidgetButton {
666
+ height: 34px;
667
+ padding: 0 0.75rem;
668
+ border: 1px solid var(--dash-line);
669
+ border-radius: var(--btn-br, 8px);
670
+ background: var(--dash-surface);
671
+ color: var(--dash-ink);
672
+ font-family: inherit;
673
+ font-size: 0.8125rem;
674
+ cursor: pointer;
894
675
  }
895
676
 
896
- .skeletonStrip {
897
- height: 16px;
898
- min-height: 16px;
677
+ .editModeWidget,
678
+ .editModeWidgetNormal,
679
+ .editModeWidgetHighlight {
680
+ outline: 2px dashed color-mix(in srgb, var(--dash-primary) 35%, transparent);
681
+ outline-offset: 3px;
682
+ border-radius: var(--dash-radius);
899
683
  }
900
684
 
901
- .skeletonCircle {
902
- width: 60%;
903
- max-width: 260px;
904
- aspect-ratio: 1 / 1;
905
- border-radius: 50%;
685
+ .editModeWidgetHighlight {
686
+ outline-color: color-mix(in srgb, var(--dash-accent) 60%, transparent);
906
687
  }
907
688
 
908
- /* Table: header 44px / rows 38px, matching .table-container th and td */
909
- .skeletonTable {
910
- width: 100%;
689
+ .widgetTools {
911
690
  display: flex;
912
- flex-direction: column;
913
- gap: 1px;
914
- box-sizing: border-box;
915
- border: 1px solid #dadce0;
916
- border-radius: var(--br, 5px);
917
- overflow: hidden;
691
+ justify-content: flex-end;
692
+ margin-top: 0.5rem;
693
+ padding-top: 0.5rem;
694
+ border-top: 1px solid var(--dash-line);
918
695
  }
919
696
 
920
- .skeletonTableHeader {
921
- width: 100%;
922
- height: 44px;
923
- border-radius: 0;
697
+ .resizeTools {
698
+ display: flex;
699
+ gap: 0.25rem;
924
700
  }
925
701
 
926
- .skeletonTableRow {
927
- width: 100%;
928
- height: 38px;
929
- border-radius: 0;
702
+ .resizeTools button {
703
+ display: inline-flex;
704
+ align-items: center;
705
+ justify-content: center;
706
+ width: 28px;
707
+ height: 28px;
708
+ padding: 0;
709
+ border: 1px solid var(--dash-line);
710
+ border-radius: var(--radius-sm, 6px);
711
+ background: var(--dash-surface);
712
+ color: var(--dash-muted);
713
+ cursor: pointer;
714
+ transition: background var(--dash-motion), color var(--dash-motion);
930
715
  }
931
716
 
932
- /* List: rows sized like .dashList li button */
933
- .skeletonList {
934
- width: 100%;
935
- display: flex;
936
- flex-direction: column;
937
- gap: 1px;
938
- font-size: 0.875rem;
717
+ .resizeTools button:hover {
718
+ background: var(--dash-hover);
719
+ color: var(--dash-primary);
939
720
  }
940
721
 
941
- .skeletonListRow {
942
- width: 100%;
943
- height: 30px;
944
- border-radius: 0;
945
-
946
- &:first-child {
947
- border-top-left-radius: var(--br, 5px);
948
- border-top-right-radius: var(--br, 5px);
949
- }
722
+ // ---------------------------------------------------------------------------
723
+ // Modal
724
+ // ---------------------------------------------------------------------------
950
725
 
951
- &:last-child {
952
- border-bottom-left-radius: var(--br, 5px);
953
- border-bottom-right-radius: var(--br, 5px);
954
- }
726
+ .modalwrap {
727
+ position: fixed;
728
+ inset: 0;
729
+ z-index: 1000;
730
+ display: flex;
731
+ align-items: center;
732
+ justify-content: center;
733
+ padding: 1rem;
734
+ background: color-mix(in srgb, #000 55%, transparent);
735
+ backdrop-filter: blur(2px);
955
736
  }
956
737
 
957
- .skeletonTimeline {
958
- width: 100%;
738
+ .modal {
739
+ width: min(720px, 100%);
740
+ max-height: 85vh;
741
+ overflow: auto;
742
+ background: var(--dash-surface);
743
+ border-radius: var(--dash-radius);
744
+ box-shadow: var(--dash-shadow-lift);
959
745
  }
960
746
 
961
- .skeletonGeneric {
962
- width: 100%;
963
- height: 120px;
747
+ .modal__header {
748
+ display: flex;
749
+ align-items: center;
750
+ justify-content: space-between;
751
+ gap: 1rem;
752
+ padding: 1rem 1.25rem;
753
+ border-bottom: 1px solid var(--dash-line);
754
+ font-weight: 600;
755
+ color: var(--dash-heading);
964
756
  }
965
757
 
966
- /* Keep the shape, drop the motion */
967
- @media (prefers-reduced-motion: reduce) {
968
- .skeletonBlock {
969
- animation: none;
970
- }
971
- }
758
+ .modal__content { padding: 1.25rem; }