@moodlehq/design-system 3.2.0 → 4.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/README.md +7 -6
- package/dist/components/badge/Badge.d.ts +19 -0
- package/dist/components/badge/Badge.js +42 -0
- package/dist/components/badge/Badge.js.map +1 -0
- package/dist/components/badge/index.d.ts +1 -0
- package/dist/components/badge/index.js +2 -0
- package/dist/components/button/Button.d.ts +4 -3
- package/dist/components/button/Button.js +25 -11
- package/dist/components/button/Button.js.map +1 -1
- package/dist/components/checkbox/Checkbox.d.ts +23 -0
- package/dist/components/checkbox/Checkbox.js +68 -0
- package/dist/components/checkbox/Checkbox.js.map +1 -0
- package/dist/components/checkbox/index.d.ts +1 -0
- package/dist/components/checkbox/index.js +2 -0
- package/dist/components/index.d.ts +4 -0
- package/dist/index.css +514 -50
- package/dist/index.js +3 -1
- package/package.json +3 -1
- package/tokens/css/colors.css +5 -5
- package/tokens/css/primitives.css +1 -1
- package/tokens/scss/_colors.scss +6 -6
- package/tokens/scss/_index_css_vars.scss +340 -0
- package/tokens/scss/_primitives.scss +1 -1
- package/tokens/scss/_typography.scss +1 -1
package/README.md
CHANGED
|
@@ -109,13 +109,13 @@ import { Button } from '@moodlehq/design-system/components/button';
|
|
|
109
109
|
|
|
110
110
|
### Fonts
|
|
111
111
|
|
|
112
|
-
The recommended typeface for Moodle Design System is **[
|
|
112
|
+
The recommended typeface for Moodle Design System is **[Noto Sans](https://fonts.google.com/specimen/Noto+Sans)**. The package does not bundle font files. Provide Noto Sans in your application:
|
|
113
113
|
|
|
114
114
|
**Option 1: Google Fonts CDN**
|
|
115
115
|
|
|
116
116
|
```html
|
|
117
117
|
<link
|
|
118
|
-
href="https://fonts.googleapis.com/css2?family=
|
|
118
|
+
href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap"
|
|
119
119
|
rel="stylesheet"
|
|
120
120
|
/>
|
|
121
121
|
```
|
|
@@ -125,15 +125,16 @@ Place font files in your project and add `@font-face` declarations:
|
|
|
125
125
|
|
|
126
126
|
```css
|
|
127
127
|
@font-face {
|
|
128
|
-
font-family: '
|
|
129
|
-
src: url('./fonts/
|
|
128
|
+
font-family: 'Noto Sans';
|
|
129
|
+
src: url('./fonts/NotoSans-VariableFont_wdth,wght.woff2') format('woff2');
|
|
130
130
|
font-weight: 100 900;
|
|
131
131
|
font-style: normal;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
134
|
@font-face {
|
|
135
|
-
font-family: '
|
|
136
|
-
src: url('./fonts/
|
|
135
|
+
font-family: 'Noto Sans';
|
|
136
|
+
src: url('./fonts/NotoSans-Italic-VariableFont_wdth,wght.woff2')
|
|
137
|
+
format('woff2');
|
|
137
138
|
font-weight: 100 900;
|
|
138
139
|
font-style: italic;
|
|
139
140
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { HTMLAttributes, ReactElement } from 'react';
|
|
2
|
+
type BadgeVariant = 'primary' | 'secondary' | 'success' | 'danger' | 'warning' | 'info';
|
|
3
|
+
type IconElement = ReactElement<'i' | 'svg'>;
|
|
4
|
+
export interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {
|
|
5
|
+
/** Visible badge text. Must be a caller-supplied translated string. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Colour/semantic variant. Defaults to `primary`. */
|
|
8
|
+
variant?: BadgeVariant;
|
|
9
|
+
/** When true, renders the low-contrast (subtle) style with a light background and border. */
|
|
10
|
+
subtle?: boolean;
|
|
11
|
+
/** When true, renders fully rounded pill shape instead of the default slight rounding. */
|
|
12
|
+
pill?: boolean;
|
|
13
|
+
/** Optional icon rendered before the label. Must be an `<i>` or `<svg>` element. Mutually exclusive with `endIcon`. */
|
|
14
|
+
startIcon?: IconElement;
|
|
15
|
+
/** Optional icon rendered after the label. Must be an `<i>` or `<svg>` element. Mutually exclusive with `startIcon`. */
|
|
16
|
+
endIcon?: IconElement;
|
|
17
|
+
}
|
|
18
|
+
export declare const Badge: ({ label, variant, subtle, pill, startIcon, endIcon, className, ...props }: BadgeProps) => import("react/jsx-runtime").JSX.Element;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { isValidElement } from "react";
|
|
2
|
+
import { jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region components/badge/Badge.tsx
|
|
4
|
+
var isIconElement = (el, propName) => {
|
|
5
|
+
return isValidElement(el) && (el.type === "i" || el.type === "svg");
|
|
6
|
+
};
|
|
7
|
+
var allowedVariants = [
|
|
8
|
+
"primary",
|
|
9
|
+
"secondary",
|
|
10
|
+
"success",
|
|
11
|
+
"danger",
|
|
12
|
+
"warning",
|
|
13
|
+
"info"
|
|
14
|
+
];
|
|
15
|
+
var Badge = ({ label, variant, subtle = false, pill = false, startIcon, endIcon, className, ...props }) => {
|
|
16
|
+
const resolvedVariant = variant && allowedVariants.includes(variant) ? variant : "primary";
|
|
17
|
+
const resolvedStartIcon = isIconElement(startIcon, "startIcon") ? startIcon : null;
|
|
18
|
+
let resolvedEndIcon = isIconElement(endIcon, "endIcon") ? endIcon : null;
|
|
19
|
+
if (resolvedStartIcon && resolvedEndIcon) resolvedEndIcon = null;
|
|
20
|
+
const classes = [
|
|
21
|
+
"mds-badge",
|
|
22
|
+
"badge",
|
|
23
|
+
`mds-badge--${resolvedVariant}`
|
|
24
|
+
];
|
|
25
|
+
if (resolvedStartIcon || resolvedEndIcon) classes.push("mds-badge--has-icon");
|
|
26
|
+
if (subtle) classes.push("mds-badge--subtle");
|
|
27
|
+
if (pill) classes.push("mds-badge--pill");
|
|
28
|
+
if (className) classes.push(className);
|
|
29
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
30
|
+
className: classes.join(" "),
|
|
31
|
+
...props,
|
|
32
|
+
children: [
|
|
33
|
+
resolvedStartIcon,
|
|
34
|
+
label,
|
|
35
|
+
resolvedEndIcon
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { Badge };
|
|
41
|
+
|
|
42
|
+
//# sourceMappingURL=Badge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Badge.js","names":[],"sources":["../../../components/badge/Badge.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactElement } from 'react';\nimport { isValidElement } from 'react';\n\ntype BadgeVariant =\n | 'primary'\n | 'secondary'\n | 'success'\n | 'danger'\n | 'warning'\n | 'info';\n\ntype IconElement = ReactElement<'i' | 'svg'>;\n\n// Runtime guard — icon props must be <i> or <svg> elements\nconst isIconElement = (el: unknown, propName: string): el is IconElement => {\n const valid = isValidElement(el) && (el.type === 'i' || el.type === 'svg');\n if (!valid && el != null && import.meta.env.DEV) {\n console.error(`Badge: \\`${propName}\\` must be an <i> or <svg> element.`);\n }\n return valid;\n};\n\nconst allowedVariants: BadgeVariant[] = [\n 'primary',\n 'secondary',\n 'success',\n 'danger',\n 'warning',\n 'info',\n];\n\nexport interface BadgeProps extends HTMLAttributes<HTMLSpanElement> {\n /** Visible badge text. Must be a caller-supplied translated string. */\n label: string;\n /** Colour/semantic variant. Defaults to `primary`. */\n variant?: BadgeVariant;\n /** When true, renders the low-contrast (subtle) style with a light background and border. */\n subtle?: boolean;\n /** When true, renders fully rounded pill shape instead of the default slight rounding. */\n pill?: boolean;\n /** Optional icon rendered before the label. Must be an `<i>` or `<svg>` element. Mutually exclusive with `endIcon`. */\n startIcon?: IconElement;\n /** Optional icon rendered after the label. Must be an `<i>` or `<svg>` element. Mutually exclusive with `startIcon`. */\n endIcon?: IconElement;\n}\n\nexport const Badge = ({\n label,\n variant,\n subtle = false,\n pill = false,\n startIcon,\n endIcon,\n className,\n ...props\n}: BadgeProps) => {\n const resolvedVariant =\n variant && allowedVariants.includes(variant as BadgeVariant)\n ? variant\n : 'primary';\n\n const resolvedStartIcon = isIconElement(startIcon, 'startIcon')\n ? startIcon\n : null;\n let resolvedEndIcon = isIconElement(endIcon, 'endIcon') ? endIcon : null;\n\n if (import.meta.env.DEV) {\n if (variant && !allowedVariants.includes(variant as BadgeVariant)) {\n console.warn(\n `[MDS Badge] Invalid variant \"${variant}\". Falling back to \"primary\". Allowed: ${allowedVariants.join(', ')}`,\n );\n }\n if (resolvedStartIcon && resolvedEndIcon) {\n console.warn(\n '[MDS Badge] `startIcon` and `endIcon` are mutually exclusive. Rendering `startIcon` only.',\n );\n }\n }\n\n // Only one icon can be rendered at a time; startIcon takes precedence when both are provided.\n if (resolvedStartIcon && resolvedEndIcon) {\n resolvedEndIcon = null;\n }\n\n const classes = ['mds-badge', 'badge', `mds-badge--${resolvedVariant}`];\n if (resolvedStartIcon || resolvedEndIcon) classes.push('mds-badge--has-icon');\n if (subtle) classes.push('mds-badge--subtle');\n if (pill) classes.push('mds-badge--pill');\n if (className) classes.push(className);\n\n return (\n <span className={classes.join(' ')} {...props}>\n {resolvedStartIcon}\n {label}\n {resolvedEndIcon}\n </span>\n );\n};\n"],"mappings":";;;AAcA,IAAM,iBAAiB,IAAa,aAAwC;CAK1E,OAJc,eAAe,EAAE,MAAM,GAAG,SAAS,OAAO,GAAG,SAAS;AAKtE;AAEA,IAAM,kBAAkC;CACtC;CACA;CACA;CACA;CACA;CACA;AACF;AAiBA,IAAa,SAAS,EACpB,OACA,SACA,SAAS,OACT,OAAO,OACP,WACA,SACA,WACA,GAAG,YACa;CAChB,MAAM,kBACJ,WAAW,gBAAgB,SAAS,OAAuB,IACvD,UACA;CAEN,MAAM,oBAAoB,cAAc,WAAW,WAAW,IAC1D,YACA;CACJ,IAAI,kBAAkB,cAAc,SAAS,SAAS,IAAI,UAAU;CAgBpE,IAAI,qBAAqB,iBACvB,kBAAkB;CAGpB,MAAM,UAAU;EAAC;EAAa;EAAS,cAAc;CAAiB;CACtE,IAAI,qBAAqB,iBAAiB,QAAQ,KAAK,qBAAqB;CAC5E,IAAI,QAAQ,QAAQ,KAAK,mBAAmB;CAC5C,IAAI,MAAM,QAAQ,KAAK,iBAAiB;CACxC,IAAI,WAAW,QAAQ,KAAK,SAAS;CAErC,OACE,qBAAC,QAAD;EAAM,WAAW,QAAQ,KAAK,GAAG;EAAG,GAAI;YAAxC;GACG;GACA;GACA;EACG;;AAEV"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Badge';
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { ButtonHTMLAttributes, ReactElement } from 'react';
|
|
2
|
-
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'outline-primary' | 'outline-secondary' | 'outline-danger';
|
|
2
|
+
type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost' | 'outline-primary' | 'outline-secondary' | 'outline-danger';
|
|
3
|
+
type ButtonSize = 'sm' | 'md' | 'lg';
|
|
3
4
|
type IconElement = ReactElement<'i' | 'svg'>;
|
|
4
5
|
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
5
6
|
label?: string;
|
|
6
7
|
variant?: ButtonVariant;
|
|
7
|
-
size?:
|
|
8
|
+
size?: ButtonSize;
|
|
8
9
|
startIcon?: IconElement;
|
|
9
10
|
endIcon?: IconElement;
|
|
10
11
|
}
|
|
11
|
-
export declare const Button: (
|
|
12
|
+
export declare const Button: import('react').ForwardRefExoticComponent<ButtonProps & import('react').RefAttributes<HTMLButtonElement>>;
|
|
12
13
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { isValidElement } from "react";
|
|
2
|
-
import { jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { forwardRef, isValidElement } from "react";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
//#region components/button/Button.tsx
|
|
4
4
|
var isIconElement = (el, propName) => {
|
|
5
5
|
return isValidElement(el) && (el.type === "i" || el.type === "svg");
|
|
@@ -8,29 +8,43 @@ var allowedVariants = [
|
|
|
8
8
|
"primary",
|
|
9
9
|
"secondary",
|
|
10
10
|
"danger",
|
|
11
|
+
"ghost",
|
|
11
12
|
"outline-primary",
|
|
12
13
|
"outline-secondary",
|
|
13
14
|
"outline-danger"
|
|
14
15
|
];
|
|
15
|
-
var
|
|
16
|
+
var allowedSizes = [
|
|
17
|
+
"sm",
|
|
18
|
+
"md",
|
|
19
|
+
"lg"
|
|
20
|
+
];
|
|
21
|
+
var Button = forwardRef(function Button({ label, variant, size, startIcon, endIcon, className, type = "button", ...props }, ref) {
|
|
22
|
+
const resolvedVariant = variant && allowedVariants.includes(variant) ? variant : "primary";
|
|
23
|
+
const resolvedSize = size && allowedSizes.includes(size) ? size : "md";
|
|
24
|
+
const resolvedStartIcon = isIconElement(startIcon, "startIcon") ? startIcon : null;
|
|
25
|
+
const resolvedEndIcon = isIconElement(endIcon, "endIcon") ? endIcon : null;
|
|
26
|
+
const isIconOnly = !label && Boolean(resolvedStartIcon || resolvedEndIcon);
|
|
16
27
|
const classes = [
|
|
17
28
|
"mds-btn",
|
|
18
29
|
"btn",
|
|
19
|
-
`btn-${
|
|
30
|
+
`btn-${resolvedVariant}`,
|
|
31
|
+
`mds-btn--size-${resolvedSize}`
|
|
20
32
|
];
|
|
21
|
-
if (
|
|
33
|
+
if (isIconOnly) classes.push("mds-btn--icon-only");
|
|
22
34
|
if (className) classes.push(className);
|
|
23
|
-
return /* @__PURE__ */
|
|
35
|
+
return /* @__PURE__ */ jsx("button", {
|
|
36
|
+
ref,
|
|
24
37
|
className: classes.join(" "),
|
|
25
38
|
type,
|
|
26
39
|
...props,
|
|
27
|
-
children: [
|
|
28
|
-
|
|
40
|
+
children: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
41
|
+
resolvedStartIcon,
|
|
29
42
|
label,
|
|
30
|
-
|
|
31
|
-
]
|
|
43
|
+
resolvedStartIcon ? null : resolvedEndIcon
|
|
44
|
+
] })
|
|
32
45
|
});
|
|
33
|
-
};
|
|
46
|
+
});
|
|
47
|
+
Button.displayName = "Button";
|
|
34
48
|
//#endregion
|
|
35
49
|
export { Button };
|
|
36
50
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Button.js","names":[],"sources":["../../../components/button/Button.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes, ReactElement } from 'react';\nimport { isValidElement } from 'react';\n\ntype ButtonVariant =\n | 'primary'\n | 'secondary'\n | 'danger'\n | 'outline-primary'\n | 'outline-secondary'\n | 'outline-danger';\n\ntype IconElement = ReactElement<'i' | 'svg'>;\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n label?: string;\n variant?: ButtonVariant;\n size?:
|
|
1
|
+
{"version":3,"file":"Button.js","names":[],"sources":["../../../components/button/Button.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes, ReactElement } from 'react';\nimport { forwardRef, isValidElement } from 'react';\n\ntype ButtonVariant =\n | 'primary'\n | 'secondary'\n | 'danger'\n | 'ghost'\n | 'outline-primary'\n | 'outline-secondary'\n | 'outline-danger';\n\ntype ButtonSize = 'sm' | 'md' | 'lg';\n\ntype IconElement = ReactElement<'i' | 'svg'>;\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n label?: string;\n variant?: ButtonVariant;\n size?: ButtonSize;\n startIcon?: IconElement;\n endIcon?: IconElement;\n}\n\n// Runtime guard — prop for icons must be <i> or <svg> elements\nconst isIconElement = (el: unknown, propName: string): el is IconElement => {\n const valid = isValidElement(el) && (el.type === 'i' || el.type === 'svg');\n if (!valid && el != null && import.meta.env.DEV) {\n console.error(`Button: \\`${propName}\\` must be an <i> or <svg> element.`);\n }\n return valid;\n};\n\nconst allowedVariants: ButtonVariant[] = [\n 'primary',\n 'secondary',\n 'danger',\n 'ghost',\n 'outline-primary',\n 'outline-secondary',\n 'outline-danger',\n];\n\nconst allowedSizes: ButtonSize[] = ['sm', 'md', 'lg'];\n\nexport const Button = forwardRef<HTMLButtonElement, ButtonProps>(\n function Button(\n {\n label,\n variant,\n size,\n startIcon,\n endIcon,\n className,\n type = 'button',\n ...props\n },\n ref,\n ) {\n const resolvedVariant =\n variant && allowedVariants.includes(variant as ButtonVariant)\n ? variant\n : 'primary';\n const resolvedSize =\n size && allowedSizes.includes(size as ButtonSize) ? size : 'md';\n const resolvedStartIcon = isIconElement(startIcon, 'startIcon')\n ? startIcon\n : null;\n const resolvedEndIcon = isIconElement(endIcon, 'endIcon') ? endIcon : null;\n\n if (import.meta.env.DEV) {\n const hasAccessibleName =\n Boolean(label) ||\n Boolean(props['aria-label']?.trim()) ||\n Boolean(props['aria-labelledby']?.trim());\n if (!hasAccessibleName) {\n console.warn(\n 'Button: provide a label, aria-label, or aria-labelledby for accessibility.',\n );\n }\n if (variant && !allowedVariants.includes(variant as ButtonVariant)) {\n console.warn(\n `[MDS Button] Invalid variant \"${variant}\". Falling back to \"primary\". Allowed: ${allowedVariants.join(', ')}`,\n );\n }\n if (size && !allowedSizes.includes(size as ButtonSize)) {\n console.warn(\n `[MDS Button] Invalid size \"${size}\". Falling back to \"md\". Allowed: ${allowedSizes.join(', ')}`,\n );\n }\n if (resolvedStartIcon && resolvedEndIcon) {\n console.warn(\n 'Button: pass either startIcon or endIcon, not both. Rendering startIcon only.',\n );\n }\n if (!label && !resolvedStartIcon && !resolvedEndIcon) {\n console.warn(\n 'Button: provide a label or icon so the button does not render as visually empty.',\n );\n }\n }\n\n const isIconOnly = !label && Boolean(resolvedStartIcon || resolvedEndIcon);\n\n const classes = [\n 'mds-btn',\n 'btn',\n `btn-${resolvedVariant}`,\n `mds-btn--size-${resolvedSize}`,\n ];\n if (isIconOnly) {\n classes.push('mds-btn--icon-only');\n }\n if (className) {\n classes.push(className);\n }\n\n return (\n <button ref={ref} className={classes.join(' ')} type={type} {...props}>\n <>\n {resolvedStartIcon}\n {label}\n {resolvedStartIcon ? null : resolvedEndIcon}\n </>\n </button>\n );\n },\n);\nButton.displayName = 'Button';\n"],"mappings":";;;AAyBA,IAAM,iBAAiB,IAAa,aAAwC;CAK1E,OAJc,eAAe,EAAE,MAAM,GAAG,SAAS,OAAO,GAAG,SAAS;AAKtE;AAEA,IAAM,kBAAmC;CACvC;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,IAAM,eAA6B;CAAC;CAAM;CAAM;AAAI;AAEpD,IAAa,SAAS,WACpB,SAAS,OACP,EACE,OACA,SACA,MACA,WACA,SACA,WACA,OAAO,UACP,GAAG,SAEL,KACA;CACA,MAAM,kBACJ,WAAW,gBAAgB,SAAS,OAAwB,IACxD,UACA;CACN,MAAM,eACJ,QAAQ,aAAa,SAAS,IAAkB,IAAI,OAAO;CAC7D,MAAM,oBAAoB,cAAc,WAAW,WAAW,IAC1D,YACA;CACJ,MAAM,kBAAkB,cAAc,SAAS,SAAS,IAAI,UAAU;CAkCtE,MAAM,aAAa,CAAC,SAAS,QAAQ,qBAAqB,eAAe;CAEzE,MAAM,UAAU;EACd;EACA;EACA,OAAO;EACP,iBAAiB;CACnB;CACA,IAAI,YACF,QAAQ,KAAK,oBAAoB;CAEnC,IAAI,WACF,QAAQ,KAAK,SAAS;CAGxB,OACE,oBAAC,UAAD;EAAa;EAAK,WAAW,QAAQ,KAAK,GAAG;EAAS;EAAM,GAAI;YAC9D,qBAAA,UAAA,EAAA,UAAA;GACG;GACA;GACA,oBAAoB,OAAO;EAC5B,EAAA,CAAA;CACI,CAAA;AAEZ,CACF;AACA,OAAO,cAAc"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { InputHTMLAttributes } from 'react';
|
|
2
|
+
export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
|
|
3
|
+
/** Visible label text. When hideLabel is true this also serves as the aria-label fallback
|
|
4
|
+
* if no explicit aria-label prop is provided. */
|
|
5
|
+
label?: string;
|
|
6
|
+
/** When true, the visible label element is hidden. The input is still labelled accessibly
|
|
7
|
+
* via aria-label (prop) → label (prop) in that order of precedence. Suppresses
|
|
8
|
+
* invalidFeedback — feedback text requires a visible label to provide context. */
|
|
9
|
+
hideLabel?: boolean;
|
|
10
|
+
/** Marks the input as invalid: applies danger border/label colour and sets aria-invalid.
|
|
11
|
+
* Independent of invalidFeedback — invalid styling can be shown without a message. */
|
|
12
|
+
invalid?: boolean;
|
|
13
|
+
/** Renders the checkbox in a mixed state, typically for "select all" parent controls.
|
|
14
|
+
* This state is visual/semantic and should usually be controlled by parent logic. */
|
|
15
|
+
indeterminate?: boolean;
|
|
16
|
+
/** Optional supporting/helper text shown below the label in non-error state.
|
|
17
|
+
* Hidden when hideLabel is true. */
|
|
18
|
+
supportingText?: string;
|
|
19
|
+
/** Pre-translated error message rendered below the label. Requires invalid={true} and
|
|
20
|
+
* hideLabel={false} to be displayed. */
|
|
21
|
+
invalidFeedback?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const Checkbox: import('react').ForwardRefExoticComponent<CheckboxProps & import('react').RefAttributes<HTMLInputElement>>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { forwardRef, useEffect, useId, useRef } from "react";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
//#region components/checkbox/Checkbox.tsx
|
|
4
|
+
var Checkbox = forwardRef(({ invalidFeedback, invalid, indeterminate = false, supportingText, className, label, hideLabel = false, id: idProp, required, "aria-label": ariaLabelProp, ...inputProps }, ref) => {
|
|
5
|
+
const generatedId = useId();
|
|
6
|
+
const id = idProp ?? generatedId;
|
|
7
|
+
const hasVisibleLabel = !hideLabel;
|
|
8
|
+
const isInvalid = !!invalid;
|
|
9
|
+
const isIndeterminate = !!indeterminate;
|
|
10
|
+
const inputRef = useRef(null);
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (inputRef.current) inputRef.current.indeterminate = isIndeterminate;
|
|
13
|
+
}, [isIndeterminate]);
|
|
14
|
+
const classes = ["mds-checkbox"];
|
|
15
|
+
if (hasVisibleLabel) classes.push("form-check");
|
|
16
|
+
if (className) classes.push(className);
|
|
17
|
+
const ariaLabel = hideLabel ? ariaLabelProp ?? label : void 0;
|
|
18
|
+
const messageText = hasVisibleLabel ? isInvalid && invalidFeedback ? invalidFeedback : supportingText : void 0;
|
|
19
|
+
const hasInvalidFeedback = hasVisibleLabel && isInvalid && !!invalidFeedback;
|
|
20
|
+
const feedbackId = messageText ? `${id}-feedback` : void 0;
|
|
21
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
22
|
+
className: classes.join(" "),
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsx("input", {
|
|
25
|
+
className: [
|
|
26
|
+
"mds-checkbox-input",
|
|
27
|
+
"form-check-input",
|
|
28
|
+
isInvalid ? "is-invalid" : ""
|
|
29
|
+
].filter(Boolean).join(" "),
|
|
30
|
+
ref: (node) => {
|
|
31
|
+
inputRef.current = node;
|
|
32
|
+
if (typeof ref === "function") ref(node);
|
|
33
|
+
else if (ref) ref.current = node;
|
|
34
|
+
},
|
|
35
|
+
...inputProps,
|
|
36
|
+
type: "checkbox",
|
|
37
|
+
required,
|
|
38
|
+
"aria-invalid": isInvalid ? true : void 0,
|
|
39
|
+
"aria-label": ariaLabel,
|
|
40
|
+
"aria-checked": isIndeterminate ? "mixed" : void 0,
|
|
41
|
+
"aria-describedby": feedbackId,
|
|
42
|
+
id
|
|
43
|
+
}),
|
|
44
|
+
hasVisibleLabel && /* @__PURE__ */ jsxs("label", {
|
|
45
|
+
className: "mds-checkbox-label form-check-label",
|
|
46
|
+
htmlFor: id,
|
|
47
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
48
|
+
className: "mds-checkbox-label-text",
|
|
49
|
+
children: label
|
|
50
|
+
}), required && /* @__PURE__ */ jsx("span", {
|
|
51
|
+
className: "mds-checkbox-required",
|
|
52
|
+
"aria-hidden": "true",
|
|
53
|
+
children: "*"
|
|
54
|
+
})]
|
|
55
|
+
}),
|
|
56
|
+
feedbackId && /* @__PURE__ */ jsx("div", {
|
|
57
|
+
id: feedbackId,
|
|
58
|
+
className: ["mds-checkbox-feedback", hasInvalidFeedback ? "invalid-feedback" : "mds-checkbox-supporting-text"].filter(Boolean).join(" "),
|
|
59
|
+
children: messageText
|
|
60
|
+
})
|
|
61
|
+
]
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
Checkbox.displayName = "Checkbox";
|
|
65
|
+
//#endregion
|
|
66
|
+
export { Checkbox };
|
|
67
|
+
|
|
68
|
+
//# sourceMappingURL=Checkbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Checkbox.js","names":[],"sources":["../../../components/checkbox/Checkbox.tsx"],"sourcesContent":["import {\n type InputHTMLAttributes,\n forwardRef,\n useEffect,\n useId,\n useRef,\n} from 'react';\n\nexport interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {\n /** Visible label text. When hideLabel is true this also serves as the aria-label fallback\n * if no explicit aria-label prop is provided. */\n label?: string;\n /** When true, the visible label element is hidden. The input is still labelled accessibly\n * via aria-label (prop) → label (prop) in that order of precedence. Suppresses\n * invalidFeedback — feedback text requires a visible label to provide context. */\n hideLabel?: boolean;\n /** Marks the input as invalid: applies danger border/label colour and sets aria-invalid.\n * Independent of invalidFeedback — invalid styling can be shown without a message. */\n invalid?: boolean;\n /** Renders the checkbox in a mixed state, typically for \"select all\" parent controls.\n * This state is visual/semantic and should usually be controlled by parent logic. */\n indeterminate?: boolean;\n /** Optional supporting/helper text shown below the label in non-error state.\n * Hidden when hideLabel is true. */\n supportingText?: string;\n /** Pre-translated error message rendered below the label. Requires invalid={true} and\n * hideLabel={false} to be displayed. */\n invalidFeedback?: string;\n}\n\nexport const Checkbox = forwardRef<HTMLInputElement, CheckboxProps>(\n (\n {\n invalidFeedback,\n invalid,\n indeterminate = false,\n supportingText,\n className,\n label,\n hideLabel = false,\n id: idProp,\n required,\n 'aria-label': ariaLabelProp,\n ...inputProps\n }: CheckboxProps,\n ref,\n ) => {\n const generatedId = useId();\n const id = idProp ?? generatedId;\n const hasVisibleLabel = !hideLabel;\n const isInvalid = !!invalid;\n const isIndeterminate = !!indeterminate;\n const inputRef = useRef<HTMLInputElement | null>(null);\n\n useEffect(() => {\n if (inputRef.current) inputRef.current.indeterminate = isIndeterminate;\n }, [isIndeterminate]);\n\n const warnDev = (condition: boolean, message: string) => {\n if (import.meta.env.DEV && condition) {\n console.warn(message);\n }\n };\n\n if (import.meta.env.DEV) {\n warnDev(\n hideLabel && !ariaLabelProp && !label,\n 'Checkbox: label prop or aria-label attribute is required for accessibility when hideLabel is true.',\n );\n warnDev(\n !hideLabel && !label,\n 'Checkbox: label prop is required when hideLabel is false. An empty label creates an inaccessible form control.',\n );\n warnDev(\n hideLabel && !!invalidFeedback,\n 'Checkbox: invalidFeedback is ignored when hideLabel is true. Feedback text requires a visible label to provide context.',\n );\n warnDev(\n !hideLabel && !!invalidFeedback && !invalid,\n 'Checkbox: invalidFeedback is provided without invalid={true}. Pass invalid={true} to apply invalid styling alongside the feedback text.',\n );\n }\n\n const classes = ['mds-checkbox'];\n if (hasVisibleLabel) classes.push('form-check');\n if (className) {\n classes.push(className);\n }\n\n const ariaLabel = hideLabel ? (ariaLabelProp ?? label) : undefined;\n const messageText = hasVisibleLabel\n ? isInvalid && invalidFeedback\n ? invalidFeedback\n : supportingText\n : undefined;\n const hasInvalidFeedback =\n hasVisibleLabel && isInvalid && !!invalidFeedback;\n const feedbackId = messageText ? `${id}-feedback` : undefined;\n\n return (\n <div className={classes.join(' ')}>\n <input\n className={[\n 'mds-checkbox-input',\n 'form-check-input',\n isInvalid ? 'is-invalid' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n ref={(node) => {\n // Keep a local ref for the native indeterminate property while still forwarding refs.\n inputRef.current = node;\n if (typeof ref === 'function') {\n ref(node);\n } else if (ref) {\n ref.current = node;\n }\n }}\n {...inputProps}\n type=\"checkbox\"\n required={required}\n aria-invalid={isInvalid ? true : undefined}\n aria-label={ariaLabel}\n aria-checked={isIndeterminate ? 'mixed' : undefined}\n aria-describedby={feedbackId}\n id={id}\n />\n {hasVisibleLabel && (\n <label className=\"mds-checkbox-label form-check-label\" htmlFor={id}>\n <span className=\"mds-checkbox-label-text\">{label}</span>\n {required && (\n <span className=\"mds-checkbox-required\" aria-hidden=\"true\">\n *\n </span>\n )}\n </label>\n )}\n {feedbackId && (\n <div\n id={feedbackId}\n className={[\n 'mds-checkbox-feedback',\n hasInvalidFeedback\n ? 'invalid-feedback'\n : 'mds-checkbox-supporting-text',\n ]\n .filter(Boolean)\n .join(' ')}\n >\n {messageText}\n </div>\n )}\n </div>\n );\n },\n);\nCheckbox.displayName = 'Checkbox';\n"],"mappings":";;;AA8BA,IAAa,WAAW,YAEpB,EACE,iBACA,SACA,gBAAgB,OAChB,gBACA,WACA,OACA,YAAY,OACZ,IAAI,QACJ,UACA,cAAc,eACd,GAAG,cAEL,QACG;CACH,MAAM,cAAc,MAAM;CAC1B,MAAM,KAAK,UAAU;CACrB,MAAM,kBAAkB,CAAC;CACzB,MAAM,YAAY,CAAC,CAAC;CACpB,MAAM,kBAAkB,CAAC,CAAC;CAC1B,MAAM,WAAW,OAAgC,IAAI;CAErD,gBAAgB;EACd,IAAI,SAAS,SAAS,SAAS,QAAQ,gBAAgB;CACzD,GAAG,CAAC,eAAe,CAAC;CA2BpB,MAAM,UAAU,CAAC,cAAc;CAC/B,IAAI,iBAAiB,QAAQ,KAAK,YAAY;CAC9C,IAAI,WACF,QAAQ,KAAK,SAAS;CAGxB,MAAM,YAAY,YAAa,iBAAiB,QAAS,KAAA;CACzD,MAAM,cAAc,kBAChB,aAAa,kBACX,kBACA,iBACF,KAAA;CACJ,MAAM,qBACJ,mBAAmB,aAAa,CAAC,CAAC;CACpC,MAAM,aAAa,cAAc,GAAG,GAAG,aAAa,KAAA;CAEpD,OACE,qBAAC,OAAD;EAAK,WAAW,QAAQ,KAAK,GAAG;YAAhC;GACE,oBAAC,SAAD;IACE,WAAW;KACT;KACA;KACA,YAAY,eAAe;IAC7B,EACG,OAAO,OAAO,EACd,KAAK,GAAG;IACX,MAAM,SAAS;KAEb,SAAS,UAAU;KACnB,IAAI,OAAO,QAAQ,YACjB,IAAI,IAAI;UACH,IAAI,KACT,IAAI,UAAU;IAElB;IACA,GAAI;IACJ,MAAK;IACK;IACV,gBAAc,YAAY,OAAO,KAAA;IACjC,cAAY;IACZ,gBAAc,kBAAkB,UAAU,KAAA;IAC1C,oBAAkB;IACd;GACL,CAAA;GACA,mBACC,qBAAC,SAAD;IAAO,WAAU;IAAsC,SAAS;cAAhE,CACE,oBAAC,QAAD;KAAM,WAAU;eAA2B;IAAY,CAAA,GACtD,YACC,oBAAC,QAAD;KAAM,WAAU;KAAwB,eAAY;eAAO;IAErD,CAAA,CAEH;;GAER,cACC,oBAAC,OAAD;IACE,IAAI;IACJ,WAAW,CACT,yBACA,qBACI,qBACA,8BACN,EACG,OAAO,OAAO,EACd,KAAK,GAAG;cAEV;GACE,CAAA;EAEJ;;AAET,CACF;AACA,SAAS,cAAc"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './Checkbox';
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
export { ActivityIcon } from './activity-icon';
|
|
2
2
|
export type { ActivityIconProps } from './activity-icon';
|
|
3
|
+
export { Badge } from './badge';
|
|
4
|
+
export type { BadgeProps } from './badge';
|
|
3
5
|
export { Button } from './button';
|
|
4
6
|
export type { ButtonProps } from './button';
|
|
7
|
+
export { Checkbox } from './checkbox';
|
|
8
|
+
export type { CheckboxProps } from './checkbox';
|
|
5
9
|
export { CloseButton } from './close-button';
|
|
6
10
|
export type { CloseButtonProps } from './close-button';
|
|
7
11
|
export { Radio } from './radio';
|