@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.
Files changed (232) hide show
  1. package/DESIGN.md +11 -9
  2. package/README.md +28 -24
  3. package/SKILL.md +18 -9
  4. package/THIRD-PARTY-NOTICES.md +35 -0
  5. package/VERSION +1 -1
  6. package/_ds_bundle.js +701 -1342
  7. package/_esm/cs.mjs +2 -2
  8. package/_esm/react.mjs +6 -1
  9. package/_vendor/NOTICE.md +16 -0
  10. package/_vendor/babel/README.md +7 -0
  11. package/_vendor/babel/babel.min.js +4 -0
  12. package/_vendor/react/README.md +3 -0
  13. package/_vendor/react/react-dom.production.min.js +1 -0
  14. package/_vendor/react/react.production.min.js +1 -0
  15. package/assets/aurora-hoa.webp +0 -0
  16. package/assets/aurora-kim.webp +0 -0
  17. package/assets/aurora-moc.webp +0 -0
  18. package/assets/aurora-tho.webp +0 -0
  19. package/assets/aurora-thuy.webp +0 -0
  20. package/base/ai.css +8 -3
  21. package/base/collections.css +10 -4
  22. package/base/components.css +5 -2
  23. package/base/controls.css +58 -21
  24. package/base/data.css +1 -1
  25. package/base/feedback.css +9 -2
  26. package/base/forms.css +1 -1
  27. package/base/glass.css +1 -1
  28. package/base/interaction.css +6 -2
  29. package/base/navigation.css +1 -1
  30. package/base/reset.css +7 -1
  31. package/base/responsive.css +13 -12
  32. package/base/shell.css +8 -2
  33. package/components/_i18n/i18n.js +28 -3
  34. package/components/_i18n/strings.js +42 -24
  35. package/components/ai/ChatMessage.d.ts +2 -0
  36. package/components/ai/ChatMessage.jsx +8 -4
  37. package/components/ai/ConfidenceMeter.d.ts +2 -0
  38. package/components/ai/HumanReviewGate.d.ts +2 -0
  39. package/components/ai/PromptInput.d.ts +2 -0
  40. package/components/ai/TypingIndicator.d.ts +2 -0
  41. package/components/ai/ai-conversation.card.html +3 -3
  42. package/components/ai/ai-lumi.card.html +3 -3
  43. package/components/ai/ai.card.html +3 -3
  44. package/components/brand/brand.card.html +3 -3
  45. package/components/button/buttons.card.html +3 -3
  46. package/components/data/Image.jsx +18 -2
  47. package/components/data/Sortable.d.ts +5 -1
  48. package/components/data/Sortable.jsx +42 -9
  49. package/components/data/Sortable.prompt.md +4 -2
  50. package/components/data/Terminal.jsx +3 -2
  51. package/components/data/Tooltip.d.ts +2 -0
  52. package/components/data/Tooltip.jsx +6 -3
  53. package/components/data/Tree.d.ts +2 -1
  54. package/components/data/Tree.jsx +137 -16
  55. package/components/data/Tree.prompt.md +1 -1
  56. package/components/data/collections.card.html +3 -3
  57. package/components/data/data.card.html +3 -3
  58. package/components/datatable/DataTable.d.ts +24 -1
  59. package/components/datatable/DataTable.jsx +78 -5
  60. package/components/datatable/DataTable.prompt.md +2 -1
  61. package/components/datatable/datatable.card.html +20 -16
  62. package/components/dialog/dialog.card.html +3 -3
  63. package/components/feedback/Alert.d.ts +2 -0
  64. package/components/feedback/Alert.jsx +8 -2
  65. package/components/feedback/EmptyState.d.ts +2 -0
  66. package/components/feedback/EmptyState.jsx +6 -3
  67. package/components/feedback/ProgressBar.d.ts +2 -0
  68. package/components/feedback/ProgressBar.jsx +6 -3
  69. package/components/feedback/Toast.jsx +15 -1
  70. package/components/feedback/Toast.prompt.md +2 -1
  71. package/components/feedback/feedback-2.card.html +3 -3
  72. package/components/feedback/feedback.card.html +3 -3
  73. package/components/forms/Cascader.jsx +7 -2
  74. package/components/forms/FileUpload.d.ts +2 -0
  75. package/components/forms/Form.d.ts +3 -1
  76. package/components/forms/Form.jsx +9 -1
  77. package/components/forms/Form.prompt.md +1 -1
  78. package/components/forms/Mentions.d.ts +1 -1
  79. package/components/forms/Mentions.jsx +59 -7
  80. package/components/forms/Mentions.prompt.md +3 -1
  81. package/components/forms/Rating.d.ts +3 -2
  82. package/components/forms/Rating.jsx +34 -3
  83. package/components/forms/Rating.prompt.md +1 -1
  84. package/components/forms/Select.d.ts +2 -0
  85. package/components/forms/Select.jsx +6 -2
  86. package/components/forms/Slider.d.ts +2 -0
  87. package/components/forms/Slider.jsx +5 -2
  88. package/components/forms/Switch.d.ts +2 -0
  89. package/components/forms/Switch.jsx +7 -9
  90. package/components/forms/Toggle.d.ts +2 -0
  91. package/components/forms/Toggle.jsx +14 -2
  92. package/components/forms/forms-2.card.html +3 -3
  93. package/components/forms/forms.card.html +3 -3
  94. package/components/icon/Icon.d.ts +1 -1
  95. package/components/icon/Icon.prompt.md +1 -1
  96. package/components/icon/icon.card.html +3 -3
  97. package/components/logo/logo.card.html +3 -3
  98. package/components/navigation/CommandPalette.d.ts +2 -0
  99. package/components/navigation/Menu.d.ts +2 -0
  100. package/components/navigation/Menu.jsx +60 -6
  101. package/components/navigation/Steps.d.ts +2 -0
  102. package/components/navigation/Steps.jsx +6 -3
  103. package/components/navigation/Tabs.d.ts +2 -0
  104. package/components/navigation/Tabs.jsx +6 -3
  105. package/components/navigation/Toolbar.d.ts +3 -2
  106. package/components/navigation/Toolbar.jsx +117 -11
  107. package/components/navigation/Toolbar.prompt.md +1 -1
  108. package/components/navigation/navigation.card.html +3 -3
  109. package/components/navigation/shell.card.html +3 -3
  110. package/components/overlays/ContextMenu.d.ts +2 -0
  111. package/components/overlays/ContextMenu.jsx +6 -3
  112. package/components/overlays/Drawer.d.ts +2 -0
  113. package/components/overlays/alertdialog.card.html +3 -3
  114. package/components/overlays/overlays.card.html +3 -3
  115. package/components/textfield/textfield.card.html +3 -3
  116. package/dist/styles.min.css +312 -64
  117. package/docs/benchmark-rubric.md +2 -2
  118. package/docs/ci-cd.md +18 -13
  119. package/docs/consumer-grant.md +4 -1
  120. package/docs/consuming.md +16 -12
  121. package/docs/contrast-report.md +81 -1
  122. package/docs/conventions.md +18 -5
  123. package/docs/decisions.md +41 -3
  124. package/docs/deploy.md +19 -6
  125. package/docs/design-styles.md +2 -2
  126. package/docs/doctrine.md +51 -0
  127. package/docs/figma.md +2 -0
  128. package/docs/live-hub.md +3 -1
  129. package/docs/products.md +1 -1
  130. package/docs/quality-gates.md +24 -12
  131. package/docs/release-notes.md +1 -1
  132. package/docs/release-runbook.md +78 -0
  133. package/docs/storybook.md +3 -3
  134. package/docs/sync.md +8 -8
  135. package/docs/template-schema-v2.md +1 -1
  136. package/docs/vi/benchmark-rubric.md +2 -2
  137. package/docs/vi/ci-cd.md +17 -14
  138. package/docs/vi/consumer-grant.md +4 -1
  139. package/docs/vi/consuming.md +16 -12
  140. package/docs/vi/contrast-report.md +81 -1
  141. package/docs/vi/conventions.md +19 -6
  142. package/docs/vi/decisions.md +40 -3
  143. package/docs/vi/deploy.md +19 -6
  144. package/docs/vi/design-styles.md +1 -1
  145. package/docs/vi/doctrine.md +51 -0
  146. package/docs/vi/figma.md +2 -0
  147. package/docs/vi/live-hub.md +3 -1
  148. package/docs/vi/products.md +1 -1
  149. package/docs/vi/quality-gates.md +23 -12
  150. package/docs/vi/release-notes.md +1 -1
  151. package/docs/vi/release-runbook.md +78 -0
  152. package/docs/vi/storybook.md +3 -3
  153. package/docs/vi/sync.md +7 -7
  154. package/docs/vi/template-schema-v2.md +1 -1
  155. package/docs/viewer.html +16 -5
  156. package/fonts/OFL.txt +98 -0
  157. package/guidelines/atomic-view.html +2 -2
  158. package/guidelines/brand-imagery.html +1 -1
  159. package/guidelines/elements-variants.html +5 -5
  160. package/guidelines/spacing-breakpoints.html +2 -1
  161. package/guidelines/surfaces-glass.html +1 -1
  162. package/llms.txt +18 -11
  163. package/package.json +28 -8
  164. package/templates/app-shell/AppShell.dc.html +3 -3
  165. package/templates/app-shell/content-schema.json +7 -0
  166. package/templates/article/Article.dc.html +3 -3
  167. package/templates/article/content-schema.json +7 -0
  168. package/templates/auth/Auth.dc.html +26 -10
  169. package/templates/auth/content-schema.json +25 -0
  170. package/templates/dashboard/Dashboard.dc.html +4 -4
  171. package/templates/dashboard/content-schema.json +7 -0
  172. package/templates/delivery-change-note/DeliveryChangeNote.dc.html +2 -2
  173. package/templates/delivery-runbook/DeliveryRunbook.dc.html +2 -2
  174. package/templates/email-safe/email-announcement.html +2 -2
  175. package/templates/email-safe/email-dunning.html +2 -2
  176. package/templates/email-safe/email-investor.html +2 -2
  177. package/templates/email-safe/email-launch.html +2 -2
  178. package/templates/email-safe/email-newsletter.html +2 -2
  179. package/templates/email-safe/email-status.html +2 -2
  180. package/templates/email-safe/email-transactional.html +2 -2
  181. package/templates/finance-dunning-email/FinanceDunningEmail.dc.html +2 -2
  182. package/templates/finance-expense-report/FinanceExpenseReport.dc.html +2 -2
  183. package/templates/finance-invoice/FinanceInvoice.dc.html +2 -2
  184. package/templates/finance-quote/FinanceQuote.dc.html +4 -4
  185. package/templates/hr-interview-kit/HrInterviewKit.dc.html +1 -1
  186. package/templates/marketing-case-study/MarketingCaseStudy.dc.html +1 -1
  187. package/templates/marketing-page/MarketingPage.dc.html +3 -3
  188. package/templates/marketing-page/content-schema.json +1 -0
  189. package/templates/settings/Settings.dc.html +3 -3
  190. package/templates/settings/content-schema.json +7 -0
  191. package/tokens/element-seeds.json +2 -2
  192. package/tokens/elements.css +193 -19
  193. package/tokens/native/CSTokens.kt +3 -2
  194. package/tokens/native/CSTokens.swift +3 -2
  195. package/tokens/native/cs_tokens.dart +3 -2
  196. package/tokens/provenance.json +8 -8
  197. package/tokens/spacing.css +2 -1
  198. package/tokens/tokens.dtcg.json +19 -14
  199. package/tokens/tokens.js +16 -15
  200. package/tokens/tokens.json +15 -14
  201. package/ui_kits/deck/index.html +1 -1
  202. package/ui_kits/status-hub/StatusHub.jsx +1 -1
  203. package/ui_kits/status-hub/login.html +52 -10
  204. package/ui_kits/status-hub/project.html +1 -1
  205. package/ui_kits/status-hub/settings.html +1 -1
  206. package/ui_kits/website/Website.jsx +1 -1
  207. package/ui_kits/website/careers.html +2 -2
  208. package/ui_kits/website/chat.html +1 -1
  209. package/ui_kits/website/site.css +1 -1
  210. package/ui_kits/website/work.html +2 -2
  211. package/assets/aurora-hoa.png +0 -0
  212. package/assets/aurora-kim.png +0 -0
  213. package/assets/aurora-moc.png +0 -0
  214. package/assets/aurora-thuy.png +0 -0
  215. package/docs/decisions/container-query-migration.md +0 -18
  216. package/docs/decisions/overlay-manager-design.md +0 -101
  217. package/docs/plans/cyberskill-design-system-audit-and-12-month-evolution-plan.md +0 -1490
  218. package/docs/status/assets/favicon.svg +0 -5
  219. package/docs/status/assets/status-legacy.css +0 -399
  220. package/docs/status/assets/status-legacy.js +0 -505
  221. package/docs/status/assets/status.css +0 -644
  222. package/docs/status/assets/status.js +0 -1308
  223. package/docs/status/data/status-feed.json +0 -1
  224. package/docs/status/data/task/TASK-REL-001.js +0 -1
  225. package/docs/status/data/task/TASK-REL-002.js +0 -1
  226. package/docs/status/index.html +0 -46
  227. package/docs/status/roadmap.html +0 -4
  228. package/docs/status/status-legacy.html +0 -122
  229. package/docs/tasks/BACKLOG.md +0 -26
  230. package/docs/tasks/_state/commit-links.yaml +0 -14
  231. package/docs/tasks/release/TASK-REL-001-launch-1-1-0/spec.md +0 -55
  232. 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((t) => t.value === value));
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 (role=toolbar). Items after `overflowAfter` collapse
4
- * into a "⋯" menu (bilingual aria). "-" renders a separator. */
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 "⋯" menu. items: {label,icon,onSelect} or "-". */
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) => it === "-"
23
- ? <span key={i} className="cs-toolbar__sep" aria-hidden="true" />
24
- : <button key={i} type="button" className="cs-toolbar__btn" onClick={() => it.onSelect && it.onSelect()}>{it.icon}{it.label ? <span>{it.label}</span> : null}</button>)}
25
- {tail.length ? (
26
- <span className="cs-toolbar__more">
27
- <button type="button" className="cs-toolbar__btn" aria-haspopup="menu" aria-expanded={open} aria-label={t("more")} onClick={() => setOpen((o) => !o)}>⋯</button>
28
- {open ? <span className="cs-menu__list" role="menu">{tail.map((it, i) => <button key={i} type="button" role="menuitem" className="cs-menu__item" onClick={() => { setOpen(false); it.onSelect && it.onSelect(); }}>{it.label}</button>)}</span> : null}
29
- </span>
30
- ) : null}
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="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
15
- <script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
16
- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
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="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
17
- <script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
18
- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
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="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
15
- <script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
16
- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
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="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
15
- <script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
16
- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
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="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
13
- <script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
14
- <script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
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>