MDL-87909 core: Include @moodlehq/design-system js

This commit also includes the @types/react, and teaches typescript how
to consume the design system definition files.
This commit is contained in:
Andrew Nicols
2026-03-13 14:34:57 +08:00
committed by Adrian Greeve
parent b46a8f8c3e
commit 780f9fff56
18 changed files with 579 additions and 12 deletions
+6 -2
View File
@@ -167,8 +167,12 @@ export function generateAliases() {
// Build TS paths for tsconfig.aliases.json
/** @type {Record<string, string[]>} */
const tsPaths = {};
tsPaths["@moodle/lms/core/*"] = ["./public/lib/js/esm/src/*"]; // Always include core alias.
const tsPaths = {
// Always include core alias.
"@moodle/lms/core/*": ["./public/lib/js/esm/src/*"],
// Add the design system alias for TypeScript.
"@moodlehq/design-system": ["./lib/js/bundles/design-system/index.d.ts"]
};
for (const [componentPath, componentName] of Object.entries(componentPathMap)) {
const reactSrcDir = path.join(rootDir, componentPath, "js", "esm", "src");
+5
View File
@@ -60,6 +60,11 @@ module.exports = grunt => {
(async() => {
try {
const {watchComponents} = await import('../../.esbuild/plugin/plugincomponents.mjs');
const { generateAliases } = await import('../../.esbuild/generate-aliases.mjs');
const { buildPluginComponents } = await import('../../.esbuild/plugin/plugincomponents.mjs');
generateAliases();
await buildPluginComponents(isDev);
const ctx = await watchComponents(true, onRebuild);
@@ -0,0 +1,7 @@
import { ButtonHTMLAttributes } from 'react';
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
label: string;
variant?: string;
size?: 'sm' | 'lg';
}
export declare const Button: ({ label, variant, size, className, type, ...props }: ButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1 @@
export * from './Button';
+2
View File
@@ -0,0 +1,2 @@
export { Button } from './button';
export type { ButtonProps } from './button';
File diff suppressed because one or more lines are too long
+1
View File
@@ -0,0 +1 @@
export * from './components';
+31
View File
@@ -0,0 +1,31 @@
import { jsx } from "react/jsx-runtime";
const allowedVariants = [
"primary",
"secondary",
"danger",
"outline-primary",
"outline-secondary",
"outline-danger"
];
const Button = ({
label,
variant,
size,
className,
type = "button",
...props
}) => {
const resolvedVariant = variant && allowedVariants.includes(variant) ? variant : "primary";
const classes = ["mds-btn", "btn", `btn-${resolvedVariant}`];
if (size) {
classes.push(`btn-${size}`);
}
if (className) {
classes.push(className);
}
return /* @__PURE__ */ jsx("button", { className: classes.join(" "), type, ...props, children: label });
};
export {
Button
};
//# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
{"version":3,"file":"index.js","sources":["../components/button/Button.tsx"],"sourcesContent":["import type { ButtonHTMLAttributes } from 'react';\n\ntype ButtonVariant =\n | 'primary'\n | 'secondary'\n | 'danger'\n | 'outline-primary'\n | 'outline-secondary'\n | 'outline-danger';\n\nexport interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {\n label: string;\n variant?: string;\n size?: 'sm' | 'lg';\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 className,\n type = 'button',\n ...props\n}: ButtonProps) => {\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 {label}\n </button>\n );\n};\n"],"names":[],"mappings":";AAgBA,MAAM,kBAAmC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,MAAM,SAAS,CAAC;AAAA,EACrB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,GAAG;AACL,MAAmB;AACjB,QAAM,kBACJ,WAAW,gBAAgB,SAAS,OAAwB,IACxD,UACA;AAEN,QAAM,UAAU,CAAC,WAAW,OAAO,OAAO,eAAe,EAAE;AAC3D,MAAI,MAAM;AACR,YAAQ,KAAK,OAAO,IAAI,EAAE;AAAA,EAC5B;AACA,MAAI,WAAW;AACb,YAAQ,KAAK,SAAS;AAAA,EACxB;AAEA,SACE,oBAAC,UAAA,EAAO,WAAW,QAAQ,KAAK,GAAG,GAAG,MAAa,GAAG,OACnD,UAAA,MAAA,CACH;AAEJ;"}
@@ -0,0 +1 @@
The content of this directory is automatically managed by the `npm install` and `npm update` commands.
+11
View File
@@ -18,4 +18,15 @@
<licenseversion/>
<repository>https://github.com/facebook/react</repository>
</library>
<library>
<location>js/bundles/design-system</location>
<name>@moodlehq/design-system</name>
<description>The Moodle HQ design system based on React</description>
<version>2.1.1</version>
<license>GNU</license>
<repository>https://github.com/moodlehq/design-system</repository>
<copyrights>
<copyright>Moodle HQ</copyright>
</copyrights>
</library>
</libraries>
+2 -3
View File
@@ -16,6 +16,7 @@
"@babel/eslint-parser": "^7.21.3",
"@babel/eslint-plugin": "7.19.1",
"@babel/preset-env": "7.16.11",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/parser": "^8.53.1",
"@xmldom/xmldom": "^0.8.7",
@@ -2227,7 +2228,6 @@
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -4058,8 +4058,7 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"dev": true,
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/ctype": {
"version": "0.5.3",
+3 -2
View File
@@ -10,15 +10,16 @@
"upgradenote": "node .grunt/upgradenotes.mjs create"
},
"dependencies": {
"@moodlehq/design-system": "^2.1",
"react": "^19.1",
"react-dom": "^19.1",
"@moodlehq/design-system": "^2.1"
"react-dom": "^19.1"
},
"devDependencies": {
"@babel/core": "7.17.5",
"@babel/eslint-parser": "^7.21.3",
"@babel/eslint-plugin": "7.19.1",
"@babel/preset-env": "7.16.11",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@typescript-eslint/parser": "^8.53.1",
"@xmldom/xmldom": "^0.8.7",
@@ -98,7 +98,7 @@ class import_map implements \JsonSerializable {
*/
protected function add_standard_imports(): void {
$this->add_import('@moodle/lms/', path: 'js/esm/build', loadfromcomponent: true);
$this->add_import('@moodlehq/design-system', path: 'lib/js/bundles/design-system');
$this->add_import('@moodlehq/design-system', path: 'lib/js/bundles/design-system/index');
$this->add_import('react', path: 'lib/js/bundles/react/react');
$this->add_import('react/', path: 'lib/js/bundles/react');
$this->add_import('react-dom', path: 'lib/js/bundles/react-dom/react-dom');
@@ -122,11 +122,13 @@ class import_map implements \JsonSerializable {
?\core\url $loader = null,
?string $path = null,
bool $loadfromcomponent = false,
string $suffix = '.js',
): void {
$this->imports[$specifier] = (object) [
'loader' => $loader,
'path' => $path,
'loadfromcomponent' => $loadfromcomponent,
'suffix' => $suffix,
];
$this->importssorted = false;
}
@@ -171,7 +173,7 @@ class import_map implements \JsonSerializable {
if (in_array('..', explode('/', $pathremainder), true)) {
return null;
}
return implode(DIRECTORY_SEPARATOR, array_filter([$CFG->root, $importdata->path, $pathremainder])) . '.js';
return implode(DIRECTORY_SEPARATOR, array_filter([$CFG->root, $importdata->path, $pathremainder])) . $importdata->suffix;
}
return null;
@@ -204,7 +206,7 @@ class import_map implements \JsonSerializable {
// Resolve the component directory; an unknown component name returns null.
$dir = \core\component::get_component_directory($component);
$file = "{$dir}/{$importdata->path}/{$modulerest}.js";
$file = "{$dir}/{$importdata->path}/{$modulerest}{$importdata->suffix}";
if (!file_exists($file)) {
throw new \core\exception\not_found_exception('script', $subpath);
}
+1 -1
View File
@@ -350,7 +350,7 @@ enum param: string {
* Accepts lowercase letters, numbers, hyphens, underscores, dots, forward slashes, and a leading @.
* The path must start with a letter or @. Directory traversal via '..' is rejected by the ESM controller.
*/
#[param_clientside_regex('^[@a-z][a-z0-9_.-]*(/[a-z0-9_./-]+)?$')]
#[param_clientside_regex('^[@a-zA-Z][a-zA-Z0-9_.-]*(/[a-zA-Z0-9_./-]+)?$')]
case ESM_PATH = 'esm_path';
/**
+1
View File
@@ -164,6 +164,7 @@ final class param_test extends \advanced_testcase {
['react/jsx-runtime', param::ESM_PATH],
['button.small', param::ESM_PATH],
['@moodlehq/design-system/button.small', param::ESM_PATH],
['@moodlehq/design-system/Button', param::ESM_PATH],
];
}
+11
View File
@@ -18,14 +18,23 @@ async function init() {
const DS_VERSION = getPackageVersion('@moodlehq/design-system');
const nodeModuleRoot = path.join(rootDir, 'node_modules', '@moodlehq', 'design-system');
const bundleRoot = path.join(rootDir, 'lib', 'js', 'bundles', 'design-system');
const themeRoot = path.join(rootDir, 'public', 'theme', 'boost');
const themeDesignSystemRoot = path.join(themeRoot, 'scss', 'design-system');
console.log(chalk.blue.bold.underline('Updating @moodlehq/design-system bundle to version %s from Node Modules'), DS_VERSION);
console.log(chalk.blue('Removing old bundles...'));
fs.removeSync(bundleRoot, { recursive: true, force: true });
fs.removeSync(themeDesignSystemRoot, { recursive: true, force: true });
console.log(chalk.green('Old bundles removed ✓'));
// Copy the JS bundles to the lib folder.
fs.copySync(
path.join(nodeModuleRoot, 'dist'),
path.join(bundleRoot),
);
console.log(chalk.green(`→ @moodlehq/design-system:${DS_VERSION} JS bundles ✓`));
// Copy tokens into the themes.
fs.copySync(
path.join(nodeModuleRoot, 'tokens', 'scss'),
@@ -35,10 +44,12 @@ async function init() {
// Create readme files in the package folders.
console.log(chalk.green(`→ Creating readme_moodle.txt files ✓`));
createPackageReadme(bundleRoot, '@moodlehq/design-system');
createPackageReadme(themeDesignSystemRoot, '@moodlehq/design-system');
// And update the version in thirdpartylibs.xml.
console.log(chalk.green(`→ Updating thirdpartylibs.xml files ✓`));
updateThirdPartyLibsXml(path.join(rootDir, 'lib'), 'js/bundles/design-system', '@moodlehq/design-system', DS_VERSION);
updateThirdPartyLibsXml(themeRoot, 'scss/design-system', '@moodlehq/design-system', DS_VERSION);
console.log("\nAll bundles saved" + chalk.green(" ✓"));
+2 -1
View File
@@ -1,9 +1,10 @@
{
"extends": "./tsconfig.aliases.json", // Added to extend the generated aliases.
"compilerOptions": {
"baseUrl": ".",
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "Node",
"moduleResolution": "node",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true