@cyberskill/design 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/DESIGN.md +10 -9
- package/README.md +31 -28
- package/SKILL.md +13 -9
- package/VERSION +1 -1
- package/_ds_bundle.js +660 -202
- package/_ds_manifest.json +1 -1
- package/_esm/cs.mjs +14 -8
- package/_esm/react.d.ts +216 -0
- package/_esm/react.mjs +114 -0
- package/base/collections.css +31 -0
- package/base/components.css +3 -0
- package/base/data.css +33 -0
- package/base/forms.css +8 -0
- package/base/interaction.css +1 -0
- package/base/typography.css +6 -0
- package/components/_i18n/strings.js +8 -4
- package/components/ai/ai-conversation.card.html +2 -2
- package/components/ai/ai-lumi.card.html +2 -2
- package/components/ai/ai.card.html +2 -2
- package/components/brand/brand.card.html +2 -2
- package/components/button/buttons.card.html +2 -2
- package/components/data/AspectRatio.d.ts +16 -0
- package/components/data/AspectRatio.figma.tsx +12 -0
- package/components/data/AspectRatio.jsx +16 -0
- package/components/data/AspectRatio.prompt.md +7 -0
- package/components/data/Collapsible.d.ts +21 -0
- package/components/data/Collapsible.figma.tsx +12 -0
- package/components/data/Collapsible.jsx +49 -0
- package/components/data/Collapsible.prompt.md +7 -0
- package/components/data/Image.d.ts +4 -0
- package/components/data/Image.prompt.md +2 -2
- package/components/data/Item.d.ts +26 -0
- package/components/data/Item.figma.tsx +12 -0
- package/components/data/Item.jsx +63 -0
- package/components/data/Item.prompt.md +10 -0
- package/components/data/ScrollArea.d.ts +16 -0
- package/components/data/ScrollArea.figma.tsx +12 -0
- package/components/data/ScrollArea.jsx +25 -0
- package/components/data/ScrollArea.prompt.md +7 -0
- package/components/data/collections.card.html +15 -4
- package/components/data/data.card.html +15 -4
- package/components/datatable/datatable.card.html +2 -2
- package/components/dialog/dialog.card.html +2 -2
- package/components/feedback/feedback-2.card.html +2 -2
- package/components/feedback/feedback.card.html +2 -2
- package/components/forms/Cascader.jsx +5 -3
- package/components/forms/Editor.prompt.md +1 -1
- package/components/forms/InputGroup.d.ts +4 -0
- package/components/forms/InputGroup.jsx +38 -8
- package/components/forms/InputGroup.prompt.md +3 -3
- package/components/forms/NativeSelect.d.ts +22 -0
- package/components/forms/NativeSelect.figma.tsx +12 -0
- package/components/forms/NativeSelect.jsx +56 -0
- package/components/forms/NativeSelect.prompt.md +10 -0
- package/components/forms/NumberField.d.ts +5 -0
- package/components/forms/NumberField.jsx +51 -7
- package/components/forms/NumberField.prompt.md +2 -2
- package/components/forms/SegmentedControl.jsx +1 -1
- package/components/forms/TagInput.d.ts +3 -0
- package/components/forms/TagInput.jsx +62 -20
- package/components/forms/TagInput.prompt.md +2 -2
- package/components/forms/forms-2.card.html +2 -2
- package/components/forms/forms.card.html +5 -4
- package/components/icon/Icon.prompt.md +1 -1
- package/components/icon/icon.card.html +2 -2
- package/components/logo/Logo.jsx +2 -1
- package/components/logo/logo.card.html +2 -2
- package/components/navigation/Menu.jsx +14 -1
- package/components/navigation/navigation.card.html +2 -2
- package/components/navigation/shell.card.html +2 -2
- package/components/overlays/AlertDialog.d.ts +29 -0
- package/components/overlays/AlertDialog.figma.tsx +12 -0
- package/components/overlays/AlertDialog.jsx +142 -0
- package/components/overlays/AlertDialog.prompt.md +12 -0
- package/components/overlays/Drawer.jsx +1 -1
- package/components/overlays/Popconfirm.jsx +14 -1
- package/components/overlays/Popover.jsx +14 -1
- package/components/overlays/alertdialog.card.html +42 -0
- package/components/overlays/overlays.card.html +2 -2
- package/components/textfield/textfield.card.html +2 -2
- package/docs/benchmark-rubric.md +46 -0
- package/docs/ci-cd.md +17 -11
- package/docs/consumer-grant.md +37 -0
- package/docs/consuming.md +45 -16
- package/docs/contrast-report.md +91 -103
- package/docs/conventions.md +12 -9
- package/docs/decisions.md +33 -10
- package/docs/deploy.md +1 -1
- package/docs/design-styles.md +26 -8
- package/docs/figma.md +3 -3
- package/docs/live-hub.md +5 -3
- package/docs/products.md +13 -11
- package/docs/quality-gates.md +35 -21
- package/docs/release-notes.md +13 -3
- package/docs/status/assets/favicon.svg +5 -0
- package/docs/status/assets/status-legacy.css +399 -0
- package/docs/status/assets/status-legacy.js +505 -0
- package/docs/status/assets/status.css +644 -0
- package/docs/status/assets/status.js +1308 -0
- package/docs/status/data/status-feed.json +1 -0
- package/docs/status/data/task/TASK-REL-001.js +1 -0
- package/docs/status/index.html +45 -0
- package/docs/status/roadmap.html +4 -0
- package/docs/status/status-legacy.html +121 -0
- package/docs/storybook.md +5 -4
- package/docs/sync.md +3 -2
- package/docs/tasks/BACKLOG.md +25 -0
- package/docs/tasks/release/TASK-REL-001-launch-1-1-0/spec.md +55 -0
- package/docs/template-schema-v2.md +15 -3
- package/docs/vi/benchmark-rubric.md +46 -0
- package/docs/vi/ci-cd.md +17 -11
- package/docs/vi/consumer-grant.md +37 -0
- package/docs/vi/consuming.md +45 -16
- package/docs/vi/contrast-report.md +94 -106
- package/docs/vi/conventions.md +12 -9
- package/docs/vi/decisions.md +33 -10
- package/docs/vi/deploy.md +1 -1
- package/docs/vi/design-styles.md +26 -8
- package/docs/vi/figma.md +3 -3
- package/docs/vi/live-hub.md +5 -3
- package/docs/vi/products.md +13 -11
- package/docs/vi/quality-gates.md +35 -21
- package/docs/vi/release-notes.md +18 -8
- package/docs/vi/storybook.md +5 -4
- package/docs/vi/sync.md +3 -2
- package/docs/vi/template-schema-v2.md +15 -3
- package/docs/viewer.html +2 -2
- package/fonts/spacegrotesk-var-latin-ext.woff2 +0 -0
- package/fonts/spacegrotesk-var-latin.woff2 +0 -0
- package/fonts/spacegrotesk-var-vietnamese.woff2 +0 -0
- package/guidelines/atomic-view.html +40 -27
- package/guidelines/elements-geometry.html +69 -0
- package/guidelines/elements-variants.html +17 -17
- package/guidelines/type-families.html +16 -10
- package/llms.txt +17 -11
- package/package.json +38 -16
- package/styles.css +1 -0
- package/templates/ai-cluster-demo.html +29 -14
- package/templates/app-shell/support.js +4 -4
- package/templates/article/content-schema.json +6 -0
- package/templates/article/support.js +4 -4
- package/templates/auth/support.js +4 -4
- package/templates/bod-deck/support.js +4 -4
- package/templates/bod-investor-update/support.js +4 -4
- package/templates/bod-memo/support.js +4 -4
- package/templates/bod-minutes/content-schema.json +12 -0
- package/templates/bod-minutes/support.js +4 -4
- package/templates/bod-report/support.js +4 -4
- package/templates/bod-resolution/support.js +4 -4
- package/templates/culture-event-invite/support.js +4 -4
- package/templates/dashboard/support.js +4 -4
- package/templates/delivery-change-note/support.js +4 -4
- package/templates/delivery-kickoff/support.js +4 -4
- package/templates/delivery-qbr-deck/support.js +4 -4
- package/templates/delivery-runbook/content-schema.json +6 -0
- package/templates/delivery-runbook/support.js +4 -4
- package/templates/delivery-status-email/support.js +4 -4
- package/templates/doc-style-guide/DocStyleGuide.dc.html +2 -2
- package/templates/doc-style-guide/support.js +4 -4
- package/templates/doc-suite-index/support.js +4 -4
- package/templates/doc-templates/DocTemplates.dc.html +1 -1
- package/templates/doc-templates/support.js +4 -4
- package/templates/email/support.js +4 -4
- package/templates/email-safe/README.md +17 -4
- package/templates/email-safe/email-launch.html +3 -3
- package/templates/email-safe/email-newsletter.html +1 -1
- package/templates/finance-dunning-email/support.js +4 -4
- package/templates/finance-expense-report/support.js +4 -4
- package/templates/finance-invoice/support.js +4 -4
- package/templates/finance-quote/support.js +4 -4
- package/templates/hr-announcement/support.js +4 -4
- package/templates/hr-interview-kit/content-schema.json +12 -0
- package/templates/hr-interview-kit/support.js +4 -4
- package/templates/hr-offboarding/content-schema.json +12 -0
- package/templates/hr-offboarding/support.js +4 -4
- package/templates/hr-performance-review/content-schema.json +12 -0
- package/templates/hr-performance-review/support.js +4 -4
- package/templates/hr-pip/content-schema.json +12 -0
- package/templates/hr-pip/support.js +4 -4
- package/templates/legal-msa/content-schema.json +16 -0
- package/templates/legal-msa/support.js +4 -4
- package/templates/legal-sow/support.js +4 -4
- package/templates/marketing-campaign-brief/support.js +4 -4
- package/templates/marketing-case-study/support.js +4 -4
- package/templates/marketing-launch/support.js +4 -4
- package/templates/marketing-newsletter/support.js +4 -4
- package/templates/marketing-page/content-schema.json +0 -1
- package/templates/marketing-page/support.js +4 -4
- package/templates/marketing-press-release/support.js +4 -4
- package/templates/marketing-social-kit/support.js +4 -4
- package/templates/playground.html +8 -4
- package/templates/sales-one-pager/support.js +4 -4
- package/templates/sales-proposal-deck/support.js +4 -4
- package/templates/settings/Settings.dc.html +26 -14
- package/templates/settings/content-schema.json +36 -0
- package/templates/settings/support.js +4 -4
- package/templates/slide-deck/support.js +4 -4
- package/templates/team-meeting-agenda/content-schema.json +16 -0
- package/templates/team-meeting-agenda/support.js +4 -4
- package/templates/tech-incident-report/support.js +4 -4
- package/templates/tech-release-notes/support.js +4 -4
- package/templates/tech-retro/content-schema.json +12 -0
- package/templates/tech-retro/support.js +4 -4
- package/templates/tech-rfc/content-schema.json +6 -0
- package/templates/tech-rfc/support.js +4 -4
- package/templates/vn-appointment-transfer/support.js +4 -4
- package/templates/vn-certificate-of-employment/support.js +4 -4
- package/templates/vn-code-of-conduct/support.js +4 -4
- package/templates/vn-compensation-regulation/support.js +4 -4
- package/templates/vn-contract-amendment/support.js +4 -4
- package/templates/vn-contractor-agreement/support.js +4 -4
- package/templates/vn-data-protection-policy/support.js +4 -4
- package/templates/vn-disciplinary-case-file/support.js +4 -4
- package/templates/vn-disciplinary-schedule/content-schema.json +15 -0
- package/templates/vn-disciplinary-schedule/support.js +4 -4
- package/templates/vn-employee-request/support.js +4 -4
- package/templates/vn-exit-handover/support.js +4 -4
- package/templates/vn-foreign-employee-pack/support.js +4 -4
- package/templates/vn-grassroots-democracy/support.js +4 -4
- package/templates/vn-infosec-policy/support.js +4 -4
- package/templates/vn-internal-labor-regulations/support.js +4 -4
- package/templates/vn-internship-agreement/support.js +4 -4
- package/templates/vn-job-description/support.js +4 -4
- package/templates/vn-labor-contract/support.js +4 -4
- package/templates/vn-labour-management-book/content-schema.json +15 -0
- package/templates/vn-labour-management-book/support.js +4 -4
- package/templates/vn-mutual-nda/support.js +4 -4
- package/templates/vn-ndnca-ip/support.js +4 -4
- package/templates/vn-offer-letter/support.js +4 -4
- package/templates/vn-onboarding-checklist/support.js +4 -4
- package/templates/vn-pdpl-consent/support.js +4 -4
- package/templates/vn-performance-evaluation-regulation/support.js +4 -4
- package/templates/vn-performance-review-form/support.js +4 -4
- package/templates/vn-phantom-stock/support.js +4 -4
- package/templates/vn-probation-contract/support.js +4 -4
- package/templates/vn-receipt-acknowledgement/content-schema.json +15 -0
- package/templates/vn-receipt-acknowledgement/support.js +4 -4
- package/templates/vn-remote-work-policy/support.js +4 -4
- package/templates/vn-resignation-letter/support.js +4 -4
- package/templates/vn-salary-scale/content-schema.json +15 -0
- package/templates/vn-salary-scale/support.js +4 -4
- package/templates/vn-sow-appendix/support.js +4 -4
- package/templates/vn-termination-decision/support.js +4 -4
- package/templates/vn-total-rewards-appendix/support.js +4 -4
- package/templates/vn-training-commitment/support.js +4 -4
- package/templates/vn-travel-expense-policy/support.js +4 -4
- package/tokens/element-seeds.json +112 -0
- package/tokens/elements.css +274 -187
- package/tokens/fonts.css +12 -3
- package/tokens/native/CSTokens.kt +84 -68
- package/tokens/native/CSTokens.swift +84 -68
- package/tokens/native/cs_tokens.dart +84 -68
- package/tokens/provenance.json +9 -9
- package/tokens/styles.css +15 -0
- package/tokens/tokens.dtcg.json +258 -233
- package/tokens/tokens.js +168 -167
- package/tokens/tokens.json +167 -166
- package/tokens/typography.css +9 -1
- package/ui_kits/status-hub/README.md +1 -2
- package/ui_kits/status-hub/index.html +2 -2
- package/ui_kits/status-hub/settings.html +6 -3
- package/ui_kits/website/chat.html +2 -2
- package/ui_kits/website/index.html +2 -2
- package/ui_kits/status-hub/identity-lab.html +0 -134
|
@@ -11,8 +11,8 @@
|
|
|
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="https://unpkg.com/react@
|
|
15
|
-
<script src="https://unpkg.com/react
|
|
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
16
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
17
17
|
<script src="../../_ds_bundle.js"></script>
|
|
18
18
|
</head>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { ReactNode, CSSProperties } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CSS `aspect-ratio` wrapper for media and framed content.
|
|
6
|
+
* `ratio` accepts a number (e.g. `1.777`) or CSS string (default `"16 / 9"`).
|
|
7
|
+
*/
|
|
8
|
+
export interface AspectRatioProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
/** Number or CSS aspect-ratio string. Default `"16 / 9"`. */
|
|
10
|
+
ratio?: number | string;
|
|
11
|
+
children?: ReactNode;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function AspectRatio(props: AspectRatioProps): React.ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import figma from '@figma/code-connect'
|
|
2
|
+
import { AspectRatio } from './AspectRatio.jsx'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Code Connect stub — AspectRatio
|
|
6
|
+
* Prop maps stay minimal until the matching Figma library component is published;
|
|
7
|
+
* then set nodeId in code-connect/node-map.json and re-run generate-code-connect.
|
|
8
|
+
*/
|
|
9
|
+
figma.connect(AspectRatio, 'https://www.figma.com/design/CS_FIGMA_FILE_KEY/CyberSkill?node-id=9999-5', {
|
|
10
|
+
example: () => <AspectRatio />,
|
|
11
|
+
imports: ["import { AspectRatio } from '@cyberskill/design'"],
|
|
12
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cx } from "../_utils/cx.js";
|
|
3
|
+
|
|
4
|
+
/** CyberSkill AspectRatio — CSS aspect-ratio lock for media and framed content. */
|
|
5
|
+
export function AspectRatio({ ratio = "16 / 9", children, className, style, ...props }) {
|
|
6
|
+
const ar = typeof ratio === "number" ? String(ratio) : ratio;
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
className={cx("cs-aspect-ratio", className)}
|
|
10
|
+
style={{ ...(style || {}), aspectRatio: ar }}
|
|
11
|
+
{...props}
|
|
12
|
+
>
|
|
13
|
+
{children}
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { ReactNode } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Light single-panel expand without Accordion chrome (settings, policy clauses).
|
|
6
|
+
* Controlled via `open` + `onOpenChange`, or uncontrolled via `defaultOpen`.
|
|
7
|
+
* `title` (or `trigger`) is the button label.
|
|
8
|
+
*/
|
|
9
|
+
export interface CollapsibleProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
10
|
+
open?: boolean;
|
|
11
|
+
defaultOpen?: boolean;
|
|
12
|
+
onOpenChange?: (open: boolean) => void;
|
|
13
|
+
/** Trigger label (preferred). */
|
|
14
|
+
title?: ReactNode;
|
|
15
|
+
/** Alias for `title`. */
|
|
16
|
+
trigger?: ReactNode;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
className?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function Collapsible(props: CollapsibleProps): React.ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import figma from '@figma/code-connect'
|
|
2
|
+
import { Collapsible } from './Collapsible.jsx'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Code Connect stub — Collapsible
|
|
6
|
+
* Prop maps stay minimal until the matching Figma library component is published;
|
|
7
|
+
* then set nodeId in code-connect/node-map.json and re-run generate-code-connect.
|
|
8
|
+
*/
|
|
9
|
+
figma.connect(Collapsible, 'https://www.figma.com/design/CS_FIGMA_FILE_KEY/CyberSkill?node-id=9999-21', {
|
|
10
|
+
example: () => <Collapsible />,
|
|
11
|
+
imports: ["import { Collapsible } from '@cyberskill/design'"],
|
|
12
|
+
})
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cx } from "../_utils/cx.js";
|
|
3
|
+
|
|
4
|
+
/** CyberSkill Collapsible — single expand panel without Accordion chrome. */
|
|
5
|
+
export function Collapsible({
|
|
6
|
+
open,
|
|
7
|
+
defaultOpen = false,
|
|
8
|
+
onOpenChange,
|
|
9
|
+
title,
|
|
10
|
+
trigger,
|
|
11
|
+
children,
|
|
12
|
+
className,
|
|
13
|
+
...props
|
|
14
|
+
}) {
|
|
15
|
+
const uncontrolled = open === undefined;
|
|
16
|
+
const [internal, setInternal] = React.useState(!!defaultOpen);
|
|
17
|
+
const isOpen = uncontrolled ? internal : !!open;
|
|
18
|
+
const setOpen = (next) => {
|
|
19
|
+
if (uncontrolled) setInternal(next);
|
|
20
|
+
onOpenChange?.(next);
|
|
21
|
+
};
|
|
22
|
+
const label = title ?? trigger;
|
|
23
|
+
const panelId = React.useId();
|
|
24
|
+
const triggerId = React.useId();
|
|
25
|
+
return (
|
|
26
|
+
<div className={cx("cs-collapsible", className)} {...props}>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
id={triggerId}
|
|
30
|
+
className="cs-collapsible__trigger"
|
|
31
|
+
aria-expanded={isOpen}
|
|
32
|
+
aria-controls={panelId}
|
|
33
|
+
onClick={() => setOpen(!isOpen)}
|
|
34
|
+
>
|
|
35
|
+
<span className="cs-collapsible__label">{label}</span>
|
|
36
|
+
<span className="cs-collapsible__chevron" aria-hidden="true">
|
|
37
|
+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
38
|
+
<path d="M6 9l6 6 6-6" />
|
|
39
|
+
</svg>
|
|
40
|
+
</span>
|
|
41
|
+
</button>
|
|
42
|
+
{isOpen ? (
|
|
43
|
+
<div id={panelId} role="region" aria-labelledby={triggerId} className="cs-collapsible__panel">
|
|
44
|
+
{children}
|
|
45
|
+
</div>
|
|
46
|
+
) : null}
|
|
47
|
+
</div>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
@@ -11,6 +11,10 @@ export interface ImageProps {
|
|
|
11
11
|
preview?: boolean;
|
|
12
12
|
/** Custom fallback node when the image fails. */
|
|
13
13
|
fallback?: ReactNode;
|
|
14
|
+
/** Forwarded to the underlying <img>. */
|
|
15
|
+
width?: number | string;
|
|
16
|
+
/** Forwarded to the underlying <img>. */
|
|
17
|
+
height?: number | string;
|
|
14
18
|
/** "en" | "vi" — else resolved from the nearest [lang] ancestor (vi default). */
|
|
15
19
|
lang?: string;
|
|
16
20
|
className?: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
**Image** — img with shimmer, warm error fallback, optional `preview` lightbox.
|
|
1
|
+
**Image** — img with shimmer, warm error fallback, optional `preview` lightbox. `width` / `height` forward to the underlying `<img>`.
|
|
2
2
|
|
|
3
3
|
```jsx
|
|
4
|
-
<Image src="/assets/aurora-gold.jpg" alt="Aurora" ratio="16/9" preview/>
|
|
4
|
+
<Image src="/assets/aurora-gold.jpg" alt="Aurora" ratio="16/9" width={640} height={360} preview/>
|
|
5
5
|
```
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ReactNode, MouseEventHandler } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Shared list / settings / nav row: leading · title · description · trailing,
|
|
5
|
+
* with selected / disabled and href-or-button semantics when interactive.
|
|
6
|
+
*/
|
|
7
|
+
export interface ItemProps {
|
|
8
|
+
leading?: ReactNode;
|
|
9
|
+
trailing?: ReactNode;
|
|
10
|
+
title?: ReactNode;
|
|
11
|
+
description?: ReactNode;
|
|
12
|
+
selected?: boolean;
|
|
13
|
+
disabled?: boolean;
|
|
14
|
+
/** Renders as an <a> when set (and not disabled). */
|
|
15
|
+
href?: string;
|
|
16
|
+
/** Renders as a <button> when set without href. */
|
|
17
|
+
onClick?: MouseEventHandler<HTMLButtonElement>;
|
|
18
|
+
/**
|
|
19
|
+
* Free-form main content when `trailing` is set, or the trailing slot when
|
|
20
|
+
* `title` is set and `trailing` is omitted (DC-friendly Switch nesting).
|
|
21
|
+
*/
|
|
22
|
+
children?: ReactNode;
|
|
23
|
+
className?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function Item(props: ItemProps): React.ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import figma from '@figma/code-connect'
|
|
2
|
+
import { Item } from './Item.jsx'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Code Connect stub — Item
|
|
6
|
+
* Prop maps stay minimal until the matching Figma library component is published;
|
|
7
|
+
* then set nodeId in code-connect/node-map.json and re-run generate-code-connect.
|
|
8
|
+
*/
|
|
9
|
+
figma.connect(Item, 'https://www.figma.com/design/CS_FIGMA_FILE_KEY/CyberSkill?node-id=9999-50', {
|
|
10
|
+
example: () => <Item />,
|
|
11
|
+
imports: ["import { Item } from '@cyberskill/design'"],
|
|
12
|
+
})
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cx } from "../_utils/cx.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* CyberSkill Item — shared list / settings / nav row chrome.
|
|
6
|
+
* Leading + trailing slots, title + description, selected / disabled, and
|
|
7
|
+
* href (anchor) or onClick (button) when interactive.
|
|
8
|
+
* When `title` is set and `trailing` is omitted, `children` render in the
|
|
9
|
+
* trailing slot (handy for DC templates nesting a Switch).
|
|
10
|
+
*/
|
|
11
|
+
export function Item({
|
|
12
|
+
leading,
|
|
13
|
+
trailing,
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
selected = false,
|
|
17
|
+
disabled = false,
|
|
18
|
+
href,
|
|
19
|
+
onClick,
|
|
20
|
+
children,
|
|
21
|
+
className,
|
|
22
|
+
...props
|
|
23
|
+
}) {
|
|
24
|
+
const interactive = !disabled && (href != null || typeof onClick === "function");
|
|
25
|
+
const Tag = href != null && !disabled ? "a" : interactive ? "button" : "div";
|
|
26
|
+
const rowProps = { ...props };
|
|
27
|
+
if (Tag === "a") {
|
|
28
|
+
rowProps.href = href;
|
|
29
|
+
if (disabled) rowProps["aria-disabled"] = true;
|
|
30
|
+
} else if (Tag === "button") {
|
|
31
|
+
rowProps.type = "button";
|
|
32
|
+
rowProps.disabled = disabled || undefined;
|
|
33
|
+
rowProps.onClick = onClick;
|
|
34
|
+
} else if (disabled) {
|
|
35
|
+
rowProps["aria-disabled"] = true;
|
|
36
|
+
}
|
|
37
|
+
if (selected) rowProps["aria-current"] = rowProps["aria-current"] ?? "true";
|
|
38
|
+
|
|
39
|
+
const trailExplicit = trailing !== undefined;
|
|
40
|
+
const trailNode = trailExplicit ? trailing : title != null ? children : null;
|
|
41
|
+
const mainExtra = trailExplicit ? children : title == null ? children : null;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<Tag
|
|
45
|
+
className={cx(
|
|
46
|
+
"cs-item",
|
|
47
|
+
interactive && "cs-item--interactive",
|
|
48
|
+
selected && "is-selected",
|
|
49
|
+
disabled && "is-disabled",
|
|
50
|
+
className
|
|
51
|
+
)}
|
|
52
|
+
{...rowProps}
|
|
53
|
+
>
|
|
54
|
+
{leading != null ? <span className="cs-item__leading">{leading}</span> : null}
|
|
55
|
+
<span className="cs-item__main">
|
|
56
|
+
{title != null ? <span className="cs-item__title">{title}</span> : null}
|
|
57
|
+
{description != null ? <span className="cs-item__description">{description}</span> : null}
|
|
58
|
+
{mainExtra}
|
|
59
|
+
</span>
|
|
60
|
+
{trailNode != null && trailNode !== false ? <span className="cs-item__trailing">{trailNode}</span> : null}
|
|
61
|
+
</Tag>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
**Item** — shared list / settings / nav row chrome. Leading + trailing slots, title + description, selected / disabled, and `href` (link) or `onClick` (button) when interactive.
|
|
2
|
+
|
|
3
|
+
```jsx
|
|
4
|
+
<Item
|
|
5
|
+
title="Release notes"
|
|
6
|
+
description="A summary each time a project ships."
|
|
7
|
+
trailing={<Switch />}
|
|
8
|
+
/>
|
|
9
|
+
<Item title="Profile" href="#profile" selected />
|
|
10
|
+
```
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type * as React from "react";
|
|
2
|
+
import type { ReactNode, CSSProperties } from "react";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Focusable overflow region for dense panels (settings, long VN copy).
|
|
6
|
+
* `maxHeight` caps the scrollport; keyboard focus scrolls when needed.
|
|
7
|
+
*/
|
|
8
|
+
export interface ScrollAreaProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
9
|
+
children?: ReactNode;
|
|
10
|
+
/** Max block size — number → px, or any CSS length string. */
|
|
11
|
+
maxHeight?: number | string;
|
|
12
|
+
className?: string;
|
|
13
|
+
style?: CSSProperties;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function ScrollArea(props: ScrollAreaProps): React.ReactElement;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import figma from '@figma/code-connect'
|
|
2
|
+
import { ScrollArea } from './ScrollArea.jsx'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Code Connect stub — ScrollArea
|
|
6
|
+
* Prop maps stay minimal until the matching Figma library component is published;
|
|
7
|
+
* then set nodeId in code-connect/node-map.json and re-run generate-code-connect.
|
|
8
|
+
*/
|
|
9
|
+
figma.connect(ScrollArea, 'https://www.figma.com/design/CS_FIGMA_FILE_KEY/CyberSkill?node-id=9999-70', {
|
|
10
|
+
example: () => <ScrollArea />,
|
|
11
|
+
imports: ["import { ScrollArea } from '@cyberskill/design'"],
|
|
12
|
+
})
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { cx } from "../_utils/cx.js";
|
|
3
|
+
|
|
4
|
+
/** CyberSkill ScrollArea — focusable overflow region for dense bilingual panels. */
|
|
5
|
+
export function ScrollArea({ children, maxHeight, className, style, ...props }) {
|
|
6
|
+
const max =
|
|
7
|
+
maxHeight == null
|
|
8
|
+
? undefined
|
|
9
|
+
: typeof maxHeight === "number"
|
|
10
|
+
? `${maxHeight}px`
|
|
11
|
+
: maxHeight;
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
className={cx("cs-scroll-area", className)}
|
|
15
|
+
tabIndex={0}
|
|
16
|
+
style={{
|
|
17
|
+
...(style || {}),
|
|
18
|
+
...(max != null ? { maxBlockSize: max } : {}),
|
|
19
|
+
}}
|
|
20
|
+
{...props}
|
|
21
|
+
>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
);
|
|
25
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- @dsCard group="Components" viewport="
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x520" name="Collections & content" subtitle="List, Item, DescriptionList, Timeline, Accordion, Collapsible, Kbd, CodeBlock" -->
|
|
2
2
|
<!DOCTYPE html>
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
@@ -10,15 +10,15 @@
|
|
|
10
10
|
.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; }
|
|
11
11
|
.blk { margin-bottom: 20px; }
|
|
12
12
|
</style>
|
|
13
|
-
<script src="https://unpkg.com/react@
|
|
14
|
-
<script src="https://unpkg.com/react
|
|
13
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
|
|
14
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
|
|
15
15
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
16
16
|
<script src="../../_ds_bundle.js"></script>
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
20
20
|
<script type="text/babel">
|
|
21
|
-
const { List, ListItem, DescriptionList, Timeline, Accordion, Kbd, CodeBlock, Avatar, Badge } = (window[Object.keys(window).find(k=>/^CyberSkillDesignSystem_[0-9a-f]{6}$/.test(k))]||{});
|
|
21
|
+
const { List, ListItem, Item, DescriptionList, Timeline, Accordion, Collapsible, Kbd, CodeBlock, Avatar, Badge, Switch } = (window[Object.keys(window).find(k=>/^CyberSkillDesignSystem_[0-9a-f]{6}$/.test(k))]||{});
|
|
22
22
|
function Demo() {
|
|
23
23
|
return (
|
|
24
24
|
<div className="cols">
|
|
@@ -32,6 +32,17 @@ function Demo() {
|
|
|
32
32
|
<div className="blk"><div className="lbl">Description list</div>
|
|
33
33
|
<DescriptionList items={[{term:"Owner",value:"An Le"},{term:"Squad",value:"Platform"},{term:"Progress",value:"72%"},{term:"Updated",value:"2h ago"}]} />
|
|
34
34
|
</div>
|
|
35
|
+
<div className="blk"><div className="lbl">Item</div>
|
|
36
|
+
<div style={{ display: "grid", gap: 6 }}>
|
|
37
|
+
<Item title="Release notes" description="Bilingual digest when a project ships" trailing={<Switch defaultChecked aria-label="Release notes" />} />
|
|
38
|
+
<Item title="Profile" description="How you appear" href="#profile" selected />
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
<div className="blk"><div className="lbl">Collapsible</div>
|
|
42
|
+
<Collapsible title="Advanced preferences" defaultOpen={false}>
|
|
43
|
+
<p style={{ margin: 0 }}>Quiet by default — only what you opt into.</p>
|
|
44
|
+
</Collapsible>
|
|
45
|
+
</div>
|
|
35
46
|
<div><div className="lbl">Kbd</div><span><Kbd>⌘</Kbd> <Kbd>K</Kbd> to search</span></div>
|
|
36
47
|
</div>
|
|
37
48
|
<div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<!-- @dsCard group="Components" viewport="
|
|
1
|
+
<!-- @dsCard group="Components" viewport="700x420" name="Data & layout" subtitle="Card, Stat, Avatar, Tooltip, Divider, ScrollArea, AspectRatio" -->
|
|
2
2
|
<!DOCTYPE html>
|
|
3
3
|
<html lang="en">
|
|
4
4
|
<head>
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
.stats { display: flex; gap: 26px; align-items: center; }
|
|
12
12
|
.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; }
|
|
13
13
|
</style>
|
|
14
|
-
<script src="https://unpkg.com/react@
|
|
15
|
-
<script src="https://unpkg.com/react
|
|
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
16
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
17
17
|
<script src="../../_ds_bundle.js"></script>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
21
21
|
<script type="text/babel">
|
|
22
|
-
const { Card, CardHeader, CardBody, CardFooter, Stat, Avatar, AvatarGroup, Tooltip, Divider, Badge } = (window[Object.keys(window).find(k=>/^CyberSkillDesignSystem_[0-9a-f]{6}$/.test(k))]||{});
|
|
22
|
+
const { Card, CardHeader, CardBody, CardFooter, Stat, Avatar, AvatarGroup, Tooltip, Divider, Badge, ScrollArea, AspectRatio } = (window[Object.keys(window).find(k=>/^CyberSkillDesignSystem_[0-9a-f]{6}$/.test(k))]||{});
|
|
23
23
|
function Demo() {
|
|
24
24
|
return (
|
|
25
25
|
<div className="grid">
|
|
@@ -40,6 +40,17 @@ function Demo() {
|
|
|
40
40
|
<Avatar name="Nguyễn Vũ" size="lg" />
|
|
41
41
|
<Tooltip label="Blocked on infra"><span className="cs-badge cs-badge--danger">Blocked</span></Tooltip>
|
|
42
42
|
</div>
|
|
43
|
+
<Divider label="layout" />
|
|
44
|
+
<div className="lbl">ScrollArea</div>
|
|
45
|
+
<ScrollArea maxHeight={72} aria-label="Dense notes" style={{ border: "1px solid var(--cs-color-border-default)", padding: "6px 10px", fontSize: 13, color: "var(--cs-color-text-muted)" }}>
|
|
46
|
+
<p style={{ margin: "0 0 6px" }}>Release notes stay bilingual.</p>
|
|
47
|
+
<p style={{ margin: "0 0 6px" }}>Blocked alerts only when asked.</p>
|
|
48
|
+
<p style={{ margin: 0 }}>Quiet by default.</p>
|
|
49
|
+
</ScrollArea>
|
|
50
|
+
<div className="lbl" style={{ marginTop: 14 }}>AspectRatio</div>
|
|
51
|
+
<AspectRatio ratio="16 / 9" style={{ maxWidth: 200, background: "var(--cs-accent-tint)" }}>
|
|
52
|
+
<div style={{ display: "grid", placeItems: "center", color: "var(--cs-color-text-muted)", fontSize: 12 }}>16:9</div>
|
|
53
|
+
</AspectRatio>
|
|
43
54
|
</div>
|
|
44
55
|
</div>
|
|
45
56
|
);
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
.pill.hold { background: var(--cs-color-semantic-danger); color: #fff; }
|
|
15
15
|
.mono { font-family: var(--cs-font-family-mono); font-weight: 600; }
|
|
16
16
|
</style>
|
|
17
|
-
<script src="https://unpkg.com/react@
|
|
18
|
-
<script src="https://unpkg.com/react
|
|
17
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
|
|
18
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
|
|
19
19
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
20
20
|
<script src="../../_ds_bundle.js"></script>
|
|
21
21
|
</head>
|
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
.frame .cs-dialog-layer { position: absolute; }
|
|
13
13
|
.frame .cs-dialog { top: 24px; }
|
|
14
14
|
</style>
|
|
15
|
-
<script src="https://unpkg.com/react@
|
|
16
|
-
<script src="https://unpkg.com/react
|
|
15
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
|
|
16
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
|
|
17
17
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
18
18
|
<script src="../../_ds_bundle.js"></script>
|
|
19
19
|
</head>
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
.lbl { width: 100%; font: 600 11px/1 var(--cs-font-family-ui); text-transform: uppercase; letter-spacing: .09em; color: var(--cs-color-text-muted); }
|
|
11
11
|
.frame { border: 1px solid var(--cs-color-border-default); border-radius: var(--cs-radius-lg); background: var(--cs-color-surface-panel); }
|
|
12
12
|
</style>
|
|
13
|
-
<script src="https://unpkg.com/react@
|
|
14
|
-
<script src="https://unpkg.com/react
|
|
13
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react.development.js" integrity="sha384-r22tQd8qRIHDPIkmdXZcVtvPi1Tj2x1QaW2tmFQHEvHscATUnHQ5siTyxWw5PMUs" crossorigin="anonymous"></script>
|
|
14
|
+
<script src="https://unpkg.com/umd-react@19.2.8/dist/react-dom.development.js" integrity="sha384-gJZYmumEmwqVmX3JVsLZq+4QrxoPGh/BW47Rk7SqNPCPFpTGnRtvmeRPJ7//ZDPk" crossorigin="anonymous"></script>
|
|
15
15
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
16
16
|
<script src="../../_ds_bundle.js"></script>
|
|
17
17
|
</head>
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
.frame { position: relative; height: 96px; border: 1px solid var(--cs-color-border-default); border-radius: var(--cs-radius-md); background: var(--cs-color-surface-raised); }
|
|
14
14
|
.frame .cs-toast-stack { position: absolute; inset-block-end: 12px; inset-inline-end: 12px; }
|
|
15
15
|
</style>
|
|
16
|
-
<script src="https://unpkg.com/react@
|
|
17
|
-
<script src="https://unpkg.com/react
|
|
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
18
|
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
|
|
19
19
|
<script src="../../_ds_bundle.js"></script>
|
|
20
20
|
</head>
|
|
@@ -7,6 +7,7 @@ export function Cascader({ nodes = [], value = [], onChange, placeholder, label,
|
|
|
7
7
|
const [open, setOpen] = React.useState(false);
|
|
8
8
|
const [path, setPath] = React.useState(value);
|
|
9
9
|
const wrap = React.useRef(null);
|
|
10
|
+
const fieldId = React.useId();
|
|
10
11
|
const [ref, L] = useLang(lang);
|
|
11
12
|
const t = makeT("Cascader", L);
|
|
12
13
|
const ph = placeholder != null ? placeholder : t("placeholder");
|
|
@@ -30,15 +31,16 @@ export function Cascader({ nodes = [], value = [], onChange, placeholder, label,
|
|
|
30
31
|
{ let lv = nodes; for (const k of value) { const n = (lv || []).find((x) => x.key === k); if (!n) break; labels.push(n.label); lv = n.children; } }
|
|
31
32
|
return (
|
|
32
33
|
<div ref={(el) => { wrap.current = el; ref.current = el; }} className={cx("cs-cascader", className)}>
|
|
33
|
-
<button type="button" className="cs-treeselect__field" disabled={disabled} aria-haspopup="listbox" aria-expanded={open} aria-label={label} onClick={() => setOpen((o) => !o)}>
|
|
34
|
+
<button type="button" id={fieldId} className="cs-treeselect__field" disabled={disabled} aria-haspopup="listbox" aria-expanded={open} aria-label={label} onClick={() => setOpen((o) => !o)}>
|
|
34
35
|
<span className={labels.length ? undefined : "ph"}>{labels.length ? labels.join(" / ") : ph}</span><span aria-hidden="true">▾</span>
|
|
35
36
|
</button>
|
|
36
37
|
{open ? (
|
|
37
38
|
<div className="cs-cascader__pop">
|
|
38
39
|
{cols.map((col, i) => (
|
|
39
|
-
<ul key={i} role="listbox" className="cs-cascader__col">
|
|
40
|
+
<ul key={i} role="listbox" aria-labelledby={fieldId} className="cs-cascader__col">
|
|
40
41
|
{col.map((n) => (
|
|
41
|
-
<li
|
|
42
|
+
// role="none" — the <li> wrapper must not sit between listbox and option.
|
|
43
|
+
<li key={n.key} role="none">
|
|
42
44
|
<button type="button" role="option" aria-selected={path[i] === n.key} className={cx("cs-cascader__opt", path[i] === n.key && "on")}
|
|
43
45
|
onClick={() => {
|
|
44
46
|
const next = [...path.slice(0, i), n.key];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
**Editor** — light rich-text (B/I/list), HTML onChange.
|
|
1
|
+
**Editor** — light rich-text (B/I/list), HTML onChange. `defaultValue` is injected as raw HTML via `dangerouslySetInnerHTML` — pass only trusted or consumer-sanitized markup.
|
|
2
2
|
|
|
3
3
|
```jsx
|
|
4
4
|
<Editor defaultValue="<p>Điều ước…</p>" onChange={setHtml}/>
|
|
@@ -3,6 +3,9 @@ import type { ReactNode } from "react";
|
|
|
3
3
|
/** Input with prefix/suffix addons (text or nodes), a clearable ×, and a
|
|
4
4
|
* password show/hide reveal. Controlled or uncontrolled. */
|
|
5
5
|
export interface InputGroupProps {
|
|
6
|
+
id?: string;
|
|
7
|
+
/** Visible field label (associates via htmlFor). When omitted, falls back to aria-label, placeholder, or bilingual "input". */
|
|
8
|
+
label?: string;
|
|
6
9
|
prefix?: ReactNode;
|
|
7
10
|
suffix?: ReactNode;
|
|
8
11
|
clearable?: boolean;
|
|
@@ -16,5 +19,6 @@ export interface InputGroupProps {
|
|
|
16
19
|
/** "en" | "vi" — else resolved from the nearest [lang] ancestor (vi default). */
|
|
17
20
|
lang?: string;
|
|
18
21
|
className?: string;
|
|
22
|
+
"aria-label"?: string;
|
|
19
23
|
}
|
|
20
24
|
export function InputGroup(props: InputGroupProps): React.ReactElement;
|
|
@@ -6,20 +6,50 @@ const EYE_OFF = <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stro
|
|
|
6
6
|
const X = <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg>;
|
|
7
7
|
|
|
8
8
|
/** CyberSkill InputGroup — input with prefix/suffix addons, clearable ×, and password reveal. */
|
|
9
|
-
export function InputGroup({
|
|
9
|
+
export function InputGroup({
|
|
10
|
+
id,
|
|
11
|
+
label,
|
|
12
|
+
prefix,
|
|
13
|
+
suffix,
|
|
14
|
+
clearable = false,
|
|
15
|
+
password = false,
|
|
16
|
+
value,
|
|
17
|
+
onChange,
|
|
18
|
+
defaultValue = "",
|
|
19
|
+
placeholder,
|
|
20
|
+
disabled = false,
|
|
21
|
+
lang,
|
|
22
|
+
className,
|
|
23
|
+
"aria-label": ariaLabel,
|
|
24
|
+
...props
|
|
25
|
+
}) {
|
|
10
26
|
const [inner, setInner] = React.useState(defaultValue);
|
|
11
27
|
const val = value != null ? value : inner;
|
|
12
28
|
const set = (v) => { if (value == null) setInner(v); onChange && onChange(v); };
|
|
13
29
|
const [show, setShow] = React.useState(false);
|
|
14
30
|
const [ref, L] = useLang(lang);
|
|
15
31
|
const t = makeT("InputGroup", L);
|
|
32
|
+
const gid = React.useId();
|
|
33
|
+
const sid = id ?? gid;
|
|
16
34
|
return (
|
|
17
|
-
<
|
|
18
|
-
{
|
|
19
|
-
<
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
35
|
+
<label ref={ref} className={cx("cs-field", disabled && "is-disabled", className)} htmlFor={sid}>
|
|
36
|
+
{label ? <span className="cs-field__label">{label}</span> : null}
|
|
37
|
+
<span className="cs-igroup">
|
|
38
|
+
{prefix != null ? <span className="cs-igroup__fix">{prefix}</span> : null}
|
|
39
|
+
<input
|
|
40
|
+
{...props}
|
|
41
|
+
id={sid}
|
|
42
|
+
type={password && !show ? "password" : "text"}
|
|
43
|
+
value={val}
|
|
44
|
+
placeholder={placeholder}
|
|
45
|
+
disabled={disabled}
|
|
46
|
+
aria-label={label ? undefined : (ariaLabel || placeholder || t("input"))}
|
|
47
|
+
onChange={(e) => set(e.target.value)}
|
|
48
|
+
/>
|
|
49
|
+
{clearable && String(val).length ? <button type="button" className="cs-igroup__btn" aria-label={t("clear")} onClick={() => set("")}>{X}</button> : null}
|
|
50
|
+
{password ? <button type="button" className="cs-igroup__btn" aria-label={show ? t("hide") : t("show")} aria-pressed={show} onClick={() => setShow((s) => !s)}>{show ? EYE_OFF : EYE}</button> : null}
|
|
51
|
+
{suffix != null ? <span className="cs-igroup__fix cs-igroup__fix--suffix">{suffix}</span> : null}
|
|
52
|
+
</span>
|
|
53
|
+
</label>
|
|
24
54
|
);
|
|
25
55
|
}
|