@7365admin1/layer-common 3.2.7 → 3.2.8-staging.201
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/assets/css/primitives.css +1113 -0
- package/assets/css/screens.css +1097 -0
- package/assets/css/shell.css +258 -0
- package/assets/css/tokens.css +236 -0
- package/components/AcceptDialog.vue +17 -18
- package/components/AccessCard/AvailableStats.vue +24 -16
- package/components/AccessCardAddForm.vue +42 -42
- package/components/AccessCardAssignToUnitForm.vue +13 -21
- package/components/AccessCardDeleteDialog.vue +41 -39
- package/components/AccessCardDetailsDialog.vue +125 -79
- package/components/AccessCardPreviewDialog.vue +58 -84
- package/components/AccessCardQrTagging.vue +60 -63
- package/components/AccessCardReplaceForm.vue +27 -23
- package/components/AccessManagement.vue +86 -85
- package/components/AddEqupmentForm.vue +18 -22
- package/components/AddPassKeyToVisitor.vue +95 -73
- package/components/AppButton.vue +62 -0
- package/components/AppCard.vue +20 -0
- package/components/AppDateField.vue +49 -0
- package/components/AppField.vue +76 -0
- package/components/AppKpiTile.vue +65 -0
- package/components/AppSegmented.vue +38 -0
- package/components/AppSelect.vue +247 -0
- package/components/AreaChecklistHistoryLogs.vue +51 -44
- package/components/AreaChecklistHistoryMain.vue +14 -7
- package/components/AreaFormDialog.vue +71 -59
- package/components/AreaMain.vue +73 -107
- package/components/AttachmentsViewer.vue +84 -22
- package/components/AttendanceCheckInOutDialog.vue +85 -88
- package/components/AttendanceDetailsDialog.vue +57 -63
- package/components/AttendanceMain.vue +4 -3
- package/components/AttendanceMapSearchDialog.vue +42 -44
- package/components/AttendanceSettingsDialog.vue +79 -63
- package/components/Avatar/Main.vue +54 -2
- package/components/BillingMain.vue +26 -30
- package/components/BtnUploadFile.vue +53 -11
- package/components/BuildingForm.vue +34 -27
- package/components/BuildingManagement/buildings.vue +39 -39
- package/components/BuildingManagement/units.vue +26 -42
- package/components/BuildingUnitFormAdd.vue +20 -23
- package/components/BuildingUnitFormEdit.vue +8 -4
- package/components/BulletinBoardForm.vue +19 -24
- package/components/BulletinBoardManagement.vue +49 -57
- package/components/BulletinBoardView.vue +9 -6
- package/components/BulletinExpirationChip.vue +13 -4
- package/components/Button/Close.vue +2 -2
- package/components/CameraForm.vue +21 -22
- package/components/CameraMain.vue +50 -49
- package/components/Card/DeleteConfirmation.vue +24 -22
- package/components/Card/MemberInfoSummary.vue +28 -3
- package/components/Card/Toggle.vue +15 -3
- package/components/CardToolbar.vue +37 -0
- package/components/Carousel.vue +56 -36
- package/components/Chat/Bubbles.vue +43 -14
- package/components/Chat/Information.vue +49 -32
- package/components/Chat/ListCard.vue +38 -9
- package/components/Chat/Message.vue +41 -15
- package/components/Chat/Navigation.vue +27 -15
- package/components/Chat/SkeletonLoader.vue +9 -2
- package/components/ChecklistItemRow.vue +25 -1
- package/components/CleaningScheduleMain.vue +44 -49
- package/components/ClientDetailForm.vue +81 -56
- package/components/ClientMain.vue +92 -82
- package/components/ConfirmDialog.vue +14 -8
- package/components/Container/Standard.vue +9 -7
- package/components/CreateSubsciptionPlan.vue +148 -89
- package/components/DashboardEmptyState.vue +48 -3
- package/components/DashboardMain.vue +924 -676
- package/components/DashboardPanel.vue +84 -21
- package/components/DeliveryCompany.vue +34 -20
- package/components/Dialog/DeleteConfirmation.vue +20 -21
- package/components/Dialog/ReplaceAutofillPrompt.vue +19 -26
- package/components/Dialog/ReusablePrompt.vue +30 -29
- package/components/Dialog/UpdateMoreAction.vue +62 -59
- package/components/DocumentForm.vue +14 -21
- package/components/DocumentManagement.vue +162 -108
- package/components/DocumentViewer.vue +29 -12
- package/components/DrawImage.vue +29 -21
- package/components/EntryPass/QrTemplatePreview.vue +27 -16
- package/components/EntryPassInformation.vue +40 -10
- package/components/EntryPassMain.vue +49 -27
- package/components/EquipmentItemMain.vue +84 -97
- package/components/EquipmentManagementMain.vue +81 -103
- package/components/Facility/BookingSetup.vue +43 -52
- package/components/Feedback/Form.vue +26 -12
- package/components/FeedbackMain.vue +35 -49
- package/components/FileInput.vue +75 -58
- package/components/FileInputWithList.vue +59 -12
- package/components/FormDialog.vue +60 -21
- package/components/HidAccessLogDashboard.vue +512 -490
- package/components/HidCameraCaptureDialog.vue +216 -0
- package/components/HidEnabledGate.vue +67 -9
- package/components/HidIntercomManagement.vue +1322 -653
- package/components/HidQrCodeConfiguration.vue +1030 -258
- package/components/HidReaderForm.vue +142 -59
- package/components/HidReaderManagement.vue +211 -323
- package/components/HidServiceSettingsPanel.vue +44 -13
- package/components/HidUserEnrollment.vue +1185 -412
- package/components/HygieneUpdateMoreAction.vue +44 -44
- package/components/IncidentReport/Authorities.vue +30 -18
- package/components/IncidentReport/IncidentInformation.vue +10 -10
- package/components/IncidentReport/IncidentInformationDownload.vue +3 -4
- package/components/IncidentReport/affectedEntities.vue +41 -20
- package/components/Input/Date.vue +3 -0
- package/components/Input/DatePicker.vue +1 -2
- package/components/Input/DateTimePicker.vue +1 -1
- package/components/Input/File.vue +55 -10
- package/components/Input/FileV2.vue +39 -6
- package/components/Input/InputPhoneNumberV2.vue +2 -1
- package/components/Input/ListGroupSelection.vue +56 -8
- package/components/Input/NRICNumber.vue +1 -0
- package/components/Input/NewDate.vue +1 -0
- package/components/Input/Number.vue +1 -0
- package/components/Input/Password.vue +1 -0
- package/components/Input/PhoneNumber.vue +21 -1
- package/components/Input/VehicleNumber.vue +1 -0
- package/components/InputLabel.vue +2 -2
- package/components/InvitationClientForm.vue +21 -19
- package/components/InvitationForm.vue +15 -21
- package/components/InvitationMain.vue +46 -61
- package/components/Layout/Header.vue +263 -36
- package/components/Layout/NavigationDrawer.vue +300 -30
- package/components/ListItem.vue +33 -3
- package/components/LocalPagination.vue +27 -3
- package/components/ManageChecklistMain.vue +136 -86
- package/components/MemberInformation.vue +32 -16
- package/components/MemberMain.vue +113 -93
- package/components/MyAttendanceMain.vue +3 -5
- package/components/NavigationItem.vue +83 -3
- package/components/Nfc/NFCPatrolReportMain.vue +47 -33
- package/components/Nfc/NFCPatrolRouteForm.vue +18 -20
- package/components/Nfc/NFCPatrolRouteMain.vue +116 -103
- package/components/Nfc/NFCPatrolSettings.vue +14 -4
- package/components/Nfc/NFCTagForm.vue +22 -8
- package/components/Nfc/NFCTagMain.vue +41 -43
- package/components/Nfc/PatrolReport/DailyReportTab.vue +8 -1
- package/components/Nfc/PatrolReport/MonthlyReportTab.vue +8 -1
- package/components/Nfc/PatrolReport/MonthlyReportTable.vue +5 -3
- package/components/Nfc/PatrolReport/PatrolActivityTable.vue +26 -6
- package/components/Nfc/PatrolReport/PatrolReportTab.vue +8 -0
- package/components/Nfc/PatrolReport/PatrolRouteSummary.vue +41 -9
- package/components/Nfc/PatrolReport/RemarksDialog.vue +39 -9
- package/components/Nfc/PatrolReport/ReportEmptyState.vue +29 -9
- package/components/Nfc/PatrolReport/ReportFilters.vue +19 -10
- package/components/Nfc/PatrolReport/ReportLocationHeader.vue +32 -6
- package/components/Nfc/PatrolReport/SummaryReportTable.vue +28 -6
- package/components/NotificationSettings.vue +373 -0
- package/components/NumberSettingField.vue +9 -2
- package/components/OnlineFormConfigurationForm.vue +725 -237
- package/components/OnlineFormFill.vue +213 -106
- package/components/OnlineFormRenderPreview.vue +25 -30
- package/components/OnlineFormsConfiguration.vue +113 -109
- package/components/OrgViewDialog.vue +34 -16
- package/components/OvernightParkingAvailability.vue +49 -26
- package/components/OvernightParkingManagement.vue +78 -18
- package/components/PageHeader.vue +43 -0
- package/components/PassInformation.vue +109 -53
- package/components/PasswordConfirmation.vue +44 -26
- package/components/PeopleForm.vue +41 -20
- package/components/PhotoUpload.vue +110 -48
- package/components/PlaceholderComponent.vue +35 -19
- package/components/PlanActionsMenu.vue +12 -2
- package/components/PlanStatusBadge.vue +7 -8
- package/components/PlateNumberDisplay.vue +9 -6
- package/components/QrTemplate/PrintDialog.vue +66 -43
- package/components/RolePermissionFormCreate.vue +104 -79
- package/components/RolePermissionFormPreviewUpdate.vue +111 -109
- package/components/RolePermissionMain.vue +183 -81
- package/components/ScanVisitorQRCode.vue +99 -35
- package/components/ScheduleAreaMain.vue +34 -59
- package/components/ScheduleTaskForm.vue +61 -42
- package/components/ScheduleTaskMain.vue +56 -69
- package/components/SearchVehicleNumberUser.vue +39 -37
- package/components/ServiceProviderFormCreate.vue +29 -20
- package/components/ServiceProviderInvitationPrompt.vue +155 -0
- package/components/ServiceProviderMain.vue +601 -432
- package/components/Signature.vue +40 -34
- package/components/SignaturePad.vue +5 -29
- package/components/SiteSettings.vue +41 -11
- package/components/SiteSettingsWorkOrder.vue +61 -30
- package/components/SlideCardGroup.vue +35 -11
- package/components/Snackbar.vue +36 -3
- package/components/SpecificAttr.vue +18 -8
- package/components/StatusChip.vue +156 -0
- package/components/StockCard.vue +17 -12
- package/components/SubscriptionPlanTable.vue +207 -140
- package/components/SwitchContext.vue +201 -9
- package/components/TableHygiene.vue +36 -46
- package/components/TableList.vue +10 -10
- package/components/TableListSecondary.vue +105 -54
- package/components/TableMain.vue +251 -78
- package/components/TableV3.vue +61 -70
- package/components/TiptapEditor.vue +33 -40
- package/components/TiptapEditorQr.vue +5 -2
- package/components/Tooltip/Info.vue +69 -24
- package/components/UnitMain.vue +40 -60
- package/components/UnitPersonCard.vue +42 -13
- package/components/VehicleAddSelection.vue +48 -32
- package/components/VehicleForm.vue +112 -129
- package/components/VehicleManagement.vue +26 -12
- package/components/VehicleUpdateMoreAction.vue +59 -46
- package/components/VisitorDataFromScannedQRCodeForm.vue +90 -65
- package/components/VisitorForm.vue +774 -210
- package/components/VisitorFormSelection.vue +40 -37
- package/components/VisitorManagement.vue +239 -82
- package/components/VisitorPassKeyQRScanner.vue +65 -18
- package/components/VisitorSearchResultSummary.vue +60 -16
- package/components/VisitorSocketPopUp.vue +22 -14
- package/components/VisitorsReportPreview.vue +44 -29
- package/components/WorkOrder/Create.vue +26 -17
- package/components/WorkOrder/ListView.vue +38 -7
- package/components/WorkOrder/Main.vue +62 -59
- package/composables/useFeedback.ts +3 -3
- package/composables/useHidAmico.ts +269 -0
- package/composables/useHidNavigation.ts +2 -9
- package/composables/useMember.ts +3 -3
- package/composables/useNFCPatrolDailyReport.ts +13 -0
- package/composables/useNFCPatrolMonthlyReport.ts +22 -56
- package/composables/useNFCPatrolReport.ts +13 -0
- package/composables/useNotificationPreference.ts +87 -0
- package/composables/useOnlineFormPages.ts +2 -0
- package/composables/useRole.ts +22 -0
- package/composables/useServiceProvider.ts +3 -3
- package/composables/useServiceProviderInvitation.ts +145 -0
- package/composables/useSipWebPhone.ts +336 -0
- package/composables/useSubscriptionPlan.ts +62 -0
- package/composables/useThemePreference.ts +63 -0
- package/composables/useVisitor.ts +6 -1
- package/composables/useWebUsb.ts +127 -37
- package/composables/useWorkOrder.ts +17 -3
- package/error.vue +30 -6
- package/nuxt.config.ts +36 -0
- package/package.json +25 -4
- package/pages/[org]/[site]/access-mgmt/administrator/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/{identity-mapping → hid-cards}/index.vue +2 -2
- package/pages/[org]/[site]/access-mgmt/intercom/index.vue +1 -1
- package/pages/notification-settings.vue +19 -0
- package/pages/payment-method-linked.vue +34 -10
- package/pages/require-customer.vue +48 -10
- package/pages/require-organization-membership.vue +32 -8
- package/pages/unauthorized.vue +29 -4
- package/plugins/secure-member.client.ts +21 -56
- package/plugins/vuetify.ts +69 -2
- package/types/nfc-patrol-report.ts +3 -2
- package/types/site.d.ts +71 -0
- package/types/subscription.ts +18 -11
- package/utils/breadcrumb.test.ts +108 -0
- package/utils/breadcrumb.ts +82 -0
- package/utils/dashboard.test.ts +118 -0
- package/utils/dashboard.ts +53 -0
- package/utils/data.test.ts +95 -0
- package/utils/data.ts +40 -2
- package/utils/hid-discover.test.ts +102 -0
- package/utils/list-page.test.ts +60 -0
- package/utils/list-page.ts +38 -0
- package/utils/materialSymbols.ts +363 -0
- package/utils/nav-sections.test.ts +190 -0
- package/utils/nav-sections.ts +151 -0
- package/utils/nfc-patrol-report.test.ts +130 -0
- package/utils/nfc-patrol-report.ts +101 -0
- package/utils/role.test.ts +69 -0
- package/utils/role.ts +10 -0
- package/utils/route-name.test.ts +85 -0
- package/utils/route-name.ts +24 -0
- package/utils/status.test.ts +110 -0
- package/utils/status.ts +135 -0
- package/utils/table-card.test.ts +84 -0
- package/utils/theme-aa-ledger.ts +148 -0
- package/utils/theme.test.ts +485 -7
- package/utils/theme.ts +295 -110
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -11
- package/.editorconfig +0 -12
- package/.github/workflows/main.yml +0 -17
- package/.github/workflows/publish-staging.yml +0 -47
- package/.github/workflows/publish.yml +0 -39
- package/PUBLISHING.md +0 -269
- package/components/HidIdentityMapping.vue +0 -975
- package/components/ScheduleTaskAreaFormDialog.vue +0 -144
- package/components/ScheduleTaskAreaUpdateMoreAction.vue +0 -109
- package/components/ScheduleTastTicketMain.vue +0 -182
- package/components/TableWithButton.vue +0 -94
- package/test/visitor-socket.test.mjs +0 -36
package/utils/status.ts
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE SEMANTIC STATUS MAPPING, IN ONE PLACE.
|
|
3
|
+
*
|
|
4
|
+
* The design assigns a colour to a status WORD rather than to a module, so the
|
|
5
|
+
* same word has to look the same wherever it appears - "Open" on a Daily
|
|
6
|
+
* Occurrence Book and "Open" on a work order are the same green. Keeping the
|
|
7
|
+
* list here rather than in each screen is what makes that true, and is what
|
|
8
|
+
* Phases 3 and 4 read when they restyle the chips.
|
|
9
|
+
*
|
|
10
|
+
* Straight from the handoff:
|
|
11
|
+
* Completed / Paid / Available / Open -> ok
|
|
12
|
+
* Pending -> warn
|
|
13
|
+
* In Use / Closed / Checkout -> err
|
|
14
|
+
* Role and info tags -> info
|
|
15
|
+
*
|
|
16
|
+
* Anything not listed is `neutral` on purpose. A status this product has and
|
|
17
|
+
* the design did not name must NOT be guessed into a colour - a wrong green on
|
|
18
|
+
* a failed job is worse than a grey one - so it comes out neutral and visible,
|
|
19
|
+
* which is how it gets noticed and added.
|
|
20
|
+
*/
|
|
21
|
+
export type TStatusTone = "ok" | "warn" | "err" | "info" | "neutral";
|
|
22
|
+
|
|
23
|
+
const TONES: Record<string, TStatusTone> = {
|
|
24
|
+
completed: "ok",
|
|
25
|
+
paid: "ok",
|
|
26
|
+
available: "ok",
|
|
27
|
+
open: "ok",
|
|
28
|
+
|
|
29
|
+
pending: "warn",
|
|
30
|
+
|
|
31
|
+
"in use": "err",
|
|
32
|
+
closed: "err",
|
|
33
|
+
checkout: "err",
|
|
34
|
+
|
|
35
|
+
role: "info",
|
|
36
|
+
info: "info",
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* PHASE 4 - THE WORDS THIS PRODUCT ACTUALLY USES.
|
|
40
|
+
*
|
|
41
|
+
* The handoff names ten words; the module screens show more than that, and
|
|
42
|
+
* each of them is ALREADY painted a colour today by a per-screen
|
|
43
|
+
* `getStatusColor`. So these are not new opinions - each one is the tone the
|
|
44
|
+
* screens already give that word, moved into the one list so that "Ongoing"
|
|
45
|
+
* is the same blue on a cleaning schedule and on a scheduled area. Where the
|
|
46
|
+
* old per-screen colour and the design disagree the design wins, and there
|
|
47
|
+
* is exactly one of those: `Open` was grey and the handoff names it `ok`.
|
|
48
|
+
*
|
|
49
|
+
* Anything still unlisted stays neutral, deliberately - see above.
|
|
50
|
+
*/
|
|
51
|
+
ongoing: "info", // was `primary`
|
|
52
|
+
"in progress": "warn", // was #FB8C00, orange
|
|
53
|
+
"for review": "info", // was `primary`
|
|
54
|
+
"to do": "warn", // was `orange`
|
|
55
|
+
|
|
56
|
+
accepted: "ok", // was `success`
|
|
57
|
+
approved: "ok",
|
|
58
|
+
resolved: "ok", // was #4CAF50, green
|
|
59
|
+
active: "ok", // was `success`
|
|
60
|
+
|
|
61
|
+
// An online-form submission sent back to the resident to correct and send
|
|
62
|
+
// again. Unlisted it would come out neutral, which would make "needs your
|
|
63
|
+
// attention" look the same as "nothing is happening".
|
|
64
|
+
resubmission: "warn",
|
|
65
|
+
|
|
66
|
+
assigned: "info", // was `primary`, on the access-card details dialog
|
|
67
|
+
replaced: "warn", // was `orange`, same screen
|
|
68
|
+
returned: "warn", // was `orange`, same screen - the card came back
|
|
69
|
+
|
|
70
|
+
rejected: "err", // was `error`
|
|
71
|
+
deleted: "err", // was `error`
|
|
72
|
+
suspended: "err", // was `error`
|
|
73
|
+
|
|
74
|
+
complete: "ok",
|
|
75
|
+
|
|
76
|
+
inactive: "neutral", // was `grey`
|
|
77
|
+
"in active": "neutral", // the same word, printed "In-Active"
|
|
78
|
+
declined: "neutral", // was Vuetify's `default`
|
|
79
|
+
expired: "neutral", // was `grey`
|
|
80
|
+
|
|
81
|
+
"awaiting approval": "warn", // a waiting state, like Pending
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* HID enrolment: is this reader identity tied to a real person record yet?
|
|
85
|
+
* Same rule as the Phase 4 block above - not a new opinion, but the tone
|
|
86
|
+
* `HidUserEnrollment` already painted these two words, moved into the one
|
|
87
|
+
* list. Left unlisted they would BOTH come out neutral, which would erase
|
|
88
|
+
* the only distinction the column exists to draw.
|
|
89
|
+
*/
|
|
90
|
+
mapped: "ok", // was `status-success`, #1b7f3a on #dff7e7
|
|
91
|
+
unmapped: "warn", // was `status-warning`, #9a6700 on #fff0cc
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* HID intercom SIP state, from `HidIntercomManagement`'s own
|
|
95
|
+
* `getStatusClass`. Same rule again: the tone the screen already gave the
|
|
96
|
+
* word, moved into the one list. "Disabled" and "Not connected" are NOT
|
|
97
|
+
* failures - the operator turned SIP off, or never set it up - so they stay
|
|
98
|
+
* neutral, which is what `status-muted` painted them.
|
|
99
|
+
*/
|
|
100
|
+
/**
|
|
101
|
+
* HID access authorization. These arrive with `b0667a7` on `staging`
|
|
102
|
+
* ("Call the Authorized status API from Amico via Access Logs"), which
|
|
103
|
+
* replaces the Access Logs' Mapped/Unmapped column with these three words.
|
|
104
|
+
* Listed here AHEAD of that merge so the shared chip already knows them -
|
|
105
|
+
* unlisted they would all come out neutral, silently erasing the difference
|
|
106
|
+
* between a granted and a denied door. Tones are that commit's own:
|
|
107
|
+
* `mapped-chip` green, `unmapped-chip` red, `unknown-chip` amber.
|
|
108
|
+
*/
|
|
109
|
+
authorized: "ok",
|
|
110
|
+
"not authorized": "err",
|
|
111
|
+
unknown: "warn",
|
|
112
|
+
|
|
113
|
+
connected: "ok", // was `status-success`
|
|
114
|
+
connecting: "warn", // was `status-warning`, the fall-through case
|
|
115
|
+
"not connected": "neutral", // was `status-muted`
|
|
116
|
+
disabled: "neutral", // was `status-muted`
|
|
117
|
+
failed: "err", // was `status-error`
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* The tone for a status word. Case- and space-insensitive, because the same
|
|
122
|
+
* status arrives as "In Use", "in_use" and "IN USE" from different endpoints.
|
|
123
|
+
*/
|
|
124
|
+
export function statusTone(status: string | null | undefined): TStatusTone {
|
|
125
|
+
if (!status) return "neutral";
|
|
126
|
+
|
|
127
|
+
const key = String(status).trim().toLowerCase().replace(/[_-]+/g, " ");
|
|
128
|
+
|
|
129
|
+
return TONES[key] ?? "neutral";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** The class `assets/css/tokens.css` paints that tone with. */
|
|
133
|
+
export function statusToneClass(status: string | null | undefined): string {
|
|
134
|
+
return `tone-${statusTone(status)}`;
|
|
135
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REGRESSION GUARDS FOR THE TABLE CARD'S BUSY SIGNAL.
|
|
3
|
+
*
|
|
4
|
+
* Both faults here are a CSS declaration and a template binding, not a pure
|
|
5
|
+
* function, and this repo has no component test runner (adding one is a
|
|
6
|
+
* dependency change). So these assert the SOURCE CONTRACT: the exact
|
|
7
|
+
* declarations whose absence caused the defect. They fail the moment someone
|
|
8
|
+
* deletes the anchor or the disabled binding again.
|
|
9
|
+
*
|
|
10
|
+
* The behavioural proof is the driven browser measurement recorded on the PR -
|
|
11
|
+
* bar 1440x2 at `top: 0` of the WINDOW before, 1406x2 at the card's own top
|
|
12
|
+
* edge after; refresh button `false -> false -> false` before,
|
|
13
|
+
* `false -> true -> false` after.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, it } from "node:test";
|
|
16
|
+
import assert from "node:assert/strict";
|
|
17
|
+
import { readFileSync } from "node:fs";
|
|
18
|
+
import { fileURLToPath } from "node:url";
|
|
19
|
+
|
|
20
|
+
const read = (rel: string) =>
|
|
21
|
+
readFileSync(fileURLToPath(new URL(`../${rel}`, import.meta.url)), "utf8");
|
|
22
|
+
|
|
23
|
+
/** The declaration block for a selector, comments stripped. */
|
|
24
|
+
const ruleFor = (css: string, selector: string) => {
|
|
25
|
+
const at = css.indexOf(`\n${selector} {`);
|
|
26
|
+
assert.notEqual(at, -1, `no rule for "${selector}"`);
|
|
27
|
+
const open = css.indexOf("{", at);
|
|
28
|
+
const close = css.indexOf("}", open);
|
|
29
|
+
return css
|
|
30
|
+
.slice(open + 1, close)
|
|
31
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
32
|
+
.trim();
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
describe("the loading bar's containing block", () => {
|
|
36
|
+
const screens = read("assets/css/screens.css");
|
|
37
|
+
|
|
38
|
+
it("anchors .table-card__loading to its card, not the viewport", () => {
|
|
39
|
+
// The bar is absolute. Without this the nearest positioned ancestor is
|
|
40
|
+
// the initial containing block, i.e. the browser window: the bar was
|
|
41
|
+
// measured 1440x2 at top:0 while its card sat 61.5px lower.
|
|
42
|
+
assert.match(ruleFor(screens, ".table-card"), /position:\s*relative/);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it("still positions the bar out of flow, so a load causes no 2px jump", () => {
|
|
46
|
+
assert.match(ruleFor(screens, ".table-card__loading"), /position:\s*absolute/);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
describe("TableMain's refresh button", () => {
|
|
51
|
+
const sfc = read("components/TableMain.vue");
|
|
52
|
+
// The one `v-btn` carrying the refresh emit.
|
|
53
|
+
const refreshBtn = (() => {
|
|
54
|
+
const at = sfc.indexOf(`@click="emits('refresh')"`);
|
|
55
|
+
assert.notEqual(at, -1, "no refresh button in TableMain");
|
|
56
|
+
const open = sfc.lastIndexOf("<v-btn", at);
|
|
57
|
+
return sfc.slice(open, at);
|
|
58
|
+
})();
|
|
59
|
+
|
|
60
|
+
it("is disabled while the caller's request is in flight", () => {
|
|
61
|
+
// Without this, clicking refresh on a list that comes back identical
|
|
62
|
+
// changed zero pixels - the Work Orders / Duty Officer Book report.
|
|
63
|
+
assert.match(refreshBtn, /:disabled="loading"/);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("reads the same `loading` prop the bar reads, so the two cannot diverge", () => {
|
|
67
|
+
assert.match(sfc, /v-if="loading"[\s\S]{0,200}table-card__loading/);
|
|
68
|
+
assert.match(sfc, /loading:\s*\{\s*type:\s*Boolean,\s*default:\s*false,?\s*\}/);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe(".app-switch's thumb", () => {
|
|
73
|
+
const primitives = read("assets/css/primitives.css");
|
|
74
|
+
|
|
75
|
+
it("beats Vuetify's 3- and 4-class slide on the input", () => {
|
|
76
|
+
// `.v-locale--is-ltr .v-switch .v-selection-control__input` and its
|
|
77
|
+
// `--dirty` twin outrank a 2-class rule, so the plain reset never
|
|
78
|
+
// applied and every thumb sat 10px out of its track.
|
|
79
|
+
assert.match(
|
|
80
|
+
ruleFor(primitives, ".app-switch .v-selection-control__input"),
|
|
81
|
+
/transform:\s*none\s*!important/,
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE LIGHT THEME'S KNOWN AA FAILURES, MEASURED.
|
|
3
|
+
*
|
|
4
|
+
* The owner's 2026-08-14 decision is 100% design fidelity over WCAG AA, so the
|
|
5
|
+
* six light values that had been darkened are back on the handoff and the light
|
|
6
|
+
* theme no longer clears 4.5:1 everywhere. This file is the ledger of exactly
|
|
7
|
+
* what that costs: every pair, its measured ratio, and what it needs.
|
|
8
|
+
*
|
|
9
|
+
* It is data, not prose - `theme.test.ts` asserts each entry still measures what
|
|
10
|
+
* it says, so a later change that drifts a colour (in either direction) fails
|
|
11
|
+
* the build and has to come back here and say so.
|
|
12
|
+
*
|
|
13
|
+
* `run` prints the table: `node --experimental-strip-types utils/theme-aa-ledger.ts`
|
|
14
|
+
*/
|
|
15
|
+
import { PALETTE, LIGHT_THEME, DARK_THEME } from "./theme.ts";
|
|
16
|
+
|
|
17
|
+
const luminance = (hex: string): number => {
|
|
18
|
+
const c = [0, 2, 4]
|
|
19
|
+
.map((i) => parseInt(hex.slice(1 + i, 3 + i), 16) / 255)
|
|
20
|
+
.map((v) => (v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)));
|
|
21
|
+
return 0.2126 * c[0] + 0.7152 * c[1] + 0.0722 * c[2];
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const contrast = (a: string, b: string): number => {
|
|
25
|
+
const [x, y] = [luminance(a), luminance(b)];
|
|
26
|
+
return (Math.max(x, y) + 0.05) / (Math.min(x, y) + 0.05);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const over = (fg: string, bg: string, alpha: number): string =>
|
|
30
|
+
"#" +
|
|
31
|
+
[0, 2, 4]
|
|
32
|
+
.map((i) =>
|
|
33
|
+
Math.round(
|
|
34
|
+
parseInt(fg.slice(1 + i, 3 + i), 16) * alpha +
|
|
35
|
+
parseInt(bg.slice(1 + i, 3 + i), 16) * (1 - alpha),
|
|
36
|
+
)
|
|
37
|
+
.toString(16)
|
|
38
|
+
.padStart(2, "0"),
|
|
39
|
+
)
|
|
40
|
+
.join("");
|
|
41
|
+
|
|
42
|
+
const L = PALETTE.light;
|
|
43
|
+
|
|
44
|
+
/** `--thead` is a 2% ink wash over the card, not a colour of its own. */
|
|
45
|
+
export const thead = over("#14161a", L.card, 0.02);
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* `--hover` is a 5% ink wash. On the page background it is the fill a DISABLED
|
|
49
|
+
* control sits on (the HID reader identification methods, greyed until Online
|
|
50
|
+
* Mode is on), so `--muted` label on `--hover` is a pair a person actually reads.
|
|
51
|
+
*/
|
|
52
|
+
export const hoverOnBg = over("#14161a", L.bg, 0.05);
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
/** The soft chip backgrounds, exactly as the design states them. */
|
|
56
|
+
export const chip = {
|
|
57
|
+
ok: over("#0f8a62", L.card, 0.1),
|
|
58
|
+
warn: over("#be8c19", L.card, 0.13),
|
|
59
|
+
err: over("#cf4b4b", L.card, 0.1),
|
|
60
|
+
info: over("#3b6fd4", L.card, 0.1),
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
/** `--accent-soft` is transparent, so it takes the colour of what is behind it. */
|
|
64
|
+
export const accentSoft = {
|
|
65
|
+
sidebar: over(L.accent, L.sidebar, 0.12),
|
|
66
|
+
card: over(L.accent, L.card, 0.12),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Every LIGHT pair a person actually reads. `need` is 4.5 for body text and 3
|
|
71
|
+
* for large text (>=18.66px, or >=14px at 700+) and for non-text edges.
|
|
72
|
+
*/
|
|
73
|
+
export const LIGHT_PAIRS: Array<{
|
|
74
|
+
what: string;
|
|
75
|
+
fg: string;
|
|
76
|
+
bg: string;
|
|
77
|
+
need: number;
|
|
78
|
+
}> = [
|
|
79
|
+
{ what: "muted on a card (KPI sub-lines, field labels)", fg: L.muted, bg: L.card, need: 4.5 },
|
|
80
|
+
{ what: "muted on the page background", fg: L.muted, bg: L.bg, need: 4.5 },
|
|
81
|
+
/*
|
|
82
|
+
* `muted on a table header band` used to sit here at 3.10:1. `.table-card
|
|
83
|
+
* thead th` now takes `--text2` (9.22:1 light / 9.57:1 dark), and nothing
|
|
84
|
+
* else paints `--muted` on a `--thead` band - `.table-card__group`'s label
|
|
85
|
+
* is Vuetify's `text-medium-emphasis`. The pair no longer occurs, so it is
|
|
86
|
+
* no longer carried as accepted debt.
|
|
87
|
+
*/
|
|
88
|
+
{ what: "muted on the sidebar", fg: L.muted, bg: L.sidebar, need: 4.5 },
|
|
89
|
+
{ what: "muted on a disabled control's hover fill", fg: L.muted, bg: hoverOnBg, need: 4.5 },
|
|
90
|
+
{ what: "ok label on its own chip", fg: L.ok, bg: chip.ok, need: 4.5 },
|
|
91
|
+
{ what: "warn label on its own chip", fg: L.warn, bg: chip.warn, need: 4.5 },
|
|
92
|
+
{ what: "err label on its own chip", fg: L.err, bg: chip.err, need: 4.5 },
|
|
93
|
+
{ what: "info label on its own chip", fg: L.info, bg: chip.info, need: 4.5 },
|
|
94
|
+
{ what: "ok as plain text on a card", fg: L.ok, bg: L.card, need: 4.5 },
|
|
95
|
+
{ what: "warn as plain text on a card", fg: L.warn, bg: L.card, need: 4.5 },
|
|
96
|
+
{ what: "err as plain text on a card (the Logout row)", fg: L.err, bg: L.card, need: 4.5 },
|
|
97
|
+
{ what: "info as plain text on a card", fg: L.info, bg: L.card, need: 4.5 },
|
|
98
|
+
{ what: "accent-text as a link on a card", fg: L.accentText, bg: L.card, need: 4.5 },
|
|
99
|
+
{ what: "accent-text on accent-soft over the sidebar (active nav item)", fg: L.accentText, bg: accentSoft.sidebar, need: 4.5 },
|
|
100
|
+
{ what: "accent-text on accent-soft over a card (active tab, avatar initials)", fg: L.accentText, bg: accentSoft.card, need: 4.5 },
|
|
101
|
+
{ what: "white on the filled success colour", fg: "#ffffff", bg: L.ok, need: 4.5 },
|
|
102
|
+
{ what: "white on the filled warning colour", fg: "#ffffff", bg: L.warn, need: 4.5 },
|
|
103
|
+
{ what: "white on the filled error colour", fg: "#ffffff", bg: L.err, need: 4.5 },
|
|
104
|
+
{ what: "white on the filled info colour", fg: "#ffffff", bg: L.info, need: 4.5 },
|
|
105
|
+
{ what: "white on primary-button (accent-strong) - NOT reverted", fg: "#ffffff", bg: L.accentStrong, need: 4.5 },
|
|
106
|
+
{ what: "text on a card", fg: L.text, bg: L.card, need: 4.5 },
|
|
107
|
+
{ what: "text2 on a card", fg: L.text2, bg: L.card, need: 4.5 },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
export const measure = () =>
|
|
111
|
+
LIGHT_PAIRS.map((p) => ({
|
|
112
|
+
...p,
|
|
113
|
+
ratio: Math.round(contrast(p.fg, p.bg) * 100) / 100,
|
|
114
|
+
passes: contrast(p.fg, p.bg) >= p.need,
|
|
115
|
+
}));
|
|
116
|
+
|
|
117
|
+
/** The dark theme was exact as handed over and clears AA - this proves it still does. */
|
|
118
|
+
export const DARK_PAIRS = () => {
|
|
119
|
+
const D = PALETTE.dark;
|
|
120
|
+
const dthead = over("#ffffff", D.card, 0.02);
|
|
121
|
+
return [
|
|
122
|
+
{ what: "muted on a card", fg: D.muted, bg: D.card, need: 4.5 },
|
|
123
|
+
{ what: "muted on the page background", fg: D.muted, bg: D.bg, need: 4.5 },
|
|
124
|
+
{ what: "muted on a --thead band (the tint itself, no longer the header text)", fg: D.muted, bg: dthead, need: 4.5 },
|
|
125
|
+
{ what: "muted on the sidebar", fg: D.muted, bg: D.sidebar, need: 4.5 },
|
|
126
|
+
{ what: "text on a card", fg: D.text, bg: D.card, need: 4.5 },
|
|
127
|
+
{ what: "text2 on a card", fg: D.text2, bg: D.card, need: 4.5 },
|
|
128
|
+
{ what: "ok on its chip", fg: D.ok, bg: over("#41c795", D.card, 0.12), need: 4.5 },
|
|
129
|
+
{ what: "warn on its chip", fg: D.warn, bg: over("#dcaf4e", D.card, 0.13), need: 4.5 },
|
|
130
|
+
{ what: "err on its chip", fg: D.err, bg: over("#e57373", D.card, 0.12), need: 4.5 },
|
|
131
|
+
{ what: "info on its chip", fg: D.info, bg: over("#7da6ec", D.card, 0.12), need: 4.5 },
|
|
132
|
+
{ what: "accent-text on accent-soft over the sidebar", fg: D.accentText, bg: over(D.accent, D.sidebar, 0.12), need: 4.5 },
|
|
133
|
+
{ what: "accent-text on accent-soft over a card", fg: D.accentText, bg: over(D.accent, D.card, 0.12), need: 4.5 },
|
|
134
|
+
].map((p) => ({ ...p, ratio: Math.round(contrast(p.fg, p.bg) * 100) / 100, passes: contrast(p.fg, p.bg) >= p.need }));
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
if (process.argv[1]?.endsWith("theme-aa-ledger.ts")) {
|
|
138
|
+
const row = (r: { what: string; fg: string; bg: string; ratio: number; need: number; passes: boolean }) =>
|
|
139
|
+
`${r.passes ? "PASS" : "FAIL"} ${String(r.ratio).padStart(5)}:1 / ${r.need} ${r.fg} on ${r.bg} ${r.what}`;
|
|
140
|
+
console.log("LIGHT");
|
|
141
|
+
for (const r of measure()) console.log(" " + row(r));
|
|
142
|
+
console.log("\nDARK");
|
|
143
|
+
for (const r of DARK_PAIRS()) console.log(" " + row(r));
|
|
144
|
+
const f = measure().filter((r) => !r.passes).length;
|
|
145
|
+
console.log(`\n${f} of ${LIGHT_PAIRS.length} light pairs below AA; ${DARK_PAIRS().filter((r) => !r.passes).length} dark.`);
|
|
146
|
+
void LIGHT_THEME;
|
|
147
|
+
void DARK_THEME;
|
|
148
|
+
}
|