@cyberskill/design 1.2.0 → 1.3.9
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 +11 -9
- package/README.md +28 -24
- package/SKILL.md +18 -9
- 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/benchmark-rubric.md +2 -2
- package/docs/ci-cd.md +18 -13
- package/docs/consumer-grant.md +4 -1
- package/docs/consuming.md +16 -12
- package/docs/contrast-report.md +81 -1
- package/docs/conventions.md +18 -5
- package/docs/decisions.md +41 -3
- package/docs/deploy.md +19 -6
- package/docs/design-styles.md +2 -2
- package/docs/doctrine.md +51 -0
- package/docs/figma.md +2 -0
- package/docs/live-hub.md +3 -1
- package/docs/products.md +1 -1
- package/docs/quality-gates.md +24 -12
- package/docs/release-notes.md +1 -1
- package/docs/release-runbook.md +78 -0
- package/docs/storybook.md +3 -3
- package/docs/sync.md +8 -8
- package/docs/template-schema-v2.md +1 -1
- package/docs/vi/benchmark-rubric.md +2 -2
- package/docs/vi/ci-cd.md +17 -14
- package/docs/vi/consumer-grant.md +4 -1
- package/docs/vi/consuming.md +16 -12
- package/docs/vi/contrast-report.md +81 -1
- package/docs/vi/conventions.md +19 -6
- package/docs/vi/decisions.md +40 -3
- package/docs/vi/deploy.md +19 -6
- package/docs/vi/design-styles.md +1 -1
- package/docs/vi/doctrine.md +51 -0
- package/docs/vi/figma.md +2 -0
- package/docs/vi/live-hub.md +3 -1
- package/docs/vi/products.md +1 -1
- package/docs/vi/quality-gates.md +23 -12
- package/docs/vi/release-notes.md +1 -1
- package/docs/vi/release-runbook.md +78 -0
- package/docs/vi/storybook.md +3 -3
- package/docs/vi/sync.md +7 -7
- package/docs/vi/template-schema-v2.md +1 -1
- package/docs/viewer.html +16 -5
- 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 +18 -11
- 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
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
2
3
|
import { cx } from "../_utils/cx.js";
|
|
3
4
|
|
|
4
5
|
/** CyberSkill Tabs — data-driven tablist, controlled by value/onChange. Ochre underline on active. */
|
|
5
|
-
export function Tabs({ tabs = [], value, onChange, className, ...props }) {
|
|
6
|
+
export function Tabs({ tabs = [], value, onChange, lang, className, "aria-label": ariaLabel, ...props }) {
|
|
7
|
+
const [ref, L] = useLang(lang);
|
|
8
|
+
const t = makeT("Tabs", L);
|
|
6
9
|
const refs = React.useRef([]);
|
|
7
|
-
const idx = Math.max(0, tabs.findIndex((
|
|
10
|
+
const idx = Math.max(0, tabs.findIndex((tab) => tab.value === value));
|
|
8
11
|
const key = (e, i) => {
|
|
9
12
|
let n = null;
|
|
10
13
|
if (e.key === "ArrowRight") n = (i + 1) % tabs.length;
|
|
@@ -17,7 +20,7 @@ export function Tabs({ tabs = [], value, onChange, className, ...props }) {
|
|
|
17
20
|
const b = refs.current[n]; if (b) b.focus();
|
|
18
21
|
};
|
|
19
22
|
return (
|
|
20
|
-
<div role="tablist" className={cx("cs-tabs", className)} {...props}>
|
|
23
|
+
<div ref={ref} role="tablist" aria-label={ariaLabel ?? t("list")} className={cx("cs-tabs", className)} {...props}>
|
|
21
24
|
{tabs.map((t, i) => (
|
|
22
25
|
<button key={t.value} type="button" role="tab" aria-selected={value === t.value} tabIndex={i === idx ? 0 : -1}
|
|
23
26
|
ref={(el) => (refs.current[i] = el)} onKeyDown={(e) => key(e, i)}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
-
/** Horizontal action bar (
|
|
4
|
-
*
|
|
3
|
+
/** Horizontal action bar (APG toolbar: arrow-key roving among controls).
|
|
4
|
+
* Items after `overflowAfter` collapse into a ⋯ menubutton + popup menu
|
|
5
|
+
* (bilingual aria). "-" renders a separator. */
|
|
5
6
|
export type ToolbarItem = { label?: ReactNode; icon?: ReactNode; onSelect?: () => void } | "-";
|
|
6
7
|
export interface ToolbarProps {
|
|
7
8
|
items: ToolbarItem[];
|
|
@@ -2,32 +2,138 @@ import React from "react";
|
|
|
2
2
|
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
3
3
|
import { cx } from "../_utils/cx.js";
|
|
4
4
|
|
|
5
|
-
/** CyberSkill Toolbar — horizontal action bar; overflow items collapse into a
|
|
5
|
+
/** CyberSkill Toolbar — horizontal action bar (APG toolbar arrows); overflow items collapse into a menubutton + popup menu. items: {label,icon,onSelect} or "-". */
|
|
6
6
|
export function Toolbar({ items = [], overflowAfter, label, lang, className }) {
|
|
7
7
|
const [open, setOpen] = React.useState(false);
|
|
8
|
+
const [focusIdx, setFocusIdx] = React.useState(0);
|
|
8
9
|
const wrap = React.useRef(null);
|
|
10
|
+
const btnRefs = React.useRef([]);
|
|
11
|
+
const menuRefs = React.useRef([]);
|
|
9
12
|
const [ref, L] = useLang(lang);
|
|
10
13
|
const t = makeT("Toolbar", L);
|
|
11
14
|
const cut = overflowAfter != null ? overflowAfter : items.length;
|
|
12
15
|
const head = items.slice(0, cut);
|
|
13
16
|
const tail = items.slice(cut).filter((x) => x !== "-");
|
|
17
|
+
const hasMore = tail.length > 0;
|
|
18
|
+
|
|
19
|
+
// Controllable tab stops: head action buttons + optional ⋯ menubutton (separators skipped).
|
|
20
|
+
const controlMeta = [];
|
|
21
|
+
head.forEach((it, i) => {
|
|
22
|
+
if (it !== "-") controlMeta.push({ kind: "head", headIndex: i });
|
|
23
|
+
});
|
|
24
|
+
if (hasMore) controlMeta.push({ kind: "more" });
|
|
25
|
+
|
|
14
26
|
React.useEffect(() => {
|
|
15
27
|
if (!open) return;
|
|
16
28
|
const d = (e) => { if (wrap.current && !wrap.current.contains(e.target)) setOpen(false); };
|
|
17
29
|
document.addEventListener("mousedown", d);
|
|
18
30
|
return () => document.removeEventListener("mousedown", d);
|
|
19
31
|
}, [open]);
|
|
32
|
+
|
|
33
|
+
React.useEffect(() => {
|
|
34
|
+
if (focusIdx >= controlMeta.length) setFocusIdx(Math.max(0, controlMeta.length - 1));
|
|
35
|
+
}, [focusIdx, controlMeta.length]);
|
|
36
|
+
|
|
37
|
+
const focusControl = (i) => {
|
|
38
|
+
const next = Math.max(0, Math.min(controlMeta.length - 1, i));
|
|
39
|
+
setFocusIdx(next);
|
|
40
|
+
requestAnimationFrame(() => {
|
|
41
|
+
const el = btnRefs.current[next];
|
|
42
|
+
if (el) el.focus();
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const onToolbarKeyDown = (e, i) => {
|
|
47
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
48
|
+
if (e.key === "ArrowRight") { e.preventDefault(); focusControl(i + 1); }
|
|
49
|
+
else if (e.key === "ArrowLeft") { e.preventDefault(); focusControl(i - 1); }
|
|
50
|
+
else if (e.key === "Home") { e.preventDefault(); focusControl(0); }
|
|
51
|
+
else if (e.key === "End") { e.preventDefault(); focusControl(controlMeta.length - 1); }
|
|
52
|
+
else if (e.key === "Escape" && open) { e.preventDefault(); setOpen(false); }
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const onMenuKeyDown = (e, i) => {
|
|
56
|
+
if (e.nativeEvent.isComposing || e.keyCode === 229) return;
|
|
57
|
+
if (e.key === "ArrowDown") {
|
|
58
|
+
e.preventDefault();
|
|
59
|
+
const n = Math.min(tail.length - 1, i + 1);
|
|
60
|
+
const el = menuRefs.current[n]; if (el) el.focus();
|
|
61
|
+
} else if (e.key === "ArrowUp") {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
const n = Math.max(0, i - 1);
|
|
64
|
+
const el = menuRefs.current[n]; if (el) el.focus();
|
|
65
|
+
} else if (e.key === "Escape") {
|
|
66
|
+
e.preventDefault();
|
|
67
|
+
setOpen(false);
|
|
68
|
+
const moreIdx = controlMeta.findIndex((c) => c.kind === "more");
|
|
69
|
+
if (moreIdx >= 0) focusControl(moreIdx);
|
|
70
|
+
} else if (e.key === "Home") {
|
|
71
|
+
e.preventDefault();
|
|
72
|
+
const el = menuRefs.current[0]; if (el) el.focus();
|
|
73
|
+
} else if (e.key === "End") {
|
|
74
|
+
e.preventDefault();
|
|
75
|
+
const el = menuRefs.current[tail.length - 1]; if (el) el.focus();
|
|
76
|
+
}
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
let controlI = 0;
|
|
20
80
|
return (
|
|
21
|
-
<div ref={(el) => { wrap.current = el; ref.current = el; }} className={cx("cs-toolbar", className)} role="toolbar" aria-label={label}>
|
|
22
|
-
{head.map((it, i) =>
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
81
|
+
<div ref={(el) => { wrap.current = el; ref.current = el; }} className={cx("cs-toolbar", className)} role="toolbar" aria-label={label} aria-orientation="horizontal">
|
|
82
|
+
{head.map((it, i) => {
|
|
83
|
+
if (it === "-") return <span key={"sep-" + i} className="cs-toolbar__sep" aria-hidden="true" />;
|
|
84
|
+
const ci = controlI++;
|
|
85
|
+
return (
|
|
86
|
+
<button
|
|
87
|
+
key={"btn-" + i}
|
|
88
|
+
type="button"
|
|
89
|
+
className="cs-toolbar__btn"
|
|
90
|
+
tabIndex={ci === focusIdx ? 0 : -1}
|
|
91
|
+
ref={(el) => { btnRefs.current[ci] = el; }}
|
|
92
|
+
onFocus={() => setFocusIdx(ci)}
|
|
93
|
+
onKeyDown={(e) => onToolbarKeyDown(e, ci)}
|
|
94
|
+
onClick={() => it.onSelect && it.onSelect()}
|
|
95
|
+
>{it.icon}{it.label ? <span>{it.label}</span> : null}</button>
|
|
96
|
+
);
|
|
97
|
+
})}
|
|
98
|
+
{hasMore ? (() => {
|
|
99
|
+
const ci = controlI++;
|
|
100
|
+
return (
|
|
101
|
+
<span className="cs-toolbar__more">
|
|
102
|
+
<button
|
|
103
|
+
type="button"
|
|
104
|
+
className="cs-toolbar__btn"
|
|
105
|
+
aria-haspopup="menu"
|
|
106
|
+
aria-expanded={open}
|
|
107
|
+
aria-label={t("more")}
|
|
108
|
+
tabIndex={ci === focusIdx ? 0 : -1}
|
|
109
|
+
ref={(el) => { btnRefs.current[ci] = el; }}
|
|
110
|
+
onFocus={() => setFocusIdx(ci)}
|
|
111
|
+
onKeyDown={(e) => {
|
|
112
|
+
onToolbarKeyDown(e, ci);
|
|
113
|
+
if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
|
|
114
|
+
if (!open) { e.preventDefault(); setOpen(true); requestAnimationFrame(() => { const el = menuRefs.current[0]; if (el) el.focus(); }); }
|
|
115
|
+
}
|
|
116
|
+
}}
|
|
117
|
+
onClick={() => setOpen((o) => !o)}
|
|
118
|
+
>⋯</button>
|
|
119
|
+
{open ? (
|
|
120
|
+
<span className="cs-menu__list" role="menu">
|
|
121
|
+
{tail.map((it, i) => (
|
|
122
|
+
<button
|
|
123
|
+
key={i}
|
|
124
|
+
type="button"
|
|
125
|
+
role="menuitem"
|
|
126
|
+
className="cs-menu__item"
|
|
127
|
+
ref={(el) => { menuRefs.current[i] = el; }}
|
|
128
|
+
onKeyDown={(e) => onMenuKeyDown(e, i)}
|
|
129
|
+
onClick={() => { setOpen(false); it.onSelect && it.onSelect(); }}
|
|
130
|
+
>{it.label}</button>
|
|
131
|
+
))}
|
|
132
|
+
</span>
|
|
133
|
+
) : null}
|
|
134
|
+
</span>
|
|
135
|
+
);
|
|
136
|
+
})() : null}
|
|
31
137
|
</div>
|
|
32
138
|
);
|
|
33
139
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
**Toolbar** — action bar with "⋯" overflow menu and "-" separators.
|
|
1
|
+
**Toolbar** — action bar with APG arrow navigation, "⋯" overflow menubutton + popup menu, and "-" separators. Escape closes the overflow menu.
|
|
2
2
|
|
|
3
3
|
```jsx
|
|
4
4
|
<Toolbar label="Editor" overflowAfter={3} items={[{icon:<Icon name="edit" size="sm"/>,label:"Edit"},"-",{label:"Share"},{label:"Export"},{label:"Archive"}]}/>
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
.lbl { font: 600 11px/1 var(--cs-font-family-ui); text-transform: uppercase; letter-spacing: .09em; color: var(--cs-color-text-muted); margin-bottom: 10px; }
|
|
12
12
|
.row { display: flex; align-items: center; gap: 20px; flex-wrap: wrap; }
|
|
13
13
|
</style>
|
|
14
|
-
<script src="
|
|
15
|
-
<script src="
|
|
16
|
-
<script src="
|
|
14
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
15
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
16
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
17
17
|
<script src="../../_ds_bundle.js"></script>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
.frame .cs-cmdk-scrim { position: absolute; padding-top: 14px; }
|
|
14
14
|
.frame .cs-cmdk { inline-size: 100%; box-shadow: none; }
|
|
15
15
|
</style>
|
|
16
|
-
<script src="
|
|
17
|
-
<script src="
|
|
18
|
-
<script src="
|
|
16
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
17
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
18
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
19
19
|
<script src="../../_ds_bundle.js"></script>
|
|
20
20
|
</head>
|
|
21
21
|
<body>
|
|
@@ -5,6 +5,8 @@ export type ContextMenuItem = { label: ReactNode; onSelect?: () => void; danger?
|
|
|
5
5
|
export interface ContextMenuProps {
|
|
6
6
|
items: ContextMenuItem[];
|
|
7
7
|
children?: ReactNode;
|
|
8
|
+
/** "en" | "vi" — else resolved from the nearest [lang] ancestor (vi default). */
|
|
9
|
+
lang?: string;
|
|
8
10
|
className?: string;
|
|
9
11
|
}
|
|
10
12
|
export function ContextMenu(props: ContextMenuProps): React.ReactElement;
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import { makeT, useLang } from "../_i18n/i18n.js";
|
|
2
3
|
import { cx } from "../_utils/cx.js";
|
|
3
4
|
|
|
4
5
|
/** CyberSkill ContextMenu — right-click menu scoped to its children. items: {label,onSelect,danger} or "-" separator. */
|
|
5
|
-
export function ContextMenu({ items = [], children, className }) {
|
|
6
|
+
export function ContextMenu({ items = [], children, lang, className }) {
|
|
6
7
|
const [pos, setPos] = React.useState(null);
|
|
8
|
+
const [ref, L] = useLang(lang);
|
|
9
|
+
const t = makeT("ContextMenu", L);
|
|
7
10
|
React.useEffect(() => {
|
|
8
11
|
if (!pos) return;
|
|
9
12
|
const close = () => setPos(null);
|
|
@@ -13,11 +16,11 @@ export function ContextMenu({ items = [], children, className }) {
|
|
|
13
16
|
return () => { document.removeEventListener("click", close); document.removeEventListener("keydown", k); };
|
|
14
17
|
}, [pos]);
|
|
15
18
|
return (
|
|
16
|
-
<div className={cx("cs-ctxmenu-zone", className)}
|
|
19
|
+
<div ref={ref} className={cx("cs-ctxmenu-zone", className)}
|
|
17
20
|
onContextMenu={(e) => { e.preventDefault(); const r = e.currentTarget.getBoundingClientRect(); setPos({ x: e.clientX - r.left, y: e.clientY - r.top }); }}>
|
|
18
21
|
{children}
|
|
19
22
|
{pos ? (
|
|
20
|
-
<div className="cs-menu__list" role="menu" style={{ position: "absolute", insetInlineStart: pos.x, insetBlockStart: pos.y }}>
|
|
23
|
+
<div className="cs-menu__list" role="menu" aria-label={t("menu")} style={{ position: "absolute", insetInlineStart: pos.x, insetBlockStart: pos.y }}>
|
|
21
24
|
{items.map((it, i) => it === "-"
|
|
22
25
|
? <div key={i} className="cs-menu__sep" />
|
|
23
26
|
: <button key={i} type="button" role="menuitem" className={cx("cs-menu__item", it.danger && "cs-menu__item--danger")} onClick={() => { setPos(null); it.onSelect && it.onSelect(); }}>{it.label}</button>)}
|
|
@@ -13,6 +13,8 @@ export interface DrawerProps {
|
|
|
13
13
|
side?: "right" | "left";
|
|
14
14
|
children?: ReactNode;
|
|
15
15
|
actions?: ReactNode;
|
|
16
|
+
/** "en" | "vi" — else resolved from the nearest [lang] ancestor (vi default). */
|
|
17
|
+
lang?: string;
|
|
16
18
|
className?: string;
|
|
17
19
|
}
|
|
18
20
|
export function Drawer(props: DrawerProps): React.ReactElement | null;
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
.frame .cs-dialog-layer { position: absolute; }
|
|
12
12
|
.frame .cs-dialog { top: 28px; }
|
|
13
13
|
</style>
|
|
14
|
-
<script src="
|
|
15
|
-
<script src="
|
|
16
|
-
<script src="
|
|
14
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
15
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
16
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
17
17
|
<script src="../../_ds_bundle.js"></script>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
.frame { position: relative; overflow: hidden; height: 260px; border: 1px solid var(--cs-color-border-default); border-radius: var(--cs-radius-lg); background: var(--cs-color-surface-raised); }
|
|
12
12
|
.frame .cs-drawer-scrim, .frame .cs-drawer { position: absolute; animation: none; }
|
|
13
13
|
</style>
|
|
14
|
-
<script src="
|
|
15
|
-
<script src="
|
|
16
|
-
<script src="
|
|
14
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
15
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
16
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
17
17
|
<script src="../../_ds_bundle.js"></script>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
body { margin: 0; background: var(--cs-color-surface-page); padding: 22px 24px; }
|
|
10
10
|
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 18px 24px; max-width: 640px; }
|
|
11
11
|
</style>
|
|
12
|
-
<script src="
|
|
13
|
-
<script src="
|
|
14
|
-
<script src="
|
|
12
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
13
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
14
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
15
15
|
<script src="../../_ds_bundle.js"></script>
|
|
16
16
|
</head>
|
|
17
17
|
<body>
|