@saasicat/ui-vue 0.22.2 → 0.23.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/index.cjs +213 -159
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -1
- 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/components/BundleVersionPublishDialog.vue +17 -17
- package/src/components/KvBlock.vue +6 -6
- package/src/components/MarketingPromotionsTab.vue +78 -78
- 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 +48 -48
- 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 +61 -61
- 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 +49 -49
- package/src/components/plan-detail/PlanDetail.vue +172 -156
- package/src/components/plan-detail/PlanTitleEdit.vue +11 -11
- package/src/components/plan-list/PlanList.vue +98 -77
- package/src/components/plan-matrix/PlanMatrix.vue +89 -89
- package/src/components/plan-review/PlanReview.vue +60 -56
- package/src/components/plan-version-editor/PlanVersionEditor.vue +224 -200
- 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 +36 -36
- package/src/pages-standard/DashboardPage.vue +17 -17
- package/src/pages-standard/DiscoveryPage.vue +55 -49
- package/src/pages-standard/EmailHistoryPage.vue +53 -71
- package/src/pages-standard/MarketingCatalogPage.vue +226 -218
- 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 +7 -6
- 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 +11 -46
- 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/plan-versions/PlanDiffCard.vue +16 -16
- package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +21 -15
- 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-tenant/LimitsRow.vue +4 -4
- package/src/pages-tenant/MySubscriptionBundlesPage.vue +61 -61
- package/src/pages-tenant/OnboardingConfigurator.vue +24 -24
- package/src/pages-tenant/PackageSnapshotPanel.vue +10 -16
- package/src/pages-tenant/PendingVersionBanner.vue +12 -12
- package/src/pages-tenant/PlanChangeWizard.vue +20 -27
- package/src/pages-tenant/TenantPlanSection.vue +13 -21
- package/src/pages-tenant/UsageBar.vue +7 -7
- 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 +28 -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 +48 -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 +108 -0
- package/src/ui/theme/tokens.scale.css +103 -0
- package/src/ui/theme/tokens.semantic.dark.css +173 -0
- package/src/ui/theme/tokens.semantic.light.css +212 -0
- package/src/vue/use-sa-theme.ts +154 -0
|
@@ -511,18 +511,11 @@ watch(
|
|
|
511
511
|
}
|
|
512
512
|
/* CSS vars for light + dark mode */
|
|
513
513
|
.sp-wizard {
|
|
514
|
-
--sp-wiz-border:
|
|
515
|
-
--sp-wiz-text-strong:
|
|
516
|
-
--sp-wiz-text-muted:
|
|
517
|
-
--sp-wiz-text-faint:
|
|
518
|
-
--sp-wiz-border-soft:
|
|
519
|
-
}
|
|
520
|
-
:global(.body--dark) .sp-wizard {
|
|
521
|
-
--sp-wiz-border: rgba(255, 255, 255, 0.12);
|
|
522
|
-
--sp-wiz-text-strong: rgba(255, 255, 255, 0.92);
|
|
523
|
-
--sp-wiz-text-muted: rgba(255, 255, 255, 0.7);
|
|
524
|
-
--sp-wiz-text-faint: rgba(255, 255, 255, 0.6);
|
|
525
|
-
--sp-wiz-border-soft: rgba(255, 255, 255, 0.18);
|
|
514
|
+
--sp-wiz-border: var(--sa-color-border);
|
|
515
|
+
--sp-wiz-text-strong: var(--sa-color-fg-heading);
|
|
516
|
+
--sp-wiz-text-muted: var(--sa-color-fg-secondary);
|
|
517
|
+
--sp-wiz-text-faint: var(--sa-color-fg-muted);
|
|
518
|
+
--sp-wiz-border-soft: var(--sa-color-border-soft);
|
|
526
519
|
}
|
|
527
520
|
.sp-wizard__head {
|
|
528
521
|
display: flex;
|
|
@@ -531,13 +524,13 @@ watch(
|
|
|
531
524
|
border-bottom: 1px solid var(--sp-wiz-border);
|
|
532
525
|
}
|
|
533
526
|
.sp-wizard__title {
|
|
534
|
-
font-size:
|
|
527
|
+
font-size: var(--sa-text-xl);
|
|
535
528
|
font-weight: 600;
|
|
536
529
|
color: var(--sp-wiz-text-strong);
|
|
537
530
|
}
|
|
538
531
|
.sp-wizard__sub {
|
|
539
532
|
color: var(--sp-wiz-text-muted);
|
|
540
|
-
font-size:
|
|
533
|
+
font-size: var(--sa-text-md);
|
|
541
534
|
}
|
|
542
535
|
.sp-wizard__intro {
|
|
543
536
|
color: var(--sp-wiz-text-muted);
|
|
@@ -550,8 +543,8 @@ watch(
|
|
|
550
543
|
padding: 24px 0;
|
|
551
544
|
}
|
|
552
545
|
.sp-wizard__error {
|
|
553
|
-
color: var(--
|
|
554
|
-
background:
|
|
546
|
+
color: var(--sa-color-negative);
|
|
547
|
+
background: var(--sa-color-negative-surface);
|
|
555
548
|
padding: 12px;
|
|
556
549
|
border-radius: 4px;
|
|
557
550
|
}
|
|
@@ -559,14 +552,14 @@ watch(
|
|
|
559
552
|
margin-bottom: 12px;
|
|
560
553
|
}
|
|
561
554
|
.sp-wizard__proration {
|
|
562
|
-
background:
|
|
555
|
+
background: var(--sa-color-info-surface);
|
|
563
556
|
padding: 12px 14px;
|
|
564
557
|
border-radius: 4px;
|
|
565
558
|
margin-top: 12px;
|
|
566
559
|
}
|
|
567
560
|
.sp-wizard__proration h4 {
|
|
568
561
|
margin: 0 0 6px;
|
|
569
|
-
font-size:
|
|
562
|
+
font-size: var(--sa-text-lg);
|
|
570
563
|
}
|
|
571
564
|
.sp-wizard__proration p {
|
|
572
565
|
margin: 0;
|
|
@@ -579,7 +572,7 @@ watch(
|
|
|
579
572
|
.sp-wizard__limits thead th {
|
|
580
573
|
padding: 6px 12px;
|
|
581
574
|
text-align: left;
|
|
582
|
-
font-size:
|
|
575
|
+
font-size: var(--sa-text-sm);
|
|
583
576
|
color: var(--sp-wiz-text-faint);
|
|
584
577
|
text-transform: uppercase;
|
|
585
578
|
border-bottom: 1px solid var(--sp-wiz-border-soft);
|
|
@@ -592,33 +585,33 @@ watch(
|
|
|
592
585
|
.sp-wizard__feat h4,
|
|
593
586
|
.sp-wizard__blockers h4 {
|
|
594
587
|
margin: 0 0 6px;
|
|
595
|
-
font-size:
|
|
588
|
+
font-size: var(--sa-text-lg);
|
|
596
589
|
}
|
|
597
590
|
.sp-wizard__feat--warn {
|
|
598
|
-
color:
|
|
591
|
+
color: var(--sa-color-negative);
|
|
599
592
|
}
|
|
600
593
|
.sp-wizard__blockers {
|
|
601
|
-
color: var(--
|
|
602
|
-
background:
|
|
594
|
+
color: var(--sa-color-negative);
|
|
595
|
+
background: var(--sa-color-negative-surface);
|
|
603
596
|
padding: 12px 14px;
|
|
604
597
|
border-radius: 4px;
|
|
605
598
|
}
|
|
606
599
|
.sp-wizard__warnings {
|
|
607
|
-
color: var(--
|
|
608
|
-
font-size:
|
|
600
|
+
color: var(--sa-color-warning);
|
|
601
|
+
font-size: var(--sa-text-md);
|
|
609
602
|
}
|
|
610
603
|
.sp-wizard__confirm-line {
|
|
611
604
|
color: var(--sp-wiz-text-muted);
|
|
612
605
|
}
|
|
613
606
|
.sp-wizard__price-summary {
|
|
614
607
|
margin-top: 16px;
|
|
615
|
-
background:
|
|
608
|
+
background: var(--sa-color-info-surface);
|
|
616
609
|
padding: 12px 14px;
|
|
617
610
|
border-radius: 4px;
|
|
618
611
|
}
|
|
619
612
|
.sp-wizard__price-summary h4 {
|
|
620
613
|
margin: 0 0 8px;
|
|
621
|
-
font-size:
|
|
614
|
+
font-size: var(--sa-text-lg);
|
|
622
615
|
}
|
|
623
616
|
.sp-wizard__price-row {
|
|
624
617
|
display: flex;
|
|
@@ -616,25 +616,17 @@ async function changePlan(plan: string, cycle: 'MONTHLY' | 'YEARLY', immediate:
|
|
|
616
616
|
|
|
617
617
|
<style>
|
|
618
618
|
.sp-plan-section {
|
|
619
|
-
--sp-text-secondary:
|
|
620
|
-
--sp-text-muted:
|
|
621
|
-
--sp-text-disabled:
|
|
622
|
-
--sp-text-strong:
|
|
623
|
-
--sp-border:
|
|
624
|
-
--sp-summary-bg:
|
|
619
|
+
--sp-text-secondary: var(--sa-color-fg-secondary);
|
|
620
|
+
--sp-text-muted: var(--sa-color-fg-muted);
|
|
621
|
+
--sp-text-disabled: var(--sa-color-fg-subtle);
|
|
622
|
+
--sp-text-strong: var(--sa-color-fg-body);
|
|
623
|
+
--sp-border: var(--sa-color-border);
|
|
624
|
+
--sp-summary-bg: var(--sa-color-info-surface);
|
|
625
625
|
|
|
626
626
|
display: flex;
|
|
627
627
|
flex-direction: column;
|
|
628
628
|
gap: 16px;
|
|
629
629
|
}
|
|
630
|
-
body.body--dark .sp-plan-section {
|
|
631
|
-
--sp-text-secondary: rgba(255, 255, 255, 0.72);
|
|
632
|
-
--sp-text-muted: rgba(255, 255, 255, 0.62);
|
|
633
|
-
--sp-text-disabled: rgba(255, 255, 255, 0.5);
|
|
634
|
-
--sp-text-strong: rgba(255, 255, 255, 0.85);
|
|
635
|
-
--sp-border: rgba(255, 255, 255, 0.16);
|
|
636
|
-
--sp-summary-bg: rgba(25, 118, 210, 0.18);
|
|
637
|
-
}
|
|
638
630
|
.sp-plan-section__loading,
|
|
639
631
|
.sp-plan-section__empty {
|
|
640
632
|
display: flex;
|
|
@@ -644,8 +636,8 @@ body.body--dark .sp-plan-section {
|
|
|
644
636
|
color: var(--sp-text-secondary);
|
|
645
637
|
}
|
|
646
638
|
.sp-plan-section__error {
|
|
647
|
-
color: var(--
|
|
648
|
-
background:
|
|
639
|
+
color: var(--sa-color-negative);
|
|
640
|
+
background: var(--sa-color-negative-surface);
|
|
649
641
|
padding: 12px 16px;
|
|
650
642
|
border-radius: 4px;
|
|
651
643
|
}
|
|
@@ -657,7 +649,7 @@ body.body--dark .sp-plan-section {
|
|
|
657
649
|
flex-wrap: wrap;
|
|
658
650
|
}
|
|
659
651
|
.sp-plan-section__eyebrow {
|
|
660
|
-
font-size:
|
|
652
|
+
font-size: var(--sa-text-sm);
|
|
661
653
|
text-transform: uppercase;
|
|
662
654
|
letter-spacing: 0.05em;
|
|
663
655
|
color: var(--sp-text-muted);
|
|
@@ -665,7 +657,7 @@ body.body--dark .sp-plan-section {
|
|
|
665
657
|
}
|
|
666
658
|
.sp-plan-section__plan-name {
|
|
667
659
|
margin: 0 0 8px;
|
|
668
|
-
font-size:
|
|
660
|
+
font-size: var(--sa-text-2xl);
|
|
669
661
|
font-weight: 600;
|
|
670
662
|
}
|
|
671
663
|
.sp-plan-section__meta {
|
|
@@ -683,7 +675,7 @@ body.body--dark .sp-plan-section {
|
|
|
683
675
|
.sp-plan-section__sub {
|
|
684
676
|
margin: 6px 0 0;
|
|
685
677
|
color: var(--sp-text-muted);
|
|
686
|
-
font-size:
|
|
678
|
+
font-size: var(--sa-text-md);
|
|
687
679
|
}
|
|
688
680
|
.sp-plan-section__usage-title {
|
|
689
681
|
font-weight: 600;
|
|
@@ -716,10 +708,10 @@ body.body--dark .sp-plan-section {
|
|
|
716
708
|
}
|
|
717
709
|
.sp-plan-section__item-price {
|
|
718
710
|
color: var(--sp-text-secondary);
|
|
719
|
-
font-size:
|
|
711
|
+
font-size: var(--sa-text-md);
|
|
720
712
|
}
|
|
721
713
|
.sp-plan-section__item-canceled {
|
|
722
714
|
color: var(--sp-text-disabled);
|
|
723
|
-
font-size:
|
|
715
|
+
font-size: var(--sa-text-md);
|
|
724
716
|
}
|
|
725
717
|
</style>
|
|
@@ -73,20 +73,20 @@ const formattedMax = computed(() => {
|
|
|
73
73
|
display: flex;
|
|
74
74
|
justify-content: space-between;
|
|
75
75
|
align-items: center;
|
|
76
|
-
font-size:
|
|
76
|
+
font-size: var(--sa-text-md);
|
|
77
77
|
}
|
|
78
78
|
.sp-usage-bar__label {
|
|
79
|
-
color: var(--
|
|
79
|
+
color: var(--sa-color-fg-secondary);
|
|
80
80
|
font-weight: 500;
|
|
81
81
|
}
|
|
82
82
|
.sp-usage-bar__values {
|
|
83
83
|
font-variant-numeric: tabular-nums;
|
|
84
|
-
color: var(--
|
|
84
|
+
color: var(--sa-color-fg-secondary);
|
|
85
85
|
}
|
|
86
86
|
.sp-usage-bar__track {
|
|
87
87
|
width: 100%;
|
|
88
88
|
height: 8px;
|
|
89
|
-
background:
|
|
89
|
+
background: var(--sa-color-bg-sunken);
|
|
90
90
|
border-radius: 4px;
|
|
91
91
|
overflow: hidden;
|
|
92
92
|
}
|
|
@@ -96,12 +96,12 @@ const formattedMax = computed(() => {
|
|
|
96
96
|
border-radius: 4px;
|
|
97
97
|
}
|
|
98
98
|
.sp-usage-bar--ok .sp-usage-bar__fill {
|
|
99
|
-
background: var(--
|
|
99
|
+
background: var(--sa-color-positive);
|
|
100
100
|
}
|
|
101
101
|
.sp-usage-bar--warn .sp-usage-bar__fill {
|
|
102
|
-
background: var(--
|
|
102
|
+
background: var(--sa-color-warning-strong);
|
|
103
103
|
}
|
|
104
104
|
.sp-usage-bar--full .sp-usage-bar__fill {
|
|
105
|
-
background: var(--
|
|
105
|
+
background: var(--sa-color-negative);
|
|
106
106
|
}
|
|
107
107
|
</style>
|
|
@@ -246,32 +246,32 @@ function close() {
|
|
|
246
246
|
padding-bottom: 8px;
|
|
247
247
|
}
|
|
248
248
|
.sp-bundle-preview__title {
|
|
249
|
-
font-size:
|
|
249
|
+
font-size: var(--sa-text-lg);
|
|
250
250
|
font-weight: 600;
|
|
251
251
|
}
|
|
252
252
|
.sp-bundle-preview__bundle {
|
|
253
253
|
margin-top: 2px;
|
|
254
|
-
color: var(--sp-text-secondary,
|
|
254
|
+
color: var(--sp-text-secondary, var(--sa-color-fg-secondary));
|
|
255
255
|
}
|
|
256
256
|
.sp-bundle-preview__loading {
|
|
257
257
|
display: flex;
|
|
258
258
|
align-items: center;
|
|
259
259
|
gap: 12px;
|
|
260
|
-
color: var(--sp-text-secondary,
|
|
260
|
+
color: var(--sp-text-secondary, var(--sa-color-fg-secondary));
|
|
261
261
|
}
|
|
262
262
|
.sp-bundle-preview__error {
|
|
263
|
-
color: var(--
|
|
263
|
+
color: var(--sa-color-negative);
|
|
264
264
|
}
|
|
265
265
|
.sp-bundle-preview__blockers {
|
|
266
|
-
background:
|
|
267
|
-
color: var(--
|
|
266
|
+
background: var(--sa-color-negative-surface);
|
|
267
|
+
color: var(--sa-color-negative);
|
|
268
268
|
}
|
|
269
269
|
.sp-bundle-preview__warnings {
|
|
270
|
-
background:
|
|
270
|
+
background: var(--sa-color-warning-surface-strong);
|
|
271
271
|
}
|
|
272
272
|
.sp-bundle-preview__block-title {
|
|
273
273
|
font-weight: 600;
|
|
274
|
-
font-size:
|
|
274
|
+
font-size: var(--sa-text-md);
|
|
275
275
|
margin-bottom: 6px;
|
|
276
276
|
}
|
|
277
277
|
.sp-bundle-preview__block-subtitle {
|
|
@@ -281,26 +281,26 @@ function close() {
|
|
|
281
281
|
.sp-bundle-preview__warnings ul {
|
|
282
282
|
margin: 0;
|
|
283
283
|
padding-left: 18px;
|
|
284
|
-
font-size:
|
|
284
|
+
font-size: var(--sa-text-md);
|
|
285
285
|
}
|
|
286
286
|
.sp-bundle-preview__requires {
|
|
287
287
|
margin-top: 6px;
|
|
288
|
-
font-size:
|
|
288
|
+
font-size: var(--sa-text-md);
|
|
289
289
|
}
|
|
290
290
|
.sp-bundle-preview__price-row {
|
|
291
291
|
display: flex;
|
|
292
292
|
justify-content: space-between;
|
|
293
293
|
gap: 16px;
|
|
294
294
|
padding: 4px 0;
|
|
295
|
-
font-size:
|
|
295
|
+
font-size: var(--sa-text-lg);
|
|
296
296
|
}
|
|
297
297
|
.sp-bundle-preview__price-row small {
|
|
298
298
|
font-weight: 400;
|
|
299
|
-
color: var(--sp-text-muted,
|
|
299
|
+
color: var(--sp-text-muted, var(--sa-color-fg-muted));
|
|
300
300
|
}
|
|
301
301
|
.sp-bundle-preview__note {
|
|
302
|
-
font-size:
|
|
303
|
-
color: var(--sp-text-muted,
|
|
302
|
+
font-size: var(--sa-text-md);
|
|
303
|
+
color: var(--sp-text-muted, var(--sa-color-fg-muted));
|
|
304
304
|
padding: 4px 0;
|
|
305
305
|
}
|
|
306
306
|
</style>
|
|
@@ -246,9 +246,9 @@ const availableRows = computed<AvailableRow[]>(() =>
|
|
|
246
246
|
|
|
247
247
|
<style scoped>
|
|
248
248
|
.sp-bundle-store__subtitle {
|
|
249
|
-
font-size:
|
|
249
|
+
font-size: var(--sa-text-md);
|
|
250
250
|
font-weight: 600;
|
|
251
|
-
color: var(--sp-text-strong,
|
|
251
|
+
color: var(--sp-text-strong, var(--sa-color-fg-body));
|
|
252
252
|
margin: 4px 0 8px;
|
|
253
253
|
}
|
|
254
254
|
.sp-bundle-store__available {
|
|
@@ -260,8 +260,8 @@ const availableRows = computed<AvailableRow[]>(() =>
|
|
|
260
260
|
gap: 12px;
|
|
261
261
|
}
|
|
262
262
|
.sp-bundle-store__empty {
|
|
263
|
-
color: var(--sp-text-muted,
|
|
264
|
-
font-size:
|
|
263
|
+
color: var(--sp-text-muted, var(--sa-color-fg-muted));
|
|
264
|
+
font-size: var(--sa-text-md);
|
|
265
265
|
}
|
|
266
266
|
.sp-bundle-store__grid {
|
|
267
267
|
display: grid;
|
|
@@ -288,7 +288,7 @@ const availableRows = computed<AvailableRow[]>(() =>
|
|
|
288
288
|
}
|
|
289
289
|
.sp-bundle-store__card-name {
|
|
290
290
|
font-weight: 600;
|
|
291
|
-
font-size:
|
|
291
|
+
font-size: var(--sa-text-lg);
|
|
292
292
|
}
|
|
293
293
|
.sp-bundle-store__card-price {
|
|
294
294
|
font-weight: 600;
|
|
@@ -296,16 +296,16 @@ const availableRows = computed<AvailableRow[]>(() =>
|
|
|
296
296
|
}
|
|
297
297
|
.sp-bundle-store__card-price small {
|
|
298
298
|
font-weight: 400;
|
|
299
|
-
color: var(--sp-text-muted,
|
|
299
|
+
color: var(--sp-text-muted, var(--sa-color-fg-muted));
|
|
300
300
|
}
|
|
301
301
|
.sp-bundle-store__card-desc {
|
|
302
302
|
margin: 0;
|
|
303
|
-
font-size:
|
|
304
|
-
color: var(--sp-text-secondary,
|
|
303
|
+
font-size: var(--sa-text-md);
|
|
304
|
+
color: var(--sp-text-secondary, var(--sa-color-fg-secondary));
|
|
305
305
|
}
|
|
306
306
|
.sp-bundle-store__card-feats {
|
|
307
|
-
font-size:
|
|
308
|
-
color: var(--sp-text-muted,
|
|
307
|
+
font-size: var(--sa-text-sm);
|
|
308
|
+
color: var(--sp-text-muted, var(--sa-color-fg-muted));
|
|
309
309
|
}
|
|
310
310
|
.sp-bundle-store__card-feats-label {
|
|
311
311
|
font-weight: 500;
|
|
@@ -118,8 +118,8 @@ const features = computed<FeatureRow[]>(() => {
|
|
|
118
118
|
flex-wrap: wrap;
|
|
119
119
|
}
|
|
120
120
|
.sp-feature-matrix__desc {
|
|
121
|
-
font-size:
|
|
122
|
-
color: var(--sp-text-muted,
|
|
121
|
+
font-size: var(--sa-text-sm);
|
|
122
|
+
color: var(--sp-text-muted, var(--sa-color-fg-muted));
|
|
123
123
|
margin-top: 2px;
|
|
124
124
|
}
|
|
125
125
|
</style>
|
|
@@ -44,12 +44,19 @@ import {
|
|
|
44
44
|
type SuperAdminLoginAdapter,
|
|
45
45
|
} from '../vue/super-admin-context.js';
|
|
46
46
|
import { SUPER_ADMIN_NOTIFY_KEY, type UiNotify } from '../vue/ui-notify.js';
|
|
47
|
+
import {
|
|
48
|
+
SA_THEME_KEY,
|
|
49
|
+
createSaTheme,
|
|
50
|
+
type SaTheme,
|
|
51
|
+
type SaThemeOptions,
|
|
52
|
+
} from '../vue/use-sa-theme.js';
|
|
47
53
|
import {
|
|
48
54
|
SUPER_ADMIN_I18N_KEY,
|
|
49
55
|
createSuperAdminI18n,
|
|
50
56
|
type SuperAdminI18n,
|
|
51
57
|
type SuperAdminI18nOptions,
|
|
52
58
|
} from '../vue/use-super-admin-i18n.js';
|
|
59
|
+
import { bindSaThemeToDocument } from './dark-bridge.js';
|
|
53
60
|
import { quasarNotify } from './notify.js';
|
|
54
61
|
|
|
55
62
|
export interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
|
|
@@ -112,6 +119,17 @@ export interface CreateSuperAdminAppOptions extends SuperAdminGuardOptions {
|
|
|
112
119
|
* runtime. Consumed via `useSuperAdminI18n()` / `useSaMessages()`.
|
|
113
120
|
*/
|
|
114
121
|
i18n?: SuperAdminI18nOptions;
|
|
122
|
+
/**
|
|
123
|
+
* Optional: colour scheme. Default follows the operating system and
|
|
124
|
+
* remembers an explicit pick. The context is returned on the handle
|
|
125
|
+
* (`handle.theme`) — set `handle.theme.scheme.value = 'dark'` to switch at
|
|
126
|
+
* runtime, and read it anywhere via `useSaTheme()`.
|
|
127
|
+
*
|
|
128
|
+
* Whatever it resolves to is mirrored onto `<html data-sa-theme>` AND into
|
|
129
|
+
* Quasar's `Dark`, so the platform's surfaces and Quasar's own components
|
|
130
|
+
* never disagree.
|
|
131
|
+
*/
|
|
132
|
+
theme?: SaThemeOptions;
|
|
115
133
|
}
|
|
116
134
|
|
|
117
135
|
export interface SuperAdminAppHandle {
|
|
@@ -120,10 +138,29 @@ export interface SuperAdminAppHandle {
|
|
|
120
138
|
pinia: Pinia;
|
|
121
139
|
/** i18n context of the shell — switch locale via `i18n.locale.value`. */
|
|
122
140
|
i18n: SuperAdminI18n;
|
|
141
|
+
/** Colour scheme of the shell — switch via `theme.scheme.value`. */
|
|
142
|
+
theme: SaTheme;
|
|
123
143
|
/** Mounts the app on a selector. Returns the root component instance. */
|
|
124
144
|
mount: (selector: string | Element) => ReturnType<App['mount']>;
|
|
145
|
+
/**
|
|
146
|
+
* Releases what this helper attached to the DOCUMENT — the theme's
|
|
147
|
+
* `prefers-color-scheme` subscription and the bridge that mirrors it onto
|
|
148
|
+
* `data-sa-theme` and Quasar's `Dark`. Idempotent; it does not unmount the
|
|
149
|
+
* app, which is `handle.app.unmount()`.
|
|
150
|
+
*
|
|
151
|
+
* Only matters where a shell is torn down while the document survives: hot
|
|
152
|
+
* reload, a micro-frontend, a second shell in one page. Skip it and the old
|
|
153
|
+
* bridge keeps writing to the one document on the next OS theme change, and
|
|
154
|
+
* can overrule the scheme the new shell was given.
|
|
155
|
+
*/
|
|
156
|
+
dispose: () => void;
|
|
125
157
|
}
|
|
126
158
|
|
|
159
|
+
/** Marks Quasar's teleported nodes so the theme can reach them. Exported so a
|
|
160
|
+
* test fixture can install the same config a real app gets — without it the
|
|
161
|
+
* `.sa-portal` rules are exercised by nothing. */
|
|
162
|
+
export const SA_PORTAL_CLASS = 'sa-portal';
|
|
163
|
+
|
|
127
164
|
const DEFAULT_QUASAR_OPTIONS: QuasarPluginOptions = {
|
|
128
165
|
plugins: { Notify, Dialog, Loading },
|
|
129
166
|
config: { notify: { position: 'top-right', timeout: 3000 } },
|
|
@@ -138,7 +175,35 @@ const DEFAULT_QUASAR_OPTIONS: QuasarPluginOptions = {
|
|
|
138
175
|
export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperAdminAppHandle {
|
|
139
176
|
const app = createApp(options.rootComponent);
|
|
140
177
|
|
|
141
|
-
|
|
178
|
+
// Quasar teleports every dialog, menu and tooltip into a div appended to
|
|
179
|
+
// `<body>`, so a rule prefixed with `.sa-page` never reaches them — which is
|
|
180
|
+
// why dialog cards kept Quasar's neutral grey in dark mode while the page
|
|
181
|
+
// behind them was slate, and why the outlined-field correction had silently
|
|
182
|
+
// never applied inside a dialog.
|
|
183
|
+
//
|
|
184
|
+
// Scope, stated plainly: `globalNodes` is document-wide, not per-owner —
|
|
185
|
+
// `createGlobalNode` stamps the class on every portal Quasar opens, the
|
|
186
|
+
// shell's own and any a contributed project page opens inside it. That is
|
|
187
|
+
// intended: a page mounted in the admin shell is admin UI, and the same
|
|
188
|
+
// theme already repaints its cards via `.sa-page`. What the class buys is
|
|
189
|
+
// that an app which never calls `createSuperAdminApp` stays untouched —
|
|
190
|
+
// which a bare `.q-dialog .q-card` rule could not promise.
|
|
191
|
+
const quasarOptions = options.quasarOptions ?? DEFAULT_QUASAR_OPTIONS;
|
|
192
|
+
// `globalNodes` is a documented Quasar config option that its TypeScript
|
|
193
|
+
// types do not declare (see quasar/src/utils/private.config/nodes.js, which
|
|
194
|
+
// reads `globalConfig.globalNodes.class`). The cast is the exception this
|
|
195
|
+
// codebase allows for an API that exists and is simply untyped upstream.
|
|
196
|
+
const config = quasarOptions.config as Record<string, unknown> | undefined;
|
|
197
|
+
const existingPortalClass = (config?.globalNodes as { class?: string } | undefined)?.class;
|
|
198
|
+
app.use(Quasar, {
|
|
199
|
+
...quasarOptions,
|
|
200
|
+
config: {
|
|
201
|
+
...quasarOptions.config,
|
|
202
|
+
globalNodes: {
|
|
203
|
+
class: [existingPortalClass, SA_PORTAL_CLASS].filter(Boolean).join(' '),
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
} as QuasarPluginOptions);
|
|
142
207
|
|
|
143
208
|
const pinia = createPinia();
|
|
144
209
|
app.use(pinia);
|
|
@@ -164,9 +229,34 @@ export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperA
|
|
|
164
229
|
};
|
|
165
230
|
|
|
166
231
|
const i18n = createSuperAdminI18n(options.i18n);
|
|
232
|
+
// An app that configured Quasar's dark mode has STATED a preference; the
|
|
233
|
+
// platform's default of 'system' has not. Seeding from it keeps the
|
|
234
|
+
// documented "Quasar decided" path working here — without it the bridge's
|
|
235
|
+
// first, immediate tick resolved 'system' against the machine and called
|
|
236
|
+
// `Dark.set()` with the answer, erasing the app's own choice one line after
|
|
237
|
+
// it was applied. In both directions: a configured `true` was undone on a
|
|
238
|
+
// light machine, a configured `false` on a dark one.
|
|
239
|
+
//
|
|
240
|
+
// Read from the OPTION rather than from `Dark.isActive`, because the option
|
|
241
|
+
// distinguishes three states and the resulting flag only two: `'auto'` means
|
|
242
|
+
// "follow the machine", which is what 'system' already does, and reading the
|
|
243
|
+
// flag would freeze whatever the machine happened to say at boot.
|
|
244
|
+
//
|
|
245
|
+
// A stored operator pick still outranks this — `createSaTheme` gives storage
|
|
246
|
+
// precedence over the `scheme` option — because that is a person choosing
|
|
247
|
+
// rather than a default.
|
|
248
|
+
const configuredDark = options.quasarOptions?.config?.dark;
|
|
249
|
+
const theme = createSaTheme({
|
|
250
|
+
...options.theme,
|
|
251
|
+
scheme:
|
|
252
|
+
options.theme?.scheme ??
|
|
253
|
+
(typeof configuredDark === 'boolean' ? (configuredDark ? 'dark' : 'light') : undefined),
|
|
254
|
+
});
|
|
255
|
+
const stopThemeBridge = bindSaThemeToDocument(theme);
|
|
167
256
|
|
|
168
257
|
app.provide(SUPER_ADMIN_BRAND_KEY, { tag: 'SuperAdmin', ...options.brand });
|
|
169
258
|
app.provide(SUPER_ADMIN_I18N_KEY, i18n);
|
|
259
|
+
app.provide(SA_THEME_KEY, theme);
|
|
170
260
|
app.provide(SUPER_ADMIN_ENDPOINTS_KEY, endpoints);
|
|
171
261
|
app.provide(SUPER_ADMIN_EXTENSIONS_KEY, options.extensions ?? {});
|
|
172
262
|
app.provide(SUPER_ADMIN_ACTIONS_KEY, options.actions ?? {});
|
|
@@ -191,6 +281,11 @@ export function createSuperAdminApp(options: CreateSuperAdminAppOptions): SuperA
|
|
|
191
281
|
router,
|
|
192
282
|
pinia,
|
|
193
283
|
i18n,
|
|
284
|
+
theme,
|
|
194
285
|
mount: (selector) => app.mount(selector),
|
|
286
|
+
dispose: () => {
|
|
287
|
+
stopThemeBridge();
|
|
288
|
+
theme.dispose();
|
|
289
|
+
},
|
|
195
290
|
};
|
|
196
291
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// The half of the theme switch that touches the DOM and Quasar.
|
|
2
|
+
//
|
|
3
|
+
// Two things have to agree, or the screen ends up half-dark:
|
|
4
|
+
//
|
|
5
|
+
// `data-sa-theme` on <html> drives the platform's own role tokens
|
|
6
|
+
// Quasar's `Dark.set()` drives q-card, q-table, q-menu, q-dialog …
|
|
7
|
+
//
|
|
8
|
+
// The stylesheet already accepts Quasar's `body--dark` as a dark trigger, so an
|
|
9
|
+
// app that only calls `$q.dark.set(true)` is complete without this module. What
|
|
10
|
+
// the bridge adds is the other direction — an operator switching the theme in
|
|
11
|
+
// the admin's own header — and, once both exist, keeping them from disagreeing:
|
|
12
|
+
// it watches BOTH and writes each into the other.
|
|
13
|
+
|
|
14
|
+
import { Dark } from 'quasar';
|
|
15
|
+
import { watch, type WatchStopHandle } from 'vue';
|
|
16
|
+
|
|
17
|
+
import type { SaTheme } from '../vue/use-sa-theme.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Mirrors a theme context onto the document and Quasar. Returns the stop
|
|
21
|
+
* handle; `createSuperAdminApp()` wires this up for you.
|
|
22
|
+
*
|
|
23
|
+
* `immediate` is not optional: at bootstrap the stored pick is already resolved
|
|
24
|
+
* and nothing has painted yet, so a first-change-only watcher would render one
|
|
25
|
+
* frame in the wrong theme on every reload.
|
|
26
|
+
*
|
|
27
|
+
* That first tick MIRRORS, it does not negotiate — whatever `theme.resolved`
|
|
28
|
+
* says is written to Quasar, including `Dark.set(false)`. So a caller that
|
|
29
|
+
* bootstraps with Quasar already dark has to say so when it builds the theme,
|
|
30
|
+
* or its own choice is erased one line after it was applied.
|
|
31
|
+
* `createSuperAdminApp` does exactly that, seeding from `Dark.isActive`.
|
|
32
|
+
*/
|
|
33
|
+
export function bindSaThemeToDocument(theme: SaTheme): WatchStopHandle {
|
|
34
|
+
const toQuasar = watch(
|
|
35
|
+
theme.resolved,
|
|
36
|
+
(resolved) => {
|
|
37
|
+
if (typeof document !== 'undefined') {
|
|
38
|
+
document.documentElement.setAttribute('data-sa-theme', resolved);
|
|
39
|
+
}
|
|
40
|
+
Dark.set(resolved === 'dark');
|
|
41
|
+
},
|
|
42
|
+
{ immediate: true },
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
// …and back. An app with its own toggle calls `$q.dark.set(false)`, which
|
|
46
|
+
// this side of the bridge never hears: the theme has not changed, so
|
|
47
|
+
// `data-sa-theme="dark"` stays on `<html>` while Quasar goes light. That is
|
|
48
|
+
// the half-dark screen the bridge exists to prevent, arriving from the
|
|
49
|
+
// other direction.
|
|
50
|
+
//
|
|
51
|
+
// No loop: the write-back only fires when Quasar and the theme actually
|
|
52
|
+
// disagree, and setting the scheme drives `resolved` to the value Quasar
|
|
53
|
+
// already has, so the outbound watcher's `Dark.set` is a no-op.
|
|
54
|
+
const fromQuasar = watch(
|
|
55
|
+
() => Dark.isActive,
|
|
56
|
+
(isDark) => {
|
|
57
|
+
const asScheme = isDark ? 'dark' : 'light';
|
|
58
|
+
if (theme.resolved.value !== asScheme) theme.scheme.value = asScheme;
|
|
59
|
+
},
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return () => {
|
|
63
|
+
toQuasar();
|
|
64
|
+
fromQuasar();
|
|
65
|
+
};
|
|
66
|
+
}
|
package/src/quasar/index.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Breakpoints — the one scale that cannot be a custom property.
|
|
2
|
+
//
|
|
3
|
+
// `@media (min-width: var(--sa-bp-md))` is invalid CSS: custom properties are
|
|
4
|
+
// not substituted inside a media condition. So the values live here for SCSS
|
|
5
|
+
// blocks, and plain-CSS `<style>` blocks use the five literals, which the lint
|
|
6
|
+
// rule allows and nothing else.
|
|
7
|
+
//
|
|
8
|
+
// They are Quasar's own breakpoints, deliberately. A component that changes
|
|
9
|
+
// layout at 980px inside an app whose grid changes at 1024px produces a band
|
|
10
|
+
// 44px wide where the two disagree — and the admin surface had six such
|
|
11
|
+
// values (540, 600, 980, 1100, 1180, 1280), so it had several such bands.
|
|
12
|
+
//
|
|
13
|
+
// Usage:
|
|
14
|
+
// @use '@saasicat/ui-vue/theme/breakpoints' as bp;
|
|
15
|
+
// @media (max-width: bp.$sm-max) { … }
|
|
16
|
+
|
|
17
|
+
$xs: 0;
|
|
18
|
+
$sm: 600px;
|
|
19
|
+
$md: 1024px;
|
|
20
|
+
$lg: 1440px;
|
|
21
|
+
$xl: 1920px;
|
|
22
|
+
|
|
23
|
+
// Upper bounds, for the `max-width` direction. Quasar uses the same 0.02px
|
|
24
|
+
// step back, so the two never both match at an integer viewport width.
|
|
25
|
+
$xs-max: 599.98px;
|
|
26
|
+
$sm-max: 1023.98px;
|
|
27
|
+
$md-max: 1439.98px;
|
|
28
|
+
$lg-max: 1919.98px;
|