@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
@@ -47,16 +47,17 @@
47
47
  >
48
48
  <template #row-actions="{ row }">
49
49
  <slot name="row-actions" :row="row">
50
- <q-btn
50
+ <button
51
51
  v-for="action in visibleActions(row)"
52
52
  :key="action.id"
53
- flat
54
- dense
55
- :icon="action.icon"
53
+ type="button"
54
+ class="sa-icon-btn"
55
+ :class="action.color === 'negative' ? 'sa-icon-btn--negative' : ''"
56
56
  :title="action.label"
57
- :color="action.color ?? 'grey-7'"
58
57
  @click="action.handler(row)"
59
- />
58
+ >
59
+ <q-icon :name="action.icon" size="18px" />
60
+ </button>
60
61
  </slot>
61
62
  </template>
62
63
  </AdminTable>
@@ -36,30 +36,30 @@
36
36
  </q-td>
37
37
  </template>
38
38
  <template #row-actions="{ row }">
39
- <q-btn
40
- flat
41
- dense
42
- icon="send"
43
- color="primary"
39
+ <button
40
+ type="button"
41
+ class="sa-icon-btn"
44
42
  :title="msg.provider.sendTestMail"
45
43
  @click="openTest(row)"
46
- />
47
- <q-btn
48
- flat
49
- dense
50
- icon="edit"
51
- color="grey-7"
44
+ >
45
+ <q-icon name="send" size="18px" />
46
+ </button>
47
+ <button
48
+ type="button"
49
+ class="sa-icon-btn"
52
50
  :title="common.edit"
53
51
  @click="openEdit(row)"
54
- />
55
- <q-btn
56
- flat
57
- dense
58
- icon="delete"
59
- color="negative"
52
+ >
53
+ <q-icon name="edit" size="18px" />
54
+ </button>
55
+ <button
56
+ type="button"
57
+ class="sa-icon-btn sa-icon-btn--negative"
60
58
  :title="common.delete"
61
59
  @click="onDelete(row)"
62
- />
60
+ >
61
+ <q-icon name="delete" size="18px" />
62
+ </button>
63
63
  </template>
64
64
  </AdminTable>
65
65
  </AdminSection>
@@ -297,11 +297,11 @@ const redemptionRows = computed(() => data.value?.redemptions ?? []);
297
297
  gap: 14px;
298
298
  }
299
299
  .sa-promo-detail__kv {
300
- background: #fafbfc;
301
- border: 1px solid var(--sa-border);
300
+ background: var(--sa-color-bg-surface-raised);
301
+ border: 1px solid var(--sa-color-border);
302
302
  border-radius: 8px;
303
303
  padding: 12px;
304
- font-size: 12px;
304
+ font-size: var(--sa-text-sm);
305
305
  margin: 0;
306
306
  overflow-x: auto;
307
307
  font-family: var(--sa-font-mono);
@@ -66,16 +66,17 @@
66
66
  </template>
67
67
  <template #row-actions="{ row }">
68
68
  <slot name="row-actions" :row="row" :reload="reload">
69
- <q-btn
69
+ <button
70
70
  v-for="action in visibleActions(row)"
71
71
  :key="action.id"
72
- flat
73
- dense
74
- :icon="action.icon"
72
+ type="button"
73
+ class="sa-icon-btn"
74
+ :class="action.color === 'negative' ? 'sa-icon-btn--negative' : ''"
75
75
  :title="action.label"
76
- :color="action.color ?? 'grey-7'"
77
76
  @click="action.handler(row)"
78
- />
77
+ >
78
+ <q-icon :name="action.icon" size="18px" />
79
+ </button>
79
80
  </slot>
80
81
  </template>
81
82
  </AdminTable>
@@ -109,7 +110,9 @@
109
110
  import AdminTable from '../components/admin-page/AdminTable.vue';
110
111
  import type { PromoCodePlanOption } from '../components/dialogs/types.js';
111
112
 
112
- const PLAN_COLOR_PALETTE = ['#0ea5e9', '#10b981', '#f59e0b', '#7c3aed', '#dc2626', '#64748b'];
113
+ import { IDENTITY_ACCENTS } from '../client/identity-accents.js';
114
+
115
+ const PLAN_COLOR_PALETTE = IDENTITY_ACCENTS;
113
116
 
114
117
  /**
115
118
  * Heuristic from a consumer wrapper: assigns each plan a stable color so plan
@@ -199,7 +199,7 @@ async function handleSubmit(): Promise<void> {
199
199
  on top of the centered card on short viewports. */
200
200
  margin-left: auto;
201
201
  align-self: flex-start;
202
- color: var(--sa-login-tag-color, var(--sa-muted-dark));
202
+ color: var(--sa-login-tag-color, var(--sa-color-fg-secondary));
203
203
  }
