diff --git a/package.json b/package.json index 343e3104..71d0541d 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,28 @@ "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "require": "./dist/index.cjs.js", + "import": "./dist/index.esm.js" + }, + "./navigation": { + "types": "./dist/navigation.d.ts", + "require": "./dist/navigation.cjs.js", + "import": "./dist/navigation.esm.js" + }, + "./themes": { + "types": "./dist/themes.d.ts", + "require": "./dist/themes.cjs.js", + "import": "./dist/themes.esm.js" + }, + "./controls": { + "types": "./dist/controls.d.ts", + "require": "./dist/controls.cjs.js", + "import": "./dist/controls.esm.js" + } + }, "scripts": { "build": "tsc -b && rollup --config rollup.config.mjs", "docs:build": "typedoc src", @@ -29,16 +51,16 @@ "dependencies": { "keycloak-js": "^26.2.1", "react-icons": "^5.3.0", - "utif": "^3.1.0" + "utif": "^3.1.0", + "@mui/icons-material": "^7.0.0" }, "peerDependencies": { "@emotion/react": "^11.13.3", "@emotion/styled": "^11.13.0", - "@jsonforms/core": "^3.6.0", - "@jsonforms/material-renderers": "^3.6.0", - "@jsonforms/react": "^3.6.0", - "@mui/icons-material": "^6.1.7", - "@mui/material": "^6.1.7", + "@jsonforms/core": "^3.7.0", + "@jsonforms/material-renderers": "^3.7.0", + "@jsonforms/react": "^3.7.0", + "@mui/material": "^7.0.0", "react": "^18.3.1" }, "devDependencies": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e159246c..69aa58b3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,20 +15,20 @@ importers: specifier: ^11.13.0 version: 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@jsonforms/core': - specifier: ^3.6.0 - version: 3.6.0 + specifier: ^3.7.0 + version: 3.7.0 '@jsonforms/material-renderers': - specifier: ^3.6.0 - version: 3.6.0(urt6nrxqlj2unp4w44pnlaf44a) + specifier: ^3.7.0 + version: 3.7.0(tycpmb7mlqgjusrbuymfwpcqdy) '@jsonforms/react': - specifier: ^3.6.0 - version: 3.6.0(@jsonforms/core@3.6.0)(react@18.3.1) + specifier: ^3.7.0 + version: 3.7.0(@jsonforms/core@3.7.0)(react@18.3.1) '@mui/icons-material': - specifier: ^6.1.7 - version: 6.3.1(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + specifier: ^7.0.0 + version: 7.3.10(@mui/material@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@mui/material': - specifier: ^6.1.7 - version: 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + specifier: ^7.0.0 + version: 7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) keycloak-js: specifier: ^26.2.1 version: 26.2.1 @@ -735,6 +735,10 @@ packages: resolution: {integrity: sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw==} engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + '@babel/template@7.25.9': resolution: {integrity: sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==} engines: {node: '>=6.9.0'} @@ -1202,25 +1206,25 @@ packages: '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} - '@jsonforms/core@3.6.0': - resolution: {integrity: sha512-Qz7qJPf/yP4ybqknZ500zggIDZRJfcufu+3efp/xNWf05mpXvxN9TdfmA++BdXi5Nr4UAgjos2kFmQpZpQaCDw==} + '@jsonforms/core@3.7.0': + resolution: {integrity: sha512-CE9viWtwi9QWLqlWLeOul1/R1GRAyOA9y6OoUpsCc0FhyR+g5p29F3k0fUExHWxL0Sf4KHcXYkfhtqfRBPS8ww==} - '@jsonforms/material-renderers@3.6.0': - resolution: {integrity: sha512-23ktHVnDDykOXQP2go312/7yNKiR1f/o0GJ2xNg+LVH6PtCWtzdPxaY6WFKWLt84s1DgEHyCw466XEVrPec5dA==} + '@jsonforms/material-renderers@3.7.0': + resolution: {integrity: sha512-WO9D3zigJ/x/gCckEGxvfQgrdLuy6X6g76hHMlo3KCsusEvabLQHvYz3EJmOOBsuEu8JYXgZetTKjZ44WaBXww==} peerDependencies: - '@emotion/react': ^11.4.1 + '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 - '@jsonforms/core': 3.6.0 - '@jsonforms/react': 3.6.0 - '@mui/icons-material': ^5.11.16 || ^6.0.0 - '@mui/material': ^5.13.0 || ^6.0.0 - '@mui/x-date-pickers': ^6.0.0 || ^7.0.0 + '@jsonforms/core': 3.7.0 + '@jsonforms/react': 3.7.0 + '@mui/icons-material': ^7.0.0 + '@mui/material': ^7.0.0 + '@mui/x-date-pickers': ^8.0.0 react: ^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - '@jsonforms/react@3.6.0': - resolution: {integrity: sha512-dor7FYltCkNkAM+SVZGtabjpUhGlj0/coAqx7GIZ8h+leET+d1sLEAc8kfxxh6gZBq9C4KAErb0Pj3uHedOs9Q==} + '@jsonforms/react@3.7.0': + resolution: {integrity: sha512-HkY7qAx8vW97wPEgZ7GxCB3iiXG1c95GuObxtcDHGPBJWMwnxWBnVYJmv5h7nthrInKsQKHZL5OusnC/sj/1GQ==} peerDependencies: - '@jsonforms/core': 3.6.0 + '@jsonforms/core': 3.7.0 react: ^16.12.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 '@mdx-js/react@3.1.0': @@ -1229,27 +1233,27 @@ packages: '@types/react': '>=16' react: '>=16' - '@mui/core-downloads-tracker@6.3.1': - resolution: {integrity: sha512-2OmnEyoHpj5//dJJpMuxOeLItCCHdf99pjMFfUFdBteCunAK9jW+PwEo4mtdGcLs7P+IgZ+85ypd52eY4AigoQ==} + '@mui/core-downloads-tracker@7.3.10': + resolution: {integrity: sha512-vrOpWRmPJSuwLo23J62wggEm/jvGdzqctej+UOCtgDUz6nZJQuj3ByPccVyaa7eQmwAzUwKN56FQPMKkqbj1GA==} - '@mui/icons-material@6.3.1': - resolution: {integrity: sha512-nJmWj1PBlwS3t1PnoqcixIsftE+7xrW3Su7f0yrjPw4tVjYrgkhU0hrRp+OlURfZ3ptdSkoBkalee9Bhf1Erfw==} + '@mui/icons-material@7.3.10': + resolution: {integrity: sha512-Au0ma4NSKGKNiimukj8UT/W1x2Qx6Qwn2RvFGykiSqVLYBNlIOPbjnIMvrwLGLu89EEpTVdu/ys/OduZR+tWqw==} engines: {node: '>=14.0.0'} peerDependencies: - '@mui/material': ^6.3.1 + '@mui/material': ^7.3.10 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true - '@mui/material@6.3.1': - resolution: {integrity: sha512-ynG9ayhxgCsHJ/dtDcT1v78/r2GwQyP3E0hPz3GdPRl0uFJz/uUTtI5KFYwadXmbC+Uv3bfB8laZ6+Cpzh03gA==} + '@mui/material@7.3.10': + resolution: {integrity: sha512-cHvGOk2ZEfbQt3LnGe0ZKd/ETs9gsUpkW66DCO+GSjMZhpdKU4XsuIr7zJ/B/2XaN8ihxuzHfYAR4zPtCN4RYg==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.5.0 '@emotion/styled': ^11.3.0 - '@mui/material-pigment-css': ^6.3.1 + '@mui/material-pigment-css': ^7.3.10 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1263,8 +1267,18 @@ packages: '@types/react': optional: true - '@mui/private-theming@6.3.1': - resolution: {integrity: sha512-g0u7hIUkmXmmrmmf5gdDYv9zdAig0KoxhIQn1JN8IVqApzf/AyRhH3uDGx5mSvs8+a1zb4+0W6LC260SyTTtdQ==} + '@mui/private-theming@7.3.10': + resolution: {integrity: sha512-j3EZN+zOctxUISvJSmsEPo5o2F8zse4l5vRkBY+ps6UtnL6J7o14kUaI4w7gwo73id9e3cDNMVQK/9BVaMHVBw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/private-theming@9.0.0': + resolution: {integrity: sha512-JtuZoaiCqwD6vjgYu6Xp3T7DZkrxJlgtDz5yESzhI34fEX5hHMh2VJUbuL9UOg8xrfIFMrq6dcYoH/7Zi4G0RA==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -1273,8 +1287,21 @@ packages: '@types/react': optional: true - '@mui/styled-engine@6.3.1': - resolution: {integrity: sha512-/7CC0d2fIeiUxN5kCCwYu4AWUDd9cCTxWCyo0v/Rnv6s8uk6hWgJC3VLZBoDENBHf/KjqDZuYJ2CR+7hD6QYww==} + '@mui/styled-engine@7.3.10': + resolution: {integrity: sha512-WxE9SiF8xskAQqGjsp0poXCkCqsoXFEsSr0HBXfApmGHR+DBnXRp+z46Vsltg4gpPM4Z96DeAQRpeAOnhNg7Ng==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + + '@mui/styled-engine@9.0.0': + resolution: {integrity: sha512-9RLGdX4Jg0aQPRuvqh/OLzYSPlgd5zyEw5/1HIRfdavSiOd03WtUaGZH9/w1RoTYuRKwpgy0hpIFaMHIqPVIWg==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.4.1 @@ -1286,8 +1313,8 @@ packages: '@emotion/styled': optional: true - '@mui/system@6.3.1': - resolution: {integrity: sha512-AwqQ3EAIT2np85ki+N15fF0lFXX1iFPqenCzVOSl3QXKy2eifZeGd9dGtt7pGMoFw5dzW4dRGGzRpLAq9rkl7A==} + '@mui/system@7.3.10': + resolution: {integrity: sha512-/sfPpdpJaQn7BSF+avjIdHSYmxHp0UOBYNxSG9QGKfMOD6sLANCpRPCnanq1Pe0lFf0NHkO2iUk0TNzdWC1USQ==} engines: {node: '>=14.0.0'} peerDependencies: '@emotion/react': ^11.5.0 @@ -1302,16 +1329,50 @@ packages: '@types/react': optional: true - '@mui/types@7.2.21': - resolution: {integrity: sha512-6HstngiUxNqLU+/DPqlUJDIPbzUBxIVHb1MmXP0eTWDIROiCR2viugXpEif0PPe2mLqqakPzzRClWAnK+8UJww==} + '@mui/system@9.0.0': + resolution: {integrity: sha512-YnC5Zg6j04IxiLc/boAKs0464jfZlLFVa7mf5E8lF0XOtZVUvG6R6gJK50lgUYdaaLdyLfxF6xR7LaPuEpeT/g==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + + '@mui/types@7.4.12': + resolution: {integrity: sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 peerDependenciesMeta: '@types/react': optional: true - '@mui/utils@6.3.1': - resolution: {integrity: sha512-sjGjXAngoio6lniQZKJ5zGfjm+LD2wvLwco7FbKe1fu8A7VIFmz2SwkLb+MDPLNX1lE7IscvNNyh1pobtZg2tw==} + '@mui/types@9.0.0': + resolution: {integrity: sha512-i1cuFCAWN44b3AJWO7mh7tuh1sqbQSeVr/94oG0TX5uXivac8XalgE4/6fQZcmGZigzbQ35IXxj/4jLpRIBYZg==} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@7.3.10': + resolution: {integrity: sha512-7y2eIfy0h7JPz+Yy4pS+wgV68d46PuuxDqKBN4Q8VlPQSsCAGwroMCV6xWyc7g9dvEp8ZNFsknc59GHWO+r6Ow==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 + react: ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + '@mui/utils@9.0.0': + resolution: {integrity: sha512-bQcqyg/gjULUqTuyUjSAFr6LQGLvtkNtDbJerAtoUn9kGZ0hg5QJiN1PLHMLbeFpe3te1831uq7GFl2ITokGdg==} engines: {node: '>=14.0.0'} peerDependencies: '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 @@ -2058,6 +2119,9 @@ packages: '@types/prop-types@15.7.14': resolution: {integrity: sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ==} + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + '@types/react-dom@18.3.5': resolution: {integrity: sha512-P4t6saawp+b/dFrUr2cvkVsfvPguwsxtH6dNIYRllMsefqFzkZk5UIjzyDOv5g1dXIPdG4Sp1yCR4Z6RCUsG/Q==} peerDependencies: @@ -2651,6 +2715,9 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + data-urls@3.0.2: resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} engines: {node: '>=12'} @@ -4162,8 +4229,8 @@ packages: react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@19.0.0: - resolution: {integrity: sha512-H91OHcwjZsbq3ClIDHMzBShc1rotbfACdWENsmEf0IFvZ3FgGPtdHMcsv45bQ1hAbgdfiA8SnxTKfDS+x/8m2g==} + react-is@19.2.4: + resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} react-router-dom@7.12.0: resolution: {integrity: sha512-pfO9fiBcpEfX4Tx+iTYKDtPbrSLLCbwJ5EqP+SPYQu1VYCXdy79GSj0wttR0U4cikVdlImZuEZ/9ZNCgoaxwBA==} @@ -5657,6 +5724,8 @@ snapshots: dependencies: regenerator-runtime: 0.14.1 + '@babel/runtime@7.29.2': {} + '@babel/template@7.25.9': dependencies: '@babel/code-frame': 7.26.2 @@ -6021,30 +6090,30 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.0 - '@jsonforms/core@3.6.0': + '@jsonforms/core@3.7.0': dependencies: '@types/json-schema': 7.0.15 ajv: 8.17.1 ajv-formats: 2.1.1(ajv@8.17.1) lodash: 4.17.21 - '@jsonforms/material-renderers@3.6.0(urt6nrxqlj2unp4w44pnlaf44a)': + '@jsonforms/material-renderers@3.7.0(tycpmb7mlqgjusrbuymfwpcqdy)': dependencies: '@date-io/dayjs': 3.2.0(dayjs@1.10.7) '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@jsonforms/core': 3.6.0 - '@jsonforms/react': 3.6.0(@jsonforms/core@3.6.0)(react@18.3.1) - '@mui/icons-material': 6.3.1(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/material': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(dayjs@1.10.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@jsonforms/core': 3.7.0 + '@jsonforms/react': 3.7.0(@jsonforms/core@3.7.0)(react@18.3.1) + '@mui/icons-material': 7.3.10(@mui/material@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + '@mui/material': 7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@9.0.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(dayjs@1.10.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) dayjs: 1.10.7 lodash: 4.17.21 react: 18.3.1 - '@jsonforms/react@3.6.0(@jsonforms/core@3.6.0)(react@18.3.1)': + '@jsonforms/react@3.7.0(@jsonforms/core@3.7.0)(react@18.3.1)': dependencies: - '@jsonforms/core': 3.6.0 + '@jsonforms/core': 3.7.0 lodash: 4.17.21 react: 18.3.1 @@ -6054,68 +6123,90 @@ snapshots: '@types/react': 18.3.18 react: 18.3.1 - '@mui/core-downloads-tracker@6.3.1': {} + '@mui/core-downloads-tracker@7.3.10': {} - '@mui/icons-material@6.3.1(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@mui/icons-material@7.3.10(@mui/material@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/material': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@babel/runtime': 7.29.2 + '@mui/material': 7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/material@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/core-downloads-tracker': 6.3.1 - '@mui/system': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/types': 7.2.21(@types/react@18.3.18) - '@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.29.2 + '@mui/core-downloads-tracker': 7.3.10 + '@mui/system': 7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + '@mui/types': 7.4.12(@types/react@18.3.18) + '@mui/utils': 7.3.10(@types/react@18.3.18)(react@18.3.1) '@popperjs/core': 2.11.8 '@types/react-transition-group': 4.4.12(@types/react@18.3.18) clsx: 2.1.1 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-is: 19.0.0 + react-is: 19.2.4 react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) optionalDependencies: '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@types/react': 18.3.18 - '@mui/private-theming@6.3.1(@types/react@18.3.18)(react@18.3.1)': + '@mui/private-theming@7.3.10(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.29.2 + '@mui/utils': 7.3.10(@types/react@18.3.18)(react@18.3.1) prop-types: 15.8.1 react: 18.3.1 optionalDependencies: '@types/react': 18.3.18 - '@mui/styled-engine@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1)': + '@mui/private-theming@9.0.0(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.29.2 + '@mui/utils': 9.0.0(@types/react@18.3.18)(react@18.3.1) + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@types/react': 18.3.18 + + '@mui/styled-engine@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 '@emotion/cache': 11.14.0 '@emotion/serialize': 1.3.3 '@emotion/sheet': 1.4.0 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 18.3.1 optionalDependencies: '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/system@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + '@mui/styled-engine@9.0.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/private-theming': 6.3.1(@types/react@18.3.18)(react@18.3.1) - '@mui/styled-engine': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1) - '@mui/types': 7.2.21(@types/react@18.3.18) - '@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.29.2 + '@emotion/cache': 11.14.0 + '@emotion/serialize': 1.3.3 + '@emotion/sheet': 1.4.0 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 18.3.1 + optionalDependencies: + '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + + '@mui/system@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/private-theming': 7.3.10(@types/react@18.3.18)(react@18.3.1) + '@mui/styled-engine': 7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1) + '@mui/types': 7.4.12(@types/react@18.3.18) + '@mui/utils': 7.3.10(@types/react@18.3.18)(react@18.3.1) clsx: 2.1.1 - csstype: 3.1.3 + csstype: 3.2.3 prop-types: 15.8.1 react: 18.3.1 optionalDependencies: @@ -6123,28 +6214,64 @@ snapshots: '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@types/react': 18.3.18 - '@mui/types@7.2.21(@types/react@18.3.18)': + '@mui/system@9.0.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/private-theming': 9.0.0(@types/react@18.3.18)(react@18.3.1) + '@mui/styled-engine': 9.0.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(react@18.3.1) + '@mui/types': 9.0.0(@types/react@18.3.18) + '@mui/utils': 9.0.0(@types/react@18.3.18)(react@18.3.1) + clsx: 2.1.1 + csstype: 3.2.3 + prop-types: 15.8.1 + react: 18.3.1 optionalDependencies: + '@emotion/react': 11.14.0(@types/react@18.3.18)(react@18.3.1) + '@emotion/styled': 11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) '@types/react': 18.3.18 - '@mui/utils@6.3.1(@types/react@18.3.18)(react@18.3.1)': + '@mui/types@7.4.12(@types/react@18.3.18)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/types': 7.2.21(@types/react@18.3.18) - '@types/prop-types': 15.7.14 + '@babel/runtime': 7.29.2 + optionalDependencies: + '@types/react': 18.3.18 + + '@mui/types@9.0.0(@types/react@18.3.18)': + dependencies: + '@babel/runtime': 7.29.2 + optionalDependencies: + '@types/react': 18.3.18 + + '@mui/utils@7.3.10(@types/react@18.3.18)(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/types': 7.4.12(@types/react@18.3.18) + '@types/prop-types': 15.7.15 clsx: 2.1.1 prop-types: 15.8.1 react: 18.3.1 - react-is: 19.0.0 + react-is: 19.2.4 optionalDependencies: '@types/react': 18.3.18 - '@mui/x-date-pickers@7.29.4(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(dayjs@1.10.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + '@mui/utils@9.0.0(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/material': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@mui/system': 6.3.1(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) - '@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.29.2 + '@mui/types': 9.0.0(@types/react@18.3.18) + '@types/prop-types': 15.7.15 + clsx: 2.1.1 + prop-types: 15.8.1 + react: 18.3.1 + react-is: 19.2.4 + optionalDependencies: + '@types/react': 18.3.18 + + '@mui/x-date-pickers@7.29.4(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@mui/material@7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@9.0.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(dayjs@1.10.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.29.2 + '@mui/material': 7.3.10(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@mui/system': 9.0.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@emotion/styled@11.14.0(@emotion/react@11.14.0(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1))(@types/react@18.3.18)(react@18.3.1) + '@mui/utils': 7.3.10(@types/react@18.3.18)(react@18.3.1) '@mui/x-internals': 7.29.0(@types/react@18.3.18)(react@18.3.1) '@types/react-transition-group': 4.4.12(@types/react@18.3.18) clsx: 2.1.1 @@ -6161,8 +6288,8 @@ snapshots: '@mui/x-internals@7.29.0(@types/react@18.3.18)(react@18.3.1)': dependencies: - '@babel/runtime': 7.26.0 - '@mui/utils': 6.3.1(@types/react@18.3.18)(react@18.3.1) + '@babel/runtime': 7.29.2 + '@mui/utils': 7.3.10(@types/react@18.3.18)(react@18.3.1) react: 18.3.1 transitivePeerDependencies: - '@types/react' @@ -6871,6 +6998,8 @@ snapshots: '@types/prop-types@15.7.14': {} + '@types/prop-types@15.7.15': {} + '@types/react-dom@18.3.5(@types/react@18.3.18)': dependencies: '@types/react': 18.3.18 @@ -7285,7 +7414,7 @@ snapshots: babel-plugin-macros@3.1.0: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.29.2 cosmiconfig: 7.1.0 resolve: 1.22.10 @@ -7592,6 +7721,8 @@ snapshots: csstype@3.1.3: {} + csstype@3.2.3: {} + data-urls@3.0.2: dependencies: abab: 2.0.6 @@ -7679,8 +7810,8 @@ snapshots: dom-helpers@5.2.1: dependencies: - '@babel/runtime': 7.26.0 - csstype: 3.1.3 + '@babel/runtime': 7.29.2 + csstype: 3.2.3 dom-serializer@1.4.1: dependencies: @@ -9251,7 +9382,7 @@ snapshots: react-is@17.0.2: {} - react-is@19.0.0: {} + react-is@19.2.4: {} react-router-dom@7.12.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: @@ -9269,7 +9400,7 @@ snapshots: react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.29.2 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -9318,7 +9449,7 @@ snapshots: regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.26.0 + '@babel/runtime': 7.29.2 regexp.prototype.flags@1.5.4: dependencies: diff --git a/rollup.config.mjs b/rollup.config.mjs index a575d944..f24542e1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -10,35 +10,20 @@ import packageJson from "./package.json" with { type: "json" }; export default [ { - input: "src/index.ts", - output: { - format: "cjs", - file: packageJson.main, - }, - plugins: [ - peerDepsExternal({ - includeDependencies: true, - }), - image(), - resolve(), - commonjs(), - terser(), - typescript({ - tsconfig: "./tsconfig.json", - exclude: ["**/*.stories.*", "**/*.test.*"], - }), - postcss({ - extensions: [".css"], - }), + input: ["src/index.ts", "src/navigation.ts", "src/themes.ts", "src/controls.ts"], + output: [ + { + format: "cjs", + dir: "dist", + entryFileNames: "[name].cjs.js", + }, + { + format: "esm", + sourcemap: true, + dir: "dist", + entryFileNames: "[name].esm.js", + }, ], - }, - { - input: "src/index.ts", - output: { - format: "esm", - sourcemap: true, - file: packageJson.module, - }, plugins: [ peerDepsExternal({ includeDependencies: true, diff --git a/src/__test-utils__/helpers.tsx b/src/__test-utils__/helpers.tsx index 9131904b..9f17382a 100644 --- a/src/__test-utils__/helpers.tsx +++ b/src/__test-utils__/helpers.tsx @@ -1,9 +1,8 @@ import React from "react"; -import { ThemeProvider } from "@mui/material/styles"; +import { ThemeProvider, ThemeProviderProps } from "@mui/material/styles"; import { DiamondTheme } from "../themes/DiamondTheme"; import { render, RenderResult } from "@testing-library/react"; -import { ThemeProviderProps } from "@mui/material/styles/ThemeProvider"; type ThemeProviderPropsWithOptionalTheme = Omit & Partial>; diff --git a/src/components/controls/ColourSchemeButton.tsx b/src/components/controls/ColourSchemeButton.tsx index 1eada4c6..7c7b1705 100644 --- a/src/components/controls/ColourSchemeButton.tsx +++ b/src/components/controls/ColourSchemeButton.tsx @@ -1,10 +1,8 @@ import { useColorScheme, useTheme } from "@mui/material"; import { IconButton, IconButtonProps } from "@mui/material"; -import { - LightMode as LightModeIcon, - Bedtime as BedtimeIcon, -} from "@mui/icons-material"; +import LightModeIcon from "@mui/icons-material/LightMode"; +import BedtimeIcon from "@mui/icons-material/Bedtime"; import { ColourSchemes } from "../../utils/globals"; diff --git a/src/components/controls/Image.stories.tsx b/src/components/controls/Image.stories.tsx new file mode 100644 index 00000000..2d7d0fc2 --- /dev/null +++ b/src/components/controls/Image.stories.tsx @@ -0,0 +1,42 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { Image } from "./Image"; + +import diamond from "../../public/images/diamond.jpg"; + +const meta: Meta = { + title: "Components/Controls/Image", + component: Image, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: "Image with placeholder, fallback and loading indicator", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const BasicImage: Story = { + args: { src: diamond, style: { width: "20vw" } }, + parameters: { + docs: { + description: { + story: "Basic Image", + }, + }, + }, +}; + +export const ErrorImage: Story = { + args: { src: "doesnotexist.jpg", style: { width: "20vw" } }, + parameters: { + docs: { + description: { + story: "Image displayed when original image fails to load", + }, + }, + }, +}; diff --git a/src/components/controls/Image.test.tsx b/src/components/controls/Image.test.tsx new file mode 100644 index 00000000..0b5dada3 --- /dev/null +++ b/src/components/controls/Image.test.tsx @@ -0,0 +1,27 @@ +import { fireEvent, render, screen } from "@testing-library/react"; +import { Image } from "./Image"; + +import placeholderStaticImport from "../../public/images/diamond.jpg"; + +describe("Image", () => { + it("should render spinner while image isn't loaded", () => { + render({"foo"}); + + const image = screen.getByAltText("foo"); + + expect(image).toHaveAttribute("aria-busy", "true"); + + fireEvent.load(image); + + expect(image).toHaveAttribute("aria-busy", "false"); + }); + + it("should render placeholder image if an error occurs while loading image", () => { + render({"foo"}); + + const image = screen.getByAltText("foo"); + fireEvent.error(image); + + expect(image).toHaveAttribute("aria-errormessage", "Image not available"); + }); +}); diff --git a/src/components/controls/Image.tsx b/src/components/controls/Image.tsx new file mode 100644 index 00000000..5f495fe1 --- /dev/null +++ b/src/components/controls/Image.tsx @@ -0,0 +1,87 @@ +"use client"; +import { + DetailedHTMLProps, + ImgHTMLAttributes, + SyntheticEvent, + useState, +} from "react"; +import placeholder from "../../public/generic/no-image.png"; +import CircularProgress from "@mui/material/CircularProgress"; +import Box from "@mui/material/Box"; + +export interface ImageProps + extends Omit< + Omit< + DetailedHTMLProps, HTMLImageElement>, + "onLoad" | "onError" + >, + "src" + > { + src?: string | null; + onLoad?: () => void; + onError?: () => void; +} + +/** + * Smart image component that displays a placeholder on error, and a loading indicator if the image is still loading + */ +export const Image = ({ src, alt, onLoad, onError, ...props }: ImageProps) => { + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + + const handleError = (e: SyntheticEvent) => { + if (onError) { + onError(); + } + + e.currentTarget.src = placeholder; + setIsError(true); + }; + + const handleLoad = () => { + if (onLoad) { + onLoad(); + } + + setIsLoading(false); + }; + + return ( + + {isLoading && ( + + + + )} + {alt + + ); +}; diff --git a/src/components/controls/ImageWithZoom.stories.tsx b/src/components/controls/ImageWithZoom.stories.tsx new file mode 100644 index 00000000..cca59f42 --- /dev/null +++ b/src/components/controls/ImageWithZoom.stories.tsx @@ -0,0 +1,42 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { ImageWithZoom } from "./ImageWithZoom"; + +import diamond from "../../public/images/diamond.jpg"; + +const meta: Meta = { + title: "Components/Controls/ImageWithZoom", + component: ImageWithZoom, + tags: ["autodocs"], + parameters: { + docs: { + description: { + component: "Image with user-controlled magnified area", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const BasicImage: Story = { + args: { src: diamond, alt: "Diamond" }, + parameters: { + docs: { + description: { + story: "Basic image with magnified view on side", + }, + }, + }, +}; + +export const Brightness: Story = { + args: { src: diamond, alt: "Diamond", brightness: 0.5 }, + parameters: { + docs: { + description: { + story: "Image with brightness filter applied", + }, + }, + }, +}; diff --git a/src/components/controls/ImageWithZoom.test.tsx b/src/components/controls/ImageWithZoom.test.tsx new file mode 100644 index 00000000..93f867bd --- /dev/null +++ b/src/components/controls/ImageWithZoom.test.tsx @@ -0,0 +1,68 @@ +import { ImageWithZoom } from "./ImageWithZoom"; +import { render, screen } from "@testing-library/react"; + +/** + * This is particularly hard to test without visual testing (screenshot matching) + * With unit tests, refs don't work properly, nor is it particularly useful because there might be visual changes, + * but CSS remains the same. We should revisit this once we implement visual matching through Playwright/Vitest browser mode. + */ + +vi.mock("./Image", () => ({ + Image: ({ + onLoad, + alt, + src, + onClick, + }: { + onClick?: (e: Record) => void; + onLoad?: () => void; + alt: string; + src: string; + }) => { + if (onLoad) { + onLoad(); + } + if (onClick) { + onClick({ + currentTarget: { + getBoundingClientRect: () => ({ + left: 0, + top: 0, + width: 100, + height: 100, + }), + }, + }); + } + return {alt}; + }, +})); + +describe("Image with Zoom Viewer", () => { + it("should update brightness/contrast", () => { + render( + , + ); + + // https://github.com/vitest-dev/vitest/issues/9797 + const zoomView = screen.getByLabelText("Zoom View"); + expect(zoomView).toHaveAttribute( + "style", + expect.stringContaining("brightness(1.5)"), + ); + expect(zoomView).toHaveAttribute( + "style", + expect.stringContaining("contrast(0.5)"), + ); + }); + + it("should update image colour inversion", () => { + render(); + + const zoomView = screen.getByLabelText("Zoom View"); + expect(zoomView).toHaveAttribute( + "style", + expect.stringContaining("invert(1)"), + ); + }); +}); diff --git a/src/components/controls/ImageWithZoom.tsx b/src/components/controls/ImageWithZoom.tsx new file mode 100644 index 00000000..e512b4aa --- /dev/null +++ b/src/components/controls/ImageWithZoom.tsx @@ -0,0 +1,217 @@ +"use client"; +import { useCallback, useMemo, useState, useRef, useEffect } from "react"; +import { Typography, useTheme } from "@mui/material"; +import { clampNumber } from "../../utils/generic"; +import { Image } from "./Image"; +import { useWindowSize } from "../../utils/hooks"; + +export interface ImageWithZoomProps { + src: string; + alt?: string; + /** Width of zoomed view (magnified view) */ + zoomWidth?: string; + /** Width of lens. The zoom effect is a ratio between the lens size and the width of the magnified view. */ + lensWidth?: string; + /** Max total width */ + maxWidth?: string; + /** Always leave enough space on the left for the magnified view */ + alwaysPad?: boolean; + width?: string; + /** Whether to invert colours */ + invert?: boolean; + /** CSS filter brightness value (0 to 2, 1 being the default) */ + brightness?: number; + /** CSS filter contrast value (0 to 2, 1 being the default) */ + contrast?: number; +} + +/** + * Image viewer with zoomed in view that the user can move around + */ +export const ImageWithZoom = ({ + src, + alt, + width = "80%", + zoomWidth = "15vh", + maxWidth = "100vw", + lensWidth = "4vh", + alwaysPad = false, + invert = false, + brightness = 1, + contrast = 1, +}: ImageWithZoomProps) => { + const [isLoading, setIsLoading] = useState(true); + // Whether or not user has already zoomed in at least once + const [userZoomedIn, setUserZoomedIn] = useState(false); + + const [windowWidth, windowHeight] = useWindowSize(); + + const lensRef = useRef(null); + const zoomRef = useRef(null); + // The zoom view wrapper is a separate ref because that way it's unaffected by image filters + const zoomViewRef = useRef(null); + + const { breakpoints } = useTheme(); + + const imageFilter = useMemo( + // CSS value responsible for applying filters + () => + `invert(${invert ? "1" : "0"}) brightness(${brightness}) contrast(${contrast})`, + [invert, brightness, contrast], + ); + + const moveZoomWindow = useCallback( + (windowPos = "0", hideLens = false) => { + if (zoomViewRef.current && zoomRef.current) { + if (hideLens && lensRef.current) { + lensRef.current.style.display = "none"; + } + // Position window to left of image on larger screens + // MUI breakpoints are not granular enough - the extra 100px is just to be on the safe side + if (window.innerWidth > breakpoints.values.xl + 100) { + zoomViewRef.current.style.left = "0px"; + } else { + // Move the zoomed in view left or right depending on where the lens is + // When the window is resized, it defaults to the left-hand side + zoomViewRef.current.style.left = windowPos; + } + } + }, + [zoomViewRef, zoomRef, lensRef, breakpoints], + ); + + // Width and height are dependencies as we need to listen to both to update the zoom window position + useEffect(() => { + moveZoomWindow("0", true); + }, [windowWidth, windowHeight, moveZoomWindow]); + + useEffect(() => { + // Reset loading indicator, user status when component is closed + return () => { + setIsLoading(false); + setUserZoomedIn(false); + }; + }, []); + + const updateMagPosition = useCallback( + (e: React.MouseEvent) => { + if (lensRef.current && zoomRef.current && zoomViewRef.current) { + // The zoom window starts off hidden + zoomViewRef.current.style.display = "block"; + lensRef.current.style.display = "block"; + setUserZoomedIn(true); + + const target = e.currentTarget.getBoundingClientRect(); + const ratioX = + zoomRef.current.offsetWidth / lensRef.current.offsetWidth; + const ratioY = + zoomRef.current.offsetHeight / lensRef.current.offsetHeight; + const halfWidth = lensRef.current.offsetWidth / 2; + const halfHeight = lensRef.current.offsetHeight / 2; + + // Limit the lens location to within bounds + /* + * If you modify the lens size in the CSS file, it will result in a zoom change inversely proportional + * to the size of the lens. The larger the lens, the lesser the zoom, and vice versa. + * This is to keep the lens true to what is actually displayed on the zoomed in view. + */ + const posX = clampNumber( + e.clientX - target.left - halfWidth, + target.width - halfWidth * 2, + ); + const posY = clampNumber( + e.clientY - target.top - halfHeight, + target.height - halfHeight * 2, + ); + + lensRef.current.style.left = `${posX}px`; + lensRef.current.style.top = `${posY}px`; + + // This moves the background in the zoom div, which corresponds to a zoomed in version of the larger + // image. + zoomRef.current.style.backgroundSize = `${target.width * ratioX}px ${target.height * ratioY}px`; + zoomRef.current.style.backgroundPosition = `-${posX * ratioX}px -${posY * ratioY}px`; + + moveZoomWindow( + posX > target.width / 2 + ? "0" + : `${target.width - zoomRef.current.offsetWidth}px`, + ); + } + }, + [lensRef, zoomRef, zoomViewRef, moveZoomWindow], + ); + + return ( +
+ {!isLoading && ( +
+
+
+ )} +
+ {!isLoading && ( +
+ )} + {alt} setIsLoading(false)} + onClick={updateMagPosition} + /> +
+ {!isLoading && ( + + Click to zoom in + + )} +
+ ); +}; diff --git a/src/components/controls/ScrollableImages.tsx b/src/components/controls/ScrollableImages.tsx index 44c02407..780ccfe0 100644 --- a/src/components/controls/ScrollableImages.tsx +++ b/src/components/controls/ScrollableImages.tsx @@ -1,11 +1,10 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { Box, Button, IconButton, Slider, Stack } from "@mui/material"; -import { - ArrowBack as ArrowBackIcon, - ArrowForward as ArrowForwardIcon, - ArrowBackIosNew as ArrowBackIosNewIcon, - ArrowForwardIos as ArrowForwardIosIcon, -} from "@mui/icons-material"; + +import ArrowBackIcon from "@mui/icons-material/ArrowBack"; +import ArrowForwardIcon from "@mui/icons-material/ArrowForward"; +import ArrowBackIosNewIcon from "@mui/icons-material/ArrowBackIosNew"; +import ArrowForwardIosIcon from "@mui/icons-material/ArrowForwardIos"; import { extractFramesFromTiff, isTiff } from "../../utils/TiffUtils"; diff --git a/src/components/controls/User.tsx b/src/components/controls/User.tsx index d7039dba..7c422594 100644 --- a/src/components/controls/User.tsx +++ b/src/components/controls/User.tsx @@ -99,7 +99,9 @@ const User = ({ = { diff --git a/src/components/navigation/NavMenu.tsx b/src/components/navigation/NavMenu.tsx index 351b8822..6a75fe08 100644 --- a/src/components/navigation/NavMenu.tsx +++ b/src/components/navigation/NavMenu.tsx @@ -8,7 +8,7 @@ import { type MenuItemProps, } from "@mui/material"; import React, { useState, forwardRef, useId } from "react"; -import { ExpandMore as ExpandMoreIcon } from "@mui/icons-material"; +import ExpandMoreIcon from "@mui/icons-material/Expand"; import { NavLink, NavLinkProps } from "./Navbar"; type NavMenuLinkProps = MenuItemProps & NavLinkProps; diff --git a/src/controls.ts b/src/controls.ts new file mode 100644 index 00000000..eabcf5d5 --- /dev/null +++ b/src/controls.ts @@ -0,0 +1,10 @@ +export * from "./components/controls/AppTitlebar"; +export * from "./components/controls/Bar"; +export * from "./components/controls/ColourSchemeButton"; +export * from "./components/controls/ImageColourSchemeSwitch"; +export * from "./components/controls/Logo"; +export * from "./components/controls/Progress"; +export * from "./components/controls/ProgressDelayed"; +export * from "./components/controls/User"; +export * from "./components/controls/ScrollableImages"; +export * from "./components/controls/VisitInput"; diff --git a/src/navigation.ts b/src/navigation.ts new file mode 100644 index 00000000..8891ee86 --- /dev/null +++ b/src/navigation.ts @@ -0,0 +1,4 @@ +export * from "./components/navigation/Breadcrumbs"; +export * from "./components/navigation/Footer"; +export * from "./components/navigation/Navbar"; +export * from "./components/navigation/NavMenu"; diff --git a/src/public/generic/no-image.png b/src/public/generic/no-image.png new file mode 100644 index 00000000..42f17e7f Binary files /dev/null and b/src/public/generic/no-image.png differ diff --git a/src/themes.ts b/src/themes.ts new file mode 100644 index 00000000..f55fdf99 --- /dev/null +++ b/src/themes.ts @@ -0,0 +1,6 @@ +export * from "./themes/BaseTheme"; +export * from "./themes/DiamondTheme"; +export * from "./themes/DiamondOldTheme"; +export * from "./themes/GenericTheme"; +export * from "./themes/ThemeProvider"; +export * from "./themes/ThemeManager"; diff --git a/src/themes/DiamondTheme.ts b/src/themes/DiamondTheme.ts index be9ebc5d..ccf8f385 100644 --- a/src/themes/DiamondTheme.ts +++ b/src/themes/DiamondTheme.ts @@ -65,6 +65,22 @@ const DiamondThemeOptions = mergeThemeOptions({ }, }, components: { + MuiTypography: { + defaultProps: { + variantMapping: { + h1: "h2", + h2: "h2", + h3: "h2", + h4: "h2", + h5: "h2", + h6: "h2", + subtitle1: "h2", + subtitle2: "h2", + body1: "span", + body2: "span", + }, + }, + }, MuiButton: { styleOverrides: { root: ({ theme }: { theme: Theme }) => ({ diff --git a/src/themes/ThemeProvider.tsx b/src/themes/ThemeProvider.tsx index d4328d3b..2ad79695 100644 --- a/src/themes/ThemeProvider.tsx +++ b/src/themes/ThemeProvider.tsx @@ -1,7 +1,7 @@ import { ThemeProvider as Mui_ThemeProvider } from "@mui/material/styles"; import { CssBaseline } from "@mui/material"; import { GenericTheme } from "./GenericTheme"; -import { ThemeProviderProps as Mui_ThemeProviderProps } from "@mui/material/styles/ThemeProvider"; +import { ThemeProviderProps as Mui_ThemeProviderProps } from "@mui/material/styles"; interface ThemeProviderProps extends Partial { baseline?: boolean; diff --git a/src/utils/generic.ts b/src/utils/generic.ts new file mode 100644 index 00000000..c8a5b8f2 --- /dev/null +++ b/src/utils/generic.ts @@ -0,0 +1,10 @@ +/** Clamp number between two min/max values + * + * @param value Number to champ + * @param max Maximum value + * @param min Minimum value (default 0) + * + * @returns Value or max/min if it goes over/under those thresholds + */ +export const clampNumber = (value: number, max: number, min = 0) => + Math.min(Math.max(value, min), max); diff --git a/src/utils/hooks.ts b/src/utils/hooks.ts new file mode 100644 index 00000000..45831f05 --- /dev/null +++ b/src/utils/hooks.ts @@ -0,0 +1,19 @@ +import { useState, useLayoutEffect } from "react"; + +/** + * Hook for listening to window size changes + * + * @returns Window width and height + */ +export const useWindowSize = () => { + const [size, setSize] = useState([0, 0]); + useLayoutEffect(() => { + function updateSize() { + setSize([window.innerWidth, window.innerHeight]); + } + window.addEventListener("resize", updateSize); + updateSize(); + return () => window.removeEventListener("resize", updateSize); + }, []); + return size; +};