@cyberskill/design 1.1.1 → 1.2.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/DESIGN.md +25 -3
- package/README.md +3 -3
- package/SKILL.md +3 -3
- package/VERSION +1 -1
- package/_ds_bundle.js +819 -416
- package/_ds_manifest.json +1 -1
- package/_esm/cs.mjs +18 -4
- package/_esm/react.d.ts +2 -0
- package/_esm/react.mjs +1 -0
- package/base/a11y.css +18 -0
- package/base/ai.css +2 -2
- package/base/collections.css +8 -3
- package/base/components.css +11 -2
- package/base/controls.css +61 -48
- package/base/data.css +3 -3
- package/base/feedback.css +5 -0
- package/base/forms.css +9 -3
- package/base/glass.css +6 -0
- package/base/navigation.css +8 -2
- package/base/reset.css +1 -1
- package/base/responsive.css +25 -0
- package/base/shell.css +7 -1
- package/components/_i18n/i18n.js +13 -1
- package/components/_i18n/strings.js +52 -3
- package/components/ai/ConfidenceMeter.jsx +8 -6
- package/components/data/Watermark.jsx +11 -3
- package/components/datatable/DataGrid.jsx +10 -1
- package/components/dialog/Dialog.jsx +13 -45
- package/components/feedback/ProgressBar.jsx +4 -2
- package/components/forms/Cascader.jsx +69 -6
- package/components/forms/Editor.jsx +37 -2
- package/components/forms/FileUpload.jsx +22 -8
- package/components/forms/Switch.jsx +16 -2
- package/components/forms/TimePicker.jsx +3 -1
- package/components/icon/Icon.jsx +1 -0
- package/components/navigation/CommandPalette.jsx +22 -7
- package/components/navigation/Toolbar.jsx +2 -1
- package/components/overlays/AlertDialog.jsx +19 -49
- package/components/overlays/Drawer.jsx +24 -20
- package/components/overlays/OverlayManager.d.ts +40 -0
- package/components/overlays/OverlayManager.jsx +214 -0
- package/components/overlays/OverlayManager.prompt.md +12 -0
- package/components/overlays/Tour.jsx +14 -7
- package/dist/styles.min.css +2004 -0
- package/docs/benchmark-rubric.md +1 -1
- package/docs/ci-cd.md +3 -1
- package/docs/consuming.md +6 -6
- package/docs/conventions.md +4 -4
- package/docs/decisions/container-query-migration.md +18 -0
- package/docs/decisions/overlay-manager-design.md +101 -0
- package/docs/decisions.md +5 -5
- package/docs/design-styles.md +1 -1
- package/docs/plans/cyberskill-design-system-audit-and-12-month-evolution-plan.md +1490 -0
- package/docs/quality-gates.md +20 -8
- package/docs/release-notes.md +2 -2
- package/docs/status/data/status-feed.json +1 -1
- package/docs/status/index.html +9 -5
- package/docs/status/status-legacy.html +12 -7
- package/docs/sync.md +1 -1
- package/docs/tasks/_state/commit-links.yaml +14 -0
- package/docs/vi/benchmark-rubric.md +1 -1
- package/docs/vi/ci-cd.md +3 -1
- package/docs/vi/consuming.md +6 -6
- package/docs/vi/conventions.md +2 -2
- package/docs/vi/decisions.md +4 -4
- package/docs/vi/quality-gates.md +21 -9
- package/docs/vi/release-notes.md +2 -2
- package/docs/vi/sync.md +1 -1
- package/guidelines/atomic-view.html +11 -7
- package/llms.txt +2 -2
- package/package.json +14 -6
- package/templates/_ds-base.canonical.js +55 -0
- package/templates/ai-cluster-demo.html +2 -2
- package/templates/app-shell/AppShell.dc.html +5 -3
- package/templates/app-shell/ds-base.js +33 -1
- package/templates/app-shell/support.js +13 -20
- package/templates/article/Article.dc.html +5 -1
- package/templates/article/ds-base.js +33 -1
- package/templates/article/support.js +13 -20
- package/templates/auth/Auth.dc.html +10 -5
- package/templates/auth/content-schema.json +12 -0
- package/templates/auth/ds-base.js +33 -1
- package/templates/auth/support.js +13 -20
- package/templates/bod-deck/BodDeck.dc.html +11 -10
- package/templates/bod-deck/ds-base.js +33 -1
- package/templates/bod-deck/support.js +13 -20
- package/templates/bod-investor-update/BodInvestorUpdate.dc.html +4 -3
- package/templates/bod-investor-update/ds-base.js +33 -1
- package/templates/bod-investor-update/support.js +13 -20
- package/templates/bod-memo/BodMemo.dc.html +6 -5
- package/templates/bod-memo/ds-base.js +33 -1
- package/templates/bod-memo/support.js +13 -20
- package/templates/bod-minutes/BodMinutes.dc.html +2 -1
- package/templates/bod-minutes/ds-base.js +33 -1
- package/templates/bod-minutes/support.js +13 -20
- package/templates/bod-report/BodReport.dc.html +3 -2
- package/templates/bod-report/ds-base.js +33 -1
- package/templates/bod-report/support.js +13 -20
- package/templates/bod-resolution/BodResolution.dc.html +5 -4
- package/templates/bod-resolution/ds-base.js +33 -1
- package/templates/bod-resolution/support.js +13 -20
- package/templates/culture-event-invite/CultureEventInvite.dc.html +3 -2
- package/templates/culture-event-invite/ds-base.js +33 -1
- package/templates/culture-event-invite/support.js +13 -20
- package/templates/dashboard/Dashboard.dc.html +7 -3
- package/templates/dashboard/ds-base.js +33 -1
- package/templates/dashboard/support.js +13 -20
- package/templates/delivery-change-note/DeliveryChangeNote.dc.html +2 -1
- package/templates/delivery-change-note/ds-base.js +33 -1
- package/templates/delivery-change-note/support.js +13 -20
- package/templates/delivery-kickoff/DeliveryKickoff.dc.html +3 -2
- package/templates/delivery-kickoff/ds-base.js +33 -1
- package/templates/delivery-kickoff/support.js +13 -20
- package/templates/delivery-qbr-deck/DeliveryQbrDeck.dc.html +6 -5
- package/templates/delivery-qbr-deck/ds-base.js +33 -1
- package/templates/delivery-qbr-deck/support.js +13 -20
- package/templates/delivery-runbook/DeliveryRunbook.dc.html +3 -2
- package/templates/delivery-runbook/ds-base.js +33 -1
- package/templates/delivery-runbook/support.js +13 -20
- package/templates/delivery-status-email/DeliveryStatusEmail.dc.html +3 -2
- package/templates/delivery-status-email/ds-base.js +33 -1
- package/templates/delivery-status-email/support.js +13 -20
- package/templates/doc-style-guide/DocStyleGuide.dc.html +67 -64
- package/templates/doc-style-guide/ds-base.js +33 -1
- package/templates/doc-style-guide/support.js +13 -20
- package/templates/doc-suite-index/DocSuiteIndex.dc.html +46 -45
- package/templates/doc-suite-index/ds-base.js +33 -1
- package/templates/doc-suite-index/support.js +13 -20
- package/templates/doc-templates/DocTemplates.dc.html +42 -41
- package/templates/doc-templates/ds-base.js +33 -1
- package/templates/doc-templates/support.js +13 -20
- package/templates/email/Email.dc.html +3 -2
- package/templates/email/ds-base.js +33 -1
- package/templates/email/support.js +13 -20
- package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +4 -3
- package/templates/finance-dunning-email/ds-base.js +33 -1
- package/templates/finance-dunning-email/support.js +13 -20
- package/templates/finance-expense-report/FinanceExpenseReport.dc.html +4 -3
- package/templates/finance-expense-report/ds-base.js +33 -1
- package/templates/finance-expense-report/support.js +13 -20
- package/templates/finance-invoice/FinanceInvoice.dc.html +4 -3
- package/templates/finance-invoice/ds-base.js +33 -1
- package/templates/finance-invoice/support.js +13 -20
- package/templates/finance-quote/FinanceQuote.dc.html +4 -3
- package/templates/finance-quote/ds-base.js +33 -1
- package/templates/finance-quote/support.js +13 -20
- package/templates/hr-announcement/HrAnnouncement.dc.html +5 -4
- package/templates/hr-announcement/ds-base.js +33 -1
- package/templates/hr-announcement/support.js +13 -20
- package/templates/hr-interview-kit/HrInterviewKit.dc.html +4 -3
- package/templates/hr-interview-kit/ds-base.js +33 -1
- package/templates/hr-interview-kit/support.js +13 -20
- package/templates/hr-offboarding/HrOffboarding.dc.html +2 -1
- package/templates/hr-offboarding/ds-base.js +33 -1
- package/templates/hr-offboarding/support.js +13 -20
- package/templates/hr-performance-review/HrPerformanceReview.dc.html +2 -1
- package/templates/hr-performance-review/ds-base.js +33 -1
- package/templates/hr-performance-review/support.js +13 -20
- package/templates/hr-pip/HrPip.dc.html +2 -1
- package/templates/hr-pip/ds-base.js +33 -1
- package/templates/hr-pip/support.js +13 -20
- package/templates/kitchen-sink.html +9 -4
- package/templates/legal-msa/LegalMsa.dc.html +2 -1
- package/templates/legal-msa/ds-base.js +33 -1
- package/templates/legal-msa/support.js +13 -20
- package/templates/legal-sow/LegalSow.dc.html +2 -1
- package/templates/legal-sow/ds-base.js +33 -1
- package/templates/legal-sow/support.js +13 -20
- package/templates/marketing-campaign-brief/MarketingCampaignBrief.dc.html +3 -2
- package/templates/marketing-campaign-brief/ds-base.js +33 -1
- package/templates/marketing-campaign-brief/support.js +13 -20
- package/templates/marketing-case-study/MarketingCaseStudy.dc.html +5 -4
- package/templates/marketing-case-study/ds-base.js +33 -1
- package/templates/marketing-case-study/support.js +13 -20
- package/templates/marketing-launch/MarketingLaunch.dc.html +6 -5
- package/templates/marketing-launch/ds-base.js +33 -1
- package/templates/marketing-launch/support.js +13 -20
- package/templates/marketing-newsletter/MarketingNewsletter.dc.html +13 -12
- package/templates/marketing-newsletter/ds-base.js +33 -1
- package/templates/marketing-newsletter/support.js +13 -20
- package/templates/marketing-page/MarketingPage.dc.html +17 -12
- package/templates/marketing-page/ds-base.js +33 -1
- package/templates/marketing-page/support.js +13 -20
- package/templates/marketing-press-release/MarketingPressRelease.dc.html +2 -1
- package/templates/marketing-press-release/ds-base.js +33 -1
- package/templates/marketing-press-release/support.js +13 -20
- package/templates/marketing-social-kit/MarketingSocialKit.dc.html +5 -4
- package/templates/marketing-social-kit/ds-base.js +33 -1
- package/templates/marketing-social-kit/support.js +13 -20
- package/templates/sales-one-pager/SalesOnePager.dc.html +5 -4
- package/templates/sales-one-pager/ds-base.js +33 -1
- package/templates/sales-one-pager/support.js +13 -20
- package/templates/sales-proposal-deck/SalesProposalDeck.dc.html +7 -6
- package/templates/sales-proposal-deck/ds-base.js +33 -1
- package/templates/sales-proposal-deck/support.js +13 -20
- package/templates/settings/Settings.dc.html +5 -1
- package/templates/settings/ds-base.js +33 -1
- package/templates/settings/support.js +13 -20
- package/templates/slide-deck/SlideDeck.dc.html +5 -4
- package/templates/slide-deck/ds-base.js +33 -1
- package/templates/slide-deck/support.js +13 -20
- package/templates/team-meeting-agenda/TeamMeetingAgenda.dc.html +2 -1
- package/templates/team-meeting-agenda/ds-base.js +33 -1
- package/templates/team-meeting-agenda/support.js +13 -20
- package/templates/tech-incident-report/TechIncidentReport.dc.html +3 -2
- package/templates/tech-incident-report/ds-base.js +33 -1
- package/templates/tech-incident-report/support.js +13 -20
- package/templates/tech-release-notes/TechReleaseNotes.dc.html +6 -5
- package/templates/tech-release-notes/ds-base.js +33 -1
- package/templates/tech-release-notes/support.js +13 -20
- package/templates/tech-retro/TechRetro.dc.html +4 -3
- package/templates/tech-retro/ds-base.js +33 -1
- package/templates/tech-retro/support.js +13 -20
- package/templates/tech-rfc/TechRfc.dc.html +3 -2
- package/templates/tech-rfc/ds-base.js +33 -1
- package/templates/tech-rfc/support.js +13 -20
- package/templates/vn-appointment-transfer/VnAppointmentTransfer.dc.html +36 -35
- package/templates/vn-appointment-transfer/ds-base.js +33 -1
- package/templates/vn-appointment-transfer/support.js +13 -20
- package/templates/vn-certificate-of-employment/VnCertificateOfEmployment.dc.html +27 -26
- package/templates/vn-certificate-of-employment/ds-base.js +33 -1
- package/templates/vn-certificate-of-employment/support.js +13 -20
- package/templates/vn-code-of-conduct/VnCodeOfConduct.dc.html +44 -43
- package/templates/vn-code-of-conduct/ds-base.js +33 -1
- package/templates/vn-code-of-conduct/support.js +13 -20
- package/templates/vn-compensation-regulation/VnCompensationRegulation.dc.html +53 -52
- package/templates/vn-compensation-regulation/ds-base.js +33 -1
- package/templates/vn-compensation-regulation/support.js +13 -20
- package/templates/vn-contract-amendment/VnContractAmendment.dc.html +41 -40
- package/templates/vn-contract-amendment/ds-base.js +33 -1
- package/templates/vn-contract-amendment/support.js +13 -20
- package/templates/vn-contractor-agreement/VnContractorAgreement.dc.html +50 -49
- package/templates/vn-contractor-agreement/ds-base.js +33 -1
- package/templates/vn-contractor-agreement/support.js +13 -20
- package/templates/vn-data-protection-policy/VnDataProtectionPolicy.dc.html +54 -53
- package/templates/vn-data-protection-policy/ds-base.js +33 -1
- package/templates/vn-data-protection-policy/support.js +13 -20
- package/templates/vn-disciplinary-case-file/VnDisciplinaryCaseFile.dc.html +72 -71
- package/templates/vn-disciplinary-case-file/ds-base.js +33 -1
- package/templates/vn-disciplinary-case-file/support.js +13 -20
- package/templates/vn-disciplinary-schedule/VnDisciplinarySchedule.dc.html +21 -20
- package/templates/vn-disciplinary-schedule/ds-base.js +33 -1
- package/templates/vn-disciplinary-schedule/support.js +13 -20
- package/templates/vn-employee-request/VnEmployeeRequest.dc.html +54 -53
- package/templates/vn-employee-request/ds-base.js +33 -1
- package/templates/vn-employee-request/support.js +13 -20
- package/templates/vn-exit-handover/VnExitHandover.dc.html +122 -121
- package/templates/vn-exit-handover/ds-base.js +33 -1
- package/templates/vn-exit-handover/support.js +13 -20
- package/templates/vn-foreign-employee-pack/VnForeignEmployeePack.dc.html +31 -30
- package/templates/vn-foreign-employee-pack/ds-base.js +33 -1
- package/templates/vn-foreign-employee-pack/support.js +13 -20
- package/templates/vn-grassroots-democracy/VnGrassrootsDemocracy.dc.html +54 -53
- package/templates/vn-grassroots-democracy/ds-base.js +33 -1
- package/templates/vn-grassroots-democracy/support.js +13 -20
- package/templates/vn-infosec-policy/VnInfosecPolicy.dc.html +50 -49
- package/templates/vn-infosec-policy/ds-base.js +33 -1
- package/templates/vn-infosec-policy/support.js +13 -20
- package/templates/vn-internal-labor-regulations/VnInternalLaborRegulations.dc.html +84 -83
- package/templates/vn-internal-labor-regulations/ds-base.js +33 -1
- package/templates/vn-internal-labor-regulations/support.js +13 -20
- package/templates/vn-internship-agreement/VnInternshipAgreement.dc.html +44 -43
- package/templates/vn-internship-agreement/ds-base.js +33 -1
- package/templates/vn-internship-agreement/support.js +13 -20
- package/templates/vn-job-description/VnJobDescription.dc.html +28 -27
- package/templates/vn-job-description/ds-base.js +33 -1
- package/templates/vn-job-description/support.js +13 -20
- package/templates/vn-labor-contract/VnLaborContract.dc.html +109 -108
- package/templates/vn-labor-contract/ds-base.js +33 -1
- package/templates/vn-labor-contract/support.js +13 -20
- package/templates/vn-labour-management-book/VnLabourManagementBook.dc.html +32 -31
- package/templates/vn-labour-management-book/ds-base.js +33 -1
- package/templates/vn-labour-management-book/support.js +13 -20
- package/templates/vn-mutual-nda/VnMutualNda.dc.html +45 -44
- package/templates/vn-mutual-nda/ds-base.js +33 -1
- package/templates/vn-mutual-nda/support.js +13 -20
- package/templates/vn-ndnca-ip/VnNdncaIp.dc.html +73 -72
- package/templates/vn-ndnca-ip/ds-base.js +33 -1
- package/templates/vn-ndnca-ip/support.js +13 -20
- package/templates/vn-offer-letter/VnOfferLetter.dc.html +32 -31
- package/templates/vn-offer-letter/ds-base.js +33 -1
- package/templates/vn-offer-letter/support.js +13 -20
- package/templates/vn-onboarding-checklist/VnOnboardingChecklist.dc.html +16 -15
- package/templates/vn-onboarding-checklist/ds-base.js +33 -1
- package/templates/vn-onboarding-checklist/support.js +13 -20
- package/templates/vn-pdpl-consent/VnPdplConsent.dc.html +43 -42
- package/templates/vn-pdpl-consent/ds-base.js +33 -1
- package/templates/vn-pdpl-consent/support.js +13 -20
- package/templates/vn-performance-evaluation-regulation/VnPerformanceEvaluationRegulation.dc.html +65 -64
- package/templates/vn-performance-evaluation-regulation/ds-base.js +33 -1
- package/templates/vn-performance-evaluation-regulation/support.js +13 -20
- package/templates/vn-performance-review-form/VnPerformanceReviewForm.dc.html +49 -48
- package/templates/vn-performance-review-form/ds-base.js +33 -1
- package/templates/vn-performance-review-form/support.js +13 -20
- package/templates/vn-phantom-stock/VnPhantomStock.dc.html +41 -40
- package/templates/vn-phantom-stock/ds-base.js +33 -1
- package/templates/vn-phantom-stock/support.js +13 -20
- package/templates/vn-probation-contract/VnProbationContract.dc.html +56 -55
- package/templates/vn-probation-contract/ds-base.js +33 -1
- package/templates/vn-probation-contract/support.js +13 -20
- package/templates/vn-receipt-acknowledgement/VnReceiptAcknowledgement.dc.html +28 -27
- package/templates/vn-receipt-acknowledgement/ds-base.js +33 -1
- package/templates/vn-receipt-acknowledgement/support.js +13 -20
- package/templates/vn-remote-work-policy/VnRemoteWorkPolicy.dc.html +44 -43
- package/templates/vn-remote-work-policy/ds-base.js +33 -1
- package/templates/vn-remote-work-policy/support.js +13 -20
- package/templates/vn-resignation-letter/VnResignationLetter.dc.html +20 -19
- package/templates/vn-resignation-letter/ds-base.js +33 -1
- package/templates/vn-resignation-letter/support.js +13 -20
- package/templates/vn-salary-scale/VnSalaryScale.dc.html +39 -38
- package/templates/vn-salary-scale/ds-base.js +33 -1
- package/templates/vn-salary-scale/support.js +13 -20
- package/templates/vn-sow-appendix/VnSowAppendix.dc.html +21 -20
- package/templates/vn-sow-appendix/ds-base.js +33 -1
- package/templates/vn-sow-appendix/support.js +13 -20
- package/templates/vn-termination-decision/VnTerminationDecision.dc.html +25 -24
- package/templates/vn-termination-decision/ds-base.js +33 -1
- package/templates/vn-termination-decision/support.js +13 -20
- package/templates/vn-total-rewards-appendix/VnTotalRewardsAppendix.dc.html +63 -62
- package/templates/vn-total-rewards-appendix/ds-base.js +33 -1
- package/templates/vn-total-rewards-appendix/support.js +13 -20
- package/templates/vn-training-commitment/VnTrainingCommitment.dc.html +39 -38
- package/templates/vn-training-commitment/ds-base.js +33 -1
- package/templates/vn-training-commitment/support.js +13 -20
- package/templates/vn-travel-expense-policy/VnTravelExpensePolicy.dc.html +50 -49
- package/templates/vn-travel-expense-policy/ds-base.js +33 -1
- package/templates/vn-travel-expense-policy/support.js +13 -20
- package/tokens/colors.css +19 -1
- package/tokens/component-tokens.css +1 -0
- package/tokens/elements.css +35 -2
- package/tokens/elevation.css +8 -2
- package/tokens/native/CSTokens.kt +35 -1
- package/tokens/native/CSTokens.swift +35 -1
- package/tokens/native/cs_tokens.dart +35 -1
- package/tokens/provenance.json +12 -12
- package/tokens/spacing.css +2 -0
- package/tokens/tokens.dtcg.json +111 -3
- package/tokens/tokens.js +46 -6
- package/tokens/tokens.json +45 -5
- package/tokens/typography.css +2 -0
- package/ui_kits/deck/index.html +4 -0
- package/ui_kits/status-hub/StatusHub.jsx +30 -4
- package/ui_kits/status-hub/index.html +3 -3
- package/ui_kits/status-hub/login.html +4 -0
- package/ui_kits/status-hub/project.html +4 -0
- package/ui_kits/status-hub/settings.html +35 -13
- package/ui_kits/status-hub/status.css +5 -1
- package/ui_kits/website/Website.jsx +25 -3
- package/ui_kits/website/careers.html +4 -0
- package/ui_kits/website/chat.html +5 -3
- package/ui_kits/website/index.html +3 -3
- package/ui_kits/website/site.css +2 -2
- package/ui_kits/website/work.html +4 -0
package/base/responsive.css
CHANGED
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
/* 1 — Media and embeds never blow out a column */
|
|
14
14
|
img, svg, video, canvas { max-width: 100%; height: auto; }
|
|
15
15
|
|
|
16
|
+
/* G1 / CDS-RESP-001 — container-query migration (compatibility layer).
|
|
17
|
+
* Opt-in hosts mark a sizing context with `.cs-cq`. Children can use
|
|
18
|
+
* `@container (max-width: …)` without waiting for a full layout rewrite.
|
|
19
|
+
* Media queries above remain the default until packs generate CQ variants. */
|
|
20
|
+
.cs-cq { container-type: inline-size; container-name: cs; }
|
|
21
|
+
@container cs (max-width: 640px) {
|
|
22
|
+
.cs-cq-stack { display: flex !important; flex-direction: column !important; }
|
|
23
|
+
.cs-cq-stack > * { min-inline-size: 0; max-inline-size: 100%; }
|
|
24
|
+
}
|
|
25
|
+
|
|
16
26
|
/* 2 — Print-doc archetype: shrink desk + sheet chrome as the screen narrows.
|
|
17
27
|
* cs-desk is the grey backing; cs-sheet is the white page. */
|
|
18
28
|
@media screen and (max-width: 900px) {
|
|
@@ -126,3 +136,18 @@ img, svg, video, canvas { max-width: 100%; height: auto; }
|
|
|
126
136
|
main > * { min-width: 0; }
|
|
127
137
|
.cs-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
|
|
128
138
|
}
|
|
139
|
+
|
|
140
|
+
/* ---- 320 reflow (UX-003 / WCAG 1.4.10) ---- */
|
|
141
|
+
@media (max-width: 360px) {
|
|
142
|
+
.cs-tabs { overflow-x: auto; overscroll-behavior-x: contain; scrollbar-width: thin; }
|
|
143
|
+
.cs-pagination { flex-wrap: wrap; max-inline-size: 100%; }
|
|
144
|
+
.cs-pagination button { min-inline-size: 32px; }
|
|
145
|
+
.cs-otp { gap: 4px; }
|
|
146
|
+
.cs-otp__box { inline-size: 36px; block-size: 40px; font-size: var(--cs-font-size-base); }
|
|
147
|
+
.cs-dl { grid-template-columns: 1fr; }
|
|
148
|
+
.cs-field { min-inline-size: 0; }
|
|
149
|
+
.cs-field__control { min-inline-size: 0; }
|
|
150
|
+
.card, button.card, article.case { min-width: 0 !important; }
|
|
151
|
+
[style*="minmax(320px"] { grid-template-columns: 1fr !important; }
|
|
152
|
+
[style*="grid-template-columns: 248px 1fr"] { grid-template-columns: 1fr !important; }
|
|
153
|
+
}
|
package/base/shell.css
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
.cs-steps { display: flex; gap: 0; }
|
|
17
17
|
.cs-step { flex: 1; display: flex; flex-direction: column; align-items: flex-start; gap: 8px; position: relative; padding-inline-end: 12px; }
|
|
18
18
|
.cs-step:not(:last-child)::after { content: ""; position: absolute; inset-block-start: 13px; inset-inline-start: 34px; inline-size: calc(100% - 46px); block-size: 2px; background: var(--cs-color-border-default); }
|
|
19
|
-
.cs-step__marker { inline-size: 28px; block-size: 28px; border-radius: 50%; display: grid; place-items: center; font-family: var(--cs-font-family-mono); font-weight: 700; font-size:
|
|
19
|
+
.cs-step__marker { inline-size: 28px; block-size: 28px; border-radius: 50%; display: grid; place-items: center; font-family: var(--cs-font-family-mono); font-weight: 700; font-size: var(--cs-font-size-sm); background: var(--cs-color-surface-raised); color: var(--cs-color-text-muted); border: 1px solid var(--cs-color-border-default); z-index: 1; }
|
|
20
20
|
.cs-step--done .cs-step__marker { background: var(--cs-color-brand-umber); color: #fff; border-color: transparent; }
|
|
21
21
|
.cs-step--done:not(:last-child)::after { background: var(--cs-color-brand-umber); }
|
|
22
22
|
.cs-step--active .cs-step__marker { background: var(--cs-color-brand-ochre); color: var(--cs-color-brand-umber); border-color: transparent; box-shadow: 0 0 0 4px color-mix(in oklab, var(--cs-color-brand-ochre) 28%, transparent); }
|
|
@@ -53,3 +53,9 @@
|
|
|
53
53
|
.cs-cmdk__item { display: flex; align-items: center; gap: 11px; inline-size: 100%; text-align: start; border: 0; background: transparent; font: inherit; font-family: var(--cs-font-family-ui); font-size: var(--cs-font-size-sm); color: var(--cs-color-text-primary); padding: 10px 12px; border-radius: var(--cs-radius-md); cursor: pointer; }
|
|
54
54
|
.cs-cmdk__item:hover, .cs-cmdk__item[aria-selected="true"] { background: var(--cs-color-surface-raised); }
|
|
55
55
|
.cs-cmdk__item .cs-kbd { margin-inline-start: auto; }
|
|
56
|
+
|
|
57
|
+
/* ---- system-theme dark parity (UX-010 / CDS-THEME-001) ---- */
|
|
58
|
+
@media (prefers-color-scheme: dark) {
|
|
59
|
+
[data-theme="system"] .cs-nav-item.is-active { color: var(--cs-color-brand-ochre); }
|
|
60
|
+
[data-theme="system"] .cs-nav-item.is-active .cs-nav-item__icon { color: var(--cs-color-brand-ochre); }
|
|
61
|
+
}
|
package/components/_i18n/i18n.js
CHANGED
|
@@ -31,7 +31,19 @@ export function makeT(component, lang) { return (key) => tr(component, key, lang
|
|
|
31
31
|
export function useLang(propLang) {
|
|
32
32
|
const ref = React.useRef(null);
|
|
33
33
|
const [lang, setLang] = React.useState(() => resolveLang(propLang, null));
|
|
34
|
-
React.useLayoutEffect(() => {
|
|
34
|
+
React.useLayoutEffect(() => {
|
|
35
|
+
const el = ref.current;
|
|
36
|
+
setLang(resolveLang(propLang, el));
|
|
37
|
+
if (!el || propLang) return undefined;
|
|
38
|
+
// Re-resolve when an ancestor [lang] changes (template Language axis / __dcSetProps).
|
|
39
|
+
const obs = new MutationObserver(() => setLang(resolveLang(propLang, el)));
|
|
40
|
+
let node = el.parentElement;
|
|
41
|
+
while (node) {
|
|
42
|
+
obs.observe(node, { attributes: true, attributeFilter: ["lang"] });
|
|
43
|
+
node = node.parentElement;
|
|
44
|
+
}
|
|
45
|
+
return () => obs.disconnect();
|
|
46
|
+
}, [propLang]);
|
|
35
47
|
return [ref, lang];
|
|
36
48
|
}
|
|
37
49
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* CyberSkill — central bilingual string registry (v3 Batch 0/1).
|
|
1
|
+
/* CyberSkill — central bilingual string registry (v3 Batch 0/1/2).
|
|
2
2
|
* Every component with BUILT-IN UI text (text the consumer does NOT pass as a prop)
|
|
3
3
|
* registers its strings here, EN + VI. _audit/bilingual-parity.html enforces:
|
|
4
4
|
* • each component has both `en` and `vi`
|
|
@@ -77,8 +77,8 @@ export const strings = {
|
|
|
77
77
|
vi: { input: "Ô nhập lệnh" },
|
|
78
78
|
},
|
|
79
79
|
DataGrid: {
|
|
80
|
-
en: { empty: "No records", selectAll: "Select all rows", selectRow: "Select row" },
|
|
81
|
-
vi: { empty: "Không có bản ghi", selectAll: "Chọn tất cả", selectRow: "Chọn dòng" },
|
|
80
|
+
en: { empty: "No records", selectAll: "Select all rows", selectRow: "Select row", pin: "Pin this column", pinNamed: "Pin column {name}" },
|
|
81
|
+
vi: { empty: "Không có bản ghi", selectAll: "Chọn tất cả", selectRow: "Chọn dòng", pin: "Ghim cột này", pinNamed: "Ghim cột {name}" },
|
|
82
82
|
},
|
|
83
83
|
Splitter: {
|
|
84
84
|
en: { label: "Resize panes" },
|
|
@@ -188,4 +188,53 @@ export const strings = {
|
|
|
188
188
|
en: { aria: "Human review gate", risk: "review required", approve: "Approve", reject: "Reject", reviewer: "Reviewer" },
|
|
189
189
|
vi: { aria: "Cổng kiểm duyệt của con người", risk: "cần xem xét", approve: "Phê duyệt", reject: "Từ chối", reviewer: "Người duyệt" },
|
|
190
190
|
},
|
|
191
|
+
|
|
192
|
+
Switch: {
|
|
193
|
+
en: { on: "On", off: "Off" },
|
|
194
|
+
vi: { on: "Bật", off: "Tắt" },
|
|
195
|
+
},
|
|
196
|
+
Tabs: {
|
|
197
|
+
en: { list: "Tabs" },
|
|
198
|
+
vi: { list: "Tab" },
|
|
199
|
+
},
|
|
200
|
+
EmptyState: {
|
|
201
|
+
en: { title: "Nothing here yet", action: "Get started" },
|
|
202
|
+
vi: { title: "Chưa có gì ở đây", action: "Bắt đầu" },
|
|
203
|
+
},
|
|
204
|
+
Alert: {
|
|
205
|
+
en: { dismiss: "Dismiss" },
|
|
206
|
+
vi: { dismiss: "Đóng" },
|
|
207
|
+
},
|
|
208
|
+
Select: {
|
|
209
|
+
en: { placeholder: "Select…" },
|
|
210
|
+
vi: { placeholder: "Chọn…" },
|
|
211
|
+
},
|
|
212
|
+
Slider: {
|
|
213
|
+
en: { label: "Slider" },
|
|
214
|
+
vi: { label: "Thanh trượt" },
|
|
215
|
+
},
|
|
216
|
+
Steps: {
|
|
217
|
+
en: { label: "Steps" },
|
|
218
|
+
vi: { label: "Các bước" },
|
|
219
|
+
},
|
|
220
|
+
Toggle: {
|
|
221
|
+
en: { pressed: "Pressed", unpressed: "Not pressed" },
|
|
222
|
+
vi: { pressed: "Đang bật", unpressed: "Chưa bật" },
|
|
223
|
+
},
|
|
224
|
+
Menu: {
|
|
225
|
+
en: { menu: "Menu" },
|
|
226
|
+
vi: { menu: "Trình đơn" },
|
|
227
|
+
},
|
|
228
|
+
ContextMenu: {
|
|
229
|
+
en: { menu: "Context menu" },
|
|
230
|
+
vi: { menu: "Trình đơn ngữ cảnh" },
|
|
231
|
+
},
|
|
232
|
+
Tooltip: {
|
|
233
|
+
en: { label: "Tooltip" },
|
|
234
|
+
vi: { label: "Chú thích" },
|
|
235
|
+
},
|
|
236
|
+
ProgressBar: {
|
|
237
|
+
en: { label: "Progress" },
|
|
238
|
+
vi: { label: "Tiến độ" },
|
|
239
|
+
},
|
|
191
240
|
};
|
|
@@ -15,12 +15,14 @@ const TONE = {
|
|
|
15
15
|
*/
|
|
16
16
|
export function ConfidenceMeter({ value, level, segments = 5, label, lang, className }) {
|
|
17
17
|
let tone, filled;
|
|
18
|
+
const segs = Math.max(1, Number(segments) || 5);
|
|
18
19
|
if (value != null) {
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
const v = Math.max(0, Math.min(1, Number(value) || 0));
|
|
21
|
+
tone = v < 0.4 ? "low" : v < 0.75 ? "medium" : "high";
|
|
22
|
+
filled = Math.max(0, Math.min(segs, Math.round(v * segs)));
|
|
21
23
|
} else {
|
|
22
|
-
tone = level
|
|
23
|
-
filled = Math.round((TONE[tone].fill / 5) *
|
|
24
|
+
tone = TONE[level] ? level : "medium";
|
|
25
|
+
filled = Math.max(0, Math.min(segs, Math.round((TONE[tone].fill / 5) * segs)));
|
|
24
26
|
}
|
|
25
27
|
const meta = TONE[tone];
|
|
26
28
|
const [ref, L] = useLang(lang);
|
|
@@ -33,8 +35,8 @@ export function ConfidenceMeter({ value, level, segments = 5, label, lang, class
|
|
|
33
35
|
<span>{lbl}</span>
|
|
34
36
|
<span className="cs-confidence__level" style={{ color: meta.color }}>{levelText}</span>
|
|
35
37
|
</div>
|
|
36
|
-
<div className="cs-confidence__track" role="meter" aria-valuemin={0} aria-valuemax={
|
|
37
|
-
{Array.from({ length:
|
|
38
|
+
<div className="cs-confidence__track" role="meter" aria-valuemin={0} aria-valuemax={segs} aria-valuenow={filled} aria-label={lbl + ": " + levelText}>
|
|
39
|
+
{Array.from({ length: segs }).map((_, i) => (
|
|
38
40
|
<span key={i} className="cs-confidence__seg" style={i < filled ? { background: meta.color } : undefined} />
|
|
39
41
|
))}
|
|
40
42
|
</div>
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cx } from "../_utils/cx.js";
|
|
3
3
|
|
|
4
|
-
/** CyberSkill Watermark — repeats rotated text behind children (draft/confidential marking).
|
|
4
|
+
/** CyberSkill Watermark — repeats rotated text behind children (draft/confidential marking).
|
|
5
|
+
* Fill colour is read from --cs-color-text-primary at render time (data-URIs cannot resolve CSS vars). */
|
|
5
6
|
export function Watermark({ text = "CyberSkill", opacity = 0.09, gap = 140, rotate = -22, children, className }) {
|
|
7
|
+
const host = React.useRef(null);
|
|
8
|
+
const [fill, setFill] = React.useState("#45210E");
|
|
9
|
+
React.useLayoutEffect(() => {
|
|
10
|
+
if (!host.current || typeof getComputedStyle === "undefined") return;
|
|
11
|
+
const c = getComputedStyle(host.current).getPropertyValue("--cs-color-text-primary").trim();
|
|
12
|
+
if (c) setFill(c);
|
|
13
|
+
}, []);
|
|
6
14
|
const svg = encodeURIComponent(
|
|
7
|
-
`<svg xmlns="http://www.w3.org/2000/svg" width="${gap}" height="${gap}"><text x="50%" y="50%" font-family="Be Vietnam Pro, sans-serif" font-size="14" font-weight="700" fill="
|
|
15
|
+
`<svg xmlns="http://www.w3.org/2000/svg" width="${gap}" height="${gap}"><text x="50%" y="50%" font-family="Be Vietnam Pro, sans-serif" font-size="14" font-weight="700" fill="${fill}" fill-opacity="${opacity}" text-anchor="middle" transform="rotate(${rotate} ${gap / 2} ${gap / 2})">${String(text).replace(/&/g, "&").replace(/</g, "<")}</text></svg>`
|
|
8
16
|
);
|
|
9
17
|
return (
|
|
10
|
-
<div className={cx("cs-watermark", className)} style={{ position: "relative" }}>
|
|
18
|
+
<div ref={host} className={cx("cs-watermark", className)} style={{ position: "relative" }}>
|
|
11
19
|
{children}
|
|
12
20
|
<div aria-hidden="true" style={{ position: "absolute", inset: 0, pointerEvents: "none", backgroundImage: `url("data:image/svg+xml,${svg}")` }} />
|
|
13
21
|
</div>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
3
|
+
import { Icon } from "../icon/Icon.jsx";
|
|
3
4
|
import { cx } from "../_utils/cx.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
@@ -114,7 +115,15 @@ export function DataGrid({
|
|
|
114
115
|
</button>
|
|
115
116
|
) : c.header}
|
|
116
117
|
{persistKey ? (
|
|
117
|
-
<button
|
|
118
|
+
<button
|
|
119
|
+
type="button"
|
|
120
|
+
className="cs-datagrid__pin"
|
|
121
|
+
title={t("pin")}
|
|
122
|
+
onClick={() => pinColumn(c.key)}
|
|
123
|
+
aria-label={t("pinNamed").replace("{name}", String(c.header != null ? c.header : c.key))}
|
|
124
|
+
>
|
|
125
|
+
<Icon name="pin" size="sm" />
|
|
126
|
+
</button>
|
|
118
127
|
) : null}
|
|
119
128
|
</th>
|
|
120
129
|
))}
|
|
@@ -2,27 +2,12 @@ import React from "react";
|
|
|
2
2
|
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
3
3
|
import { Icon } from "../icon/Icon.jsx";
|
|
4
4
|
import { cx } from "../_utils/cx.js";
|
|
5
|
-
|
|
6
|
-
const FOCUSABLE = [
|
|
7
|
-
'a[href]',
|
|
8
|
-
'area[href]',
|
|
9
|
-
'button:not([disabled])',
|
|
10
|
-
'input:not([disabled]):not([type="hidden"])',
|
|
11
|
-
'select:not([disabled])',
|
|
12
|
-
'textarea:not([disabled])',
|
|
13
|
-
'audio[controls]',
|
|
14
|
-
'video[controls]',
|
|
15
|
-
'summary',
|
|
16
|
-
'iframe',
|
|
17
|
-
'[contenteditable]:not([contenteditable="false"])',
|
|
18
|
-
'[tabindex]:not([tabindex="-1"])',
|
|
19
|
-
].join(",");
|
|
5
|
+
import { useOverlayLayer } from "../overlays/OverlayManager.jsx";
|
|
20
6
|
|
|
21
7
|
/**
|
|
22
8
|
* CyberSkill Dialog — modal overlay + panel. aria-modal, labelled by title and
|
|
23
9
|
* described by its body, scrim click closes. Body scroll is locked while open
|
|
24
|
-
*
|
|
25
|
-
* with Buttons.
|
|
10
|
+
* via the overlay manager (nested-safe). Compose actions in the footer with Buttons.
|
|
26
11
|
*/
|
|
27
12
|
export function Dialog({
|
|
28
13
|
open,
|
|
@@ -40,34 +25,17 @@ export function Dialog({
|
|
|
40
25
|
const bodyId = children == null ? undefined : baseId + "-body";
|
|
41
26
|
const [ref, L] = useLang(lang);
|
|
42
27
|
const panel = React.useRef(null);
|
|
43
|
-
const closeRef = React.useRef(onClose);
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
if (e.key !== "Tab" || !panel.current) return;
|
|
55
|
-
const f = focusables();
|
|
56
|
-
if (!f.length) { e.preventDefault(); panel.current.focus(); return; }
|
|
57
|
-
const a = f[0], z = f[f.length - 1];
|
|
58
|
-
const active = document.activeElement;
|
|
59
|
-
const inside = panel.current.contains(active);
|
|
60
|
-
// Wrap at either edge; if focus somehow escaped the panel, pull it back in.
|
|
61
|
-
if (e.shiftKey && (!inside || active === a)) { e.preventDefault(); z.focus(); }
|
|
62
|
-
else if (!e.shiftKey && (!inside || active === z)) { e.preventDefault(); a.focus(); }
|
|
63
|
-
};
|
|
64
|
-
document.addEventListener("keydown", k);
|
|
65
|
-
return () => {
|
|
66
|
-
document.removeEventListener("keydown", k);
|
|
67
|
-
document.body.style.overflow = prevOverflow;
|
|
68
|
-
if (prev && prev.focus) prev.focus();
|
|
69
|
-
};
|
|
70
|
-
}, [open]);
|
|
28
|
+
const closeRef = React.useRef(onClose);
|
|
29
|
+
closeRef.current = onClose;
|
|
30
|
+
|
|
31
|
+
useOverlayLayer({
|
|
32
|
+
open: !!open,
|
|
33
|
+
kind: "modal",
|
|
34
|
+
trapFocus: true,
|
|
35
|
+
onEscape: () => closeRef.current && closeRef.current(),
|
|
36
|
+
panelRef: panel,
|
|
37
|
+
});
|
|
38
|
+
|
|
71
39
|
const cl = closeLabel != null ? closeLabel : makeT("Dialog", L)("close");
|
|
72
40
|
if (!open) return null;
|
|
73
41
|
return (
|
|
@@ -3,10 +3,12 @@ import { cx } from "../_utils/cx.js";
|
|
|
3
3
|
|
|
4
4
|
/** CyberSkill ProgressBar — determinate progress. variant: ochre (default) | umber | success. */
|
|
5
5
|
export function ProgressBar({ value = 0, max = 100, variant, label, className }) {
|
|
6
|
-
const
|
|
6
|
+
const safeMax = max > 0 ? max : 100;
|
|
7
|
+
const clamped = Math.max(0, Math.min(safeMax, Number(value) || 0));
|
|
8
|
+
const pct = Math.max(0, Math.min(100, (clamped / safeMax) * 100));
|
|
7
9
|
return (
|
|
8
10
|
<div className={cx("cs-progress", variant && `cs-progress--${variant}`, className)}
|
|
9
|
-
role="progressbar" aria-valuenow={Math.round(
|
|
11
|
+
role="progressbar" aria-valuenow={Math.round(clamped)} aria-valuemin={0} aria-valuemax={safeMax} aria-label={label}>
|
|
10
12
|
<span className="cs-progress__fill" style={{ inlineSize: pct + "%", width: pct + "%" }} />
|
|
11
13
|
</div>
|
|
12
14
|
);
|
|
@@ -15,11 +15,52 @@ export function Cascader({ nodes = [], value = [], onChange, placeholder, label,
|
|
|
15
15
|
React.useEffect(() => {
|
|
16
16
|
if (!open) return;
|
|
17
17
|
const d = (e) => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); };
|
|
18
|
-
const k = (e) => {
|
|
18
|
+
const k = (e) => {
|
|
19
|
+
if (e.key === "Escape") {
|
|
20
|
+
setOpen(false);
|
|
21
|
+
const field = wrap.current && wrap.current.querySelector(".cs-treeselect__field");
|
|
22
|
+
field && field.focus && field.focus();
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (!wrap.current) return;
|
|
26
|
+
const opts = [...wrap.current.querySelectorAll('[role="option"]')];
|
|
27
|
+
if (!opts.length) return;
|
|
28
|
+
const active = document.activeElement;
|
|
29
|
+
const idx = opts.indexOf(active);
|
|
30
|
+
if (e.key === "ArrowDown") {
|
|
31
|
+
e.preventDefault();
|
|
32
|
+
(opts[idx < 0 ? 0 : Math.min(opts.length - 1, idx + 1)] || opts[0]).focus();
|
|
33
|
+
} else if (e.key === "ArrowUp") {
|
|
34
|
+
e.preventDefault();
|
|
35
|
+
(opts[idx < 0 ? opts.length - 1 : Math.max(0, idx - 1)] || opts[0]).focus();
|
|
36
|
+
} else if (e.key === "Home") {
|
|
37
|
+
e.preventDefault();
|
|
38
|
+
opts[0].focus();
|
|
39
|
+
} else if (e.key === "End") {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
opts[opts.length - 1].focus();
|
|
42
|
+
} else if (e.key === "ArrowRight" && idx >= 0) {
|
|
43
|
+
// Expand into children by activating the selected option when it has a chevron.
|
|
44
|
+
active.click && active.click();
|
|
45
|
+
} else if (e.key === "ArrowLeft") {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
if (path.length) {
|
|
48
|
+
const next = path.slice(0, -1);
|
|
49
|
+
setPath(next);
|
|
50
|
+
} else {
|
|
51
|
+
setOpen(false);
|
|
52
|
+
const field = wrap.current.querySelector(".cs-treeselect__field");
|
|
53
|
+
field && field.focus && field.focus();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
};
|
|
19
57
|
document.addEventListener("mousedown", d);
|
|
20
58
|
document.addEventListener("keydown", k);
|
|
21
|
-
return () => {
|
|
22
|
-
|
|
59
|
+
return () => {
|
|
60
|
+
document.removeEventListener("mousedown", d);
|
|
61
|
+
document.removeEventListener("keydown", k);
|
|
62
|
+
};
|
|
63
|
+
}, [open, path]);
|
|
23
64
|
const cols = [];
|
|
24
65
|
let level = nodes;
|
|
25
66
|
for (let i = 0; level && level.length; i++) {
|
|
@@ -31,7 +72,22 @@ export function Cascader({ nodes = [], value = [], onChange, placeholder, label,
|
|
|
31
72
|
{ let lv = nodes; for (const k of value) { const n = (lv || []).find((x) => x.key === k); if (!n) break; labels.push(n.label); lv = n.children; } }
|
|
32
73
|
return (
|
|
33
74
|
<div ref={(el) => { wrap.current = el; ref.current = el; }} className={cx("cs-cascader", className)}>
|
|
34
|
-
<button
|
|
75
|
+
<button
|
|
76
|
+
type="button"
|
|
77
|
+
id={fieldId}
|
|
78
|
+
className="cs-treeselect__field"
|
|
79
|
+
disabled={disabled}
|
|
80
|
+
aria-haspopup="listbox"
|
|
81
|
+
aria-expanded={open}
|
|
82
|
+
aria-label={label}
|
|
83
|
+
onClick={() => setOpen((o) => !o)}
|
|
84
|
+
onKeyDown={(e) => {
|
|
85
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
86
|
+
e.preventDefault();
|
|
87
|
+
setOpen(true);
|
|
88
|
+
}
|
|
89
|
+
}}
|
|
90
|
+
>
|
|
35
91
|
<span className={labels.length ? undefined : "ph"}>{labels.length ? labels.join(" / ") : ph}</span><span aria-hidden="true">▾</span>
|
|
36
92
|
</button>
|
|
37
93
|
{open ? (
|
|
@@ -41,12 +97,19 @@ export function Cascader({ nodes = [], value = [], onChange, placeholder, label,
|
|
|
41
97
|
{col.map((n) => (
|
|
42
98
|
// role="none" — the <li> wrapper must not sit between listbox and option.
|
|
43
99
|
<li key={n.key} role="none">
|
|
44
|
-
<button
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
role="option"
|
|
103
|
+
aria-selected={path[i] === n.key}
|
|
104
|
+
className={cx("cs-cascader__opt", path[i] === n.key && "on")}
|
|
105
|
+
// Keep options in the focusable set for arrow models (E3).
|
|
106
|
+
data-cs-focusable=""
|
|
45
107
|
onClick={() => {
|
|
46
108
|
const next = [...path.slice(0, i), n.key];
|
|
47
109
|
setPath(next);
|
|
48
110
|
if (!(n.children && n.children.length)) { onChange && onChange(next); setOpen(false); }
|
|
49
|
-
}}
|
|
111
|
+
}}
|
|
112
|
+
>
|
|
50
113
|
{n.label}{n.children && n.children.length ? <span aria-hidden="true"> ›</span> : null}
|
|
51
114
|
</button>
|
|
52
115
|
</li>
|
|
@@ -2,13 +2,39 @@ import React from "react";
|
|
|
2
2
|
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
3
3
|
import { cx } from "../_utils/cx.js";
|
|
4
4
|
|
|
5
|
+
/** Strip script/style/event-handler markup before injecting HTML (CDS-SEC-001). */
|
|
6
|
+
function sanitizeHtml(html) {
|
|
7
|
+
if (html == null || html === "") return "";
|
|
8
|
+
if (typeof document === "undefined") {
|
|
9
|
+
return String(html)
|
|
10
|
+
.replace(/<script[\s\S]*?>[\s\S]*?<\/script>/gi, "")
|
|
11
|
+
.replace(/on\w+\s*=\s*("[^"]*"|'[^']*'|[^\s>]+)/gi, "")
|
|
12
|
+
.replace(/javascript:/gi, "");
|
|
13
|
+
}
|
|
14
|
+
const tpl = document.createElement("template");
|
|
15
|
+
tpl.innerHTML = String(html);
|
|
16
|
+
tpl.content.querySelectorAll("script,style,iframe,object,embed,link").forEach((n) => n.remove());
|
|
17
|
+
tpl.content.querySelectorAll("*").forEach((n) => {
|
|
18
|
+
[...n.attributes].forEach((a) => {
|
|
19
|
+
if (/^on/i.test(a.name) || /javascript:/i.test(a.value)) n.removeAttribute(a.name);
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
return tpl.innerHTML;
|
|
23
|
+
}
|
|
24
|
+
|
|
5
25
|
/** CyberSkill Editor — light rich-text (contentEditable): bold · italic · bullet list. onChange(html). */
|
|
6
26
|
export function Editor({ defaultValue = "", onChange, minHeight = 120, lang, className }) {
|
|
7
27
|
const box = React.useRef(null);
|
|
28
|
+
const seeded = React.useRef(false);
|
|
8
29
|
const [ref, L] = useLang(lang);
|
|
9
30
|
const t = makeT("Editor", L);
|
|
10
31
|
const cmd = (c) => { document.execCommand(c); box.current && box.current.focus(); emit(); };
|
|
11
32
|
const emit = () => { onChange && box.current && onChange(box.current.innerHTML); };
|
|
33
|
+
React.useLayoutEffect(() => {
|
|
34
|
+
if (!box.current || seeded.current) return;
|
|
35
|
+
box.current.innerHTML = sanitizeHtml(defaultValue);
|
|
36
|
+
seeded.current = true;
|
|
37
|
+
}, [defaultValue]);
|
|
12
38
|
const B = ({ c, label, children }) => (
|
|
13
39
|
<button type="button" className="cs-toolbar__btn" aria-label={label} onMouseDown={(e) => { e.preventDefault(); cmd(c); }}>{children}</button>
|
|
14
40
|
);
|
|
@@ -19,8 +45,17 @@ export function Editor({ defaultValue = "", onChange, minHeight = 120, lang, cla
|
|
|
19
45
|
<B c="italic" label={t("italic")}><i>I</i></B>
|
|
20
46
|
<B c="insertUnorderedList" label={t("list")}>≔</B>
|
|
21
47
|
</div>
|
|
22
|
-
<div
|
|
23
|
-
|
|
48
|
+
<div
|
|
49
|
+
ref={box}
|
|
50
|
+
className="cs-editor__area"
|
|
51
|
+
contentEditable
|
|
52
|
+
suppressContentEditableWarning
|
|
53
|
+
role="textbox"
|
|
54
|
+
aria-multiline="true"
|
|
55
|
+
aria-label={t("area")}
|
|
56
|
+
style={{ minHeight }}
|
|
57
|
+
onInput={emit}
|
|
58
|
+
/>
|
|
24
59
|
</div>
|
|
25
60
|
);
|
|
26
61
|
}
|
|
@@ -2,28 +2,42 @@ import React from "react";
|
|
|
2
2
|
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
3
3
|
import { cx } from "../_utils/cx.js";
|
|
4
4
|
|
|
5
|
-
/** CyberSkill FileUpload — click-or-drag dropzone. Calls onFiles(File[]) on select/drop.
|
|
5
|
+
/** CyberSkill FileUpload — click-or-drag dropzone. Calls onFiles(File[]) on select/drop.
|
|
6
|
+
* Uses a single labeled <input type="file"> (no nested interactive) for axe name/role. */
|
|
6
7
|
export function FileUpload({ title, hint, accept, multiple = false, onFiles, icon, lang, className }) {
|
|
7
8
|
const [drag, setDrag] = React.useState(false);
|
|
9
|
+
const inputRef = React.useRef(null);
|
|
8
10
|
const pick = (files) => { if (files && files.length && onFiles) onFiles(Array.from(files)); };
|
|
9
11
|
const [ref, L] = useLang(lang);
|
|
10
12
|
const t = makeT("FileUpload", L);
|
|
11
13
|
const tt = title != null ? title : t("title");
|
|
12
14
|
const hh = hint != null ? hint : t("hint");
|
|
15
|
+
const id = React.useId();
|
|
13
16
|
return (
|
|
14
|
-
<
|
|
17
|
+
<div
|
|
15
18
|
ref={ref}
|
|
16
19
|
className={cx("cs-dropzone", drag && "is-dragging", className)}
|
|
17
20
|
onDragOver={(e) => { e.preventDefault(); setDrag(true); }}
|
|
18
21
|
onDragLeave={() => setDrag(false)}
|
|
19
22
|
onDrop={(e) => { e.preventDefault(); setDrag(false); pick(e.dataTransfer.files); }}
|
|
23
|
+
onClick={() => inputRef.current && inputRef.current.click()}
|
|
20
24
|
>
|
|
21
|
-
<span className="cs-dropzone__icon">
|
|
22
|
-
{icon ?? <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"
|
|
25
|
+
<span className="cs-dropzone__icon" aria-hidden="true">
|
|
26
|
+
{icon ?? <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round"><path d="M12 16V4M7 9l5-5 5 5" /><path d="M5 16v3a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-3" /></svg>}
|
|
23
27
|
</span>
|
|
24
|
-
<
|
|
25
|
-
<span className="cs-dropzone__hint">{hh}</span>
|
|
26
|
-
<input
|
|
27
|
-
|
|
28
|
+
<label className="cs-dropzone__title" htmlFor={id}>{tt}</label>
|
|
29
|
+
<span className="cs-dropzone__hint" id={id + "-hint"}>{hh}</span>
|
|
30
|
+
<input
|
|
31
|
+
id={id}
|
|
32
|
+
ref={inputRef}
|
|
33
|
+
type="file"
|
|
34
|
+
accept={accept}
|
|
35
|
+
multiple={multiple}
|
|
36
|
+
className="cs-sr-only"
|
|
37
|
+
aria-describedby={id + "-hint"}
|
|
38
|
+
onChange={(e) => pick(e.target.files)}
|
|
39
|
+
onClick={(e) => e.stopPropagation()}
|
|
40
|
+
/>
|
|
41
|
+
</div>
|
|
28
42
|
);
|
|
29
43
|
}
|
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { cx } from "../_utils/cx.js";
|
|
3
3
|
|
|
4
|
+
let warnedNameless = false;
|
|
5
|
+
|
|
4
6
|
/** CyberSkill Switch — accessible toggle (role="switch"); Umber on, Ochre knob-track in dark. */
|
|
5
7
|
// `children` is destructured but never rendered on purpose: keeps stray children out of {...props} → void <input>.
|
|
6
|
-
export function Switch({ label, disabled = false, className, children, ...props }) {
|
|
8
|
+
export function Switch({ label, disabled = false, className, children, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }) {
|
|
9
|
+
const named = !!(label || ariaLabel || ariaLabelledby);
|
|
10
|
+
if (!named && !warnedNameless && typeof console !== "undefined" && console.warn) {
|
|
11
|
+
warnedNameless = true;
|
|
12
|
+
console.warn("CyberSkill Switch: render with label, aria-label, or aria-labelledby.");
|
|
13
|
+
}
|
|
7
14
|
return (
|
|
8
15
|
<label className={cx("cs-switch", disabled && "is-disabled", className)}>
|
|
9
|
-
<input
|
|
16
|
+
<input
|
|
17
|
+
type="checkbox"
|
|
18
|
+
role="switch"
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
aria-label={ariaLabel}
|
|
21
|
+
aria-labelledby={ariaLabelledby}
|
|
22
|
+
{...props}
|
|
23
|
+
/>
|
|
10
24
|
<span className="cs-switch__track" aria-hidden="true" />
|
|
11
25
|
{label ? <span className="cs-switch__label">{label}</span> : null}
|
|
12
26
|
</label>
|
|
@@ -7,8 +7,10 @@ const CLOCK = <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke
|
|
|
7
7
|
export function TimePicker({ value = "09:00", onChange, step = 30, label, disabled = false, lang, className }) {
|
|
8
8
|
const [ref, L] = useLang(lang);
|
|
9
9
|
const t = makeT("TimePicker", L);
|
|
10
|
+
const stepMin = Number(step);
|
|
11
|
+
const safeStep = Number.isFinite(stepMin) && stepMin > 0 ? stepMin : 30;
|
|
10
12
|
const opts = [];
|
|
11
|
-
for (let m = 0; m < 24 * 60; m +=
|
|
13
|
+
for (let m = 0; m < 24 * 60; m += safeStep) {
|
|
12
14
|
opts.push(String(Math.floor(m / 60)).padStart(2, "0") + ":" + String(m % 60).padStart(2, "0"));
|
|
13
15
|
}
|
|
14
16
|
return (
|
package/components/icon/Icon.jsx
CHANGED
|
@@ -49,6 +49,7 @@ export const CS_ICONS = {
|
|
|
49
49
|
copy: { viewBox: "0 0 24 24", els: [["rect", { x: 8, y: 8, width: 12, height: 12, rx: 2 }], ["path", { d: "M4 16V6a2 2 0 0 1 2-2h10" }]] },
|
|
50
50
|
info: { viewBox: "0 0 24 24", els: [["circle", { cx: 12, cy: 12, r: 9 }], ["path", { d: "M12 11v5M12 7.75v.01" }]] },
|
|
51
51
|
"alert-triangle": { viewBox: "0 0 24 24", els: [["path", { d: "M12 3.5l9 16H3z" }], ["path", { d: "M12 10v4M12 17.5v.01" }]] },
|
|
52
|
+
pin: { viewBox: "0 0 24 24", els: [["path", { d: "M12 21v-7" }], ["path", { d: "M8.5 3.5h7l-1.2 6.5h2.7L12 14.5 6.999 10h2.7z" }]] },
|
|
52
53
|
};
|
|
53
54
|
|
|
54
55
|
export function Icon({ name, size = "md", label, className, strokeWidth = 1.75, ...props }) {
|
|
@@ -1,23 +1,38 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
3
3
|
import { cx } from "../_utils/cx.js";
|
|
4
|
+
import { useOverlayLayer } from "../overlays/OverlayManager.jsx";
|
|
4
5
|
|
|
5
6
|
/** CyberSkill CommandPalette — ⌘K overlay with search + grouped actions. Controlled via open/onClose. */
|
|
6
7
|
export function CommandPalette({ open, onClose, placeholder, groups = [], lang, className }) {
|
|
7
8
|
const [q, setQ] = React.useState("");
|
|
8
|
-
React.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
9
|
+
const panel = React.useRef(null);
|
|
10
|
+
const closeRef = React.useRef(onClose);
|
|
11
|
+
closeRef.current = onClose;
|
|
12
|
+
|
|
13
|
+
useOverlayLayer({
|
|
14
|
+
open: !!open,
|
|
15
|
+
kind: "modal",
|
|
16
|
+
trapFocus: true,
|
|
17
|
+
preferFocusSelector: ".cs-cmdk__search input",
|
|
18
|
+
onEscape: () => closeRef.current && closeRef.current(),
|
|
19
|
+
panelRef: panel,
|
|
20
|
+
});
|
|
21
|
+
|
|
14
22
|
const [ref, L] = useLang(lang);
|
|
15
23
|
const t = makeT("CommandPalette", L);
|
|
16
24
|
const ph = placeholder != null ? placeholder : t("placeholder");
|
|
17
25
|
if (!open) return null;
|
|
18
26
|
const needle = q.trim().toLowerCase();
|
|
19
27
|
return (
|
|
20
|
-
<div
|
|
28
|
+
<div
|
|
29
|
+
ref={(el) => {
|
|
30
|
+
ref.current = el;
|
|
31
|
+
panel.current = el;
|
|
32
|
+
}}
|
|
33
|
+
className="cs-cmdk-scrim"
|
|
34
|
+
onClick={onClose}
|
|
35
|
+
>
|
|
21
36
|
<div className={cx("cs-cmdk", className)} role="dialog" aria-modal="true" aria-label={t("aria")} onClick={(e) => e.stopPropagation()}>
|
|
22
37
|
<div className="cs-cmdk__search">
|
|
23
38
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="7" /><path d="M21 21l-4.3-4.3" /></svg>
|