@moodlehq/design-system 3.1.0 → 4.0.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 CHANGED
@@ -75,7 +75,7 @@ This repository follows these standards:
75
75
 
76
76
  ### Prerequisites
77
77
 
78
- - Node.js v22.13.0 or higher
78
+ - Node.js v22.22.1 or higher
79
79
  - npm
80
80
  - Git
81
81
 
@@ -1 +1 @@
1
- {"version":3,"file":"ActivityIcon.js","names":[],"sources":["../../../components/activity-icon/ActivityIcon.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\nimport { useEffect, useState } from 'react';\nimport type { ActivityIconName } from './activityIconRegistry';\nimport {\n activityIconNames,\n activityIconRegistry,\n} from './activityIconRegistry';\n\nconst iconGlob = import.meta.glob<{ default: string }>('./assets/*.svg', {\n eager: false,\n});\n\nfunction loadIconSrc(fileName: string): Promise<string> {\n const globPath = `./assets/${fileName}.svg`;\n const loader = iconGlob[globPath];\n\n if (!loader) {\n return Promise.reject(\n new Error(\n `[MDS ActivityIcon] Icon file \"${fileName}.svg\" not found in assets.`,\n ),\n );\n }\n\n return loader().then((mod) => mod.default);\n}\n\nexport type ActivityIconVariant = 'none' | 'default' | 'large';\nconst allowedVariants: ActivityIconVariant[] = ['none', 'default', 'large'];\n\nexport type ActivityIconSize = 'sm' | 'md' | 'lg' | 'xl';\nconst allowedSizes: ActivityIconSize[] = ['sm', 'md', 'lg', 'xl'];\nconst iconLookupFallback: ActivityIconName = 'file-unknown';\n\nexport interface ActivityIconProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Activity/resource icon key used to resolve the SVG asset from the registry.\n */\n icon: ActivityIconName;\n /**\n * Accessible text for the rendered image. Use an empty string for decorative icons.\n */\n alt?: string;\n /**\n * Visual container style around the icon.\n */\n variant?: ActivityIconVariant;\n /**\n * Icon size token.\n */\n size?: ActivityIconSize;\n}\n\nexport const ActivityIcon = ({\n icon,\n alt = '',\n variant,\n size,\n className,\n ...props\n}: ActivityIconProps) => {\n // TS callers must pass icon, but JS consumers can still provide undefined at runtime.\n const normalizedIcon = icon?.toLowerCase();\n const hasValidIcon =\n normalizedIcon !== undefined && normalizedIcon in activityIconRegistry;\n\n if (!hasValidIcon) {\n const invalidIconMessage = `[MDS ActivityIcon] Invalid icon \"${icon}\". Allowed: ${activityIconNames.join(', ')}`;\n console.error(\n `${invalidIconMessage}. Falling back to \"${iconLookupFallback}\" placeholder.`,\n );\n }\n\n if (import.meta.env.DEV && variant && !allowedVariants.includes(variant)) {\n console.warn(\n `[MDS ActivityIcon] Invalid variant \"${variant}\". Falling back to \"default\". Allowed: ${allowedVariants.join(', ')}`,\n );\n }\n\n if (import.meta.env.DEV && size && !allowedSizes.includes(size)) {\n console.warn(\n `[MDS ActivityIcon] Invalid size \"${size}\". Falling back to \"md\". Allowed: ${allowedSizes.join(', ')}`,\n );\n }\n\n const resolvedIcon: ActivityIconName = hasValidIcon\n ? (normalizedIcon as ActivityIconName)\n : iconLookupFallback;\n const resolvedVariant =\n variant && allowedVariants.includes(variant) ? variant : 'default';\n const resolvedSize = size && allowedSizes.includes(size) ? size : 'md';\n const resolvedCategory = activityIconRegistry[resolvedIcon].category;\n const [iconSrc, setIconSrc] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n let isMounted = true;\n const { fileName } = activityIconRegistry[resolvedIcon];\n\n void loadIconSrc(fileName)\n .then((src) => {\n if (isMounted) {\n setIconSrc(src);\n }\n })\n .catch(() => {\n if (isMounted) {\n setIconSrc(undefined);\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [resolvedIcon]);\n\n const classes = [\n 'mds-activity-icon',\n `mds-activity-icon--${resolvedVariant}`,\n `mds-activity-icon--size-${resolvedSize}`,\n `mds-activity-icon--category-${resolvedCategory}`,\n ];\n if (className) {\n classes.push(className);\n }\n\n return (\n <span className={classes.join(' ')} {...props}>\n <img alt={alt} className=\"mds-activity-icon__asset\" src={iconSrc} />\n </span>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ActivityIcon.js","names":[],"sources":["../../../components/activity-icon/ActivityIcon.tsx"],"sourcesContent":["import type { HTMLAttributes } from 'react';\nimport { useEffect, useState } from 'react';\nimport type { ActivityIconName } from './activityIconRegistry';\nimport {\n activityIconNames,\n activityIconRegistry,\n} from './activityIconRegistry';\n\nconst iconGlob = import.meta.glob<{ default: string }>('./assets/*.svg', {\n eager: false,\n});\n\nfunction loadIconSrc(fileName: string): Promise<string> {\n const globPath = `./assets/${fileName}.svg`;\n const loader = iconGlob[globPath];\n\n if (!loader) {\n return Promise.reject(\n new Error(\n `[MDS ActivityIcon] Icon file \"${fileName}.svg\" not found in assets.`,\n ),\n );\n }\n\n return loader().then((mod) => mod.default);\n}\n\nexport type ActivityIconVariant = 'none' | 'default' | 'large';\nconst allowedVariants: ActivityIconVariant[] = ['none', 'default', 'large'];\n\nexport type ActivityIconSize = 'sm' | 'md' | 'lg' | 'xl';\nconst allowedSizes: ActivityIconSize[] = ['sm', 'md', 'lg', 'xl'];\nconst iconLookupFallback: ActivityIconName = 'file-unknown';\n\nexport interface ActivityIconProps extends HTMLAttributes<HTMLSpanElement> {\n /**\n * Activity/resource icon key used to resolve the SVG asset from the registry.\n */\n icon: ActivityIconName;\n /**\n * Accessible text for the rendered image. Use an empty string for decorative icons.\n */\n alt?: string;\n /**\n * Visual container style around the icon.\n */\n variant?: ActivityIconVariant;\n /**\n * Icon size token.\n */\n size?: ActivityIconSize;\n}\n\nexport const ActivityIcon = ({\n icon,\n alt = '',\n variant,\n size,\n className,\n ...props\n}: ActivityIconProps) => {\n // TS callers must pass icon, but JS consumers can still provide undefined at runtime.\n const normalizedIcon = icon?.toLowerCase();\n const hasValidIcon =\n normalizedIcon !== undefined && normalizedIcon in activityIconRegistry;\n\n if (!hasValidIcon) {\n const invalidIconMessage = `[MDS ActivityIcon] Invalid icon \"${icon}\". Allowed: ${activityIconNames.join(', ')}`;\n console.error(\n `${invalidIconMessage}. Falling back to \"${iconLookupFallback}\" placeholder.`,\n );\n }\n\n if (import.meta.env.DEV && variant && !allowedVariants.includes(variant)) {\n console.warn(\n `[MDS ActivityIcon] Invalid variant \"${variant}\". Falling back to \"default\". Allowed: ${allowedVariants.join(', ')}`,\n );\n }\n\n if (import.meta.env.DEV && size && !allowedSizes.includes(size)) {\n console.warn(\n `[MDS ActivityIcon] Invalid size \"${size}\". Falling back to \"md\". Allowed: ${allowedSizes.join(', ')}`,\n );\n }\n\n const resolvedIcon: ActivityIconName = hasValidIcon\n ? (normalizedIcon as ActivityIconName)\n : iconLookupFallback;\n const resolvedVariant =\n variant && allowedVariants.includes(variant) ? variant : 'default';\n const resolvedSize = size && allowedSizes.includes(size) ? size : 'md';\n const resolvedCategory = activityIconRegistry[resolvedIcon].category;\n const [iconSrc, setIconSrc] = useState<string | undefined>(undefined);\n\n useEffect(() => {\n let isMounted = true;\n const { fileName } = activityIconRegistry[resolvedIcon];\n\n void loadIconSrc(fileName)\n .then((src) => {\n if (isMounted) {\n setIconSrc(src);\n }\n })\n .catch(() => {\n if (isMounted) {\n setIconSrc(undefined);\n }\n });\n\n return () => {\n isMounted = false;\n };\n }, [resolvedIcon]);\n\n const classes = [\n 'mds-activity-icon',\n `mds-activity-icon--${resolvedVariant}`,\n `mds-activity-icon--size-${resolvedSize}`,\n `mds-activity-icon--category-${resolvedCategory}`,\n ];\n if (className) {\n classes.push(className);\n }\n\n return (\n <span className={classes.join(' ')} {...props}>\n <img alt={alt} className=\"mds-activity-icon__asset\" src={iconSrc} />\n </span>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"activityIconRegistry.js","names":[],"sources":["../../../components/activity-icon/activityIconRegistry.ts"],"sourcesContent":["export type ActivityIconCategory =\n | 'assessment'\n | 'collaboration'\n | 'communication'\n | 'interactive'\n | 'other'\n | 'resource';\n\nexport interface ActivityIconRegistryEntry {\n fileName: string;\n category: ActivityIconCategory;\n}\n\n// Single source of truth: icon metadata (category + file name). Assets are lazy-loaded on demand.\n// Adding/removing an icon requires updating one entry here.\nconst registryMetadata = {\n assignment: 'assessment',\n quiz: 'assessment',\n workshop: 'assessment',\n database: 'collaboration',\n forum: 'collaboration',\n glossary: 'collaboration',\n wiki: 'collaboration',\n bigbluebutton: 'other',\n chat: 'communication',\n choice: 'communication',\n feedback: 'communication',\n survey: 'communication',\n h5p: 'other',\n 'ims-package': 'interactive',\n lesson: 'interactive',\n 'scorm-package': 'interactive',\n book: 'resource',\n 'external-tool': 'resource',\n file: 'resource',\n folder: 'resource',\n page: 'resource',\n 'text-and-media': 'resource',\n url: 'resource',\n subsection: 'other',\n 'file-ai': 'resource',\n 'file-archive': 'resource',\n 'file-audio': 'resource',\n 'file-code': 'resource',\n 'file-database': 'resource',\n 'file-doc': 'resource',\n 'file-draw': 'resource',\n 'file-eps': 'resource',\n 'file-epub': 'resource',\n 'file-flash': 'resource',\n 'file-folder': 'resource',\n 'file-gif': 'resource',\n 'file-graphic': 'resource',\n 'file-h5p': 'resource',\n 'file-image': 'resource',\n 'file-isf-flowchart': 'resource',\n 'file-json': 'resource',\n 'file-math': 'resource',\n 'file-moodle': 'resource',\n 'file-oth': 'resource',\n 'file-pdf': 'resource',\n 'file-plain-text': 'resource',\n 'file-presentation': 'resource',\n 'file-ppt': 'resource',\n 'file-psd': 'resource',\n 'file-pub': 'resource',\n 'file-source-code': 'resource',\n 'file-spreadsheet': 'resource',\n 'file-text-editor': 'resource',\n 'file-unknown': 'resource',\n 'file-video': 'resource',\n 'file-xls': 'resource',\n} satisfies Record<string, ActivityIconCategory>;\n\nexport const activityIconRegistry: Record<string, ActivityIconRegistryEntry> =\n Object.fromEntries(\n Object.entries(registryMetadata).map(([name, category]) => [\n name,\n {\n fileName: name,\n category,\n },\n ]),\n );\n\nexport type ActivityIconName = keyof typeof activityIconRegistry;\n\nexport const activityIconNames = Object.keys(activityIconRegistry).sort();\n"],"mappings":"AA0EA,IAAa,uBACX,OAAO,YACL,OAAO,QAAQ;CA5DjB,YAAY;CACZ,MAAM;CACN,UAAU;CACV,UAAU;CACV,OAAO;CACP,UAAU;CACV,MAAM;CACN,eAAe;CACf,MAAM;CACN,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,KAAK;CACL,eAAe;CACf,QAAQ;CACR,iBAAiB;CACjB,MAAM;CACN,iBAAiB;CACjB,MAAM;CACN,QAAQ;CACR,MAAM;CACN,kBAAkB;CAClB,KAAK;CACL,YAAY;CACZ,WAAW;CACX,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,iBAAiB;CACjB,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,aAAa;CACb,cAAc;CACd,eAAe;CACf,YAAY;CACZ,gBAAgB;CAChB,YAAY;CACZ,cAAc;CACd,sBAAsB;CACtB,aAAa;CACb,aAAa;CACb,eAAe;CACf,YAAY;CACZ,YAAY;CACZ,mBAAmB;CACnB,qBAAqB;CACrB,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,oBAAoB;CACpB,oBAAoB;CACpB,oBAAoB;CACpB,gBAAgB;CAChB,cAAc;CACd,YAAY;CAKK,CAAiB,CAAC,KAAK,CAAC,MAAM,cAAc,CACzD,MACA;CACE,UAAU;CACV;CACD,CACF,CAAC,CACH;AAIH,IAAa,oBAAoB,OAAO,KAAK,qBAAqB,CAAC,MAAM"}
1
+ {"version":3,"file":"activityIconRegistry.js","names":[],"sources":["../../../components/activity-icon/activityIconRegistry.ts"],"sourcesContent":["export type ActivityIconCategory =\n | 'assessment'\n | 'collaboration'\n | 'communication'\n | 'interactive'\n | 'other'\n | 'resource';\n\nexport interface ActivityIconRegistryEntry {\n fileName: string;\n category: ActivityIconCategory;\n}\n\n// Single source of truth: icon metadata (category + file name). Assets are lazy-loaded on demand.\n// Adding/removing an icon requires updating one entry here.\nconst registryMetadata = {\n assignment: 'assessment',\n quiz: 'assessment',\n workshop: 'assessment',\n database: 'collaboration',\n forum: 'collaboration',\n glossary: 'collaboration',\n wiki: 'collaboration',\n bigbluebutton: 'other',\n chat: 'communication',\n choice: 'communication',\n feedback: 'communication',\n survey: 'communication',\n h5p: 'other',\n 'ims-package': 'interactive',\n lesson: 'interactive',\n 'scorm-package': 'interactive',\n book: 'resource',\n 'external-tool': 'resource',\n file: 'resource',\n folder: 'resource',\n page: 'resource',\n 'text-and-media': 'resource',\n url: 'resource',\n subsection: 'other',\n 'file-ai': 'resource',\n 'file-archive': 'resource',\n 'file-audio': 'resource',\n 'file-code': 'resource',\n 'file-database': 'resource',\n 'file-doc': 'resource',\n 'file-draw': 'resource',\n 'file-eps': 'resource',\n 'file-epub': 'resource',\n 'file-flash': 'resource',\n 'file-folder': 'resource',\n 'file-gif': 'resource',\n 'file-graphic': 'resource',\n 'file-h5p': 'resource',\n 'file-image': 'resource',\n 'file-isf-flowchart': 'resource',\n 'file-json': 'resource',\n 'file-math': 'resource',\n 'file-moodle': 'resource',\n 'file-oth': 'resource',\n 'file-pdf': 'resource',\n 'file-plain-text': 'resource',\n 'file-presentation': 'resource',\n 'file-ppt': 'resource',\n 'file-psd': 'resource',\n 'file-pub': 'resource',\n 'file-source-code': 'resource',\n 'file-spreadsheet': 'resource',\n 'file-text-editor': 'resource',\n 'file-unknown': 'resource',\n 'file-video': 'resource',\n 'file-xls': 'resource',\n} satisfies Record<string, ActivityIconCategory>;\n\nexport const activityIconRegistry: Record<string, ActivityIconRegistryEntry> =\n Object.fromEntries(\n Object.entries(registryMetadata).map(([name, category]) => [\n name,\n {\n fileName: name,\n category,\n },\n ]),\n );\n\nexport type ActivityIconName = keyof typeof activityIconRegistry;\n\nexport const activityIconNames = Object.keys(activityIconRegistry).sort();\n"],"mappings":"AA0EA,IAAa,uBACX,OAAO,YACL,OAAO,QAAQ;CA5DjB,YAAY;CACZ,MAAM;CACN,UAAU;CACV,UAAU;CACV,OAAO;CACP,UAAU;CACV,MAAM;CACN,eAAe;CACf,MAAM;CACN,QAAQ;CACR,UAAU;CACV,QAAQ;CACR,KAAK;CACL,eAAe;CACf,QAAQ;CACR,iBAAiB;CACjB,MAAM;CACN,iBAAiB;CACjB,MAAM;CACN,QAAQ;CACR,MAAM;CACN,kBAAkB;CAClB,KAAK;CACL,YAAY;CACZ,WAAW;CACX,gBAAgB;CAChB,cAAc;CACd,aAAa;CACb,iBAAiB;CACjB,YAAY;CACZ,aAAa;CACb,YAAY;CACZ,aAAa;CACb,cAAc;CACd,eAAe;CACf,YAAY;CACZ,gBAAgB;CAChB,YAAY;CACZ,cAAc;CACd,sBAAsB;CACtB,aAAa;CACb,aAAa;CACb,eAAe;CACf,YAAY;CACZ,YAAY;CACZ,mBAAmB;CACnB,qBAAqB;CACrB,YAAY;CACZ,YAAY;CACZ,YAAY;CACZ,oBAAoB;CACpB,oBAAoB;CACpB,oBAAoB;CACpB,gBAAgB;CAChB,cAAc;CACd,YAAY;AAKK,CAAgB,EAAE,KAAK,CAAC,MAAM,cAAc,CACzD,MACA;CACE,UAAU;CACV;AACF,CACF,CAAC,CACH;AAIF,IAAa,oBAAoB,OAAO,KAAK,oBAAoB,EAAE,KAAK"}
@@ -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,43 @@
1
+ /* empty css */
2
+ import { isValidElement } from "react";
3
+ import { jsxs } from "react/jsx-runtime";
4
+ //#region components/badge/Badge.tsx
5
+ var isIconElement = (el, propName) => {
6
+ return isValidElement(el) && (el.type === "i" || el.type === "svg");
7
+ };
8
+ var allowedVariants = [
9
+ "primary",
10
+ "secondary",
11
+ "success",
12
+ "danger",
13
+ "warning",
14
+ "info"
15
+ ];
16
+ var Badge = ({ label, variant, subtle = false, pill = false, startIcon, endIcon, className, ...props }) => {
17
+ const resolvedVariant = variant && allowedVariants.includes(variant) ? variant : "primary";
18
+ const resolvedStartIcon = isIconElement(startIcon, "startIcon") ? startIcon : null;
19
+ let resolvedEndIcon = isIconElement(endIcon, "endIcon") ? endIcon : null;
20
+ if (resolvedStartIcon && resolvedEndIcon) resolvedEndIcon = null;
21
+ const classes = [
22
+ "mds-badge",
23
+ "badge",
24
+ `mds-badge--${resolvedVariant}`
25
+ ];
26
+ if (resolvedStartIcon || resolvedEndIcon) classes.push("mds-badge--has-icon");
27
+ if (subtle) classes.push("mds-badge--subtle");
28
+ if (pill) classes.push("mds-badge--pill");
29
+ if (className) classes.push(className);
30
+ return /* @__PURE__ */ jsxs("span", {
31
+ className: classes.join(" "),
32
+ ...props,
33
+ children: [
34
+ resolvedStartIcon,
35
+ label,
36
+ resolvedEndIcon
37
+ ]
38
+ });
39
+ };
40
+ //#endregion
41
+ export { Badge };
42
+
43
+ //# sourceMappingURL=Badge2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Badge2.js","names":[],"sources":["../../../components/badge/Badge.tsx"],"sourcesContent":["import type { HTMLAttributes, ReactElement } from 'react';\nimport { isValidElement } from 'react';\nimport './badge.css';\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":";;;;AAeA,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';
@@ -0,0 +1,2 @@
1
+ import { Badge } from "./Badge2.js";
2
+ export { 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?: 'sm' | 'lg';
8
+ size?: ButtonSize;
8
9
  startIcon?: IconElement;
9
10
  endIcon?: IconElement;
10
11
  }
11
- export declare const Button: ({ label, variant, size, startIcon, endIcon, className, type, ...props }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
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 Button = ({ label, variant, size, startIcon, endIcon, className, type = "button", ...props }) => {
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-${variant && allowedVariants.includes(variant) ? variant : "primary"}`
30
+ `btn-${resolvedVariant}`,
31
+ `mds-btn--size-${resolvedSize}`
20
32
  ];
21
- if (size) classes.push(`btn-${size}`);
33
+ if (isIconOnly) classes.push("mds-btn--icon-only");
22
34
  if (className) classes.push(className);
23
- return /* @__PURE__ */ jsxs("button", {
35
+ return /* @__PURE__ */ jsx("button", {
36
+ ref,
24
37
  className: classes.join(" "),
25
38
  type,
26
39
  ...props,
27
- children: [
28
- isIconElement(startIcon, "startIcon") ? startIcon : null,
40
+ children: /* @__PURE__ */ jsxs(Fragment, { children: [
41
+ resolvedStartIcon,
29
42
  label,
30
- isIconElement(endIcon, "endIcon") ? endIcon : null
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?: 'sm' | 'lg';\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 'outline-primary',\n 'outline-secondary',\n 'outline-danger',\n];\n\nexport const Button = ({\n label,\n variant,\n size,\n startIcon,\n endIcon,\n className,\n type = 'button',\n ...props\n}: ButtonProps) => {\n // Warn in development if button has no accessible name\n if (import.meta.env.DEV) {\n const hasLabel = Boolean(label);\n const hasAriaLabel = 'aria-label' in props;\n if (!hasLabel && !hasAriaLabel) {\n console.warn(\n 'Button: label prop or aria-label attribute is required 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 }\n\n const resolvedVariant =\n variant && allowedVariants.includes(variant as ButtonVariant)\n ? variant\n : 'primary';\n\n const classes = ['mds-btn', 'btn', `btn-${resolvedVariant}`];\n if (size) {\n classes.push(`btn-${size}`);\n }\n if (className) {\n classes.push(className);\n }\n\n return (\n <button className={classes.join(' ')} type={type} {...props}>\n {isIconElement(startIcon, 'startIcon') ? startIcon : null}\n {label}\n {isIconElement(endIcon, 'endIcon') ? endIcon : null}\n </button>\n );\n};\n"],"mappings":";;;AAsBA,IAAM,iBAAiB,IAAa,aAAwC;CAK1E,OAJc,eAAe,GAAG,KAAK,GAAG,SAAS,OAAO,GAAG,SAAS;;AAOtE,IAAM,kBAAmC;CACvC;CACA;CACA;CACA;CACA;CACA;CACD;AAED,IAAa,UAAU,EACrB,OACA,SACA,MACA,WACA,SACA,WACA,OAAO,UACP,GAAG,YACc;CAsBjB,MAAM,UAAU;EAAC;EAAW;EAAO,OAJjC,WAAW,gBAAgB,SAAS,QAAyB,GACzD,UACA;EAEsD;CAC5D,IAAI,MACF,QAAQ,KAAK,OAAO,OAAO;CAE7B,IAAI,WACF,QAAQ,KAAK,UAAU;CAGzB,OACE,qBAAC,UAAD;EAAQ,WAAW,QAAQ,KAAK,IAAI;EAAQ;EAAM,GAAI;YAAtD;GACG,cAAc,WAAW,YAAY,GAAG,YAAY;GACpD;GACA,cAAc,SAAS,UAAU,GAAG,UAAU;GACxC"}
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';
@@ -0,0 +1,2 @@
1
+ import { Checkbox } from "./Checkbox.js";
2
+ export { Checkbox };
@@ -1 +1 @@
1
- {"version":3,"file":"CloseButton.js","names":[],"sources":["../../../components/close-button/CloseButton.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes } from 'react';\n\ntype CloseButtonSize = 'sm' | 'md' | 'lg';\n\nexport interface CloseButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'type'\n> {\n /**\n * Accessible name announced by screen readers for the close button control.\n * Must be a translated string provided by the caller.\n */\n 'aria-label': string;\n\n /**\n * Visual size variant for the close icon button.\n * Invalid values fall back to the default `md` size at runtime.\n */\n size?: string;\n}\n\nconst allowedSizes: CloseButtonSize[] = ['sm', 'md', 'lg'];\n\nexport const CloseButton = ({\n 'aria-label': ariaLabel,\n size,\n className,\n ...props\n}: CloseButtonProps) => {\n if (\n import.meta.env.DEV &&\n size &&\n !allowedSizes.includes(size as CloseButtonSize)\n ) {\n console.warn(\n `[MDS CloseButton] Invalid size \"${size}\". Falling back to \"md\". Allowed: ${allowedSizes.join(', ')}`,\n );\n }\n\n const resolvedSize =\n size && allowedSizes.includes(size as CloseButtonSize) ? size : 'md';\n\n const classes = [\n 'mds-close-button',\n 'btn-close',\n `mds-close-button--${resolvedSize}`,\n ];\n if (className) {\n classes.push(className);\n }\n\n return (\n <button\n className={classes.join(' ')}\n aria-label={ariaLabel}\n {...props}\n type=\"button\"\n ></button>\n );\n};\n"],"mappings":";;AAqBA,IAAM,eAAkC;CAAC;CAAM;CAAM;CAAK;AAE1D,IAAa,eAAe,EAC1B,cAAc,WACd,MACA,WACA,GAAG,YACmB;CActB,MAAM,UAAU;EACd;EACA;EACA,qBALA,QAAQ,aAAa,SAAS,KAAwB,GAAG,OAAO;EAMjE;CACD,IAAI,WACF,QAAQ,KAAK,UAAU;CAGzB,OACE,oBAAC,UAAD;EACE,WAAW,QAAQ,KAAK,IAAI;EAC5B,cAAY;EACZ,GAAI;EACJ,MAAK;EACG,CAAA"}
1
+ {"version":3,"file":"CloseButton.js","names":[],"sources":["../../../components/close-button/CloseButton.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes } from 'react';\n\ntype CloseButtonSize = 'sm' | 'md' | 'lg';\n\nexport interface CloseButtonProps extends Omit<\n ButtonHTMLAttributes<HTMLButtonElement>,\n 'type'\n> {\n /**\n * Accessible name announced by screen readers for the close button control.\n * Must be a translated string provided by the caller.\n */\n 'aria-label': string;\n\n /**\n * Visual size variant for the close icon button.\n * Invalid values fall back to the default `md` size at runtime.\n */\n size?: string;\n}\n\nconst allowedSizes: CloseButtonSize[] = ['sm', 'md', 'lg'];\n\nexport const CloseButton = ({\n 'aria-label': ariaLabel,\n size,\n className,\n ...props\n}: CloseButtonProps) => {\n if (\n import.meta.env.DEV &&\n size &&\n !allowedSizes.includes(size as CloseButtonSize)\n ) {\n console.warn(\n `[MDS CloseButton] Invalid size \"${size}\". Falling back to \"md\". Allowed: ${allowedSizes.join(', ')}`,\n );\n }\n\n const resolvedSize =\n size && allowedSizes.includes(size as CloseButtonSize) ? size : 'md';\n\n const classes = [\n 'mds-close-button',\n 'btn-close',\n `mds-close-button--${resolvedSize}`,\n ];\n if (className) {\n classes.push(className);\n }\n\n return (\n <button\n className={classes.join(' ')}\n aria-label={ariaLabel}\n {...props}\n type=\"button\"\n ></button>\n );\n};\n"],"mappings":";;AAqBA,IAAM,eAAkC;CAAC;CAAM;CAAM;AAAI;AAEzD,IAAa,eAAe,EAC1B,cAAc,WACd,MACA,WACA,GAAG,YACmB;CActB,MAAM,UAAU;EACd;EACA;EACA,qBALA,QAAQ,aAAa,SAAS,IAAuB,IAAI,OAAO;CAMlE;CACA,IAAI,WACF,QAAQ,KAAK,SAAS;CAGxB,OACE,oBAAC,UAAD;EACE,WAAW,QAAQ,KAAK,GAAG;EAC3B,cAAY;EACZ,GAAI;EACJ,MAAK;CACE,CAAA;AAEb"}
@@ -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';
@@ -1 +1 @@
1
- {"version":3,"file":"Radio.js","names":[],"sources":["../../../components/radio/Radio.tsx"],"sourcesContent":["import { type InputHTMLAttributes, forwardRef, useId } from 'react';\n\n// Extend InputHTMLAttributes to allow passing any valid input attributes, but also add our custom props like feedback and label.\nexport interface RadioProps 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 *\n * Use cases: hideLabel is appropriate when the visual label would be redundant or visually cluttered,\n * such as in dense tables where the column header acts as the label, or in icon-only UIs. Always ensure\n * an accessible name is provided via aria-label or label prop. */\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 /** Pre-translated error message rendered below the label. Requires invalid={true} and\n * hideLabel={false} to be displayed. Only invalid feedback is supported; is-valid\n * and neutral feedback states are intentionally not implemented. */\n invalidFeedback?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(\n (\n {\n invalidFeedback,\n invalid,\n className,\n label,\n hideLabel = false,\n ...props\n }: RadioProps,\n ref,\n ) => {\n const generatedId = useId();\n const id = props.id ?? generatedId;\n\n // The invalid state on the input (border, aria-invalid) is independent of\n // hideLabel — a label-less input can still be invalid. Only the feedback text\n // requires a visible label for context and is suppressed when hideLabel is true.\n const isInvalid = !!invalid;\n\n if (import.meta.env.DEV) {\n if (hideLabel && !props['aria-label'] && !label) {\n console.warn(\n 'Radio: label prop or aria-label attribute is required for accessibility when hideLabel is true.',\n );\n }\n if (!hideLabel && !label) {\n console.warn(\n 'Radio: label prop is required when hideLabel is false. An empty label creates an inaccessible form control.',\n );\n }\n if (hideLabel && invalidFeedback) {\n console.warn(\n 'Radio: invalidFeedback is ignored when hideLabel is true. Feedback text requires a visible label to provide context.',\n );\n }\n if (!hideLabel && invalidFeedback && !invalid) {\n console.warn(\n 'Radio: invalidFeedback is provided without invalid={true}. Pass invalid={true} to apply invalid styling alongside the feedback text.',\n );\n }\n }\n // Build the class list for the radio wrapper div. mds-form-check is always applied\n // as a stable hook for consumers; form-check and layout classes are added only when\n // the label is visible (Bootstrap label/feedback styling and grid layout).\n const classes = ['mds-form-check'];\n if (!hideLabel) {\n classes.push('form-check');\n }\n if (className) {\n classes.push(className);\n }\n\n // When the label is hidden, derive aria-label from: caller's aria-label → label prop → undefined (warned above).\n const ariaLabel = hideLabel ? (props['aria-label'] ?? label) : undefined;\n\n // Link the input to its feedback text so screen readers announce the error message.\n // The ID is only generated when feedback will actually be rendered.\n const feedbackId =\n invalidFeedback && !hideLabel && invalid ? `${id}-feedback` : undefined;\n\n return (\n <div className={classes.join(' ')}>\n <input\n className={[\n 'mds-form-check-input',\n 'form-check-input',\n isInvalid ? 'is-invalid' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n type=\"radio\"\n ref={ref}\n {...props}\n aria-invalid={isInvalid ? true : undefined}\n aria-label={ariaLabel}\n aria-describedby={feedbackId}\n id={id} // Ensure we use the generated ID if no ID is provided, so the label can be properly associated with the input for accessibility.\n />\n {!hideLabel && (\n <label className=\"mds-form-check-label form-check-label\" htmlFor={id}>\n {label}\n </label>\n )}\n {feedbackId && (\n <div\n id={feedbackId}\n className=\"mds-form-check-feedback invalid-feedback\"\n >\n {invalidFeedback}\n </div>\n )}\n </div>\n );\n },\n);\nRadio.displayName = 'Radio';\n"],"mappings":";;;AAwBA,IAAa,QAAQ,YAEjB,EACE,iBACA,SACA,WACA,OACA,YAAY,OACZ,GAAG,SAEL,QACG;CACH,MAAM,cAAc,OAAO;CAC3B,MAAM,KAAK,MAAM,MAAM;CAKvB,MAAM,YAAY,CAAC,CAAC;CA2BpB,MAAM,UAAU,CAAC,iBAAiB;CAClC,IAAI,CAAC,WACH,QAAQ,KAAK,aAAa;CAE5B,IAAI,WACF,QAAQ,KAAK,UAAU;CAIzB,MAAM,YAAY,YAAa,MAAM,iBAAiB,QAAS,KAAA;CAI/D,MAAM,aACJ,mBAAmB,CAAC,aAAa,UAAU,GAAG,GAAG,aAAa,KAAA;CAEhE,OACE,qBAAC,OAAD;EAAK,WAAW,QAAQ,KAAK,IAAI;YAAjC;GACE,oBAAC,SAAD;IACE,WAAW;KACT;KACA;KACA,YAAY,eAAe;KAC5B,CACE,OAAO,QAAQ,CACf,KAAK,IAAI;IACZ,MAAK;IACA;IACL,GAAI;IACJ,gBAAc,YAAY,OAAO,KAAA;IACjC,cAAY;IACZ,oBAAkB;IACd;IACJ,CAAA;GACD,CAAC,aACA,oBAAC,SAAD;IAAO,WAAU;IAAwC,SAAS;cAC/D;IACK,CAAA;GAET,cACC,oBAAC,OAAD;IACE,IAAI;IACJ,WAAU;cAET;IACG,CAAA;GAEJ;;EAGX;AACD,MAAM,cAAc"}
1
+ {"version":3,"file":"Radio.js","names":[],"sources":["../../../components/radio/Radio.tsx"],"sourcesContent":["import { type InputHTMLAttributes, forwardRef, useId } from 'react';\n\n// Extend InputHTMLAttributes to allow passing any valid input attributes, but also add our custom props like feedback and label.\nexport interface RadioProps 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 *\n * Use cases: hideLabel is appropriate when the visual label would be redundant or visually cluttered,\n * such as in dense tables where the column header acts as the label, or in icon-only UIs. Always ensure\n * an accessible name is provided via aria-label or label prop. */\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 /** Pre-translated error message rendered below the label. Requires invalid={true} and\n * hideLabel={false} to be displayed. Only invalid feedback is supported; is-valid\n * and neutral feedback states are intentionally not implemented. */\n invalidFeedback?: string;\n}\n\nexport const Radio = forwardRef<HTMLInputElement, RadioProps>(\n (\n {\n invalidFeedback,\n invalid,\n className,\n label,\n hideLabel = false,\n ...props\n }: RadioProps,\n ref,\n ) => {\n const generatedId = useId();\n const id = props.id ?? generatedId;\n\n // The invalid state on the input (border, aria-invalid) is independent of\n // hideLabel — a label-less input can still be invalid. Only the feedback text\n // requires a visible label for context and is suppressed when hideLabel is true.\n const isInvalid = !!invalid;\n\n if (import.meta.env.DEV) {\n if (hideLabel && !props['aria-label'] && !label) {\n console.warn(\n 'Radio: label prop or aria-label attribute is required for accessibility when hideLabel is true.',\n );\n }\n if (!hideLabel && !label) {\n console.warn(\n 'Radio: label prop is required when hideLabel is false. An empty label creates an inaccessible form control.',\n );\n }\n if (hideLabel && invalidFeedback) {\n console.warn(\n 'Radio: invalidFeedback is ignored when hideLabel is true. Feedback text requires a visible label to provide context.',\n );\n }\n if (!hideLabel && invalidFeedback && !invalid) {\n console.warn(\n 'Radio: invalidFeedback is provided without invalid={true}. Pass invalid={true} to apply invalid styling alongside the feedback text.',\n );\n }\n }\n // Build the class list for the radio wrapper div. mds-form-check is always applied\n // as a stable hook for consumers; form-check and layout classes are added only when\n // the label is visible (Bootstrap label/feedback styling and grid layout).\n const classes = ['mds-form-check'];\n if (!hideLabel) {\n classes.push('form-check');\n }\n if (className) {\n classes.push(className);\n }\n\n // When the label is hidden, derive aria-label from: caller's aria-label → label prop → undefined (warned above).\n const ariaLabel = hideLabel ? (props['aria-label'] ?? label) : undefined;\n\n // Link the input to its feedback text so screen readers announce the error message.\n // The ID is only generated when feedback will actually be rendered.\n const feedbackId =\n invalidFeedback && !hideLabel && invalid ? `${id}-feedback` : undefined;\n\n return (\n <div className={classes.join(' ')}>\n <input\n className={[\n 'mds-form-check-input',\n 'form-check-input',\n isInvalid ? 'is-invalid' : '',\n ]\n .filter(Boolean)\n .join(' ')}\n type=\"radio\"\n ref={ref}\n {...props}\n aria-invalid={isInvalid ? true : undefined}\n aria-label={ariaLabel}\n aria-describedby={feedbackId}\n id={id} // Ensure we use the generated ID if no ID is provided, so the label can be properly associated with the input for accessibility.\n />\n {!hideLabel && (\n <label className=\"mds-form-check-label form-check-label\" htmlFor={id}>\n {label}\n </label>\n )}\n {feedbackId && (\n <div\n id={feedbackId}\n className=\"mds-form-check-feedback invalid-feedback\"\n >\n {invalidFeedback}\n </div>\n )}\n </div>\n );\n },\n);\nRadio.displayName = 'Radio';\n"],"mappings":";;;AAwBA,IAAa,QAAQ,YAEjB,EACE,iBACA,SACA,WACA,OACA,YAAY,OACZ,GAAG,SAEL,QACG;CACH,MAAM,cAAc,MAAM;CAC1B,MAAM,KAAK,MAAM,MAAM;CAKvB,MAAM,YAAY,CAAC,CAAC;CA2BpB,MAAM,UAAU,CAAC,gBAAgB;CACjC,IAAI,CAAC,WACH,QAAQ,KAAK,YAAY;CAE3B,IAAI,WACF,QAAQ,KAAK,SAAS;CAIxB,MAAM,YAAY,YAAa,MAAM,iBAAiB,QAAS,KAAA;CAI/D,MAAM,aACJ,mBAAmB,CAAC,aAAa,UAAU,GAAG,GAAG,aAAa,KAAA;CAEhE,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,MAAK;IACA;IACL,GAAI;IACJ,gBAAc,YAAY,OAAO,KAAA;IACjC,cAAY;IACZ,oBAAkB;IACd;GACL,CAAA;GACA,CAAC,aACA,oBAAC,SAAD;IAAO,WAAU;IAAwC,SAAS;cAC/D;GACI,CAAA;GAER,cACC,oBAAC,OAAD;IACE,IAAI;IACJ,WAAU;cAET;GACE,CAAA;EAEJ;;AAET,CACF;AACA,MAAM,cAAc"}