From 82be3feae2de7dfe56aecd02e2b7cc0e1d59032e Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 10 Apr 2026 16:21:40 +0100 Subject: [PATCH 1/8] Upgrade to MUI v7 --- package.json | 12 +- pnpm-lock.yaml | 323 +++++++++++++++++++++++---------- src/__test-utils__/helpers.tsx | 3 +- src/themes/DiamondTheme.ts | 16 ++ src/themes/ThemeProvider.tsx | 2 +- 5 files changed, 251 insertions(+), 105 deletions(-) diff --git a/package.json b/package.json index 343e3104..22b9eed0 100644 --- a/package.json +++ b/package.json @@ -29,16 +29,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/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/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; From 8f2fb6c15867bd590a00097d2c6fbec16f5c4e46 Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 10 Apr 2026 16:22:46 +0100 Subject: [PATCH 2/8] Split package into navigation, themes --- package.json | 17 ++++++++ rollup.config.mjs | 41 ++++++------------- .../controls/ColourSchemeButton.tsx | 6 +-- src/components/controls/ScrollableImages.tsx | 11 +++-- src/components/navigation/Breadcrumbs.tsx | 6 +-- src/components/navigation/NavMenu.stories.tsx | 2 +- src/components/navigation/NavMenu.tsx | 2 +- src/navigation.ts | 4 ++ src/themes.ts | 6 +++ 9 files changed, 51 insertions(+), 44 deletions(-) create mode 100644 src/navigation.ts create mode 100644 src/themes.ts diff --git a/package.json b/package.json index 22b9eed0..98eeda9f 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,23 @@ "main": "dist/index.cjs.js", "module": "dist/index.esm.js", "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.esm.js", + "require": "./dist/index.cjs.js", + "types": "./dist/index.d.js" + }, + "./navigation": { + "import": "./dist/navigation.esm.js", + "require": "./dist/navigation.cjs.js", + "types": "./dist/navigation.d.js" + }, + "./themes": { + "import": "./dist/themes.esm.js", + "require": "./dist/themes.cjs.js", + "types": "./dist/themes.d.js" + } + }, "scripts": { "build": "tsc -b && rollup --config rollup.config.mjs", "docs:build": "typedoc src", diff --git a/rollup.config.mjs b/rollup.config.mjs index a575d944..cfe3c5ed 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"], + 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/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/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/navigation/Breadcrumbs.tsx b/src/components/navigation/Breadcrumbs.tsx index 874e0ede..7e0bc560 100644 --- a/src/components/navigation/Breadcrumbs.tsx +++ b/src/components/navigation/Breadcrumbs.tsx @@ -6,10 +6,8 @@ import { styled, Typography, } from "@mui/material"; -import { - Home as HomeIcon, - NavigateNext as NavigateNextIcon, -} from "@mui/icons-material"; +import HomeIcon from "@mui/icons-material/Home"; +import NavigateNextIcon from "@mui/icons-material/NavigateNext"; import { CustomLink } from "types/links"; import { Bar, BarProps } from "../controls/Bar"; diff --git a/src/components/navigation/NavMenu.stories.tsx b/src/components/navigation/NavMenu.stories.tsx index 6f2a8965..fca2e9a8 100644 --- a/src/components/navigation/NavMenu.stories.tsx +++ b/src/components/navigation/NavMenu.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { NavMenu, NavMenuLink } from "./NavMenu"; import { Button, Divider, Typography } from "@mui/material"; -import { Autorenew } from "@mui/icons-material"; +import Autorenew from "@mui/icons-material/Autorenew"; import { MockLink } from "../../utils/MockLink"; const meta: Meta = { 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/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/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"; From 5f5cdae71d60879d29140ec272af74b67c0abd88 Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Mon, 13 Apr 2026 13:21:31 +0100 Subject: [PATCH 3/8] Export controls, fix typos --- package.json | 11 ++++++++--- rollup.config.mjs | 2 +- src/controls.ts | 10 ++++++++++ 3 files changed, 19 insertions(+), 4 deletions(-) create mode 100644 src/controls.ts diff --git a/package.json b/package.json index 98eeda9f..a47cc61d 100644 --- a/package.json +++ b/package.json @@ -18,17 +18,22 @@ ".": { "import": "./dist/index.esm.js", "require": "./dist/index.cjs.js", - "types": "./dist/index.d.js" + "types": "./dist/index.d.ts" }, "./navigation": { "import": "./dist/navigation.esm.js", "require": "./dist/navigation.cjs.js", - "types": "./dist/navigation.d.js" + "types": "./dist/navigation.d.ts" }, "./themes": { "import": "./dist/themes.esm.js", "require": "./dist/themes.cjs.js", - "types": "./dist/themes.d.js" + "types": "./dist/themes.d.ts" + }, + "./controls": { + "import": "./dist/controls.esm.js", + "require": "./dist/controls.cjs.js", + "types": "./dist/controls.d.ts" } }, "scripts": { diff --git a/rollup.config.mjs b/rollup.config.mjs index cfe3c5ed..f24542e1 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -10,7 +10,7 @@ import packageJson from "./package.json" with { type: "json" }; export default [ { - input: ["src/index.ts", "src/navigation.ts", "src/themes.ts"], + input: ["src/index.ts", "src/navigation.ts", "src/themes.ts", "src/controls.ts"], output: [ { format: "cjs", 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"; From d5b5c9b14291b2611365baa0ab2a6d2226d1140a Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 17 Apr 2026 10:07:06 +0100 Subject: [PATCH 4/8] Update export order to make Vitest happy --- package.json | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index a47cc61d..71d0541d 100644 --- a/package.json +++ b/package.json @@ -16,24 +16,24 @@ "types": "dist/index.d.ts", "exports": { ".": { - "import": "./dist/index.esm.js", + "types": "./dist/index.d.ts", "require": "./dist/index.cjs.js", - "types": "./dist/index.d.ts" + "import": "./dist/index.esm.js" }, "./navigation": { - "import": "./dist/navigation.esm.js", + "types": "./dist/navigation.d.ts", "require": "./dist/navigation.cjs.js", - "types": "./dist/navigation.d.ts" + "import": "./dist/navigation.esm.js" }, "./themes": { - "import": "./dist/themes.esm.js", + "types": "./dist/themes.d.ts", "require": "./dist/themes.cjs.js", - "types": "./dist/themes.d.ts" + "import": "./dist/themes.esm.js" }, "./controls": { - "import": "./dist/controls.esm.js", + "types": "./dist/controls.d.ts", "require": "./dist/controls.cjs.js", - "types": "./dist/controls.d.ts" + "import": "./dist/controls.esm.js" } }, "scripts": { From 282683c48e8fa89e9cb934a7c4635870f820f3a1 Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 17 Apr 2026 10:08:01 +0100 Subject: [PATCH 5/8] Add image and magnified image componets --- src/components/controls/Image.stories.tsx | 42 ++++ src/components/controls/Image.test.tsx | 27 +++ src/components/controls/Image.tsx | 87 +++++++ .../controls/ImageWithZoom.stories.tsx | 42 ++++ .../controls/ImageWithZoom.test.tsx | 68 ++++++ src/components/controls/ImageWithZoom.tsx | 213 ++++++++++++++++++ src/components/controls/User.tsx | 4 +- src/public/generic/no-image.png | Bin 0 -> 3877 bytes src/utils/generic.ts | 10 + src/utils/hooks.ts | 19 ++ 10 files changed, 511 insertions(+), 1 deletion(-) create mode 100644 src/components/controls/Image.stories.tsx create mode 100644 src/components/controls/Image.test.tsx create mode 100644 src/components/controls/Image.tsx create mode 100644 src/components/controls/ImageWithZoom.stories.tsx create mode 100644 src/components/controls/ImageWithZoom.test.tsx create mode 100644 src/components/controls/ImageWithZoom.tsx create mode 100644 src/public/generic/no-image.png create mode 100644 src/utils/generic.ts create mode 100644 src/utils/hooks.ts 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..fd5f1cfa --- /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 placeholder, fallback and loading indicator", + }, + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const BasicImage: Story = { + args: { src: diamond, alt: "Diamond" }, + parameters: { + docs: { + description: { + story: "Basic image with zoomable 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..fa3d1f7c --- /dev/null +++ b/src/components/controls/ImageWithZoom.tsx @@ -0,0 +1,213 @@ +"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; + /** 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 = "60vh", + zoomWidth = "15vh", + 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 && !userZoomedIn && ( + + Click to zoom in + + )} +
+ ); +}; 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 = ({ 35% z9bJcTM>ik8px_`QyQsvdfCR6=Ab*e{jC_22!h*tTqM~a4rn;v7B!mBl7z8;Or!XF3 zW)x@ci+Wc+`GL5hKqk%1YA5rCDEnT3rBC?|p<3|8>}76T76BLkBFvj77F;}-{n zu2I%t3jxmM2f$9^nTqN%roa%e0L!8ks1jaU4Gav-Yz_=lLQy3+T>}^xm<(+;1uzJ8 zEkKcD)K~@NS8n!n*utPH>VP5#vdEpafiJmZQ7$%(j54#hTpJiH_!ehi(JFXy0n@~o z6Y7G!M?YtRZ9y_zsH>$ZgMn#c!W=h-JMrpM(JlVq3rwEZiWnGoyw+(z(#OESBHRP! zvpv3qBF@0VJOR4|+vFz@3Eu~(I!1YeDFi^JnYsW2BQp~#3llTUWy}mr!i+3J3MMWA z0)hq(#tTFgm4SsA2e4>kWM%rLF7iGz-D=LO;+5x?W20slcv{r{XIPM%&cFW)@0y3- z|4B~{7uu#9Kjqdr`TB>Z22&$1|4K32+7 z^ZAlv5?0GQnJ?Tr$L>GF(u2RBZnu+}&VKlU%ZfY4FZ8-yEBO7jpzi0}^X*Fyet06N zWp4PT+81coclP%WGw)q|3KX&YQhT1ic*Pgy&jsJ(?H9<;ZZDYe>X0hO6s3SpCu~%J z^U}J`{|ul~ToAc%2ZhT9Z~y=ceS1dJ{diO`!BuDhOM7N^76vwWxH2#@2?`k+83!yB zP;hKibOPo>pxP$+l}&iVk+<$)#|8QP!^Qddi&uWM za}Hi*wRJ~FQFTm=g>By=`zVv<;6+wjEr23-fFd$3b=ypuPjbAv^hojKzv;$1m)J{9 z{Rjc%wXIZ1Q&Cjp=l2g3xDX8xy&*{<)9j;foGGCP{L$n&P2;Q1!c_(FJDZ( zccH|pN@w>izIK1Xv>pi@;Qc><9F)Qb)%UTuKzkVDl07DvdT>c{}| zxB@~yP?rOQhZL%eDhx2w8W2hum|&(Mg{lGrhXOGzEwnR1(RlND>W5Y&a9iscs0x@Ok8jfUR(?OXW%^3 zb0E3p;a^d%odK(-5+ z{CIp}qwgM_?~`0-Wv0sHpW@=)p(1_o12@wbVRgo>AhB%w-h^AqQ$I?~_9;vd*pqg| ztUX}E;oq~T$lcj9xl-42v#-wL-sF!x(k{hMuQjP%mWg6liZf@MU*Ug5yTf8y`&pAs zedhCzZO9coJHNx_R&r~foCDwUf^VD;g*=O5rtr9H_cvVM^4Docb7w|Pu(!tJirp81 z7S660=-hH#PQp_5@D24APt}t}HO(_mJ$_*N*1ln#!lNirptU*Tk0Z=Fo_~~AesRO@ zz<-8WN}hV&lT&2Qh08Op&%DONVKI4*%z_T5#Dnc0vZbC%%c-24tmZS*Cel-at2v7r7?`~RB&{jrVT literal 0 HcmV?d00001 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; +}; From f076635866cfc5f3748d28cc8af898e9b6f7baaa Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 17 Apr 2026 10:10:57 +0100 Subject: [PATCH 6/8] Update component descriptions --- src/components/controls/ImageWithZoom.stories.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/controls/ImageWithZoom.stories.tsx b/src/components/controls/ImageWithZoom.stories.tsx index fd5f1cfa..cca59f42 100644 --- a/src/components/controls/ImageWithZoom.stories.tsx +++ b/src/components/controls/ImageWithZoom.stories.tsx @@ -10,7 +10,7 @@ const meta: Meta = { parameters: { docs: { description: { - component: "Image with placeholder, fallback and loading indicator", + component: "Image with user-controlled magnified area", }, }, }, @@ -24,7 +24,7 @@ export const BasicImage: Story = { parameters: { docs: { description: { - story: "Basic image with zoomable view on side", + story: "Basic image with magnified view on side", }, }, }, From 5c73f015a8b36d12b7e4134ee93d90625fb61fb1 Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 17 Apr 2026 11:59:21 +0100 Subject: [PATCH 7/8] Add max width --- src/components/controls/ImageWithZoom.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/controls/ImageWithZoom.tsx b/src/components/controls/ImageWithZoom.tsx index fa3d1f7c..e512b4aa 100644 --- a/src/components/controls/ImageWithZoom.tsx +++ b/src/components/controls/ImageWithZoom.tsx @@ -12,6 +12,8 @@ export interface ImageWithZoomProps { 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; @@ -29,8 +31,9 @@ export interface ImageWithZoomProps { export const ImageWithZoom = ({ src, alt, - width = "60vh", + width = "80%", zoomWidth = "15vh", + maxWidth = "100vw", lensWidth = "4vh", alwaysPad = false, invert = false, @@ -145,6 +148,7 @@ export const ImageWithZoom = ({ width: userZoomedIn ? `calc(${zoomWidth} + ${width})` : width, position: "relative", alignSelf: "center", + maxWidth, paddingLeft: userZoomedIn || alwaysPad ? zoomWidth : 0, }} > @@ -197,13 +201,13 @@ export const ImageWithZoom = ({ onClick={updateMagPosition} />
- {!isLoading && !userZoomedIn && ( + {!isLoading && ( Click to zoom in From 922b73f8c8bdba2403c1da643127fe8e3358b316 Mon Sep 17 00:00:00 2001 From: Guilherme de Freitas Date: Fri, 24 Apr 2026 16:55:16 +0100 Subject: [PATCH 8/8] Fix zoom window positioning on phone screens --- src/components/controls/ImageWithZoom.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/components/controls/ImageWithZoom.tsx b/src/components/controls/ImageWithZoom.tsx index e512b4aa..fbda8a9b 100644 --- a/src/components/controls/ImageWithZoom.tsx +++ b/src/components/controls/ImageWithZoom.tsx @@ -1,6 +1,6 @@ "use client"; import { useCallback, useMemo, useState, useRef, useEffect } from "react"; -import { Typography, useTheme } from "@mui/material"; +import { Box, Typography, useTheme } from "@mui/material"; import { clampNumber } from "../../utils/generic"; import { Image } from "./Image"; import { useWindowSize } from "../../utils/hooks"; @@ -143,13 +143,16 @@ export const ImageWithZoom = ({ ); return ( -
{!isLoading && ( @@ -212,6 +215,6 @@ export const ImageWithZoom = ({ Click to zoom in )} -
+ ); };