@moodlehq/design-system 3.0.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.
Files changed (134) hide show
  1. package/README.md +1 -1
  2. package/dist/components/activity-icon/ActivityIcon.d.ts +23 -0
  3. package/dist/components/activity-icon/ActivityIcon.js +124 -0
  4. package/dist/components/activity-icon/ActivityIcon.js.map +1 -0
  5. package/dist/components/activity-icon/activityIconRegistry.d.ts +8 -0
  6. package/dist/components/activity-icon/activityIconRegistry.js +66 -0
  7. package/dist/components/activity-icon/activityIconRegistry.js.map +1 -0
  8. package/dist/components/activity-icon/assets/assignment.js +6 -0
  9. package/dist/components/activity-icon/assets/assignment.js.map +1 -0
  10. package/dist/components/activity-icon/assets/bigbluebutton.js +6 -0
  11. package/dist/components/activity-icon/assets/bigbluebutton.js.map +1 -0
  12. package/dist/components/activity-icon/assets/book.js +6 -0
  13. package/dist/components/activity-icon/assets/book.js.map +1 -0
  14. package/dist/components/activity-icon/assets/chat.js +6 -0
  15. package/dist/components/activity-icon/assets/chat.js.map +1 -0
  16. package/dist/components/activity-icon/assets/choice.js +6 -0
  17. package/dist/components/activity-icon/assets/choice.js.map +1 -0
  18. package/dist/components/activity-icon/assets/database.js +6 -0
  19. package/dist/components/activity-icon/assets/database.js.map +1 -0
  20. package/dist/components/activity-icon/assets/external-tool.js +6 -0
  21. package/dist/components/activity-icon/assets/external-tool.js.map +1 -0
  22. package/dist/components/activity-icon/assets/feedback.js +6 -0
  23. package/dist/components/activity-icon/assets/feedback.js.map +1 -0
  24. package/dist/components/activity-icon/assets/file-ai.js +6 -0
  25. package/dist/components/activity-icon/assets/file-ai.js.map +1 -0
  26. package/dist/components/activity-icon/assets/file-archive.js +6 -0
  27. package/dist/components/activity-icon/assets/file-archive.js.map +1 -0
  28. package/dist/components/activity-icon/assets/file-audio.js +6 -0
  29. package/dist/components/activity-icon/assets/file-audio.js.map +1 -0
  30. package/dist/components/activity-icon/assets/file-code.js +6 -0
  31. package/dist/components/activity-icon/assets/file-code.js.map +1 -0
  32. package/dist/components/activity-icon/assets/file-database.js +6 -0
  33. package/dist/components/activity-icon/assets/file-database.js.map +1 -0
  34. package/dist/components/activity-icon/assets/file-doc.js +6 -0
  35. package/dist/components/activity-icon/assets/file-doc.js.map +1 -0
  36. package/dist/components/activity-icon/assets/file-draw.js +6 -0
  37. package/dist/components/activity-icon/assets/file-draw.js.map +1 -0
  38. package/dist/components/activity-icon/assets/file-eps.js +6 -0
  39. package/dist/components/activity-icon/assets/file-eps.js.map +1 -0
  40. package/dist/components/activity-icon/assets/file-epub.js +6 -0
  41. package/dist/components/activity-icon/assets/file-epub.js.map +1 -0
  42. package/dist/components/activity-icon/assets/file-flash.js +6 -0
  43. package/dist/components/activity-icon/assets/file-flash.js.map +1 -0
  44. package/dist/components/activity-icon/assets/file-folder.js +6 -0
  45. package/dist/components/activity-icon/assets/file-folder.js.map +1 -0
  46. package/dist/components/activity-icon/assets/file-gif.js +6 -0
  47. package/dist/components/activity-icon/assets/file-gif.js.map +1 -0
  48. package/dist/components/activity-icon/assets/file-graphic.js +6 -0
  49. package/dist/components/activity-icon/assets/file-graphic.js.map +1 -0
  50. package/dist/components/activity-icon/assets/file-h5p.js +6 -0
  51. package/dist/components/activity-icon/assets/file-h5p.js.map +1 -0
  52. package/dist/components/activity-icon/assets/file-image.js +6 -0
  53. package/dist/components/activity-icon/assets/file-image.js.map +1 -0
  54. package/dist/components/activity-icon/assets/file-isf-flowchart.js +6 -0
  55. package/dist/components/activity-icon/assets/file-isf-flowchart.js.map +1 -0
  56. package/dist/components/activity-icon/assets/file-json.js +6 -0
  57. package/dist/components/activity-icon/assets/file-json.js.map +1 -0
  58. package/dist/components/activity-icon/assets/file-math.js +6 -0
  59. package/dist/components/activity-icon/assets/file-math.js.map +1 -0
  60. package/dist/components/activity-icon/assets/file-moodle.js +6 -0
  61. package/dist/components/activity-icon/assets/file-moodle.js.map +1 -0
  62. package/dist/components/activity-icon/assets/file-oth.js +6 -0
  63. package/dist/components/activity-icon/assets/file-oth.js.map +1 -0
  64. package/dist/components/activity-icon/assets/file-pdf.js +6 -0
  65. package/dist/components/activity-icon/assets/file-pdf.js.map +1 -0
  66. package/dist/components/activity-icon/assets/file-plain-text.js +6 -0
  67. package/dist/components/activity-icon/assets/file-plain-text.js.map +1 -0
  68. package/dist/components/activity-icon/assets/file-ppt.js +6 -0
  69. package/dist/components/activity-icon/assets/file-ppt.js.map +1 -0
  70. package/dist/components/activity-icon/assets/file-presentation.js +6 -0
  71. package/dist/components/activity-icon/assets/file-presentation.js.map +1 -0
  72. package/dist/components/activity-icon/assets/file-psd.js +6 -0
  73. package/dist/components/activity-icon/assets/file-psd.js.map +1 -0
  74. package/dist/components/activity-icon/assets/file-pub.js +6 -0
  75. package/dist/components/activity-icon/assets/file-pub.js.map +1 -0
  76. package/dist/components/activity-icon/assets/file-source-code.js +6 -0
  77. package/dist/components/activity-icon/assets/file-source-code.js.map +1 -0
  78. package/dist/components/activity-icon/assets/file-spreadsheet.js +6 -0
  79. package/dist/components/activity-icon/assets/file-spreadsheet.js.map +1 -0
  80. package/dist/components/activity-icon/assets/file-text-editor.js +6 -0
  81. package/dist/components/activity-icon/assets/file-text-editor.js.map +1 -0
  82. package/dist/components/activity-icon/assets/file-unknown.js +6 -0
  83. package/dist/components/activity-icon/assets/file-unknown.js.map +1 -0
  84. package/dist/components/activity-icon/assets/file-video.js +6 -0
  85. package/dist/components/activity-icon/assets/file-video.js.map +1 -0
  86. package/dist/components/activity-icon/assets/file-xls.js +6 -0
  87. package/dist/components/activity-icon/assets/file-xls.js.map +1 -0
  88. package/dist/components/activity-icon/assets/file.js +6 -0
  89. package/dist/components/activity-icon/assets/file.js.map +1 -0
  90. package/dist/components/activity-icon/assets/folder.js +6 -0
  91. package/dist/components/activity-icon/assets/folder.js.map +1 -0
  92. package/dist/components/activity-icon/assets/forum.js +6 -0
  93. package/dist/components/activity-icon/assets/forum.js.map +1 -0
  94. package/dist/components/activity-icon/assets/glossary.js +6 -0
  95. package/dist/components/activity-icon/assets/glossary.js.map +1 -0
  96. package/dist/components/activity-icon/assets/h5p.js +6 -0
  97. package/dist/components/activity-icon/assets/h5p.js.map +1 -0
  98. package/dist/components/activity-icon/assets/ims-package.js +6 -0
  99. package/dist/components/activity-icon/assets/ims-package.js.map +1 -0
  100. package/dist/components/activity-icon/assets/lesson.js +6 -0
  101. package/dist/components/activity-icon/assets/lesson.js.map +1 -0
  102. package/dist/components/activity-icon/assets/page.js +6 -0
  103. package/dist/components/activity-icon/assets/page.js.map +1 -0
  104. package/dist/components/activity-icon/assets/quiz.js +6 -0
  105. package/dist/components/activity-icon/assets/quiz.js.map +1 -0
  106. package/dist/components/activity-icon/assets/scorm-package.js +6 -0
  107. package/dist/components/activity-icon/assets/scorm-package.js.map +1 -0
  108. package/dist/components/activity-icon/assets/subsection.js +6 -0
  109. package/dist/components/activity-icon/assets/subsection.js.map +1 -0
  110. package/dist/components/activity-icon/assets/survey.js +6 -0
  111. package/dist/components/activity-icon/assets/survey.js.map +1 -0
  112. package/dist/components/activity-icon/assets/text-and-media.js +6 -0
  113. package/dist/components/activity-icon/assets/text-and-media.js.map +1 -0
  114. package/dist/components/activity-icon/assets/url.js +6 -0
  115. package/dist/components/activity-icon/assets/url.js.map +1 -0
  116. package/dist/components/activity-icon/assets/wiki.js +6 -0
  117. package/dist/components/activity-icon/assets/wiki.js.map +1 -0
  118. package/dist/components/activity-icon/assets/workshop.js +6 -0
  119. package/dist/components/activity-icon/assets/workshop.js.map +1 -0
  120. package/dist/components/activity-icon/index.d.ts +1 -0
  121. package/dist/components/activity-icon/index.js +2 -0
  122. package/dist/components/button/Button.js.map +1 -1
  123. package/dist/components/close-button/CloseButton.js.map +1 -1
  124. package/dist/components/index.d.ts +2 -0
  125. package/dist/components/radio/Radio.js.map +1 -1
  126. package/dist/index.css +188 -1
  127. package/dist/index.js +2 -1
  128. package/package.json +4 -4
  129. package/tokens/css/colors.css +7 -0
  130. package/tokens/css/primitives.css +6 -0
  131. package/tokens/css/typography.css +1 -0
  132. package/tokens/scss/_colors.scss +7 -0
  133. package/tokens/scss/_primitives.scss +6 -0
  134. package/tokens/scss/_typography.scss +1 -0
