@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.
- package/README.md +1 -1
- package/dist/{chunk-LY52T4N4.js → chunk-TQQMKWN6.js} +58 -1
- package/dist/{chunk-XMMFN2WJ.js → chunk-VYMTWCTT.js} +51 -1
- package/dist/client/index.cjs +57 -0
- package/dist/client/index.d.cts +58 -1
- package/dist/client/index.d.ts +58 -1
- package/dist/client/index.js +15 -1
- package/dist/index.cjs +321 -159
- package/dist/index.d.cts +67 -4
- package/dist/index.d.ts +67 -4
- package/dist/index.js +72 -2
- package/dist/quasar/index.cjs +116 -20
- package/dist/quasar/index.d.cts +48 -3
- package/dist/quasar/index.d.ts +48 -3
- package/dist/quasar/index.js +58 -3
- package/dist/{use-super-admin-i18n-CgTuNi8h.d.cts → use-sa-theme-B5t7oXph.d.cts} +51 -1
- package/dist/{use-super-admin-i18n-CHNdsSlm.d.ts → use-sa-theme-OUBlaqgl.d.ts} +51 -1
- package/package.json +5 -2
- package/src/client/identity-accents.ts +129 -0
- package/src/client/index.ts +1 -0
- package/src/components/BundleVersionPublishDialog.vue +17 -17
- package/src/components/KvBlock.vue +6 -6
- package/src/components/MarketingPromotionsTab.vue +84 -80
- package/src/components/MfaPromptDialog.vue +2 -2
- package/src/components/TenantActionConfirmDialog.vue +5 -5
- package/src/components/VersionDiffPreview.vue +18 -18
- package/src/components/bundle-editor/BundleCreatePanel.vue +52 -52
- package/src/components/bundle-editor/BundleFeaturesEditor.vue +20 -20
- package/src/components/bundle-editor/BundlePlanCompatPicker.vue +33 -33
- package/src/components/bundle-editor/BundleQuotasEditor.vue +26 -26
- package/src/components/bundle-editor/BundleStatusBanner.vue +21 -21
- package/src/components/bundle-editor/BundleVersionInlineEditor.vue +49 -49
- package/src/components/bundle-editor/BundleVersionStrip.vue +30 -30
- package/src/components/dialogs/PilotCreateDialog.vue +20 -20
- package/src/components/dialogs/PilotEditDialog.vue +6 -6
- package/src/components/dialogs/PromoCodeCreateDialog.vue +9 -9
- package/src/components/dialogs/PromoCodeDialogFields.vue +64 -63
- package/src/components/dialogs/PromoCodeEditDialog.vue +11 -11
- package/src/components/dialogs/pilot-dialog.css +48 -48
- package/src/components/plan/PlanCycleToggle.vue +9 -16
- package/src/components/plan/PlanGrid.vue +30 -34
- package/src/components/plan/PriceSummary.vue +35 -35
- package/src/components/plan/PromoCodeInput.vue +26 -26
- package/src/components/plan/PublicBundleGrid.vue +32 -28
- package/src/components/plan-create-dialog/PlanCreateDialog.vue +50 -50
- package/src/components/plan-detail/PlanDetail.vue +178 -157
- package/src/components/plan-detail/PlanTitleEdit.vue +11 -11
- package/src/components/plan-list/PlanList.vue +116 -100
- package/src/components/plan-matrix/PlanMatrix.vue +97 -114
- package/src/components/plan-review/PlanReview.vue +61 -57
- package/src/components/plan-version-editor/PlanVersionEditor.vue +246 -203
- package/src/components/wizard-stepper/WizardStepper.vue +17 -17
- package/src/index.ts +1 -0
- package/src/pages-standard/AdminLayout.vue +58 -37
- package/src/pages-standard/AdminManifestErrorPage.vue +6 -6
- package/src/pages-standard/AuditPage.vue +3 -3
- package/src/pages-standard/BundlesPage.vue +37 -37
- package/src/pages-standard/DashboardPage.vue +22 -19
- package/src/pages-standard/DiscoveryPage.vue +55 -49
- package/src/pages-standard/EmailHistoryPage.vue +53 -71
- package/src/pages-standard/MarketingCatalogPage.vue +245 -221
- package/src/pages-standard/PilotsPage.vue +7 -6
- package/src/pages-standard/PlatformEmailPage.vue +18 -18
- package/src/pages-standard/PromoCodeDetailPage.vue +3 -3
- package/src/pages-standard/PromoCodesPage.vue +10 -7
- package/src/pages-standard/SuperAdminLoginPage.vue +39 -25
- package/src/pages-standard/SuperAdminSetupWizard.vue +44 -33
- package/src/pages-standard/TenantDetailPage.vue +7 -7
- package/src/pages-standard/TenantsPage.vue +16 -50
- package/src/pages-standard/bundles-page/BundleAccordionList.vue +13 -13
- package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +24 -24
- package/src/pages-standard/discovery-page/DiscoveryCapList.vue +22 -22
- package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +39 -39
- package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +26 -26
- package/src/pages-standard/discovery-page/DiscoveryStatusControl.vue +1 -1
- package/src/pages-standard/discovery-page/discovery-ui.ts +10 -7
- package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +2 -5
- package/src/pages-standard/plan-versions/PlanDiffCard.vue +27 -20
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +24 -16
- package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +45 -45
- package/src/pages-standard/plans-page/PlanBundleOverview.vue +21 -15
- package/src/pages-standard/plans-page/PlanPublishDialog.vue +4 -4
- package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
- package/src/pages-standard/sa-theme.css +9 -687
- package/src/pages-standard/tenants/StatusPill.vue +13 -13
- package/src/pages-standard/tenants/format.ts +11 -12
- package/src/pages-tenant/LimitsRow.vue +4 -4
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +70 -61
- package/src/pages-tenant/OnboardingConfigurator.vue +25 -25
- package/src/pages-tenant/PackageSnapshotPanel.vue +10 -16
- package/src/pages-tenant/PendingVersionBanner.vue +12 -12
- package/src/pages-tenant/PlanChangeWizard.vue +22 -76
- package/src/pages-tenant/TenantPlanSection.vue +19 -69
- package/src/pages-tenant/UsageBar.vue +7 -7
- package/src/pages-tenant/default-i18n.ts +113 -0
- package/src/pages-tenant/tenant-plan-section/BundlePreviewDialog.vue +14 -14
- package/src/pages-tenant/tenant-plan-section/TenantBundleStore.vue +10 -10
- package/src/pages-tenant/tenant-plan-section/TenantFeatureMatrix.vue +2 -2
- package/src/quasar/create-super-admin-app.ts +96 -1
- package/src/quasar/dark-bridge.ts +66 -0
- package/src/quasar/index.ts +1 -0
- package/src/ui/theme/_breakpoints.scss +34 -0
- package/src/ui/theme/base.css +50 -0
- package/src/ui/theme/components/body.css +23 -0
- package/src/ui/theme/components/button.css +158 -0
- package/src/ui/theme/components/card.css +74 -0
- package/src/ui/theme/components/field.css +38 -0
- package/src/ui/theme/components/hero.css +46 -0
- package/src/ui/theme/components/paginator.css +63 -0
- package/src/ui/theme/components/section.css +67 -0
- package/src/ui/theme/components/statistics.css +189 -0
- package/src/ui/theme/components/table.css +60 -0
- package/src/ui/theme/index.css +42 -0
- package/src/ui/theme/tokens.legacy.css +147 -0
- package/src/ui/theme/tokens.primitive.css +112 -0
- package/src/ui/theme/tokens.scale.css +103 -0
- package/src/ui/theme/tokens.semantic.dark.css +185 -0
- package/src/ui/theme/tokens.semantic.light.css +242 -0
- package/src/vue/use-sa-theme.ts +154 -0
|
@@ -1,689 +1,11 @@
|
|
|
1
|
-
/*
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
|
|
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';
|