diff --git a/site/package.json b/site/package.json index 8476c50b94423..f95d9b0a9acf8 100644 --- a/site/package.json +++ b/site/package.json @@ -45,6 +45,7 @@ "@mui/material": "5.14.0", "@mui/styles": "5.14.0", "@mui/system": "5.14.0", + "@mui/utils": "5.14.11", "@tanstack/react-query": "4.35.3", "@vitejs/plugin-react": "4.1.0", "@xstate/inspect": "0.8.0", diff --git a/site/pnpm-lock.yaml b/site/pnpm-lock.yaml index 99e40829a3d31..d4b34d74f9ba6 100644 --- a/site/pnpm-lock.yaml +++ b/site/pnpm-lock.yaml @@ -51,6 +51,9 @@ dependencies: '@mui/system': specifier: 5.14.0 version: 5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) + '@mui/utils': + specifier: 5.14.11 + version: 5.14.11(@types/react@18.2.6)(react@18.2.0) '@tanstack/react-query': specifier: 4.35.3 version: 4.35.3(react-dom@18.2.0)(react@18.2.0) @@ -2112,6 +2115,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 + dev: true /@babel/runtime@7.22.6: resolution: {integrity: sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==} @@ -2124,7 +2128,6 @@ packages: engines: {node: '>=6.9.0'} dependencies: regenerator-runtime: 0.14.0 - dev: true /@babel/template@7.22.15: resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} @@ -2239,7 +2242,7 @@ packages: resolution: {integrity: sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ==} dependencies: '@babel/helper-module-imports': 7.22.5 - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@emotion/hash': 0.9.1 '@emotion/memoize': 0.8.1 '@emotion/serialize': 1.1.2 @@ -3260,10 +3263,10 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@emotion/is-prop-valid': 1.2.1 '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.3(react@18.2.0) + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@popperjs/core': 2.11.8 '@types/react': 18.2.6 clsx: 1.2.1 @@ -3284,10 +3287,10 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@emotion/is-prop-valid': 1.2.1 '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.3(react@18.2.0) + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@popperjs/core': 2.11.8 '@types/react': 18.2.6 clsx: 1.2.1 @@ -3343,7 +3346,7 @@ packages: '@mui/material': 5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@mui/system': 5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.1(react@18.2.0) + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 clsx: 1.2.1 prop-types: 15.8.1 @@ -3376,7 +3379,7 @@ packages: '@mui/core-downloads-tracker': 5.14.2 '@mui/system': 5.14.0(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(@types/react@18.2.6)(react@18.2.0) '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.1(react@18.2.0) + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 '@types/react-transition-group': 4.4.6 clsx: 1.2.1 @@ -3398,8 +3401,8 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 - '@mui/utils': 5.14.3(react@18.2.0) + '@babel/runtime': 7.23.1 + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 prop-types: 15.8.1 react: 18.2.0 @@ -3418,7 +3421,7 @@ packages: '@emotion/styled': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@emotion/cache': 11.11.0 '@emotion/react': 11.11.1(@types/react@18.2.6)(react@18.2.0) '@emotion/styled': 11.11.0(@emotion/react@11.11.1)(@types/react@18.2.6)(react@18.2.0) @@ -3441,7 +3444,7 @@ packages: '@emotion/hash': 0.9.1 '@mui/private-theming': 5.13.7(@types/react@18.2.6)(react@18.2.0) '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.1(react@18.2.0) + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 clsx: 1.2.1 csstype: 3.1.2 @@ -3480,7 +3483,7 @@ packages: '@mui/private-theming': 5.13.7(@types/react@18.2.6)(react@18.2.0) '@mui/styled-engine': 5.13.2(@emotion/react@11.11.1)(@emotion/styled@11.11.0)(react@18.2.0) '@mui/types': 7.2.4(@types/react@18.2.6) - '@mui/utils': 5.14.3(react@18.2.0) + '@mui/utils': 5.14.11(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 clsx: 1.2.1 csstype: 3.1.2 @@ -3499,29 +3502,19 @@ packages: '@types/react': 18.2.6 dev: false - /@mui/utils@5.14.1(react@18.2.0): - resolution: {integrity: sha512-39KHKK2JeqRmuUcLDLwM+c2XfVC136C5/yUyQXmO2PVbOb2Bol4KxtkssEqCbTwg87PSCG3f1Tb0keRsK7cVGw==} - engines: {node: '>=12.0.0'} - peerDependencies: - react: ^17.0.0 || ^18.0.0 - dependencies: - '@babel/runtime': 7.22.15 - '@types/prop-types': 15.7.5 - '@types/react-is': 18.2.1 - prop-types: 15.8.1 - react: 18.2.0 - react-is: 18.2.0 - dev: false - - /@mui/utils@5.14.3(react@18.2.0): - resolution: {integrity: sha512-gZ6Etw+ppO43GYc1HFZSLjwd4DoZoa+RrYTD25wQLfzcSoPjVoC/zZqA2Lkq0zjgwGBQOSxKZI6jfp9uXR+kgw==} + /@mui/utils@5.14.11(@types/react@18.2.6)(react@18.2.0): + resolution: {integrity: sha512-fmkIiCPKyDssYrJ5qk+dime1nlO3dmWfCtaPY/uVBqCRMBZ11JhddB9m8sjI2mgqQQwRJG5bq3biaosNdU/s4Q==} engines: {node: '>=12.0.0'} peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/prop-types': 15.7.5 - '@types/react-is': 18.2.1 + '@types/react': 18.2.6 prop-types: 15.8.1 react: 18.2.0 react-is: 18.2.0 @@ -3622,13 +3615,13 @@ packages: /@radix-ui/number@1.0.1: resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 dev: true /@radix-ui/primitive@1.0.1: resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 dev: true /@radix-ui/react-arrow@1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0): @@ -3644,7 +3637,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.6 '@types/react-dom': 18.2.4 @@ -3665,7 +3658,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@radix-ui/react-context': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) @@ -3685,7 +3678,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -3699,7 +3692,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -3713,7 +3706,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -3731,7 +3724,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) @@ -3752,7 +3745,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -3770,7 +3763,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.6)(react@18.2.0) @@ -3789,7 +3782,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 react: 18.2.0 @@ -3808,7 +3801,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@floating-ui/react-dom': 2.0.2(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-arrow': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) @@ -3838,7 +3831,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.6 '@types/react-dom': 18.2.4 @@ -3859,7 +3852,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-slot': 1.0.2(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 '@types/react-dom': 18.2.4 @@ -3880,7 +3873,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) @@ -3909,7 +3902,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-collection': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) @@ -3950,7 +3943,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.6 '@types/react-dom': 18.2.4 @@ -3967,7 +3960,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 react: 18.2.0 @@ -3986,7 +3979,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.6)(react@18.2.0) @@ -4013,7 +4006,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.6)(react@18.2.0) @@ -4036,7 +4029,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@radix-ui/react-direction': 1.0.1(@types/react@18.2.6)(react@18.2.0) @@ -4059,7 +4052,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -4073,7 +4066,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 react: 18.2.0 @@ -4088,7 +4081,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 react: 18.2.0 @@ -4103,7 +4096,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -4117,7 +4110,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/react': 18.2.6 react: 18.2.0 dev: true @@ -4131,7 +4124,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/rect': 1.0.1 '@types/react': 18.2.6 react: 18.2.0 @@ -4146,7 +4139,7 @@ packages: '@types/react': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.6)(react@18.2.0) '@types/react': 18.2.6 react: 18.2.0 @@ -4165,7 +4158,7 @@ packages: '@types/react-dom': optional: true dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.4)(@types/react@18.2.6)(react-dom@18.2.0)(react@18.2.0) '@types/react': 18.2.6 '@types/react-dom': 18.2.4 @@ -4176,7 +4169,7 @@ packages: /@radix-ui/rect@1.0.1: resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 dev: true /@remix-run/router@1.9.0: @@ -5547,7 +5540,7 @@ packages: engines: {node: '>=14'} dependencies: '@babel/code-frame': 7.22.13 - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 '@types/aria-query': 5.0.1 aria-query: 5.1.3 chalk: 4.1.2 @@ -5980,12 +5973,6 @@ packages: '@types/react': 18.2.6 dev: true - /@types/react-is@18.2.1: - resolution: {integrity: sha512-wyUkmaaSZEzFZivD8F2ftSyAfk6L+DfFliVj/mYdOXbVjRcS87fQJLTnhk6dRZPuJjI+9g6RZJO4PNCngUrmyw==} - dependencies: - '@types/react': 18.2.6 - dev: false - /@types/react-syntax-highlighter@15.5.5: resolution: {integrity: sha512-QH3JZQXa2usAvJvSsdSUJ4Yu4j8ReuZpgRrEW+XP+Rmosbn425YshW9iGEb/pAARm8496axHhHUPRH3UmTiB6A==} dependencies: @@ -6843,7 +6830,7 @@ packages: resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} engines: {node: '>=10', npm: '>=6'} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 cosmiconfig: 7.1.0 resolve: 1.22.4 dev: false @@ -7562,7 +7549,7 @@ packages: /css-vendor@2.0.8: resolution: {integrity: sha512-x9Aq0XTInxrkuFeHKbYC7zWY8ai7qJ04Kxd9MnvbC1uO5DagxoHQjm4JvG+vCdXOoFtCjbL2XSZfxmoYa9uQVQ==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 is-in-browser: 1.1.3 dev: false @@ -7862,7 +7849,7 @@ packages: /dom-helpers@5.2.1: resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 csstype: 3.1.2 dev: false @@ -10549,7 +10536,7 @@ packages: /jss-plugin-camel-case@10.10.0: resolution: {integrity: sha512-z+HETfj5IYgFxh1wJnUAU8jByI48ED+v0fuTuhKrPR+pRBYS2EDwbusU8aFOpCdYhtRc9zhN+PJ7iNE8pAWyPw==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 hyphenate-style-name: 1.0.4 jss: 10.10.0 dev: false @@ -10557,21 +10544,21 @@ packages: /jss-plugin-default-unit@10.10.0: resolution: {integrity: sha512-SvpajxIECi4JDUbGLefvNckmI+c2VWmP43qnEy/0eiwzRUsafg5DVSIWSzZe4d2vFX1u9nRDP46WCFV/PXVBGQ==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 jss: 10.10.0 dev: false /jss-plugin-global@10.10.0: resolution: {integrity: sha512-icXEYbMufiNuWfuazLeN+BNJO16Ge88OcXU5ZDC2vLqElmMybA31Wi7lZ3lf+vgufRocvPj8443irhYRgWxP+A==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 jss: 10.10.0 dev: false /jss-plugin-nested@10.10.0: resolution: {integrity: sha512-9R4JHxxGgiZhurDo3q7LdIiDEgtA1bTGzAbhSPyIOWb7ZubrjQe8acwhEQ6OEKydzpl8XHMtTnEwHXCARLYqYA==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 jss: 10.10.0 tiny-warning: 1.0.3 dev: false @@ -10579,14 +10566,14 @@ packages: /jss-plugin-props-sort@10.10.0: resolution: {integrity: sha512-5VNJvQJbnq/vRfje6uZLe/FyaOpzP/IH1LP+0fr88QamVrGJa0hpRRyAa0ea4U/3LcorJfBFVyC4yN2QC73lJg==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 jss: 10.10.0 dev: false /jss-plugin-rule-value-function@10.10.0: resolution: {integrity: sha512-uEFJFgaCtkXeIPgki8ICw3Y7VMkL9GEan6SqmT9tqpwM+/t+hxfMUdU4wQ0MtOiMNWhwnckBV0IebrKcZM9C0g==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 jss: 10.10.0 tiny-warning: 1.0.3 dev: false @@ -10594,7 +10581,7 @@ packages: /jss-plugin-vendor-prefixer@10.10.0: resolution: {integrity: sha512-UY/41WumgjW8r1qMCO8l1ARg7NHnfRVWRhZ2E2m0DMYsr2DD91qIXLyNhiX83hHswR7Wm4D+oDYNC1zWCJWtqg==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 css-vendor: 2.0.8 jss: 10.10.0 dev: false @@ -10602,7 +10589,7 @@ packages: /jss@10.10.0: resolution: {integrity: sha512-cqsOTS7jqPsPMjtKYDUpdFC0AbhYFLTcuGRqymgmdJIeQ8cH7+AgX7YSgQy79wXloZq2VvATYxUOUQEvS1V/Zw==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 csstype: 3.1.2 is-in-browser: 1.1.3 tiny-warning: 1.0.3 @@ -12230,7 +12217,7 @@ packages: peerDependencies: react: '>=16.13.1' dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 react: 18.2.0 dev: true @@ -12411,7 +12398,7 @@ packages: react: '>=16.6.0' react-dom: '>=16.6.0' dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 dom-helpers: 5.2.1 loose-envify: 1.4.0 prop-types: 15.8.1 @@ -12602,7 +12589,7 @@ packages: /regenerator-transform@0.15.2: resolution: {integrity: sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 dev: true /regexp-tree@0.1.27: @@ -12826,7 +12813,7 @@ packages: /rtl-css-js@1.16.1: resolution: {integrity: sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg==} dependencies: - '@babel/runtime': 7.22.15 + '@babel/runtime': 7.23.1 dev: false /run-async@2.4.1: @@ -14133,7 +14120,7 @@ packages: peerDependencies: eslint: '>=7' meow: ^9.0.0 - optionator: 0.9.3 + optionator: ^0.9.1 stylelint: '>=13' typescript: '*' vite: '>=2.0.0' diff --git a/site/src/components/OverflowY/OverflowY.stories.tsx b/site/src/components/OverflowY/OverflowY.stories.tsx new file mode 100644 index 0000000000000..128fdb6652b92 --- /dev/null +++ b/site/src/components/OverflowY/OverflowY.stories.tsx @@ -0,0 +1,34 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { OverflowY } from "./OverflowY"; + +const numbers: number[] = []; +for (let i = 0; i < 20; i++) { + numbers.push(i + 1); +} + +const meta: Meta = { + title: "components/OverflowY", + component: OverflowY, + args: { + maxHeight: 400, + children: numbers.map((num, i) => ( +

+ Element {num} +

+ )), + }, +}; + +export default meta; + +type Story = StoryObj; +export const Example: Story = {}; diff --git a/site/src/components/OverflowY/OverflowY.tsx b/site/src/components/OverflowY/OverflowY.tsx new file mode 100644 index 0000000000000..1252287a94f97 --- /dev/null +++ b/site/src/components/OverflowY/OverflowY.tsx @@ -0,0 +1,39 @@ +/** + * @file Provides reusable vertical overflow behavior. + */ +import { type ReactNode } from "react"; +import { type SystemStyleObject } from "@mui/system"; +import Box from "@mui/system/Box"; + +type Props = { + children: ReactNode; + height?: number; + maxHeight?: number; + sx?: SystemStyleObject; +}; + +export function OverflowY({ children, height, maxHeight, sx }: Props) { + const computedHeight = height === undefined ? "100%" : `${height}px`; + + // Doing Math.max check to catch cases where height is accidentally larger + // than maxHeight + const computedMaxHeight = + maxHeight === undefined + ? computedHeight + : `${Math.max(height ?? 0, maxHeight)}px`; + + return ( + + {children} + + ); +} diff --git a/site/src/components/PageHeader/PageHeader.tsx b/site/src/components/PageHeader/PageHeader.tsx index 1202c7adbd1d0..f963560958ea0 100644 --- a/site/src/components/PageHeader/PageHeader.tsx +++ b/site/src/components/PageHeader/PageHeader.tsx @@ -36,7 +36,6 @@ export const PageHeader: FC> = ({ marginLeft: "auto", [theme.breakpoints.down("md")]: { - marginTop: theme.spacing(3), marginLeft: "initial", width: "100%", }, diff --git a/site/src/components/PopoverContainer/PopoverContainer.stories.tsx b/site/src/components/PopoverContainer/PopoverContainer.stories.tsx new file mode 100644 index 0000000000000..c466be5bdb674 --- /dev/null +++ b/site/src/components/PopoverContainer/PopoverContainer.stories.tsx @@ -0,0 +1,23 @@ +import { Meta, StoryObj } from "@storybook/react"; +import { PopoverContainer } from "./PopoverContainer"; +import Button from "@mui/material/Button"; + +const numbers: number[] = []; +for (let i = 0; i < 20; i++) { + numbers.push(i + 1); +} + +const meta: Meta = { + title: "components/PopoverContainer", + component: PopoverContainer, + args: { + anchorButton: , + children:

Hiya!

, + originY: "bottom", + }, +}; + +export default meta; + +type Story = StoryObj; +export const Example: Story = {}; diff --git a/site/src/components/PopoverContainer/PopoverContainer.tsx b/site/src/components/PopoverContainer/PopoverContainer.tsx new file mode 100644 index 0000000000000..833d8b267d5fe --- /dev/null +++ b/site/src/components/PopoverContainer/PopoverContainer.tsx @@ -0,0 +1,244 @@ +/** + * @file Abstracts over MUI's Popover component to simplify using it (and hide + * some of the wonkier parts of the API). + * + * Just place a button and some content in the component, and things just work. + * No setup needed with hooks or refs. + */ +import { + type KeyboardEvent, + type MouseEvent, + type PropsWithChildren, + type ReactElement, + createContext, + useCallback, + useContext, + useEffect, + useRef, + useState, +} from "react"; + +import { type Theme, type SystemStyleObject, Box } from "@mui/system"; +import Popover, { type PopoverOrigin } from "@mui/material/Popover"; +import { useNavigate, type LinkProps } from "react-router-dom"; +import { useTheme } from "@emotion/react"; + +function getButton(container: HTMLElement) { + return ( + container.querySelector("button") ?? + container.querySelector('[aria-role="button"]') + ); +} + +const ClosePopoverContext = createContext<(() => void) | null>(null); + +type PopoverLinkProps = LinkProps & { + to: string; + sx?: SystemStyleObject; +}; + +/** + * A custom version of a React Router Link that makes sure to close the popover + * before starting a navigation. + * + * This is necessary because React Router's navigation logic doesn't work well + * with modals (including MUI's base Popover component). + * + * --- + * If the page being navigated to has lazy loading and isn't available yet, the + * previous components are supposed to be hidden during the transition, but + * because most React modals use React.createPortal to put content outside of + * the main DOM tree, React Router has no way of knowing about them. So open + * modals have a high risk of not disappearing until the page transition + * finishes and the previous components fully unmount. + */ +export function PopoverLink({ + children, + to, + sx, + ...linkProps +}: PopoverLinkProps) { + const closePopover = useContext(ClosePopoverContext); + if (closePopover === null) { + throw new Error("PopoverLink is not located inside of a PopoverContainer"); + } + + // Luckily, useNavigate and Link are designed to be imperative/declarative + // mirrors of each other, so their inputs should never get out of sync + const navigate = useNavigate(); + const theme = useTheme(); + + const onClick = (event: MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + closePopover(); + + // Hacky, but by using a promise to push the navigation to resolve via the + // micro-task queue, there's guaranteed to be a period for the popover to + // close. Tried React DOM's flushSync function, but it was unreliable. + void Promise.resolve().then(() => { + navigate(to, linkProps); + }); + }; + + return ( + + {children} + + ); +} + +type PopoverContainerProps = PropsWithChildren<{ + /** + * Does not require any hooks or refs to work. Also does not override any refs + * or event handlers attached to the button. + */ + anchorButton: ReactElement; + + width?: number; + originX?: PopoverOrigin["horizontal"]; + originY?: PopoverOrigin["vertical"]; + sx?: SystemStyleObject; +}>; + +export function PopoverContainer({ + children, + anchorButton, + originX = 0, + originY = 0, + width = 320, + sx = {}, +}: PopoverContainerProps) { + const parentClosePopover = useContext(ClosePopoverContext); + if (parentClosePopover !== null) { + throw new Error( + "Popover detected inside of Popover - this will always be a bad user experience", + ); + } + + const buttonContainerRef = useRef(null); + + // Ref value is for effects and event listeners; state value is for React + // renders. Have to duplicate state because after the initial render, it's + // never safe to reference ref contents inside a render path, especially with + // React 18 concurrency. Duplication is a necessary evil because of MUI's + // weird, clunky APIs + const anchorButtonRef = useRef(null); + const [loadedButton, setLoadedButton] = useState(); + + // Makes container listen to changes in button. If this approach becomes + // untenable in the future, it can be replaced with React.cloneElement, but + // the trade-off there is that every single anchorButton will need to be + // wrapped inside React.forwardRef, making the abstraction leak a little more + useEffect(() => { + const buttonContainer = buttonContainerRef.current; + if (buttonContainer === null) { + throw new Error("Please attach container ref to button container"); + } + + const initialButton = getButton(buttonContainer); + if (initialButton === null) { + throw new Error("Initial ref query failed"); + } + anchorButtonRef.current = initialButton; + + const onContainerMutation: MutationCallback = () => { + const newButton = getButton(buttonContainer); + if (newButton === null) { + throw new Error("Semantic button removed after DOM update"); + } + + anchorButtonRef.current = newButton; + setLoadedButton((current) => { + return current === undefined ? undefined : newButton; + }); + }; + + const observer = new MutationObserver(onContainerMutation); + observer.observe(buttonContainer, { + childList: true, + subtree: true, + }); + + return () => observer.disconnect(); + }, []); + + // Not using useInteractive because the container element is just meant to + // catch events from the inner button, not act as a button itself + const onInnerButtonInteraction = () => { + if (anchorButtonRef.current === null) { + throw new Error("Usable ref value is unavailable"); + } + + setLoadedButton(anchorButtonRef.current); + }; + + const onInnerButtonKeydown = (event: KeyboardEvent) => { + if (event.key === "Enter" || event.key === " ") { + onInnerButtonInteraction(); + } + }; + + const closePopover = useCallback(() => { + setLoadedButton(undefined); + }, []); + + return ( + <> + {/* Cannot switch with Box component; breaks implementation */} +
+ {anchorButton} +
+ + + + {children} + + + + ); +} diff --git a/site/src/pages/WorkspacesPage/WorkspacesButton.tsx b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx new file mode 100644 index 0000000000000..ae388a45b1ad1 --- /dev/null +++ b/site/src/pages/WorkspacesPage/WorkspacesButton.tsx @@ -0,0 +1,222 @@ +import { type PropsWithChildren, type ReactNode, useState } from "react"; +import { useTheme } from "@emotion/react"; +import { Language } from "./WorkspacesPageView"; + +import { type Template } from "api/typesGenerated"; +import { type UseQueryResult } from "@tanstack/react-query"; + +import { Link as RouterLink } from "react-router-dom"; +import Box from "@mui/system/Box"; +import Button from "@mui/material/Button"; +import Link from "@mui/material/Link"; +import AddIcon from "@mui/icons-material/AddOutlined"; +import OpenIcon from "@mui/icons-material/OpenInNewOutlined"; +import Typography from "@mui/material/Typography"; + +import { Loader } from "components/Loader/Loader"; +import { OverflowY } from "components/OverflowY/OverflowY"; +import { EmptyState } from "components/EmptyState/EmptyState"; +import { Avatar } from "components/Avatar/Avatar"; +import { SearchBox } from "./WorkspacesSearchBox"; +import { + PopoverContainer, + PopoverLink, +} from "components/PopoverContainer/PopoverContainer"; + +const ICON_SIZE = 18; +const COLUMN_GAP = 1.5; + +function sortTemplatesByUsersDesc( + templates: readonly Template[], + searchTerm: string, +) { + const allWhitespace = /^\s+$/.test(searchTerm); + if (allWhitespace) { + return templates; + } + + const termMatcher = new RegExp(searchTerm.replaceAll(/[^\w]/g, "."), "i"); + return templates + .filter((template) => termMatcher.test(template.display_name)) + .sort((t1, t2) => t2.active_user_count - t1.active_user_count) + .slice(0, 10); +} + +function WorkspaceResultsRow({ template }: { template: Template }) { + const theme = useTheme(); + + return ( + + + + {template.display_name || "-"} + + + + + {template.display_name || "[Unnamed]"} + + + + {/* + * There are some templates that have -1 as their user count – + * basically functioning like a null value in JS. Can safely just + * treat them as if they were 0. + */} + {template.active_user_count <= 0 + ? "No" + : template.active_user_count}{" "} + developer + {template.active_user_count === 1 ? "" : "s"} + + + + + ); +} + +type TemplatesQuery = UseQueryResult; + +type WorkspacesButtonProps = PropsWithChildren<{ + templatesFetchStatus: TemplatesQuery["status"]; + templates: TemplatesQuery["data"]; +}>; + +export function WorkspacesButton({ + children, + templatesFetchStatus, + templates, +}: WorkspacesButtonProps) { + const theme = useTheme(); + + // Dataset should always be small enough that client-side filtering should be + // good enough. Can swap out down the line if it becomes an issue + const [searchTerm, setSearchTerm] = useState(""); + const processed = sortTemplatesByUsersDesc(templates ?? [], searchTerm); + + let emptyState: ReactNode = undefined; + if (templates?.length === 0) { + emptyState = ( + + Create one now. + + } + /> + ); + } else if (processed.length === 0) { + emptyState = ; + } + + return ( + } variant="contained"> + {children} + + } + > + setSearchTerm(newValue)} + placeholder="Type/select a workspace template" + label="Template select for workspace" + sx={{ flexShrink: 0, columnGap: COLUMN_GAP }} + /> + + + {templatesFetchStatus === "loading" ? ( + + ) : ( + <> + {processed.map((template) => ( + + ))} + + {emptyState} + + )} + + + + + + + + {Language.seeAllTemplates} + + + + ); +} diff --git a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx index 5273cd2f4486f..1fc6174dec2b2 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPage.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPage.tsx @@ -22,6 +22,8 @@ import TextField from "@mui/material/TextField"; import { displayError } from "components/GlobalSnackbar/utils"; import { getErrorMessage } from "api/errors"; import { useEffectEvent } from "hooks/hookPolyfills"; +import { useQuery } from "@tanstack/react-query"; +import { templates } from "api/queries/templates"; function useSafeSearchParams() { // Have to wrap setSearchParams because React Router doesn't make sure that @@ -44,8 +46,13 @@ const WorkspacesPage: FC = () => { // each hook. const searchParamsResult = useSafeSearchParams(); const pagination = usePagination({ searchParamsResult }); + + const organizationId = useOrganizationId(); + const templatesQuery = useQuery(templates(organizationId)); + const filterProps = useWorkspacesFilter({ searchParamsResult, + organizationId, onFilterChange: () => pagination.goToPage(1), }); @@ -107,6 +114,8 @@ const WorkspacesPage: FC = () => { checkedWorkspaces={checkedWorkspaces} onCheckChange={setCheckedWorkspaces} canCheckWorkspaces={canCheckWorkspaces} + templates={templatesQuery.data} + templatesFetchStatus={templatesQuery.status} workspaces={data?.workspaces} dormantWorkspaces={dormantWorkspaces} error={error} @@ -143,11 +152,13 @@ export default WorkspacesPage; type UseWorkspacesFilterOptions = { searchParamsResult: ReturnType; onFilterChange: () => void; + organizationId: string; }; const useWorkspacesFilter = ({ searchParamsResult, onFilterChange, + organizationId, }: UseWorkspacesFilterOptions) => { const filter = useFilter({ fallbackFilter: "owner:me", @@ -164,9 +175,8 @@ const useWorkspacesFilter = ({ enabled: canFilterByUser, }); - const orgId = useOrganizationId(); const templateMenu = useTemplateFilterMenu({ - orgId, + orgId: organizationId, value: filter.values.template, onChange: (option) => filter.update({ ...filter.values, template: option?.value }), diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx index b7d8bcd24c6c7..81b00ac113c23 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.stories.tsx @@ -16,6 +16,7 @@ import { mockApiError, MockUser, MockPendingProvisionerJob, + MockTemplate, } from "testHelpers/entities"; import { WorkspacesPageView } from "./WorkspacesPageView"; import { DashboardProviderContext } from "components/Dashboard/DashboardProvider"; @@ -92,6 +93,19 @@ const defaultFilterProps = getDefaultFilterProps({ }, }); +const mockTemplates = [ + MockTemplate, + ...[1, 2, 3, 4].map((num) => { + return { + ...MockTemplate, + active_user_count: Math.floor(Math.random() * 10) * num, + display_name: `Extra Template ${num}`, + description: "Auto-Generated template", + icon: num % 2 === 0 ? "" : "/icon/goland.svg", + }; + }), +]; + const meta: Meta = { title: "pages/WorkspacesPageView", component: WorkspacesPageView, @@ -100,6 +114,8 @@ const meta: Meta = { filterProps: defaultFilterProps, checkedWorkspaces: [], canCheckWorkspaces: true, + templates: mockTemplates, + templatesFetchStatus: "success", }, decorators: [ (Story) => ( diff --git a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx index b19ed581dba73..81bdaddc5450c 100644 --- a/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx +++ b/site/src/pages/WorkspacesPage/WorkspacesPageView.tsx @@ -1,14 +1,8 @@ -import Link from "@mui/material/Link"; -import { Workspace } from "api/typesGenerated"; +import { Template, Workspace } from "api/typesGenerated"; import { PaginationWidgetBase } from "components/PaginationWidget/PaginationWidgetBase"; import { ComponentProps, FC } from "react"; -import { Link as RouterLink } from "react-router-dom"; import { Margins } from "components/Margins/Margins"; -import { - PageHeader, - PageHeaderSubtitle, - PageHeaderTitle, -} from "components/PageHeader/PageHeader"; +import { PageHeader, PageHeaderTitle } from "components/PageHeader/PageHeader"; import { Stack } from "components/Stack/Stack"; import { WorkspaceHelpTooltip } from "./WorkspaceHelpTooltip"; import { WorkspacesTable } from "pages/WorkspacesPage/WorkspacesTable"; @@ -24,16 +18,21 @@ import { import Box from "@mui/material/Box"; import Button from "@mui/material/Button"; import DeleteOutlined from "@mui/icons-material/DeleteOutlined"; +import { WorkspacesButton } from "./WorkspacesButton"; +import { UseQueryResult } from "@tanstack/react-query"; export const Language = { pageTitle: "Workspaces", yourWorkspacesButton: "Your workspaces", allWorkspacesButton: "All workspaces", runningWorkspacesButton: "Running workspaces", - createANewWorkspace: `Create a new workspace from a `, + createWorkspace: <>Create Workspace…, + seeAllTemplates: "See all templates", template: "Template", }; +type TemplateQuery = UseQueryResult; + export interface WorkspacesPageViewProps { error: unknown; workspaces?: Workspace[]; @@ -48,6 +47,9 @@ export interface WorkspacesPageViewProps { onCheckChange: (checkedWorkspaces: Workspace[]) => void; onDeleteAll: () => void; canCheckWorkspaces: boolean; + + templatesFetchStatus: TemplateQuery["status"]; + templates: TemplateQuery["data"]; } export const WorkspacesPageView: FC< @@ -66,6 +68,8 @@ export const WorkspacesPageView: FC< onCheckChange, onDeleteAll, canCheckWorkspaces, + templates, + templatesFetchStatus, }) => { const { saveLocal } = useLocalStorage(); @@ -78,21 +82,22 @@ export const WorkspacesPageView: FC< return ( - + + {Language.createWorkspace} + + } + > {Language.pageTitle} - - - {Language.createANewWorkspace} - - {Language.template} - - . - diff --git a/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx b/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx new file mode 100644 index 0000000000000..297ae5699fbdc --- /dev/null +++ b/site/src/pages/WorkspacesPage/WorkspacesSearchBox.tsx @@ -0,0 +1,94 @@ +/** + * @file Defines a controlled searchbox component for processing form state. + * + * Not defined as a top-level component just yet, because it's not clear how + * reusable this is outside of workspace dropdowns. + */ +import { + type ForwardedRef, + type KeyboardEvent, + forwardRef, + useId, +} from "react"; + +import Box from "@mui/system/Box"; +import SearchIcon from "@mui/icons-material/SearchOutlined"; +import { visuallyHidden } from "@mui/utils"; +import { type SystemStyleObject } from "@mui/system"; + +type Props = { + value: string; + onValueChange: (newValue: string) => void; + + placeholder?: string; + label?: string; + onKeyDown?: (event: KeyboardEvent) => void; + sx?: SystemStyleObject; +}; + +export const SearchBox = forwardRef(function SearchBox( + { + value, + onValueChange, + onKeyDown, + label = "Search", + placeholder = "Search...", + sx = {}, + }: Props, + ref?: ForwardedRef, +) { + const hookId = useId(); + const inputId = `${hookId}-${SearchBox.name}-input`; + + return ( + `1px solid ${theme.palette.divider}`, + ...sx, + }} + onKeyDown={onKeyDown} + > + + theme.palette.text.secondary, + }} + /> + + + + {label} + + + onValueChange(e.target.value)} + sx={{ + height: "100%", + border: 0, + background: "none", + width: "100%", + outline: 0, + "&::placeholder": { + color: (theme) => theme.palette.text.secondary, + }, + }} + /> + + ); +});