@@ -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;AAK1E,QAJc,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;AAC5D,KAAI,KACF,SAAQ,KAAK,OAAO,OAAO;AAE7B,KAAI,UACF,SAAQ,KAAK,UAAU;AAGzB,QACE,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;AACD,KAAI,UACF,SAAQ,KAAK,UAAU;AAGzB,QACE,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,3 +1,5 @@
1
+ export { ActivityIcon } from './activity-icon';
2
+ export type { ActivityIconProps } from './activity-icon';
1
3
  export { Button } from './button';
2
4
  export type { ButtonProps } from './button';
3
5
  export { CloseButton } from './close-button';
@@ -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;AAClC,KAAI,CAAC,UACH,SAAQ,KAAK,aAAa;AAE5B,KAAI,UACF,SAAQ,KAAK,UAAU;CAIzB,MAAM,YAAY,YAAa,MAAM,iBAAiB,QAAS,KAAA;CAI/D,MAAM,aACJ,mBAAmB,CAAC,aAAa,UAAU,GAAG,GAAG,aAAa,KAAA;AAEhE,QACE,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
@@ -1,4 +1,109 @@
1
- @charset "UTF-8";.mds-btn.btn {
1
+ @charset "UTF-8";.mds-activity-icon {
2
+ align-items: center;
3
+ /*
4
+ * content-box so block-size/inline-size declare the ICON content area.
5
+ * Padding then expands the tile beyond that — matching Figma's structure
6
+ * where "size" is the icon and padding is added by the container variant.
7
+ */
8
+ box-sizing: content-box;
9
+ display: inline-flex;
10
+ flex-shrink: 0;
11
+ justify-content: center;
12
+ overflow: hidden;
13
+ }
14
+
15
+ /* none: no background tile, no padding — tile equals the icon size */
16
+
17
+ .mds-activity-icon--none {
18
+ border-radius: var(--mds-border-radius-none);
19
+ }
20
+
21
+ /*
22
+ * default: 8px padding each side — tile = icon + 16px (e.g. lg: 24+16 = 40px total).
23
+ * large: 12px padding each side — tile = icon + 24px (e.g. lg: 24+24 = 48px total).
24
+ */
25
+
26
+ .mds-activity-icon--default {
27
+ border-radius: var(--mds-border-radius-xl);
28
+ padding: var(--mds-spacing-xs);
29
+ }
30
+
31
+ .mds-activity-icon--large {
32
+ border-radius: var(--mds-border-radius-xl);
33
+ padding: var(--mds-spacing-sm);
34
+ }
35
+
36
+ /* Figma uses a smaller outer radius for default+sm only. */
37
+
38
+ .mds-activity-icon--default.mds-activity-icon--size-sm {
39
+ border-radius: var(--mds-border-radius-lg);
40
+ }
41
+
42
+ /* Category background colors apply only when a background container is active. */
43
+
44
+ .mds-activity-icon--default.mds-activity-icon--category-assessment,
45
+ .mds-activity-icon--large.mds-activity-icon--category-assessment {
46
+ background-color: var(--mds-activity-icon-assessment-bg);
47
+ }
48
+
49
+ .mds-activity-icon--default.mds-activity-icon--category-collaboration,
50
+ .mds-activity-icon--large.mds-activity-icon--category-collaboration {
51
+ background-color: var(--mds-activity-icon-collaboration-bg);
52
+ }
53
+
54
+ .mds-activity-icon--default.mds-activity-icon--category-communication,
55
+ .mds-activity-icon--large.mds-activity-icon--category-communication {
56
+ background-color: var(--mds-activity-icon-communication-bg);
57
+ }
58
+
59
+ .mds-activity-icon--default.mds-activity-icon--category-interactive,
60
+ .mds-activity-icon--large.mds-activity-icon--category-interactive {
61
+ background-color: var(--mds-activity-icon-interactive-bg);
62
+ }
63
+
64
+ .mds-activity-icon--default.mds-activity-icon--category-other,
65
+ .mds-activity-icon--large.mds-activity-icon--category-other {
66
+ background-color: var(--mds-activity-icon-other-bg);
67
+ }
68
+
69
+ .mds-activity-icon--default.mds-activity-icon--category-resource,
70
+ .mds-activity-icon--large.mds-activity-icon--category-resource {
71
+ background-color: var(--mds-activity-icon-resource-bg);
72
+ }
73
+
74
+ /* Size variants */
75
+
76
+ .mds-activity-icon--size-sm {
77
+ block-size: var(--mds-icons-sm);
78
+ inline-size: var(--mds-icons-sm);
79
+ }
80
+
81
+ .mds-activity-icon--size-md {
82
+ /* Intentionally follows the Figma token mapping, where md uses the lg icon token. */
83
+ block-size: var(--mds-icons-lg);
84
+ inline-size: var(--mds-icons-lg);
85
+ }
86
+
87
+ .mds-activity-icon--size-lg {
88
+ /* Intentionally follows the Figma token mapping, where lg uses the xl icon token. */
89
+ block-size: var(--mds-icons-xl);
90
+ inline-size: var(--mds-icons-xl);
91
+ }
92
+
93
+ .mds-activity-icon--size-xl {
94
+ /* Intentionally follows the Figma token mapping, where xl uses the xxl icon token. */
95
+ block-size: var(--mds-icons-xxl);
96
+ inline-size: var(--mds-icons-xxl);
97
+ }
98
+
99
+ .mds-activity-icon__asset {
100
+ block-size: 100%;
101
+ border-radius: var(--mds-border-radius-xs);
102
+ display: block;
103
+ inline-size: 100%;
104
+ }
105
+
106
+ .mds-btn.btn {
2
107
  background-color: var(--mds-bg-interactive-primary-default);
3
108
  border: var(--mds-stroke-weight-sm) solid var(--mds-border-translucent);
4
109
  border-radius: var(--mds-border-radius-md);
@@ -17,129 +122,161 @@
17
122
  align-items: center;
18
123
  gap: var(--mds-spacing-xxs);
19
124
  }
125
+
20
126
  .mds-btn.btn:hover {
21
127
  background-color: var(--mds-bg-interactive-primary-hover);
22
128
  }
129
+
23
130
  .mds-btn.btn:active {
24
131
  background-color: var(--mds-bg-interactive-primary-active);
25
132
  border: var(--mds-stroke-weight-sm) solid var(--mds-border-translucent);
26
133
  }
134
+
27
135
  .mds-btn.btn:disabled {
28
136
  background-color: var(--mds-bg-interactive-primary-disabled);
29
137
  }
138
+
30
139
  /**
31
140
  * Secondary variants
32
141
  */
142
+
33
143
  .mds-btn.btn.btn-secondary {
34
144
  background-color: var(--mds-bg-interactive-secondary-default);
35
145
  color: var(--mds-text-default);
36
146
  }
147
+
37
148
  .mds-btn.btn.btn-secondary:hover {
38
149
  background-color: var(--mds-bg-interactive-secondary-hover);
39
150
  }
151
+
40
152
  .mds-btn.btn.btn-secondary:active {
41
153
  background-color: var(--mds-bg-interactive-secondary-active);
42
154
  }
155
+
43
156
  .mds-btn.btn.btn-secondary:disabled {
44
157
  background-color: var(--mds-bg-interactive-secondary-disabled);
45
158
  }
159
+
46
160
  /**
47
161
  * Danger variants
48
162
  */
163
+
49
164
  .mds-btn.btn-danger {
50
165
  background-color: var(--mds-bg-interactive-danger-default);
51
166
  }
167
+
52
168
  .mds-btn.btn-danger:hover {
53
169
  background-color: var(--mds-bg-interactive-danger-hover);
54
170
  }
171
+
55
172
  .mds-btn.btn-danger:active {
56
173
  background-color: var(--mds-bg-interactive-danger-active);
57
174
  }
175
+
58
176
  .mds-btn.btn.btn-danger:disabled {
59
177
  background-color: var(--mds-bg-interactive-danger-disabled);
60
178
  }
179
+
61
180
  /**
62
181
  * Outline Primary variants
63
182
  */
183
+
64
184
  .mds-btn.btn-outline-primary {
65
185
  background-color: transparent;
66
186
  border: var(--mds-stroke-weight-sm) solid
67
187
  var(--mds-border-interactive-primary-default);
68
188
  color: var(--mds-text-link-primary-default);
69
189
  }
190
+
70
191
  .mds-btn.btn-outline-primary:hover {
71
192
  background-color: var(--mds-bg-interactive-primary-hover);
72
193
  color: var(--mds-text-inverse);
73
194
  }
195
+
74
196
  .mds-btn.btn-outline-primary:active {
75
197
  background-color: var(--mds-bg-interactive-primary-active);
76
198
  color: var(--mds-text-inverse);
77
199
  }
200
+
78
201
  .mds-btn.btn.btn-outline-primary:disabled {
79
202
  background-color: transparent;
80
203
  border: var(--mds-stroke-weight-sm) solid
81
204
  var(--mds-border-interactive-primary-disabled);
82
205
  color: var(--mds-color-blue-300);
83
206
  }
207
+
84
208
  /**
85
209
  * Outline Secondary variants
86
210
  */
211
+
87
212
  .mds-btn.btn-outline-secondary {
88
213
  background-color: transparent;
89
214
  border: var(--mds-stroke-weight-sm) solid
90
215
  var(--mds-border-interactive-secondary-default);
91
216
  color: var(--mds-text-muted);
92
217
  }
218
+
93
219
  .mds-btn.btn-outline-secondary:hover {
94
220
  background-color: var(--mds-border-interactive-secondary-hover);
95
221
  color: var(--mds-text-inverse);
96
222
  }
223
+
97
224
  .mds-btn.btn-outline-secondary:active {
98
225
  background-color: var(--mds-border-interactive-secondary-active);
99
226
  color: var(--mds-text-inverse);
100
227
  }
228
+
101
229
  .mds-btn.btn.btn-outline-secondary:disabled {
102
230
  background-color: transparent;
103
231
  border: var(--mds-stroke-weight-sm) solid
104
232
  var(--mds-border-interactive-secondary-disabled);
105
233
  color: var(--mds-color-gray-500);
106
234
  }
235
+
107
236
  /**
108
237
  * Outline Danger variants
109
238
  */
239
+
110
240
  .mds-btn.btn-outline-danger {
111
241
  background-color: transparent;
112
242
  border: var(--mds-stroke-weight-sm) solid
113
243
  var(--mds-border-interactive-danger-default);
114
244
  color: var(--mds-text-danger);
115
245
  }
246
+
116
247
  .mds-btn.btn-outline-danger:hover {
117
248
  background-color: var(--mds-bg-interactive-danger-default);
118
249
  color: var(--mds-text-inverse);
119
250
  }
251
+
120
252
  .mds-btn.btn-outline-danger:active {
121
253
  background-color: var(--mds-bg-interactive-danger-hover);
122
254
  color: var(--mds-text-inverse);
123
255
  }
256
+
124
257
  .mds-btn.btn.btn-outline-danger:disabled {
125
258
  background-color: transparent;
126
259
  border: var(--mds-stroke-weight-sm) solid
127
260
  var(--mds-border-interactive-danger-disabled);
128
261
  color: var(--mds-color-red-300);
129
262
  }
263
+
130
264
  /**
131
265
  * Size variants
132
266
  */
267
+
133
268
  .mds-btn.btn-sm {
134
269
  padding: var(--mds-spacing-xxs) var(--mds-spacing-xs);
135
270
  font-size: var(--mds-font-size-paragraph-small);
136
271
  line-height: var(--mds-line-height-paragraph-small);
137
272
  }
273
+
138
274
  .mds-btn.btn-lg {
139
275
  padding: var(--mds-spacing-xs) var(--mds-spacing-md);
140
276
  font-size: var(--mds-font-size-paragraph-lead);
141
277
  line-height: var(--mds-line-height-paragraph-default);
142
278
  }
279
+
143
280
  .mds-close-button.btn-close {
144
281
  inline-size: var(--mds-icons-md);
145
282
  block-size: var(--mds-icons-md);
@@ -149,38 +286,47 @@
149
286
  background-size: var(--mds-icons-md) var(--mds-icons-md);
150
287
  opacity: 0.6;
151
288
  }
289
+
152
290
  .mds-close-button.btn-close:hover {
153
291
  opacity: 0.75;
154
292
  }
293
+
155
294
  .mds-close-button.btn-close:focus {
156
295
  box-shadow: none;
157
296
  }
297
+
158
298
  .mds-close-button.btn-close:focus-visible {
159
299
  outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
160
300
  outline-offset: 0;
161
301
  box-shadow: none;
162
302
  opacity: 1;
163
303
  }
304
+
164
305
  .mds-close-button.btn-close:disabled,
165
306
  .mds-close-button.btn-close.disabled {
166
307
  opacity: 0.25;
167
308
  }
309
+
168
310
  .mds-close-button.btn-close.mds-close-button--sm {
169
311
  inline-size: var(--mds-icons-sm);
170
312
  block-size: var(--mds-icons-sm);
171
313
  background-size: var(--mds-icons-sm) var(--mds-icons-sm);
172
314
  }
315
+
173
316
  .mds-close-button.btn-close.mds-close-button--md {
174
317
  inline-size: var(--mds-icons-md);
175
318
  block-size: var(--mds-icons-md);
176
319
  background-size: var(--mds-icons-md) var(--mds-icons-md);
177
320
  }
321
+
178
322
  .mds-close-button.btn-close.mds-close-button--lg {
179
323
  inline-size: var(--mds-icons-lg);
180
324
  block-size: var(--mds-icons-lg);
181
325
  background-size: var(--mds-icons-lg) var(--mds-icons-lg);
182
326
  }
327
+
183
328
  /* Parent element */
329
+
184
330
  .mds-form-check {
185
331
  /* Grid layout: column 1 = input, column 2 = label. The feedback is then placed
186
332
  explicitly into column 2 via grid-column so it aligns with the label without
@@ -210,7 +356,9 @@
210
356
  line-height: var(--mds-line-height-paragraph-small, 1.0875rem); /* 108.75% */
211
357
  letter-spacing: var(--mds-letter-spacing, 0);
212
358
  }
359
+
213
360
  /* Input element styles */
361
+
214
362
  .mds-form-check-input {
215
363
  border-radius: var(--mds-border-radius-pill, 50rem);
216
364
  border: var(--mds-stroke-weight-sm, 1px) solid
@@ -219,83 +367,106 @@
219
367
  background-image to none, which wipes out Bootstrap's --bs-form-check-bg-image
220
368
  (the inner dot SVG) on the checked state. */
221
369
  }
370
+
222
371
  /* Bootstrap's .form-check .form-check-input rule (specificity 0,2,0) sets float: left
223
372
  and a negative margin-inline-start that causes overlap. Match its specificity to
224
373
  ensure our flex-friendly reset wins regardless of load order. */
374
+
225
375
  .mds-form-check .mds-form-check-input {
226
376
  margin: 0;
227
377
  float: none;
228
378
  background-color: var(--mds-bg-surface-default, #fff);
229
379
  }
380
+
230
381
  .mds-form-check-input:checked {
231
382
  background-color: var(--mds-bg-interactive-primary-default);
232
383
  border-color: transparent;
233
384
  }
385
+
234
386
  .mds-form-check-input:disabled {
235
387
  border-color: var(--mds-border-interactive-secondary-disabled);
236
388
  }
389
+
237
390
  .mds-form-check-input:disabled:checked {
238
391
  background-color: var(--mds-bg-interactive-primary-disabled);
239
392
  border-color: transparent;
240
393
  }
394
+
241
395
  .mds-form-check-input.is-invalid {
242
396
  border-color: var(--mds-border-interactive-danger-default);
243
397
  }
398
+
244
399
  .mds-form-check-input.is-invalid:checked {
245
400
  background-color: var(--mds-bg-interactive-danger-default);
246
401
  border-color: transparent;
247
402
  }
403
+
248
404
  /* Explicit rule for invalid + disabled to avoid relying on specificity cascade order.
249
405
  Disabled takes visual precedence: use the disabled background/border, not the danger ones. */
406
+
250
407
  .mds-form-check-input.is-invalid:disabled {
251
408
  background-color: var(--mds-bg-interactive-secondary-disabled);
252
409
  border-color: var(--mds-border-interactive-secondary-disabled);
253
410
  }
411
+
254
412
  .mds-form-check-input.is-invalid:disabled:checked {
255
413
  background-color: var(--mds-bg-interactive-primary-disabled);
256
414
  border-color: transparent;
257
415
  }
416
+
258
417
  /* is-valid (Bootstrap) and neutral feedback states are intentionally not supported.
259
418
  Radio buttons surface a binary valid/invalid result; positive confirmation
260
419
  is conveyed by form-level success messaging rather than per-input valid styling. */
420
+
261
421
  /* Hover styles are intentionally omitted. The radio input relies on the OS/browser
262
422
  default appearance for hover; no design token exists for a radio hover state. */
423
+
263
424
  /* Focus ring: Only outline + outline-offset allowed (no box-shadow or border).
264
425
  See .github/instructions/components.instructions.md for full rules and rationale. */
426
+
265
427
  .mds-form-check-input:focus,
266
428
  .mds-form-check-input.is-invalid:focus {
267
429
  /* Reset Bootstrap's :focus box-shadow; our ring is applied on :focus-visible only */
268
430
  box-shadow: none;
269
431
  outline: none;
270
432
  }
433
+
271
434
  .mds-form-check-input:focus-visible {
272
435
  outline: var(--mds-stroke-weight-md) solid var(--mds-focus-default);
273
436
  outline-offset: var(--mds-spacing-offset);
274
437
  box-shadow: none;
275
438
  }
439
+
276
440
  .mds-form-check-input.is-invalid:focus-visible {
277
441
  outline: var(--mds-stroke-weight-md) solid var(--mds-border-feedback-danger);
278
442
  outline-offset: var(--mds-spacing-offset);
279
443
  box-shadow: none;
280
444
  }
445
+
281
446
  /* Label element styles */
447
+
282
448
  .mds-form-check .form-check-label,
283
449
  .mds-form-check .mds-form-check-label {
284
450
  color: var(--mds-text-default, #1d2125);
285
451
  cursor: pointer;
286
452
  }
453
+
287
454
  /* Use sibling selector so the label reflects the input's disabled/invalid state */
455
+
288
456
  .mds-form-check .mds-form-check-input:disabled ~ .form-check-label,
289
457
  .mds-form-check .mds-form-check-input:disabled ~ .mds-form-check-label {
290
458
  color: var(--mds-text-muted);
291
459
  /* Disabled inputs are not interactive — suppress the pointer cursor on the label */
292
460
  cursor: default;
293
461
  }
462
+
294
463
  .mds-form-check .mds-form-check-input.is-invalid ~ .form-check-label,
295
464
  .mds-form-check .mds-form-check-input.is-invalid ~ .mds-form-check-label {
296
465
  color: var(--mds-text-danger);
297
466
  }
467
+
298
468
  /* Feedback element styles — UI text/UI small */
469
+
299
470
  .mds-form-check .invalid-feedback,
300
471
  .mds-form-check .mds-form-check-feedback {
301
472
  /* Place feedback in column 2 (the label column) so it aligns with the label
@@ -305,8 +476,10 @@
305
476
  font-weight: var(--mds-font-weight-medium, 500);
306
477
  color: var(--mds-text-danger);
307
478
  }
479
+
308
480
  /* Dim the feedback when the input is disabled to match the reduced visual weight of
309
481
  the label and input; keeps the feedback legible without implying it is actionable. */
482
+
310
483
  .mds-form-check:has(.mds-form-check-input:disabled) .invalid-feedback,
311
484
  .mds-form-check:has(.mds-form-check-input:disabled) .mds-form-check-feedback {
312
485
  opacity: 0.5;
@@ -372,23 +545,30 @@
372
545
  --mds-activity-icon-other-icon: var(--mds-color-gray-900);
373
546
  --mds-activity-icon-resource-bg: var(--mds-color-cyan-100);
374
547
  --mds-activity-icon-resource-icon: var(--mds-color-cyan-500);
548
+ --mds-bg-feedback-danger-light: var(--mds-color-red-50);
375
549
  --mds-bg-feedback-danger-subtle: var(--mds-color-red-100);
376
550
  --mds-bg-feedback-info-default: var(--mds-color-cyan-500);
551
+ --mds-bg-feedback-info-light: var(--mds-color-cyan-50);
377
552
  --mds-bg-feedback-info-subtle: var(--mds-color-cyan-100);
378
553
  --mds-bg-feedback-primary-default: var(--mds-color-blue-500);
554
+ --mds-bg-feedback-primary-light: var(--mds-color-blue-50);
379
555
  --mds-bg-feedback-primary-subtle: var(--mds-color-blue-100);
380
556
  --mds-bg-feedback-secondary-default: var(--mds-color-gray-400);
381
557
  --mds-bg-feedback-secondary-subtle: var(--mds-color-gray-100);
382
558
  --mds-bg-feedback-success-default: var(--mds-color-green-500);
559
+ --mds-bg-feedback-success-light: var(--mds-color-green-50);
383
560
  --mds-bg-feedback-success-subtle: var(--mds-color-green-100);
384
561
  --mds-bg-feedback-warning-default: var(--mds-color-yellow-500);
562
+ --mds-bg-feedback-warning-light: var(--mds-color-yellow-50);
385
563
  --mds-bg-feedback-warning-subtle: var(--mds-color-yellow-100);
386
564
  --mds-bg-interactive-danger-active: var(--mds-color-red-700);
387
565
  --mds-bg-interactive-danger-default: var(--mds-color-red-500);
566
+ --mds-bg-interactive-danger-default-light: var(--mds-color-red-50);
388
567
  --mds-bg-interactive-danger-disabled: var(--mds-color-red-200);
389
568
  --mds-bg-interactive-danger-hover: var(--mds-color-red-600);
390
569
  --mds-bg-interactive-primary-active: var(--mds-color-blue-700);
391
570
  --mds-bg-interactive-primary-default: var(--mds-color-blue-500);
571
+ --mds-bg-interactive-primary-default-light: var(--mds-color-blue-50);
392
572
  --mds-bg-interactive-primary-disabled: var(--mds-color-blue-200);
393
573
  --mds-bg-interactive-primary-hover: var(--mds-color-blue-600);
394
574
  --mds-bg-interactive-secondary-default: var(--mds-color-gray-400);
@@ -444,6 +624,7 @@
444
624
  */
445
625
 
446
626
  :root {
627
+ --mds-color-blue-50: #e7f0f9;
447
628
  --mds-color-blue-100: #cfe2f2;
448
629
  --mds-color-blue-200: #9fc4e5;
449
630
  --mds-color-blue-300: #6fa7d9;
@@ -453,6 +634,7 @@
453
634
  --mds-color-blue-700: #094173;
454
635
  --mds-color-blue-800: #062b4c;
455
636
  --mds-color-blue-900: #031626;
637
+ --mds-color-cyan-50: #e5f2f4;
456
638
  --mds-color-cyan-100: #cce6ea;
457
639
  --mds-color-cyan-200: #99cdd5;
458
640
  --mds-color-cyan-300: #66b3c0;
@@ -473,6 +655,7 @@
473
655
  --mds-color-gray-900: #1d2125;
474
656
  --mds-color-gray-black: #000000;
475
657
  --mds-color-gray-white: #ffffff;
658
+ --mds-color-green-50: #ebf2ea;
476
659
  --mds-color-green-100: #d7e4d6;
477
660
  --mds-color-green-200: #aecaad;
478
661
  --mds-color-green-300: #86af84;
@@ -518,6 +701,7 @@
518
701
  --mds-color-purple-700: #3a254a;
519
702
  --mds-color-purple-800: #271832;
520
703
  --mds-color-purple-900: #130c19;
704
+ --mds-color-red-50: #faeae9;
521
705
  --mds-color-red-100: #f4d6d2;
522
706
  --mds-color-red-200: #eaada6;
523
707
  --mds-color-red-300: #df8379;
@@ -536,6 +720,7 @@
536
720
  --mds-color-teal-700: #13795b;
537
721
  --mds-color-teal-800: #0d503c;
538
722
  --mds-color-teal-900: #06281e;
723
+ --mds-color-yellow-50: #fdf7ed;
539
724
  --mds-color-yellow-100: #fcefdc;
540
725
  --mds-color-yellow-200: #f9deb8;
541
726
  --mds-color-yellow-300: #f6ce95;
@@ -600,6 +785,7 @@
600
785
  --mds-typography-line-height-paragraph-base: 1.5rem;
601
786
  --mds-typography-line-height-paragraph-lg: 2.5rem;
602
787
  --mds-typography-line-height-paragraph-sm: 1.0875rem;
788
+ --mds-typography-line-height-paragraph-xs: 0.75rem;
603
789
  }
604
790
 
605
791
  /**
@@ -692,6 +878,7 @@
692
878
  --mds-line-height-paragraph-default: var(--mds-typography-line-height-paragraph-base); /** Comfortable line height for paragraphs. */
693
879
  --mds-line-height-paragraph-lead: var(--mds-typography-line-height-paragraph-lg); /** Extended line height for large body text. */
694
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);
695
882
  --mds-margin-bottom-display: var(--mds-spacing-none); /** Default margin below a Displays. */
696
883
  --mds-margin-bottom-heading: var(--mds-spacing-xs); /** Default margin below a Headings. */
697
884
  --mds-margin-bottom-paragraph: var(--mds-spacing-md); /** Default margin below a paragraph. */
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  /* empty css */
2
2
  /* empty css */
3
3
  /* empty css */
4
+ import { ActivityIcon } from "./components/activity-icon/ActivityIcon.js";
4
5
  import { Button } from "./components/button/Button.js";
5
6
  import { CloseButton } from "./components/close-button/CloseButton.js";
6
7
  import { Radio } from "./components/radio/Radio.js";
7
- export { Button, CloseButton, Radio };
8
+ export { ActivityIcon, Button, CloseButton, Radio };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moodlehq/design-system",
3
- "version": "3.0.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",
@@ -97,7 +97,7 @@
97
97
  "typescript": "^5.9.3",
98
98
  "typescript-eslint": "^8.57.1",
99
99
  "vite": "^8.0.0",
100
- "vite-plugin-dts": "^4.5.4",
100
+ "vite-plugin-dts": "^5.0.0",
101
101
  "vite-tsconfig-paths": "^6.0.1",
102
102
  "vitest": "^4.0.4"
103
103
  },
@@ -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);