@moodlehq/design-system 3.1.0 → 3.2.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":";;;;AAQA,IAAM,WAAW,uBAAA,OAAA;CAAA,iCAAA,OAAA;CAAA,oCAAA,OAAA;CAAA,2BAAA,OAAA;CAAA,2BAAA,OAAA;CAAA,6BAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,oCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,8BAAA,OAAA;CAAA,mCAAA,OAAA;CAAA,iCAAA,OAAA;CAAA,gCAAA,OAAA;CAAA,oCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,gCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,gCAAA,OAAA;CAAA,iCAAA,OAAA;CAAA,kCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,mCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,iCAAA,OAAA;CAAA,yCAAA,OAAA;CAAA,gCAAA,OAAA;CAAA,gCAAA,OAAA;CAAA,kCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,sCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,wCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,uCAAA,OAAA;CAAA,uCAAA,OAAA;CAAA,uCAAA,OAAA;CAAA,mCAAA,OAAA;CAAA,iCAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,2BAAA,OAAA;CAAA,6BAAA,OAAA;CAAA,4BAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,0BAAA,OAAA;CAAA,kCAAA,OAAA;CAAA,6BAAA,OAAA;CAAA,2BAAA,OAAA;CAAA,2BAAA,OAAA;CAAA,oCAAA,OAAA;CAAA,iCAAA,OAAA;CAAA,6BAAA,OAAA;CAAA,qCAAA,OAAA;CAAA,0BAAA,OAAA;CAAA,2BAAA,OAAA;CAAA,+BAAA,OAAA;CAAA,CAEf;AAEF,SAAS,YAAY,UAAmC;CAEtD,MAAM,SAAS,SAAS,YADK,SAAS;CAGtC,IAAI,CAAC,QACH,OAAO,QAAQ,uBACb,IAAI,MACF,iCAAiC,SAAS,4BAC3C,CACF;CAGH,OAAO,QAAQ,CAAC,MAAM,QAAQ,IAAI,QAAQ;;AAI5C,IAAM,kBAAyC;CAAC;CAAQ;CAAW;CAAQ;AAG3E,IAAM,eAAmC;CAAC;CAAM;CAAM;CAAM;CAAK;AACjE,IAAM,qBAAuC;AAqB7C,IAAa,gBAAgB,EAC3B,MACA,MAAM,IACN,SACA,MACA,WACA,GAAG,YACoB;CAEvB,MAAM,iBAAiB,MAAM,aAAa;CAC1C,MAAM,eACJ,mBAAmB,KAAA,KAAa,kBAAkB;CAEpD,IAAI,CAAC,cAAc;EACjB,MAAM,qBAAqB,oCAAoC,KAAK,cAAc,kBAAkB,KAAK,KAAK;EAC9G,QAAQ,MACN,GAAG,mBAAmB,qBAAqB,mBAAmB,gBAC/D;;CAeH,MAAM,eAAiC,eAClC,iBACD;CACJ,MAAM,kBACJ,WAAW,gBAAgB,SAAS,QAAQ,GAAG,UAAU;CAC3D,MAAM,eAAe,QAAQ,aAAa,SAAS,KAAK,GAAG,OAAO;CAClE,MAAM,mBAAmB,qBAAqB,cAAc;CAC5D,MAAM,CAAC,SAAS,cAAc,SAA6B,KAAA,EAAU;CAErE,gBAAgB;EACd,IAAI,YAAY;EAChB,MAAM,EAAE,aAAa,qBAAqB;EAE1C,YAAiB,SAAS,CACvB,MAAM,QAAQ;GACb,IAAI,WACF,WAAW,IAAI;IAEjB,CACD,YAAY;GACX,IAAI,WACF,WAAW,KAAA,EAAU;IAEvB;EAEJ,aAAa;GACX,YAAY;;IAEb,CAAC,aAAa,CAAC;CAElB,MAAM,UAAU;EACd;EACA,sBAAsB;EACtB,2BAA2B;EAC3B,+BAA+B;EAChC;CACD,IAAI,WACF,QAAQ,KAAK,UAAU;CAGzB,OACE,oBAAC,QAAD;EAAM,WAAW,QAAQ,KAAK,IAAI;EAAE,GAAI;YACtC,oBAAC,OAAD;GAAU;GAAK,WAAU;GAA2B,KAAK;GAAW,CAAA;EAC/D,CAAA"}
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"}
@@ -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 { 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,EAAE,MAAM,GAAG,SAAS,OAAO,GAAG,SAAS;AAKtE;AAEA,IAAM,kBAAmC;CACvC;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,IAAa,UAAU,EACrB,OACA,SACA,MACA,WACA,SACA,WACA,OAAO,UACP,GAAG,YACc;CAsBjB,MAAM,UAAU;EAAC;EAAW;EAAO,OAJjC,WAAW,gBAAgB,SAAS,OAAwB,IACxD,UACA;CAEqD;CAC3D,IAAI,MACF,QAAQ,KAAK,OAAO,MAAM;CAE5B,IAAI,WACF,QAAQ,KAAK,SAAS;CAGxB,OACE,qBAAC,UAAD;EAAQ,WAAW,QAAQ,KAAK,GAAG;EAAS;EAAM,GAAI;YAAtD;GACG,cAAc,WAAW,WAAW,IAAI,YAAY;GACpD;GACA,cAAc,SAAS,SAAS,IAAI,UAAU;EACzC;;AAEZ"}
@@ -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 +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"}
package/dist/index.css CHANGED
@@ -545,23 +545,30 @@
545
545
  --mds-activity-icon-other-icon: var(--mds-color-gray-900);
546
546
  --mds-activity-icon-resource-bg: var(--mds-color-cyan-100);
547
547
  --mds-activity-icon-resource-icon: var(--mds-color-cyan-500);
548
+ --mds-bg-feedback-danger-light: var(--mds-color-red-50);
548
549
  --mds-bg-feedback-danger-subtle: var(--mds-color-red-100);
549
550
  --mds-bg-feedback-info-default: var(--mds-color-cyan-500);
551
+ --mds-bg-feedback-info-light: var(--mds-color-cyan-50);
550
552
  --mds-bg-feedback-info-subtle: var(--mds-color-cyan-100);
551
553
  --mds-bg-feedback-primary-default: var(--mds-color-blue-500);
554
+ --mds-bg-feedback-primary-light: var(--mds-color-blue-50);
552
555
  --mds-bg-feedback-primary-subtle: var(--mds-color-blue-100);
553
556
  --mds-bg-feedback-secondary-default: var(--mds-color-gray-400);
554
557
  --mds-bg-feedback-secondary-subtle: var(--mds-color-gray-100);
555
558
  --mds-bg-feedback-success-default: var(--mds-color-green-500);
559
+ --mds-bg-feedback-success-light: var(--mds-color-green-50);
556
560
  --mds-bg-feedback-success-subtle: var(--mds-color-green-100);
557
561
  --mds-bg-feedback-warning-default: var(--mds-color-yellow-500);
562
+ --mds-bg-feedback-warning-light: var(--mds-color-yellow-50);
558
563
  --mds-bg-feedback-warning-subtle: var(--mds-color-yellow-100);
559
564
  --mds-bg-interactive-danger-active: var(--mds-color-red-700);
560
565
  --mds-bg-interactive-danger-default: var(--mds-color-red-500);
566
+ --mds-bg-interactive-danger-default-light: var(--mds-color-red-50);
561
567
  --mds-bg-interactive-danger-disabled: var(--mds-color-red-200);
562
568
  --mds-bg-interactive-danger-hover: var(--mds-color-red-600);
563
569
  --mds-bg-interactive-primary-active: var(--mds-color-blue-700);
564
570
  --mds-bg-interactive-primary-default: var(--mds-color-blue-500);
571
+ --mds-bg-interactive-primary-default-light: var(--mds-color-blue-50);
565
572
  --mds-bg-interactive-primary-disabled: var(--mds-color-blue-200);
566
573
  --mds-bg-interactive-primary-hover: var(--mds-color-blue-600);
567
574
  --mds-bg-interactive-secondary-default: var(--mds-color-gray-400);
@@ -617,6 +624,7 @@
617
624
  */
618
625
 
619
626
  :root {
627
+ --mds-color-blue-50: #e7f0f9;
620
628
  --mds-color-blue-100: #cfe2f2;
621
629
  --mds-color-blue-200: #9fc4e5;
622
630
  --mds-color-blue-300: #6fa7d9;
@@ -626,6 +634,7 @@
626
634
  --mds-color-blue-700: #094173;
627
635
  --mds-color-blue-800: #062b4c;
628
636
  --mds-color-blue-900: #031626;
637
+ --mds-color-cyan-50: #e5f2f4;
629
638
  --mds-color-cyan-100: #cce6ea;
630
639
  --mds-color-cyan-200: #99cdd5;
631
640
  --mds-color-cyan-300: #66b3c0;
@@ -646,6 +655,7 @@
646
655
  --mds-color-gray-900: #1d2125;
647
656
  --mds-color-gray-black: #000000;
648
657
  --mds-color-gray-white: #ffffff;
658
+ --mds-color-green-50: #ebf2ea;
649
659
  --mds-color-green-100: #d7e4d6;
650
660
  --mds-color-green-200: #aecaad;
651
661
  --mds-color-green-300: #86af84;
@@ -691,6 +701,7 @@
691
701
  --mds-color-purple-700: #3a254a;
692
702
  --mds-color-purple-800: #271832;
693
703
  --mds-color-purple-900: #130c19;
704
+ --mds-color-red-50: #faeae9;
694
705
  --mds-color-red-100: #f4d6d2;
695
706
  --mds-color-red-200: #eaada6;
696
707
  --mds-color-red-300: #df8379;
@@ -709,6 +720,7 @@
709
720
  --mds-color-teal-700: #13795b;
710
721
  --mds-color-teal-800: #0d503c;
711
722
  --mds-color-teal-900: #06281e;
723
+ --mds-color-yellow-50: #fdf7ed;
712
724
  --mds-color-yellow-100: #fcefdc;
713
725
  --mds-color-yellow-200: #f9deb8;
714
726
  --mds-color-yellow-300: #f6ce95;
@@ -773,6 +785,7 @@
773
785
  --mds-typography-line-height-paragraph-base: 1.5rem;
774
786
  --mds-typography-line-height-paragraph-lg: 2.5rem;
775
787
  --mds-typography-line-height-paragraph-sm: 1.0875rem;
788
+ --mds-typography-line-height-paragraph-xs: 0.75rem;
776
789
  }
777
790
 
778
791
  /**
@@ -865,6 +878,7 @@
865
878
  --mds-line-height-paragraph-default: var(--mds-typography-line-height-paragraph-base); /** Comfortable line height for paragraphs. */
866
879
  --mds-line-height-paragraph-lead: var(--mds-typography-line-height-paragraph-lg); /** Extended line height for large body text. */
867
880
  --mds-line-height-paragraph-small: var(--mds-typography-line-height-paragraph-sm); /** Compact line height for small text. */
881
+ --mds-line-height-paragraph-xs: var(--mds-typography-line-height-paragraph-xs);
868
882
  --mds-margin-bottom-display: var(--mds-spacing-none); /** Default margin below a Displays. */
869
883
  --mds-margin-bottom-heading: var(--mds-spacing-xs); /** Default margin below a Headings. */
870
884
  --mds-margin-bottom-paragraph: var(--mds-spacing-md); /** Default margin below a paragraph. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moodlehq/design-system",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "The Moodle Design System",
5
5
  "files": [
6
6
  "/dist",
@@ -45,7 +45,7 @@
45
45
  "devDependencies": {
46
46
  "@chromatic-com/storybook": "^5.0.0",
47
47
  "@modelcontextprotocol/sdk": "^1.0.0",
48
- "@commitlint/cli": "^20.1.0",
48
+ "@commitlint/cli": "^21.0.1",
49
49
  "@commitlint/config-conventional": "^20.0.0",
50
50
  "@eslint/compat": "^2.0.0",
51
51
  "@eslint/eslintrc": "^3.3.3",
@@ -82,7 +82,7 @@
82
82
  "globals": "^17.1.0",
83
83
  "husky": "^9.1.7",
84
84
  "jsdom": "^29.0.0",
85
- "lint-staged": "^16.2.7",
85
+ "lint-staged": "^17.0.5",
86
86
  "playwright": "^1.57.0",
87
87
  "prettier": "^3.7.3",
88
88
  "prettier-plugin-organize-imports": "^4.3.0",
@@ -18,23 +18,30 @@
18
18
  --mds-activity-icon-other-icon: var(--mds-color-gray-900);
19
19
  --mds-activity-icon-resource-bg: var(--mds-color-cyan-100);
20
20
  --mds-activity-icon-resource-icon: var(--mds-color-cyan-500);
21
+ --mds-bg-feedback-danger-light: var(--mds-color-red-50);
21
22
  --mds-bg-feedback-danger-subtle: var(--mds-color-red-100);
22
23
  --mds-bg-feedback-info-default: var(--mds-color-cyan-500);
24
+ --mds-bg-feedback-info-light: var(--mds-color-cyan-50);
23
25
  --mds-bg-feedback-info-subtle: var(--mds-color-cyan-100);
24
26
  --mds-bg-feedback-primary-default: var(--mds-color-blue-500);
27
+ --mds-bg-feedback-primary-light: var(--mds-color-blue-50);
25
28
  --mds-bg-feedback-primary-subtle: var(--mds-color-blue-100);
26
29
  --mds-bg-feedback-secondary-default: var(--mds-color-gray-400);
27
30
  --mds-bg-feedback-secondary-subtle: var(--mds-color-gray-100);
28
31
  --mds-bg-feedback-success-default: var(--mds-color-green-500);
32
+ --mds-bg-feedback-success-light: var(--mds-color-green-50);
29
33
  --mds-bg-feedback-success-subtle: var(--mds-color-green-100);
30
34
  --mds-bg-feedback-warning-default: var(--mds-color-yellow-500);
35
+ --mds-bg-feedback-warning-light: var(--mds-color-yellow-50);
31
36
  --mds-bg-feedback-warning-subtle: var(--mds-color-yellow-100);
32
37
  --mds-bg-interactive-danger-active: var(--mds-color-red-700);
33
38
  --mds-bg-interactive-danger-default: var(--mds-color-red-500);
39
+ --mds-bg-interactive-danger-default-light: var(--mds-color-red-50);
34
40
  --mds-bg-interactive-danger-disabled: var(--mds-color-red-200);
35
41
  --mds-bg-interactive-danger-hover: var(--mds-color-red-600);
36
42
  --mds-bg-interactive-primary-active: var(--mds-color-blue-700);
37
43
  --mds-bg-interactive-primary-default: var(--mds-color-blue-500);
44
+ --mds-bg-interactive-primary-default-light: var(--mds-color-blue-50);
38
45
  --mds-bg-interactive-primary-disabled: var(--mds-color-blue-200);
39
46
  --mds-bg-interactive-primary-hover: var(--mds-color-blue-600);
40
47
  --mds-bg-interactive-secondary-default: var(--mds-color-gray-400);
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  :root {
6
+ --mds-color-blue-50: #e7f0f9;
6
7
  --mds-color-blue-100: #cfe2f2;
7
8
  --mds-color-blue-200: #9fc4e5;
8
9
  --mds-color-blue-300: #6fa7d9;
@@ -12,6 +13,7 @@
12
13
  --mds-color-blue-700: #094173;
13
14
  --mds-color-blue-800: #062b4c;
14
15
  --mds-color-blue-900: #031626;
16
+ --mds-color-cyan-50: #e5f2f4;
15
17
  --mds-color-cyan-100: #cce6ea;
16
18
  --mds-color-cyan-200: #99cdd5;
17
19
  --mds-color-cyan-300: #66b3c0;
@@ -32,6 +34,7 @@
32
34
  --mds-color-gray-900: #1d2125;
33
35
  --mds-color-gray-black: #000000;
34
36
  --mds-color-gray-white: #ffffff;
37
+ --mds-color-green-50: #ebf2ea;
35
38
  --mds-color-green-100: #d7e4d6;
36
39
  --mds-color-green-200: #aecaad;
37
40
  --mds-color-green-300: #86af84;
@@ -77,6 +80,7 @@
77
80
  --mds-color-purple-700: #3a254a;
78
81
  --mds-color-purple-800: #271832;
79
82
  --mds-color-purple-900: #130c19;
83
+ --mds-color-red-50: #faeae9;
80
84
  --mds-color-red-100: #f4d6d2;
81
85
  --mds-color-red-200: #eaada6;
82
86
  --mds-color-red-300: #df8379;
@@ -95,6 +99,7 @@
95
99
  --mds-color-teal-700: #13795b;
96
100
  --mds-color-teal-800: #0d503c;
97
101
  --mds-color-teal-900: #06281e;
102
+ --mds-color-yellow-50: #fdf7ed;
98
103
  --mds-color-yellow-100: #fcefdc;
99
104
  --mds-color-yellow-200: #f9deb8;
100
105
  --mds-color-yellow-300: #f6ce95;
@@ -159,4 +164,5 @@
159
164
  --mds-typography-line-height-paragraph-base: 1.5rem;
160
165
  --mds-typography-line-height-paragraph-lg: 2.5rem;
161
166
  --mds-typography-line-height-paragraph-sm: 1.0875rem;
167
+ --mds-typography-line-height-paragraph-xs: 0.75rem;
162
168
  }
@@ -41,6 +41,7 @@
41
41
  --mds-line-height-paragraph-default: var(--mds-typography-line-height-paragraph-base); /** Comfortable line height for paragraphs. */
42
42
  --mds-line-height-paragraph-lead: var(--mds-typography-line-height-paragraph-lg); /** Extended line height for large body text. */
43
43
  --mds-line-height-paragraph-small: var(--mds-typography-line-height-paragraph-sm); /** Compact line height for small text. */
44
+ --mds-line-height-paragraph-xs: var(--mds-typography-line-height-paragraph-xs);
44
45
  --mds-margin-bottom-display: var(--mds-spacing-none); /** Default margin below a Displays. */
45
46
  --mds-margin-bottom-heading: var(--mds-spacing-xs); /** Default margin below a Headings. */
46
47
  --mds-margin-bottom-paragraph: var(--mds-spacing-md); /** Default margin below a paragraph. */
@@ -16,23 +16,30 @@ $mds-activity-icon-other-icon: #1d2125 !default;
16
16
  $mds-activity-icon-resource-bg: #cce6ea !default;
17
17
  $mds-activity-icon-resource-icon: #008196 !default;
18
18
  $mds-bg-feedback-danger-default: #ca3120 !default;
19
+ $mds-bg-feedback-danger-light: #faeae9 !default;
19
20
  $mds-bg-feedback-danger-subtle: #f4d6d2 !default;
20
21
  $mds-bg-feedback-info-default: #008196 !default;
22
+ $mds-bg-feedback-info-light: #e5f2f4 !default;
21
23
  $mds-bg-feedback-info-subtle: #cce6ea !default;
22
24
  $mds-bg-feedback-primary-default: #0f6cbf !default;
25
+ $mds-bg-feedback-primary-light: #e7f0f9 !default;
23
26
  $mds-bg-feedback-primary-subtle: #cfe2f2 !default;
24
27
  $mds-bg-feedback-secondary-default: #ced4da !default;
25
28
  $mds-bg-feedback-secondary-subtle: #f8f9fa !default;
26
29
  $mds-bg-feedback-success-default: #357a32 !default;
30
+ $mds-bg-feedback-success-light: #ebf2ea !default;
27
31
  $mds-bg-feedback-success-subtle: #d7e4d6 !default;
28
32
  $mds-bg-feedback-warning-default: #f0ad4e !default;
33
+ $mds-bg-feedback-warning-light: #fdf7ed !default;
29
34
  $mds-bg-feedback-warning-subtle: #fcefdc !default;
30
35
  $mds-bg-interactive-danger-active: #791d13 !default;
31
36
  $mds-bg-interactive-danger-default: #ca3120 !default;
37
+ $mds-bg-interactive-danger-default-light: #faeae9 !default;
32
38
  $mds-bg-interactive-danger-disabled: #eaada6 !default;
33
39
  $mds-bg-interactive-danger-hover: #a2271a !default;
34
40
  $mds-bg-interactive-primary-active: #094173 !default;
35
41
  $mds-bg-interactive-primary-default: #0f6cbf !default;
42
+ $mds-bg-interactive-primary-default-light: #e7f0f9 !default;
36
43
  $mds-bg-interactive-primary-disabled: #9fc4e5 !default;
37
44
  $mds-bg-interactive-primary-hover: #0c5699 !default;
38
45
  $mds-bg-interactive-secondary-active: #ced4da !default;
@@ -1,6 +1,7 @@
1
1
 
2
2
  // THIS FILE IS AUTO-GENERATED BY STYLE DICTIONARY — DO NOT EDIT DIRECTLY.
3
3
 
4
+ $mds-color-blue-50: #e7f0f9 !default;
4
5
  $mds-color-blue-100: #cfe2f2 !default;
5
6
  $mds-color-blue-200: #9fc4e5 !default;
6
7
  $mds-color-blue-300: #6fa7d9 !default;
@@ -10,6 +11,7 @@ $mds-color-blue-600: #0c5699 !default;
10
11
  $mds-color-blue-700: #094173 !default;
11
12
  $mds-color-blue-800: #062b4c !default;
12
13
  $mds-color-blue-900: #031626 !default;
14
+ $mds-color-cyan-50: #e5f2f4 !default;
13
15
  $mds-color-cyan-100: #cce6ea !default;
14
16
  $mds-color-cyan-200: #99cdd5 !default;
15
17
  $mds-color-cyan-300: #66b3c0 !default;
@@ -30,6 +32,7 @@ $mds-color-gray-800: #343a40 !default;
30
32
  $mds-color-gray-900: #1d2125 !default;
31
33
  $mds-color-gray-black: #000000 !default;
32
34
  $mds-color-gray-white: #ffffff !default;
35
+ $mds-color-green-50: #ebf2ea !default;
33
36
  $mds-color-green-100: #d7e4d6 !default;
34
37
  $mds-color-green-200: #aecaad !default;
35
38
  $mds-color-green-300: #86af84 !default;
@@ -75,6 +78,7 @@ $mds-color-purple-600: #4e3163 !default;
75
78
  $mds-color-purple-700: #3a254a !default;
76
79
  $mds-color-purple-800: #271832 !default;
77
80
  $mds-color-purple-900: #130c19 !default;
81
+ $mds-color-red-50: #faeae9 !default;
78
82
  $mds-color-red-100: #f4d6d2 !default;
79
83
  $mds-color-red-200: #eaada6 !default;
80
84
  $mds-color-red-300: #df8379 !default;
@@ -93,6 +97,7 @@ $mds-color-teal-600: #1aa179 !default;
93
97
  $mds-color-teal-700: #13795b !default;
94
98
  $mds-color-teal-800: #0d503c !default;
95
99
  $mds-color-teal-900: #06281e !default;
100
+ $mds-color-yellow-50: #fdf7ed !default;
96
101
  $mds-color-yellow-100: #fcefdc !default;
97
102
  $mds-color-yellow-200: #f9deb8 !default;
98
103
  $mds-color-yellow-300: #f6ce95 !default;
@@ -157,3 +162,4 @@ $mds-typography-line-height-headings-h6: 1.2rem !default;
157
162
  $mds-typography-line-height-paragraph-base: 1.5rem !default;
158
163
  $mds-typography-line-height-paragraph-lg: 2.5rem !default;
159
164
  $mds-typography-line-height-paragraph-sm: 1.0875rem !default;
165
+ $mds-typography-line-height-paragraph-xs: 0.75rem !default;
@@ -39,6 +39,7 @@ $mds-line-height-headings-6: 1.2rem !default; // Tight line height for headings.
39
39
  $mds-line-height-paragraph-default: 1.5rem !default; // Comfortable line height for paragraphs.
40
40
  $mds-line-height-paragraph-lead: 2.5rem !default; // Extended line height for large body text.
41
41
  $mds-line-height-paragraph-small: 1.0875rem !default; // Compact line height for small text.
42
+ $mds-line-height-paragraph-xs: 0.75rem !default;
42
43
  $mds-margin-bottom-display: 0rem !default; // Default margin below a Displays.
43
44
  $mds-margin-bottom-heading: 0.5rem !default; // Default margin below a Headings.
44
45
  $mds-margin-bottom-paragraph: 1rem !default; // Default margin below a paragraph.