@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
@@ -0,0 +1,63 @@
1
+ /* The pager below a list (see AdminPaginator). Right-aligned, one rule, so a
2
+ * list contributes its counts and nothing about where paging sits.
3
+ */
4
+
5
+ .sa-paginator {
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: flex-end;
9
+ gap: 8px;
10
+ padding: 12px 0 0;
11
+ font-size: var(--sa-text-md);
12
+ color: var(--sa-color-fg-muted);
13
+ }
14
+ .sa-paginator__size-label {
15
+ font-weight: 500;
16
+ }
17
+ .sa-paginator__select {
18
+ min-width: 92px;
19
+ }
20
+ .sa-paginator__step,
21
+ .sa-paginator__page {
22
+ display: inline-flex;
23
+ align-items: center;
24
+ justify-content: center;
25
+ min-height: 30px;
26
+ padding: 0 8px;
27
+ border: 1px solid var(--sa-color-border);
28
+ border-radius: var(--sa-radius-control);
29
+ background: var(--sa-color-bg-surface);
30
+ color: var(--sa-color-fg-body);
31
+ font: inherit;
32
+ cursor: pointer;
33
+ transition:
34
+ background 0.12s,
35
+ border-color 0.12s;
36
+ }
37
+ .sa-paginator__page {
38
+ font-weight: 600;
39
+ color: var(--sa-color-fg-heading);
40
+ min-width: 96px;
41
+ }
42
+ .sa-paginator__step:hover:not(:disabled),
43
+ .sa-paginator__page:hover:not(:disabled) {
44
+ background: var(--sa-color-bg-sunken);
45
+ border-color: var(--sa-color-border-strong);
46
+ }
47
+ .sa-paginator__step:disabled,
48
+ .sa-paginator__page:disabled {
49
+ opacity: 0.45;
50
+ cursor: default;
51
+ }
52
+ .sa-paginator__input {
53
+ min-width: 96px;
54
+ min-height: 30px;
55
+ padding: 0 8px;
56
+ border: 1px solid var(--sa-color-accent);
57
+ border-radius: var(--sa-radius-control);
58
+ background: var(--sa-color-bg-surface);
59
+ color: var(--sa-color-fg-heading);
60
+ font: inherit;
61
+ font-weight: 600;
62
+ text-align: center;
63
+ }
@@ -0,0 +1,67 @@
1
+ /* Section — a labelled block within a page (see AdminSection). It carries the
2
+ * surface: the page frame is transparent, so what a reader sees as "a block of
3
+ * this page" is exactly one section. A section that needs an inner card still
4
+ * adds `.sa-card`; a section IS one, so the two do not stack.
5
+ */
6
+
7
+ /* Rhythm only for sections sitting directly in the page flow. Deliberately a
8
+ * child selector: sections placed inside a grid or flex container get their
9
+ * spacing from that container, and a blanket margin would fight it. */
10
+ .sa-page > .sa-section,
11
+ .sa-page-body > .sa-section {
12
+ margin-bottom: 16px;
13
+ }
14
+ .sa-page > .sa-section:last-child,
15
+ .sa-page-body > .sa-section:last-child {
16
+ margin-bottom: 0;
17
+ }
18
+
19
+ .sa-section {
20
+ background: var(--sa-color-bg-surface);
21
+ border: 1px solid var(--sa-color-border);
22
+ border-radius: var(--sa-radius-section);
23
+ }
24
+ .sa-section__body {
25
+ padding: var(--sa-pad-section);
26
+ }
27
+
28
+ /* The head is the hero's block one level down: same typography, same 22px
29
+ * inset — so a section title sits exactly under the page title — but no tinted
30
+ * box of its own. The section already is a surface; a box inside it would read
31
+ * as a panel within a panel.
32
+ *
33
+ * The size steps down because the level does. Everything else is the hero's. */
34
+ .sa-section__head {
35
+ display: flex;
36
+ align-items: flex-start;
37
+ gap: 20px;
38
+ padding: 18px 22px 0;
39
+ }
40
+ .sa-section__title {
41
+ margin: 0;
42
+ font-family: var(--sa-font-head);
43
+ font-size: var(--sa-text-xl);
44
+ font-weight: 700;
45
+ letter-spacing: -0.02em;
46
+ line-height: 1.25;
47
+ color: var(--sa-color-fg-heading);
48
+ }
49
+ .sa-section__sub {
50
+ margin: 1px 0 0;
51
+ font-size: var(--sa-text-md);
52
+ line-height: 1.35;
53
+ color: var(--sa-color-fg-muted);
54
+ }
55
+ .sa-section__actions {
56
+ margin-left: auto;
57
+ display: flex;
58
+ gap: 8px;
59
+ align-items: center;
60
+ }
61
+
62
+ /* Key-value grid — the layout `KvBlock` is meant to sit in. */
63
+ .sa-kv-grid {
64
+ display: grid;
65
+ grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
66
+ gap: 12px;
67
+ }
@@ -0,0 +1,189 @@
1
+ /* Statistics — one tile look for the whole admin surface (see AdminStatistics
2
+ * and AdminKpi). Seven near-copies preceded this; two of them were byte
3
+ * identical. Every radius here resolves to `--sa-radius-tile`, which is the
4
+ * single place a tile's corner is decided.
5
+ *
6
+ * Two arrangements, because the tiles serve two jobs: `grid` for informational
7
+ * rows that should share a baseline, `strip` for the clickable filter pills
8
+ * that wrap along a toolbar.
9
+ */
10
+
11
+ .sa-stats {
12
+ display: grid;
13
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
14
+ /* Kept in step with GAP_PX in AdminStatistics, which subtracts the gap
15
+ * total when it caps the track count. */
16
+ gap: 10px;
17
+ }
18
+
19
+ .sa-kpi {
20
+ display: flex;
21
+ align-items: stretch;
22
+ gap: 12px;
23
+ background: var(--sa-color-bg-surface);
24
+ border: 1px solid var(--sa-color-border);
25
+ border-radius: var(--sa-radius-tile);
26
+ padding: 10px 14px 9px;
27
+ font: inherit;
28
+ color: inherit;
29
+ text-align: left;
30
+ }
31
+ /* The figure column. It carries the stacking so the tile itself can put an
32
+ * icon beside it without a second set of rules. */
33
+ .sa-kpi__text {
34
+ display: flex;
35
+ flex-direction: column;
36
+ align-items: flex-start;
37
+ gap: 2px;
38
+ flex: 1;
39
+ min-width: 0;
40
+ }
41
+ /* Subs line up across a row rather than floating under uneven values. */
42
+ .sa-stats .sa-kpi__sub {
43
+ margin-top: auto;
44
+ }
45
+
46
+ .sa-kpi__label {
47
+ font-size: var(--sa-text-sm);
48
+ font-weight: 600;
49
+ color: var(--sa-color-fg-muted);
50
+ order: 2;
51
+ }
52
+ .sa-kpi__value {
53
+ font-size: var(--sa-text-2xl);
54
+ font-weight: 700;
55
+ letter-spacing: -0.02em;
56
+ line-height: 1.05;
57
+ color: var(--sa-color-fg-heading);
58
+ order: 1;
59
+ }
60
+ .sa-kpi__sub {
61
+ font: 500 var(--sa-text-2xs) var(--sa-font-mono);
62
+ letter-spacing: 0.04em;
63
+ color: var(--sa-color-fg-muted);
64
+ padding-top: 1px;
65
+ order: 3;
66
+ }
67
+ .sa-kpi__icon {
68
+ display: inline-flex;
69
+ align-items: center;
70
+ justify-content: center;
71
+ color: var(--sa-color-fg-muted);
72
+ }
73
+
74
+ /* Inline tiles: the icon leads in a tinted square and the label sits above the
75
+ * figure, for rows where the icon carries the identity of the number. */
76
+ .sa-kpi--inline {
77
+ align-items: center;
78
+ padding: 12px 14px;
79
+ }
80
+ .sa-kpi--inline .sa-kpi__icon {
81
+ width: 34px;
82
+ height: 34px;
83
+ flex: none;
84
+ border-radius: var(--sa-radius-tile);
85
+ background: var(--sa-color-accent-surface-strong);
86
+ color: var(--sa-color-accent);
87
+ }
88
+ .sa-kpi--inline .sa-kpi__label {
89
+ order: 1;
90
+ text-transform: uppercase;
91
+ letter-spacing: 0.06em;
92
+ }
93
+ .sa-kpi--inline .sa-kpi__value {
94
+ order: 2;
95
+ }
96
+
97
+ /* Interactive tiles — rendered as <button> by AdminKpi when it has an action. */
98
+ .sa-kpi--action {
99
+ cursor: pointer;
100
+ transition:
101
+ border-color 0.12s,
102
+ background 0.12s,
103
+ box-shadow 0.12s;
104
+ }
105
+ .sa-kpi--action:hover {
106
+ background: var(--sa-color-bg-sunken);
107
+ border-color: var(--sa-color-border-strong);
108
+ }
109
+ .sa-kpi--selected {
110
+ border-color: var(--sa-color-fg-heading);
111
+ box-shadow:
112
+ 0 0 0 1px var(--sa-color-fg-heading) inset,
113
+ var(--sa-shadow-2);
114
+ }
115
+
116
+ /* Tone. By default only the value takes the colour; `--surface` tints the
117
+ * whole tile, which is what the discovery and draft tiles need to read as a
118
+ * state rather than as a number. */
119
+ .sa-kpi--positive .sa-kpi__value {
120
+ color: var(--sa-color-positive);
121
+ }
122
+ .sa-kpi--warn .sa-kpi__value {
123
+ color: var(--sa-color-warning);
124
+ }
125
+ .sa-kpi--danger .sa-kpi__value {
126
+ color: var(--sa-color-negative);
127
+ }
128
+ .sa-kpi--info .sa-kpi__value {
129
+ color: var(--sa-color-info-fg);
130
+ }
131
+ .sa-kpi--muted .sa-kpi__value {
132
+ color: var(--sa-color-fg-secondary);
133
+ }
134
+ .sa-kpi--purple .sa-kpi__value {
135
+ color: var(--sa-color-feature-fg);
136
+ }
137
+
138
+ .sa-kpi--surface.sa-kpi--positive {
139
+ border-color: var(--sa-color-positive-border);
140
+ background: var(--sa-color-positive-surface);
141
+ }
142
+ .sa-kpi--surface.sa-kpi--warn {
143
+ border-color: var(--sa-color-warning-border);
144
+ background: var(--sa-color-warning-surface);
145
+ }
146
+ .sa-kpi--surface.sa-kpi--danger {
147
+ border-color: var(--sa-color-negative-border);
148
+ background: var(--sa-color-negative-surface);
149
+ }
150
+ .sa-kpi--surface.sa-kpi--positive .sa-kpi__label,
151
+ .sa-kpi--surface.sa-kpi--warn .sa-kpi__label,
152
+ .sa-kpi--surface.sa-kpi--danger .sa-kpi__label {
153
+ color: inherit;
154
+ }
155
+
156
+ /* Selected tiles keep the tone in the outline and take the neutral elevation.
157
+ * Five tinted shadows preceded this — one per tone, each a different rgba() —
158
+ * and at 6% alpha under a 1px outline none of them was distinguishable from
159
+ * the others. The outline is what carries the state. */
160
+ .sa-kpi--selected.sa-kpi--positive {
161
+ border-color: var(--sa-color-positive-strong);
162
+ box-shadow:
163
+ 0 0 0 1px var(--sa-color-positive-strong) inset,
164
+ var(--sa-shadow-2);
165
+ }
166
+ .sa-kpi--selected.sa-kpi--warn {
167
+ border-color: var(--sa-color-warning-strong);
168
+ box-shadow:
169
+ 0 0 0 1px var(--sa-color-warning-strong) inset,
170
+ var(--sa-shadow-2);
171
+ }
172
+ .sa-kpi--selected.sa-kpi--danger {
173
+ border-color: var(--sa-color-negative);
174
+ box-shadow:
175
+ 0 0 0 1px var(--sa-color-negative) inset,
176
+ var(--sa-shadow-2);
177
+ }
178
+ .sa-kpi--selected.sa-kpi--info {
179
+ border-color: var(--sa-color-info-strong);
180
+ box-shadow:
181
+ 0 0 0 1px var(--sa-color-info-strong) inset,
182
+ var(--sa-shadow-2);
183
+ }
184
+ .sa-kpi--selected.sa-kpi--purple {
185
+ border-color: var(--sa-color-feature);
186
+ box-shadow:
187
+ 0 0 0 1px var(--sa-color-feature) inset,
188
+ var(--sa-shadow-2);
189
+ }
@@ -0,0 +1,60 @@
1
+ /* Lists (see AdminTable). The package shipped no table rules at all until
2
+ * this, which is why nine pages each looked slightly different.
3
+ */
4
+
5
+ /* The surface under the rows. Measured on the promo-codes page in dark mode:
6
+ * the `.q-table__container.q-table__card.q-dark` element resolved to
7
+ * rgb(29, 29, 29) while the section around it was rgb(15, 23, 42) — Quasar's
8
+ * neutral grey inside our slate. Only `thead th` and `tbody td` were ours, and
9
+ * neither of them is the table's background. */
10
+ .sa-page .q-table__container,
11
+ .sa-portal .q-table__container {
12
+ background: var(--sa-color-bg-surface);
13
+ color: var(--sa-color-fg-body);
14
+ }
15
+
16
+ .sa-page .q-table thead th {
17
+ font-size: var(--sa-text-xs);
18
+ font-weight: 700;
19
+ letter-spacing: 0.04em;
20
+ text-transform: uppercase;
21
+ color: var(--sa-color-fg-muted);
22
+ background: var(--sa-color-bg-sunken);
23
+ border-bottom: 1px solid var(--sa-color-border);
24
+ padding: 10px 14px;
25
+ white-space: nowrap;
26
+ }
27
+ .sa-page .q-table tbody td {
28
+ font-size: var(--sa-text-md);
29
+ color: var(--sa-color-fg-body);
30
+ border-bottom: 1px solid var(--sa-color-border-soft);
31
+ padding: 10px 14px;
32
+ }
33
+ .sa-page .q-table tbody tr:last-child td {
34
+ border-bottom: 0;
35
+ }
36
+ .sa-page .q-table tbody tr:hover td {
37
+ background: var(--sa-color-bg-sunken);
38
+ }
39
+ /* Sorted column: the header carries the state, not just the arrow. */
40
+ .sa-page .q-table thead th.sortable:hover,
41
+ .sa-page .q-table thead th[aria-sort]:not([aria-sort='none']) {
42
+ color: var(--sa-color-fg-heading);
43
+ }
44
+ .sa-page .q-table thead th[aria-sort]:not([aria-sort='none']) {
45
+ background: var(--sa-color-accent-surface-strong);
46
+ }
47
+ .sa-table__actions {
48
+ white-space: nowrap;
49
+ }
50
+ .sa-table__empty {
51
+ color: var(--sa-color-fg-muted);
52
+ }
53
+
54
+ /* The pager sits below the list, separated by a line that runs the full width
55
+ * of the surface — hence the pull-out by the section's own padding token. */
56
+ .sa-table__foot {
57
+ margin: 0 calc(-1 * var(--sa-pad-section)) calc(-1 * var(--sa-pad-section));
58
+ padding: 0 var(--sa-pad-section) 10px;
59
+ border-top: 1px solid var(--sa-color-border);
60
+ }
@@ -0,0 +1,42 @@
1
+ /* The SaaSiCat admin theme. One import: `@saasicat/ui-vue/theme.css`.
2
+ *
3
+ * ── The three layers, and why there are three ──────────────────────────────
4
+ *
5
+ * L1 tokens.primitive.css the palette references nothing
6
+ * tokens.scale.css space/type/… references nothing
7
+ * L2 tokens.semantic.*.css roles references L1 and --q-*
8
+ * L3 components/*.css the shell chrome references L2
9
+ *
10
+ * A component asks for a ROLE (`--sa-color-fg-muted`), never for a value
11
+ * (`--sa-neutral-500`), and never for a literal. That one rule is what makes
12
+ * the dark theme a file instead of a project: only L2 knows the difference
13
+ * between light and dark, so only L2 has two versions.
14
+ *
15
+ * The direction is enforced by `tests/theme-layer-discipline.test.js`
16
+ * (rule 22) and the key parity of the two L2 files by
17
+ * `tests/theme-token-parity.test.js` (rule 21).
18
+ *
19
+ * ── Order is the cascade ───────────────────────────────────────────────────
20
+ *
21
+ * Tokens first, because everything below reads them; dark after light, because
22
+ * it overrides. Within `components/`, the order is the order the old single
23
+ * stylesheet had — several rules there share a specificity and settle it by
24
+ * position, so this list is not alphabetical and must not be sorted.
25
+ */
26
+
27
+ @import './tokens.primitive.css';
28
+ @import './tokens.scale.css';
29
+ @import './tokens.semantic.light.css';
30
+ @import './tokens.semantic.dark.css';
31
+ @import './tokens.legacy.css';
32
+
33
+ @import './base.css';
34
+ @import './components/hero.css';
35
+ @import './components/button.css';
36
+ @import './components/field.css';
37
+ @import './components/statistics.css';
38
+ @import './components/body.css';
39
+ @import './components/section.css';
40
+ @import './components/card.css';
41
+ @import './components/paginator.css';
42
+ @import './components/table.css';
@@ -0,0 +1,147 @@
1
+ /* The pre-role token names, kept alive as aliases for one release.
2
+ *
3
+ * Every name below is what `sa-theme.css` published before the role layer
4
+ * existed. They are aliases, not values: each resolves to the role it turned
5
+ * out to mean, so a consumer stylesheet that still says `var(--sa-muted)` keeps
6
+ * working — and starts following the dark theme for free.
7
+ *
8
+ * ── They are aliases in ONE direction ──────────────────────────────────────
9
+ *
10
+ * Reading an old name still works. OVERRIDING one no longer does: setting
11
+ * `--sa-primary` in your own `:root` used to re-tint the admin, and now the
12
+ * role it feeds is `--sa-color-accent`, which reads Quasar's `--q-primary`.
13
+ * The replacement is better than what it removes — set `$primary` and the
14
+ * admin, Quasar's own components and the tenant-facing pages all follow one
15
+ * value instead of three. At runtime, use
16
+ * `setCssVar('primary', value, document.documentElement)`; the default target
17
+ * is `<body>`, which the roles on `:root` cannot see.
18
+ *
19
+ * To override a ROLE rather than the brand, write both of its values, on the
20
+ * same selectors the theme uses — `:root` for light, and
21
+ * `[data-sa-theme='dark'], body.body--dark` for dark. A role has two values, so
22
+ * an override has two, and `:root` alone silently loses in the Quasar-triggered
23
+ * dark path. `docs/design-guide.md#overriding-a-role` has the measurements.
24
+ *
25
+ * Nothing in this repository or in the scaffolder template overrode an `--sa-*`
26
+ * name; that was checked before choosing this direction.
27
+ *
28
+ * ── Consolidations, so a moved colour is not a surprise ────────────────────
29
+ *
30
+ * Five names had no role of their own and now share one, which is the point of
31
+ * the exercise rather than a side effect:
32
+ *
33
+ * --sa-border-strong #d1d5db → --sa-color-border-strong (#cbd5e1, the slate
34
+ * ramp — the grey ramp had exactly this one member)
35
+ * --sa-positive-soft, --sa-warning-soft translucent tints → the opaque
36
+ * `-surface` roles, so they stack predictably
37
+ * --sa-amber-light #fbbf24 → --sa-color-warning-strong (#f59e0b)
38
+ * --sa-primary-50 #eff6ff → --sa-color-accent-surface-strong, which now
39
+ * follows the host's brand instead of staying blue
40
+ *
41
+ * This file is deleted in 1.0. Its contents are the migration checklist.
42
+ */
43
+
44
+ :root {
45
+ --sa-bg-app: var(--sa-color-bg-app);
46
+ --sa-bg-surface: var(--sa-color-bg-surface);
47
+ --sa-bg-surface-2: var(--sa-color-bg-sunken);
48
+
49
+ --sa-primary: var(--sa-color-accent);
50
+ --sa-primary-strong: var(--sa-color-accent-strong);
51
+ --sa-primary-soft: var(--sa-color-accent-surface);
52
+ --sa-primary-softer: var(--sa-color-accent-surface-soft);
53
+ --sa-primary-border: var(--sa-color-accent-border);
54
+ --sa-primary-50: var(--sa-color-accent-surface-strong);
55
+
56
+ --sa-positive: var(--sa-color-positive);
57
+ --sa-positive-light: var(--sa-color-positive-strong);
58
+ --sa-positive-soft: var(--sa-color-positive-surface);
59
+ --sa-positive-bg: var(--sa-color-positive-surface);
60
+
61
+ --sa-negative: var(--sa-color-negative);
62
+ --sa-negative-soft: var(--sa-color-negative-surface);
63
+
64
+ --sa-warning: var(--sa-color-warning);
65
+ --sa-warning-soft: var(--sa-color-warning-surface);
66
+ --sa-warning-bg: var(--sa-color-warning-surface);
67
+ --sa-amber: var(--sa-color-warning-strong);
68
+ --sa-amber-light: var(--sa-color-warning-strong);
69
+ --sa-amber-border: var(--sa-color-warning-border);
70
+
71
+ /* Read by `TenantsPage` as `var(--sa-accent, #7c3aed)` and never declared
72
+ * anywhere — the fallback was the only value it ever had. It marked the
73
+ * catalogue-feature action, which is now a role. */
74
+ --sa-accent: var(--sa-color-feature);
75
+
76
+ --sa-heading: var(--sa-color-fg-heading);
77
+ --sa-body: var(--sa-color-fg-body);
78
+ --sa-muted: var(--sa-color-fg-muted);
79
+ --sa-muted-dark: var(--sa-color-fg-secondary);
80
+ --sa-muted-light: var(--sa-color-fg-subtle);
81
+
82
+ --sa-border: var(--sa-color-border);
83
+ --sa-border-soft: var(--sa-color-border-soft);
84
+ --sa-border-strong: var(--sa-color-border-strong);
85
+
86
+ --sa-section-pad: var(--sa-pad-section);
87
+ }
88
+
89
+ /* The same declarations again, under the dark triggers.
90
+ *
91
+ * Not redundancy — an alias is computed WHERE it is declared. `--sa-heading` on
92
+ * `:root` substitutes whatever `--sa-color-fg-heading` resolves to ON `:root`,
93
+ * and descendants inherit that finished value. Quasar's trigger redeclares the
94
+ * roles on `<body>`, which never re-evaluates an alias computed one level up:
95
+ * a consumer still reading `var(--sa-heading)` kept light text while everything
96
+ * around it went dark, and this file promises the opposite for one release.
97
+ *
98
+ * The attribute trigger sits on `:root` itself and would not need this, but the
99
+ * pair is written the way the design guide tells consumers to write an override
100
+ * — both values, on the selectors the theme uses. A file that documents a rule
101
+ * and then exempts itself from it teaches the exemption.
102
+ */
103
+ [data-sa-theme='dark'],
104
+ body.body--dark {
105
+ --sa-bg-app: var(--sa-color-bg-app);
106
+ --sa-bg-surface: var(--sa-color-bg-surface);
107
+ --sa-bg-surface-2: var(--sa-color-bg-sunken);
108
+
109
+ --sa-primary: var(--sa-color-accent);
110
+ --sa-primary-strong: var(--sa-color-accent-strong);
111
+ --sa-primary-soft: var(--sa-color-accent-surface);
112
+ --sa-primary-softer: var(--sa-color-accent-surface-soft);
113
+ --sa-primary-border: var(--sa-color-accent-border);
114
+ --sa-primary-50: var(--sa-color-accent-surface-strong);
115
+
116
+ --sa-positive: var(--sa-color-positive);
117
+ --sa-positive-light: var(--sa-color-positive-strong);
118
+ --sa-positive-soft: var(--sa-color-positive-surface);
119
+ --sa-positive-bg: var(--sa-color-positive-surface);
120
+
121
+ --sa-negative: var(--sa-color-negative);
122
+ --sa-negative-soft: var(--sa-color-negative-surface);
123
+
124
+ --sa-warning: var(--sa-color-warning);
125
+ --sa-warning-soft: var(--sa-color-warning-surface);
126
+ --sa-warning-bg: var(--sa-color-warning-surface);
127
+ --sa-amber: var(--sa-color-warning-strong);
128
+ --sa-amber-light: var(--sa-color-warning-strong);
129
+ --sa-amber-border: var(--sa-color-warning-border);
130
+
131
+ /* Read by `TenantsPage` as `var(--sa-accent, #7c3aed)` and never declared
132
+ * anywhere — the fallback was the only value it ever had. It marked the
133
+ * catalogue-feature action, which is now a role. */
134
+ --sa-accent: var(--sa-color-feature);
135
+
136
+ --sa-heading: var(--sa-color-fg-heading);
137
+ --sa-body: var(--sa-color-fg-body);
138
+ --sa-muted: var(--sa-color-fg-muted);
139
+ --sa-muted-dark: var(--sa-color-fg-secondary);
140
+ --sa-muted-light: var(--sa-color-fg-subtle);
141
+
142
+ --sa-border: var(--sa-color-border);
143
+ --sa-border-soft: var(--sa-color-border-soft);
144
+ --sa-border-strong: var(--sa-color-border-strong);
145
+
146
+ --sa-section-pad: var(--sa-pad-section);
147
+ }
@@ -0,0 +1,112 @@
1
+ /* Layer 1 — primitives. The raw palette, and nothing else.
2
+ *
3
+ * A primitive says WHAT a colour is (`--sa-red-600` is a particular red). It
4
+ * never says what the colour is FOR — that is layer 2's job, and it is the only
5
+ * layer that changes between light and dark. Consequently:
6
+ *
7
+ * - nothing in this file may reference another variable;
8
+ * - nothing outside `tokens.semantic.*.css` may reference this file.
9
+ *
10
+ * A component reaching for `--sa-red-600` instead of `--sa-color-negative` is
11
+ * the bug this split exists to prevent: it is invisible in review, and it is
12
+ * the reason a dark theme costs 62 files instead of one. Rule 22 in the
13
+ * layer-discipline test enforces both directions.
14
+ *
15
+ * The ramps are the ones the admin surface already used. Steps that no role
16
+ * consumes are absent on purpose: a palette with unused rungs invites people to
17
+ * pick one, and then the role layer is no longer the single answer.
18
+ */
19
+
20
+ :root {
21
+ /* ── Neutral (slate). Carries text, surfaces and lines. ─────────────── */
22
+ --sa-white: #ffffff;
23
+ --sa-black: #000000;
24
+ --sa-neutral-25: #fbfbfd;
25
+ --sa-neutral-50: #f8fafc;
26
+ --sa-neutral-100: #f1f5f9;
27
+ --sa-neutral-200: #e2e8f0;
28
+ --sa-neutral-300: #cbd5e1;
29
+ --sa-neutral-400: #94a3b8;
30
+ /* Between 400 and 500, and it exists for a measured reason: `fg-subtle` at
31
+ * slate-400 reads 2.56:1 on a white card and 2.34:1 on the page canvas —
32
+ * under the 3:1 floor, i.e. captions and price units that are decoration
33
+ * rather than information. Slate-500 is the next rung and too dark to stay
34
+ * quiet. This one clears 3:1 on both. */
35
+ --sa-neutral-450: #7c8aa0;
36
+ --sa-neutral-500: #64748b;
37
+ --sa-neutral-600: #475569;
38
+ --sa-neutral-700: #334155;
39
+ --sa-neutral-800: #1e293b;
40
+ --sa-neutral-900: #0f172a;
41
+ --sa-neutral-950: #0b1220;
42
+ --sa-neutral-975: #070d18;
43
+
44
+ /* ── Blue. The `info` tone. ─────────────────────────────────────────── */
45
+ --sa-blue-50: #eff6ff;
46
+ --sa-blue-100: #dbeafe;
47
+ --sa-blue-200: #bfdbfe;
48
+ --sa-blue-300: #93c5fd;
49
+ --sa-blue-500: #3b82f6;
50
+ --sa-blue-600: #2563eb;
51
+ --sa-blue-700: #1d4ed8;
52
+ --sa-blue-800: #1e40af;
53
+
54
+ /* The platform's own brand blue. Kept out of the blue ramp because it is
55
+ * not a step in it: it is the value the accent role falls back to when the
56
+ * host application declares no Quasar `$primary` of its own. */
57
+ --sa-brand-blue: #3f6bff;
58
+
59
+ /* ── Green (emerald). `positive`. ───────────────────────────────────── */
60
+ --sa-green-50: #ecfdf5;
61
+ --sa-green-100: #d1fae5;
62
+ --sa-green-200: #a7f3d0;
63
+ --sa-green-500: #10b981;
64
+ --sa-green-700: #047857;
65
+ --sa-green-800: #065f46;
66
+
67
+ /* ── Amber. `warning`, and every "draft / scheduled / pending" state. ─ */
68
+ --sa-amber-50: #fffbeb;
69
+ --sa-amber-100: #fef3c7;
70
+ --sa-amber-200: #fde68a;
71
+ --sa-amber-300: #fcd34d;
72
+ --sa-amber-400: #fbbf24;
73
+ --sa-amber-500: #f59e0b;
74
+ --sa-amber-600: #d97706;
75
+ --sa-amber-700: #b45309;
76
+ --sa-amber-800: #92400e;
77
+
78
+ /* ── Red. `negative`. ───────────────────────────────────────────────── */
79
+ --sa-red-50: #fef2f2;
80
+ --sa-red-100: #fee2e2;
81
+ --sa-red-200: #fecaca;
82
+ --sa-red-300: #fca5a5;
83
+ --sa-red-500: #ef4444;
84
+ --sa-red-600: #dc2626;
85
+ --sa-red-700: #b91c1c;
86
+ --sa-red-800: #991b1b;
87
+
88
+ /* ── Indigo. `scheduled` — a version that is planned but not yet live. */
89
+ --sa-indigo-50: #eef2ff;
90
+ --sa-indigo-100: #e0e7ff;
91
+ --sa-indigo-200: #c7d2fe;
92
+ --sa-indigo-400: #818cf8;
93
+ --sa-indigo-700: #4338ca;
94
+
95
+ /* ── Violet and sky. The two catalogue entities, feature and quota. ── */
96
+ --sa-violet-50: #ede9fe;
97
+ --sa-violet-200: #ddd6fe;
98
+ --sa-violet-500: #8b5cf6;
99
+ --sa-violet-700: #6d28d9;
100
+ --sa-sky-50: #f0f9ff;
101
+ --sa-sky-200: #bae6fd;
102
+ --sa-sky-500: #0ea5e9;
103
+ /* Sky-500 is 2.6:1 on a white card — fine as a dot or a border, too pale to
104
+ * carry a label. The identity ramp needs a readable sky, so here is the
105
+ * rung below it. */
106
+ --sa-sky-700: #0369a1;
107
+
108
+ /* ── Type families. ─────────────────────────────────────────────────── */
109
+ --sa-font-head: 'Plus Jakarta Sans', 'Inter', system-ui, sans-serif;
110
+ --sa-font-body: 'Inter', system-ui, sans-serif;
111
+ --sa-font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
112
+ }