@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
|
@@ -17,8 +17,8 @@ export const strings = {
|
|
|
17
17
|
vi: { label: "Đường dẫn" },
|
|
18
18
|
},
|
|
19
19
|
CommandPalette: {
|
|
20
|
-
en: { placeholder: "Type a command or search…", empty: "No results
|
|
21
|
-
vi: { placeholder: "Nhập lệnh hoặc tìm kiếm…", empty: "Không
|
|
20
|
+
en: { placeholder: "Type a command or search…", empty: "No results", aria: "Command palette", esc: "Esc" },
|
|
21
|
+
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" },
|
|
22
22
|
},
|
|
23
23
|
SearchField: {
|
|
24
24
|
en: { placeholder: "Search…", clear: "Clear search" },
|
|
@@ -41,12 +41,22 @@ export const strings = {
|
|
|
41
41
|
vi: { confirm: "Xác nhận", cancel: "Hủy" },
|
|
42
42
|
},
|
|
43
43
|
Drawer: {
|
|
44
|
-
en: { close: "Close", panel: "
|
|
45
|
-
vi: { close: "Đóng", panel: "Bảng" },
|
|
44
|
+
en: { close: "Close", panel: "Side panel" },
|
|
45
|
+
vi: { close: "Đóng", panel: "Bảng bên" },
|
|
46
46
|
},
|
|
47
47
|
DataTable: {
|
|
48
|
-
en: {
|
|
49
|
-
|
|
48
|
+
en: {
|
|
49
|
+
empty: "No results",
|
|
50
|
+
loading: "Loading table",
|
|
51
|
+
error: "We couldn't load this table",
|
|
52
|
+
errorHint: "Check your connection, then try again.",
|
|
53
|
+
},
|
|
54
|
+
vi: {
|
|
55
|
+
empty: "Không có kết quả",
|
|
56
|
+
loading: "Đang tải bảng",
|
|
57
|
+
error: "Không tải được bảng này",
|
|
58
|
+
errorHint: "Kiểm tra kết nối, rồi thử lại.",
|
|
59
|
+
},
|
|
50
60
|
},
|
|
51
61
|
ConfidenceMeter: {
|
|
52
62
|
en: { label: "Confidence", low: "Low", medium: "Medium", high: "High" },
|
|
@@ -58,10 +68,10 @@ export const strings = {
|
|
|
58
68
|
},
|
|
59
69
|
Tag: {
|
|
60
70
|
en: { remove: "Remove" },
|
|
61
|
-
vi: { remove: "Gỡ
|
|
71
|
+
vi: { remove: "Gỡ" },
|
|
62
72
|
},
|
|
63
73
|
Toast: {
|
|
64
|
-
en: { notifications: "Notifications", dismiss: "
|
|
74
|
+
en: { notifications: "Notifications", dismiss: "Close" },
|
|
65
75
|
vi: { notifications: "Thông báo", dismiss: "Đóng" },
|
|
66
76
|
},
|
|
67
77
|
TypingIndicator: {
|
|
@@ -73,12 +83,12 @@ export const strings = {
|
|
|
73
83
|
vi: { toolbar: "Định dạng", bold: "Đậm", italic: "Nghiêng", list: "Danh sách", area: "Văn bản" },
|
|
74
84
|
},
|
|
75
85
|
Terminal: {
|
|
76
|
-
en: { input: "Terminal input" },
|
|
77
|
-
vi: { input: "Ô nhập lệnh" },
|
|
86
|
+
en: { input: "Terminal input", title: "cyberskill — zsh" },
|
|
87
|
+
vi: { input: "Ô nhập lệnh", title: "cyberskill — zsh" },
|
|
78
88
|
},
|
|
79
89
|
DataGrid: {
|
|
80
|
-
en: { empty: "No
|
|
81
|
-
vi: { empty: "Không có
|
|
90
|
+
en: { empty: "No results", selectAll: "Select all rows", selectRow: "Select row", pin: "Pin this column", pinNamed: "Pin column {name}" },
|
|
91
|
+
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}" },
|
|
82
92
|
},
|
|
83
93
|
Splitter: {
|
|
84
94
|
en: { label: "Resize panes" },
|
|
@@ -112,9 +122,13 @@ export const strings = {
|
|
|
112
122
|
en: { placeholder: "Add a note — @ to mention" },
|
|
113
123
|
vi: { placeholder: "Thêm ghi chú — gõ @ để nhắc tên" },
|
|
114
124
|
},
|
|
125
|
+
Sortable: {
|
|
126
|
+
en: { moveUp: "Move up", moveDown: "Move down" },
|
|
127
|
+
vi: { moveUp: "Di chuyển lên", moveDown: "Di chuyển xuống" },
|
|
128
|
+
},
|
|
115
129
|
Form: {
|
|
116
|
-
en: { summary: "Please fix the following:", required: "required", requiredField: "
|
|
117
|
-
vi: { summary: "Vui lòng sửa các mục sau:", required: "bắt buộc", requiredField: "
|
|
130
|
+
en: { summary: "Please fix the following:", required: "required", requiredField: "Enter a value" },
|
|
131
|
+
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ị" },
|
|
118
132
|
},
|
|
119
133
|
BackTop: {
|
|
120
134
|
en: { label: "Back to top" },
|
|
@@ -122,7 +136,7 @@ export const strings = {
|
|
|
122
136
|
},
|
|
123
137
|
Carousel: {
|
|
124
138
|
en: { prev: "Previous slide", next: "Next slide", slide: "Slide" },
|
|
125
|
-
vi: { prev: "
|
|
139
|
+
vi: { prev: "Ảnh trước", next: "Ảnh sau", slide: "Ảnh" },
|
|
126
140
|
},
|
|
127
141
|
Comment: {
|
|
128
142
|
en: { reply: "Reply" },
|
|
@@ -137,12 +151,12 @@ export const strings = {
|
|
|
137
151
|
vi: { more: "Thao tác khác" },
|
|
138
152
|
},
|
|
139
153
|
Popconfirm: {
|
|
140
|
-
en: { ok: "
|
|
141
|
-
vi: { ok: "
|
|
154
|
+
en: { ok: "Confirm", cancel: "Cancel" },
|
|
155
|
+
vi: { ok: "Xác nhận", cancel: "Hủy" },
|
|
142
156
|
},
|
|
143
157
|
Result: {
|
|
144
|
-
en: { success: "Done", error: "
|
|
145
|
-
vi: { success: "Hoàn tất", error: "
|
|
158
|
+
en: { success: "Done", error: "We couldn't complete that", warning: "Please check", info: "Here's what happened" },
|
|
159
|
+
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" },
|
|
146
160
|
},
|
|
147
161
|
InlineEdit: {
|
|
148
162
|
en: { edit: "Edit", empty: "Add text" },
|
|
@@ -161,8 +175,8 @@ export const strings = {
|
|
|
161
175
|
vi: { label: "Giờ" },
|
|
162
176
|
},
|
|
163
177
|
Combobox: {
|
|
164
|
-
en: { placeholder: "Select or type…", empty: "No
|
|
165
|
-
vi: { placeholder: "Chọn hoặc nhập…", empty: "Không có kết quả
|
|
178
|
+
en: { placeholder: "Select or type…", empty: "No results" },
|
|
179
|
+
vi: { placeholder: "Chọn hoặc nhập…", empty: "Không có kết quả" },
|
|
166
180
|
},
|
|
167
181
|
InputGroup: {
|
|
168
182
|
en: { clear: "Clear", show: "Show password", hide: "Hide password", input: "Input" },
|
|
@@ -198,11 +212,11 @@ export const strings = {
|
|
|
198
212
|
vi: { list: "Tab" },
|
|
199
213
|
},
|
|
200
214
|
EmptyState: {
|
|
201
|
-
en: { title: "Nothing here yet", action: "
|
|
202
|
-
vi: { title: "Chưa có gì ở đây", action: "
|
|
215
|
+
en: { title: "Nothing here yet", action: "Create your first item" },
|
|
216
|
+
vi: { title: "Chưa có gì ở đây", action: "Tạo mục đầu tiên" },
|
|
203
217
|
},
|
|
204
218
|
Alert: {
|
|
205
|
-
en: { dismiss: "
|
|
219
|
+
en: { dismiss: "Close" },
|
|
206
220
|
vi: { dismiss: "Đóng" },
|
|
207
221
|
},
|
|
208
222
|
Select: {
|
|
@@ -237,4 +251,8 @@ export const strings = {
|
|
|
237
251
|
en: { label: "Progress" },
|
|
238
252
|
vi: { label: "Tiến độ" },
|
|
239
253
|
},
|
|
254
|
+
ChatMessage: {
|
|
255
|
+
en: { you: "You" },
|
|
256
|
+
vi: { you: "Bạn" },
|
|
257
|
+
},
|
|
240
258
|
};
|
|
@@ -1,20 +1,24 @@
|
|
|
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
|
/**
|
|
5
6
|
* CyberSkill ChatMessage — one turn in a Lumi conversation. role "lumi" (genie,
|
|
6
7
|
* Ochre avatar) or "user" (Umber, right-aligned). Pass `avatar` to override.
|
|
7
8
|
*/
|
|
8
|
-
export function ChatMessage({ role = "lumi", name, avatar,
|
|
9
|
+
export function ChatMessage({ role = "lumi", name, avatar, lang, className, children }) {
|
|
9
10
|
const isUser = role === "user";
|
|
11
|
+
const [ref, L] = useLang(lang);
|
|
12
|
+
const t = makeT("ChatMessage", L);
|
|
13
|
+
const you = t("you");
|
|
10
14
|
const defaultAvatar = isUser
|
|
11
|
-
? <span aria-hidden="true">
|
|
15
|
+
? <span aria-hidden="true">{you}</span>
|
|
12
16
|
: <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M12 3l1.8 5.4L19 10l-5.2 1.6L12 17l-1.8-5.4L5 10l5.2-1.6z" /></svg>;
|
|
13
17
|
return (
|
|
14
|
-
<div className={cx("cs-chat-msg", isUser ? "cs-chat-msg--user" : "cs-chat-msg--lumi", className)}>
|
|
18
|
+
<div ref={ref} className={cx("cs-chat-msg", isUser ? "cs-chat-msg--user" : "cs-chat-msg--lumi", className)}>
|
|
15
19
|
<div className="cs-chat-msg__avatar">{avatar ?? defaultAvatar}</div>
|
|
16
20
|
<div className="cs-chat-msg__col">
|
|
17
|
-
<div className="cs-chat-msg__name">{name ?? (isUser ?
|
|
21
|
+
<div className="cs-chat-msg__name">{name ?? (isUser ? you : "Lumi")}</div>
|
|
18
22
|
<div className="cs-chat-msg__bubble">{children}</div>
|
|
19
23
|
</div>
|
|
20
24
|
</div>
|
|
@@ -12,6 +12,8 @@ export interface HumanReviewGateProps {
|
|
|
12
12
|
reviewer?: ReactNode;
|
|
13
13
|
approveLabel?: string;
|
|
14
14
|
rejectLabel?: string;
|
|
15
|
+
/** "en" | "vi" — else resolved from the nearest [lang] ancestor (vi default). */
|
|
16
|
+
lang?: string;
|
|
15
17
|
className?: string;
|
|
16
18
|
onApprove?: () => void;
|
|
17
19
|
onReject?: () => void;
|
|
@@ -4,6 +4,8 @@ import type * as React from "react";
|
|
|
4
4
|
* under reduced-motion. */
|
|
5
5
|
export interface TypingIndicatorProps {
|
|
6
6
|
label?: string;
|
|
7
|
+
/** "en" | "vi" — else resolved from the nearest [lang] ancestor (vi default). */
|
|
8
|
+
lang?: string;
|
|
7
9
|
className?: string;
|
|
8
10
|
}
|
|
9
11
|
export function TypingIndicator(props: TypingIndicatorProps): React.ReactElement;
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
.grid { display: grid; grid-template-columns: 1.2fr 1fr; gap: 24px; max-width: 660px; align-items: start; }
|
|
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
|
</style>
|
|
13
|
-
<script src="
|
|
14
|
-
<script src="
|
|
15
|
-
<script src="
|
|
13
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
14
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
15
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
16
16
|
<script src="../../_ds_bundle.js"></script>
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
.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; }
|
|
10
10
|
.blk { margin-bottom: 20px; }
|
|
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>
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
.stack { display: grid; gap: 18px; max-width: 560px; }
|
|
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: 8px; }
|
|
12
12
|
</style>
|
|
13
|
-
<script src="
|
|
14
|
-
<script src="
|
|
15
|
-
<script src="
|
|
13
|
+
<script src="../../_vendor/react/react.production.min.js"></script>
|
|
14
|
+
<script src="../../_vendor/react/react-dom.production.min.js"></script>
|
|
15
|
+
<script src="../../_vendor/babel/babel.min.js"></script>
|
|
16
16
|
<script src="../../_ds_bundle.js"></script>
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
.row { display: flex; align-items: center; gap: 24px; }
|
|
10
10
|
.cap { font: 500 12px/1.4 var(--cs-font-family-ui); color: var(--cs-color-text-muted); margin-top: 8px; }
|
|
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>
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
.row:last-child { margin-bottom: 0; }
|
|
12
12
|
.lbl { width: 100%; font: 600 11px/1 var(--cs-font-family-ui); text-transform: uppercase; letter-spacing: .09em; color: var(--cs-color-text-muted); margin-bottom: 2px; }
|
|
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>
|
|
@@ -15,13 +15,29 @@ export function Image({ src, alt = "", ratio, preview = false, fallback, lang, c
|
|
|
15
15
|
document.addEventListener("keydown", k);
|
|
16
16
|
return () => document.removeEventListener("keydown", k);
|
|
17
17
|
}, [zoom]);
|
|
18
|
+
const openPreview = () => setZoom(true);
|
|
19
|
+
const onPreviewKey = (e) => {
|
|
20
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
21
|
+
e.preventDefault();
|
|
22
|
+
openPreview();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
18
25
|
const body = state === "error"
|
|
19
26
|
? <span className="cs-image__fallback">{fallback || <svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><rect x="3" y="3" width="18" height="18" rx="2" /><circle cx="9" cy="9" r="2" /><path d="M21 15l-5-5-9 9" /></svg>}</span>
|
|
20
27
|
: <img {...props} src={src} alt={alt} onLoad={() => setState("ok")} onError={() => setState("error")} />;
|
|
28
|
+
const canPreview = preview && state === "ok";
|
|
21
29
|
return (
|
|
22
30
|
<>
|
|
23
|
-
<span
|
|
24
|
-
|
|
31
|
+
<span
|
|
32
|
+
ref={ref}
|
|
33
|
+
className={cx("cs-image", state === "loading" && "is-loading", canPreview && "is-zoomable", className)}
|
|
34
|
+
style={ratio ? { aspectRatio: ratio } : undefined}
|
|
35
|
+
onClick={canPreview ? openPreview : undefined}
|
|
36
|
+
onKeyDown={canPreview ? onPreviewKey : undefined}
|
|
37
|
+
role={canPreview ? "button" : undefined}
|
|
38
|
+
tabIndex={canPreview ? 0 : undefined}
|
|
39
|
+
aria-label={canPreview ? t("preview") + (alt ? ": " + alt : "") : undefined}
|
|
40
|
+
>
|
|
25
41
|
{body}
|
|
26
42
|
</span>
|
|
27
43
|
{zoom ? (
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/** Reorderable list item. */
|
|
4
4
|
export interface SortableItem { key: string; label: ReactNode; }
|
|
5
5
|
export interface SortableProps {
|
|
6
6
|
items: SortableItem[];
|
|
7
7
|
onChange?: (items: SortableItem[]) => void;
|
|
8
|
+
lang?: string;
|
|
8
9
|
className?: string;
|
|
9
10
|
}
|
|
11
|
+
/** Pure reorder helper shared by drag and move-button paths. */
|
|
12
|
+
export function reorderItems<T>(items: T[], from: number, to: number): T[];
|
|
13
|
+
/** Reorderable list — move-up/down buttons (WCAG 2.5.7) plus HTML5 DnD enhancement. */
|
|
10
14
|
export function Sortable(props: SortableProps): React.ReactElement;
|
|
@@ -1,28 +1,61 @@
|
|
|
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
|
-
export function
|
|
5
|
+
/** Move item at `from` to index `to` (shared by drag drop and move buttons). */
|
|
6
|
+
export function reorderItems(items, from, to) {
|
|
7
|
+
if (from < 0 || to < 0 || from >= items.length || to >= items.length || from === to) return items;
|
|
8
|
+
const next = [...items];
|
|
9
|
+
next.splice(to, 0, next.splice(from, 1)[0]);
|
|
10
|
+
return next;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/** CyberSkill Sortable — reorderable list (move buttons + HTML5 DnD enhancement). items: [{key,label}]; onChange(newItems). */
|
|
14
|
+
export function Sortable({ items = [], onChange, lang, className }) {
|
|
6
15
|
const [dragKey, setDragKey] = React.useState(null);
|
|
7
16
|
const [over, setOver] = React.useState(null);
|
|
17
|
+
const [ref, L] = useLang(lang);
|
|
18
|
+
const t = makeT("Sortable", L);
|
|
19
|
+
const move = (from, to) => {
|
|
20
|
+
const next = reorderItems(items, from, to);
|
|
21
|
+
if (next !== items) onChange && onChange(next);
|
|
22
|
+
};
|
|
8
23
|
const drop = () => {
|
|
9
24
|
if (dragKey == null || over == null || dragKey === over) { setDragKey(null); setOver(null); return; }
|
|
10
25
|
const from = items.findIndex((i) => i.key === dragKey);
|
|
11
26
|
const to = items.findIndex((i) => i.key === over);
|
|
12
|
-
|
|
13
|
-
next.splice(to, 0, next.splice(from, 1)[0]);
|
|
14
|
-
onChange && onChange(next);
|
|
27
|
+
move(from, to);
|
|
15
28
|
setDragKey(null); setOver(null);
|
|
16
29
|
};
|
|
17
30
|
return (
|
|
18
|
-
<ul className={cx("cs-sortable", className)}>
|
|
19
|
-
{items.map((it) => (
|
|
31
|
+
<ul ref={ref} className={cx("cs-sortable", className)}>
|
|
32
|
+
{items.map((it, idx) => (
|
|
20
33
|
<li key={it.key} draggable
|
|
21
34
|
className={cx("cs-sortable__item", dragKey === it.key && "is-dragging", over === it.key && "is-over")}
|
|
22
|
-
onDragStart={() =>
|
|
35
|
+
onDragStart={(e) => {
|
|
36
|
+
if (e.target.closest && e.target.closest(".cs-sortable__ops")) { e.preventDefault(); return; }
|
|
37
|
+
setDragKey(it.key);
|
|
38
|
+
}}
|
|
23
39
|
onDragOver={(e) => { e.preventDefault(); setOver(it.key); }}
|
|
24
40
|
onDrop={drop} onDragEnd={drop}>
|
|
25
|
-
<span className="cs-sortable__grip" aria-hidden="true">⠿</span>
|
|
41
|
+
<span className="cs-sortable__grip" aria-hidden="true">⠿</span>
|
|
42
|
+
<span className="cs-sortable__label">{it.label}</span>
|
|
43
|
+
<span className="cs-sortable__ops" onMouseDown={(e) => e.stopPropagation()}>
|
|
44
|
+
<button
|
|
45
|
+
type="button"
|
|
46
|
+
className="cs-button cs-button--secondary cs-button--xs"
|
|
47
|
+
aria-label={t("moveUp")}
|
|
48
|
+
disabled={idx === 0}
|
|
49
|
+
onClick={() => move(idx, idx - 1)}
|
|
50
|
+
>↑</button>
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
className="cs-button cs-button--secondary cs-button--xs"
|
|
54
|
+
aria-label={t("moveDown")}
|
|
55
|
+
disabled={idx === items.length - 1}
|
|
56
|
+
onClick={() => move(idx, idx + 1)}
|
|
57
|
+
>↓</button>
|
|
58
|
+
</span>
|
|
26
59
|
</li>
|
|
27
60
|
))}
|
|
28
61
|
</ul>
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
**Sortable** —
|
|
1
|
+
**Sortable** — reorderable list with move-up/down buttons (keyboard + single-pointer) and optional HTML5 drag. Compose columns of it for a kanban.
|
|
2
2
|
|
|
3
3
|
```jsx
|
|
4
|
-
<Sortable items={tasks} onChange={setTasks}/>
|
|
4
|
+
<Sortable items={tasks} onChange={setTasks} lang="en"/>
|
|
5
5
|
```
|
|
6
|
+
|
|
7
|
+
Buttons expose i18n `aria-label`s (`moveUp` / `moveDown`); first/last controls are disabled.
|
|
@@ -3,11 +3,12 @@ import { makeT, useLang } from "../_i18n/i18n.js";
|
|
|
3
3
|
import { cx } from "../_utils/cx.js";
|
|
4
4
|
|
|
5
5
|
/** CyberSkill Terminal — command console: history + prompt input; onCommand returns output (string or lines). */
|
|
6
|
-
export function Terminal({ title
|
|
6
|
+
export function Terminal({ title, welcome, onCommand, prompt = "➜", lang, className }) {
|
|
7
7
|
const [hist, setHist] = React.useState(() => (welcome ? [{ out: welcome }] : []));
|
|
8
8
|
const [q, setQ] = React.useState("");
|
|
9
9
|
const [ref, L] = useLang(lang);
|
|
10
10
|
const t = makeT("Terminal", L);
|
|
11
|
+
const barTitle = title ?? t("title");
|
|
11
12
|
const run = () => {
|
|
12
13
|
if (!q.trim()) return;
|
|
13
14
|
const res = onCommand ? onCommand(q.trim()) : "";
|
|
@@ -16,7 +17,7 @@ export function Terminal({ title = "cyberskill — zsh", welcome, onCommand, pro
|
|
|
16
17
|
};
|
|
17
18
|
return (
|
|
18
19
|
<div ref={ref} className={cx("cs-terminal", className)}>
|
|
19
|
-
<div className="cs-terminal__bar"><i /><i /><i /><span>{
|
|
20
|
+
<div className="cs-terminal__bar"><i /><i /><i /><span>{barTitle}</span></div>
|
|
20
21
|
<div className="cs-terminal__body">
|
|
21
22
|
{hist.map((l, i) => l.cmd != null
|
|
22
23
|
? <div key={i} className="cs-terminal__line"><span className="p">{prompt}</span> {l.cmd}</div>
|
|
@@ -1,12 +1,15 @@
|
|
|
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 Tooltip — hover/focus bubble above the trigger. Keep label short. */
|
|
5
|
-
export function Tooltip({ label, children, className }) {
|
|
6
|
+
export function Tooltip({ label, children, lang, className }) {
|
|
7
|
+
const [ref, L] = useLang(lang);
|
|
8
|
+
const t = makeT("Tooltip", L);
|
|
6
9
|
return (
|
|
7
|
-
<span className={cx("cs-tooltip", className)}>
|
|
10
|
+
<span ref={ref} className={cx("cs-tooltip", className)}>
|
|
8
11
|
{children}
|
|
9
|
-
<span className="cs-tooltip__bubble" role="tooltip">{label}</span>
|
|
12
|
+
<span className="cs-tooltip__bubble" role="tooltip">{label ?? t("label")}</span>
|
|
10
13
|
</span>
|
|
11
14
|
);
|
|
12
15
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
2
|
|
|
3
|
-
/** Expandable hierarchy (
|
|
3
|
+
/** Expandable hierarchy (APG tree: roving tabindex + arrow navigate/expand).
|
|
4
|
+
* Single-select via onSelect(key, node). */
|
|
4
5
|
export interface TreeNode { key: string; label: ReactNode; children?: TreeNode[]; }
|
|
5
6
|
export interface TreeProps {
|
|
6
7
|
nodes: TreeNode[];
|