204
204
  .sa-login-wrap {
205
205
  min-height: 100vh;
@@ -208,15 +208,19 @@ async function handleSubmit(): Promise<void> {
208
208
  align-items: center;
209
209
  justify-content: center;
210
210
  padding: 24px;
211
- background: linear-gradient(180deg, var(--sa-heading) 0%, #1e293b 100%);
211
+ background: linear-gradient(
212
+ 180deg,
213
+ var(--sa-color-inverse-bg) 0%,
214
+ var(--sa-color-inverse-surface) 100%
215
+ );
212
216
  }
213
217
  .sa-login-card {
214
218
  width: 420px;
215
219
  max-width: 92vw;
216
- background: #fff;
220
+ background: var(--sa-color-bg-surface);
217
221
  border-radius: 18px;
218
222
  padding: 32px;
219
- box-shadow: 0 24px 48px rgba(0, 0, 0, 0.25);
223
+ box-shadow: 0 24px 48px var(--sa-shadow-tint-4);
220
224
  }
221
225
  .sa-login-brand {
222
226
  display: flex;
@@ -234,10 +238,20 @@ async function handleSubmit(): Promise<void> {
234
238
  overflow: hidden;
235
239
  }
236
240
  .sa-login-logo--text {
237
- background: var(--sa-login-logo-bg, linear-gradient(135deg, var(--sa-muted-dark), #1e293b));
238
- color: var(--sa-login-logo-color, #fff);
241
+ background: var(
242
+ --sa-login-logo-bg,
243
+ linear-gradient(
244
+ 135deg,
245
+ var(--sa-color-inverse-surface-soft),
246
+ var(--sa-color-inverse-surface)
247
+ )
248
+ );
249
+ /* The fallback has to match the fallback background above, which is the
250
+ * invariant dark gradient — not the accent. An app that sets its own
251
+ * `--sa-login-logo-bg` sets `--sa-login-logo-color` with it. */
252
+ color: var(--sa-login-logo-color, var(--sa-color-inverse-fg));
239
253
  font-weight: 800;
240
- font-size: 18px;
254
+ font-size: var(--sa-text-xl);
241
255
  text-transform: uppercase;
242
256
  }
243
257
  .sa-login-logo--img img {
@@ -247,26 +261,26 @@ async function handleSubmit(): Promise<void> {
247
261
  }
248
262
  .sa-login-brand__name {
249
263
  font-weight: 800;
250
- font-size: 16px;
251
- color: var(--sa-heading);
264
+ font-size: var(--sa-text-lg);
265
+ color: var(--sa-color-fg-heading);
252
266
  line-height: 1.1;
253
267
  }
254
268
  .sa-login-brand__tag {
255
- font-size: 12px;
256
- color: var(--sa-login-tag-color, var(--sa-muted-dark));
269
+ font-size: var(--sa-text-sm);
270
+ color: var(--sa-login-tag-color, var(--sa-color-fg-secondary));
257
271
  text-transform: uppercase;
258
272
  letter-spacing: 0.08em;
259
273
  }
260
274
 
261
275
  .sa-login-title {
262
- font-size: 22px;
276
+ font-size: var(--sa-text-2xl);
263
277
  font-weight: 800;
264
- color: var(--sa-heading);
278
+ color: var(--sa-color-fg-heading);
265
279
  margin: 0 0 6px;
266
280
  }
267
281
  .sa-login-subtitle {
268
- font-size: 13px;
269
- color: var(--sa-muted-dark);
282
+ font-size: var(--sa-text-md);
283
+ color: var(--sa-color-fg-secondary);
270
284
  margin: 0 0 20px;
271
285
  line-height: 1.5;
272
286
  }
@@ -279,10 +293,10 @@ async function handleSubmit(): Promise<void> {
279
293
  width: 100%;
280
294
  }
281
295
  .sa-login-error {
282
- background: #fef2f2;
283
- border: 1px solid #fecaca;
284
- color: #b91c1c;
285
- font-size: 13px;
296
+ background: var(--sa-color-negative-surface);
297
+ border: 1px solid var(--sa-color-negative-border);
298
+ color: var(--sa-color-negative-fg);
299
+ font-size: var(--sa-text-md);
286
300
  margin-top: 4px;
287
301
  padding: 8px 12px;
288
302
  border-radius: 8px;
@@ -290,20 +304,20 @@ async function handleSubmit(): Promise<void> {
290
304
  .sa-login-hint {
291
305
  margin-top: 18px;
292
306
  padding: 10px 12px;
293
- background: var(--sa-border-soft);
307
+ background: var(--sa-color-border-soft);
294
308
  border-radius: 8px;
295
- font-size: 12px;
296
- color: var(--sa-muted-dark);
309
+ font-size: var(--sa-text-sm);
310
+ color: var(--sa-color-fg-secondary);
297
311
  }
298
312
  .sa-login-hint code {
299
- background: var(--sa-border);
313
+ background: var(--sa-color-border);
300
314
  padding: 1px 5px;
301
315
  border-radius: 4px;
302
316
  }
303
317
  .sa-login-env {
304
318
  margin-top: 18px;
305
- color: #cbd5e1;
306
- font-size: 12px;
319
+ color: var(--sa-color-inverse-fg-muted);
320
+ font-size: var(--sa-text-sm);
307
321
  text-transform: uppercase;
308
322
  letter-spacing: 0.08em;
309
323
  }
@@ -277,15 +277,19 @@ async function submitConfirm(): Promise<void> {
277
277
  align-items: center;
278
278
  justify-content: center;
279
279
  padding: 24px;
280
- background: linear-gradient(180deg, var(--sa-heading) 0%, #1e293b 100%);
280
+ background: linear-gradient(
281
+ 180deg,
282
+ var(--sa-color-inverse-bg) 0%,
283
+ var(--sa-color-inverse-surface) 100%
284
+ );
281
285
  }
282
286
  .sa-setup-card {
283
287
  width: 460px;
284
288
  max-width: 92vw;
285
- background: #fff;
289
+ background: var(--sa-color-bg-surface);
286
290
  border-radius: 18px;
287
291
  padding: 32px;
288
- box-shadow: 0 24px 48px rgba(0, 0, 0, 0.25);
292
+ box-shadow: 0 24px 48px var(--sa-shadow-tint-4);
289
293
  }
290
294
  .sa-setup-head {
291
295
  display: flex;
@@ -296,7 +300,7 @@ async function submitConfirm(): Promise<void> {
296
300
  .sa-setup-locale {
297
301
  margin-left: auto;
298
302
  align-self: flex-start;
299
- color: var(--sa-muted-dark);
303
+ color: var(--sa-color-fg-secondary);
300
304
  }
301
305
  .sa-setup-badge {
302
306
  width: 44px;
@@ -305,31 +309,38 @@ async function submitConfirm(): Promise<void> {
305
309
  display: flex;
306
310
  align-items: center;
307
311
  justify-content: center;
308
- background: linear-gradient(135deg, var(--sa-muted-dark), #1e293b);
309
- color: #fff;
312
+ background: linear-gradient(
313
+ 135deg,
314
+ var(--sa-color-inverse-surface-soft),
315
+ var(--sa-color-inverse-surface)
316
+ );
317
+ /* The gradient never moves, so the text on it must not either.
318
+ * `--sa-color-fg-on-accent` is white today but belongs to the accent, and
319
+ * the design guide tells a light brand to override it to something dark. */
320
+ color: var(--sa-color-inverse-fg);
310
321
  font-weight: 800;
311
- font-size: 18px;
322
+ font-size: var(--sa-text-xl);
312
323
  text-transform: uppercase;
313
324
  }
314
325
  .sa-setup-title {
315
326
  margin: 0;
316
327
  font-weight: 800;
317
- font-size: 18px;
318
- color: var(--sa-heading);
328
+ font-size: var(--sa-text-xl);
329
+ color: var(--sa-color-fg-heading);
319
330
  line-height: 1.1;
320
331
  }
321
332
  .sa-setup-sub {
322
- font-size: 12px;
323
- color: var(--sa-muted-dark);
333
+ font-size: var(--sa-text-sm);
334
+ color: var(--sa-color-fg-secondary);
324
335
  }
325
336
  .sa-setup-hint {
326
- font-size: 13px;
327
- color: var(--sa-muted-dark);
337
+ font-size: var(--sa-text-md);
338
+ color: var(--sa-color-fg-secondary);
328
339
  line-height: 1.5;
329
340
  margin: 0 0 16px;
330
341
  }
331
342
  .sa-setup-hint code {
332
- background: var(--sa-border);
343
+ background: var(--sa-color-border);
333
344
  padding: 1px 5px;
334
345
  border-radius: 4px;
335
346
  }
@@ -341,10 +352,10 @@ async function submitConfirm(): Promise<void> {
341
352
  width: 100%;
342
353
  }
343
354
  .sa-setup-error {
344
- background: #fef2f2;
345
- border: 1px solid #fecaca;
346
- color: #b91c1c;
347
- font-size: 13px;
355
+ background: var(--sa-color-negative-surface);
356
+ border: 1px solid var(--sa-color-negative-border);
357
+ color: var(--sa-color-negative-fg);
358
+ font-size: var(--sa-text-md);
348
359
  margin-bottom: 16px;
349
360
  padding: 8px 12px;
350
361
  border-radius: 8px;
@@ -357,46 +368,46 @@ async function submitConfirm(): Promise<void> {
357
368
  margin-bottom: 12px;
358
369
  }
359
370
  .sa-setup-qr img {
360
- border: 1px solid var(--sa-border);
371
+ border: 1px solid var(--sa-color-border);
361
372
  border-radius: 8px;
362
373
  padding: 8px;
363
- background: #fff;
374
+ background: var(--sa-color-bg-surface);
364
375
  }
365
376
  .sa-setup-qr__hint {
366
- font-size: 12px;
367
- color: var(--sa-muted);
377
+ font-size: var(--sa-text-sm);
378
+ color: var(--sa-color-fg-muted);
368
379
  }
369
380
  .sa-setup-secret {
370
- background: var(--sa-border-soft);
381
+ background: var(--sa-color-border-soft);
371
382
  border-radius: 8px;
372
383
  padding: 10px 12px;
373
384
  margin-bottom: 10px;
374
385
  }
375
386
  .sa-setup-secret__label {
376
- font-size: 11px;
387
+ font-size: var(--sa-text-xs);
377
388
  text-transform: uppercase;
378
389
  letter-spacing: 0.06em;
379
- color: var(--sa-muted);
390
+ color: var(--sa-color-fg-muted);
380
391
  margin-bottom: 4px;
381
392
  }
382
393
  .sa-setup-secret__value {
383
- font-size: 15px;
394
+ font-size: var(--sa-text-lg);
384
395
  font-weight: 700;
385
- color: var(--sa-heading);
396
+ color: var(--sa-color-fg-heading);
386
397
  word-break: break-all;
387
398
  letter-spacing: 0.04em;
388
399
  }
389
400
  .sa-setup-uri {
390
401
  margin: 4px 0 4px;
391
- font-size: 12px;
392
- color: var(--sa-muted-dark);
402
+ font-size: var(--sa-text-sm);
403
+ color: var(--sa-color-fg-secondary);
393
404
  }
394
405
  .sa-setup-uri__value {
395
406
  display: block;
396
407
  margin-top: 6px;
397
408
  word-break: break-all;
398
- color: #334155;
399
- background: var(--sa-bg-surface-2);
409
+ color: var(--sa-color-fg-body);
410
+ background: var(--sa-color-bg-sunken);
400
411
  padding: 6px 8px;
401
412
  border-radius: 6px;
402
413
  }
@@ -405,8 +416,8 @@ async function submitConfirm(): Promise<void> {
405
416
  width: 100%;
406
417
  background: none;
407
418
  border: none;
408
- color: var(--sa-muted);
409
- font-size: 12px;
419
+ color: var(--sa-color-fg-muted);
420
+ font-size: var(--sa-text-sm);
410
421
  cursor: pointer;
411
422
  text-decoration: underline;
412
423
  }
@@ -327,13 +327,13 @@ const defaultUserColumns = computed<QTableColumn[]>(() => [
327
327
  display: flex;
328
328
  justify-content: space-between;
329
329
  align-items: center;
330
- border-bottom: 1px solid var(--sa-border-soft);
330
+ border-bottom: 1px solid var(--sa-color-border-soft);
331
331
  padding-bottom: 14px;
332
332
  margin-bottom: 16px;
333
333
  }
334
334
  .sa-tenant-detail__card-sub {
335
- color: var(--sa-muted);
336
- font-size: 13px;
335
+ color: var(--sa-color-fg-muted);
336
+ font-size: var(--sa-text-md);
337
337
  margin: 4px 0 0;
338
338
  }
339
339
  .sa-tenant-detail__card-actions {
@@ -343,13 +343,13 @@ const defaultUserColumns = computed<QTableColumn[]>(() => [
343
343
  align-items: center;
344
344
  }
345
345
  .sa-tenant-detail__empty {
346
- color: var(--sa-muted);
347
- font-size: 13px;
346
+ color: var(--sa-color-fg-muted);
347
+ font-size: var(--sa-text-md);
348
348
  }
349
349
  code {
350
- background: rgba(15, 23, 42, 0.06);
350
+ background: var(--sa-color-bg-sunken);
351
351
  padding: 1px 6px;
352
352
  border-radius: 4px;
353
- font-size: 12px;
353
+ font-size: var(--sa-text-sm);
354
354
  }
355
355
  </style>
@@ -119,8 +119,8 @@
119
119
  v-for="action in visibleActions(row)"
120
120
  :key="action.id"
121
121
  :href="action.to ? action.to(row) : undefined"
122
- class="sa-tenants__icon-btn"
123
- :class="action.tone ? `sa-tenants__icon-btn--${action.tone}` : ''"
122
+ class="sa-icon-btn"
123
+ :class="action.tone ? `sa-icon-btn--${action.tone}` : ''"
124
124
  :title="action.label"
125
125
  @click="action.handler ? action.handler(row) : undefined"
126
126
  >
@@ -176,7 +176,8 @@ import {
176
176
  import MfaPromptDialog from '../components/MfaPromptDialog.vue';
177
177
  import TenantActionConfirmDialog from '../components/TenantActionConfirmDialog.vue';
178
178
  import StatusPill, { type PillTone } from './tenants/StatusPill.vue';
179
- import { DEFAULT_PLAN_ACCENTS, formatDate, planAccent, tenantInitials } from './tenants/format.js';
179
+ import { identityChipStyle } from '../client/identity-accents.js';
180
+ import { formatDate, planAccent, tenantInitials } from './tenants/format.js';
180
181
 
181
182
  // Platform standard page: tenant list.
182
183
  //
@@ -323,7 +324,7 @@ const props = withDefaults(
323
324
  pageSize: 25,
324
325
  showPlanColumn: true,
325
326
  planLabelField: 'plan',
326
- planAccents: () => DEFAULT_PLAN_ACCENTS,
327
+ planAccents: () => ({}),
327
328
  usageFields: () => [],
328
329
  manifest: null,
329
330
  },
@@ -484,8 +485,8 @@ const combinedActions = computed<TenantRowAction[]>(() => {
484
485
  const hasActions = computed(() => combinedActions.value.length > 0);
485
486
 
486
487
  function avatarStyle(row: TenantRow): Record<string, string> {
487
- const accent = planAccentFor(row);
488
- return { background: `${accent}18`, color: accent };
488
+ const { background, color } = identityChipStyle(planAccentFor(row));
489
+ return { background, color };
489
490
  }
490
491
 
491
492
  function planAccentFor(row: TenantRow): string {
@@ -543,7 +544,7 @@ function visibleActions(row: TenantRow): TenantRowAction[] {
543
544
  AdminTable, behind a prop. */
544
545
  .sa-tenants__mono {
545
546
  font-family: var(--sa-font-mono, ui-monospace, monospace);
546
- font-size: 12px;
547
+ font-size: var(--sa-text-sm);
547
548
  }
548
549
 
549
550
  .sa-tenants__tenant {
@@ -556,7 +557,7 @@ function visibleActions(row: TenantRow): TenantRowAction[] {
556
557
  height: 32px;
557
558
  border-radius: 8px;
558
559
  font-weight: 800;
559
- font-size: 12px;
560
+ font-size: var(--sa-text-sm);
560
561
  font-family: var(--sa-font-head, system-ui, sans-serif);
561
562
  display: flex;
562
563
  align-items: center;
@@ -564,13 +565,13 @@ function visibleActions(row: TenantRow): TenantRowAction[] {
564
565
  flex-shrink: 0;
565
566
  }
566
567
  .sa-tenants__name {
567
- font-size: 13px;
568
+ font-size: var(--sa-text-md);
568
569
  font-weight: 600;
569
- color: var(--sa-heading, var(--sa-heading));
570
+ color: var(--sa-color-fg-heading);
570
571
  }
571
572
  .sa-tenants__slug {
572
- font-size: 11px;
573
- color: var(--sa-muted, var(--sa-muted));
573
+ font-size: var(--sa-text-xs);
574
+ color: var(--sa-color-fg-muted);
574
575
  font-family: var(--sa-font-mono, ui-monospace, monospace);
575
576
  }
576
577
 
@@ -595,46 +596,11 @@ function visibleActions(row: TenantRow): TenantRowAction[] {
595
596
  display: inline-flex;
596
597
  align-items: center;
597
598
  gap: 4px;
598
- font-size: 12px;
599
- color: var(--sa-muted-dark, var(--sa-muted-dark));
599
+ font-size: var(--sa-text-sm);
600
+ color: var(--sa-color-fg-secondary);
600
601
  margin-left: 8px;
601
602
  }
602
603
  .sa-tenants__usage :deep(.q-icon) {
603
- color: var(--sa-muted, var(--sa-muted));
604
- }
605
-
606
- .sa-tenants__icon-btn {
607
- background: transparent;
608
- border: none;
609
- cursor: pointer;
610
- width: 28px;
611
- height: 28px;
612
- border-radius: 6px;
613
- display: inline-flex;
614
- align-items: center;
615
- justify-content: center;
616
- text-decoration: none;
617
- color: var(--sa-muted, var(--sa-muted));
618
- }
619
- .sa-tenants__icon-btn:hover {
620
- background: var(--sa-border-soft);
621
- }
622
- .sa-tenants__icon-btn :deep(.q-icon) {
623
- color: inherit;
624
- }
625
- .sa-tenants__icon-btn--negative {
626
- color: var(--sa-negative, #dc2626);
627
- }
628
- .sa-tenants__icon-btn--positive {
629
- color: var(--sa-positive, #047857);
630
- }
631
- .sa-tenants__icon-btn--primary {
632
- color: var(--sa-primary, #3f6bff);
633
- }
634
- .sa-tenants__icon-btn--warning {
635
- color: var(--sa-warning, #d97706);
636
- }
637
- .sa-tenants__icon-btn--accent {
638
- color: var(--sa-accent, #7c3aed);
604
+ color: var(--sa-color-fg-muted);
639
605
  }
640
606
  </style>
@@ -100,31 +100,31 @@ function translationCount(bundle: BundleRow): string {
100
100
  DiscoveryPage.vue would otherwise override .sa-bundle-chip--live (same specificity,
101
101
  later source order) and render the "Live" tag grey instead of green. */
102
102
  .sa-bundle-chip {
103
- font-size: 10px;
103
+ font-size: var(--sa-text-2xs);
104
104
  font-weight: 600;
105
105
  padding: 2px 7px;
106
106
  border-radius: 6px;
107
- background: var(--sa-border-soft);
108
- color: var(--sa-muted-dark);
107
+ background: var(--sa-color-border-soft);
108
+ color: var(--sa-color-fg-secondary);
109
109
  }
110
110
  .sa-bundle-chip--info {
111
- background: var(--sa-primary-50);
112
- color: var(--sa-primary-strong);
111
+ background: var(--sa-color-accent-surface-strong);
112
+ color: var(--sa-color-accent-strong);
113
113
  }
114
114
  .sa-bundle-chip--live {
115
- background: #dcfce7;
116
- color: #166534;
115
+ background: var(--sa-color-positive-surface-strong);
116
+ color: var(--sa-color-positive-fg);
117
117
  }
118
118
  .sa-bundle-chip--draft {
119
- background: #fef3c7;
120
- color: #92400e;
119
+ background: var(--sa-color-warning-surface-strong);
120
+ color: var(--sa-color-warning-fg);
121
121
  }
122
122
  .sa-bundle-chip--supersed {
123
- background: var(--sa-border);
124
- color: var(--sa-muted);
123
+ background: var(--sa-color-border);
124
+ color: var(--sa-color-fg-muted);
125
125
  }
126
126
  .sa-bundle-chip--scheduled {
127
- background: #fef3c7;
128
- color: #92400e;
127
+ background: var(--sa-color-warning-surface-strong);
128
+ color: var(--sa-color-warning-fg);
129
129
  }
130
130
  </style>