@saasicat/ui-vue 0.22.2 → 0.24.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 (119) hide show
  1. package/README.md +1 -1
  2. package/dist/{chunk-LY52T4N4.js → chunk-TQQMKWN6.js} +58 -1
  3. package/dist/{chunk-XMMFN2WJ.js → chunk-VYMTWCTT.js} +51 -1
  4. package/dist/client/index.cjs +57 -0
  5. package/dist/client/index.d.cts +58 -1
  6. package/dist/client/index.d.ts +58 -1
  7. package/dist/client/index.js +15 -1
  8. package/dist/index.cjs +321 -159
  9. package/dist/index.d.cts +67 -4
  10. package/dist/index.d.ts +67 -4
  11. package/dist/index.js +72 -2
  12. package/dist/quasar/index.cjs +116 -20
  13. package/dist/quasar/index.d.cts +48 -3
  14. package/dist/quasar/index.d.ts +48 -3
  15. package/dist/quasar/index.js +58 -3
  16. package/dist/{use-super-admin-i18n-CgTuNi8h.d.cts → use-sa-theme-B5t7oXph.d.cts} +51 -1
  17. package/dist/{use-super-admin-i18n-CHNdsSlm.d.ts → use-sa-theme-OUBlaqgl.d.ts} +51 -1
  18. package/package.json +5 -2
  19. package/src/client/identity-accents.ts +129 -0
  20. package/src/client/index.ts +1 -0
  21. package/src/components/BundleVersionPublishDialog.vue +17 -17
  22. package/src/components/KvBlock.vue +6 -6
  23. package/src/components/MarketingPromotionsTab.vue +84 -80
  24. package/src/components/MfaPromptDialog.vue +2 -2
  25. package/src/components/TenantActionConfirmDialog.vue +5 -5
  26. package/src/components/VersionDiffPreview.vue +18 -18
  27. package/src/components/bundle-editor/BundleCreatePanel.vue +52 -52
  28. package/src/components/bundle-editor/BundleFeaturesEditor.vue +20 -20
  29. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +33 -33
  30. package/src/components/bundle-editor/BundleQuotasEditor.vue +26 -26
  31. package/src/components/bundle-editor/BundleStatusBanner.vue +21 -21
  32. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +49 -49
  33. package/src/components/bundle-editor/BundleVersionStrip.vue +30 -30
  34. package/src/components/dialogs/PilotCreateDialog.vue +20 -20
  35. package/src/components/dialogs/PilotEditDialog.vue +6 -6
  36. package/src/components/dialogs/PromoCodeCreateDialog.vue +9 -9
  37. package/src/components/dialogs/PromoCodeDialogFields.vue +64 -63
  38. package/src/components/dialogs/PromoCodeEditDialog.vue +11 -11
  39. package/src/components/dialogs/pilot-dialog.css +48 -48
  40. package/src/components/plan/PlanCycleToggle.vue +9 -16
  41. package/src/components/plan/PlanGrid.vue +30 -34
  42. package/src/components/plan/PriceSummary.vue +35 -35
  43. package/src/components/plan/PromoCodeInput.vue +26 -26
  44. package/src/components/plan/PublicBundleGrid.vue +32 -28
  45. package/src/components/plan-create-dialog/PlanCreateDialog.vue +50 -50
  46. package/src/components/plan-detail/PlanDetail.vue +178 -157
  47. package/src/components/plan-detail/PlanTitleEdit.vue +11 -11
  48. package/src/components/plan-list/PlanList.vue +116 -100
  49. package/src/components/plan-matrix/PlanMatrix.vue +97 -114
  50. package/src/components/plan-review/PlanReview.vue +61 -57
  51. package/src/components/plan-version-editor/PlanVersionEditor.vue +246 -203
  52. package/src/components/wizard-stepper/WizardStepper.vue +17 -17
  53. package/src/index.ts +1 -0
  54. package/src/pages-standard/AdminLayout.vue +58 -37
  55. package/src/pages-standard/AdminManifestErrorPage.vue +6 -6
  56. package/src/pages-standard/AuditPage.vue +3 -3
  57. package/src/pages-standard/BundlesPage.vue +37 -37
  58. package/src/pages-standard/DashboardPage.vue +22 -19
  59. package/src/pages-standard/DiscoveryPage.vue +55 -49
  60. package/src/pages-standard/EmailHistoryPage.vue +53 -71
  61. package/src/pages-standard/MarketingCatalogPage.vue +245 -221
  62. package/src/pages-standard/PilotsPage.vue +7 -6
  63. package/src/pages-standard/PlatformEmailPage.vue +18 -18
  64. package/src/pages-standard/PromoCodeDetailPage.vue +3 -3
  65. package/src/pages-standard/PromoCodesPage.vue +10 -7
  66. package/src/pages-standard/SuperAdminLoginPage.vue +39 -25
  67. package/src/pages-standard/SuperAdminSetupWizard.vue +44 -33
  68. package/src/pages-standard/TenantDetailPage.vue +7 -7
  69. package/src/pages-standard/TenantsPage.vue +16 -50
  70. package/src/pages-standard/bundles-page/BundleAccordionList.vue +13 -13
  71. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +24 -24
  72. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +22 -22
  73. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +39 -39
  74. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +26 -26
  75. package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +1 -1
  76. package/src/pages-standard/discovery-page/discovery-ui.ts +10 -7
  77. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +2 -5
  78. package/src/pages-standard/plan-versions/PlanDiffCard.vue +27 -20
  79. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +24 -16
  80. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +45 -45
  81. package/src/pages-standard/plans-page/PlanBundleOverview.vue +21 -15
  82. package/src/pages-standard/plans-page/PlanPublishDialog.vue +4 -4
  83. package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
  84. package/src/pages-standard/sa-theme.css +9 -687
  85. package/src/pages-standard/tenants/StatusPill.vue +13 -13
  86. package/src/pages-standard/tenants/format.ts +11 -12
  87. package/src/pages-tenant/LimitsRow.vue +4 -4
  88. package/src/pages-tenant/MySubscriptionBundlesPage.vue +70 -61
  89. package/src/pages-tenant/OnboardingConfigurator.vue +25 -25
  90. package/src/pages-tenant/PackageSnapshotPanel.vue +10 -16
  91. package/src/pages-tenant/PendingVersionBanner.vue +12 -12
  92. package/src/pages-tenant/PlanChangeWizard.vue +22 -76
  93. package/src/pages-tenant/TenantPlanSection.vue +19 -69
  94. package/src/pages-tenant/UsageBar.vue +7 -7
  95. package/src/pages-tenant/default-i18n.ts +113 -0
  96. package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +14 -14
  97. package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +10 -10
  98. package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +2 -2
  99. package/src/quasar/create-super-admin-app.ts +96 -1
  100. package/src/quasar/dark-bridge.ts +66 -0
  101. package/src/quasar/index.ts +1 -0
  102. package/src/ui/theme/_breakpoints.scss +34 -0
  103. package/src/ui/theme/base.css +50 -0
  104. package/src/ui/theme/components/body.css +23 -0
  105. package/src/ui/theme/components/button.css +158 -0
  106. package/src/ui/theme/components/card.css +74 -0
  107. package/src/ui/theme/components/field.css +38 -0
  108. package/src/ui/theme/components/hero.css +46 -0
  109. package/src/ui/theme/components/paginator.css +63 -0
  110. package/src/ui/theme/components/section.css +67 -0
  111. package/src/ui/theme/components/statistics.css +189 -0
  112. package/src/ui/theme/components/table.css +60 -0
  113. package/src/ui/theme/index.css +42 -0
  114. package/src/ui/theme/tokens.legacy.css +147 -0
  115. package/src/ui/theme/tokens.primitive.css +112 -0
  116. package/src/ui/theme/tokens.scale.css +103 -0
  117. package/src/ui/theme/tokens.semantic.dark.css +185 -0
  118. package/src/ui/theme/tokens.semantic.light.css +242 -0
  119. package/src/vue/use-sa-theme.ts +154 -0
