@cyberskill/design 1.2.0 → 1.3.8
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 +4 -3
- package/README.md +20 -19
- package/SKILL.md +5 -5
- package/THIRD-PARTY-NOTICES.md +35 -0
- package/VERSION +1 -1
- package/_ds_bundle.js +701 -1342
- package/_esm/cs.mjs +2 -2
- package/_esm/react.mjs +6 -1
- package/_vendor/NOTICE.md +16 -0
- package/_vendor/babel/README.md +7 -0
- package/_vendor/babel/babel.min.js +4 -0
- package/_vendor/react/README.md +3 -0
- package/_vendor/react/react-dom.production.min.js +1 -0
- package/_vendor/react/react.production.min.js +1 -0
- package/assets/aurora-hoa.webp +0 -0
- package/assets/aurora-kim.webp +0 -0
- package/assets/aurora-moc.webp +0 -0
- package/assets/aurora-tho.webp +0 -0
- package/assets/aurora-thuy.webp +0 -0
- package/base/ai.css +8 -3
- package/base/collections.css +10 -4
- package/base/components.css +5 -2
- package/base/controls.css +58 -21
- package/base/data.css +1 -1
- package/base/feedback.css +9 -2
- package/base/forms.css +1 -1
- package/base/glass.css +1 -1
- package/base/interaction.css +6 -2
- package/base/navigation.css +1 -1
- package/base/reset.css +7 -1
- package/base/responsive.css +13 -12
- package/base/shell.css +8 -2
- package/components/_i18n/i18n.js +28 -3
- package/components/_i18n/strings.js +42 -24
- package/components/ai/ChatMessage.d.ts +2 -0
- package/components/ai/ChatMessage.jsx +8 -4
- package/components/ai/ConfidenceMeter.d.ts +2 -0
- package/components/ai/HumanReviewGate.d.ts +2 -0
- package/components/ai/PromptInput.d.ts +2 -0
- package/components/ai/TypingIndicator.d.ts +2 -0
- package/components/ai/ai-conversation.card.html +3 -3
- package/components/ai/ai-lumi.card.html +3 -3
- package/components/ai/ai.card.html +3 -3
- package/components/brand/brand.card.html +3 -3
- package/components/button/buttons.card.html +3 -3
- package/components/data/Image.jsx +18 -2
- package/components/data/Sortable.d.ts +5 -1
- package/components/data/Sortable.jsx +42 -9
- package/components/data/Sortable.prompt.md +4 -2
- package/components/data/Terminal.jsx +3 -2
- package/components/data/Tooltip.d.ts +2 -0
- package/components/data/Tooltip.jsx +6 -3
- package/components/data/Tree.d.ts +2 -1
- package/components/data/Tree.jsx +137 -16
- package/components/data/Tree.prompt.md +1 -1
- package/components/data/collections.card.html +3 -3
- package/components/data/data.card.html +3 -3
- package/components/datatable/DataTable.d.ts +24 -1
- package/components/datatable/DataTable.jsx +78 -5
- package/components/datatable/DataTable.prompt.md +2 -1
- package/components/datatable/datatable.card.html +20 -16
- package/components/dialog/dialog.card.html +3 -3
- package/components/feedback/Alert.d.ts +2 -0
- package/components/feedback/Alert.jsx +8 -2
- package/components/feedback/EmptyState.d.ts +2 -0
- package/components/feedback/EmptyState.jsx +6 -3
- package/components/feedback/ProgressBar.d.ts +2 -0
- package/components/feedback/ProgressBar.jsx +6 -3
- package/components/feedback/Toast.jsx +15 -1
- package/components/feedback/Toast.prompt.md +2 -1
- package/components/feedback/feedback-2.card.html +3 -3
- package/components/feedback/feedback.card.html +3 -3
- package/components/forms/Cascader.jsx +7 -2
- package/components/forms/FileUpload.d.ts +2 -0
- package/components/forms/Form.d.ts +3 -1
- package/components/forms/Form.jsx +9 -1
- package/components/forms/Form.prompt.md +1 -1
- package/components/forms/Mentions.d.ts +1 -1
- package/components/forms/Mentions.jsx +59 -7
- package/components/forms/Mentions.prompt.md +3 -1
- package/components/forms/Rating.d.ts +3 -2
- package/components/forms/Rating.jsx +34 -3
- package/components/forms/Rating.prompt.md +1 -1
- package/components/forms/Select.d.ts +2 -0
- package/components/forms/Select.jsx +6 -2
- package/components/forms/Slider.d.ts +2 -0
- package/components/forms/Slider.jsx +5 -2
- package/components/forms/Switch.d.ts +2 -0
- package/components/forms/Switch.jsx +7 -9
- package/components/forms/Toggle.d.ts +2 -0
- package/components/forms/Toggle.jsx +14 -2
- package/components/forms/forms-2.card.html +3 -3
- package/components/forms/forms.card.html +3 -3
- package/components/icon/Icon.d.ts +1 -1
- package/components/icon/Icon.prompt.md +1 -1
- package/components/icon/icon.card.html +3 -3
- package/components/logo/logo.card.html +3 -3
- package/components/navigation/CommandPalette.d.ts +2 -0
- package/components/navigation/Menu.d.ts +2 -0
- package/components/navigation/Menu.jsx +60 -6
- package/components/navigation/Steps.d.ts +2 -0
- package/components/navigation/Steps.jsx +6 -3
- package/components/navigation/Tabs.d.ts +2 -0
- package/components/navigation/Tabs.jsx +6 -3
- package/components/navigation/Toolbar.d.ts +3 -2
- package/components/navigation/Toolbar.jsx +117 -11
- package/components/navigation/Toolbar.prompt.md +1 -1
- package/components/navigation/navigation.card.html +3 -3
- package/components/navigation/shell.card.html +3 -3
- package/components/overlays/ContextMenu.d.ts +2 -0
- package/components/overlays/ContextMenu.jsx +6 -3
- package/components/overlays/Drawer.d.ts +2 -0
- package/components/overlays/alertdialog.card.html +3 -3
- package/components/overlays/overlays.card.html +3 -3
- package/components/textfield/textfield.card.html +3 -3
- package/dist/styles.min.css +312 -64
- package/docs/ci-cd.md +18 -13
- package/docs/consumer-grant.md +3 -0
- package/docs/consuming.md +7 -7
- package/docs/contrast-report.md +79 -1
- package/docs/conventions.md +17 -4
- package/docs/decisions.md +40 -2
- package/docs/deploy.md +17 -6
- package/docs/design-styles.md +1 -1
- package/docs/quality-gates.md +19 -11
- package/docs/release-runbook.md +78 -0
- package/docs/sync.md +1 -1
- package/docs/vi/ci-cd.md +16 -13
- package/docs/vi/consumer-grant.md +3 -0
- package/docs/vi/consuming.md +7 -7
- package/docs/vi/contrast-report.md +79 -1
- package/docs/vi/conventions.md +18 -5
- package/docs/vi/decisions.md +39 -2
- package/docs/vi/deploy.md +17 -6
- package/docs/vi/quality-gates.md +18 -11
- package/docs/vi/release-runbook.md +78 -0
- package/docs/vi/sync.md +1 -1
- package/docs/viewer.html +2 -2
- package/fonts/OFL.txt +98 -0
- package/guidelines/atomic-view.html +2 -2
- package/guidelines/brand-imagery.html +1 -1
- package/guidelines/elements-variants.html +5 -5
- package/guidelines/spacing-breakpoints.html +2 -1
- package/guidelines/surfaces-glass.html +1 -1
- package/llms.txt +4 -4
- package/package.json +28 -8
- package/templates/app-shell/AppShell.dc.html +3 -3
- package/templates/app-shell/content-schema.json +7 -0
- package/templates/article/Article.dc.html +3 -3
- package/templates/article/content-schema.json +7 -0
- package/templates/auth/Auth.dc.html +26 -10
- package/templates/auth/content-schema.json +25 -0
- package/templates/dashboard/Dashboard.dc.html +4 -4
- package/templates/dashboard/content-schema.json +7 -0
- package/templates/delivery-change-note/DeliveryChangeNote.dc.html +2 -2
- package/templates/delivery-runbook/DeliveryRunbook.dc.html +2 -2
- package/templates/email-safe/email-announcement.html +2 -2
- package/templates/email-safe/email-dunning.html +2 -2
- package/templates/email-safe/email-investor.html +2 -2
- package/templates/email-safe/email-launch.html +2 -2
- package/templates/email-safe/email-newsletter.html +2 -2
- package/templates/email-safe/email-status.html +2 -2
- package/templates/email-safe/email-transactional.html +2 -2
- package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +2 -2
- package/templates/finance-expense-report/FinanceExpenseReport.dc.html +2 -2
- package/templates/finance-invoice/FinanceInvoice.dc.html +2 -2
- package/templates/finance-quote/FinanceQuote.dc.html +4 -4
- package/templates/hr-interview-kit/HrInterviewKit.dc.html +1 -1
- package/templates/marketing-case-study/MarketingCaseStudy.dc.html +1 -1
- package/templates/marketing-page/MarketingPage.dc.html +3 -3
- package/templates/marketing-page/content-schema.json +1 -0
- package/templates/settings/Settings.dc.html +3 -3
- package/templates/settings/content-schema.json +7 -0
- package/tokens/element-seeds.json +2 -2
- package/tokens/elements.css +193 -19
- package/tokens/native/CSTokens.kt +3 -2
- package/tokens/native/CSTokens.swift +3 -2
- package/tokens/native/cs_tokens.dart +3 -2
- package/tokens/provenance.json +8 -8
- package/tokens/spacing.css +2 -1
- package/tokens/tokens.dtcg.json +19 -14
- package/tokens/tokens.js +16 -15
- package/tokens/tokens.json +15 -14
- package/ui_kits/deck/index.html +1 -1
- package/ui_kits/status-hub/StatusHub.jsx +1 -1
- package/ui_kits/status-hub/login.html +52 -10
- package/ui_kits/status-hub/project.html +1 -1
- package/ui_kits/status-hub/settings.html +1 -1
- package/ui_kits/website/Website.jsx +1 -1
- package/ui_kits/website/careers.html +2 -2
- package/ui_kits/website/chat.html +1 -1
- package/ui_kits/website/site.css +1 -1
- package/ui_kits/website/work.html +2 -2
- package/assets/aurora-hoa.png +0 -0
- package/assets/aurora-kim.png +0 -0
- package/assets/aurora-moc.png +0 -0
- package/assets/aurora-thuy.png +0 -0
- package/docs/decisions/container-query-migration.md +0 -18
- package/docs/decisions/overlay-manager-design.md +0 -101
- package/docs/plans/cyberskill-design-system-audit-and-12-month-evolution-plan.md +0 -1490
- package/docs/status/assets/favicon.svg +0 -5
- package/docs/status/assets/status-legacy.css +0 -399
- package/docs/status/assets/status-legacy.js +0 -505
- package/docs/status/assets/status.css +0 -644
- package/docs/status/assets/status.js +0 -1308
- package/docs/status/data/status-feed.json +0 -1
- package/docs/status/data/task/TASK-REL-001.js +0 -1
- package/docs/status/data/task/TASK-REL-002.js +0 -1
- package/docs/status/index.html +0 -46
- package/docs/status/roadmap.html +0 -4
- package/docs/status/status-legacy.html +0 -122
- package/docs/tasks/BACKLOG.md +0 -26
- package/docs/tasks/_state/commit-links.yaml +0 -14
- package/docs/tasks/release/TASK-REL-001-launch-1-1-0/spec.md +0 -55
- package/docs/tasks/release/TASK-REL-002-republish-1-1-1/spec.md +0 -54
package/_ds_bundle.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* @ds-bundle: {"format":4,"namespace":"CyberSkillDesignSystem_847b3c","components":[{"name":"AIDisclosureBadge","sourcePath":"components/ai/AIDisclosureBadge.jsx"},{"name":"ChatMessage","sourcePath":"components/ai/ChatMessage.jsx"},{"name":"CitationList","sourcePath":"components/ai/CitationList.jsx"},{"name":"ConfidenceMeter","sourcePath":"components/ai/ConfidenceMeter.jsx"},{"name":"HumanReviewGate","sourcePath":"components/ai/HumanReviewGate.jsx"},{"name":"PromptInput","sourcePath":"components/ai/PromptInput.jsx"},{"name":"PromptSuggestions","sourcePath":"components/ai/PromptSuggestions.jsx"},{"name":"TypingIndicator","sourcePath":"components/ai/TypingIndicator.jsx"},{"name":"LumiAvatar","sourcePath":"components/brand/LumiAvatar.jsx"},{"name":"Button","sourcePath":"components/button/Button.jsx"},{"name":"ButtonGroup","sourcePath":"components/button/ButtonGroup.jsx"},{"name":"FloatingActionButton","sourcePath":"components/button/FloatingActionButton.jsx"},{"name":"Accordion","sourcePath":"components/data/Accordion.jsx"},{"name":"AspectRatio","sourcePath":"components/data/AspectRatio.jsx"},{"name":"Avatar","sourcePath":"components/data/Avatar.jsx"},{"name":"AvatarGroup","sourcePath":"components/data/Avatar.jsx"},{"name":"Card","sourcePath":"components/data/Card.jsx"},{"name":"CardHeader","sourcePath":"components/data/Card.jsx"},{"name":"CardBody","sourcePath":"components/data/Card.jsx"},{"name":"CardFooter","sourcePath":"components/data/Card.jsx"},{"name":"Carousel","sourcePath":"components/data/Carousel.jsx"},{"name":"Chart","sourcePath":"components/data/Chart.jsx"},{"name":"CodeBlock","sourcePath":"components/data/CodeBlock.jsx"},{"name":"Collapsible","sourcePath":"components/data/Collapsible.jsx"},{"name":"Comment","sourcePath":"components/data/Comment.jsx"},{"name":"DescriptionList","sourcePath":"components/data/DescriptionList.jsx"},{"name":"Divider","sourcePath":"components/data/Divider.jsx"},{"name":"Image","sourcePath":"components/data/Image.jsx"},{"name":"Item","sourcePath":"components/data/Item.jsx"},{"name":"Kbd","sourcePath":"components/data/Kbd.jsx"},{"name":"List","sourcePath":"components/data/List.jsx"},{"name":"ListItem","sourcePath":"components/data/List.jsx"},{"name":"Masonry","sourcePath":"components/data/Masonry.jsx"},{"name":"QRCode","sourcePath":"components/data/QRCode.jsx"},{"name":"ScrollArea","sourcePath":"components/data/ScrollArea.jsx"},{"name":"Sortable","sourcePath":"components/data/Sortable.jsx"},{"name":"Splitter","sourcePath":"components/data/Splitter.jsx"},{"name":"Stat","sourcePath":"components/data/Stat.jsx"},{"name":"Terminal","sourcePath":"components/data/Terminal.jsx"},{"name":"Timeline","sourcePath":"components/data/Timeline.jsx"},{"name":"Tooltip","sourcePath":"components/data/Tooltip.jsx"},{"name":"Tree","sourcePath":"components/data/Tree.jsx"},{"name":"Watermark","sourcePath":"components/data/Watermark.jsx"},{"name":"DataGrid","sourcePath":"components/datatable/DataGrid.jsx"},{"name":"DataTable","sourcePath":"components/datatable/DataTable.jsx"},{"name":"TreeTable","sourcePath":"components/datatable/TreeTable.jsx"},{"name":"Dialog","sourcePath":"components/dialog/Dialog.jsx"},{"name":"Alert","sourcePath":"components/feedback/Alert.jsx"},{"name":"Badge","sourcePath":"components/feedback/Badge.jsx"},{"name":"EmptyState","sourcePath":"components/feedback/EmptyState.jsx"},{"name":"ProgressBar","sourcePath":"components/feedback/ProgressBar.jsx"},{"name":"Result","sourcePath":"components/feedback/Result.jsx"},{"name":"Skeleton","sourcePath":"components/feedback/Skeleton.jsx"},{"name":"Spinner","sourcePath":"components/feedback/Spinner.jsx"},{"name":"StatusIndicator","sourcePath":"components/feedback/StatusIndicator.jsx"},{"name":"Tag","sourcePath":"components/feedback/Tag.jsx"},{"name":"ToastStack","sourcePath":"components/feedback/Toast.jsx"},{"name":"Toast","sourcePath":"components/feedback/Toast.jsx"},{"name":"Calendar","sourcePath":"components/forms/Calendar.jsx"},{"name":"Cascader","sourcePath":"components/forms/Cascader.jsx"},{"name":"Checkbox","sourcePath":"components/forms/Checkbox.jsx"},{"name":"ColorPicker","sourcePath":"components/forms/ColorPicker.jsx"},{"name":"Combobox","sourcePath":"components/forms/Combobox.jsx"},{"name":"DatePicker","sourcePath":"components/forms/DatePicker.jsx"},{"name":"Editor","sourcePath":"components/forms/Editor.jsx"},{"name":"FileUpload","sourcePath":"components/forms/FileUpload.jsx"},{"name":"Form","sourcePath":"components/forms/Form.jsx"},{"name":"FormField","sourcePath":"components/forms/Form.jsx"},{"name":"FormFieldArray","sourcePath":"components/forms/Form.jsx"},{"name":"FormWizard","sourcePath":"components/forms/Form.jsx"},{"name":"InlineEdit","sourcePath":"components/forms/InlineEdit.jsx"},{"name":"InputGroup","sourcePath":"components/forms/InputGroup.jsx"},{"name":"InputOTP","sourcePath":"components/forms/InputOTP.jsx"},{"name":"Mentions","sourcePath":"components/forms/Mentions.jsx"},{"name":"NativeSelect","sourcePath":"components/forms/NativeSelect.jsx"},{"name":"NumberField","sourcePath":"components/forms/NumberField.jsx"},{"name":"Radio","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"RadioGroup","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"Rating","sourcePath":"components/forms/Rating.jsx"},{"name":"SearchField","sourcePath":"components/forms/SearchField.jsx"},{"name":"SegmentedControl","sourcePath":"components/forms/SegmentedControl.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Slider","sourcePath":"components/forms/Slider.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"TagInput","sourcePath":"components/forms/TagInput.jsx"},{"name":"Textarea","sourcePath":"components/forms/Textarea.jsx"},{"name":"TimePicker","sourcePath":"components/forms/TimePicker.jsx"},{"name":"Toggle","sourcePath":"components/forms/Toggle.jsx"},{"name":"Transfer","sourcePath":"components/forms/Transfer.jsx"},{"name":"TreeSelect","sourcePath":"components/forms/TreeSelect.jsx"},{"name":"CS_ICONS","sourcePath":"components/icon/Icon.jsx"},{"name":"Icon","sourcePath":"components/icon/Icon.jsx"},{"name":"Logo","sourcePath":"components/logo/Logo.jsx"},{"name":"CS_LOGO_VIEWBOX","sourcePath":"components/logo/logo-data.js"},{"name":"CS_LOGO_MARK_INNER","sourcePath":"components/logo/logo-data.js"},{"name":"Anchor","sourcePath":"components/navigation/Anchor.jsx"},{"name":"BackTop","sourcePath":"components/navigation/BackTop.jsx"},{"name":"Breadcrumb","sourcePath":"components/navigation/Breadcrumb.jsx"},{"name":"CommandPalette","sourcePath":"components/navigation/CommandPalette.jsx"},{"name":"Dock","sourcePath":"components/navigation/Dock.jsx"},{"name":"HotKeys","sourcePath":"components/navigation/HotKeys.jsx"},{"name":"Link","sourcePath":"components/navigation/Link.jsx"},{"name":"Menu","sourcePath":"components/navigation/Menu.jsx"},{"name":"MenuItem","sourcePath":"components/navigation/Menu.jsx"},{"name":"Menubar","sourcePath":"components/navigation/Menubar.jsx"},{"name":"NavigationMenu","sourcePath":"components/navigation/NavigationMenu.jsx"},{"name":"Pagination","sourcePath":"components/navigation/Pagination.jsx"},{"name":"Sidebar","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"NavItem","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"Steps","sourcePath":"components/navigation/Steps.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Tab","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Toolbar","sourcePath":"components/navigation/Toolbar.jsx"},{"name":"AlertDialog","sourcePath":"components/overlays/AlertDialog.jsx"},{"name":"ContextMenu","sourcePath":"components/overlays/ContextMenu.jsx"},{"name":"Drawer","sourcePath":"components/overlays/Drawer.jsx"},{"name":"HoverCard","sourcePath":"components/overlays/HoverCard.jsx"},{"name":"OverlayProvider","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"Popconfirm","sourcePath":"components/overlays/Popconfirm.jsx"},{"name":"Popover","sourcePath":"components/overlays/Popover.jsx"},{"name":"Tour","sourcePath":"components/overlays/Tour.jsx"},{"name":"TextField","sourcePath":"components/textfield/TextField.jsx"}],"sourceHashes":{"components/_i18n/i18n.js":"63f973d61113","components/_i18n/strings.js":"cea7ef153e18","components/_utils/cx.js":"ad6a747ea116","components/ai/AIDisclosureBadge.jsx":"4c8954aa2d79","components/ai/ChatMessage.jsx":"84076f3488f2","components/ai/CitationList.jsx":"4d12ef868ff3","components/ai/ConfidenceMeter.jsx":"68cf2bba3a9e","components/ai/HumanReviewGate.jsx":"922b278dca5c","components/ai/PromptInput.jsx":"7634643dafa5","components/ai/PromptSuggestions.jsx":"02d64117ecfa","components/ai/TypingIndicator.jsx":"149e3a0c7ef8","components/brand/LumiAvatar.jsx":"b6444360cf80","components/button/Button.jsx":"6d902a925eed","components/button/ButtonGroup.jsx":"1a1481f6fb93","components/button/FloatingActionButton.jsx":"ce49fe4129a3","components/data/Accordion.jsx":"fed81278cefd","components/data/AspectRatio.jsx":"6c1e8703891e","components/data/Avatar.jsx":"88c8f59334e6","components/data/Card.jsx":"96e1f9ef44a9","components/data/Carousel.jsx":"caf09a766c42","components/data/Chart.jsx":"1953e9fef120","components/data/CodeBlock.jsx":"04e0bd2507e2","components/data/Collapsible.jsx":"3d961fef83f8","components/data/Comment.jsx":"81469a910d80","components/data/DescriptionList.jsx":"ce50d72546bb","components/data/Divider.jsx":"2d08b4a7b18e","components/data/Image.jsx":"da5c7d4c69af","components/data/Item.jsx":"7e1ac68c4e96","components/data/Kbd.jsx":"3b3d867cbdd8","components/data/List.jsx":"5c221b19efe0","components/data/Masonry.jsx":"a65508731d79","components/data/QRCode.jsx":"6acaf577a7fc","components/data/ScrollArea.jsx":"c2e2ec304b1e","components/data/Sortable.jsx":"315b50256128","components/data/Splitter.jsx":"d231064601e9","components/data/Stat.jsx":"35734a801a71","components/data/Terminal.jsx":"3a3cd4477814","components/data/Timeline.jsx":"22058a781a98","components/data/Tooltip.jsx":"4d027aff5559","components/data/Tree.jsx":"9097456de50f","components/data/Watermark.jsx":"66b7b71488d8","components/data/qr-encode.js":"14e8de77df5f","components/datatable/DataGrid.jsx":"c84fe5d5b8cc","components/datatable/DataTable.jsx":"fbfad5570592","components/datatable/TreeTable.jsx":"597c35135ebb","components/dialog/Dialog.jsx":"8ef7722c37c4","components/feedback/Alert.jsx":"0bbe4e5f467a","components/feedback/Badge.jsx":"e691ae1634f8","components/feedback/EmptyState.jsx":"9a9c00523cec","components/feedback/ProgressBar.jsx":"3986118b0da8","components/feedback/Result.jsx":"c168f8dcb009","components/feedback/Skeleton.jsx":"86ee345cb412","components/feedback/Spinner.jsx":"cad2ed4008a6","components/feedback/StatusIndicator.jsx":"d27a5fc3db2a","components/feedback/Tag.jsx":"13d288da9f38","components/feedback/Toast.jsx":"38cc9a82ede9","components/forms/Calendar.jsx":"8a66cbe766af","components/forms/Cascader.jsx":"7b830b11edb1","components/forms/Checkbox.jsx":"2badb7b3b72b","components/forms/ColorPicker.jsx":"d04db0ee7678","components/forms/Combobox.jsx":"3f2fb4d28922","components/forms/DatePicker.jsx":"7a6c18e1e635","components/forms/Editor.jsx":"194484fb9dc4","components/forms/FileUpload.jsx":"509dfaff8bea","components/forms/Form.jsx":"f7952da4b73a","components/forms/InlineEdit.jsx":"5ffb40598769","components/forms/InputGroup.jsx":"06d6c22420d0","components/forms/InputOTP.jsx":"c3a5d02ffebc","components/forms/Mentions.jsx":"04bc4b5a4ace","components/forms/NativeSelect.jsx":"d6f6d300f75d","components/forms/NumberField.jsx":"44975fd25721","components/forms/RadioGroup.jsx":"1a64f7600e01","components/forms/Rating.jsx":"06d345ecc5f4","components/forms/SearchField.jsx":"2a12c65900ec","components/forms/SegmentedControl.jsx":"1f143c62fb56","components/forms/Select.jsx":"1b5ba905b339","components/forms/Slider.jsx":"69aa1a5f1af8","components/forms/Switch.jsx":"ed54f4bceb60","components/forms/TagInput.jsx":"2d1554422097","components/forms/Textarea.jsx":"cf244fee0e37","components/forms/TimePicker.jsx":"abd6e54b2983","components/forms/Toggle.jsx":"338453a34669","components/forms/Transfer.jsx":"dd0c02d1014e","components/forms/TreeSelect.jsx":"a5b1b91d1d6a","components/icon/Icon.jsx":"280fba8477db","components/logo/Logo.jsx":"445c5f5fbd40","components/logo/logo-data.js":"f1a251a50c8f","components/navigation/Anchor.jsx":"491fb330e718","components/navigation/BackTop.jsx":"5cac0081fc20","components/navigation/Breadcrumb.jsx":"45636f3b0a51","components/navigation/CommandPalette.jsx":"51c25b8416eb","components/navigation/Dock.jsx":"3073e2870c65","components/navigation/HotKeys.jsx":"f0f707a3335d","components/navigation/Link.jsx":"d4fe3fcffd2c","components/navigation/Menu.jsx":"6786788f13f8","components/navigation/Menubar.jsx":"1aa64f59891e","components/navigation/NavigationMenu.jsx":"020279fd7f79","components/navigation/Pagination.jsx":"a1fc19bfd104","components/navigation/Sidebar.jsx":"d578b013ad33","components/navigation/Steps.jsx":"85cb20bd45da","components/navigation/Tabs.jsx":"5d5921da1644","components/navigation/Toolbar.jsx":"ba1e0d0578b3","components/overlays/AlertDialog.jsx":"dcfbb7f61506","components/overlays/ContextMenu.jsx":"e7e3386d89ac","components/overlays/Drawer.jsx":"a1c719da3f62","components/overlays/HoverCard.jsx":"5cfabde80960","components/overlays/OverlayManager.jsx":"6b58c4dad9da","components/overlays/Popconfirm.jsx":"55f08dba5f38","components/overlays/Popover.jsx":"1582f7fa5533","components/overlays/Tour.jsx":"707cd7464f0c","components/textfield/TextField.jsx":"c06bd9729f20","image-slot.js":"26886a229370","tokens/tokens.js":"ed883dc757ae","ui_kits/status-hub/StatusHub.jsx":"8674a9fcf8fc","ui_kits/status-hub/data.js":"664bbbb30cb3","ui_kits/website/Website.jsx":"c6a98330480b","ui_kits/website/copy.js":"9d42c52c17cb"},"inlinedExternals":[],"unexposedExports":[{"name":"attachFocusTrap","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"cx","sourcePath":"components/_utils/cx.js"},{"name":"focusableSelector","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"formatCurrency","sourcePath":"components/_i18n/i18n.js"},{"name":"formatDate","sourcePath":"components/_i18n/i18n.js"},{"name":"formatNumber","sourcePath":"components/_i18n/i18n.js"},{"name":"getOverlayManager","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"getPath","sourcePath":"components/forms/Form.jsx"},{"name":"makeT","sourcePath":"components/_i18n/i18n.js"},{"name":"monthName","sourcePath":"components/_i18n/i18n.js"},{"name":"qrMatrix","sourcePath":"components/data/qr-encode.js"},{"name":"resolveLang","sourcePath":"components/_i18n/i18n.js"},{"name":"setPath","sourcePath":"components/forms/Form.jsx"},{"name":"strings","sourcePath":"components/_i18n/strings.js"},{"name":"tokens","sourcePath":"tokens/tokens.js"},{"name":"tr","sourcePath":"components/_i18n/i18n.js"},{"name":"useLang","sourcePath":"components/_i18n/i18n.js"},{"name":"useOverlayLayer","sourcePath":"components/overlays/OverlayManager.jsx"}]} */
|
|
1
|
+
/* @ds-bundle: {"format":4,"namespace":"CyberSkillDesignSystem_847b3c","components":[{"name":"AIDisclosureBadge","sourcePath":"components/ai/AIDisclosureBadge.jsx"},{"name":"ChatMessage","sourcePath":"components/ai/ChatMessage.jsx"},{"name":"CitationList","sourcePath":"components/ai/CitationList.jsx"},{"name":"ConfidenceMeter","sourcePath":"components/ai/ConfidenceMeter.jsx"},{"name":"HumanReviewGate","sourcePath":"components/ai/HumanReviewGate.jsx"},{"name":"PromptInput","sourcePath":"components/ai/PromptInput.jsx"},{"name":"PromptSuggestions","sourcePath":"components/ai/PromptSuggestions.jsx"},{"name":"TypingIndicator","sourcePath":"components/ai/TypingIndicator.jsx"},{"name":"LumiAvatar","sourcePath":"components/brand/LumiAvatar.jsx"},{"name":"Button","sourcePath":"components/button/Button.jsx"},{"name":"ButtonGroup","sourcePath":"components/button/ButtonGroup.jsx"},{"name":"FloatingActionButton","sourcePath":"components/button/FloatingActionButton.jsx"},{"name":"Accordion","sourcePath":"components/data/Accordion.jsx"},{"name":"AspectRatio","sourcePath":"components/data/AspectRatio.jsx"},{"name":"Avatar","sourcePath":"components/data/Avatar.jsx"},{"name":"AvatarGroup","sourcePath":"components/data/Avatar.jsx"},{"name":"Card","sourcePath":"components/data/Card.jsx"},{"name":"CardHeader","sourcePath":"components/data/Card.jsx"},{"name":"CardBody","sourcePath":"components/data/Card.jsx"},{"name":"CardFooter","sourcePath":"components/data/Card.jsx"},{"name":"Carousel","sourcePath":"components/data/Carousel.jsx"},{"name":"Chart","sourcePath":"components/data/Chart.jsx"},{"name":"CodeBlock","sourcePath":"components/data/CodeBlock.jsx"},{"name":"Collapsible","sourcePath":"components/data/Collapsible.jsx"},{"name":"Comment","sourcePath":"components/data/Comment.jsx"},{"name":"DescriptionList","sourcePath":"components/data/DescriptionList.jsx"},{"name":"Divider","sourcePath":"components/data/Divider.jsx"},{"name":"Image","sourcePath":"components/data/Image.jsx"},{"name":"Item","sourcePath":"components/data/Item.jsx"},{"name":"Kbd","sourcePath":"components/data/Kbd.jsx"},{"name":"List","sourcePath":"components/data/List.jsx"},{"name":"ListItem","sourcePath":"components/data/List.jsx"},{"name":"Masonry","sourcePath":"components/data/Masonry.jsx"},{"name":"QRCode","sourcePath":"components/data/QRCode.jsx"},{"name":"ScrollArea","sourcePath":"components/data/ScrollArea.jsx"},{"name":"Sortable","sourcePath":"components/data/Sortable.jsx"},{"name":"Splitter","sourcePath":"components/data/Splitter.jsx"},{"name":"Stat","sourcePath":"components/data/Stat.jsx"},{"name":"Terminal","sourcePath":"components/data/Terminal.jsx"},{"name":"Timeline","sourcePath":"components/data/Timeline.jsx"},{"name":"Tooltip","sourcePath":"components/data/Tooltip.jsx"},{"name":"Tree","sourcePath":"components/data/Tree.jsx"},{"name":"Watermark","sourcePath":"components/data/Watermark.jsx"},{"name":"DataGrid","sourcePath":"components/datatable/DataGrid.jsx"},{"name":"DataTable","sourcePath":"components/datatable/DataTable.jsx"},{"name":"TreeTable","sourcePath":"components/datatable/TreeTable.jsx"},{"name":"Dialog","sourcePath":"components/dialog/Dialog.jsx"},{"name":"Alert","sourcePath":"components/feedback/Alert.jsx"},{"name":"Badge","sourcePath":"components/feedback/Badge.jsx"},{"name":"EmptyState","sourcePath":"components/feedback/EmptyState.jsx"},{"name":"ProgressBar","sourcePath":"components/feedback/ProgressBar.jsx"},{"name":"Result","sourcePath":"components/feedback/Result.jsx"},{"name":"Skeleton","sourcePath":"components/feedback/Skeleton.jsx"},{"name":"Spinner","sourcePath":"components/feedback/Spinner.jsx"},{"name":"StatusIndicator","sourcePath":"components/feedback/StatusIndicator.jsx"},{"name":"Tag","sourcePath":"components/feedback/Tag.jsx"},{"name":"ToastStack","sourcePath":"components/feedback/Toast.jsx"},{"name":"Toast","sourcePath":"components/feedback/Toast.jsx"},{"name":"Calendar","sourcePath":"components/forms/Calendar.jsx"},{"name":"Cascader","sourcePath":"components/forms/Cascader.jsx"},{"name":"Checkbox","sourcePath":"components/forms/Checkbox.jsx"},{"name":"ColorPicker","sourcePath":"components/forms/ColorPicker.jsx"},{"name":"Combobox","sourcePath":"components/forms/Combobox.jsx"},{"name":"DatePicker","sourcePath":"components/forms/DatePicker.jsx"},{"name":"Editor","sourcePath":"components/forms/Editor.jsx"},{"name":"FileUpload","sourcePath":"components/forms/FileUpload.jsx"},{"name":"Form","sourcePath":"components/forms/Form.jsx"},{"name":"FormField","sourcePath":"components/forms/Form.jsx"},{"name":"FormFieldArray","sourcePath":"components/forms/Form.jsx"},{"name":"FormWizard","sourcePath":"components/forms/Form.jsx"},{"name":"InlineEdit","sourcePath":"components/forms/InlineEdit.jsx"},{"name":"InputGroup","sourcePath":"components/forms/InputGroup.jsx"},{"name":"InputOTP","sourcePath":"components/forms/InputOTP.jsx"},{"name":"Mentions","sourcePath":"components/forms/Mentions.jsx"},{"name":"NativeSelect","sourcePath":"components/forms/NativeSelect.jsx"},{"name":"NumberField","sourcePath":"components/forms/NumberField.jsx"},{"name":"Radio","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"RadioGroup","sourcePath":"components/forms/RadioGroup.jsx"},{"name":"Rating","sourcePath":"components/forms/Rating.jsx"},{"name":"SearchField","sourcePath":"components/forms/SearchField.jsx"},{"name":"SegmentedControl","sourcePath":"components/forms/SegmentedControl.jsx"},{"name":"Select","sourcePath":"components/forms/Select.jsx"},{"name":"Slider","sourcePath":"components/forms/Slider.jsx"},{"name":"Switch","sourcePath":"components/forms/Switch.jsx"},{"name":"TagInput","sourcePath":"components/forms/TagInput.jsx"},{"name":"Textarea","sourcePath":"components/forms/Textarea.jsx"},{"name":"TimePicker","sourcePath":"components/forms/TimePicker.jsx"},{"name":"Toggle","sourcePath":"components/forms/Toggle.jsx"},{"name":"Transfer","sourcePath":"components/forms/Transfer.jsx"},{"name":"TreeSelect","sourcePath":"components/forms/TreeSelect.jsx"},{"name":"CS_ICONS","sourcePath":"components/icon/Icon.jsx"},{"name":"Icon","sourcePath":"components/icon/Icon.jsx"},{"name":"Logo","sourcePath":"components/logo/Logo.jsx"},{"name":"CS_LOGO_VIEWBOX","sourcePath":"components/logo/logo-data.js"},{"name":"CS_LOGO_MARK_INNER","sourcePath":"components/logo/logo-data.js"},{"name":"Anchor","sourcePath":"components/navigation/Anchor.jsx"},{"name":"BackTop","sourcePath":"components/navigation/BackTop.jsx"},{"name":"Breadcrumb","sourcePath":"components/navigation/Breadcrumb.jsx"},{"name":"CommandPalette","sourcePath":"components/navigation/CommandPalette.jsx"},{"name":"Dock","sourcePath":"components/navigation/Dock.jsx"},{"name":"HotKeys","sourcePath":"components/navigation/HotKeys.jsx"},{"name":"Link","sourcePath":"components/navigation/Link.jsx"},{"name":"Menu","sourcePath":"components/navigation/Menu.jsx"},{"name":"MenuItem","sourcePath":"components/navigation/Menu.jsx"},{"name":"Menubar","sourcePath":"components/navigation/Menubar.jsx"},{"name":"NavigationMenu","sourcePath":"components/navigation/NavigationMenu.jsx"},{"name":"Pagination","sourcePath":"components/navigation/Pagination.jsx"},{"name":"Sidebar","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"NavItem","sourcePath":"components/navigation/Sidebar.jsx"},{"name":"Steps","sourcePath":"components/navigation/Steps.jsx"},{"name":"Tabs","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Tab","sourcePath":"components/navigation/Tabs.jsx"},{"name":"Toolbar","sourcePath":"components/navigation/Toolbar.jsx"},{"name":"AlertDialog","sourcePath":"components/overlays/AlertDialog.jsx"},{"name":"ContextMenu","sourcePath":"components/overlays/ContextMenu.jsx"},{"name":"Drawer","sourcePath":"components/overlays/Drawer.jsx"},{"name":"HoverCard","sourcePath":"components/overlays/HoverCard.jsx"},{"name":"OverlayProvider","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"Popconfirm","sourcePath":"components/overlays/Popconfirm.jsx"},{"name":"Popover","sourcePath":"components/overlays/Popover.jsx"},{"name":"Tour","sourcePath":"components/overlays/Tour.jsx"},{"name":"TextField","sourcePath":"components/textfield/TextField.jsx"}],"sourceHashes":{"components/_i18n/i18n.js":"c8fc9f74a4c0","components/_i18n/strings.js":"0e63dc7515d8","components/_utils/cx.js":"ad6a747ea116","components/ai/AIDisclosureBadge.jsx":"4c8954aa2d79","components/ai/ChatMessage.jsx":"344e9c13fcfe","components/ai/CitationList.jsx":"4d12ef868ff3","components/ai/ConfidenceMeter.jsx":"68cf2bba3a9e","components/ai/HumanReviewGate.jsx":"922b278dca5c","components/ai/PromptInput.jsx":"7634643dafa5","components/ai/PromptSuggestions.jsx":"02d64117ecfa","components/ai/TypingIndicator.jsx":"149e3a0c7ef8","components/brand/LumiAvatar.jsx":"b6444360cf80","components/button/Button.jsx":"6d902a925eed","components/button/ButtonGroup.jsx":"1a1481f6fb93","components/button/FloatingActionButton.jsx":"ce49fe4129a3","components/data/Accordion.jsx":"fed81278cefd","components/data/AspectRatio.jsx":"6c1e8703891e","components/data/Avatar.jsx":"88c8f59334e6","components/data/Card.jsx":"96e1f9ef44a9","components/data/Carousel.jsx":"caf09a766c42","components/data/Chart.jsx":"1953e9fef120","components/data/CodeBlock.jsx":"04e0bd2507e2","components/data/Collapsible.jsx":"3d961fef83f8","components/data/Comment.jsx":"81469a910d80","components/data/DescriptionList.jsx":"ce50d72546bb","components/data/Divider.jsx":"2d08b4a7b18e","components/data/Image.jsx":"0ea06cfe1532","components/data/Item.jsx":"7e1ac68c4e96","components/data/Kbd.jsx":"3b3d867cbdd8","components/data/List.jsx":"5c221b19efe0","components/data/Masonry.jsx":"a65508731d79","components/data/QRCode.jsx":"6acaf577a7fc","components/data/ScrollArea.jsx":"c2e2ec304b1e","components/data/Sortable.jsx":"ed7615a9469a","components/data/Splitter.jsx":"d231064601e9","components/data/Stat.jsx":"35734a801a71","components/data/Terminal.jsx":"9d419686bea8","components/data/Timeline.jsx":"22058a781a98","components/data/Tooltip.jsx":"3f30c6b1bf1d","components/data/Tree.jsx":"a1d33dec0451","components/data/Watermark.jsx":"66b7b71488d8","components/data/qr-encode.js":"14e8de77df5f","components/datatable/DataGrid.jsx":"c84fe5d5b8cc","components/datatable/DataTable.jsx":"2a7c3bda5142","components/datatable/TreeTable.jsx":"597c35135ebb","components/dialog/Dialog.jsx":"8ef7722c37c4","components/feedback/Alert.jsx":"4dbf685af3e1","components/feedback/Badge.jsx":"e691ae1634f8","components/feedback/EmptyState.jsx":"5ba1437e6d81","components/feedback/ProgressBar.jsx":"c63a431e9eed","components/feedback/Result.jsx":"c168f8dcb009","components/feedback/Skeleton.jsx":"86ee345cb412","components/feedback/Spinner.jsx":"cad2ed4008a6","components/feedback/StatusIndicator.jsx":"d27a5fc3db2a","components/feedback/Tag.jsx":"13d288da9f38","components/feedback/Toast.jsx":"6a7832751669","components/forms/Calendar.jsx":"8a66cbe766af","components/forms/Cascader.jsx":"63f35db7101d","components/forms/Checkbox.jsx":"2badb7b3b72b","components/forms/ColorPicker.jsx":"d04db0ee7678","components/forms/Combobox.jsx":"3f2fb4d28922","components/forms/DatePicker.jsx":"7a6c18e1e635","components/forms/Editor.jsx":"194484fb9dc4","components/forms/FileUpload.jsx":"509dfaff8bea","components/forms/Form.jsx":"6945025a205f","components/forms/InlineEdit.jsx":"5ffb40598769","components/forms/InputGroup.jsx":"06d6c22420d0","components/forms/InputOTP.jsx":"c3a5d02ffebc","components/forms/Mentions.jsx":"3924619137f3","components/forms/NativeSelect.jsx":"d6f6d300f75d","components/forms/NumberField.jsx":"44975fd25721","components/forms/RadioGroup.jsx":"1a64f7600e01","components/forms/Rating.jsx":"bd5c94741d63","components/forms/SearchField.jsx":"2a12c65900ec","components/forms/SegmentedControl.jsx":"1f143c62fb56","components/forms/Select.jsx":"009495d18845","components/forms/Slider.jsx":"213b7b9edec9","components/forms/Switch.jsx":"3cdf185f757c","components/forms/TagInput.jsx":"2d1554422097","components/forms/Textarea.jsx":"cf244fee0e37","components/forms/TimePicker.jsx":"abd6e54b2983","components/forms/Toggle.jsx":"a5378fd66713","components/forms/Transfer.jsx":"dd0c02d1014e","components/forms/TreeSelect.jsx":"a5b1b91d1d6a","components/icon/Icon.jsx":"280fba8477db","components/logo/Logo.jsx":"445c5f5fbd40","components/logo/logo-data.js":"f1a251a50c8f","components/navigation/Anchor.jsx":"491fb330e718","components/navigation/BackTop.jsx":"5cac0081fc20","components/navigation/Breadcrumb.jsx":"45636f3b0a51","components/navigation/CommandPalette.jsx":"51c25b8416eb","components/navigation/Dock.jsx":"3073e2870c65","components/navigation/HotKeys.jsx":"f0f707a3335d","components/navigation/Link.jsx":"d4fe3fcffd2c","components/navigation/Menu.jsx":"999e1659f8b3","components/navigation/Menubar.jsx":"1aa64f59891e","components/navigation/NavigationMenu.jsx":"020279fd7f79","components/navigation/Pagination.jsx":"a1fc19bfd104","components/navigation/Sidebar.jsx":"d578b013ad33","components/navigation/Steps.jsx":"e26c4a18cdfc","components/navigation/Tabs.jsx":"16964d61f349","components/navigation/Toolbar.jsx":"0f1f25d9e0c5","components/overlays/AlertDialog.jsx":"dcfbb7f61506","components/overlays/ContextMenu.jsx":"e3fb63e7b74f","components/overlays/Drawer.jsx":"a1c719da3f62","components/overlays/HoverCard.jsx":"5cfabde80960","components/overlays/OverlayManager.jsx":"6b58c4dad9da","components/overlays/Popconfirm.jsx":"55f08dba5f38","components/overlays/Popover.jsx":"1582f7fa5533","components/overlays/Tour.jsx":"707cd7464f0c","components/textfield/TextField.jsx":"c06bd9729f20","tokens/tokens.js":"aea97a95111a"},"inlinedExternals":[],"unexposedExports":[{"name":"attachFocusTrap","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"cx","sourcePath":"components/_utils/cx.js"},{"name":"focusableSelector","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"formatCurrency","sourcePath":"components/_i18n/i18n.js"},{"name":"formatDate","sourcePath":"components/_i18n/i18n.js"},{"name":"formatNumber","sourcePath":"components/_i18n/i18n.js"},{"name":"getOverlayManager","sourcePath":"components/overlays/OverlayManager.jsx"},{"name":"getPath","sourcePath":"components/forms/Form.jsx"},{"name":"makeT","sourcePath":"components/_i18n/i18n.js"},{"name":"monthName","sourcePath":"components/_i18n/i18n.js"},{"name":"qrMatrix","sourcePath":"components/data/qr-encode.js"},{"name":"reorderItems","sourcePath":"components/data/Sortable.jsx"},{"name":"resolveLang","sourcePath":"components/_i18n/i18n.js"},{"name":"setPath","sourcePath":"components/forms/Form.jsx"},{"name":"strings","sourcePath":"components/_i18n/strings.js"},{"name":"tokens","sourcePath":"tokens/tokens.js"},{"name":"tr","sourcePath":"components/_i18n/i18n.js"},{"name":"useLang","sourcePath":"components/_i18n/i18n.js"},{"name":"useOverlayLayer","sourcePath":"components/overlays/OverlayManager.jsx"}]} */
|
|
2
2
|
|
|
3
3
|
(() => {
|
|
4
4
|
|
|
@@ -20,8 +20,8 @@ const strings = {
|
|
|
20
20
|
vi: { label: "Đường dẫn" }
|
|
21
21
|
},
|
|
22
22
|
CommandPalette: {
|
|
23
|
-
en: { placeholder: "Type a command or search…", empty: "No results
|
|
24
|
-
vi: { placeholder: "Nhập lệnh hoặc tìm kiếm…", empty: "Không
|
|
23
|
+
en: { placeholder: "Type a command or search…", empty: "No results", aria: "Command palette", esc: "Esc" },
|
|
24
|
+
vi: { placeholder: "Nhập lệnh hoặc tìm kiếm…", empty: "Không có kết quả", aria: "Bảng lệnh", esc: "Esc" }
|
|
25
25
|
},
|
|
26
26
|
SearchField: {
|
|
27
27
|
en: { placeholder: "Search…", clear: "Clear search" },
|
|
@@ -44,12 +44,22 @@ const strings = {
|
|
|
44
44
|
vi: { confirm: "Xác nhận", cancel: "Hủy" }
|
|
45
45
|
},
|
|
46
46
|
Drawer: {
|
|
47
|
-
en: { close: "Close", panel: "
|
|
48
|
-
vi: { close: "Đóng", panel: "Bảng" }
|
|
47
|
+
en: { close: "Close", panel: "Side panel" },
|
|
48
|
+
vi: { close: "Đóng", panel: "Bảng bên" }
|
|
49
49
|
},
|
|
50
50
|
DataTable: {
|
|
51
|
-
en: {
|
|
52
|
-
|
|
51
|
+
en: {
|
|
52
|
+
empty: "No results",
|
|
53
|
+
loading: "Loading table",
|
|
54
|
+
error: "We couldn't load this table",
|
|
55
|
+
errorHint: "Check your connection, then try again."
|
|
56
|
+
},
|
|
57
|
+
vi: {
|
|
58
|
+
empty: "Không có kết quả",
|
|
59
|
+
loading: "Đang tải bảng",
|
|
60
|
+
error: "Không tải được bảng này",
|
|
61
|
+
errorHint: "Kiểm tra kết nối, rồi thử lại."
|
|
62
|
+
}
|
|
53
63
|
},
|
|
54
64
|
ConfidenceMeter: {
|
|
55
65
|
en: { label: "Confidence", low: "Low", medium: "Medium", high: "High" },
|
|
@@ -61,10 +71,10 @@ const strings = {
|
|
|
61
71
|
},
|
|
62
72
|
Tag: {
|
|
63
73
|
en: { remove: "Remove" },
|
|
64
|
-
vi: { remove: "Gỡ
|
|
74
|
+
vi: { remove: "Gỡ" }
|
|
65
75
|
},
|
|
66
76
|
Toast: {
|
|
67
|
-
en: { notifications: "Notifications", dismiss: "
|
|
77
|
+
en: { notifications: "Notifications", dismiss: "Close" },
|
|
68
78
|
vi: { notifications: "Thông báo", dismiss: "Đóng" }
|
|
69
79
|
},
|
|
70
80
|
TypingIndicator: {
|
|
@@ -76,12 +86,12 @@ const strings = {
|
|
|
76
86
|
vi: { toolbar: "Định dạng", bold: "Đậm", italic: "Nghiêng", list: "Danh sách", area: "Văn bản" }
|
|
77
87
|
},
|
|
78
88
|
Terminal: {
|
|
79
|
-
en: { input: "Terminal input" },
|
|
80
|
-
vi: { input: "Ô nhập lệnh" }
|
|
89
|
+
en: { input: "Terminal input", title: "cyberskill — zsh" },
|
|
90
|
+
vi: { input: "Ô nhập lệnh", title: "cyberskill — zsh" }
|
|
81
91
|
},
|
|
82
92
|
DataGrid: {
|
|
83
|
-
en: { empty: "No
|
|
84
|
-
vi: { empty: "Không có
|
|
93
|
+
en: { empty: "No results", selectAll: "Select all rows", selectRow: "Select row", pin: "Pin this column", pinNamed: "Pin column {name}" },
|
|
94
|
+
vi: { empty: "Không có kết quả", selectAll: "Chọn tất cả", selectRow: "Chọn dòng", pin: "Ghim cột này", pinNamed: "Ghim cột {name}" }
|
|
85
95
|
},
|
|
86
96
|
Splitter: {
|
|
87
97
|
en: { label: "Resize panes" },
|
|
@@ -115,9 +125,13 @@ const strings = {
|
|
|
115
125
|
en: { placeholder: "Add a note — @ to mention" },
|
|
116
126
|
vi: { placeholder: "Thêm ghi chú — gõ @ để nhắc tên" }
|
|
117
127
|
},
|
|
128
|
+
Sortable: {
|
|
129
|
+
en: { moveUp: "Move up", moveDown: "Move down" },
|
|
130
|
+
vi: { moveUp: "Di chuyển lên", moveDown: "Di chuyển xuống" }
|
|
131
|
+
},
|
|
118
132
|
Form: {
|
|
119
|
-
en: { summary: "Please fix the following:", required: "required", requiredField: "
|
|
120
|
-
vi: { summary: "Vui lòng sửa các mục sau:", required: "bắt buộc", requiredField: "
|
|
133
|
+
en: { summary: "Please fix the following:", required: "required", requiredField: "Enter a value" },
|
|
134
|
+
vi: { summary: "Vui lòng sửa các mục sau:", required: "bắt buộc", requiredField: "Vui lòng nhập giá trị" }
|
|
121
135
|
},
|
|
122
136
|
BackTop: {
|
|
123
137
|
en: { label: "Back to top" },
|
|
@@ -125,7 +139,7 @@ const strings = {
|
|
|
125
139
|
},
|
|
126
140
|
Carousel: {
|
|
127
141
|
en: { prev: "Previous slide", next: "Next slide", slide: "Slide" },
|
|
128
|
-
vi: { prev: "
|
|
142
|
+
vi: { prev: "Ảnh trước", next: "Ảnh sau", slide: "Ảnh" }
|
|
129
143
|
},
|
|
130
144
|
Comment: {
|
|
131
145
|
en: { reply: "Reply" },
|
|
@@ -140,12 +154,12 @@ const strings = {
|
|
|
140
154
|
vi: { more: "Thao tác khác" }
|
|
141
155
|
},
|
|
142
156
|
Popconfirm: {
|
|
143
|
-
en: { ok: "
|
|
144
|
-
vi: { ok: "
|
|
157
|
+
en: { ok: "Confirm", cancel: "Cancel" },
|
|
158
|
+
vi: { ok: "Xác nhận", cancel: "Hủy" }
|
|
145
159
|
},
|
|
146
160
|
Result: {
|
|
147
|
-
en: { success: "Done", error: "
|
|
148
|
-
vi: { success: "Hoàn tất", error: "
|
|
161
|
+
en: { success: "Done", error: "We couldn't complete that", warning: "Please check", info: "Here's what happened" },
|
|
162
|
+
vi: { success: "Hoàn tất", error: "Chúng tôi chưa hoàn tất được", warning: "Vui lòng kiểm tra", info: "Đây là điều đã xảy ra" }
|
|
149
163
|
},
|
|
150
164
|
InlineEdit: {
|
|
151
165
|
en: { edit: "Edit", empty: "Add text" },
|
|
@@ -164,8 +178,8 @@ const strings = {
|
|
|
164
178
|
vi: { label: "Giờ" }
|
|
165
179
|
},
|
|
166
180
|
Combobox: {
|
|
167
|
-
en: { placeholder: "Select or type…", empty: "No
|
|
168
|
-
vi: { placeholder: "Chọn hoặc nhập…", empty: "Không có kết quả
|
|
181
|
+
en: { placeholder: "Select or type…", empty: "No results" },
|
|
182
|
+
vi: { placeholder: "Chọn hoặc nhập…", empty: "Không có kết quả" }
|
|
169
183
|
},
|
|
170
184
|
InputGroup: {
|
|
171
185
|
en: { clear: "Clear", show: "Show password", hide: "Hide password", input: "Input" },
|
|
@@ -200,11 +214,11 @@ const strings = {
|
|
|
200
214
|
vi: { list: "Tab" }
|
|
201
215
|
},
|
|
202
216
|
EmptyState: {
|
|
203
|
-
en: { title: "Nothing here yet", action: "
|
|
204
|
-
vi: { title: "Chưa có gì ở đây", action: "
|
|
217
|
+
en: { title: "Nothing here yet", action: "Create your first item" },
|
|
218
|
+
vi: { title: "Chưa có gì ở đây", action: "Tạo mục đầu tiên" }
|
|
205
219
|
},
|
|
206
220
|
Alert: {
|
|
207
|
-
en: { dismiss: "
|
|
221
|
+
en: { dismiss: "Close" },
|
|
208
222
|
vi: { dismiss: "Đóng" }
|
|
209
223
|
},
|
|
210
224
|
Select: {
|
|
@@ -238,6 +252,10 @@ const strings = {
|
|
|
238
252
|
ProgressBar: {
|
|
239
253
|
en: { label: "Progress" },
|
|
240
254
|
vi: { label: "Tiến độ" }
|
|
255
|
+
},
|
|
256
|
+
ChatMessage: {
|
|
257
|
+
en: { you: "You" },
|
|
258
|
+
vi: { you: "Bạn" }
|
|
241
259
|
}
|
|
242
260
|
};
|
|
243
261
|
Object.assign(__ds_scope, { strings });
|
|
@@ -300,8 +318,21 @@ function formatNumber(n, lang) {
|
|
|
300
318
|
if (n == null || isNaN(n)) return "";
|
|
301
319
|
return new Intl.NumberFormat(lang === "vi" ? "vi-VN" : "en-US").format(n);
|
|
302
320
|
}
|
|
303
|
-
function formatCurrency(n,
|
|
321
|
+
function formatCurrency(n, langOrOpts) {
|
|
304
322
|
if (n == null || isNaN(n)) return "";
|
|
323
|
+
const opts = langOrOpts && typeof langOrOpts === "object" ? langOrOpts : null;
|
|
324
|
+
if (opts && opts.currency) {
|
|
325
|
+
let locale = opts.locale;
|
|
326
|
+
if (!locale) {
|
|
327
|
+
locale = opts.lang === "vi" ? "vi-VN" : "en-US";
|
|
328
|
+
}
|
|
329
|
+
return new Intl.NumberFormat(locale, {
|
|
330
|
+
style: "currency",
|
|
331
|
+
currency: String(opts.currency).toUpperCase(),
|
|
332
|
+
currencyDisplay: "symbol"
|
|
333
|
+
}).format(Number(n));
|
|
334
|
+
}
|
|
335
|
+
const lang = typeof langOrOpts === "string" ? langOrOpts : opts && opts.lang || "en";
|
|
305
336
|
return lang === "vi" ? new Intl.NumberFormat("vi-VN").format(n) + " ₫" : "$" + new Intl.NumberFormat("en-US").format(n);
|
|
306
337
|
}
|
|
307
338
|
Object.assign(__ds_scope, { resolveLang, tr, makeT, useLang, formatDate, monthName, formatNumber, formatCurrency });
|
|
@@ -344,11 +375,15 @@ Object.assign(__ds_scope, { AIDisclosureBadge });
|
|
|
344
375
|
|
|
345
376
|
// components/ai/ChatMessage.jsx
|
|
346
377
|
try { (() => {
|
|
378
|
+
const { makeT, useLang } = __ds_scope;
|
|
347
379
|
const { cx } = __ds_scope;
|
|
348
|
-
function ChatMessage({ role = "lumi", name, avatar,
|
|
380
|
+
function ChatMessage({ role = "lumi", name, avatar, lang, className, children }) {
|
|
349
381
|
const isUser = role === "user";
|
|
350
|
-
const
|
|
351
|
-
|
|
382
|
+
const [ref, L] = useLang(lang);
|
|
383
|
+
const t = makeT("ChatMessage", L);
|
|
384
|
+
const you = t("you");
|
|
385
|
+
const defaultAvatar = isUser ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, you) : /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z" }));
|
|
386
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-chat-msg", isUser ? "cs-chat-msg--user" : "cs-chat-msg--lumi", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__avatar" }, avatar ?? defaultAvatar), /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__col" }, /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__name" }, name ?? (isUser ? you : "Lumi")), /* @__PURE__ */ React.createElement("div", { className: "cs-chat-msg__bubble" }, children)));
|
|
352
387
|
}
|
|
353
388
|
Object.assign(__ds_scope, { ChatMessage });
|
|
354
389
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/ai/ChatMessage.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -881,10 +916,23 @@ Object.assign(__ds_scope, { ScrollArea });
|
|
|
881
916
|
|
|
882
917
|
// components/data/Sortable.jsx
|
|
883
918
|
try { (() => {
|
|
919
|
+
const { makeT, useLang } = __ds_scope;
|
|
884
920
|
const { cx } = __ds_scope;
|
|
885
|
-
function
|
|
921
|
+
function reorderItems(items, from, to) {
|
|
922
|
+
if (from < 0 || to < 0 || from >= items.length || to >= items.length || from === to) return items;
|
|
923
|
+
const next = [...items];
|
|
924
|
+
next.splice(to, 0, next.splice(from, 1)[0]);
|
|
925
|
+
return next;
|
|
926
|
+
}
|
|
927
|
+
function Sortable({ items = [], onChange, lang, className }) {
|
|
886
928
|
const [dragKey, setDragKey] = React.useState(null);
|
|
887
929
|
const [over, setOver] = React.useState(null);
|
|
930
|
+
const [ref, L] = useLang(lang);
|
|
931
|
+
const t = makeT("Sortable", L);
|
|
932
|
+
const move = (from, to) => {
|
|
933
|
+
const next = reorderItems(items, from, to);
|
|
934
|
+
if (next !== items) onChange && onChange(next);
|
|
935
|
+
};
|
|
888
936
|
const drop = () => {
|
|
889
937
|
if (dragKey == null || over == null || dragKey === over) {
|
|
890
938
|
setDragKey(null);
|
|
@@ -893,19 +941,23 @@ function Sortable({ items = [], onChange, className }) {
|
|
|
893
941
|
}
|
|
894
942
|
const from = items.findIndex((i) => i.key === dragKey);
|
|
895
943
|
const to = items.findIndex((i) => i.key === over);
|
|
896
|
-
|
|
897
|
-
next.splice(to, 0, next.splice(from, 1)[0]);
|
|
898
|
-
onChange && onChange(next);
|
|
944
|
+
move(from, to);
|
|
899
945
|
setDragKey(null);
|
|
900
946
|
setOver(null);
|
|
901
947
|
};
|
|
902
|
-
return /* @__PURE__ */ React.createElement("ul", { className: cx("cs-sortable", className) }, items.map((it) => /* @__PURE__ */ React.createElement(
|
|
948
|
+
return /* @__PURE__ */ React.createElement("ul", { ref, className: cx("cs-sortable", className) }, items.map((it, idx) => /* @__PURE__ */ React.createElement(
|
|
903
949
|
"li",
|
|
904
950
|
{
|
|
905
951
|
key: it.key,
|
|
906
952
|
draggable: true,
|
|
907
953
|
className: cx("cs-sortable__item", dragKey === it.key && "is-dragging", over === it.key && "is-over"),
|
|
908
|
-
onDragStart: () =>
|
|
954
|
+
onDragStart: (e) => {
|
|
955
|
+
if (e.target.closest && e.target.closest(".cs-sortable__ops")) {
|
|
956
|
+
e.preventDefault();
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
setDragKey(it.key);
|
|
960
|
+
},
|
|
909
961
|
onDragOver: (e) => {
|
|
910
962
|
e.preventDefault();
|
|
911
963
|
setOver(it.key);
|
|
@@ -914,10 +966,31 @@ function Sortable({ items = [], onChange, className }) {
|
|
|
914
966
|
onDragEnd: drop
|
|
915
967
|
},
|
|
916
968
|
/* @__PURE__ */ React.createElement("span", { className: "cs-sortable__grip", "aria-hidden": "true" }, "⠿"),
|
|
917
|
-
it.label
|
|
969
|
+
/* @__PURE__ */ React.createElement("span", { className: "cs-sortable__label" }, it.label),
|
|
970
|
+
/* @__PURE__ */ React.createElement("span", { className: "cs-sortable__ops", onMouseDown: (e) => e.stopPropagation() }, /* @__PURE__ */ React.createElement(
|
|
971
|
+
"button",
|
|
972
|
+
{
|
|
973
|
+
type: "button",
|
|
974
|
+
className: "cs-button cs-button--secondary cs-button--xs",
|
|
975
|
+
"aria-label": t("moveUp"),
|
|
976
|
+
disabled: idx === 0,
|
|
977
|
+
onClick: () => move(idx, idx - 1)
|
|
978
|
+
},
|
|
979
|
+
"↑"
|
|
980
|
+
), /* @__PURE__ */ React.createElement(
|
|
981
|
+
"button",
|
|
982
|
+
{
|
|
983
|
+
type: "button",
|
|
984
|
+
className: "cs-button cs-button--secondary cs-button--xs",
|
|
985
|
+
"aria-label": t("moveDown"),
|
|
986
|
+
disabled: idx === items.length - 1,
|
|
987
|
+
onClick: () => move(idx, idx + 1)
|
|
988
|
+
},
|
|
989
|
+
"↓"
|
|
990
|
+
))
|
|
918
991
|
)));
|
|
919
992
|
}
|
|
920
|
-
Object.assign(__ds_scope, { Sortable });
|
|
993
|
+
Object.assign(__ds_scope, { reorderItems, Sortable });
|
|
921
994
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/Sortable.jsx", error: String((e && e.message) || e) }); }
|
|
922
995
|
|
|
923
996
|
// components/data/Splitter.jsx
|
|
@@ -981,18 +1054,19 @@ Object.assign(__ds_scope, { Stat });
|
|
|
981
1054
|
try { (() => {
|
|
982
1055
|
const { makeT, useLang } = __ds_scope;
|
|
983
1056
|
const { cx } = __ds_scope;
|
|
984
|
-
function Terminal({ title
|
|
1057
|
+
function Terminal({ title, welcome, onCommand, prompt = "➜", lang, className }) {
|
|
985
1058
|
const [hist, setHist] = React.useState(() => welcome ? [{ out: welcome }] : []);
|
|
986
1059
|
const [q, setQ] = React.useState("");
|
|
987
1060
|
const [ref, L] = useLang(lang);
|
|
988
1061
|
const t = makeT("Terminal", L);
|
|
1062
|
+
const barTitle = title ?? t("title");
|
|
989
1063
|
const run = () => {
|
|
990
1064
|
if (!q.trim()) return;
|
|
991
1065
|
const res = onCommand ? onCommand(q.trim()) : "";
|
|
992
1066
|
setHist((h) => [...h, { cmd: q }, ...res ? [{ out: res }] : []]);
|
|
993
1067
|
setQ("");
|
|
994
1068
|
};
|
|
995
|
-
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-terminal", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__bar" }, /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("span", null,
|
|
1069
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-terminal", className) }, /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__bar" }, /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("i", null), /* @__PURE__ */ React.createElement("span", null, barTitle)), /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__body" }, hist.map((l, i) => l.cmd != null ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-terminal__line" }, /* @__PURE__ */ React.createElement("span", { className: "p" }, prompt), " ", l.cmd) : /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-terminal__out" }, l.out)), /* @__PURE__ */ React.createElement("div", { className: "cs-terminal__line" }, /* @__PURE__ */ React.createElement("span", { className: "p" }, prompt), /* @__PURE__ */ React.createElement(
|
|
996
1070
|
"input",
|
|
997
1071
|
{
|
|
998
1072
|
value: q,
|
|
@@ -1019,9 +1093,12 @@ Object.assign(__ds_scope, { Timeline });
|
|
|
1019
1093
|
|
|
1020
1094
|
// components/data/Tooltip.jsx
|
|
1021
1095
|
try { (() => {
|
|
1096
|
+
const { makeT, useLang } = __ds_scope;
|
|
1022
1097
|
const { cx } = __ds_scope;
|
|
1023
|
-
function Tooltip({ label, children, className }) {
|
|
1024
|
-
|
|
1098
|
+
function Tooltip({ label, children, lang, className }) {
|
|
1099
|
+
const [ref, L] = useLang(lang);
|
|
1100
|
+
const t = makeT("Tooltip", L);
|
|
1101
|
+
return /* @__PURE__ */ React.createElement("span", { ref, className: cx("cs-tooltip", className) }, children, /* @__PURE__ */ React.createElement("span", { className: "cs-tooltip__bubble", role: "tooltip" }, label ?? t("label")));
|
|
1025
1102
|
}
|
|
1026
1103
|
Object.assign(__ds_scope, { Tooltip });
|
|
1027
1104
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/Tooltip.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -1029,16 +1106,135 @@ Object.assign(__ds_scope, { Tooltip });
|
|
|
1029
1106
|
// components/data/Tree.jsx
|
|
1030
1107
|
try { (() => {
|
|
1031
1108
|
const { cx } = __ds_scope;
|
|
1032
|
-
function
|
|
1033
|
-
const
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1109
|
+
function collectVisible(nodes, openMap, out = []) {
|
|
1110
|
+
for (const n of nodes) {
|
|
1111
|
+
out.push(n);
|
|
1112
|
+
const kids = n.children || [];
|
|
1113
|
+
if (kids.length && openMap[n.key]) collectVisible(kids, openMap, out);
|
|
1114
|
+
}
|
|
1115
|
+
return out;
|
|
1116
|
+
}
|
|
1117
|
+
function parentOf(nodes, key, parent = null) {
|
|
1118
|
+
for (const n of nodes) {
|
|
1119
|
+
if (n.key === key) return parent;
|
|
1120
|
+
const kids = n.children || [];
|
|
1121
|
+
if (kids.length) {
|
|
1122
|
+
const p = parentOf(kids, key, n);
|
|
1123
|
+
if (p !== void 0) return p;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
return void 0;
|
|
1127
|
+
}
|
|
1128
|
+
function findNode(nodes, key) {
|
|
1129
|
+
for (const n of nodes) {
|
|
1130
|
+
if (n.key === key) return n;
|
|
1131
|
+
const kids = n.children || [];
|
|
1132
|
+
if (kids.length) {
|
|
1133
|
+
const hit = findNode(kids, key);
|
|
1134
|
+
if (hit) return hit;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
return null;
|
|
1138
|
+
}
|
|
1139
|
+
function seedOpen(nodes, defaultOpen, map = {}) {
|
|
1140
|
+
for (const n of nodes) {
|
|
1141
|
+
const kids = n.children || [];
|
|
1142
|
+
if (kids.length) {
|
|
1143
|
+
map[n.key] = !!defaultOpen;
|
|
1144
|
+
seedOpen(kids, defaultOpen, map);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return map;
|
|
1039
1148
|
}
|
|
1040
1149
|
function Tree({ nodes = [], selected, onSelect, defaultOpen = false, className }) {
|
|
1041
|
-
|
|
1150
|
+
const [openMap, setOpenMap] = React.useState(() => seedOpen(nodes, defaultOpen));
|
|
1151
|
+
const [focusKey, setFocusKey] = React.useState(() => {
|
|
1152
|
+
if (selected && findNode(nodes, selected)) return selected;
|
|
1153
|
+
return nodes[0] ? nodes[0].key : null;
|
|
1154
|
+
});
|
|
1155
|
+
const itemRefs = React.useRef({});
|
|
1156
|
+
React.useEffect(() => {
|
|
1157
|
+
setOpenMap((prev) => {
|
|
1158
|
+
const next = seedOpen(nodes, defaultOpen);
|
|
1159
|
+
for (const k of Object.keys(next)) if (k in prev) next[k] = prev[k];
|
|
1160
|
+
return next;
|
|
1161
|
+
});
|
|
1162
|
+
}, [nodes, defaultOpen]);
|
|
1163
|
+
const visible = React.useMemo(() => collectVisible(nodes, openMap), [nodes, openMap]);
|
|
1164
|
+
React.useEffect(() => {
|
|
1165
|
+
if (!visible.length) {
|
|
1166
|
+
setFocusKey(null);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
if (!focusKey || !visible.some((n) => n.key === focusKey)) setFocusKey(visible[0].key);
|
|
1170
|
+
}, [visible, focusKey]);
|
|
1171
|
+
const toggle = (key) => setOpenMap((m) => ({ ...m, [key]: !m[key] }));
|
|
1172
|
+
const focusItem = (key) => {
|
|
1173
|
+
setFocusKey(key);
|
|
1174
|
+
requestAnimationFrame(() => {
|
|
1175
|
+
const el = itemRefs.current[key];
|
|
1176
|
+
if (el) el.focus();
|
|
1177
|
+
});
|
|
1178
|
+
};
|
|
1179
|
+
const onKeyDown = (e, key) => {
|
|
1180
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
1181
|
+
const idx = visible.findIndex((n) => n.key === key);
|
|
1182
|
+
if (idx < 0) return;
|
|
1183
|
+
const node = visible[idx];
|
|
1184
|
+
const kids = node.children || [];
|
|
1185
|
+
const open = !!openMap[key];
|
|
1186
|
+
if (e.key === "ArrowDown") {
|
|
1187
|
+
e.preventDefault();
|
|
1188
|
+
if (idx < visible.length - 1) focusItem(visible[idx + 1].key);
|
|
1189
|
+
} else if (e.key === "ArrowUp") {
|
|
1190
|
+
e.preventDefault();
|
|
1191
|
+
if (idx > 0) focusItem(visible[idx - 1].key);
|
|
1192
|
+
} else if (e.key === "Home") {
|
|
1193
|
+
e.preventDefault();
|
|
1194
|
+
focusItem(visible[0].key);
|
|
1195
|
+
} else if (e.key === "End") {
|
|
1196
|
+
e.preventDefault();
|
|
1197
|
+
focusItem(visible[visible.length - 1].key);
|
|
1198
|
+
} else if (e.key === "ArrowRight") {
|
|
1199
|
+
e.preventDefault();
|
|
1200
|
+
if (kids.length && !open) toggle(key);
|
|
1201
|
+
else if (kids.length && open) focusItem(kids[0].key);
|
|
1202
|
+
} else if (e.key === "ArrowLeft") {
|
|
1203
|
+
e.preventDefault();
|
|
1204
|
+
if (kids.length && open) toggle(key);
|
|
1205
|
+
else {
|
|
1206
|
+
const p = parentOf(nodes, key);
|
|
1207
|
+
if (p) focusItem(p.key);
|
|
1208
|
+
}
|
|
1209
|
+
} else if (e.key === "Enter" || e.key === " ") {
|
|
1210
|
+
e.preventDefault();
|
|
1211
|
+
onSelect && onSelect(key, node);
|
|
1212
|
+
if (kids.length) toggle(key);
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
const renderNodes = (list, depth) => list.map((n) => {
|
|
1216
|
+
const kids = n.children || [];
|
|
1217
|
+
const open = !!openMap[n.key];
|
|
1218
|
+
return /* @__PURE__ */ React.createElement("li", { key: n.key, role: "treeitem", "aria-expanded": kids.length ? open : void 0, "aria-selected": selected === n.key }, /* @__PURE__ */ React.createElement("span", { className: cx("cs-tree__row", selected === n.key && "is-selected"), style: { paddingInlineStart: depth * 18 + 6 } }, kids.length ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-tree__twist", "aria-hidden": "true", tabIndex: -1, onClick: () => toggle(n.key) }, open ? "▾" : "▸") : /* @__PURE__ */ React.createElement("span", { className: "cs-tree__twist", "aria-hidden": "true" }), /* @__PURE__ */ React.createElement(
|
|
1219
|
+
"button",
|
|
1220
|
+
{
|
|
1221
|
+
type: "button",
|
|
1222
|
+
className: "cs-tree__label",
|
|
1223
|
+
tabIndex: focusKey === n.key ? 0 : -1,
|
|
1224
|
+
ref: (el) => {
|
|
1225
|
+
itemRefs.current[n.key] = el;
|
|
1226
|
+
},
|
|
1227
|
+
onClick: () => {
|
|
1228
|
+
onSelect && onSelect(n.key, n);
|
|
1229
|
+
if (kids.length) toggle(n.key);
|
|
1230
|
+
},
|
|
1231
|
+
onKeyDown: (e) => onKeyDown(e, n.key),
|
|
1232
|
+
onFocus: () => setFocusKey(n.key)
|
|
1233
|
+
},
|
|
1234
|
+
n.label
|
|
1235
|
+
)), kids.length && open ? /* @__PURE__ */ React.createElement("ul", { role: "group" }, renderNodes(kids, depth + 1)) : null);
|
|
1236
|
+
});
|
|
1237
|
+
return /* @__PURE__ */ React.createElement("ul", { role: "tree", className: cx("cs-tree", className) }, renderNodes(nodes, 0));
|
|
1042
1238
|
}
|
|
1043
1239
|
Object.assign(__ds_scope, { Tree });
|
|
1044
1240
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/Tree.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -1197,27 +1393,6 @@ function QRCode({ value = "", size = 128, color = "var(--cs-color-text-primary)"
|
|
|
1197
1393
|
Object.assign(__ds_scope, { QRCode });
|
|
1198
1394
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/data/QRCode.jsx", error: String((e && e.message) || e) }); }
|
|
1199
1395
|
|
|
1200
|
-
// components/datatable/DataTable.jsx
|
|
1201
|
-
try { (() => {
|
|
1202
|
-
const { makeT, useLang } = __ds_scope;
|
|
1203
|
-
const { cx } = __ds_scope;
|
|
1204
|
-
function DataTable({
|
|
1205
|
-
caption,
|
|
1206
|
-
columns,
|
|
1207
|
-
rows,
|
|
1208
|
-
rowKey = "id",
|
|
1209
|
-
emptyState,
|
|
1210
|
-
lang,
|
|
1211
|
-
className
|
|
1212
|
-
}) {
|
|
1213
|
-
const normalized = Array.isArray(rows) ? rows : [];
|
|
1214
|
-
const [ref, L] = useLang(lang);
|
|
1215
|
-
const es = emptyState != null ? emptyState : makeT("DataTable", L)("empty");
|
|
1216
|
-
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", className) }, /* @__PURE__ */ React.createElement("table", { className: "cs-table" }, caption ? /* @__PURE__ */ React.createElement("caption", null, caption) : null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, columns.map((c) => /* @__PURE__ */ React.createElement("th", { key: c.key, scope: "col" }, c.header)))), /* @__PURE__ */ React.createElement("tbody", null, normalized.length === 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: columns.length, className: "cs-table__empty" }, es)) : normalized.map((row, i) => /* @__PURE__ */ React.createElement("tr", { key: row[rowKey] ?? i }, columns.map((c) => /* @__PURE__ */ React.createElement("td", { key: c.key }, c.render ? c.render(row) : row[c.key])))))));
|
|
1217
|
-
}
|
|
1218
|
-
Object.assign(__ds_scope, { DataTable });
|
|
1219
|
-
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/datatable/DataTable.jsx", error: String((e && e.message) || e) }); }
|
|
1220
|
-
|
|
1221
1396
|
// components/datatable/TreeTable.jsx
|
|
1222
1397
|
try { (() => {
|
|
1223
1398
|
const { makeT, useLang } = __ds_scope;
|
|
@@ -1238,6 +1413,7 @@ Object.assign(__ds_scope, { TreeTable });
|
|
|
1238
1413
|
|
|
1239
1414
|
// components/feedback/Alert.jsx
|
|
1240
1415
|
try { (() => {
|
|
1416
|
+
const { makeT, useLang } = __ds_scope;
|
|
1241
1417
|
const { cx } = __ds_scope;
|
|
1242
1418
|
const PATHS = {
|
|
1243
1419
|
info: "M12 8h.01M11 12h1v4h1 M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z",
|
|
@@ -1248,8 +1424,10 @@ const PATHS = {
|
|
|
1248
1424
|
function DefaultIcon({ variant }) {
|
|
1249
1425
|
return /* @__PURE__ */ React.createElement("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: PATHS[variant] || PATHS.info }));
|
|
1250
1426
|
}
|
|
1251
|
-
function Alert({ variant = "info", title, icon, children, className, ...props }) {
|
|
1252
|
-
|
|
1427
|
+
function Alert({ variant = "info", title, icon, children, onDismiss, lang, className, ...props }) {
|
|
1428
|
+
const [ref, L] = useLang(lang);
|
|
1429
|
+
const t = makeT("Alert", L);
|
|
1430
|
+
return /* @__PURE__ */ React.createElement("div", { ref, role: "status", className: cx("cs-alert", `cs-alert--${variant}`, className), ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-alert__icon" }, icon ?? /* @__PURE__ */ React.createElement(DefaultIcon, { variant })), /* @__PURE__ */ React.createElement("div", null, title ? /* @__PURE__ */ React.createElement("p", { className: "cs-alert__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-alert__body" }, children) : null), onDismiss ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-alert__dismiss", "aria-label": t("dismiss"), onClick: onDismiss }, "×") : null);
|
|
1253
1431
|
}
|
|
1254
1432
|
Object.assign(__ds_scope, { Alert });
|
|
1255
1433
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Alert.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -1265,29 +1443,36 @@ Object.assign(__ds_scope, { Badge });
|
|
|
1265
1443
|
|
|
1266
1444
|
// components/feedback/EmptyState.jsx
|
|
1267
1445
|
try { (() => {
|
|
1446
|
+
const { makeT, useLang } = __ds_scope;
|
|
1268
1447
|
const { cx } = __ds_scope;
|
|
1269
|
-
function EmptyState({ icon, title, children, actions, className }) {
|
|
1270
|
-
|
|
1448
|
+
function EmptyState({ icon, title, children, actions, lang, className }) {
|
|
1449
|
+
const [ref, L] = useLang(lang);
|
|
1450
|
+
const t = makeT("EmptyState", L);
|
|
1451
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-empty", className) }, /* @__PURE__ */ React.createElement("span", { className: "cs-empty__icon" }, icon ?? /* @__PURE__ */ React.createElement("svg", { width: "26", height: "26", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: "M3 8l2-4h14l2 4M3 8v10a1 1 0 0 0 1 1h16a1 1 0 0 0 1-1V8M3 8h6l1 3h4l1-3h6" }))), /* @__PURE__ */ React.createElement("div", { className: "cs-empty__title" }, title ?? t("title")), children ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__body" }, children) : null, actions ? /* @__PURE__ */ React.createElement("div", { className: "cs-empty__actions" }, actions) : null);
|
|
1271
1452
|
}
|
|
1272
1453
|
Object.assign(__ds_scope, { EmptyState });
|
|
1273
1454
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/EmptyState.jsx", error: String((e && e.message) || e) }); }
|
|
1274
1455
|
|
|
1275
1456
|
// components/feedback/ProgressBar.jsx
|
|
1276
1457
|
try { (() => {
|
|
1458
|
+
const { makeT, useLang } = __ds_scope;
|
|
1277
1459
|
const { cx } = __ds_scope;
|
|
1278
|
-
function ProgressBar({ value = 0, max = 100, variant, label, className }) {
|
|
1460
|
+
function ProgressBar({ value = 0, max = 100, variant, label, lang, className }) {
|
|
1279
1461
|
const safeMax = max > 0 ? max : 100;
|
|
1280
1462
|
const clamped = Math.max(0, Math.min(safeMax, Number(value) || 0));
|
|
1281
1463
|
const pct = Math.max(0, Math.min(100, clamped / safeMax * 100));
|
|
1464
|
+
const [ref, L] = useLang(lang);
|
|
1465
|
+
const t = makeT("ProgressBar", L);
|
|
1282
1466
|
return /* @__PURE__ */ React.createElement(
|
|
1283
1467
|
"div",
|
|
1284
1468
|
{
|
|
1469
|
+
ref,
|
|
1285
1470
|
className: cx("cs-progress", variant && `cs-progress--${variant}`, className),
|
|
1286
1471
|
role: "progressbar",
|
|
1287
1472
|
"aria-valuenow": Math.round(clamped),
|
|
1288
1473
|
"aria-valuemin": 0,
|
|
1289
1474
|
"aria-valuemax": safeMax,
|
|
1290
|
-
"aria-label": label
|
|
1475
|
+
"aria-label": label ?? t("label")
|
|
1291
1476
|
},
|
|
1292
1477
|
/* @__PURE__ */ React.createElement("span", { className: "cs-progress__fill", style: { inlineSize: pct + "%", width: pct + "%" } })
|
|
1293
1478
|
);
|
|
@@ -1333,6 +1518,41 @@ function Skeleton({ variant = "block", width, height, lines, radius, className,
|
|
|
1333
1518
|
Object.assign(__ds_scope, { Skeleton });
|
|
1334
1519
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Skeleton.jsx", error: String((e && e.message) || e) }); }
|
|
1335
1520
|
|
|
1521
|
+
// components/datatable/DataTable.jsx
|
|
1522
|
+
try { (() => {
|
|
1523
|
+
const { makeT, useLang } = __ds_scope;
|
|
1524
|
+
const { Skeleton } = __ds_scope;
|
|
1525
|
+
const { Result } = __ds_scope;
|
|
1526
|
+
const { cx } = __ds_scope;
|
|
1527
|
+
function DataTable({
|
|
1528
|
+
caption,
|
|
1529
|
+
columns,
|
|
1530
|
+
rows,
|
|
1531
|
+
rowKey = "id",
|
|
1532
|
+
emptyState,
|
|
1533
|
+
state = "idle",
|
|
1534
|
+
errorState,
|
|
1535
|
+
loadingState,
|
|
1536
|
+
loadingRows = 5,
|
|
1537
|
+
lang,
|
|
1538
|
+
className
|
|
1539
|
+
}) {
|
|
1540
|
+
const normalized = Array.isArray(rows) ? rows : [];
|
|
1541
|
+
const [ref, L] = useLang(lang);
|
|
1542
|
+
const t = makeT("DataTable", L);
|
|
1543
|
+
const es = emptyState != null ? emptyState : t("empty");
|
|
1544
|
+
const colCount = Math.max(1, columns && columns.length || 1);
|
|
1545
|
+
if (state === "loading") {
|
|
1546
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", className), "aria-busy": "true" }, /* @__PURE__ */ React.createElement("span", { className: "cs-sr-only" }, t("loading")), loadingState != null ? loadingState : /* @__PURE__ */ React.createElement("table", { className: "cs-table" }, caption ? /* @__PURE__ */ React.createElement("caption", null, caption) : null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, (columns || []).map((c) => /* @__PURE__ */ React.createElement("th", { key: c.key, scope: "col" }, c.header)))), /* @__PURE__ */ React.createElement("tbody", { "aria-hidden": "true" }, Array.from({ length: Math.max(1, loadingRows) }).map((_, i) => /* @__PURE__ */ React.createElement("tr", { key: i }, Array.from({ length: colCount }).map((__, j) => /* @__PURE__ */ React.createElement("td", { key: j }, /* @__PURE__ */ React.createElement(Skeleton, { lines: 1 }))))))));
|
|
1547
|
+
}
|
|
1548
|
+
if (state === "error") {
|
|
1549
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", "cs-table-wrap--status", className), role: "alert" }, errorState != null ? errorState : /* @__PURE__ */ React.createElement(Result, { status: "error", lang: L, title: t("error") }, t("errorHint")));
|
|
1550
|
+
}
|
|
1551
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-table-wrap", className) }, /* @__PURE__ */ React.createElement("table", { className: "cs-table" }, caption ? /* @__PURE__ */ React.createElement("caption", null, caption) : null, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, (columns || []).map((c) => /* @__PURE__ */ React.createElement("th", { key: c.key, scope: "col" }, c.header)))), /* @__PURE__ */ React.createElement("tbody", null, normalized.length === 0 ? /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("td", { colSpan: colCount, className: "cs-table__empty" }, es)) : normalized.map((row, i) => /* @__PURE__ */ React.createElement("tr", { key: row[rowKey] ?? i }, (columns || []).map((c) => /* @__PURE__ */ React.createElement("td", { key: c.key }, c.render ? c.render(row) : row[c.key])))))));
|
|
1552
|
+
}
|
|
1553
|
+
Object.assign(__ds_scope, { DataTable });
|
|
1554
|
+
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/datatable/DataTable.jsx", error: String((e && e.message) || e) }); }
|
|
1555
|
+
|
|
1336
1556
|
// components/feedback/Spinner.jsx
|
|
1337
1557
|
try { (() => {
|
|
1338
1558
|
const { makeT, useLang } = __ds_scope;
|
|
@@ -1370,13 +1590,21 @@ Object.assign(__ds_scope, { Tag });
|
|
|
1370
1590
|
try { (() => {
|
|
1371
1591
|
const { makeT, useLang } = __ds_scope;
|
|
1372
1592
|
const { cx } = __ds_scope;
|
|
1593
|
+
const PATHS = {
|
|
1594
|
+
default: "M12 8h.01M11 12h1v4h1 M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z",
|
|
1595
|
+
success: "M4 12.5l5 5 11-11",
|
|
1596
|
+
danger: "M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18z M9 9l6 6 M15 9l-6 6"
|
|
1597
|
+
};
|
|
1598
|
+
function DefaultIcon({ variant }) {
|
|
1599
|
+
return /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: PATHS[variant] || PATHS.default }));
|
|
1600
|
+
}
|
|
1373
1601
|
function ToastStack({ children, lang, className }) {
|
|
1374
1602
|
const [ref, L] = useLang(lang);
|
|
1375
1603
|
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-toast-stack", className), role: "region", "aria-label": makeT("Toast", L)("notifications") }, children);
|
|
1376
1604
|
}
|
|
1377
1605
|
function Toast({ variant = "default", title, icon, onClose, lang, children, className, ...props }) {
|
|
1378
1606
|
const [ref, L] = useLang(lang);
|
|
1379
|
-
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-toast", `cs-toast--${variant}`, className), role: "status", ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-toast__icon", "aria-hidden": "true" }, icon ?? /* @__PURE__ */ React.createElement(
|
|
1607
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-toast", `cs-toast--${variant}`, className), role: "status", ...props }, /* @__PURE__ */ React.createElement("span", { className: "cs-toast__icon", "aria-hidden": "true" }, icon ?? /* @__PURE__ */ React.createElement(DefaultIcon, { variant })), /* @__PURE__ */ React.createElement("div", null, title ? /* @__PURE__ */ React.createElement("div", { className: "cs-toast__title" }, title) : null, children ? /* @__PURE__ */ React.createElement("div", { className: "cs-toast__body" }, children) : null), onClose ? /* @__PURE__ */ React.createElement("button", { type: "button", className: "cs-toast__close", "aria-label": makeT("Toast", L)("dismiss"), onClick: onClose }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 6l12 12M18 6L6 18" }))) : null);
|
|
1380
1608
|
}
|
|
1381
1609
|
Object.assign(__ds_scope, { ToastStack, Toast });
|
|
1382
1610
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/feedback/Toast.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -1425,7 +1653,8 @@ Object.assign(__ds_scope, { Calendar });
|
|
|
1425
1653
|
try { (() => {
|
|
1426
1654
|
const { makeT, useLang } = __ds_scope;
|
|
1427
1655
|
const { cx } = __ds_scope;
|
|
1428
|
-
|
|
1656
|
+
const EMPTY_PATH = [];
|
|
1657
|
+
function Cascader({ nodes = [], value = EMPTY_PATH, onChange, placeholder, label, disabled = false, lang, className }) {
|
|
1429
1658
|
const [open, setOpen] = React.useState(false);
|
|
1430
1659
|
const [path, setPath] = React.useState(value);
|
|
1431
1660
|
const wrap = React.useRef(null);
|
|
@@ -1433,9 +1662,10 @@ function Cascader({ nodes = [], value = [], onChange, placeholder, label, disabl
|
|
|
1433
1662
|
const [ref, L] = useLang(lang);
|
|
1434
1663
|
const t = makeT("Cascader", L);
|
|
1435
1664
|
const ph = placeholder != null ? placeholder : t("placeholder");
|
|
1665
|
+
const valueKey = Array.isArray(value) ? value.join("\0") : "";
|
|
1436
1666
|
React.useEffect(() => {
|
|
1437
|
-
setPath(value);
|
|
1438
|
-
}, [
|
|
1667
|
+
setPath(Array.isArray(value) ? value : EMPTY_PATH);
|
|
1668
|
+
}, [valueKey]);
|
|
1439
1669
|
React.useEffect(() => {
|
|
1440
1670
|
if (!open) return;
|
|
1441
1671
|
const d = (e) => {
|
|
@@ -1915,6 +2145,7 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
|
|
|
1915
2145
|
const [values, setValues] = React.useState(() => initialValues || {});
|
|
1916
2146
|
const [ruleErrors, setRuleErrors] = React.useState({});
|
|
1917
2147
|
const [pending, setPending] = React.useState(false);
|
|
2148
|
+
const pendingRef = React.useRef(false);
|
|
1918
2149
|
const setValue = React.useCallback((name, v) => {
|
|
1919
2150
|
setValues((s) => String(name).includes(".") ? setPath(s, name, v) : { ...s, [name]: v });
|
|
1920
2151
|
setRuleErrors((e) => e[name] ? { ...e, [name]: void 0 } : e);
|
|
@@ -1961,8 +2192,10 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
|
|
|
1961
2192
|
ref,
|
|
1962
2193
|
className: cx("cs-form", pending && "is-pending", className),
|
|
1963
2194
|
noValidate: true,
|
|
2195
|
+
"aria-busy": pending || void 0,
|
|
1964
2196
|
onSubmit: async (e) => {
|
|
1965
2197
|
e.preventDefault();
|
|
2198
|
+
if (pendingRef.current) return;
|
|
1966
2199
|
const fd = new FormData(e.currentTarget);
|
|
1967
2200
|
const v = {};
|
|
1968
2201
|
fd.forEach((val, k) => {
|
|
@@ -1974,6 +2207,7 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
|
|
|
1974
2207
|
setRuleErrors(errs);
|
|
1975
2208
|
return;
|
|
1976
2209
|
}
|
|
2210
|
+
pendingRef.current = true;
|
|
1977
2211
|
setPending(true);
|
|
1978
2212
|
try {
|
|
1979
2213
|
const aerrs = await runAsyncRules(v);
|
|
@@ -1981,6 +2215,7 @@ function Form({ onSubmit, errors = {}, rules, asyncRules, initialValues, childre
|
|
|
1981
2215
|
if (Object.keys(aerrs).some((k) => aerrs[k])) return;
|
|
1982
2216
|
onSubmit && onSubmit(v);
|
|
1983
2217
|
} finally {
|
|
2218
|
+
pendingRef.current = false;
|
|
1984
2219
|
setPending(false);
|
|
1985
2220
|
}
|
|
1986
2221
|
}
|
|
@@ -2281,6 +2516,7 @@ Object.assign(__ds_scope, { InputOTP });
|
|
|
2281
2516
|
try { (() => {
|
|
2282
2517
|
const { makeT, useLang } = __ds_scope;
|
|
2283
2518
|
const { cx } = __ds_scope;
|
|
2519
|
+
let mentionsUid = 0;
|
|
2284
2520
|
function Mentions({ value, defaultValue = "", onChange, users = [], placeholder, rows = 3, lang, className }) {
|
|
2285
2521
|
const [inner, setInner] = React.useState(defaultValue);
|
|
2286
2522
|
const val = value != null ? value : inner;
|
|
@@ -2289,23 +2525,80 @@ function Mentions({ value, defaultValue = "", onChange, users = [], placeholder,
|
|
|
2289
2525
|
onChange && onChange(v);
|
|
2290
2526
|
};
|
|
2291
2527
|
const [q, setQ] = React.useState(null);
|
|
2528
|
+
const [activeIndex, setActiveIndex] = React.useState(0);
|
|
2529
|
+
const [listId] = React.useState(() => "cs-mentions-" + ++mentionsUid);
|
|
2292
2530
|
const [ref, L] = useLang(lang);
|
|
2293
2531
|
const t = makeT("Mentions", L);
|
|
2294
2532
|
const ph = placeholder != null ? placeholder : t("placeholder");
|
|
2533
|
+
const hits = q == null ? [] : users.filter((u) => u.toLowerCase().includes(q)).slice(0, 6);
|
|
2534
|
+
const open = hits.length > 0;
|
|
2535
|
+
React.useEffect(() => {
|
|
2536
|
+
setActiveIndex((i) => hits.length ? Math.min(i, hits.length - 1) : 0);
|
|
2537
|
+
}, [q, users]);
|
|
2295
2538
|
const onInput = (v) => {
|
|
2296
2539
|
set(v);
|
|
2297
2540
|
const m = /(^|\s)@(\w*)$/.exec(v);
|
|
2298
|
-
|
|
2541
|
+
const next = m ? m[2].toLowerCase() : null;
|
|
2542
|
+
setQ(next);
|
|
2543
|
+
if (next != null) setActiveIndex(0);
|
|
2299
2544
|
};
|
|
2300
|
-
const hits = q == null ? [] : users.filter((u) => u.toLowerCase().includes(q)).slice(0, 6);
|
|
2301
2545
|
const pick = (u) => {
|
|
2302
2546
|
set(val.replace(/(^|\s)@\w*$/, "$1@" + u + " "));
|
|
2303
2547
|
setQ(null);
|
|
2304
2548
|
};
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2549
|
+
const optionId = (i) => listId + "-opt-" + i;
|
|
2550
|
+
const onKeyDown = (e) => {
|
|
2551
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
2552
|
+
if (!open) return;
|
|
2553
|
+
if (e.key === "ArrowDown") {
|
|
2554
|
+
e.preventDefault();
|
|
2555
|
+
setActiveIndex((i) => Math.min(hits.length - 1, i + 1));
|
|
2556
|
+
} else if (e.key === "ArrowUp") {
|
|
2557
|
+
e.preventDefault();
|
|
2558
|
+
setActiveIndex((i) => Math.max(0, i - 1));
|
|
2559
|
+
} else if (e.key === "Enter") {
|
|
2560
|
+
if (hits[activeIndex]) {
|
|
2561
|
+
e.preventDefault();
|
|
2562
|
+
pick(hits[activeIndex]);
|
|
2563
|
+
}
|
|
2564
|
+
} else if (e.key === "Escape") {
|
|
2565
|
+
e.preventDefault();
|
|
2566
|
+
setQ(null);
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
return /* @__PURE__ */ React.createElement("span", { ref, className: cx("cs-mentions", className) }, /* @__PURE__ */ React.createElement(
|
|
2570
|
+
"textarea",
|
|
2571
|
+
{
|
|
2572
|
+
className: "cs-field__control",
|
|
2573
|
+
rows,
|
|
2574
|
+
value: val,
|
|
2575
|
+
placeholder: ph,
|
|
2576
|
+
role: "combobox",
|
|
2577
|
+
"aria-autocomplete": "list",
|
|
2578
|
+
"aria-expanded": open,
|
|
2579
|
+
"aria-controls": listId,
|
|
2580
|
+
"aria-activedescendant": open && hits[activeIndex] ? optionId(activeIndex) : void 0,
|
|
2581
|
+
onChange: (e) => onInput(e.target.value),
|
|
2582
|
+
onKeyDown
|
|
2583
|
+
}
|
|
2584
|
+
), open ? /* @__PURE__ */ React.createElement("span", { className: "cs-mentions__pop", role: "listbox", id: listId }, hits.map((u, i) => /* @__PURE__ */ React.createElement(
|
|
2585
|
+
"button",
|
|
2586
|
+
{
|
|
2587
|
+
key: u,
|
|
2588
|
+
type: "button",
|
|
2589
|
+
id: optionId(i),
|
|
2590
|
+
role: "option",
|
|
2591
|
+
"aria-selected": i === activeIndex,
|
|
2592
|
+
className: cx(i === activeIndex && "hl"),
|
|
2593
|
+
onMouseEnter: () => setActiveIndex(i),
|
|
2594
|
+
onMouseDown: (e) => {
|
|
2595
|
+
e.preventDefault();
|
|
2596
|
+
pick(u);
|
|
2597
|
+
}
|
|
2598
|
+
},
|
|
2599
|
+
"@",
|
|
2600
|
+
u
|
|
2601
|
+
))) : null);
|
|
2309
2602
|
}
|
|
2310
2603
|
Object.assign(__ds_scope, { Mentions });
|
|
2311
2604
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Mentions.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -2435,15 +2728,57 @@ const STAR = "M12 2l2.9 6.2 6.6.8-4.9 4.6 1.3 6.5L12 16.9 6.1 20l1.3-6.5L2.5 9l6
|
|
|
2435
2728
|
function Rating({ value, defaultValue = 0, onChange, max = 5, readOnly = false, label, lang, className }) {
|
|
2436
2729
|
const [inner, setInner] = React.useState(defaultValue);
|
|
2437
2730
|
const val = value != null ? value : inner;
|
|
2438
|
-
const
|
|
2731
|
+
const commit = (n) => {
|
|
2439
2732
|
if (readOnly) return;
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
onChange && onChange(v);
|
|
2733
|
+
if (value == null) setInner(n);
|
|
2734
|
+
onChange && onChange(n);
|
|
2443
2735
|
};
|
|
2736
|
+
const set = (n) => commit(n === val ? 0 : n);
|
|
2444
2737
|
const [ref, L] = useLang(lang);
|
|
2445
2738
|
const lbl = label != null ? label : makeT("Rating", L)("label");
|
|
2446
|
-
|
|
2739
|
+
const refs = React.useRef([]);
|
|
2740
|
+
const focusIdx = val > 0 ? Math.min(max, val) - 1 : 0;
|
|
2741
|
+
const move = (from, delta) => {
|
|
2742
|
+
if (readOnly) return;
|
|
2743
|
+
const next = Math.max(0, Math.min(max - 1, from + delta));
|
|
2744
|
+
commit(next + 1);
|
|
2745
|
+
const b = refs.current[next];
|
|
2746
|
+
if (b) b.focus();
|
|
2747
|
+
};
|
|
2748
|
+
const onKeyDown = (e, i) => {
|
|
2749
|
+
if (readOnly || e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
2750
|
+
if (e.key === "ArrowRight" || e.key === "ArrowUp") {
|
|
2751
|
+
e.preventDefault();
|
|
2752
|
+
move(i, 1);
|
|
2753
|
+
} else if (e.key === "ArrowLeft" || e.key === "ArrowDown") {
|
|
2754
|
+
e.preventDefault();
|
|
2755
|
+
move(i, -1);
|
|
2756
|
+
} else if (e.key === "Home") {
|
|
2757
|
+
e.preventDefault();
|
|
2758
|
+
move(i, -i);
|
|
2759
|
+
} else if (e.key === "End") {
|
|
2760
|
+
e.preventDefault();
|
|
2761
|
+
move(i, max - 1 - i);
|
|
2762
|
+
}
|
|
2763
|
+
};
|
|
2764
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-rating", className), role: "radiogroup", "aria-label": lbl + ": " + val + " / " + max, "data-readonly": readOnly ? "true" : void 0 }, Array.from({ length: max }).map((_, i) => /* @__PURE__ */ React.createElement(
|
|
2765
|
+
"button",
|
|
2766
|
+
{
|
|
2767
|
+
key: i,
|
|
2768
|
+
type: "button",
|
|
2769
|
+
role: "radio",
|
|
2770
|
+
"aria-checked": i < val,
|
|
2771
|
+
"aria-label": i + 1 + " / " + max,
|
|
2772
|
+
className: i < val ? "on" : void 0,
|
|
2773
|
+
onClick: () => set(i + 1),
|
|
2774
|
+
tabIndex: readOnly ? -1 : i === focusIdx ? 0 : -1,
|
|
2775
|
+
ref: (el) => {
|
|
2776
|
+
refs.current[i] = el;
|
|
2777
|
+
},
|
|
2778
|
+
onKeyDown: (e) => onKeyDown(e, i)
|
|
2779
|
+
},
|
|
2780
|
+
/* @__PURE__ */ React.createElement("svg", { width: "22", height: "22", viewBox: "0 0 24 24", fill: i < val ? "currentColor" : "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: STAR }))
|
|
2781
|
+
)));
|
|
2447
2782
|
}
|
|
2448
2783
|
Object.assign(__ds_scope, { Rating });
|
|
2449
2784
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Rating.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -2506,44 +2841,49 @@ Object.assign(__ds_scope, { SegmentedControl });
|
|
|
2506
2841
|
|
|
2507
2842
|
// components/forms/Select.jsx
|
|
2508
2843
|
try { (() => {
|
|
2844
|
+
const { makeT, useLang } = __ds_scope;
|
|
2509
2845
|
const { cx } = __ds_scope;
|
|
2510
|
-
function Select({ id, label, description, error, options, children, disabled = false, className, ...props }) {
|
|
2846
|
+
function Select({ id, label, description, error, options, children, disabled = false, lang, className, placeholder, ...props }) {
|
|
2847
|
+
const [ref, L] = useLang(lang);
|
|
2848
|
+
const t = makeT("Select", L);
|
|
2511
2849
|
const gid = React.useId();
|
|
2512
2850
|
const sid = id ?? gid;
|
|
2513
2851
|
const descId = description ? sid + "-desc" : void 0;
|
|
2514
2852
|
const errId = error ? sid + "-err" : void 0;
|
|
2515
2853
|
const describedBy = [descId, errId].filter(Boolean).join(" ") || void 0;
|
|
2516
|
-
return /* @__PURE__ */ React.createElement("label", { className: cx("cs-field", disabled && "is-disabled", error && "is-invalid", className), htmlFor: sid }, label ? /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, label) : null, description ? /* @__PURE__ */ React.createElement("span", { id: descId, className: "cs-field__description" }, description) : null, /* @__PURE__ */ React.createElement("span", { className: "cs-select" }, /* @__PURE__ */ React.createElement("select", { ...props, id: sid, disabled, "aria-invalid": error ? true : void 0, "aria-describedby": describedBy, className: "cs-field__control" }, options ? options.map((o) => /* @__PURE__ */ React.createElement("option", { key: o.value, value: o.value }, o.label)) : children), /* @__PURE__ */ React.createElement("span", { className: "cs-select__chevron", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 9l6 6 6-6" })))), error ? /* @__PURE__ */ React.createElement("span", { id: errId, className: "cs-field__error", role: "alert" }, error) : null);
|
|
2854
|
+
return /* @__PURE__ */ React.createElement("label", { ref, className: cx("cs-field", disabled && "is-disabled", error && "is-invalid", className), htmlFor: sid }, label ? /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, label) : null, description ? /* @__PURE__ */ React.createElement("span", { id: descId, className: "cs-field__description" }, description) : null, /* @__PURE__ */ React.createElement("span", { className: "cs-select" }, /* @__PURE__ */ React.createElement("select", { ...props, id: sid, disabled, "aria-invalid": error ? true : void 0, "aria-describedby": describedBy, className: "cs-field__control" }, placeholder != null || !children ? /* @__PURE__ */ React.createElement("option", { value: "", disabled: true, hidden: true }, placeholder ?? t("placeholder")) : null, options ? options.map((o) => /* @__PURE__ */ React.createElement("option", { key: o.value, value: o.value }, o.label)) : children), /* @__PURE__ */ React.createElement("span", { className: "cs-select__chevron", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M6 9l6 6 6-6" })))), error ? /* @__PURE__ */ React.createElement("span", { id: errId, className: "cs-field__error", role: "alert" }, error) : null);
|
|
2517
2855
|
}
|
|
2518
2856
|
Object.assign(__ds_scope, { Select });
|
|
2519
2857
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Select.jsx", error: String((e && e.message) || e) }); }
|
|
2520
2858
|
|
|
2521
2859
|
// components/forms/Slider.jsx
|
|
2522
2860
|
try { (() => {
|
|
2861
|
+
const { makeT, useLang } = __ds_scope;
|
|
2523
2862
|
const { cx } = __ds_scope;
|
|
2524
|
-
function Slider({ className, children, ...props }) {
|
|
2525
|
-
|
|
2863
|
+
function Slider({ className, children, lang, "aria-label": ariaLabel, ...props }) {
|
|
2864
|
+
const [ref, L] = useLang(lang);
|
|
2865
|
+
const t = makeT("Slider", L);
|
|
2866
|
+
return /* @__PURE__ */ React.createElement("input", { ref, type: "range", className: cx("cs-slider", className), "aria-label": ariaLabel ?? t("label"), ...props });
|
|
2526
2867
|
}
|
|
2527
2868
|
Object.assign(__ds_scope, { Slider });
|
|
2528
2869
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Slider.jsx", error: String((e && e.message) || e) }); }
|
|
2529
2870
|
|
|
2530
2871
|
// components/forms/Switch.jsx
|
|
2531
2872
|
try { (() => {
|
|
2873
|
+
const { makeT, useLang } = __ds_scope;
|
|
2532
2874
|
const { cx } = __ds_scope;
|
|
2533
|
-
|
|
2534
|
-
|
|
2875
|
+
function Switch({ label, disabled = false, className, children, lang, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledby, ...props }) {
|
|
2876
|
+
const [ref, L] = useLang(lang);
|
|
2877
|
+
const t = makeT("Switch", L);
|
|
2535
2878
|
const named = !!(label || ariaLabel || ariaLabelledby);
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
console.warn("CyberSkill Switch: render with label, aria-label, or aria-labelledby.");
|
|
2539
|
-
}
|
|
2540
|
-
return /* @__PURE__ */ React.createElement("label", { className: cx("cs-switch", disabled && "is-disabled", className) }, /* @__PURE__ */ React.createElement(
|
|
2879
|
+
const fallback = props.checked || props.defaultChecked ? t("on") : t("off");
|
|
2880
|
+
return /* @__PURE__ */ React.createElement("label", { ref, className: cx("cs-switch", disabled && "is-disabled", className) }, /* @__PURE__ */ React.createElement(
|
|
2541
2881
|
"input",
|
|
2542
2882
|
{
|
|
2543
2883
|
type: "checkbox",
|
|
2544
2884
|
role: "switch",
|
|
2545
2885
|
disabled,
|
|
2546
|
-
"aria-label": ariaLabel,
|
|
2886
|
+
"aria-label": ariaLabel ?? (named ? void 0 : fallback),
|
|
2547
2887
|
"aria-labelledby": ariaLabelledby,
|
|
2548
2888
|
...props
|
|
2549
2889
|
}
|
|
@@ -2660,16 +3000,33 @@ Object.assign(__ds_scope, { TimePicker });
|
|
|
2660
3000
|
|
|
2661
3001
|
// components/forms/Toggle.jsx
|
|
2662
3002
|
try { (() => {
|
|
3003
|
+
const { makeT, useLang } = __ds_scope;
|
|
2663
3004
|
const { cx } = __ds_scope;
|
|
2664
|
-
function Toggle({ pressed, defaultPressed = false, onChange, icon, children, disabled = false, className, ...props }) {
|
|
3005
|
+
function Toggle({ pressed, defaultPressed = false, onChange, icon, children, disabled = false, lang, className, "aria-label": ariaLabel, ...props }) {
|
|
2665
3006
|
const [inner, setInner] = React.useState(defaultPressed);
|
|
2666
3007
|
const on = pressed != null ? pressed : inner;
|
|
3008
|
+
const [ref, L] = useLang(lang);
|
|
3009
|
+
const t = makeT("Toggle", L);
|
|
2667
3010
|
const flip = () => {
|
|
2668
3011
|
const v = !on;
|
|
2669
3012
|
if (pressed == null) setInner(v);
|
|
2670
3013
|
onChange && onChange(v);
|
|
2671
3014
|
};
|
|
2672
|
-
return /* @__PURE__ */ React.createElement(
|
|
3015
|
+
return /* @__PURE__ */ React.createElement(
|
|
3016
|
+
"button",
|
|
3017
|
+
{
|
|
3018
|
+
ref,
|
|
3019
|
+
type: "button",
|
|
3020
|
+
className: cx("cs-toggle", className),
|
|
3021
|
+
"aria-pressed": on,
|
|
3022
|
+
"aria-label": ariaLabel ?? (on ? t("pressed") : t("unpressed")),
|
|
3023
|
+
disabled,
|
|
3024
|
+
onClick: flip,
|
|
3025
|
+
...props
|
|
3026
|
+
},
|
|
3027
|
+
icon ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true", style: { display: "inline-flex" } }, icon) : null,
|
|
3028
|
+
children
|
|
3029
|
+
);
|
|
2673
3030
|
}
|
|
2674
3031
|
Object.assign(__ds_scope, { Toggle });
|
|
2675
3032
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/forms/Toggle.jsx", error: String((e && e.message) || e) }); }
|
|
@@ -2835,16 +3192,26 @@ function Image({ src, alt = "", ratio, preview = false, fallback, lang, classNam
|
|
|
2835
3192
|
document.addEventListener("keydown", k);
|
|
2836
3193
|
return () => document.removeEventListener("keydown", k);
|
|
2837
3194
|
}, [zoom]);
|
|
3195
|
+
const openPreview = () => setZoom(true);
|
|
3196
|
+
const onPreviewKey = (e) => {
|
|
3197
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
3198
|
+
e.preventDefault();
|
|
3199
|
+
openPreview();
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
2838
3202
|
const body = state === "error" ? /* @__PURE__ */ React.createElement("span", { className: "cs-image__fallback" }, fallback || /* @__PURE__ */ React.createElement("svg", { width: "28", height: "28", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2" }), /* @__PURE__ */ React.createElement("circle", { cx: "9", cy: "9", r: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M21 15l-5-5-9 9" }))) : /* @__PURE__ */ React.createElement("img", { ...props, src, alt, onLoad: () => setState("ok"), onError: () => setState("error") });
|
|
3203
|
+
const canPreview = preview && state === "ok";
|
|
2839
3204
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement(
|
|
2840
3205
|
"span",
|
|
2841
3206
|
{
|
|
2842
3207
|
ref,
|
|
2843
|
-
className: cx("cs-image", state === "loading" && "is-loading",
|
|
3208
|
+
className: cx("cs-image", state === "loading" && "is-loading", canPreview && "is-zoomable", className),
|
|
2844
3209
|
style: ratio ? { aspectRatio: ratio } : void 0,
|
|
2845
|
-
onClick:
|
|
2846
|
-
|
|
2847
|
-
|
|
3210
|
+
onClick: canPreview ? openPreview : void 0,
|
|
3211
|
+
onKeyDown: canPreview ? onPreviewKey : void 0,
|
|
3212
|
+
role: canPreview ? "button" : void 0,
|
|
3213
|
+
tabIndex: canPreview ? 0 : void 0,
|
|
3214
|
+
"aria-label": canPreview ? t("preview") + (alt ? ": " + alt : "") : void 0
|
|
2848
3215
|
},
|
|
2849
3216
|
body
|
|
2850
3217
|
), zoom ? /* @__PURE__ */ React.createElement("span", { className: "cs-image__zoom", role: "dialog", "aria-label": alt || t("preview"), onClick: () => setZoom(false) }, /* @__PURE__ */ React.createElement("img", { src, alt }), /* @__PURE__ */ React.createElement("button", { type: "button", "aria-label": t("close"), onClick: () => setZoom(false) }, /* @__PURE__ */ React.createElement(Icon, { name: "close", size: "sm", style: { verticalAlign: "middle" } }))) : null);
|
|
@@ -3375,21 +3742,51 @@ Object.assign(__ds_scope, { Link });
|
|
|
3375
3742
|
|
|
3376
3743
|
// components/navigation/Menu.jsx
|
|
3377
3744
|
try { (() => {
|
|
3745
|
+
const { makeT, useLang } = __ds_scope;
|
|
3378
3746
|
const { cx } = __ds_scope;
|
|
3379
|
-
function Menu({ trigger, children, align = "start", open: controlledOpen, onOpenChange, className }) {
|
|
3747
|
+
function Menu({ trigger, children, align = "start", open: controlledOpen, onOpenChange, lang, className }) {
|
|
3380
3748
|
const [uOpen, setUOpen] = React.useState(false);
|
|
3381
3749
|
const open = controlledOpen != null ? controlledOpen : uOpen;
|
|
3382
3750
|
const set = (v) => {
|
|
3383
3751
|
onOpenChange ? onOpenChange(v) : setUOpen(v);
|
|
3384
3752
|
};
|
|
3385
|
-
const ref =
|
|
3753
|
+
const [ref, L] = useLang(lang);
|
|
3754
|
+
const t = makeT("Menu", L);
|
|
3755
|
+
const triggerRef = React.useRef(null);
|
|
3756
|
+
const listRef = React.useRef(null);
|
|
3386
3757
|
React.useEffect(() => {
|
|
3387
3758
|
if (!open) return;
|
|
3759
|
+
const items = () => listRef.current ? [...listRef.current.querySelectorAll('[role="menuitem"]:not([disabled])')] : [];
|
|
3760
|
+
const first = items()[0];
|
|
3761
|
+
if (first && first.focus) first.focus();
|
|
3388
3762
|
const onDoc = (e) => {
|
|
3389
3763
|
if (ref.current && !ref.current.contains(e.target)) set(false);
|
|
3390
3764
|
};
|
|
3391
3765
|
const onKey = (e) => {
|
|
3392
|
-
|
|
3766
|
+
const opts = items();
|
|
3767
|
+
if (e.key === "Escape") {
|
|
3768
|
+
e.preventDefault();
|
|
3769
|
+
set(false);
|
|
3770
|
+
const el = triggerRef.current;
|
|
3771
|
+
if (el && el.focus) el.focus();
|
|
3772
|
+
return;
|
|
3773
|
+
}
|
|
3774
|
+
if (!opts.length) return;
|
|
3775
|
+
const active = document.activeElement;
|
|
3776
|
+
const idx = opts.indexOf(active);
|
|
3777
|
+
if (e.key === "ArrowDown") {
|
|
3778
|
+
e.preventDefault();
|
|
3779
|
+
(opts[idx < 0 ? 0 : Math.min(opts.length - 1, idx + 1)] || opts[0]).focus();
|
|
3780
|
+
} else if (e.key === "ArrowUp") {
|
|
3781
|
+
e.preventDefault();
|
|
3782
|
+
(opts[idx < 0 ? opts.length - 1 : Math.max(0, idx - 1)] || opts[0]).focus();
|
|
3783
|
+
} else if (e.key === "Home") {
|
|
3784
|
+
e.preventDefault();
|
|
3785
|
+
opts[0].focus();
|
|
3786
|
+
} else if (e.key === "End") {
|
|
3787
|
+
e.preventDefault();
|
|
3788
|
+
opts[opts.length - 1].focus();
|
|
3789
|
+
}
|
|
3393
3790
|
};
|
|
3394
3791
|
document.addEventListener("mousedown", onDoc);
|
|
3395
3792
|
document.addEventListener("keydown", onKey);
|
|
@@ -3400,14 +3797,29 @@ function Menu({ trigger, children, align = "start", open: controlledOpen, onOpen
|
|
|
3400
3797
|
}, [open]);
|
|
3401
3798
|
const toggle = () => set(!open);
|
|
3402
3799
|
const triggerNode = React.isValidElement(trigger) ? React.cloneElement(trigger, {
|
|
3800
|
+
ref: (node) => {
|
|
3801
|
+
triggerRef.current = node;
|
|
3802
|
+
const r = trigger.ref;
|
|
3803
|
+
if (typeof r === "function") r(node);
|
|
3804
|
+
else if (r && typeof r === "object") r.current = node;
|
|
3805
|
+
},
|
|
3403
3806
|
"aria-haspopup": "menu",
|
|
3404
3807
|
"aria-expanded": open,
|
|
3405
3808
|
onClick: (e) => {
|
|
3406
3809
|
if (typeof trigger.props.onClick === "function") trigger.props.onClick(e);
|
|
3407
3810
|
if (!e.defaultPrevented) toggle();
|
|
3408
3811
|
}
|
|
3409
|
-
}) : /* @__PURE__ */ React.createElement("button", { type: "button", "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle }, trigger);
|
|
3410
|
-
return /* @__PURE__ */ React.createElement("div", { className: cx("cs-menu", className), ref }, triggerNode, open ? /* @__PURE__ */ React.createElement(
|
|
3812
|
+
}) : /* @__PURE__ */ React.createElement("button", { type: "button", ref: triggerRef, "aria-haspopup": "menu", "aria-expanded": open, onClick: toggle }, trigger);
|
|
3813
|
+
return /* @__PURE__ */ React.createElement("div", { className: cx("cs-menu", className), ref }, triggerNode, open ? /* @__PURE__ */ React.createElement(
|
|
3814
|
+
"div",
|
|
3815
|
+
{
|
|
3816
|
+
ref: listRef,
|
|
3817
|
+
className: cx("cs-menu__list", align === "end" && "cs-menu__list--end"),
|
|
3818
|
+
role: "menu",
|
|
3819
|
+
"aria-label": t("menu")
|
|
3820
|
+
},
|
|
3821
|
+
children
|
|
3822
|
+
) : null);
|
|
3411
3823
|
}
|
|
3412
3824
|
function MenuItem({ danger = false, icon, children, className, ...props }) {
|
|
3413
3825
|
return /* @__PURE__ */ React.createElement("button", { type: "button", role: "menuitem", className: cx("cs-menu__item", danger && "cs-menu__item--danger", className), ...props }, icon ? /* @__PURE__ */ React.createElement("span", { "aria-hidden": "true" }, icon) : null, children);
|
|
@@ -3568,11 +3980,14 @@ Object.assign(__ds_scope, { Sidebar, NavItem });
|
|
|
3568
3980
|
|
|
3569
3981
|
// components/navigation/Steps.jsx
|
|
3570
3982
|
try { (() => {
|
|
3983
|
+
const { makeT, useLang } = __ds_scope;
|
|
3571
3984
|
const { cx } = __ds_scope;
|
|
3572
|
-
function Steps({ steps = [], current = 0, className }) {
|
|
3573
|
-
|
|
3985
|
+
function Steps({ steps = [], current = 0, lang, className }) {
|
|
3986
|
+
const [ref, L] = useLang(lang);
|
|
3987
|
+
const t = makeT("Steps", L);
|
|
3988
|
+
return /* @__PURE__ */ React.createElement("div", { ref, className: cx("cs-steps", className), role: "list", "aria-label": t("label") }, steps.map((s, i) => {
|
|
3574
3989
|
const state = i < current ? "done" : i === current ? "active" : "todo";
|
|
3575
|
-
return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("cs-step", `cs-step--${state}`) }, /* @__PURE__ */ React.createElement("span", { className: "cs-step__marker" }, state === "done" ? "✓" : s.n || i + 1), /* @__PURE__ */ React.createElement("span", { className: "cs-step__title" }, s.title), s.body ? /* @__PURE__ */ React.createElement("span", { className: "cs-step__body" }, s.body) : null);
|
|
3990
|
+
return /* @__PURE__ */ React.createElement("div", { key: i, className: cx("cs-step", `cs-step--${state}`), role: "listitem" }, /* @__PURE__ */ React.createElement("span", { className: "cs-step__marker" }, state === "done" ? "✓" : s.n || i + 1), /* @__PURE__ */ React.createElement("span", { className: "cs-step__title" }, s.title), s.body ? /* @__PURE__ */ React.createElement("span", { className: "cs-step__body" }, s.body) : null);
|
|
3576
3991
|
}));
|
|
3577
3992
|
}
|
|
3578
3993
|
Object.assign(__ds_scope, { Steps });
|
|
@@ -3580,10 +3995,13 @@ Object.assign(__ds_scope, { Steps });
|
|
|
3580
3995
|
|
|
3581
3996
|
// components/navigation/Tabs.jsx
|
|
3582
3997
|
try { (() => {
|
|
3998
|
+
const { makeT, useLang } = __ds_scope;
|
|
3583
3999
|
const { cx } = __ds_scope;
|
|
3584
|
-
function Tabs({ tabs = [], value, onChange, className, ...props }) {
|
|
4000
|
+
function Tabs({ tabs = [], value, onChange, lang, className, "aria-label": ariaLabel, ...props }) {
|
|
4001
|
+
const [ref, L] = useLang(lang);
|
|
4002
|
+
const t = makeT("Tabs", L);
|
|
3585
4003
|
const refs = React.useRef([]);
|
|
3586
|
-
const idx = Math.max(0, tabs.findIndex((
|
|
4004
|
+
const idx = Math.max(0, tabs.findIndex((tab) => tab.value === value));
|
|
3587
4005
|
const key = (e, i) => {
|
|
3588
4006
|
let n = null;
|
|
3589
4007
|
if (e.key === "ArrowRight") n = (i + 1) % tabs.length;
|
|
@@ -3596,21 +4014,21 @@ function Tabs({ tabs = [], value, onChange, className, ...props }) {
|
|
|
3596
4014
|
const b = refs.current[n];
|
|
3597
4015
|
if (b) b.focus();
|
|
3598
4016
|
};
|
|
3599
|
-
return /* @__PURE__ */ React.createElement("div", { role: "tablist", className: cx("cs-tabs", className), ...props }, tabs.map((
|
|
4017
|
+
return /* @__PURE__ */ React.createElement("div", { ref, role: "tablist", "aria-label": ariaLabel ?? t("list"), className: cx("cs-tabs", className), ...props }, tabs.map((t2, i) => /* @__PURE__ */ React.createElement(
|
|
3600
4018
|
"button",
|
|
3601
4019
|
{
|
|
3602
|
-
key:
|
|
4020
|
+
key: t2.value,
|
|
3603
4021
|
type: "button",
|
|
3604
4022
|
role: "tab",
|
|
3605
|
-
"aria-selected": value ===
|
|
4023
|
+
"aria-selected": value === t2.value,
|
|
3606
4024
|
tabIndex: i === idx ? 0 : -1,
|
|
3607
4025
|
ref: (el) => refs.current[i] = el,
|
|
3608
4026
|
onKeyDown: (e) => key(e, i),
|
|
3609
4027
|
className: "cs-tab",
|
|
3610
|
-
onClick: () => onChange && onChange(
|
|
4028
|
+
onClick: () => onChange && onChange(t2.value)
|
|
3611
4029
|
},
|
|
3612
|
-
|
|
3613
|
-
|
|
4030
|
+
t2.label,
|
|
4031
|
+
t2.count != null ? /* @__PURE__ */ React.createElement("span", { className: "cs-tab__count" }, t2.count) : null
|
|
3614
4032
|
)));
|
|
3615
4033
|
}
|
|
3616
4034
|
function Tab({ selected = false, count, children, className, ...props }) {
|
|
@@ -3625,12 +4043,21 @@ const { makeT, useLang } = __ds_scope;
|
|
|
3625
4043
|
const { cx } = __ds_scope;
|
|
3626
4044
|
function Toolbar({ items = [], overflowAfter, label, lang, className }) {
|
|
3627
4045
|
const [open, setOpen] = React.useState(false);
|
|
4046
|
+
const [focusIdx, setFocusIdx] = React.useState(0);
|
|
3628
4047
|
const wrap = React.useRef(null);
|
|
4048
|
+
const btnRefs = React.useRef([]);
|
|
4049
|
+
const menuRefs = React.useRef([]);
|
|
3629
4050
|
const [ref, L] = useLang(lang);
|
|
3630
4051
|
const t = makeT("Toolbar", L);
|
|
3631
4052
|
const cut = overflowAfter != null ? overflowAfter : items.length;
|
|
3632
4053
|
const head = items.slice(0, cut);
|
|
3633
4054
|
const tail = items.slice(cut).filter((x) => x !== "-");
|
|
4055
|
+
const hasMore = tail.length > 0;
|
|
4056
|
+
const controlMeta = [];
|
|
4057
|
+
head.forEach((it, i) => {
|
|
4058
|
+
if (it !== "-") controlMeta.push({ kind: "head", headIndex: i });
|
|
4059
|
+
});
|
|
4060
|
+
if (hasMore) controlMeta.push({ kind: "more" });
|
|
3634
4061
|
React.useEffect(() => {
|
|
3635
4062
|
if (!open) return;
|
|
3636
4063
|
const d = (e) => {
|
|
@@ -3639,22 +4066,149 @@ function Toolbar({ items = [], overflowAfter, label, lang, className }) {
|
|
|
3639
4066
|
document.addEventListener("mousedown", d);
|
|
3640
4067
|
return () => document.removeEventListener("mousedown", d);
|
|
3641
4068
|
}, [open]);
|
|
4069
|
+
React.useEffect(() => {
|
|
4070
|
+
if (focusIdx >= controlMeta.length) setFocusIdx(Math.max(0, controlMeta.length - 1));
|
|
4071
|
+
}, [focusIdx, controlMeta.length]);
|
|
4072
|
+
const focusControl = (i) => {
|
|
4073
|
+
const next = Math.max(0, Math.min(controlMeta.length - 1, i));
|
|
4074
|
+
setFocusIdx(next);
|
|
4075
|
+
requestAnimationFrame(() => {
|
|
4076
|
+
const el = btnRefs.current[next];
|
|
4077
|
+
if (el) el.focus();
|
|
4078
|
+
});
|
|
4079
|
+
};
|
|
4080
|
+
const onToolbarKeyDown = (e, i) => {
|
|
4081
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
4082
|
+
if (e.key === "ArrowRight") {
|
|
4083
|
+
e.preventDefault();
|
|
4084
|
+
focusControl(i + 1);
|
|
4085
|
+
} else if (e.key === "ArrowLeft") {
|
|
4086
|
+
e.preventDefault();
|
|
4087
|
+
focusControl(i - 1);
|
|
4088
|
+
} else if (e.key === "Home") {
|
|
4089
|
+
e.preventDefault();
|
|
4090
|
+
focusControl(0);
|
|
4091
|
+
} else if (e.key === "End") {
|
|
4092
|
+
e.preventDefault();
|
|
4093
|
+
focusControl(controlMeta.length - 1);
|
|
4094
|
+
} else if (e.key === "Escape" && open) {
|
|
4095
|
+
e.preventDefault();
|
|
4096
|
+
setOpen(false);
|
|
4097
|
+
}
|
|
4098
|
+
};
|
|
4099
|
+
const onMenuKeyDown = (e, i) => {
|
|
4100
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
4101
|
+
if (e.key === "ArrowDown") {
|
|
4102
|
+
e.preventDefault();
|
|
4103
|
+
const n = Math.min(tail.length - 1, i + 1);
|
|
4104
|
+
const el = menuRefs.current[n];
|
|
4105
|
+
if (el) el.focus();
|
|
4106
|
+
} else if (e.key === "ArrowUp") {
|
|
4107
|
+
e.preventDefault();
|
|
4108
|
+
const n = Math.max(0, i - 1);
|
|
4109
|
+
const el = menuRefs.current[n];
|
|
4110
|
+
if (el) el.focus();
|
|
4111
|
+
} else if (e.key === "Escape") {
|
|
4112
|
+
e.preventDefault();
|
|
4113
|
+
setOpen(false);
|
|
4114
|
+
const moreIdx = controlMeta.findIndex((c) => c.kind === "more");
|
|
4115
|
+
if (moreIdx >= 0) focusControl(moreIdx);
|
|
4116
|
+
} else if (e.key === "Home") {
|
|
4117
|
+
e.preventDefault();
|
|
4118
|
+
const el = menuRefs.current[0];
|
|
4119
|
+
if (el) el.focus();
|
|
4120
|
+
} else if (e.key === "End") {
|
|
4121
|
+
e.preventDefault();
|
|
4122
|
+
const el = menuRefs.current[tail.length - 1];
|
|
4123
|
+
if (el) el.focus();
|
|
4124
|
+
}
|
|
4125
|
+
};
|
|
4126
|
+
let controlI = 0;
|
|
3642
4127
|
return /* @__PURE__ */ React.createElement("div", { ref: (el) => {
|
|
3643
4128
|
wrap.current = el;
|
|
3644
4129
|
ref.current = el;
|
|
3645
|
-
}, className: cx("cs-toolbar", className), role: "toolbar", "aria-label": label
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
|
|
4130
|
+
}, className: cx("cs-toolbar", className), role: "toolbar", "aria-label": label, "aria-orientation": "horizontal" }, head.map((it, i) => {
|
|
4131
|
+
if (it === "-") return /* @__PURE__ */ React.createElement("span", { key: "sep-" + i, className: "cs-toolbar__sep", "aria-hidden": "true" });
|
|
4132
|
+
const ci = controlI++;
|
|
4133
|
+
return /* @__PURE__ */ React.createElement(
|
|
4134
|
+
"button",
|
|
4135
|
+
{
|
|
4136
|
+
key: "btn-" + i,
|
|
4137
|
+
type: "button",
|
|
4138
|
+
className: "cs-toolbar__btn",
|
|
4139
|
+
tabIndex: ci === focusIdx ? 0 : -1,
|
|
4140
|
+
ref: (el) => {
|
|
4141
|
+
btnRefs.current[ci] = el;
|
|
4142
|
+
},
|
|
4143
|
+
onFocus: () => setFocusIdx(ci),
|
|
4144
|
+
onKeyDown: (e) => onToolbarKeyDown(e, ci),
|
|
4145
|
+
onClick: () => it.onSelect && it.onSelect()
|
|
4146
|
+
},
|
|
4147
|
+
it.icon,
|
|
4148
|
+
it.label ? /* @__PURE__ */ React.createElement("span", null, it.label) : null
|
|
4149
|
+
);
|
|
4150
|
+
}), hasMore ? (() => {
|
|
4151
|
+
const ci = controlI++;
|
|
4152
|
+
return /* @__PURE__ */ React.createElement("span", { className: "cs-toolbar__more" }, /* @__PURE__ */ React.createElement(
|
|
4153
|
+
"button",
|
|
4154
|
+
{
|
|
4155
|
+
type: "button",
|
|
4156
|
+
className: "cs-toolbar__btn",
|
|
4157
|
+
"aria-haspopup": "menu",
|
|
4158
|
+
"aria-expanded": open,
|
|
4159
|
+
"aria-label": t("more"),
|
|
4160
|
+
tabIndex: ci === focusIdx ? 0 : -1,
|
|
4161
|
+
ref: (el) => {
|
|
4162
|
+
btnRefs.current[ci] = el;
|
|
4163
|
+
},
|
|
4164
|
+
onFocus: () => setFocusIdx(ci),
|
|
4165
|
+
onKeyDown: (e) => {
|
|
4166
|
+
onToolbarKeyDown(e, ci);
|
|
4167
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
4168
|
+
if (!open) {
|
|
4169
|
+
e.preventDefault();
|
|
4170
|
+
setOpen(true);
|
|
4171
|
+
requestAnimationFrame(() => {
|
|
4172
|
+
const el = menuRefs.current[0];
|
|
4173
|
+
if (el) el.focus();
|
|
4174
|
+
});
|
|
4175
|
+
}
|
|
4176
|
+
}
|
|
4177
|
+
},
|
|
4178
|
+
onClick: () => setOpen((o) => !o)
|
|
4179
|
+
},
|
|
4180
|
+
"⋯"
|
|
4181
|
+
), open ? /* @__PURE__ */ React.createElement("span", { className: "cs-menu__list", role: "menu" }, tail.map((it, i) => /* @__PURE__ */ React.createElement(
|
|
4182
|
+
"button",
|
|
4183
|
+
{
|
|
4184
|
+
key: i,
|
|
4185
|
+
type: "button",
|
|
4186
|
+
role: "menuitem",
|
|
4187
|
+
className: "cs-menu__item",
|
|
4188
|
+
ref: (el) => {
|
|
4189
|
+
menuRefs.current[i] = el;
|
|
4190
|
+
},
|
|
4191
|
+
onKeyDown: (e) => onMenuKeyDown(e, i),
|
|
4192
|
+
onClick: () => {
|
|
4193
|
+
setOpen(false);
|
|
4194
|
+
it.onSelect && it.onSelect();
|
|
4195
|
+
}
|
|
4196
|
+
},
|
|
4197
|
+
it.label
|
|
4198
|
+
))) : null);
|
|
4199
|
+
})() : null);
|
|
3649
4200
|
}
|
|
3650
4201
|
Object.assign(__ds_scope, { Toolbar });
|
|
3651
4202
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/navigation/Toolbar.jsx", error: String((e && e.message) || e) }); }
|
|
3652
4203
|
|
|
3653
4204
|
// components/overlays/ContextMenu.jsx
|
|
3654
4205
|
try { (() => {
|
|
4206
|
+
const { makeT, useLang } = __ds_scope;
|
|
3655
4207
|
const { cx } = __ds_scope;
|
|
3656
|
-
function ContextMenu({ items = [], children, className }) {
|
|
4208
|
+
function ContextMenu({ items = [], children, lang, className }) {
|
|
3657
4209
|
const [pos, setPos] = React.useState(null);
|
|
4210
|
+
const [ref, L] = useLang(lang);
|
|
4211
|
+
const t = makeT("ContextMenu", L);
|
|
3658
4212
|
React.useEffect(() => {
|
|
3659
4213
|
if (!pos) return;
|
|
3660
4214
|
const close = () => setPos(null);
|
|
@@ -3671,6 +4225,7 @@ function ContextMenu({ items = [], children, className }) {
|
|
|
3671
4225
|
return /* @__PURE__ */ React.createElement(
|
|
3672
4226
|
"div",
|
|
3673
4227
|
{
|
|
4228
|
+
ref,
|
|
3674
4229
|
className: cx("cs-ctxmenu-zone", className),
|
|
3675
4230
|
onContextMenu: (e) => {
|
|
3676
4231
|
e.preventDefault();
|
|
@@ -3679,7 +4234,7 @@ function ContextMenu({ items = [], children, className }) {
|
|
|
3679
4234
|
}
|
|
3680
4235
|
},
|
|
3681
4236
|
children,
|
|
3682
|
-
pos ? /* @__PURE__ */ React.createElement("div", { className: "cs-menu__list", role: "menu", style: { position: "absolute", insetInlineStart: pos.x, insetBlockStart: pos.y } }, items.map((it, i) => it === "-" ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-menu__sep" }) : /* @__PURE__ */ React.createElement("button", { key: i, type: "button", role: "menuitem", className: cx("cs-menu__item", it.danger && "cs-menu__item--danger"), onClick: () => {
|
|
4237
|
+
pos ? /* @__PURE__ */ React.createElement("div", { className: "cs-menu__list", role: "menu", "aria-label": t("menu"), style: { position: "absolute", insetInlineStart: pos.x, insetBlockStart: pos.y } }, items.map((it, i) => it === "-" ? /* @__PURE__ */ React.createElement("div", { key: i, className: "cs-menu__sep" }) : /* @__PURE__ */ React.createElement("button", { key: i, type: "button", role: "menuitem", className: cx("cs-menu__item", it.danger && "cs-menu__item--danger"), onClick: () => {
|
|
3683
4238
|
setPos(null);
|
|
3684
4239
|
it.onSelect && it.onSelect();
|
|
3685
4240
|
} }, it.label))) : null
|
|
@@ -4314,738 +4869,12 @@ function TextField({
|
|
|
4314
4869
|
Object.assign(__ds_scope, { TextField });
|
|
4315
4870
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "components/textfield/TextField.jsx", error: String((e && e.message) || e) }); }
|
|
4316
4871
|
|
|
4317
|
-
// image-slot.js
|
|
4318
|
-
try { (() => {
|
|
4319
|
-
(() => {
|
|
4320
|
-
const STATE_FILE = ".image-slots.state.json";
|
|
4321
|
-
const UNSPLASH_HOMEPAGE_HREF = "https://unsplash.com/?utm_source=claude_design&utm_medium=referral";
|
|
4322
|
-
const isUnsplashHost = (u) => {
|
|
4323
|
-
try {
|
|
4324
|
-
return /(^|\.)unsplash\.com$/.test(
|
|
4325
|
-
new URL(u, document.baseURI).hostname.replace(/\.$/, "")
|
|
4326
|
-
);
|
|
4327
|
-
} catch {
|
|
4328
|
-
return false;
|
|
4329
|
-
}
|
|
4330
|
-
};
|
|
4331
|
-
const withReferral = (href) => {
|
|
4332
|
-
try {
|
|
4333
|
-
const u = new URL(href);
|
|
4334
|
-
if (!/(^|\.)unsplash\.com$/.test(u.hostname.replace(/\.$/, ""))) {
|
|
4335
|
-
return href;
|
|
4336
|
-
}
|
|
4337
|
-
if (!u.searchParams.has("utm_source")) {
|
|
4338
|
-
u.searchParams.set("utm_source", "claude_design");
|
|
4339
|
-
}
|
|
4340
|
-
if (!u.searchParams.has("utm_medium")) {
|
|
4341
|
-
u.searchParams.set("utm_medium", "referral");
|
|
4342
|
-
}
|
|
4343
|
-
return u.toString();
|
|
4344
|
-
} catch (e) {
|
|
4345
|
-
return href;
|
|
4346
|
-
}
|
|
4347
|
-
};
|
|
4348
|
-
const MAX_DIM = 1200;
|
|
4349
|
-
const ACCEPT = ["image/png", "image/jpeg", "image/webp", "image/avif"];
|
|
4350
|
-
const subs = /* @__PURE__ */ new Set();
|
|
4351
|
-
let slots = {};
|
|
4352
|
-
const tombstones = /* @__PURE__ */ new Set();
|
|
4353
|
-
let loaded = false;
|
|
4354
|
-
let loadP = null;
|
|
4355
|
-
function load() {
|
|
4356
|
-
if (loadP) return loadP;
|
|
4357
|
-
loadP = fetch(STATE_FILE).then((r) => r.ok ? r.json() : null).then((j) => {
|
|
4358
|
-
if (j && typeof j === "object") {
|
|
4359
|
-
const merged = Object.assign({}, j, slots);
|
|
4360
|
-
for (const k in slots) {
|
|
4361
|
-
if (merged[k] && !merged[k].u && j[k]) {
|
|
4362
|
-
merged[k].u = typeof j[k] === "string" ? j[k] : j[k].u;
|
|
4363
|
-
}
|
|
4364
|
-
}
|
|
4365
|
-
for (const id of tombstones) delete merged[id];
|
|
4366
|
-
slots = merged;
|
|
4367
|
-
}
|
|
4368
|
-
tombstones.clear();
|
|
4369
|
-
}).catch(() => {
|
|
4370
|
-
}).then(() => {
|
|
4371
|
-
loaded = true;
|
|
4372
|
-
subs.forEach((fn) => fn());
|
|
4373
|
-
});
|
|
4374
|
-
return loadP;
|
|
4375
|
-
}
|
|
4376
|
-
let saving = false;
|
|
4377
|
-
let saveDirty = false;
|
|
4378
|
-
function flushNow() {
|
|
4379
|
-
if (!loaded) return;
|
|
4380
|
-
const w = window.omelette && window.omelette.writeFile;
|
|
4381
|
-
if (!w) return;
|
|
4382
|
-
try {
|
|
4383
|
-
Promise.resolve(w(STATE_FILE, JSON.stringify(slots))).catch(() => {
|
|
4384
|
-
});
|
|
4385
|
-
} catch (e) {
|
|
4386
|
-
}
|
|
4387
|
-
}
|
|
4388
|
-
function save() {
|
|
4389
|
-
if (saving) {
|
|
4390
|
-
saveDirty = true;
|
|
4391
|
-
return;
|
|
4392
|
-
}
|
|
4393
|
-
const w = window.omelette && window.omelette.writeFile;
|
|
4394
|
-
if (!w) return;
|
|
4395
|
-
saving = true;
|
|
4396
|
-
Promise.resolve(w(STATE_FILE, JSON.stringify(slots))).catch(() => {
|
|
4397
|
-
}).then(() => {
|
|
4398
|
-
saving = false;
|
|
4399
|
-
if (saveDirty) {
|
|
4400
|
-
saveDirty = false;
|
|
4401
|
-
save();
|
|
4402
|
-
}
|
|
4403
|
-
});
|
|
4404
|
-
}
|
|
4405
|
-
const S_MAX = 5;
|
|
4406
|
-
const clampS = (s) => Math.max(1, Math.min(S_MAX, s));
|
|
4407
|
-
function getSlot(id) {
|
|
4408
|
-
const v = slots[id];
|
|
4409
|
-
if (!v) return null;
|
|
4410
|
-
return typeof v === "string" ? { u: v, s: 1, x: 0, y: 0 } : v;
|
|
4411
|
-
}
|
|
4412
|
-
function setSlot(id, val) {
|
|
4413
|
-
if (!id) return;
|
|
4414
|
-
if (val) {
|
|
4415
|
-
slots[id] = val;
|
|
4416
|
-
tombstones.delete(id);
|
|
4417
|
-
} else {
|
|
4418
|
-
delete slots[id];
|
|
4419
|
-
if (!loaded) tombstones.add(id);
|
|
4420
|
-
}
|
|
4421
|
-
subs.forEach((fn) => fn());
|
|
4422
|
-
if (loaded) save();
|
|
4423
|
-
else load().then(save);
|
|
4424
|
-
}
|
|
4425
|
-
async function toDataUrl(file, targetW) {
|
|
4426
|
-
const bitmap = await createImageBitmap(file);
|
|
4427
|
-
try {
|
|
4428
|
-
const cap = Math.min(MAX_DIM, Math.max(1, Math.round(targetW * 2)) || MAX_DIM);
|
|
4429
|
-
const scale = Math.min(1, cap / Math.max(bitmap.width, bitmap.height));
|
|
4430
|
-
const w = Math.max(1, Math.round(bitmap.width * scale));
|
|
4431
|
-
const h = Math.max(1, Math.round(bitmap.height * scale));
|
|
4432
|
-
const canvas = document.createElement("canvas");
|
|
4433
|
-
canvas.width = w;
|
|
4434
|
-
canvas.height = h;
|
|
4435
|
-
canvas.getContext("2d").drawImage(bitmap, 0, 0, w, h);
|
|
4436
|
-
return canvas.toDataURL("image/webp", 0.85);
|
|
4437
|
-
} finally {
|
|
4438
|
-
bitmap.close && bitmap.close();
|
|
4439
|
-
}
|
|
4440
|
-
}
|
|
4441
|
-
const stylesheet = (
|
|
4442
|
-
// Fill the container by default: slots are usually placed inside a
|
|
4443
|
-
// sized wrapper (a hero frame, a grid cell, an inset:0 layer) and are
|
|
4444
|
-
// expected to take that box — a fixed intrinsic size would render as
|
|
4445
|
-
// a small tile in the corner of a full-bleed wrapper instead.
|
|
4446
|
-
// aspect-ratio is the companion fallback that keeps a bare slot
|
|
4447
|
-
// visible when the parent's height is indefinite: height:100%
|
|
4448
|
-
// resolves to auto there, and the ratio then derives height from
|
|
4449
|
-
// width instead of letting the slot collapse to zero height.
|
|
4450
|
-
// Explicit width/height on the element override all of this.
|
|
4451
|
-
':host{display:block;position:relative; font:13px/1.3 system-ui,-apple-system,sans-serif;color:rgba(0,0,0,.55); width:100%;height:100%;aspect-ratio:3/2}.frame{position:absolute;inset:0;overflow:hidden;background:rgba(0,0,0,.04)}.frame img{position:absolute;max-width:none;transform:translate(-50%,-50%); -webkit-user-drag:none;user-select:none;touch-action:none}.spill{position:fixed;margin:0;inset:auto;border:0;padding:0;background:transparent; overflow:visible;transform:translate(-50%,-50%);z-index:1;cursor:grab;touch-action:none}:host([data-panning]) .spill{cursor:grabbing}.spill .ghost{position:absolute;inset:0;width:100%;height:100%;opacity:.35; pointer-events:none;-webkit-user-drag:none;user-select:none; box-shadow:0 0 0 1px rgba(0,0,0,.2),0 12px 32px rgba(0,0,0,.2)}.spill .handle{position:absolute;width:12px;height:12px;border-radius:50%; background:#fff;box-shadow:0 0 0 1.5px #c96442,0 1px 3px rgba(0,0,0,.3); transform:translate(-50%,-50%)}.spill .handle[data-c=nw]{left:0;top:0;cursor:nwse-resize}.spill .handle[data-c=ne]{left:100%;top:0;cursor:nesw-resize}.spill .handle[data-c=sw]{left:0;top:100%;cursor:nesw-resize}.spill .handle[data-c=se]{left:100%;top:100%;cursor:nwse-resize}:host([data-reframe]){z-index:10}:host([data-reframe]) .frame{box-shadow:0 0 0 2px #c96442}.empty{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center; justify-content:center;gap:6px;text-align:center;padding:12px;box-sizing:border-box; cursor:pointer;user-select:none}.empty svg{opacity:.45}.empty .cap{max-width:90%;font-weight:500;letter-spacing:.01em}.empty .sub{font-size:11px}.empty .sub u{text-underline-offset:2px;text-decoration-color:rgba(0,0,0,.25)}.empty:hover .sub u{color:rgba(0,0,0,.75);text-decoration-color:currentColor}:host([data-over]) .frame{outline:2px solid #c96442;outline-offset:-2px; background:rgba(201,100,66,.10)}.ring{position:absolute;inset:0;pointer-events:none;border:1.5px dashed rgba(0,0,0,.25); transition:border-color .12s}:host([data-over]) .ring{border-color:#c96442}:host([data-filled]) .ring{display:none}.ctl{position:absolute;inset:auto;top:8px;right:8px;margin:0;border:0;padding:0; background:transparent;overflow:visible; display:flex;gap:6px;opacity:0;pointer-events:none;transition:opacity .12s;z-index:2; white-space:nowrap}.ctl:popover-open{position:fixed;inset:auto;transform:translateX(-100%)}:host([data-filled][data-editable]:hover) .ctl,:host([data-reframe]) .ctl {opacity:1;pointer-events:auto}.ctl button{appearance:none;border:0;border-radius:6px;padding:5px 10px;cursor:pointer; background:rgba(0,0,0,.65);color:#fff;font:11px/1 system-ui,-apple-system,sans-serif; backdrop-filter:blur(6px)}.ctl button:hover{background:rgba(0,0,0,.8)}.err{position:absolute;left:8px;bottom:8px;right:8px;color:#b3261e;font-size:11px; background:rgba(255,255,255,.85);padding:4px 6px;border-radius:5px;pointer-events:none}:host([data-swapping]) .frame img{visibility:hidden}.loading{position:absolute;inset:0;display:none;align-items:center; justify-content:center;pointer-events:none}:host([data-swapping]) .loading{display:flex}.loading::after{content:"";width:22px;height:22px;border-radius:50%; border:2px solid rgba(0,0,0,.12);border-top-color:rgba(0,0,0,.45); animation:om-slot-spin .7s linear infinite}@keyframes om-slot-spin{to{transform:rotate(360deg)}}@media (prefers-reduced-motion:reduce){.loading::after{animation:none}}.credit{position:absolute;left:6px;bottom:6px;max-width:calc(100% - 12px);display:none; padding:3px 7px;border-radius:5px;background:rgba(0,0,0,.55);color:#fff; font:10px/1.2 system-ui,-apple-system,sans-serif;text-decoration:none; white-space:nowrap;overflow:hidden;text-overflow:ellipsis;backdrop-filter:blur(6px)}.credit a{color:inherit;text-decoration:none}.credit a:hover,.credit a:focus-visible{text-decoration:underline}:host([data-filled][data-credit]) .credit{display:block}:host-context([data-om-exporting]) .ctl,:host-context([data-om-exporting]) .credit{display:none !important}.attr-error{position:absolute;inset:0;display:none;flex-direction:column;align-items:center; justify-content:center;gap:6px;text-align:center;padding:12px;box-sizing:border-box; background:#f2f1ef;color:#6e6c66;user-select:none; font:13px/1.45 system-ui,-apple-system,sans-serif}.attr-error svg{opacity:.55}.attr-error .cap{max-width:92%;font-weight:500;letter-spacing:.01em}:host([data-attribution-error]) .attr-error{display:flex}:host([data-attribution-error]) .ring{display:none}'
|
|
4452
|
-
);
|
|
4453
|
-
const icon = '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><path d="m21 15-5-5L5 21"/></svg>';
|
|
4454
|
-
const warnIcon = '<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path d="m21.73 18-8-14a2 2 0 0 0-3.46 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>';
|
|
4455
|
-
class ImageSlot extends HTMLElement {
|
|
4456
|
-
static get observedAttributes() {
|
|
4457
|
-
return ["shape", "radius", "mask", "fit", "placeholder", "src", "id", "credit", "credit-href"];
|
|
4458
|
-
}
|
|
4459
|
-
/** Duplicate-slide hook (called by deck-stage, see its
|
|
4460
|
-
* _remintDuplicateIds): copy this id's stored image, if any, under a
|
|
4461
|
-
* freshly minted key and return that key — so a duplicated slide's
|
|
4462
|
-
* slot keeps its dropped photo instead of reverting to the
|
|
4463
|
-
* placeholder. 'isFree' is the caller's uniqueness check (document
|
|
4464
|
-
* ids); candidates must ALSO be unused in the sidecar, which can
|
|
4465
|
-
* hold keys from other pages sharing the project root. (An EMPTY
|
|
4466
|
-
* slot on another page leaves no sidecar entry, so its id is not
|
|
4467
|
-
* detectable here — a minted key can collide with it and that slot
|
|
4468
|
-
* would show this photo. Same blast radius as two pages reusing an
|
|
4469
|
-
* id by hand, which the shared sidecar already permits.) Returns null
|
|
4470
|
-
* when no id could be minted (caller strips the id, today's
|
|
4471
|
-
* behavior). */
|
|
4472
|
-
static cloneSlot(fromId, isFree) {
|
|
4473
|
-
if (typeof fromId !== "string" || !fromId) return null;
|
|
4474
|
-
if (!loaded) return null;
|
|
4475
|
-
const stem = fromId.replace(/-\d+$/, "") || fromId;
|
|
4476
|
-
for (let n = 2; n < 100; n++) {
|
|
4477
|
-
const toId = stem + "-" + n;
|
|
4478
|
-
if (toId === fromId) continue;
|
|
4479
|
-
if (slots[toId] !== void 0) {
|
|
4480
|
-
const prev = getSlot(toId);
|
|
4481
|
-
const cur = getSlot(fromId);
|
|
4482
|
-
if (!(prev && cur && prev.u && prev.u === cur.u && prev.s === cur.s && prev.x === cur.x && prev.y === cur.y && (typeof isFree !== "function" || isFree(toId)))) continue;
|
|
4483
|
-
return toId;
|
|
4484
|
-
}
|
|
4485
|
-
if (typeof isFree === "function" && !isFree(toId)) continue;
|
|
4486
|
-
const v = getSlot(fromId);
|
|
4487
|
-
if (v) setSlot(toId, Object.assign({}, v));
|
|
4488
|
-
return toId;
|
|
4489
|
-
}
|
|
4490
|
-
return null;
|
|
4491
|
-
}
|
|
4492
|
-
constructor() {
|
|
4493
|
-
super();
|
|
4494
|
-
const root = this.shadowRoot || this.attachShadow({ mode: "open", clonable: true });
|
|
4495
|
-
root.innerHTML = "<style>" + stylesheet + '</style><div class="frame" part="frame"> <img part="image" alt="" draggable="false" style="display:none"> <div class="empty" part="empty">' + icon + ' <div class="cap"></div> <div class="sub">or <u>browse files</u></div></div> <div class="attr-error" part="attribution-error">' + warnIcon + ' <div class="cap">This photo needs attribution</div></div> <div class="loading" part="loading"></div> <div class="ring" part="ring"></div></div><span class="credit" part="credit"></span><div class="spill" popover="manual" data-dc-edit-transparent> <img class="ghost" alt="" draggable="false"> <div class="handle" data-c="nw"></div><div class="handle" data-c="ne"></div> <div class="handle" data-c="sw"></div><div class="handle" data-c="se"></div></div><div class="ctl" popover="manual" data-dc-edit-transparent><button data-act="replace" title="Replace image">Replace</button> <button data-act="edit" title="Reframe image">Edit</button></div><input type="file" accept="' + ACCEPT.join(",") + '" hidden>';
|
|
4496
|
-
this._frame = root.querySelector(".frame");
|
|
4497
|
-
this._ring = root.querySelector(".ring");
|
|
4498
|
-
this._img = root.querySelector(".frame img");
|
|
4499
|
-
this._empty = root.querySelector(".empty");
|
|
4500
|
-
this._cap = root.querySelector(".cap");
|
|
4501
|
-
this._sub = root.querySelector(".sub");
|
|
4502
|
-
this._spill = root.querySelector(".spill");
|
|
4503
|
-
this._ctl = root.querySelector(".ctl");
|
|
4504
|
-
this._credit = root.querySelector(".credit");
|
|
4505
|
-
this._attrError = root.querySelector(".attr-error");
|
|
4506
|
-
this._credit.addEventListener("click", (e) => e.stopPropagation());
|
|
4507
|
-
this._credit.addEventListener("dblclick", (e) => e.stopPropagation());
|
|
4508
|
-
this._ghost = root.querySelector(".ghost");
|
|
4509
|
-
this._err = null;
|
|
4510
|
-
this._input = root.querySelector("input");
|
|
4511
|
-
this._depth = 0;
|
|
4512
|
-
this._gen = 0;
|
|
4513
|
-
this._swapGen = 0;
|
|
4514
|
-
this._loadPending = false;
|
|
4515
|
-
this._hidShowing = false;
|
|
4516
|
-
this._view = { s: 1, x: 0, y: 0 };
|
|
4517
|
-
this._subFn = () => this._render();
|
|
4518
|
-
this._empty.addEventListener("click", () => this._input.click());
|
|
4519
|
-
root.addEventListener("click", (e) => {
|
|
4520
|
-
const act = e.target && e.target.getAttribute && e.target.getAttribute("data-act");
|
|
4521
|
-
if (!act) return;
|
|
4522
|
-
if (!this.hasAttribute("data-editable")) return;
|
|
4523
|
-
if (act === "replace") {
|
|
4524
|
-
this._exitReframe(true);
|
|
4525
|
-
this.dispatchEvent(new CustomEvent("image-slot:pick", {
|
|
4526
|
-
bubbles: true,
|
|
4527
|
-
composed: true,
|
|
4528
|
-
detail: { id: this.id || null }
|
|
4529
|
-
}));
|
|
4530
|
-
}
|
|
4531
|
-
if (act === "edit") {
|
|
4532
|
-
if (!this._reframes()) return;
|
|
4533
|
-
if (this.hasAttribute("data-reframe")) this._exitReframe(true);
|
|
4534
|
-
else this._enterReframe();
|
|
4535
|
-
}
|
|
4536
|
-
});
|
|
4537
|
-
this._input.addEventListener("change", () => {
|
|
4538
|
-
const f = this._input.files && this._input.files[0];
|
|
4539
|
-
if (f) this._ingest(f);
|
|
4540
|
-
this._input.value = "";
|
|
4541
|
-
});
|
|
4542
|
-
this._img.addEventListener("load", () => {
|
|
4543
|
-
this._loadPending = false;
|
|
4544
|
-
this._releaseMask(true);
|
|
4545
|
-
this._applyView();
|
|
4546
|
-
});
|
|
4547
|
-
this._img.addEventListener("error", () => {
|
|
4548
|
-
this._loadPending = false;
|
|
4549
|
-
this._releaseMask(true);
|
|
4550
|
-
});
|
|
4551
|
-
this.addEventListener("dblclick", (e) => {
|
|
4552
|
-
if (!this.hasAttribute("data-editable") || !this._reframes()) return;
|
|
4553
|
-
e.preventDefault();
|
|
4554
|
-
if (this.hasAttribute("data-reframe")) this._exitReframe(true);
|
|
4555
|
-
else this._enterReframe();
|
|
4556
|
-
});
|
|
4557
|
-
this._spill.addEventListener("pointerdown", (e) => {
|
|
4558
|
-
if (e.button !== 0 || !this.hasAttribute("data-reframe")) return;
|
|
4559
|
-
e.preventDefault();
|
|
4560
|
-
e.stopPropagation();
|
|
4561
|
-
this._spill.setPointerCapture(e.pointerId);
|
|
4562
|
-
const rect = this.getBoundingClientRect();
|
|
4563
|
-
const fw = rect.width || 1, fh = rect.height || 1;
|
|
4564
|
-
const corner = e.target.getAttribute && e.target.getAttribute("data-c");
|
|
4565
|
-
let move;
|
|
4566
|
-
if (corner) {
|
|
4567
|
-
const iw = this._img.naturalWidth || 1, ih = this._img.naturalHeight || 1;
|
|
4568
|
-
const contain = (this.getAttribute("fit") || "cover").toLowerCase() === "contain";
|
|
4569
|
-
const base = contain ? Math.min(fw / iw, fh / ih) : Math.max(fw / iw, fh / ih);
|
|
4570
|
-
const sx = corner.includes("e") ? 1 : -1;
|
|
4571
|
-
const sy = corner.includes("s") ? 1 : -1;
|
|
4572
|
-
const s0 = this._view.s;
|
|
4573
|
-
const w0 = iw * base * s0, h0 = ih * base * s0;
|
|
4574
|
-
const cx0 = (50 + this._view.x) / 100 * fw;
|
|
4575
|
-
const cy0 = (50 + this._view.y) / 100 * fh;
|
|
4576
|
-
const ox = cx0 - sx * w0 / 2, oy = cy0 - sy * h0 / 2;
|
|
4577
|
-
const diag0 = Math.hypot(w0, h0);
|
|
4578
|
-
const ux = sx * w0 / diag0, uy = sy * h0 / diag0;
|
|
4579
|
-
move = (ev) => {
|
|
4580
|
-
const proj = (ev.clientX - rect.left - ox) * ux + (ev.clientY - rect.top - oy) * uy;
|
|
4581
|
-
const s = clampS(s0 * proj / diag0);
|
|
4582
|
-
const d = diag0 * s / s0;
|
|
4583
|
-
this._view.s = s;
|
|
4584
|
-
this._view.x = (ox + ux * d / 2) / fw * 100 - 50;
|
|
4585
|
-
this._view.y = (oy + uy * d / 2) / fh * 100 - 50;
|
|
4586
|
-
this._clampView();
|
|
4587
|
-
this._applyView();
|
|
4588
|
-
};
|
|
4589
|
-
} else {
|
|
4590
|
-
this.setAttribute("data-panning", "");
|
|
4591
|
-
const start = { px: e.clientX, py: e.clientY, x: this._view.x, y: this._view.y };
|
|
4592
|
-
move = (ev) => {
|
|
4593
|
-
this._view.x = start.x + (ev.clientX - start.px) / fw * 100;
|
|
4594
|
-
this._view.y = start.y + (ev.clientY - start.py) / fh * 100;
|
|
4595
|
-
this._clampView();
|
|
4596
|
-
this._applyView();
|
|
4597
|
-
};
|
|
4598
|
-
}
|
|
4599
|
-
const up = () => {
|
|
4600
|
-
try {
|
|
4601
|
-
this._spill.releasePointerCapture(e.pointerId);
|
|
4602
|
-
} catch {
|
|
4603
|
-
}
|
|
4604
|
-
this._spill.removeEventListener("pointermove", move);
|
|
4605
|
-
this._spill.removeEventListener("pointerup", up);
|
|
4606
|
-
this._spill.removeEventListener("pointercancel", up);
|
|
4607
|
-
this.removeAttribute("data-panning");
|
|
4608
|
-
this._dragUp = null;
|
|
4609
|
-
};
|
|
4610
|
-
this._dragUp = up;
|
|
4611
|
-
this._spill.addEventListener("pointermove", move);
|
|
4612
|
-
this._spill.addEventListener("pointerup", up);
|
|
4613
|
-
this._spill.addEventListener("pointercancel", up);
|
|
4614
|
-
});
|
|
4615
|
-
this.addEventListener("wheel", (e) => {
|
|
4616
|
-
if (!this.hasAttribute("data-reframe")) return;
|
|
4617
|
-
e.preventDefault();
|
|
4618
|
-
const r = this.getBoundingClientRect();
|
|
4619
|
-
const cx = (e.clientX - r.left) / r.width * 100 - 50;
|
|
4620
|
-
const cy = (e.clientY - r.top) / r.height * 100 - 50;
|
|
4621
|
-
const prev = this._view.s;
|
|
4622
|
-
const next = clampS(prev * Math.pow(1.0015, -e.deltaY));
|
|
4623
|
-
if (next === prev) return;
|
|
4624
|
-
const k = next / prev;
|
|
4625
|
-
this._view.s = next;
|
|
4626
|
-
this._view.x = cx * (1 - k) + this._view.x * k;
|
|
4627
|
-
this._view.y = cy * (1 - k) + this._view.y * k;
|
|
4628
|
-
this._clampView();
|
|
4629
|
-
this._applyView();
|
|
4630
|
-
}, { passive: false });
|
|
4631
|
-
}
|
|
4632
|
-
connectedCallback() {
|
|
4633
|
-
if (!this.id && !ImageSlot._warned) {
|
|
4634
|
-
ImageSlot._warned = true;
|
|
4635
|
-
console.warn("<image-slot> without an id will not persist its dropped image.");
|
|
4636
|
-
}
|
|
4637
|
-
this.addEventListener("dragenter", this);
|
|
4638
|
-
this.addEventListener("dragover", this);
|
|
4639
|
-
this.addEventListener("dragleave", this);
|
|
4640
|
-
this.addEventListener("drop", this);
|
|
4641
|
-
subs.add(this._subFn);
|
|
4642
|
-
this.addEventListener("pointerenter", this._subFn);
|
|
4643
|
-
this._ro = new ResizeObserver(() => this._render());
|
|
4644
|
-
this._ro.observe(this);
|
|
4645
|
-
load();
|
|
4646
|
-
this._render();
|
|
4647
|
-
}
|
|
4648
|
-
disconnectedCallback() {
|
|
4649
|
-
subs.delete(this._subFn);
|
|
4650
|
-
this.removeEventListener("pointerenter", this._subFn);
|
|
4651
|
-
this.removeEventListener("dragenter", this);
|
|
4652
|
-
this.removeEventListener("dragover", this);
|
|
4653
|
-
this.removeEventListener("dragleave", this);
|
|
4654
|
-
this.removeEventListener("drop", this);
|
|
4655
|
-
if (this._ro) {
|
|
4656
|
-
this._ro.disconnect();
|
|
4657
|
-
this._ro = null;
|
|
4658
|
-
}
|
|
4659
|
-
this._exitReframe(false);
|
|
4660
|
-
}
|
|
4661
|
-
_enterReframe() {
|
|
4662
|
-
if (this.hasAttribute("data-reframe")) return;
|
|
4663
|
-
this.setAttribute("data-reframe", "");
|
|
4664
|
-
this._signalReframe(true);
|
|
4665
|
-
this._pagehide = () => {
|
|
4666
|
-
this._exitReframe(true);
|
|
4667
|
-
flushNow();
|
|
4668
|
-
};
|
|
4669
|
-
window.addEventListener("pagehide", this._pagehide);
|
|
4670
|
-
try {
|
|
4671
|
-
this._spill.showPopover();
|
|
4672
|
-
} catch {
|
|
4673
|
-
}
|
|
4674
|
-
try {
|
|
4675
|
-
this._ctl.showPopover();
|
|
4676
|
-
} catch {
|
|
4677
|
-
}
|
|
4678
|
-
this._reposition = () => {
|
|
4679
|
-
if (this.hasAttribute("data-reframe")) this._applyView();
|
|
4680
|
-
};
|
|
4681
|
-
window.addEventListener("scroll", this._reposition, true);
|
|
4682
|
-
window.addEventListener("resize", this._reposition);
|
|
4683
|
-
this._lastRect = "";
|
|
4684
|
-
this._watch = () => {
|
|
4685
|
-
if (!this.hasAttribute("data-reframe")) return;
|
|
4686
|
-
const r = this.getBoundingClientRect();
|
|
4687
|
-
const key = r.left + "," + r.top + "," + r.width + "," + r.height;
|
|
4688
|
-
if (key !== this._lastRect) {
|
|
4689
|
-
this._lastRect = key;
|
|
4690
|
-
this._applyView();
|
|
4691
|
-
}
|
|
4692
|
-
this._watchId = requestAnimationFrame(this._watch);
|
|
4693
|
-
};
|
|
4694
|
-
this._watchId = requestAnimationFrame(this._watch);
|
|
4695
|
-
this._applyView();
|
|
4696
|
-
this._outside = (e) => {
|
|
4697
|
-
if (e.composedPath && e.composedPath().includes(this)) return;
|
|
4698
|
-
this._exitReframe(true);
|
|
4699
|
-
};
|
|
4700
|
-
this._esc = (e) => {
|
|
4701
|
-
if (e.key === "Escape") this._exitReframe(true);
|
|
4702
|
-
};
|
|
4703
|
-
document.addEventListener("pointerdown", this._outside, true);
|
|
4704
|
-
document.addEventListener("keydown", this._esc, true);
|
|
4705
|
-
}
|
|
4706
|
-
_exitReframe(commit) {
|
|
4707
|
-
if (!this.hasAttribute("data-reframe")) return;
|
|
4708
|
-
if (this._dragUp) this._dragUp();
|
|
4709
|
-
this.removeAttribute("data-reframe");
|
|
4710
|
-
this.removeAttribute("data-panning");
|
|
4711
|
-
if (this._outside) document.removeEventListener("pointerdown", this._outside, true);
|
|
4712
|
-
if (this._esc) document.removeEventListener("keydown", this._esc, true);
|
|
4713
|
-
this._outside = this._esc = null;
|
|
4714
|
-
if (this._reposition) {
|
|
4715
|
-
window.removeEventListener("scroll", this._reposition, true);
|
|
4716
|
-
window.removeEventListener("resize", this._reposition);
|
|
4717
|
-
this._reposition = null;
|
|
4718
|
-
}
|
|
4719
|
-
if (this._watchId) {
|
|
4720
|
-
cancelAnimationFrame(this._watchId);
|
|
4721
|
-
this._watchId = 0;
|
|
4722
|
-
}
|
|
4723
|
-
if (this._pagehide) {
|
|
4724
|
-
window.removeEventListener("pagehide", this._pagehide);
|
|
4725
|
-
this._pagehide = null;
|
|
4726
|
-
}
|
|
4727
|
-
try {
|
|
4728
|
-
this._spill.hidePopover();
|
|
4729
|
-
} catch {
|
|
4730
|
-
}
|
|
4731
|
-
try {
|
|
4732
|
-
this._ctl.hidePopover();
|
|
4733
|
-
} catch {
|
|
4734
|
-
}
|
|
4735
|
-
this._ctl.style.left = "";
|
|
4736
|
-
this._ctl.style.top = "";
|
|
4737
|
-
if (commit) this._commitView();
|
|
4738
|
-
this._signalReframe(false);
|
|
4739
|
-
}
|
|
4740
|
-
// Reframe state lives only in this DOM until commit, invisible to the
|
|
4741
|
-
// host's dirty signals — announce enter/exit so the host can hold
|
|
4742
|
-
// auto-reloads for exactly the gesture (the guest bundle forwards
|
|
4743
|
-
// image-slot:reframe to the host as imageSlotReframe). Dispatched on
|
|
4744
|
-
// the element (composed, so it escapes shadow roots) while connected;
|
|
4745
|
-
// a disconnected exit (disconnectedCallback) falls back to document so
|
|
4746
|
-
// the host still hears it.
|
|
4747
|
-
_signalReframe(active) {
|
|
4748
|
-
const target = this.isConnected ? this : document;
|
|
4749
|
-
target.dispatchEvent(new CustomEvent("image-slot:reframe", {
|
|
4750
|
-
bubbles: true,
|
|
4751
|
-
composed: true,
|
|
4752
|
-
detail: { active, id: this.id || null }
|
|
4753
|
-
}));
|
|
4754
|
-
}
|
|
4755
|
-
// Public: host's "Import from computer" calls this to run local browse.
|
|
4756
|
-
openFilePicker() {
|
|
4757
|
-
this._exitReframe(true);
|
|
4758
|
-
this._input.click();
|
|
4759
|
-
}
|
|
4760
|
-
attributeChangedCallback() {
|
|
4761
|
-
if (this.shadowRoot) this._render();
|
|
4762
|
-
}
|
|
4763
|
-
// handleEvent — one listener object for all four drag events keeps the
|
|
4764
|
-
// add/remove symmetric and the depth counter correct.
|
|
4765
|
-
handleEvent(e) {
|
|
4766
|
-
if (e.type === "dragenter" || e.type === "dragover") {
|
|
4767
|
-
e.preventDefault();
|
|
4768
|
-
e.stopPropagation();
|
|
4769
|
-
if (e.dataTransfer) e.dataTransfer.dropEffect = "copy";
|
|
4770
|
-
if (e.type === "dragenter") this._depth++;
|
|
4771
|
-
this.setAttribute("data-over", "");
|
|
4772
|
-
} else if (e.type === "dragleave") {
|
|
4773
|
-
if (--this._depth <= 0) {
|
|
4774
|
-
this._depth = 0;
|
|
4775
|
-
this.removeAttribute("data-over");
|
|
4776
|
-
}
|
|
4777
|
-
} else if (e.type === "drop") {
|
|
4778
|
-
e.preventDefault();
|
|
4779
|
-
e.stopPropagation();
|
|
4780
|
-
this._depth = 0;
|
|
4781
|
-
this.removeAttribute("data-over");
|
|
4782
|
-
const f = e.dataTransfer && e.dataTransfer.files && e.dataTransfer.files[0];
|
|
4783
|
-
if (f) this._ingest(f);
|
|
4784
|
-
}
|
|
4785
|
-
}
|
|
4786
|
-
async _ingest(file) {
|
|
4787
|
-
this._setError(null);
|
|
4788
|
-
if (!file || ACCEPT.indexOf(file.type) < 0) {
|
|
4789
|
-
this._setError("Drop a PNG, JPEG, WebP, or AVIF image.");
|
|
4790
|
-
return;
|
|
4791
|
-
}
|
|
4792
|
-
const gen = ++this._gen;
|
|
4793
|
-
if (this.hasAttribute("data-filled")) {
|
|
4794
|
-
this.setAttribute("data-swapping", "");
|
|
4795
|
-
this._swapGen = gen;
|
|
4796
|
-
}
|
|
4797
|
-
try {
|
|
4798
|
-
const w = this.clientWidth || this.offsetWidth || MAX_DIM;
|
|
4799
|
-
const url = await toDataUrl(file, w);
|
|
4800
|
-
if (gen !== this._gen) return;
|
|
4801
|
-
this._exitReframe(false);
|
|
4802
|
-
this._swapGen = 0;
|
|
4803
|
-
const val = { u: url, s: 1, x: 0, y: 0 };
|
|
4804
|
-
setSlot(this.id || "", val);
|
|
4805
|
-
if (!this.id) {
|
|
4806
|
-
this._local = val;
|
|
4807
|
-
this._render();
|
|
4808
|
-
}
|
|
4809
|
-
} catch (err) {
|
|
4810
|
-
if (gen !== this._gen) return;
|
|
4811
|
-
this._swapGen = 0;
|
|
4812
|
-
this._releaseMask();
|
|
4813
|
-
this._setError("Could not read that image.");
|
|
4814
|
-
console.warn("<image-slot> ingest failed:", err);
|
|
4815
|
-
}
|
|
4816
|
-
}
|
|
4817
|
-
_setError(msg) {
|
|
4818
|
-
if (this._err) {
|
|
4819
|
-
this._err.remove();
|
|
4820
|
-
this._err = null;
|
|
4821
|
-
}
|
|
4822
|
-
if (!msg) return;
|
|
4823
|
-
const d = document.createElement("div");
|
|
4824
|
-
d.className = "err";
|
|
4825
|
-
d.textContent = msg;
|
|
4826
|
-
this.shadowRoot.appendChild(d);
|
|
4827
|
-
this._err = d;
|
|
4828
|
-
setTimeout(() => {
|
|
4829
|
-
if (this._err === d) {
|
|
4830
|
-
d.remove();
|
|
4831
|
-
this._err = null;
|
|
4832
|
-
}
|
|
4833
|
-
}, 3e3);
|
|
4834
|
-
}
|
|
4835
|
-
// Reframing (pan/resize) is available on any filled slot — the user can
|
|
4836
|
-
// always reposition/scale. `fit` only sets the initial baseline (see
|
|
4837
|
-
// _geom): contain starts fully-visible, cover starts frame-filling.
|
|
4838
|
-
_reframes() {
|
|
4839
|
-
return this.hasAttribute("data-filled");
|
|
4840
|
-
}
|
|
4841
|
-
// The single release discipline for the replacement-in-flight mask
|
|
4842
|
-
// (data-swapping). The mask comes off only when BOTH hold:
|
|
4843
|
-
// - no encode is pending (_swapGen) — mid-encode the stored value
|
|
4844
|
-
// still resolves to the old image, so any reveal paints it;
|
|
4845
|
-
// - the frame img has settled on its current src — an unsettled src
|
|
4846
|
-
// means some replacement is still in flight (e.g. a remote pick),
|
|
4847
|
-
// whoever started it, and revealing would paint the previous
|
|
4848
|
-
// frame. The load/error listeners pass settled=true (the event IS
|
|
4849
|
-
// the settlement signal, per spec complete is true by then);
|
|
4850
|
-
// other callers rely on the complete flag (covers loaded AND
|
|
4851
|
-
// failed).
|
|
4852
|
-
// Every release path funnels through here EXCEPT _render's empty
|
|
4853
|
-
// branch (the img is being cleared — nothing will ever settle).
|
|
4854
|
-
_releaseMask(settled) {
|
|
4855
|
-
if (!this._swapGen && !this._loadPending && (settled || this._img.complete)) {
|
|
4856
|
-
this.removeAttribute("data-swapping");
|
|
4857
|
-
}
|
|
4858
|
-
}
|
|
4859
|
-
// Baseline geometry, shared by clamp/apply/resize. `base` is the scale at
|
|
4860
|
-
// view-scale s=1: cover = fill the frame (overflow on the looser axis),
|
|
4861
|
-
// contain = fit fully inside (letterboxed). Zooming a contain image past
|
|
4862
|
-
// s where it overflows naturally becomes a crop. Null until the img has
|
|
4863
|
-
// loaded (naturalWidth is 0 before that) or when the slot has no layout
|
|
4864
|
-
// box — ResizeObserver fires with a 0×0 rect under display:none, and
|
|
4865
|
-
// clamping against a degenerate 1×1 frame would silently pull the stored
|
|
4866
|
-
// pan toward zero.
|
|
4867
|
-
_geom() {
|
|
4868
|
-
const iw = this._img.naturalWidth, ih = this._img.naturalHeight;
|
|
4869
|
-
const fw = this.clientWidth, fh = this.clientHeight;
|
|
4870
|
-
if (!iw || !ih || !fw || !fh) return null;
|
|
4871
|
-
const contain = (this.getAttribute("fit") || "cover").toLowerCase() === "contain";
|
|
4872
|
-
const base = contain ? Math.min(fw / iw, fh / ih) : Math.max(fw / iw, fh / ih);
|
|
4873
|
-
return { iw, ih, fw, fh, base };
|
|
4874
|
-
}
|
|
4875
|
-
_clampView() {
|
|
4876
|
-
const g = this._geom();
|
|
4877
|
-
if (!g) return;
|
|
4878
|
-
const mx = Math.max(0, (g.iw * g.base * this._view.s / g.fw - 1) * 50);
|
|
4879
|
-
const my = Math.max(0, (g.ih * g.base * this._view.s / g.fh - 1) * 50);
|
|
4880
|
-
this._view.x = Math.max(-mx, Math.min(mx, this._view.x));
|
|
4881
|
-
this._view.y = Math.max(-my, Math.min(my, this._view.y));
|
|
4882
|
-
}
|
|
4883
|
-
_applyView() {
|
|
4884
|
-
const g = this._geom();
|
|
4885
|
-
if (this.hasAttribute("data-reframe")) {
|
|
4886
|
-
let onTop = false;
|
|
4887
|
-
try {
|
|
4888
|
-
onTop = this._ctl.matches(":popover-open");
|
|
4889
|
-
} catch {
|
|
4890
|
-
}
|
|
4891
|
-
if (onTop) {
|
|
4892
|
-
const r = this.getBoundingClientRect();
|
|
4893
|
-
this._ctl.style.left = r.right - 8 + "px";
|
|
4894
|
-
this._ctl.style.top = r.top + 8 + "px";
|
|
4895
|
-
}
|
|
4896
|
-
}
|
|
4897
|
-
if (!g) {
|
|
4898
|
-
const contain = (this.getAttribute("fit") || "cover").toLowerCase() === "contain";
|
|
4899
|
-
this._img.style.width = "100%";
|
|
4900
|
-
this._img.style.height = "100%";
|
|
4901
|
-
this._img.style.left = "50%";
|
|
4902
|
-
this._img.style.top = "50%";
|
|
4903
|
-
this._img.style.objectFit = contain ? "contain" : "cover";
|
|
4904
|
-
return;
|
|
4905
|
-
}
|
|
4906
|
-
const k = g.base * this._view.s;
|
|
4907
|
-
const w = g.iw * k / g.fw * 100 + "%";
|
|
4908
|
-
const h = g.ih * k / g.fh * 100 + "%";
|
|
4909
|
-
const l = 50 + this._view.x + "%";
|
|
4910
|
-
const t = 50 + this._view.y + "%";
|
|
4911
|
-
this._img.style.width = w;
|
|
4912
|
-
this._img.style.height = h;
|
|
4913
|
-
this._img.style.left = l;
|
|
4914
|
-
this._img.style.top = t;
|
|
4915
|
-
this._img.style.objectFit = "";
|
|
4916
|
-
if (this.hasAttribute("data-reframe")) {
|
|
4917
|
-
const r = this.getBoundingClientRect();
|
|
4918
|
-
const sx = g.fw ? r.width / g.fw : 1;
|
|
4919
|
-
const sy = g.fh ? r.height / g.fh : 1;
|
|
4920
|
-
this._spill.style.width = g.iw * k * sx + "px";
|
|
4921
|
-
this._spill.style.height = g.ih * k * sy + "px";
|
|
4922
|
-
this._spill.style.left = r.left + (50 + this._view.x) / 100 * r.width + "px";
|
|
4923
|
-
this._spill.style.top = r.top + (50 + this._view.y) / 100 * r.height + "px";
|
|
4924
|
-
}
|
|
4925
|
-
}
|
|
4926
|
-
_commitView() {
|
|
4927
|
-
const v = { s: this._view.s, x: this._view.x, y: this._view.y };
|
|
4928
|
-
if (this._userUrl) v.u = this._userUrl;
|
|
4929
|
-
if (this.id) setSlot(this.id, v);
|
|
4930
|
-
else {
|
|
4931
|
-
this._local = v;
|
|
4932
|
-
}
|
|
4933
|
-
}
|
|
4934
|
-
_render() {
|
|
4935
|
-
const mask = this.getAttribute("mask");
|
|
4936
|
-
const shape = (this.getAttribute("shape") || "rounded").toLowerCase();
|
|
4937
|
-
let radius = "";
|
|
4938
|
-
if (shape === "circle") radius = "50%";
|
|
4939
|
-
else if (shape === "pill") radius = "9999px";
|
|
4940
|
-
else if (shape === "rounded") {
|
|
4941
|
-
const n = parseFloat(this.getAttribute("radius"));
|
|
4942
|
-
radius = (Number.isFinite(n) ? n : 12) + "px";
|
|
4943
|
-
}
|
|
4944
|
-
this._frame.style.borderRadius = mask ? "" : radius;
|
|
4945
|
-
this._frame.style.clipPath = mask || "";
|
|
4946
|
-
this._ring.style.borderRadius = mask ? "" : radius;
|
|
4947
|
-
this._ring.style.display = mask ? "none" : "";
|
|
4948
|
-
const editable = !!(window.omelette && window.omelette.writeFile);
|
|
4949
|
-
this.toggleAttribute("data-editable", editable);
|
|
4950
|
-
this._sub.style.display = editable ? "" : "none";
|
|
4951
|
-
let stored = this.id ? getSlot(this.id) : this._local;
|
|
4952
|
-
if (stored && stored.u && !/^data:image\//i.test(stored.u)) stored = null;
|
|
4953
|
-
const srcAttr = this.getAttribute("src") || "";
|
|
4954
|
-
this._userUrl = stored && stored.u || null;
|
|
4955
|
-
const url = this._userUrl || srcAttr;
|
|
4956
|
-
if (!this.hasAttribute("data-reframe")) {
|
|
4957
|
-
this._view = {
|
|
4958
|
-
s: stored && Number.isFinite(stored.s) ? clampS(stored.s) : 1,
|
|
4959
|
-
x: stored && Number.isFinite(stored.x) ? stored.x : 0,
|
|
4960
|
-
y: stored && Number.isFinite(stored.y) ? stored.y : 0
|
|
4961
|
-
};
|
|
4962
|
-
}
|
|
4963
|
-
const authoring = typeof window !== "undefined" && typeof window.__dcSetProps === "function";
|
|
4964
|
-
const phEn = this.getAttribute("placeholder") || "Drop an image";
|
|
4965
|
-
const phVi = this.getAttribute("placeholder-vi") || "Thả ảnh (không bắt buộc)";
|
|
4966
|
-
const lang = (document.documentElement.getAttribute("lang") || "").toLowerCase();
|
|
4967
|
-
this._cap.textContent = authoring ? lang.startsWith("vi") ? phVi : phEn : "";
|
|
4968
|
-
if (this._cap) this._cap.style.display = authoring ? "" : "none";
|
|
4969
|
-
const credit = (this.getAttribute("credit") || "").trim();
|
|
4970
|
-
const attrError = !!(!credit && !this._userUrl && srcAttr && isUnsplashHost(srcAttr));
|
|
4971
|
-
this.toggleAttribute("data-attribution-error", attrError);
|
|
4972
|
-
if (url && !attrError) {
|
|
4973
|
-
const prev = this._img.getAttribute("src");
|
|
4974
|
-
if (prev !== url) {
|
|
4975
|
-
if (prev || this._hidShowing) this.setAttribute("data-swapping", "");
|
|
4976
|
-
this._loadPending = true;
|
|
4977
|
-
this._img.src = url;
|
|
4978
|
-
this._ghost.src = url;
|
|
4979
|
-
} else {
|
|
4980
|
-
this._releaseMask();
|
|
4981
|
-
}
|
|
4982
|
-
this._hidShowing = false;
|
|
4983
|
-
this._img.style.display = "block";
|
|
4984
|
-
this._empty.style.display = "none";
|
|
4985
|
-
this.setAttribute("data-filled", "");
|
|
4986
|
-
this._clampView();
|
|
4987
|
-
this._applyView();
|
|
4988
|
-
} else {
|
|
4989
|
-
this.removeAttribute("data-swapping");
|
|
4990
|
-
this._loadPending = false;
|
|
4991
|
-
this._hidShowing = attrError && !!this._img.getAttribute("src");
|
|
4992
|
-
this._img.style.display = "none";
|
|
4993
|
-
this._img.removeAttribute("src");
|
|
4994
|
-
this._ghost.removeAttribute("src");
|
|
4995
|
-
this._empty.style.display = attrError ? "none" : "flex";
|
|
4996
|
-
this.removeAttribute("data-filled");
|
|
4997
|
-
}
|
|
4998
|
-
const showCredit = !!(url && credit && !this._userUrl && !attrError);
|
|
4999
|
-
this._credit.textContent = "";
|
|
5000
|
-
if (showCredit) {
|
|
5001
|
-
let href = "";
|
|
5002
|
-
const rawHref = this.getAttribute("credit-href") || "";
|
|
5003
|
-
if (rawHref) {
|
|
5004
|
-
try {
|
|
5005
|
-
const u = new URL(rawHref, document.baseURI);
|
|
5006
|
-
if (u.protocol === "http:" || u.protocol === "https:") {
|
|
5007
|
-
href = withReferral(u.href);
|
|
5008
|
-
}
|
|
5009
|
-
} catch {
|
|
5010
|
-
}
|
|
5011
|
-
}
|
|
5012
|
-
const mkLink = (text, linkHref) => {
|
|
5013
|
-
const a = document.createElement("a");
|
|
5014
|
-
a.setAttribute("target", "_blank");
|
|
5015
|
-
a.setAttribute("rel", "noopener noreferrer");
|
|
5016
|
-
a.setAttribute("href", linkHref);
|
|
5017
|
-
a.textContent = text;
|
|
5018
|
-
return a;
|
|
5019
|
-
};
|
|
5020
|
-
const m = /^Photo by (.+) on Unsplash$/.exec(credit);
|
|
5021
|
-
if (m) {
|
|
5022
|
-
this._credit.appendChild(document.createTextNode("Photo by "));
|
|
5023
|
-
this._credit.appendChild(
|
|
5024
|
-
href ? mkLink(m[1], href) : document.createTextNode(m[1])
|
|
5025
|
-
);
|
|
5026
|
-
this._credit.appendChild(document.createTextNode(" on "));
|
|
5027
|
-
this._credit.appendChild(mkLink("Unsplash", UNSPLASH_HOMEPAGE_HREF));
|
|
5028
|
-
} else if (href) {
|
|
5029
|
-
this._credit.appendChild(mkLink(credit, href));
|
|
5030
|
-
} else {
|
|
5031
|
-
this._credit.textContent = credit;
|
|
5032
|
-
}
|
|
5033
|
-
}
|
|
5034
|
-
this.toggleAttribute("data-credit", showCredit);
|
|
5035
|
-
}
|
|
5036
|
-
}
|
|
5037
|
-
if (!customElements.get("image-slot")) {
|
|
5038
|
-
customElements.define("image-slot", ImageSlot);
|
|
5039
|
-
}
|
|
5040
|
-
})();
|
|
5041
|
-
})(); } catch (e) { __ds_ns.__errors.push({ path: "image-slot.js", error: String((e && e.message) || e) }); }
|
|
5042
|
-
|
|
5043
4872
|
// tokens/tokens.js
|
|
5044
4873
|
try { (() => {
|
|
5045
4874
|
const tokens = {
|
|
5046
4875
|
"$meta": {
|
|
5047
4876
|
"system": "CyberSkill Design System",
|
|
5048
|
-
"version": "1.
|
|
4877
|
+
"version": "1.3.8",
|
|
5049
4878
|
"generated": "2026-07-23",
|
|
5050
4879
|
"note": "Generated from tokens/*.css. Root grouped by category; theme/element scopes carry overrides only. Regenerate after any token change (see docs/conventions.md)."
|
|
5051
4880
|
},
|
|
@@ -5109,7 +4938,7 @@ const tokens = {
|
|
|
5109
4938
|
"--cs-accent-ink": "#45210E",
|
|
5110
4939
|
"--cs-accent-glow": "rgba(244, 186, 23, 0.48)",
|
|
5111
4940
|
"--cs-accent-grad-a": "#F4BA17",
|
|
5112
|
-
"--cs-accent-grad-b": "#
|
|
4941
|
+
"--cs-accent-grad-b": "#F3B958",
|
|
5113
4942
|
"--cs-accent-on-strong": "#1A1108"
|
|
5114
4943
|
},
|
|
5115
4944
|
"depth": {
|
|
@@ -5178,6 +5007,7 @@ const tokens = {
|
|
|
5178
5007
|
},
|
|
5179
5008
|
"breakpoint": {
|
|
5180
5009
|
"--cs-breakpoint-xs": "0px",
|
|
5010
|
+
"--cs-breakpoint-phone": "360px",
|
|
5181
5011
|
"--cs-breakpoint-sm": "640px",
|
|
5182
5012
|
"--cs-breakpoint-md": "768px",
|
|
5183
5013
|
"--cs-breakpoint-lg": "1024px",
|
|
@@ -5317,9 +5147,9 @@ const tokens = {
|
|
|
5317
5147
|
"--cs-accent-ink": "#292414",
|
|
5318
5148
|
"--cs-accent-glow": "rgba(248, 233, 192, 0.48)",
|
|
5319
5149
|
"--cs-accent-grad-a": "#F8E9C0",
|
|
5320
|
-
"--cs-accent-grad-b": "#
|
|
5150
|
+
"--cs-accent-grad-b": "#F3B958",
|
|
5321
5151
|
"--cs-color-text-accent": "#90825E",
|
|
5322
|
-
"--cs-color-link": "#
|
|
5152
|
+
"--cs-color-link": "#7B6D4A"
|
|
5323
5153
|
},
|
|
5324
5154
|
"tho": {
|
|
5325
5155
|
"--cs-accent": "#F4BA17",
|
|
@@ -5331,7 +5161,7 @@ const tokens = {
|
|
|
5331
5161
|
"--cs-accent-ink": "#45210E",
|
|
5332
5162
|
"--cs-accent-glow": "rgba(244, 186, 23, 0.48)",
|
|
5333
5163
|
"--cs-accent-grad-a": "#F4BA17",
|
|
5334
|
-
"--cs-accent-grad-b": "#
|
|
5164
|
+
"--cs-accent-grad-b": "#F3B958"
|
|
5335
5165
|
},
|
|
5336
5166
|
"tho.clay": {
|
|
5337
5167
|
"--cs-accent": "#513801",
|
|
@@ -5343,7 +5173,7 @@ const tokens = {
|
|
|
5343
5173
|
"--cs-accent-ink": "#2D220E",
|
|
5344
5174
|
"--cs-accent-glow": "rgba(81, 56, 1, 0.48)",
|
|
5345
5175
|
"--cs-accent-grad-a": "#513801",
|
|
5346
|
-
"--cs-accent-grad-b": "#
|
|
5176
|
+
"--cs-accent-grad-b": "#F3B958",
|
|
5347
5177
|
"--cs-color-text-accent": "#201300",
|
|
5348
5178
|
"--cs-color-link": "#201300"
|
|
5349
5179
|
},
|
|
@@ -5359,7 +5189,7 @@ const tokens = {
|
|
|
5359
5189
|
"--cs-accent-grad-a": "#FFE1D8",
|
|
5360
5190
|
"--cs-accent-grad-b": "#F4BA17",
|
|
5361
5191
|
"--cs-color-text-accent": "#967C75",
|
|
5362
|
-
"--cs-color-link": "#
|
|
5192
|
+
"--cs-color-link": "#846B64"
|
|
5363
5193
|
},
|
|
5364
5194
|
"hoa": {
|
|
5365
5195
|
"--cs-accent": "#FCAF91",
|
|
@@ -5373,7 +5203,7 @@ const tokens = {
|
|
|
5373
5203
|
"--cs-accent-grad-a": "#FCAF91",
|
|
5374
5204
|
"--cs-accent-grad-b": "#F4BA17",
|
|
5375
5205
|
"--cs-color-text-accent": "#B47054",
|
|
5376
|
-
"--cs-color-link": "#
|
|
5206
|
+
"--cs-color-link": "#A15E43"
|
|
5377
5207
|
},
|
|
5378
5208
|
"hoa.lava": {
|
|
5379
5209
|
"--cs-accent": "#622B06",
|
|
@@ -5401,7 +5231,7 @@ const tokens = {
|
|
|
5401
5231
|
"--cs-accent-grad-a": "#C0F5FC",
|
|
5402
5232
|
"--cs-accent-grad-b": "#A3D768",
|
|
5403
5233
|
"--cs-color-text-accent": "#5A8A90",
|
|
5404
|
-
"--cs-color-link": "#
|
|
5234
|
+
"--cs-color-link": "#48787E"
|
|
5405
5235
|
},
|
|
5406
5236
|
"thuy": {
|
|
5407
5237
|
"--cs-accent": "#2EDBF7",
|
|
@@ -5415,7 +5245,7 @@ const tokens = {
|
|
|
5415
5245
|
"--cs-accent-grad-a": "#2EDBF7",
|
|
5416
5246
|
"--cs-accent-grad-b": "#A3D768",
|
|
5417
5247
|
"--cs-color-text-accent": "#108EA0",
|
|
5418
|
-
"--cs-color-link": "#
|
|
5248
|
+
"--cs-color-link": "#027B8B"
|
|
5419
5249
|
},
|
|
5420
5250
|
"thuy.ocean": {
|
|
5421
5251
|
"--cs-accent": "#004554",
|
|
@@ -5443,7 +5273,7 @@ const tokens = {
|
|
|
5443
5273
|
"--cs-accent-grad-a": "#E1F1C8",
|
|
5444
5274
|
"--cs-accent-grad-b": "#FCAF91",
|
|
5445
5275
|
"--cs-color-text-accent": "#7C8965",
|
|
5446
|
-
"--cs-color-link": "#
|
|
5276
|
+
"--cs-color-link": "#687451"
|
|
5447
5277
|
},
|
|
5448
5278
|
"moc": {
|
|
5449
5279
|
"--cs-accent": "#A3D768",
|
|
@@ -5457,7 +5287,7 @@ const tokens = {
|
|
|
5457
5287
|
"--cs-accent-grad-a": "#A3D768",
|
|
5458
5288
|
"--cs-accent-grad-b": "#FCAF91",
|
|
5459
5289
|
"--cs-color-text-accent": "#629122",
|
|
5460
|
-
"--cs-color-link": "#
|
|
5290
|
+
"--cs-color-link": "#517B13"
|
|
5461
5291
|
},
|
|
5462
5292
|
"moc.forest": {
|
|
5463
5293
|
"--cs-accent": "#1A4A01",
|
|
@@ -5485,7 +5315,7 @@ const tokens = {
|
|
|
5485
5315
|
"--cs-accent-grad-a": "#FEE5C7",
|
|
5486
5316
|
"--cs-accent-grad-b": "#2EDBF7",
|
|
5487
5317
|
"--cs-color-text-accent": "#947F65",
|
|
5488
|
-
"--cs-color-link": "#
|
|
5318
|
+
"--cs-color-link": "#826D54"
|
|
5489
5319
|
},
|
|
5490
5320
|
"kim": {
|
|
5491
5321
|
"--cs-accent": "#F3B958",
|
|
@@ -5499,7 +5329,7 @@ const tokens = {
|
|
|
5499
5329
|
"--cs-accent-grad-a": "#F3B958",
|
|
5500
5330
|
"--cs-accent-grad-b": "#2EDBF7",
|
|
5501
5331
|
"--cs-color-text-accent": "#AC780B",
|
|
5502
|
-
"--cs-color-link": "#
|
|
5332
|
+
"--cs-color-link": "#966700"
|
|
5503
5333
|
},
|
|
5504
5334
|
"kim.steel": {
|
|
5505
5335
|
"--cs-accent": "#503901",
|
|
@@ -5520,477 +5350,6 @@ const tokens = {
|
|
|
5520
5350
|
Object.assign(__ds_scope, { tokens, __ds_default_tokens_tokens_9gob35: tokens });
|
|
5521
5351
|
})(); } catch (e) { __ds_ns.__errors.push({ path: "tokens/tokens.js", error: String((e && e.message) || e) }); }
|
|
5522
5352
|
|
|
5523
|
-
// ui_kits/status-hub/StatusHub.jsx
|
|
5524
|
-
try { (() => {
|
|
5525
|
-
const { useState, useMemo } = React;
|
|
5526
|
-
const ICON = {
|
|
5527
|
-
search: "M11 4a7 7 0 1 0 4.2 12.6L20 21l1.4-1.4-4.4-4.4A7 7 0 0 0 11 4zm0 2a5 5 0 1 1 0 10 5 5 0 0 1 0-10z",
|
|
5528
|
-
close: "M6 6l12 12M18 6L6 18",
|
|
5529
|
-
sun: null,
|
|
5530
|
-
moon: null
|
|
5531
|
-
};
|
|
5532
|
-
function Glyph({ d, size = 18 }) {
|
|
5533
|
-
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d }));
|
|
5534
|
-
}
|
|
5535
|
-
function SearchGlyph() {
|
|
5536
|
-
return /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: ICON.search }));
|
|
5537
|
-
}
|
|
5538
|
-
function ThemeGlyph({ dark }) {
|
|
5539
|
-
return dark ? /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "4" }), /* @__PURE__ */ React.createElement("path", { d: "M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4" })) : /* @__PURE__ */ React.createElement("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round" }, /* @__PURE__ */ React.createElement("path", { d: "M20 13.5A8 8 0 1 1 10.5 4a6.5 6.5 0 0 0 9.5 9.5z" }));
|
|
5540
|
-
}
|
|
5541
|
-
const T = {
|
|
5542
|
-
en: {
|
|
5543
|
-
sub: "CyberSkill · one page, three lenses · Hiện Thực Hoá Ý Chí",
|
|
5544
|
-
updated: "Updated",
|
|
5545
|
-
portfolio: "Portfolio",
|
|
5546
|
-
projects: "Projects",
|
|
5547
|
-
inprog: "In progress",
|
|
5548
|
-
shipped: "Shipped",
|
|
5549
|
-
blocked: "Blocked",
|
|
5550
|
-
planned: "Planned",
|
|
5551
|
-
avg: "Avg progress",
|
|
5552
|
-
velocity: "Velocity · tasks shipped / week",
|
|
5553
|
-
perShipped: " shipped",
|
|
5554
|
-
nowTitle: "Now shipping",
|
|
5555
|
-
search: "Search projects…",
|
|
5556
|
-
view: "View",
|
|
5557
|
-
lenses: { board: "Board", table: "Table", releases: "Releases" },
|
|
5558
|
-
status: "Status",
|
|
5559
|
-
all: "All",
|
|
5560
|
-
of: "of",
|
|
5561
|
-
key: "Key",
|
|
5562
|
-
project: "Project",
|
|
5563
|
-
squad: "Squad",
|
|
5564
|
-
owner: "Owner",
|
|
5565
|
-
progress: "Progress",
|
|
5566
|
-
thisWeek: "This week",
|
|
5567
|
-
tasks: "Tasks",
|
|
5568
|
-
risk: "Risk",
|
|
5569
|
-
close: "Close",
|
|
5570
|
-
langBtn: "VN",
|
|
5571
|
-
theme: "Toggle theme"
|
|
5572
|
-
},
|
|
5573
|
-
vi: {
|
|
5574
|
-
sub: "CyberSkill · một trang, ba lăng kính · Hiện Thực Hoá Ý Chí",
|
|
5575
|
-
updated: "Cập nhật",
|
|
5576
|
-
portfolio: "Danh mục",
|
|
5577
|
-
projects: "Dự án",
|
|
5578
|
-
inprog: "Đang làm",
|
|
5579
|
-
shipped: "Đã ra mắt",
|
|
5580
|
-
blocked: "Bị chặn",
|
|
5581
|
-
planned: "Dự kiến",
|
|
5582
|
-
avg: "Tiến độ TB",
|
|
5583
|
-
velocity: "Tốc độ · task giao / tuần",
|
|
5584
|
-
perShipped: " đã giao",
|
|
5585
|
-
nowTitle: "Đang ra mắt",
|
|
5586
|
-
search: "Tìm dự án…",
|
|
5587
|
-
view: "Chế độ xem",
|
|
5588
|
-
lenses: { board: "Bảng công việc", table: "Bảng dữ liệu", releases: "Phát hành" },
|
|
5589
|
-
status: "Trạng thái",
|
|
5590
|
-
all: "Tất cả",
|
|
5591
|
-
of: "trên",
|
|
5592
|
-
key: "Mã",
|
|
5593
|
-
project: "Dự án",
|
|
5594
|
-
squad: "Nhóm",
|
|
5595
|
-
owner: "Phụ trách",
|
|
5596
|
-
progress: "Tiến độ",
|
|
5597
|
-
thisWeek: "Tuần này",
|
|
5598
|
-
tasks: "Công việc",
|
|
5599
|
-
risk: "Rủi ro",
|
|
5600
|
-
close: "Đóng",
|
|
5601
|
-
langBtn: "EN",
|
|
5602
|
-
theme: "Đổi giao diện"
|
|
5603
|
-
}
|
|
5604
|
-
};
|
|
5605
|
-
const statusLabels = (t) => ({ done: t.shipped, active: t.inprog, hold: t.blocked, todo: t.planned });
|
|
5606
|
-
function Segments({ seg, className }) {
|
|
5607
|
-
return /* @__PURE__ */ React.createElement("div", { className }, seg.done > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-done", style: { width: seg.done + "%" } }), seg.active > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-active", style: { width: seg.active + "%" } }), seg.hold > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-hold", style: { width: seg.hold + "%" } }), seg.todo > 0 && /* @__PURE__ */ React.createElement("i", { className: "seg-todo", style: { width: seg.todo + "%" } }));
|
|
5608
|
-
}
|
|
5609
|
-
function Drawer({ p, onClose, t }) {
|
|
5610
|
-
if (!p) return null;
|
|
5611
|
-
const SL = statusLabels(t);
|
|
5612
|
-
return /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "scrim", onClick: onClose }), /* @__PURE__ */ React.createElement("aside", { className: "drawer", role: "dialog", "aria-modal": "true", "aria-label": p.name }, /* @__PURE__ */ React.createElement("div", { className: "dw-h" }, /* @__PURE__ */ React.createElement("div", { className: "row" }, /* @__PURE__ */ React.createElement("span", { className: "mono muted", style: { fontWeight: 700 } }, p.key), /* @__PURE__ */ React.createElement("span", { className: "pill " + p.status }, SL[p.status]), /* @__PURE__ */ React.createElement("button", { className: "dw-x", onClick: onClose, "aria-label": t.close }, /* @__PURE__ */ React.createElement(Glyph, { d: ICON.close, size: 20 }))), /* @__PURE__ */ React.createElement("h2", null, p.name)), /* @__PURE__ */ React.createElement("div", { className: "dw-b" }, /* @__PURE__ */ React.createElement("p", { className: "muted", style: { marginTop: 0 } }, p.blurb), /* @__PURE__ */ React.createElement("dl", { className: "meta" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.owner), /* @__PURE__ */ React.createElement("dd", null, p.owner)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.squad), /* @__PURE__ */ React.createElement("dd", null, p.squad)), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.progress), /* @__PURE__ */ React.createElement("dd", null, p.pct, "%")), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("dt", null, t.updated), /* @__PURE__ */ React.createElement("dd", null, p.updated))), /* @__PURE__ */ React.createElement(Segments, { seg: p.seg, className: "minibar" }), /* @__PURE__ */ React.createElement("div", { className: "sect" }, /* @__PURE__ */ React.createElement("h3", null, t.tasks), /* @__PURE__ */ React.createElement("div", { className: "task-chips" }, p.tasks.map((tk) => /* @__PURE__ */ React.createElement("span", { key: tk[0], className: "chip " + tk[2], title: tk[1] }, tk[0]))), /* @__PURE__ */ React.createElement("ul", { className: "tasks" }, p.tasks.map((tk) => /* @__PURE__ */ React.createElement("li", { key: tk[0] }, tk[1], " — ", /* @__PURE__ */ React.createElement("span", { className: "muted" }, SL[tk[2]]))))), p.risk ? /* @__PURE__ */ React.createElement("div", { className: "sect" }, /* @__PURE__ */ React.createElement("h3", null, t.risk), /* @__PURE__ */ React.createElement("div", { className: "risk" }, p.risk)) : null)));
|
|
5613
|
-
}
|
|
5614
|
-
function StatusHub() {
|
|
5615
|
-
const D = window.SH_DATA;
|
|
5616
|
-
const [dark, setDark] = useState(false);
|
|
5617
|
-
const [lang, setLang] = useState("en");
|
|
5618
|
-
const [lens, setLens] = useState("board");
|
|
5619
|
-
const [q, setQ] = useState("");
|
|
5620
|
-
const [facet, setFacet] = useState("all");
|
|
5621
|
-
const [kpi, setKpi] = useState(null);
|
|
5622
|
-
const [sel, setSel] = useState(null);
|
|
5623
|
-
const t = T[lang];
|
|
5624
|
-
const SL = statusLabels(t);
|
|
5625
|
-
const VI = lang === "vi" ? window.SH_VI || null : null;
|
|
5626
|
-
const loc = (p) => VI ? {
|
|
5627
|
-
...p,
|
|
5628
|
-
blurb: VI.blurbs[p.key] || p.blurb,
|
|
5629
|
-
risk: VI.risks[p.key] != null ? VI.risks[p.key] : p.risk,
|
|
5630
|
-
owner: VI.owners[p.owner] || p.owner,
|
|
5631
|
-
squad: VI.squads[p.squad] || p.squad,
|
|
5632
|
-
updated: VI.updatedRel[p.updated] || p.updated
|
|
5633
|
-
} : p;
|
|
5634
|
-
const counts = useMemo(() => {
|
|
5635
|
-
const c = { active: 0, done: 0, hold: 0 };
|
|
5636
|
-
D.projects.forEach((p) => {
|
|
5637
|
-
if (c[p.status] != null) c[p.status]++;
|
|
5638
|
-
});
|
|
5639
|
-
const avg = Math.round(D.projects.reduce((a, p) => a + p.pct, 0) / D.projects.length);
|
|
5640
|
-
return { total: D.projects.length, ...c, avg };
|
|
5641
|
-
}, [D]);
|
|
5642
|
-
const shown = useMemo(() => {
|
|
5643
|
-
const needle = q.trim().toLowerCase();
|
|
5644
|
-
const eff = kpi || (facet === "all" ? null : facet);
|
|
5645
|
-
return D.projects.filter((p) => {
|
|
5646
|
-
if (eff && p.status !== eff) return false;
|
|
5647
|
-
if (!needle) return true;
|
|
5648
|
-
return (p.name + " " + p.key + " " + p.blurb).toLowerCase().includes(needle);
|
|
5649
|
-
});
|
|
5650
|
-
}, [D, q, facet, kpi]);
|
|
5651
|
-
const maxV = Math.max(...D.velocity.map((v) => v.n));
|
|
5652
|
-
const toggleKpi = (k) => setKpi((cur) => cur === k ? null : k);
|
|
5653
|
-
return /* @__PURE__ */ React.createElement("div", { className: "sh", lang, "data-theme": dark ? "dark" : void 0 }, /* @__PURE__ */ React.createElement("a", { className: "cs-skip", href: "#main", style: { position: "absolute", left: -999, width: 1, height: 1, overflow: "hidden" } }, "Skip to content"), /* @__PURE__ */ React.createElement("header", { className: "hd" }, /* @__PURE__ */ React.createElement("div", { className: "hd-in" }, /* @__PURE__ */ React.createElement("div", { className: "hd-id" }, /* @__PURE__ */ React.createElement("span", { className: "hd-mark" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/logo-mark.svg", alt: "" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h1", null, "Status Hub"), /* @__PURE__ */ React.createElement("p", { className: "hd-sub" }, t.sub))), /* @__PURE__ */ React.createElement("div", { className: "hd-side" }, /* @__PURE__ */ React.createElement("p", { className: "hd-meta" }, t.updated, /* @__PURE__ */ React.createElement("br", null), VI ? VI.updated : D.updated), /* @__PURE__ */ React.createElement("button", { className: "btn ghost", onClick: () => setLang((l) => l === "en" ? "vi" : "en"), "aria-label": "Toggle language", style: { fontWeight: 700, fontSize: 13 } }, t.langBtn), /* @__PURE__ */ React.createElement("button", { className: "btn ghost", onClick: () => setDark((v) => !v), "aria-label": t.theme }, /* @__PURE__ */ React.createElement(ThemeGlyph, { dark }))))), /* @__PURE__ */ React.createElement("main", { id: "main", className: "wrap" }, /* @__PURE__ */ React.createElement("div", { className: "deck" }, /* @__PURE__ */ React.createElement("section", { className: "panel" }, /* @__PURE__ */ React.createElement("h2", null, t.portfolio), /* @__PURE__ */ React.createElement("div", { className: "kpis" }, /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === null ? "1" : "0", onClick: () => setKpi(null) }, /* @__PURE__ */ React.createElement("b", null, counts.total), /* @__PURE__ */ React.createElement("span", null, t.projects)), /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === "active" ? "1" : "0", onClick: () => toggleKpi("active") }, /* @__PURE__ */ React.createElement("b", null, counts.active), /* @__PURE__ */ React.createElement("span", null, t.inprog)), /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === "done" ? "1" : "0", onClick: () => toggleKpi("done") }, /* @__PURE__ */ React.createElement("b", null, counts.done), /* @__PURE__ */ React.createElement("span", null, t.shipped)), /* @__PURE__ */ React.createElement("button", { className: "kpi", "data-on": kpi === "hold" ? "1" : "0", onClick: () => toggleKpi("hold") }, /* @__PURE__ */ React.createElement("b", null, counts.hold), /* @__PURE__ */ React.createElement("span", null, t.blocked)), /* @__PURE__ */ React.createElement("button", { className: "kpi", onClick: () => setKpi(null) }, /* @__PURE__ */ React.createElement("b", null, counts.avg, "%"), /* @__PURE__ */ React.createElement("span", null, t.avg))), /* @__PURE__ */ React.createElement(Segments, { seg: { done: 60, active: 22, hold: 6, todo: 12 }, className: "bar-seg" }), /* @__PURE__ */ React.createElement("div", { className: "legend" }, /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-color-semantic-success)" } }), t.shipped), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-color-brand-ochre)" } }), t.inprog), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-color-semantic-danger)" } }), t.blocked), /* @__PURE__ */ React.createElement("span", null, /* @__PURE__ */ React.createElement("i", { className: "dot", style: { background: "var(--cs-status-todo)" } }), t.planned))), /* @__PURE__ */ React.createElement("section", { className: "panel" }, /* @__PURE__ */ React.createElement("h2", null, t.velocity), /* @__PURE__ */ React.createElement("div", { className: "spark" }, D.velocity.map((v) => /* @__PURE__ */ React.createElement("a", { key: v.wk, title: v.n + t.perShipped }, /* @__PURE__ */ React.createElement("b", null, v.n), /* @__PURE__ */ React.createElement("i", { style: { height: v.n / maxV * 72 + "px" } }), /* @__PURE__ */ React.createElement("span", null, v.wk)))))), /* @__PURE__ */ React.createElement("div", { className: "now" }, /* @__PURE__ */ React.createElement("h2", null, t.nowTitle), lang === "vi" ? /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("b", null, "v1.6.0 — Liên kết Glass + gói Ngũ Hành v2."), " Liên kết component ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, ".cs-surface-*"), " (tùy chọn), trình kiểm 0 cảnh báo trên cả 15 gói, và kiểm tra lại APCA cho chế độ tối. Ra mắt tuần này.") : /* @__PURE__ */ React.createElement("p", null, /* @__PURE__ */ React.createElement("b", null, "v1.6.0 — Glass bindings + element-pack v2."), " Opt-in ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, ".cs-surface-*"), " component bindings, a zero-warning verifier across all 15 packs, and a dark-mode APCA re-check. Ships this week.")), /* @__PURE__ */ React.createElement("div", { className: "bar" }, /* @__PURE__ */ React.createElement("div", { className: "bar-top" }, /* @__PURE__ */ React.createElement("label", { className: "srch" }, /* @__PURE__ */ React.createElement(SearchGlyph, null), /* @__PURE__ */ React.createElement("input", { placeholder: t.search, value: q, onChange: (e) => setQ(e.target.value) })), /* @__PURE__ */ React.createElement(
|
|
5654
|
-
"div",
|
|
5655
|
-
{
|
|
5656
|
-
className: "lenses",
|
|
5657
|
-
role: "tablist",
|
|
5658
|
-
"aria-label": t.view,
|
|
5659
|
-
onKeyDown: (e) => {
|
|
5660
|
-
const order = ["board", "table", "releases"];
|
|
5661
|
-
const i = order.indexOf(lens);
|
|
5662
|
-
let next = i;
|
|
5663
|
-
if (e.key === "ArrowRight") next = (i + 1) % order.length;
|
|
5664
|
-
else if (e.key === "ArrowLeft") next = (i - 1 + order.length) % order.length;
|
|
5665
|
-
else if (e.key === "Home") next = 0;
|
|
5666
|
-
else if (e.key === "End") next = order.length - 1;
|
|
5667
|
-
else return;
|
|
5668
|
-
e.preventDefault();
|
|
5669
|
-
setLens(order[next]);
|
|
5670
|
-
const tabs = e.currentTarget.querySelectorAll('[role="tab"]');
|
|
5671
|
-
tabs[next] && tabs[next].focus && tabs[next].focus();
|
|
5672
|
-
}
|
|
5673
|
-
},
|
|
5674
|
-
["board", "table", "releases"].map((l) => /* @__PURE__ */ React.createElement(
|
|
5675
|
-
"button",
|
|
5676
|
-
{
|
|
5677
|
-
key: l,
|
|
5678
|
-
className: "ln",
|
|
5679
|
-
role: "tab",
|
|
5680
|
-
"aria-selected": lens === l,
|
|
5681
|
-
tabIndex: lens === l ? 0 : -1,
|
|
5682
|
-
onClick: () => setLens(l)
|
|
5683
|
-
},
|
|
5684
|
-
t.lenses[l]
|
|
5685
|
-
))
|
|
5686
|
-
), /* @__PURE__ */ React.createElement("label", { className: "facet" }, t.status, /* @__PURE__ */ React.createElement("select", { value: kpi || facet, onChange: (e) => {
|
|
5687
|
-
setFacet(e.target.value);
|
|
5688
|
-
setKpi(null);
|
|
5689
|
-
} }, /* @__PURE__ */ React.createElement("option", { value: "all" }, t.all), /* @__PURE__ */ React.createElement("option", { value: "active" }, t.inprog), /* @__PURE__ */ React.createElement("option", { value: "done" }, t.shipped), /* @__PURE__ */ React.createElement("option", { value: "hold" }, t.blocked))), /* @__PURE__ */ React.createElement("span", { className: "cnt" }, shown.length, " ", t.of, " ", D.projects.length))), lens === "board" && /* @__PURE__ */ React.createElement("div", { className: "grid" }, shown.map((p0) => {
|
|
5690
|
-
const p = loc(p0);
|
|
5691
|
-
return /* @__PURE__ */ React.createElement("button", { className: "card" + (p.status === "active" ? " hot" : ""), key: p.key, onClick: () => setSel(p0) }, /* @__PURE__ */ React.createElement("div", { className: "card-h" }, /* @__PURE__ */ React.createElement("h3", null, /* @__PURE__ */ React.createElement("span", { className: "k mono" }, p.key), " · ", p.name), /* @__PURE__ */ React.createElement("span", { className: "pct" }, p.pct, "%")), /* @__PURE__ */ React.createElement("p", null, p.blurb), /* @__PURE__ */ React.createElement(Segments, { seg: p.seg, className: "minibar" }), /* @__PURE__ */ React.createElement("div", { className: "task-chips" }, /* @__PURE__ */ React.createElement("span", { className: "pill " + p.status }, SL[p.status]), p.tasks.slice(0, 3).map((tk) => /* @__PURE__ */ React.createElement("span", { key: tk[0], className: "chip " + tk[2] }, tk[0]))));
|
|
5692
|
-
})), lens === "table" && /* @__PURE__ */ React.createElement("div", { className: "tbl-wrap" }, /* @__PURE__ */ React.createElement("table", { className: "tbl" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("th", null, t.key), /* @__PURE__ */ React.createElement("th", null, t.project), /* @__PURE__ */ React.createElement("th", null, t.squad), /* @__PURE__ */ React.createElement("th", null, t.owner), /* @__PURE__ */ React.createElement("th", null, t.progress), /* @__PURE__ */ React.createElement("th", null, t.status))), /* @__PURE__ */ React.createElement("tbody", null, shown.map((p0) => {
|
|
5693
|
-
const p = loc(p0);
|
|
5694
|
-
return /* @__PURE__ */ React.createElement("tr", { key: p.key, onClick: () => setSel(p0) }, /* @__PURE__ */ React.createElement("td", { className: "mono", style: { fontWeight: 700 } }, p.key), /* @__PURE__ */ React.createElement("td", null, p.name), /* @__PURE__ */ React.createElement("td", null, p.squad), /* @__PURE__ */ React.createElement("td", null, p.owner), /* @__PURE__ */ React.createElement("td", null, p.pct, "%"), /* @__PURE__ */ React.createElement("td", null, /* @__PURE__ */ React.createElement("span", { className: "pill " + p.status }, SL[p.status])));
|
|
5695
|
-
})))), lens === "releases" && /* @__PURE__ */ React.createElement("div", { className: "rels" }, D.releases.map((r) => /* @__PURE__ */ React.createElement("div", { className: "rel" + (r.now ? " now" : ""), key: r.ver }, /* @__PURE__ */ React.createElement("div", { className: "tick" }, r.done ? "✓" : "→"), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "rel-h" }, /* @__PURE__ */ React.createElement("b", null, r.ver), /* @__PURE__ */ React.createElement("span", { className: "pill " + (r.now ? "active" : "done") }, r.now ? t.thisWeek : t.shipped), /* @__PURE__ */ React.createElement("span", { className: "muted" }, VI ? VI.relWhen[r.ver] || r.when : r.when)), /* @__PURE__ */ React.createElement("div", { className: "rel-sec" }, /* @__PURE__ */ React.createElement("h4", null, VI ? VI.relTitles[r.ver] || r.title : r.title), /* @__PURE__ */ React.createElement("ul", null, (VI ? VI.relItems[r.ver] || r.items : r.items).map((it, i) => /* @__PURE__ */ React.createElement("li", { key: i }, it)))))))), /* @__PURE__ */ React.createElement("p", { className: "ft" }, lang === "vi" ? /* @__PURE__ */ React.createElement(React.Fragment, null, "CyberSkill Status Hub — bản tái hiện UI kit của design system. Dựng trên token ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, "styles.css"), ". Turn Your Will Into Real.") : /* @__PURE__ */ React.createElement(React.Fragment, null, "CyberSkill Status Hub — a design-system UI kit recreation. Built on ", /* @__PURE__ */ React.createElement("span", { className: "mono" }, "styles.css"), " tokens. Turn Your Will Into Real."))), /* @__PURE__ */ React.createElement(Drawer, { p: sel && loc(sel), onClose: () => setSel(null), t }));
|
|
5696
|
-
}
|
|
5697
|
-
window.StatusHub = StatusHub;
|
|
5698
|
-
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/status-hub/StatusHub.jsx", error: String((e && e.message) || e) }); }
|
|
5699
|
-
|
|
5700
|
-
// ui_kits/status-hub/data.js
|
|
5701
|
-
try { (() => {
|
|
5702
|
-
window.SH_DATA = {
|
|
5703
|
-
updated: "18 Jul 2026, 13:20 ICT",
|
|
5704
|
-
velocity: [
|
|
5705
|
-
{ wk: "W23", n: 4 },
|
|
5706
|
-
{ wk: "W24", n: 7 },
|
|
5707
|
-
{ wk: "W25", n: 5 },
|
|
5708
|
-
{ wk: "W26", n: 9 },
|
|
5709
|
-
{ wk: "W27", n: 6 },
|
|
5710
|
-
{ wk: "W28", n: 11 },
|
|
5711
|
-
{ wk: "W29", n: 8 }
|
|
5712
|
-
],
|
|
5713
|
-
projects: [
|
|
5714
|
-
{
|
|
5715
|
-
key: "CDS",
|
|
5716
|
-
name: "Design System v1.6",
|
|
5717
|
-
status: "active",
|
|
5718
|
-
pct: 72,
|
|
5719
|
-
blurb: "Liquid Glass component bindings + 15 element packs.",
|
|
5720
|
-
seg: { done: 62, active: 20, hold: 4, todo: 14 },
|
|
5721
|
-
owner: "Design Lead",
|
|
5722
|
-
updated: "2h ago",
|
|
5723
|
-
squad: "Platform",
|
|
5724
|
-
tasks: [["TASK-DS-041", "Glass surface bindings", "done"], ["TASK-DS-047", "Element-pack verifier v2", "active"], ["TASK-DS-052", "Dark-mode APCA re-check", "active"], ["TASK-DS-055", "Figma variable sync", "todo"]],
|
|
5725
|
-
risk: ""
|
|
5726
|
-
},
|
|
5727
|
-
{
|
|
5728
|
-
key: "GENIE",
|
|
5729
|
-
name: "Lumi Genie Chat",
|
|
5730
|
-
status: "active",
|
|
5731
|
-
pct: 58,
|
|
5732
|
-
blurb: "Keyless Claude proxy; scripted wish-capture flow.",
|
|
5733
|
-
seg: { done: 48, active: 24, hold: 8, todo: 20 },
|
|
5734
|
-
owner: "AI Lead",
|
|
5735
|
-
updated: "5h ago",
|
|
5736
|
-
squad: "AI",
|
|
5737
|
-
tasks: [["TASK-CHAR-026", "In-chat wish flow", "done"], ["TASK-CHAR-030", "One-time mascot hint", "active"], ["TASK-CHAR-021", "Golden-genie GLB", "hold"]],
|
|
5738
|
-
risk: "GLB art asset is a hard external dependency — commission not yet confirmed (TASK-CHAR-021)."
|
|
5739
|
-
},
|
|
5740
|
-
{
|
|
5741
|
-
key: "SITE",
|
|
5742
|
-
name: "cyberskill.world",
|
|
5743
|
-
status: "active",
|
|
5744
|
-
pct: 84,
|
|
5745
|
-
blurb: "Bilingual storytelling landing; lead capture live.",
|
|
5746
|
-
seg: { done: 78, active: 12, hold: 0, todo: 10 },
|
|
5747
|
-
owner: "Web Lead",
|
|
5748
|
-
updated: "1h ago",
|
|
5749
|
-
squad: "Growth",
|
|
5750
|
-
tasks: [["TASK-BIZ-003", "Lead pipeline e2e", "done"], ["TASK-SEO-012", "llms.txt route", "done"], ["TASK-CTA-020", "Teardown funnel polish", "active"]],
|
|
5751
|
-
risk: ""
|
|
5752
|
-
},
|
|
5753
|
-
{
|
|
5754
|
-
key: "OS",
|
|
5755
|
-
name: "CyberOS Agent Spine",
|
|
5756
|
-
status: "hold",
|
|
5757
|
-
pct: 41,
|
|
5758
|
-
blurb: "BRAIN memory store + HITL acceptance gates.",
|
|
5759
|
-
seg: { done: 36, active: 8, hold: 30, todo: 26 },
|
|
5760
|
-
owner: "Eng Lead",
|
|
5761
|
-
updated: "1d ago",
|
|
5762
|
-
squad: "Platform",
|
|
5763
|
-
tasks: [["TASK-OS-018", "Gate runner", "done"], ["TASK-OS-022", "Memory compaction", "hold"], ["TASK-OS-025", "Provenance ledger", "hold"]],
|
|
5764
|
-
risk: "Blocked on infra: provenance ledger needs the signing service (target Q1 2027)."
|
|
5765
|
-
},
|
|
5766
|
-
{
|
|
5767
|
-
key: "AUDIT",
|
|
5768
|
-
name: "DS Audit Framework",
|
|
5769
|
-
status: "done",
|
|
5770
|
-
pct: 100,
|
|
5771
|
-
blurb: "125-criteria vendor-neutral audit; L3 tier held.",
|
|
5772
|
-
seg: { done: 100, active: 0, hold: 0, todo: 0 },
|
|
5773
|
-
owner: "DesignOps",
|
|
5774
|
-
updated: "3d ago",
|
|
5775
|
-
squad: "Platform",
|
|
5776
|
-
tasks: [["TASK-AUD-009", "Deterministic engine", "done"], ["TASK-AUD-011", "No-regression CI gate", "done"]],
|
|
5777
|
-
risk: ""
|
|
5778
|
-
},
|
|
5779
|
-
{
|
|
5780
|
-
key: "TOKENS",
|
|
5781
|
-
name: "Token Pipeline",
|
|
5782
|
-
status: "active",
|
|
5783
|
-
pct: 66,
|
|
5784
|
-
blurb: "DTCG → CSS/TS/Swift/Kotlin/Flutter/Figma.",
|
|
5785
|
-
seg: { done: 58, active: 22, hold: 0, todo: 20 },
|
|
5786
|
-
owner: "Eng Lead",
|
|
5787
|
-
updated: "6h ago",
|
|
5788
|
-
squad: "Platform",
|
|
5789
|
-
tasks: [["TASK-TOK-014", "Contrast grid export", "done"], ["TASK-TOK-019", "Flutter target", "active"], ["TASK-TOK-021", "Provenance JSON", "todo"]],
|
|
5790
|
-
risk: ""
|
|
5791
|
-
}
|
|
5792
|
-
],
|
|
5793
|
-
releases: [
|
|
5794
|
-
{
|
|
5795
|
-
ver: "v1.6.0",
|
|
5796
|
-
when: "This week",
|
|
5797
|
-
now: true,
|
|
5798
|
-
done: false,
|
|
5799
|
-
title: "Glass bindings + element-pack v2",
|
|
5800
|
-
items: ["Opt-in .cs-surface-* component bindings", "Element-pack verifier v2 (0 warnings across 15)", "Dark-mode APCA re-check"]
|
|
5801
|
-
},
|
|
5802
|
-
{
|
|
5803
|
-
ver: "v1.5.1",
|
|
5804
|
-
when: "04 Jul 2026",
|
|
5805
|
-
now: false,
|
|
5806
|
-
done: true,
|
|
5807
|
-
title: "15 element packs ship",
|
|
5808
|
-
items: ["All 15 packs promoted to shipped", "Per-pack screenshot regression", "Gallery build script"]
|
|
5809
|
-
},
|
|
5810
|
-
{
|
|
5811
|
-
ver: "v1.4.0",
|
|
5812
|
-
when: "13 Jun 2026",
|
|
5813
|
-
now: false,
|
|
5814
|
-
done: true,
|
|
5815
|
-
title: "Liquid Glass in code",
|
|
5816
|
-
items: ["--cs-glass-* + --cs-depth-* scalars", "glass.css material layer", "Reduced-transparency fallbacks"]
|
|
5817
|
-
},
|
|
5818
|
-
{
|
|
5819
|
-
ver: "v1.3.0",
|
|
5820
|
-
when: "14 May 2026",
|
|
5821
|
-
now: false,
|
|
5822
|
-
done: true,
|
|
5823
|
-
title: "Brand assets + Logo",
|
|
5824
|
-
items: ["Official master mark ships", "React <Logo> from master artwork", "Light becomes default theme"]
|
|
5825
|
-
}
|
|
5826
|
-
]
|
|
5827
|
-
};
|
|
5828
|
-
window.SH_VI = {
|
|
5829
|
-
updated: "18/07/2026, 13:20 ICT",
|
|
5830
|
-
blurbs: { CDS: "Liên kết component Liquid Glass + 15 gói Ngũ Hành.", GENIE: "Proxy Claude không cần khóa; luồng tiếp nhận điều ước theo kịch bản.", SITE: "Trang giới thiệu song ngữ kể chuyện; thu lead đang chạy.", OS: "Kho nhớ BRAIN + cổng nghiệm thu có con người.", AUDIT: "Bộ kiểm định trung lập 125 tiêu chí; giữ hạng L3.", TOKENS: "DTCG → CSS/TS/Swift/Kotlin/Flutter/Figma." },
|
|
5831
|
-
risks: { GENIE: "Tài sản GLB là phụ thuộc ngoài khó — chưa chốt đặt hàng (TASK-CHAR-021).", OS: "Bị chặn bởi hạ tầng: sổ cái nguồn gốc cần dịch vụ ký số (mục tiêu Q1 2027)." },
|
|
5832
|
-
owners: { "Design Lead": "Trưởng thiết kế", "AI Lead": "Trưởng AI", "Web Lead": "Trưởng web", "Eng Lead": "Trưởng kỹ thuật", "DesignOps": "DesignOps" },
|
|
5833
|
-
squads: { Platform: "Nền tảng", AI: "AI", Growth: "Tăng trưởng" },
|
|
5834
|
-
updatedRel: { "2h ago": "2 giờ trước", "5h ago": "5 giờ trước", "1h ago": "1 giờ trước", "1d ago": "1 ngày trước", "3d ago": "3 ngày trước", "6h ago": "6 giờ trước" },
|
|
5835
|
-
relWhen: { "v1.6.0": "Tuần này", "v1.5.1": "04/07/2026", "v1.4.0": "13/06/2026", "v1.3.0": "14/05/2026" },
|
|
5836
|
-
relTitles: { "v1.6.0": "Liên kết Glass + gói Ngũ Hành v2", "v1.5.1": "15 gói Ngũ Hành ra mắt", "v1.4.0": "Liquid Glass vào mã", "v1.3.0": "Tài sản thương hiệu + Logo" },
|
|
5837
|
-
relItems: { "v1.6.0": ["Liên kết component .cs-surface-* (tùy chọn)", "Trình kiểm gói Ngũ Hành v2 (0 cảnh báo trên 15 gói)", "Kiểm tra lại APCA cho chế độ tối"], "v1.5.1": ["Cả 15 gói lên trạng thái đã ra mắt", "Regression ảnh chụp từng gói", "Script dựng gallery"], "v1.4.0": ["Scalar --cs-glass-* + --cs-depth-*", "Lớp chất liệu glass.css", "Fallback khi giảm độ trong suốt"], "v1.3.0": ["Master mark chính thức ra mắt", "<Logo> React từ artwork gốc", "Light thành theme mặc định"] }
|
|
5838
|
-
};
|
|
5839
|
-
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/status-hub/data.js", error: String((e && e.message) || e) }); }
|
|
5840
|
-
|
|
5841
|
-
// ui_kits/website/Website.jsx
|
|
5842
|
-
try { (() => {
|
|
5843
|
-
const { useState, useRef, useEffect } = React;
|
|
5844
|
-
function I({ name, size = 20, stroke = 1.9 }) {
|
|
5845
|
-
const p = {
|
|
5846
|
-
sparkle: "M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z",
|
|
5847
|
-
chat: "M4 5h16v11H8l-4 4z",
|
|
5848
|
-
check: "M4 12.5l5 5 11-11",
|
|
5849
|
-
"arrow-right": "M5 12h14M13 6l6 6-6 6",
|
|
5850
|
-
close: "M6 6l12 12M18 6L6 18",
|
|
5851
|
-
sun: "M12 8a4 4 0 1 0 0 8 4 4 0 0 0 0-8zM12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M19.1 4.9l-1.4 1.4M6.3 17.7l-1.4 1.4",
|
|
5852
|
-
moon: "M20 13.5A8 8 0 1 1 10.5 4a6.5 6.5 0 0 0 9.5 9.5z"
|
|
5853
|
-
}[name];
|
|
5854
|
-
return /* @__PURE__ */ React.createElement("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: stroke, strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true" }, /* @__PURE__ */ React.createElement("path", { d: p }));
|
|
5855
|
-
}
|
|
5856
|
-
function Chat({ t, seed, onClose }) {
|
|
5857
|
-
const [msgs, setMsgs] = useState([{ who: "bot", text: t.genie.greeting }]);
|
|
5858
|
-
const [val, setVal] = useState("");
|
|
5859
|
-
const bodyRef = useRef(null);
|
|
5860
|
-
useEffect(() => {
|
|
5861
|
-
if (seed) send(seed);
|
|
5862
|
-
}, []);
|
|
5863
|
-
useEffect(() => {
|
|
5864
|
-
const b = bodyRef.current;
|
|
5865
|
-
if (b) b.scrollTop = b.scrollHeight;
|
|
5866
|
-
}, [msgs]);
|
|
5867
|
-
function send(text) {
|
|
5868
|
-
const m = (text || "").trim();
|
|
5869
|
-
if (!m) return;
|
|
5870
|
-
setMsgs((cur) => [...cur, { who: "me", text: m }]);
|
|
5871
|
-
setVal("");
|
|
5872
|
-
setTimeout(() => setMsgs((cur) => [...cur, { who: "bot", text: "A wish worth granting. I’ll hand it to the team — first, what should I call you?" }]), 500);
|
|
5873
|
-
}
|
|
5874
|
-
return /* @__PURE__ */ React.createElement("div", { className: "chat" }, /* @__PURE__ */ React.createElement("div", { className: "chat-h" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/lumi-poster.webp", alt: "" }), /* @__PURE__ */ React.createElement("b", null, t.genie.title), /* @__PURE__ */ React.createElement("button", { onClick: onClose, "aria-label": "Close chat" }, /* @__PURE__ */ React.createElement(I, { name: "close", size: 18 }))), /* @__PURE__ */ React.createElement("div", { className: "chat-b", ref: bodyRef }, msgs.map((m, i) => /* @__PURE__ */ React.createElement("div", { key: i, className: "msg " + m.who }, m.text)), msgs.length === 1 && /* @__PURE__ */ React.createElement("div", { className: "chips" }, t.genie.chips.map((c) => /* @__PURE__ */ React.createElement("button", { key: c, onClick: () => send(c) }, c)))), /* @__PURE__ */ React.createElement("div", { className: "chat-f" }, /* @__PURE__ */ React.createElement("input", { value: val, onChange: (e) => setVal(e.target.value), onKeyDown: (e) => e.key === "Enter" && send(val), placeholder: t.genie.placeholder }), /* @__PURE__ */ React.createElement("button", { onClick: () => send(val) }, t.genie.send)));
|
|
5875
|
-
}
|
|
5876
|
-
function Website() {
|
|
5877
|
-
const [loc, setLoc] = useState("en");
|
|
5878
|
-
const [dark, setDark] = useState(false);
|
|
5879
|
-
const [chat, setChat] = useState(false);
|
|
5880
|
-
const [seed, setSeed] = useState("");
|
|
5881
|
-
const [wish, setWish] = useState("");
|
|
5882
|
-
const [sent, setSent] = useState(false);
|
|
5883
|
-
const [fabVisible, setFabVisible] = useState(false);
|
|
5884
|
-
const t = window.SITE_COPY[loc];
|
|
5885
|
-
function openChat(s) {
|
|
5886
|
-
setSeed(s || "");
|
|
5887
|
-
setChat(true);
|
|
5888
|
-
}
|
|
5889
|
-
useEffect(() => {
|
|
5890
|
-
const sync = () => setFabVisible(window.scrollY > 140);
|
|
5891
|
-
sync();
|
|
5892
|
-
window.addEventListener("scroll", sync, { passive: true });
|
|
5893
|
-
return () => window.removeEventListener("scroll", sync);
|
|
5894
|
-
}, []);
|
|
5895
|
-
return /* @__PURE__ */ React.createElement("div", { className: "site", "data-theme": dark ? "dark" : void 0 }, /* @__PURE__ */ React.createElement("a", { className: "cs-skip", href: "#main", style: { position: "absolute", left: -999, width: 1, height: 1, overflow: "hidden" } }, "Skip to content"), /* @__PURE__ */ React.createElement("header", { className: "hdr cs-surface-light" }, /* @__PURE__ */ React.createElement("div", { className: "container hdr-in" }, /* @__PURE__ */ React.createElement("a", { className: "brand", href: "#top" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/logo-mark.svg", alt: "" }), "CyberSkill"), /* @__PURE__ */ React.createElement("nav", { className: "nav" }, t.nav.map((n, i) => /* @__PURE__ */ React.createElement("a", { key: i, href: "#" + ["services", "work", "process", "careers", "contact"][i] }, n))), /* @__PURE__ */ React.createElement("div", { className: "hdr-side" }, /* @__PURE__ */ React.createElement("button", { className: "mini", onClick: () => setLoc(loc === "en" ? "vi" : "en") }, t.langLabel), /* @__PURE__ */ React.createElement("button", { className: "mini icon", onClick: () => setDark(!dark), "aria-label": "Toggle theme" }, /* @__PURE__ */ React.createElement(I, { name: dark ? "sun" : "moon", size: 18 })), /* @__PURE__ */ React.createElement("button", { className: "mini", style: { background: "var(--cs-color-brand-umber)", color: "var(--cs-color-text-inverse,#fff)", borderColor: "transparent" }, onClick: () => openChat("") }, t.talk)))), /* @__PURE__ */ React.createElement("main", { id: "main" }, /* @__PURE__ */ React.createElement("section", { className: "hero", id: "top" }, /* @__PURE__ */ React.createElement("div", { className: "container hero-in" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.eyebrow), /* @__PURE__ */ React.createElement("h1", null, t.slogan), /* @__PURE__ */ React.createElement("div", { className: "vn" }, t.sub), /* @__PURE__ */ React.createElement("p", { className: "lead" }, t.lead), /* @__PURE__ */ React.createElement("div", { className: "wish" }, /* @__PURE__ */ React.createElement("input", { value: wish, onChange: (e) => setWish(e.target.value), onKeyDown: (e) => e.key === "Enter" && openChat(wish), placeholder: t.wishPlaceholder, "aria-label": t.wishPlaceholder }), /* @__PURE__ */ React.createElement("button", { className: "btn-gold", onClick: () => openChat(wish) }, /* @__PURE__ */ React.createElement(I, { name: "sparkle", size: 18 }), t.wishCta))), /* @__PURE__ */ React.createElement("div", { className: "hero-art" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/lumi-poster.webp", alt: "Lumi, the golden genie" })))), /* @__PURE__ */ React.createElement("div", { className: "marq" }, /* @__PURE__ */ React.createElement("div", { className: "container marq-in" }, t.marquee.map((m, i) => /* @__PURE__ */ React.createElement(React.Fragment, { key: i }, i > 0 && /* @__PURE__ */ React.createElement("b", null, "·"), /* @__PURE__ */ React.createElement("span", null, m))))), /* @__PURE__ */ React.createElement("section", { className: "sec", id: "services" }, /* @__PURE__ */ React.createElement("div", { className: "container" }, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.nav[0]), /* @__PURE__ */ React.createElement("h2", null, t.servicesTitle), /* @__PURE__ */ React.createElement("p", { className: "sec-lead" }, t.servicesLead), /* @__PURE__ */ React.createElement("div", { className: "svc-grid" }, t.services.map((s, i) => /* @__PURE__ */ React.createElement("article", { className: "svc cs-surface-standard", key: i }, /* @__PURE__ */ React.createElement("div", { className: "svc-ic" }, /* @__PURE__ */ React.createElement(I, { name: s.icon })), /* @__PURE__ */ React.createElement("h3", null, s.t), /* @__PURE__ */ React.createElement("p", null, s.s), /* @__PURE__ */ React.createElement("ul", null, s.o.map((o) => /* @__PURE__ */ React.createElement("li", { key: o }, o)))))))), /* @__PURE__ */ React.createElement("section", { className: "sec sec-alt", id: "process" }, /* @__PURE__ */ React.createElement("div", { className: "container" }, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.nav[2]), /* @__PURE__ */ React.createElement("h2", null, t.processTitle), /* @__PURE__ */ React.createElement("p", { className: "sec-lead" }, t.processLead), /* @__PURE__ */ React.createElement("div", { className: "steps" }, t.steps.map((s) => /* @__PURE__ */ React.createElement("div", { className: "step", key: s.n }, /* @__PURE__ */ React.createElement("span", { className: "n" }, s.n), /* @__PURE__ */ React.createElement("h3", null, s.t), /* @__PURE__ */ React.createElement("p", null, s.s)))))), /* @__PURE__ */ React.createElement("section", { className: "sec", id: "careers" }, /* @__PURE__ */ React.createElement("div", { className: "container" }, /* @__PURE__ */ React.createElement("div", { className: "band" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("h2", null, t.careersTitle), /* @__PURE__ */ React.createElement("p", null, t.careersLead)), /* @__PURE__ */ React.createElement("button", { className: "btn-gold", onClick: () => openChat("") }, t.careersCta, /* @__PURE__ */ React.createElement(I, { name: "arrow-right", size: 18 }))))), /* @__PURE__ */ React.createElement("section", { className: "sec sec-alt", id: "contact" }, /* @__PURE__ */ React.createElement("div", { className: "container contact-grid" }, /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "eyebrow" }, t.nav[4]), /* @__PURE__ */ React.createElement("h2", null, t.contactTitle), /* @__PURE__ */ React.createElement("p", { className: "sec-lead", style: { marginBottom: 16 } }, t.contactLead), /* @__PURE__ */ React.createElement("p", { className: "trust" }, /* @__PURE__ */ React.createElement(I, { name: "check", size: 16 }), t.form.trust)), sent ? /* @__PURE__ */ React.createElement("div", { className: "sent" }, /* @__PURE__ */ React.createElement(I, { name: "check", size: 18 }), " ", loc === "vi" ? "Đã nhận được lời nhắn. Cảm ơn bạn!" : "Message received. Thank you!") : /* @__PURE__ */ React.createElement("form", { className: "form", onSubmit: (e) => {
|
|
5896
|
-
e.preventDefault();
|
|
5897
|
-
setSent(true);
|
|
5898
|
-
} }, /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.name), /* @__PURE__ */ React.createElement("input", { className: "cs-field__control", required: true })), /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.email), /* @__PURE__ */ React.createElement("input", { className: "cs-field__control", type: "email", required: true })), /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.company, " ", /* @__PURE__ */ React.createElement("span", { className: "muted", style: { fontWeight: 400, color: "var(--cs-color-text-muted)" } }, "· ", t.form.optional)), /* @__PURE__ */ React.createElement("input", { className: "cs-field__control" })), /* @__PURE__ */ React.createElement("label", { className: "cs-field" }, /* @__PURE__ */ React.createElement("span", { className: "cs-field__label" }, t.form.message), /* @__PURE__ */ React.createElement("textarea", { className: "cs-field__control", rows: "3" })), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("button", { type: "submit", className: "btn-gold" }, t.form.submit, /* @__PURE__ */ React.createElement(I, { name: "arrow-right", size: 18 }))))))), /* @__PURE__ */ React.createElement("footer", { className: "ft" }, /* @__PURE__ */ React.createElement("div", { className: "container ft-in" }, /* @__PURE__ */ React.createElement("a", { className: "brand", href: "#top" }, /* @__PURE__ */ React.createElement("img", { src: "../../assets/logo-mark.svg", alt: "" }), "CyberSkill"), /* @__PURE__ */ React.createElement("div", { className: "ft-links" }, t.footerLinks.map((l) => /* @__PURE__ */ React.createElement("a", { key: l, href: "#top" }, l))), /* @__PURE__ */ React.createElement("small", null, "© 2026 CyberSkill · ", t.footerRights, " · Saigon"))), !chat && /* @__PURE__ */ React.createElement(
|
|
5899
|
-
"button",
|
|
5900
|
-
{
|
|
5901
|
-
className: "fab",
|
|
5902
|
-
type: "button",
|
|
5903
|
-
"data-fab-deferred": "1",
|
|
5904
|
-
onClick: () => openChat(""),
|
|
5905
|
-
style: { opacity: fabVisible ? 1 : 0, pointerEvents: fabVisible ? "auto" : "none", transition: "opacity 160ms ease" },
|
|
5906
|
-
"aria-hidden": !fabVisible,
|
|
5907
|
-
tabIndex: fabVisible ? 0 : -1
|
|
5908
|
-
},
|
|
5909
|
-
/* @__PURE__ */ React.createElement("img", { src: "../../assets/lumi-poster.webp", alt: "" }),
|
|
5910
|
-
t.talk
|
|
5911
|
-
), chat && /* @__PURE__ */ React.createElement(Chat, { t, seed, onClose: () => setChat(false) }));
|
|
5912
|
-
}
|
|
5913
|
-
window.Website = Website;
|
|
5914
|
-
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/website/Website.jsx", error: String((e && e.message) || e) }); }
|
|
5915
|
-
|
|
5916
|
-
// ui_kits/website/copy.js
|
|
5917
|
-
try { (() => {
|
|
5918
|
-
window.SITE_COPY = {
|
|
5919
|
-
en: {
|
|
5920
|
-
nav: ["Services", "Work", "Process", "Careers", "Contact"],
|
|
5921
|
-
langLabel: "Tiếng Việt",
|
|
5922
|
-
eyebrow: "A Saigon studio that turns will into working software. Since 2020.",
|
|
5923
|
-
slogan: "Turn Your Will Into Real",
|
|
5924
|
-
sub: "Hiện Thực Hoá Ý Chí",
|
|
5925
|
-
lead: "Lumi, our golden genie, takes a clear wish and helps the human team turn it into software your people actually run — built to ship, and built to last.",
|
|
5926
|
-
wishPlaceholder: "Make a wish… a faster checkout, an app, a tool",
|
|
5927
|
-
wishCta: "Make a wish",
|
|
5928
|
-
talk: "Talk to Lumi",
|
|
5929
|
-
marquee: ["Web apps", "Mobile apps", "Internal systems", "Automation", "Design systems", "Built to last"],
|
|
5930
|
-
servicesTitle: "What we build",
|
|
5931
|
-
servicesLead: "Three focused practices. One team that answers for the result.",
|
|
5932
|
-
services: [
|
|
5933
|
-
{ icon: "sparkle", t: "Web & mobile apps", s: "Product apps your customers reach for — fast, accessible, bilingual by default.", o: ["Web apps", "Mobile apps", "Design systems"] },
|
|
5934
|
-
{ icon: "chat", t: "Internal systems & automation", s: "The tools your team runs on: dashboards, workflows, and the automation behind them.", o: ["Internal systems", "Automation"] },
|
|
5935
|
-
{ icon: "check", t: "Built to last", s: "Shipped software that keeps running — tested, documented, handed over cleanly.", o: ["Testing", "Docs", "Handover"] }
|
|
5936
|
-
],
|
|
5937
|
-
processTitle: "The arc of a wish",
|
|
5938
|
-
processLead: "Every project follows one arc: from a clear wish to software running in the real world.",
|
|
5939
|
-
steps: [
|
|
5940
|
-
{ n: "01", t: "Make a wish", s: "Tell us the outcome in a sentence or two. We reply within one business day." },
|
|
5941
|
-
{ n: "02", t: "Shape it", s: "A small senior team scopes the work and is honest about trade-offs." },
|
|
5942
|
-
{ n: "03", t: "Build to ship", s: "Short cycles, real software you can run — not slideware." },
|
|
5943
|
-
{ n: "04", t: "Built to last", s: "Tested, documented, handed over. It keeps running after we leave." }
|
|
5944
|
-
],
|
|
5945
|
-
careersTitle: "Build with us",
|
|
5946
|
-
careersLead: "We hire for craft and care — senior, small, and honest about trade-offs.",
|
|
5947
|
-
careersCta: "See open roles",
|
|
5948
|
-
contactTitle: "Tell us your wish",
|
|
5949
|
-
contactLead: "Send a short note. We reply within one business day.",
|
|
5950
|
-
form: { name: "Your name", email: "Work email", company: "Company", message: "Anything we should know?", submit: "Send", optional: "optional", trust: "Your details go straight to the team. We never share them." },
|
|
5951
|
-
genie: { title: "Lumi · CyberSkill’s golden genie", greeting: "Hello, I’m Lumi. Think of me as a friendly guide: tell me a wish, and I’ll share plain advice before asking for anything. When a wish is clear, it goes to a real person on the team.", placeholder: "Type your wish…", send: "Send", chips: ["A web app", "An internal tool", "A design system"] },
|
|
5952
|
-
footerRights: "All rights reserved.",
|
|
5953
|
-
footerLinks: ["Privacy", "Accessibility", "Terms"]
|
|
5954
|
-
},
|
|
5955
|
-
vi: {
|
|
5956
|
-
nav: ["Dịch vụ", "Dự án", "Quy trình", "Tuyển dụng", "Liên hệ"],
|
|
5957
|
-
langLabel: "English",
|
|
5958
|
-
eyebrow: "Studio Sài Gòn. Biến ý chí thành phần mềm chạy thật. Từ năm 2020.",
|
|
5959
|
-
slogan: "Hiện Thực Hoá Ý Chí",
|
|
5960
|
-
sub: "Turn Your Will Into Real",
|
|
5961
|
-
lead: "Lumi, thần đèn vàng của CyberSkill, cùng đội ngũ biến điều ước rõ thành phần mềm đội bạn dùng mỗi ngày: bàn giao được, dùng lâu được.",
|
|
5962
|
-
wishPlaceholder: "Ước một điều… thanh toán nhanh hơn, một ứng dụng, một công cụ",
|
|
5963
|
-
wishCta: "Ước điều này",
|
|
5964
|
-
talk: "Trò chuyện với Lumi",
|
|
5965
|
-
marquee: ["Ứng dụng web", "Ứng dụng di động", "Hệ thống nội bộ", "Tự động hóa", "Hệ thống thiết kế", "Bền bỉ lâu dài"],
|
|
5966
|
-
servicesTitle: "Chúng tôi xây dựng những gì",
|
|
5967
|
-
servicesLead: "Ba mảng chuyên sâu. Một đội chịu trách nhiệm đến kết quả cuối cùng.",
|
|
5968
|
-
services: [
|
|
5969
|
-
{ icon: "sparkle", t: "Ứng dụng web & di động", s: "Sản phẩm khách hàng dùng mỗi ngày — nhanh, dễ tiếp cận, song ngữ mặc định.", o: ["Ứng dụng web", "Di động", "Hệ thống thiết kế"] },
|
|
5970
|
-
{ icon: "chat", t: "Hệ thống nội bộ & tự động hóa", s: "Công cụ đội bạn vận hành: bảng điều khiển, quy trình và tự động hóa phía sau.", o: ["Hệ thống nội bộ", "Tự động hóa"] },
|
|
5971
|
-
{ icon: "check", t: "Bền bỉ lâu dài", s: "Phần mềm bàn giao vẫn chạy tốt — được kiểm thử, tài liệu đầy đủ, bàn giao gọn.", o: ["Kiểm thử", "Tài liệu", "Bàn giao"] }
|
|
5972
|
-
],
|
|
5973
|
-
processTitle: "Hành trình của một điều ước",
|
|
5974
|
-
processLead: "Mỗi dự án một hành trình: từ điều ước rõ đến phần mềm chạy giữa đời thật.",
|
|
5975
|
-
steps: [
|
|
5976
|
-
{ n: "01", t: "Ước một điều", s: "Kể kết quả bạn muốn trong một hai câu. Chúng tôi phản hồi trong một ngày làm việc." },
|
|
5977
|
-
{ n: "02", t: "Định hình", s: "Đội nhỏ, kinh nghiệm bóc tách công việc và nói thẳng về đánh đổi." },
|
|
5978
|
-
{ n: "03", t: "Xây để bàn giao", s: "Chu kỳ ngắn, phần mềm chạy thật — không phải slide." },
|
|
5979
|
-
{ n: "04", t: "Dùng lâu được", s: "Kiểm thử, tài liệu, bàn giao. Vẫn chạy tốt sau khi chúng tôi rời đi." }
|
|
5980
|
-
],
|
|
5981
|
-
careersTitle: "Cùng chúng tôi kiến tạo",
|
|
5982
|
-
careersLead: "Tuyển người có tay nghề và chỉn chu — đội gọn, kinh nghiệm, nói thẳng đánh đổi.",
|
|
5983
|
-
careersCta: "Xem vị trí đang mở",
|
|
5984
|
-
contactTitle: "Kể chúng tôi nghe điều bạn ước",
|
|
5985
|
-
contactLead: "Một lời nhắn ngắn là đủ. Chúng tôi phản hồi trong một ngày làm việc.",
|
|
5986
|
-
form: { name: "Tên của bạn", email: "Email công việc", company: "Công ty", message: "Bạn muốn chia sẻ thêm điều gì?", submit: "Gửi", optional: "không bắt buộc", trust: "Thông tin của bạn được gửi thẳng đến đội ngũ. Chúng tôi không chia sẻ cho bên thứ ba." },
|
|
5987
|
-
genie: { title: "Lumi · thần đèn vàng CyberSkill", greeting: "Chào bạn, mình là Lumi. Cứ kể mình một điều ước; mình tư vấn trước, chỉ xin thông tin khi bạn muốn gửi. Điều ước rõ sẽ được chuyển cho người thật trong đội.", placeholder: "Viết điều ước của bạn…", send: "Gửi", chips: ["Một ứng dụng web", "Công cụ nội bộ", "Hệ thống thiết kế"] },
|
|
5988
|
-
footerRights: "Bảo lưu mọi quyền.",
|
|
5989
|
-
footerLinks: ["Quyền riêng tư", "Khả năng tiếp cận", "Điều khoản"]
|
|
5990
|
-
}
|
|
5991
|
-
};
|
|
5992
|
-
})(); } catch (e) { __ds_ns.__errors.push({ path: "ui_kits/website/copy.js", error: String((e && e.message) || e) }); }
|
|
5993
|
-
|
|
5994
5353
|
__ds_ns.AIDisclosureBadge = __ds_scope.AIDisclosureBadge;
|
|
5995
5354
|
|
|
5996
5355
|
__ds_ns.ChatMessage = __ds_scope.ChatMessage;
|