@@ -1,689 +1,11 @@
1
- /* SuperAdmin UI theme CSS variables for the platform pages.
2
- *
3
- * Consumer apps can override individual
4
- * variables via their own `:root` block in the app CSS like
5
- * any CSS var override. Class names follow the `sa-` prefix convention,
6
- * i.e. an app CSS can, for example, re-style `.sa-tenants__title` precisely
7
- * without touching the platform code.
8
- *
1
+ /* Moved. The theme now lives in `src/ui/theme/` and is imported as
2
+ * `@saasicat/ui-vue/theme.css`.
3
+ *
4
+ * A global theme in a folder of pages was the wrong address: a reader looking
5
+ * for "where are the colours decided" had to know that one of nineteen page
6
+ * files was not a page. This file stays for one release so that existing
7
+ * `import '@saasicat/ui-vue/sa-theme.css'` lines keep working; it is removed
8
+ * in 1.0.
9
9
  */
10
10
 
11
- :root {
12
- --sa-bg-app: #f1f5f9;
13
- --sa-bg-surface: #ffffff;
14
- --sa-primary: #3f6bff;
15
- --sa-primary-soft: rgba(63, 107, 255, 0.08);
16
- --sa-primary-softer: rgba(63, 107, 255, 0.04);
17
- --sa-primary-border: rgba(63, 107, 255, 0.18);
18
- --sa-positive: #047857;
19
- --sa-positive-light: #10b981;
20
- --sa-positive-soft: rgba(4, 120, 87, 0.1);
21
- --sa-negative: #dc2626;
22
- --sa-negative-soft: rgba(220, 38, 38, 0.08);
23
- --sa-warning: #b45309;
24
- --sa-warning-soft: rgba(245, 158, 11, 0.1);
25
- --sa-amber: #f59e0b;
26
- --sa-amber-light: #fbbf24;
27
- --sa-amber-border: rgba(245, 158, 11, 0.3);
28
- --sa-heading: #0f172a;
29
- --sa-body: #1e293b;
30
- --sa-muted: #64748b;
31
- --sa-muted-dark: #475569;
32
- --sa-border: #e2e8f0;
33
- --sa-border-soft: #f1f5f9;
34
- --sa-bg-surface-2: #f8fafc;
35
- --sa-border-strong: #d1d5db;
36
- --sa-muted-light: #94a3b8;
37
- --sa-primary-50: #eff6ff;
38
- --sa-positive-bg: #ecfdf5;
39
- --sa-warning-bg: #fffbeb;
40
- --sa-primary-strong: #1d4ed8;
41
-
42
- /* Corner radii, largest to smallest, so the nesting reads as hierarchy:
43
- * a section is a bigger block than the cards inside it, a card bigger
44
- * than a tile. `--sa-radius-tile` is the single declaration every
45
- * statistic tile in the admin surface resolves to. */
46
- --sa-radius-hero: 16px;
47
- --sa-radius-section: 14px;
48
- --sa-radius-card: 12px;
49
- --sa-radius-head: 10px;
50
- --sa-radius-tile: 10px;
51
- --sa-radius-control: 7px;
52
- --sa-section-pad: 20px;
53
- --sa-radius-field: 8px;
54
-
55
- --sa-font-head: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
56
- --sa-font-body: 'Inter', system-ui, sans-serif;
57
- --sa-font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
58
- }
59
-
60
- /* ─────────────────────────────────────────────────────────────
61
- * Shared page chrome — page frame, header strip, section, stat pills, card.
62
- *
63
- * These classes are the styling half of the page shell. The structural half is
64
- * the `AdminPage` / `AdminHero` / `AdminSection` components in
65
- * `components/admin-page/`, and pages are expected to reach for those rather
66
- * than write the markup by hand: a component with a required `title` prop
67
- * cannot be half-adopted the way a class name can.
68
- *
69
- * The classes stay public and unprefixed by scoping on purpose, so a consumer
70
- * app can restyle `.sa-page-head__title` from its own CSS without patching the
71
- * platform.
72
- * ───────────────────────────────────────────────────────────── */
73
-
74
- /* The app canvas. It sits on the layout rather than on the page so that the
75
- * gutters beside `max-width: 1600px` are painted too — and so a page can be a
76
- * transparent frame while sections carry the surfaces. Declared here rather
77
- * than on `body`: both known consumer apps load their own stylesheet after
78
- * this one, and a `body` rule would lose to them. */
79
- .sa-admin-layout,
80
- .sa-admin-content {
81
- background: var(--sa-bg-app);
82
- }
83
-
84
- /* Page frame — min-height, the page padding and the admin type, in one place
85
- * instead of once per page (see AdminPage). No surface of its own.
86
- *
87
- * The font and text colour belong here because the hosting app's stylesheet
88
- * loads after this one: without a rule on the page itself, admin pages would
89
- * inherit whatever the consumer sets. Two full-page views used to pin both on
90
- * their own root, which is exactly the divergence this prevents. */
91
- .sa-page {
92
- min-height: calc(100vh - 56px);
93
- padding: 20px 28px 28px;
94
- font-family: var(--sa-font-body);
95
- color: var(--sa-body);
96
- }
97
-
98
- /* Consumer apps load Quasar, whose element-level h1–h6 rules carry display
99
- * sizes and huge line-heights. Neutralised here so a bare heading inside a
100
- * page is legible without a class of its own.
101
- *
102
- * `:where()` keeps this at the specificity of a single class, so any heading
103
- * class in the package still wins — this is a baseline, not a verdict. Three
104
- * views used to carry their own copy of it, each covering a different subset
105
- * of h1–h6. */
106
- .sa-page :where(h1, h2, h3, h4, h5, h6) {
107
- margin: 0;
108
- line-height: 1.2;
109
- }
110
-
111
- /* Border-box for the whole page. Six views declared this for their own
112
- * subtree, which is the same rule six times with six different roots. */
113
- .sa-page * {
114
- box-sizing: border-box;
115
- }
116
-
117
- /* The hero reads as a banner rather than as the first paragraph of the page:
118
- * a tinted, bordered block that separates "which page am I on" from the
119
- * content below it. Tint and border come from the primary token, so an app
120
- * that overrides `--sa-primary` re-tints the hero with it. */
121
- .sa-page-head {
122
- /* Top-aligned, so the controls sit level with the title rather than
123
- * drifting down with the subtitle's height. */
124
- display: flex;
125
- align-items: flex-start;
126
- gap: 20px;
127
- margin-bottom: 20px;
128
- flex-wrap: wrap;
129
- padding: 13px 22px 15px;
130
- border: 1px solid var(--sa-primary-border);
131
- border-radius: var(--sa-radius-hero);
132
- background: linear-gradient(135deg, var(--sa-primary-soft) 0%, var(--sa-primary-softer) 100%);
133
- }
134
- .sa-page-head__title {
135
- margin: 0;
136
- font-family: var(--sa-font-head);
137
- font-size: 22px;
138
- font-weight: 700;
139
- letter-spacing: -0.02em;
140
- line-height: 1.25;
141
- color: var(--sa-heading);
142
- }
143
- .sa-page-head__sub {
144
- margin: 1px 0 0;
145
- font-size: 12.5px;
146
- line-height: 1.35;
147
- color: var(--sa-muted);
148
- }
149
- .sa-page-head__actions {
150
- margin-left: auto;
151
- display: flex;
152
- gap: 8px;
153
- align-items: center;
154
- }
155
-
156
- /* One button look across the admin surface, with two ways in.
157
- *
158
- * `.sa-btn` is for the hand-built buttons — eight files declared the same
159
- * bordered pill independently, differing only in which local token they named.
160
- *
161
- * The Quasar selectors are the second way: any `q-btn` placed in a hero's or a
162
- * section's action area adopts the same shape. That is what reaches the buttons
163
- * a consumer app puts into those slots — a component could only style what the
164
- * platform itself renders, and autohauspro's `#card-actions` button would have
165
- * stayed behind in the Quasar look while everything around it changed.
166
- *
167
- * Colour is left to Quasar on its own buttons: `color="primary"` stays a filled
168
- * primary, `flat` becomes the bordered surface below. Only shape and typography
169
- * are unified, because those are what read as "a different design". */
170
- .sa-btn,
171
- .sa-page-head__actions .q-btn,
172
- .sa-section__actions .q-btn {
173
- padding: 8px 14px;
174
- min-height: 0;
175
- border-radius: var(--sa-radius-control);
176
- font-family: var(--sa-font-body);
177
- font-size: 13px;
178
- font-weight: 500;
179
- line-height: 1.3;
180
- letter-spacing: 0;
181
- text-transform: none;
182
- }
183
-
184
- .sa-btn {
185
- display: inline-flex;
186
- align-items: center;
187
- gap: 7px;
188
- cursor: pointer;
189
- color: var(--sa-body);
190
- transition:
191
- background 0.12s,
192
- border-color 0.12s;
193
- }
194
-
195
- /* The default is a bordered surface, not a bare label. */
196
- .sa-btn,
197
- .sa-page-head__actions .q-btn--flat,
198
- .sa-section__actions .q-btn--flat {
199
- border: 1px solid var(--sa-border-strong);
200
- background: var(--sa-bg-surface);
201
- }
202
- .sa-btn:hover:not(:disabled),
203
- .sa-page-head__actions .q-btn--flat:hover,
204
- .sa-section__actions .q-btn--flat:hover {
205
- background: var(--sa-bg-surface-2);
206
- }
207
-
208
- .sa-btn--primary {
209
- background: var(--sa-primary);
210
- border-color: var(--sa-primary);
211
- color: #fff;
212
- }
213
- .sa-btn--primary:hover:not(:disabled) {
214
- background: var(--sa-primary-strong);
215
- border-color: var(--sa-primary-strong);
216
- }
217
-
218
- .sa-btn:disabled {
219
- opacity: 0.5;
220
- cursor: not-allowed;
221
- }
222
-
223
- /* Size and intent modifiers, carried over from the families that merged. */
224
- .sa-btn--sm {
225
- padding: 5px 8px;
226
- font-size: 12px;
227
- gap: 5px;
228
- }
229
- .sa-btn--ghost {
230
- border-color: transparent;
231
- background: transparent;
232
- }
233
- .sa-btn--ghost:hover:not(:disabled) {
234
- background: rgba(15, 23, 42, 0.05);
235
- }
236
- .sa-btn--danger {
237
- color: var(--sa-negative);
238
- }
239
- .sa-btn--danger:hover:not(:disabled) {
240
- background: var(--sa-negative-soft);
241
- }
242
- .sa-btn--flex {
243
- flex: 1;
244
- }
245
-
246
- /* Icon-only — the reload control, which carries no label and therefore needs
247
- * an `aria-label` at the call site. */
248
- .sa-btn--icon {
249
- padding: 8px;
250
- gap: 0;
251
- }
252
- .sa-btn--positive {
253
- color: var(--sa-positive);
254
- }
255
- .sa-btn--positive:hover:not(:disabled) {
256
- background: var(--sa-positive-soft);
257
- }
258
-
259
- /* Quasar cards round at 4px, which reads sharper than every surface around
260
- * them. One rule so a card inside the admin agrees with the sections and tiles
261
- * it sits between, without each page saying so. */
262
- .sa-page .q-card {
263
- border-radius: var(--sa-radius-card);
264
- }
265
-
266
- /* Form fields. Quasar's outlined input rounds at 4px, which reads sharper than
267
- * everything around it; the admin surface settles on 8px, the radius the
268
- * hand-built search boxes already used. Declared on the field control so it
269
- * covers inputs, selects and date pickers alike. */
270
- .sa-page .q-field--outlined .q-field__control,
271
- .sa-page .q-field--outlined .q-field__control:before,
272
- .sa-page .q-field--outlined .q-field__control:after {
273
- border-radius: var(--sa-radius-field);
274
- }
275
- /* Quasar's outlined control is transparent, which reads as a hole wherever it
276
- * sits on a section surface rather than on the page canvas. */
277
- .sa-page .q-field--outlined .q-field__control {
278
- background: var(--sa-bg-surface);
279
- }
280
-
281
- /* The filter row (see AdminFilters). Declared once so no page writes its own
282
- * flex rule — which is how five pages ended up with five filter bars. */
283
- .sa-filters .sa-filters__body {
284
- display: flex;
285
- gap: 10px;
286
- flex-wrap: wrap;
287
- align-items: center;
288
- }
289
- .sa-filters .sa-filters__body > * {
290
- flex: 1;
291
- min-width: 200px;
292
- }
293
-
294
- /* Statistics — one tile look for the whole admin surface (see AdminStatistics
295
- * and AdminKpi). Seven near-copies preceded this; two of them were byte
296
- * identical. Every radius here resolves to `--sa-radius-tile`, which is the
297
- * single place a tile's corner is decided.
298
- *
299
- * Two arrangements, because the tiles serve two jobs: `grid` for informational
300
- * rows that should share a baseline, `strip` for the clickable filter pills
301
- * that wrap along a toolbar. */
302
- .sa-stats {
303
- display: grid;
304
- grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
305
- /* Kept in step with GAP_PX in AdminStatistics, which subtracts the gap
306
- * total when it caps the track count. */
307
- gap: 10px;
308
- }
309
-
310
- .sa-kpi {
311
- display: flex;
312
- align-items: stretch;
313
- gap: 12px;
314
- background: var(--sa-bg-surface);
315
- border: 1px solid var(--sa-border);
316
- border-radius: var(--sa-radius-tile);
317
- padding: 10px 14px 9px;
318
- font: inherit;
319
- color: inherit;
320
- text-align: left;
321
- }
322
- /* The figure column. It carries the stacking so the tile itself can put an
323
- * icon beside it without a second set of rules. */
324
- .sa-kpi__text {
325
- display: flex;
326
- flex-direction: column;
327
- align-items: flex-start;
328
- gap: 2px;
329
- flex: 1;
330
- min-width: 0;
331
- }
332
- /* Subs line up across a row rather than floating under uneven values. */
333
- .sa-stats .sa-kpi__sub {
334
- margin-top: auto;
335
- }
336
-
337
- .sa-kpi__label {
338
- font-size: 11.5px;
339
- font-weight: 600;
340
- color: var(--sa-muted);
341
- order: 2;
342
- }
343
- .sa-kpi__value {
344
- font-size: 22px;
345
- font-weight: 700;
346
- letter-spacing: -0.02em;
347
- line-height: 1.05;
348
- color: var(--sa-heading);
349
- order: 1;
350
- }
351
- .sa-kpi__sub {
352
- font: 500 10px var(--sa-font-mono);
353
- letter-spacing: 0.04em;
354
- color: var(--sa-muted);
355
- padding-top: 1px;
356
- order: 3;
357
- }
358
- .sa-kpi__icon {
359
- display: inline-flex;
360
- align-items: center;
361
- justify-content: center;
362
- color: var(--sa-muted);
363
- }
364
-
365
- /* Inline tiles: the icon leads in a tinted square and the label sits above the
366
- * figure, for rows where the icon carries the identity of the number. */
367
- .sa-kpi--inline {
368
- align-items: center;
369
- padding: 12px 14px;
370
- }
371
- .sa-kpi--inline .sa-kpi__icon {
372
- width: 34px;
373
- height: 34px;
374
- flex: none;
375
- border-radius: var(--sa-radius-tile);
376
- background: var(--sa-primary-50);
377
- color: var(--sa-primary);
378
- }
379
- .sa-kpi--inline .sa-kpi__label {
380
- order: 1;
381
- text-transform: uppercase;
382
- letter-spacing: 0.06em;
383
- }
384
- .sa-kpi--inline .sa-kpi__value {
385
- order: 2;
386
- }
387
-
388
- /* Interactive tiles — rendered as <button> by AdminKpi when it has an action. */
389
- .sa-kpi--action {
390
- cursor: pointer;
391
- transition:
392
- border-color 0.12s,
393
- background 0.12s,
394
- box-shadow 0.12s;
395
- }
396
- .sa-kpi--action:hover {
397
- background: var(--sa-bg-surface-2);
398
- border-color: #cbd5e1;
399
- }
400
- .sa-kpi--selected {
401
- border-color: var(--sa-heading);
402
- box-shadow:
403
- 0 0 0 1px var(--sa-heading) inset,
404
- 0 2px 6px rgba(15, 23, 42, 0.06);
405
- }
406
-
407
- /* Tone. By default only the value takes the colour; `--surface` tints the
408
- * whole tile, which is what the discovery and draft tiles need to read as a
409
- * state rather than as a number. */
410
- .sa-kpi--positive .sa-kpi__value {
411
- color: var(--sa-positive);
412
- }
413
- .sa-kpi--warn .sa-kpi__value {
414
- color: var(--sa-warning);
415
- }
416
- .sa-kpi--danger .sa-kpi__value {
417
- color: var(--sa-negative);
418
- }
419
- .sa-kpi--info .sa-kpi__value {
420
- color: #1d4ed8;
421
- }
422
- .sa-kpi--muted .sa-kpi__value {
423
- color: var(--sa-muted-dark);
424
- }
425
- .sa-kpi--purple .sa-kpi__value {
426
- color: #6b21a8;
427
- }
428
-
429
- .sa-kpi--surface.sa-kpi--positive {
430
- border-color: #a7f3d0;
431
- background: var(--sa-positive-soft);
432
- }
433
- .sa-kpi--surface.sa-kpi--warn {
434
- border-color: var(--sa-amber-border);
435
- background: var(--sa-warning-soft);
436
- }
437
- .sa-kpi--surface.sa-kpi--danger {
438
- border-color: #fecaca;
439
- background: var(--sa-negative-soft);
440
- }
441
- .sa-kpi--surface.sa-kpi--positive .sa-kpi__label,
442
- .sa-kpi--surface.sa-kpi--warn .sa-kpi__label,
443
- .sa-kpi--surface.sa-kpi--danger .sa-kpi__label {
444
- color: inherit;
445
- }
446
-
447
- .sa-kpi--selected.sa-kpi--positive {
448
- border-color: var(--sa-positive-light);
449
- box-shadow:
450
- 0 0 0 1px var(--sa-positive-light) inset,
451
- 0 2px 6px rgba(16, 185, 129, 0.1);
452
- }
453
- .sa-kpi--selected.sa-kpi--warn {
454
- border-color: var(--sa-amber);
455
- box-shadow:
456
- 0 0 0 1px var(--sa-amber) inset,
457
- 0 2px 6px rgba(245, 158, 11, 0.1);
458
- }
459
- .sa-kpi--selected.sa-kpi--danger {
460
- border-color: var(--sa-negative);
461
- box-shadow:
462
- 0 0 0 1px var(--sa-negative) inset,
463
- 0 2px 6px rgba(239, 68, 68, 0.1);
464
- }
465
- .sa-kpi--selected.sa-kpi--info {
466
- border-color: #3b82f6;
467
- box-shadow:
468
- 0 0 0 1px #3b82f6 inset,
469
- 0 2px 6px rgba(59, 130, 246, 0.1);
470
- }
471
- .sa-kpi--selected.sa-kpi--purple {
472
- border-color: #a855f7;
473
- box-shadow:
474
- 0 0 0 1px #a855f7 inset,
475
- 0 2px 6px rgba(168, 85, 247, 0.1);
476
- }
477
-
478
- /* Page body — everything below the hero (see AdminBody), including the
479
- * loading and empty states the pages used to spell out one by one.
480
- *
481
- * Inset from the page edge, so the hero frames the page and everything below
482
- * sits inside it. The inset lives here rather than on the section, because
483
- * AdminStatistics is the other thing allowed at this level and would otherwise
484
- * be the only block sticking out. */
485
- .sa-page-body {
486
- padding-left: 20px;
487
- padding-right: 20px;
488
- }
489
- .sa-page-body__state {
490
- padding: 32px 0;
491
- color: var(--sa-muted);
492
- display: flex;
493
- align-items: center;
494
- gap: 12px;
495
- }
496
- .sa-page-body__state--empty {
497
- font-size: 13px;
498
- }
499
-
500
- /* Rhythm only for sections sitting directly in the page flow. Deliberately a
501
- * child selector: sections placed inside a grid or flex container get their
502
- * spacing from that container, and a blanket margin would fight it. */
503
- .sa-page > .sa-section,
504
- .sa-page-body > .sa-section {
505
- margin-bottom: 16px;
506
- }
507
- .sa-page > .sa-section:last-child,
508
- .sa-page-body > .sa-section:last-child {
509
- margin-bottom: 0;
510
- }
511
- /* Section — a labelled block within a page (see AdminSection). It carries the
512
- * surface now: the page frame is transparent, so what a reader sees as "a
513
- * block of this page" is exactly one section. A section that needs an inner
514
- * card still adds `.sa-card`; a section IS one, so the two do not stack. */
515
- .sa-section {
516
- background: var(--sa-bg-surface);
517
- border: 1px solid var(--sa-border);
518
- border-radius: var(--sa-radius-section);
519
- }
520
- .sa-section__body {
521
- padding: var(--sa-section-pad);
522
- }
523
-
524
- /* The head is the hero's block one level down: same typography, same 22px
525
- * inset — so a section title sits exactly under the page title — but no tinted
526
- * box of its own. The section already is a surface; a box inside it would read
527
- * as a panel within a panel.
528
- *
529
- * The size steps down because the level does. Everything else is the hero's. */
530
- .sa-section__head {
531
- display: flex;
532
- align-items: flex-start;
533
- gap: 20px;
534
- padding: 18px 22px 0;
535
- }
536
- .sa-section__title {
537
- margin: 0;
538
- font-family: var(--sa-font-head);
539
- font-size: 17px;
540
- font-weight: 700;
541
- letter-spacing: -0.02em;
542
- line-height: 1.25;
543
- color: var(--sa-heading);
544
- }
545
- .sa-section__sub {
546
- margin: 1px 0 0;
547
- font-size: 12.5px;
548
- line-height: 1.35;
549
- color: var(--sa-muted);
550
- }
551
- .sa-section__actions {
552
- margin-left: auto;
553
- display: flex;
554
- gap: 8px;
555
- align-items: center;
556
- }
557
-
558
- /* Key-value grid — the layout `KvBlock` is meant to sit in. */
559
- .sa-kv-grid {
560
- display: grid;
561
- grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
562
- gap: 12px;
563
- }
564
-
565
- /* Generic card (cards in dashboard/detail sections). */
566
- .sa-card {
567
- background: var(--sa-bg-surface);
568
- border: 1px solid var(--sa-border);
569
- border-radius: var(--sa-radius-card);
570
- padding: 16px 18px;
571
- }
572
- .sa-card__title {
573
- margin: 0 0 12px;
574
- font-family: var(--sa-font-head);
575
- font-size: 14px;
576
- font-weight: 700;
577
- color: var(--sa-heading);
578
- letter-spacing: -0.005em;
579
- }
580
-
581
- /* The pager below a list (see AdminPaginator). Right-aligned, one rule, so a
582
- * list contributes its counts and nothing about where paging sits. */
583
- .sa-paginator {
584
- display: flex;
585
- align-items: center;
586
- justify-content: flex-end;
587
- gap: 8px;
588
- padding: 12px 0 0;
589
- font-size: 12.5px;
590
- color: var(--sa-muted);
591
- }
592
- .sa-paginator__size-label {
593
- font-weight: 500;
594
- }
595
- .sa-paginator__select {
596
- min-width: 92px;
597
- }
598
- .sa-paginator__step,
599
- .sa-paginator__page {
600
- display: inline-flex;
601
- align-items: center;
602
- justify-content: center;
603
- min-height: 30px;
604
- padding: 0 8px;
605
- border: 1px solid var(--sa-border);
606
- border-radius: var(--sa-radius-control);
607
- background: var(--sa-bg-surface);
608
- color: var(--sa-body);
609
- font: inherit;
610
- cursor: pointer;
611
- transition:
612
- background 0.12s,
613
- border-color 0.12s;
614
- }
615
- .sa-paginator__page {
616
- font-weight: 600;
617
- color: var(--sa-heading);
618
- min-width: 96px;
619
- }
620
- .sa-paginator__step:hover:not(:disabled),
621
- .sa-paginator__page:hover:not(:disabled) {
622
- background: var(--sa-bg-surface-2);
623
- border-color: var(--sa-border-strong);
624
- }
625
- .sa-paginator__step:disabled,
626
- .sa-paginator__page:disabled {
627
- opacity: 0.45;
628
- cursor: default;
629
- }
630
- .sa-paginator__input {
631
- min-width: 96px;
632
- min-height: 30px;
633
- padding: 0 8px;
634
- border: 1px solid var(--sa-primary);
635
- border-radius: var(--sa-radius-control);
636
- background: var(--sa-bg-surface);
637
- color: var(--sa-heading);
638
- font: inherit;
639
- font-weight: 600;
640
- text-align: center;
641
- }
642
-
643
- /* Lists (see AdminTable). The package shipped no table rules at all until
644
- * this, which is why nine pages each looked slightly different. */
645
- .sa-page .q-table thead th {
646
- font-size: 11px;
647
- font-weight: 700;
648
- letter-spacing: 0.04em;
649
- text-transform: uppercase;
650
- color: var(--sa-muted);
651
- background: var(--sa-bg-surface-2);
652
- border-bottom: 1px solid var(--sa-border);
653
- padding: 10px 14px;
654
- white-space: nowrap;
655
- }
656
- .sa-page .q-table tbody td {
657
- font-size: 13px;
658
- color: var(--sa-body);
659
- border-bottom: 1px solid var(--sa-border-soft);
660
- padding: 10px 14px;
661
- }
662
- .sa-page .q-table tbody tr:last-child td {
663
- border-bottom: 0;
664
- }
665
- .sa-page .q-table tbody tr:hover td {
666
- background: var(--sa-bg-surface-2);
667
- }
668
- /* Sorted column: the header carries the state, not just the arrow. */
669
- .sa-page .q-table thead th.sortable:hover,
670
- .sa-page .q-table thead th[aria-sort]:not([aria-sort='none']) {
671
- color: var(--sa-heading);
672
- }
673
- .sa-page .q-table thead th[aria-sort]:not([aria-sort='none']) {
674
- background: var(--sa-primary-50);
675
- }
676
- .sa-table__actions {
677
- white-space: nowrap;
678
- }
679
- .sa-table__empty {
680
- color: var(--sa-muted);
681
- }
682
-
683
- /* The pager sits below the list, separated by a line that runs the full width
684
- * of the surface — hence the pull-out by the section's own padding token. */
685
- .sa-table__foot {
686
- margin: 0 calc(-1 * var(--sa-section-pad)) calc(-1 * var(--sa-section-pad));
687
- padding: 0 var(--sa-section-pad) 10px;
688
- border-top: 1px solid var(--sa-border);
689
- }
11
+ @import '../ui/theme/index.css';