;
+ }
+ }
+}
diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts
new file mode 100644
index 0000000..11acccd
--- /dev/null
+++ b/cypress/support/e2e.ts
@@ -0,0 +1,2 @@
+import './commands';
+import '@cypress/code-coverage/support';
diff --git a/eslint.config.js b/eslint.config.js
index 46279c1..639a49f 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -21,5 +21,12 @@ export default defineFlatConfig([
},
'node': { version: '18' }
}
+ },
+ {
+ files: ['cypress/**/*.cy.{ts,tsx}', 'cypress/**/commands.ts'],
+ rules: {
+ 'promise/prefer-await-to-then': 'off',
+ 'newline-per-chained-call': 'off'
+ }
}
]);
diff --git a/package.json b/package.json
index 5b1e85b..59955fb 100644
--- a/package.json
+++ b/package.json
@@ -13,6 +13,7 @@
"@reduxjs/toolkit": "^1.9.5",
"@sentry/react": "^7.54.0",
"cheerio": "1.0.0-rc.12",
+ "dayjs": "^1.11.8",
"i18next": "^22.5.1",
"i18next-browser-languagedetector": "^7.0.2",
"i18next-http-backend": "^2.2.1",
@@ -28,13 +29,18 @@
"tslib": "^2.5.3"
},
"devDependencies": {
- "@dtrw/eslint-config": "^2.0.2",
+ "@cypress/code-coverage": "^3.10.7",
+ "@cypress/react18": "^2.0.0",
+ "@dtrw/eslint-config": "^2.1.1",
"@edge-runtime/vm": "^3.0.2",
"@emotion/jest": "^11.11.0",
+ "@istanbuljs/nyc-config-typescript": "^1.0.2",
"@sentry/vite-plugin": "^2.2.1",
+ "@testing-library/cypress": "^9.0.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.4.3",
+ "@types/cypress__code-coverage": "^3.10.0",
"@types/express": "^4.17.17",
"@types/node": "^18.16.16",
"@types/react": "^18.2.8",
@@ -47,14 +53,18 @@
"@vercel/node": "^2.14.5",
"@vitejs/plugin-react": "^4.0.0",
"@vitest/coverage-istanbul": "^0.32.0",
+ "cypress": "^12.14.0",
+ "cypress-vite": "^1.4.0",
"eslint": "^8.40.0",
"eslint-define-config": "^1.20.0",
"express": "^4.18.2",
"jest-extended": "^3.2.4",
"jsdom": "^22.1.0",
+ "start-server-and-test": "^2.0.0",
"supertest": "^6.3.3",
"typescript": "^5.1.3",
"vite": "^4.3.9",
+ "vite-plugin-istanbul": "^4.1.0",
"vite-plugin-pwa": "^0.16.3",
"vite-tsconfig-paths": "^4.2.0",
"vitest": "^0.32.0"
@@ -62,6 +72,7 @@
"scripts": {
"build": "tsc && vite build",
"coverage": "vitest run --coverage",
+ "cy": "start-server-and-test 'yarn d --listen 5137' 5137 'cypress open'",
"d": "vercel dev",
"lint:fix": "eslint --fix src",
"lint": "eslint src",
diff --git a/src/__tests__/__snapshots__/app.test.tsx.snap b/src/__tests__/__snapshots__/app.test.tsx.snap
index 5c89526..80d2efc 100644
--- a/src/__tests__/__snapshots__/app.test.tsx.snap
+++ b/src/__tests__/__snapshots__/app.test.tsx.snap
@@ -169,6 +169,10 @@ exports[`app > should match snapshot 1`] = `
-webkit-flex-wrap: wrap;
-ms-flex-wrap: wrap;
flex-wrap: wrap;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
}
.emotion-7>* {
@@ -331,6 +335,7 @@ exports[`app > should match snapshot 1`] = `
>
should match snapshot 1`] = `
class="MuiTypography-root MuiTypography-inherit MuiLink-root MuiLink-underlineAlways emotion-13"
href="https://github.com/burtek"
referrerpolicy="no-referrer"
- rel="noopener noreferrer nofollow"
+ rel="noopener noreferrer"
target="_blank"
>
burtek
@@ -397,7 +402,7 @@ exports[`app > should match snapshot 1`] = `
class="MuiTypography-root MuiTypography-inherit MuiLink-root MuiLink-underlineAlways emotion-13"
href="https://github.com/burtek/speeddial"
referrerpolicy="no-referrer"
- rel="noopener noreferrer nofollow"
+ rel="noopener noreferrer"
target="_blank"
>
footer.openSource
diff --git a/src/components/app-footer/__snapshots__/index.test.tsx.snap b/src/components/app-footer/__snapshots__/index.test.tsx.snap
index 7bf46e8..0724897 100644
--- a/src/components/app-footer/__snapshots__/index.test.tsx.snap
+++ b/src/components/app-footer/__snapshots__/index.test.tsx.snap
@@ -44,7 +44,7 @@ exports[`app-footer > should match snapshot 1`] = `
class="MuiTypography-root MuiTypography-inherit MuiLink-root MuiLink-underlineAlways emotion-2"
href="https://github.com/burtek"
referrerpolicy="no-referrer"
- rel="noopener noreferrer nofollow"
+ rel="noopener noreferrer"
target="_blank"
>
burtek
@@ -54,7 +54,7 @@ exports[`app-footer > should match snapshot 1`] = `
class="MuiTypography-root MuiTypography-inherit MuiLink-root MuiLink-underlineAlways emotion-2"
href="https://github.com/burtek/speeddial"
referrerpolicy="no-referrer"
- rel="noopener noreferrer nofollow"
+ rel="noopener noreferrer"
target="_blank"
>
footer.openSource
diff --git a/src/components/app-footer/index.tsx b/src/components/app-footer/index.tsx
index 11695ab..a9c2b9b 100644
--- a/src/components/app-footer/index.tsx
+++ b/src/components/app-footer/index.tsx
@@ -31,7 +31,7 @@ export const AppFooter: FC<{ gridArea?: string }> = ({ gridArea }) => {
href={AUTHOR_URL}
target="_blank"
referrerPolicy="no-referrer"
- rel="noopener noreferrer nofollow"
+ rel="noopener noreferrer"
>
{AUTHOR}
@@ -40,7 +40,7 @@ export const AppFooter: FC<{ gridArea?: string }> = ({ gridArea }) => {
href={REPO_URL}
target="_blank"
referrerPolicy="no-referrer"
- rel="noopener noreferrer nofollow"
+ rel="noopener noreferrer"
>
{t('footer.openSource')}
diff --git a/src/components/settings/__snapshots__/index.test.tsx.snap b/src/components/settings/__snapshots__/index.test.tsx.snap
index 38a1288..fb9fdd3 100644
--- a/src/components/settings/__snapshots__/index.test.tsx.snap
+++ b/src/components/settings/__snapshots__/index.test.tsx.snap
@@ -825,6 +825,85 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
visibility: visible;
}
+.emotion-21 {
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ position: relative;
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: transparent;
+ background-color: transparent;
+ outline: 0;
+ border: 0;
+ margin: 0;
+ border-radius: 0;
+ padding: 0;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ vertical-align: middle;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ color: inherit;
+ font-family: "Roboto","Helvetica","Arial",sans-serif;
+ font-weight: 500;
+ font-size: 0.875rem;
+ line-height: 1.75;
+ letter-spacing: 0.02857em;
+ text-transform: uppercase;
+ min-width: 64px;
+ padding: 6px 8px;
+ border-radius: 4px;
+ -webkit-transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ color: #1976d2;
+}
+
+.emotion-21::-moz-focus-inner {
+ border-style: none;
+}
+
+.emotion-21.Mui-disabled {
+ pointer-events: none;
+ cursor: default;
+}
+
+@media print {
+ .emotion-21 {
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+ }
+}
+
+.emotion-21:hover {
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ background-color: rgba(25, 118, 210, 0.04);
+}
+
+@media (hover: none) {
+ .emotion-21:hover {
+ background-color: transparent;
+ }
+}
+
+.emotion-21.Mui-disabled {
+ color: rgba(0, 0, 0, 0.26);
+}
+
@@ -956,6 +1035,33 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
+
+
+
+
+
settings > theme mode > should change theme mode to auto 1`] = `
-@keyframes animation-0 {
- 0% {
- -webkit-transform: scale(0);
- -moz-transform: scale(0);
- -ms-transform: scale(0);
- transform: scale(0);
- opacity: 0.1;
- }
-
- 100% {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- opacity: 0.3;
- }
-}
-
-@keyframes animation-1 {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0;
- }
-}
-
-@keyframes animation-2 {
- 0% {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- }
-
- 50% {
- -webkit-transform: scale(0.92);
- -moz-transform: scale(0.92);
- -ms-transform: scale(0.92);
- transform: scale(0.92);
- }
-
- 100% {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- }
-}
-
.emotion-0 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
@@ -1125,65 +1180,6 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
}
.emotion-3 {
- opacity: 0;
- position: absolute;
-}
-
-.emotion-3.MuiTouchRipple-rippleVisible {
- opacity: 0.3;
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- -webkit-animation-name: animation-0;
- animation-name: animation-0;
- -webkit-animation-duration: 550ms;
- animation-duration: 550ms;
- -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.emotion-3.MuiTouchRipple-ripplePulsate {
- -webkit-animation-duration: 200ms;
- animation-duration: 200ms;
-}
-
-.emotion-3 .MuiTouchRipple-child {
- opacity: 1;
- display: block;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background-color: currentColor;
-}
-
-.emotion-3 .MuiTouchRipple-childLeaving {
- opacity: 0;
- -webkit-animation-name: animation-1;
- animation-name: animation-1;
- -webkit-animation-duration: 550ms;
- animation-duration: 550ms;
- -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.emotion-3 .MuiTouchRipple-childPulsate {
- position: absolute;
- left: 0px;
- top: 0;
- -webkit-animation-name: animation-2;
- animation-name: animation-2;
- -webkit-animation-duration: 2500ms;
- animation-duration: 2500ms;
- -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- -webkit-animation-iteration-count: infinite;
- animation-iteration-count: infinite;
- -webkit-animation-delay: 200ms;
- animation-delay: 200ms;
-}
-
-.emotion-4 {
position: fixed;
z-index: 1300;
right: 0;
@@ -1193,12 +1189,12 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
}
@media print {
- .emotion-4 {
+ .emotion-3 {
position: absolute!important;
}
}
-.emotion-5 {
+.emotion-4 {
position: fixed;
display: -webkit-box;
display: -webkit-flex;
@@ -1221,7 +1217,7 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
z-index: -1;
}
-.emotion-6 {
+.emotion-5 {
height: 100%;
outline: 0;
display: -webkit-box;
@@ -1239,12 +1235,12 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
}
@media print {
- .emotion-6 {
+ .emotion-5 {
height: auto;
}
}
-.emotion-7 {
+.emotion-6 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
-webkit-transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
@@ -1267,19 +1263,19 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
}
@media print {
- .emotion-7 {
+ .emotion-6 {
overflow-y: visible;
box-shadow: none;
}
}
@media (max-width:963.95px) {
- .emotion-7.MuiDialog-paperScrollBody {
+ .emotion-6.MuiDialog-paperScrollBody {
max-width: calc(100% - 64px);
}
}
-.emotion-8 {
+.emotion-7 {
margin: 0;
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-weight: 500;
@@ -1292,7 +1288,7 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
flex: 0 0 auto;
}
-.emotion-9 {
+.emotion-8 {
margin: 0;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
@@ -1303,7 +1299,7 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
border-bottom-width: thin;
}
-.emotion-10 {
+.emotion-9 {
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
@@ -1312,11 +1308,11 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
padding: 20px 24px;
}
-.MuiDialogTitle-root+.emotion-10 {
+.MuiDialogTitle-root+.emotion-9 {
padding-top: 0;
}
-.emotion-11 {
+.emotion-10 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
@@ -1333,7 +1329,7 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
width: 100%;
}
-.emotion-12 {
+.emotion-11 {
color: rgba(0, 0, 0, 0.6);
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-weight: 400;
@@ -1365,19 +1361,19 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
user-select: none;
}
-.emotion-12.Mui-focused {
+.emotion-11.Mui-focused {
color: #1976d2;
}
-.emotion-12.Mui-disabled {
+.emotion-11.Mui-disabled {
color: rgba(0, 0, 0, 0.38);
}
-.emotion-12.Mui-error {
+.emotion-11.Mui-error {
color: #d32f2f;
}
-.emotion-13 {
+.emotion-12 {
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-weight: 400;
font-size: 1rem;
@@ -1399,35 +1395,35 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
border-radius: 4px;
}
-.emotion-13.Mui-disabled {
+.emotion-12.Mui-disabled {
color: rgba(0, 0, 0, 0.38);
cursor: default;
}
-.emotion-13:hover .MuiOutlinedInput-notchedOutline {
+.emotion-12:hover .MuiOutlinedInput-notchedOutline {
border-color: rgba(0, 0, 0, 0.87);
}
@media (hover: none) {
- .emotion-13:hover .MuiOutlinedInput-notchedOutline {
+ .emotion-12:hover .MuiOutlinedInput-notchedOutline {
border-color: rgba(0, 0, 0, 0.23);
}
}
-.emotion-13.Mui-focused .MuiOutlinedInput-notchedOutline {
+.emotion-12.Mui-focused .MuiOutlinedInput-notchedOutline {
border-color: #1976d2;
border-width: 2px;
}
-.emotion-13.Mui-error .MuiOutlinedInput-notchedOutline {
+.emotion-12.Mui-error .MuiOutlinedInput-notchedOutline {
border-color: #d32f2f;
}
-.emotion-13.Mui-disabled .MuiOutlinedInput-notchedOutline {
+.emotion-12.Mui-disabled .MuiOutlinedInput-notchedOutline {
border-color: rgba(0, 0, 0, 0.26);
}
-.emotion-14 {
+.emotion-13 {
-moz-appearance: none;
-webkit-appearance: none;
-webkit-user-select: none;
@@ -1456,32 +1452,32 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
padding: 16.5px 14px;
}
-.emotion-14:focus {
+.emotion-13:focus {
border-radius: 4px;
}
-.emotion-14::-ms-expand {
+.emotion-13::-ms-expand {
display: none;
}
-.emotion-14.Mui-disabled {
+.emotion-13.Mui-disabled {
cursor: default;
}
-.emotion-14[multiple] {
+.emotion-13[multiple] {
height: auto;
}
-.emotion-14:not([multiple]) option,
-.emotion-14:not([multiple]) optgroup {
+.emotion-13:not([multiple]) option,
+.emotion-13:not([multiple]) optgroup {
background-color: #fff;
}
-.emotion-14.emotion-14.emotion-14 {
+.emotion-13.emotion-13.emotion-13 {
padding-right: 32px;
}
-.emotion-14.MuiSelect-select {
+.emotion-13.MuiSelect-select {
height: auto;
min-height: 1.4375em;
text-overflow: ellipsis;
@@ -1489,95 +1485,95 @@ exports[`settings > settings > theme mode > should change theme mode to auto 1`]
overflow: hidden;
}
-.emotion-14::-webkit-input-placeholder {
+.emotion-13::-webkit-input-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14::-moz-placeholder {
+.emotion-13::-moz-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14:-ms-input-placeholder {
+.emotion-13:-ms-input-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14::-ms-input-placeholder {
+.emotion-13::-ms-input-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14:focus {
+.emotion-13:focus {
outline: 0;
}
-.emotion-14:invalid {
+.emotion-13:invalid {
box-shadow: none;
}
-.emotion-14::-webkit-search-decoration {
+.emotion-13::-webkit-search-decoration {
-webkit-appearance: none;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14::-webkit-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13::-webkit-input-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14::-moz-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13::-moz-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:-ms-input-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14::-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13::-ms-input-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-webkit-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus::-webkit-input-placeholder {
opacity: 0.42;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-moz-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus::-moz-placeholder {
opacity: 0.42;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus:-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus:-ms-input-placeholder {
opacity: 0.42;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus::-ms-input-placeholder {
opacity: 0.42;
}
-.emotion-14.Mui-disabled {
+.emotion-13.Mui-disabled {
opacity: 1;
-webkit-text-fill-color: rgba(0, 0, 0, 0.38);
}
-.emotion-14:-webkit-autofill {
+.emotion-13:-webkit-autofill {
-webkit-animation-duration: 5000s;
animation-duration: 5000s;
-webkit-animation-name: mui-auto-fill;
animation-name: mui-auto-fill;
}
-.emotion-14:-webkit-autofill {
+.emotion-13:-webkit-autofill {
border-radius: inherit;
}
-.emotion-15 {
+.emotion-14 {
bottom: 0;
left: 0;
position: absolute;
@@ -1587,7 +1583,7 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
box-sizing: border-box;
}
-.emotion-16 {
+.emotion-15 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@@ -1609,11 +1605,11 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
color: rgba(0, 0, 0, 0.54);
}
-.emotion-16.Mui-disabled {
+.emotion-15.Mui-disabled {
color: rgba(0, 0, 0, 0.26);
}
-.emotion-17 {
+.emotion-16 {
text-align: left;
position: absolute;
bottom: 0;
@@ -1631,7 +1627,7 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
border-color: rgba(0, 0, 0, 0.23);
}
-.emotion-18 {
+.emotion-17 {
float: unset;
width: auto;
overflow: hidden;
@@ -1646,7 +1642,7 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
white-space: nowrap;
}
-.emotion-18>span {
+.emotion-17>span {
padding-left: 5px;
padding-right: 5px;
display: inline-block;
@@ -1654,6 +1650,85 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
visibility: visible;
}
+.emotion-20 {
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ position: relative;
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: transparent;
+ background-color: transparent;
+ outline: 0;
+ border: 0;
+ margin: 0;
+ border-radius: 0;
+ padding: 0;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ vertical-align: middle;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ color: inherit;
+ font-family: "Roboto","Helvetica","Arial",sans-serif;
+ font-weight: 500;
+ font-size: 0.875rem;
+ line-height: 1.75;
+ letter-spacing: 0.02857em;
+ text-transform: uppercase;
+ min-width: 64px;
+ padding: 6px 8px;
+ border-radius: 4px;
+ -webkit-transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ color: #1976d2;
+}
+
+.emotion-20::-moz-focus-inner {
+ border-style: none;
+}
+
+.emotion-20.Mui-disabled {
+ pointer-events: none;
+ cursor: default;
+}
+
+@media print {
+ .emotion-20 {
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+ }
+}
+
+.emotion-20:hover {
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ background-color: rgba(25, 118, 210, 0.04);
+}
+
+@media (hover: none) {
+ .emotion-20:hover {
+ background-color: transparent;
+ }
+}
+
+.emotion-20.Mui-disabled {
+ color: rgba(0, 0, 0, 0.26);
+}
+
@@ -1680,25 +1755,16 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
-
-
-
-
+ />
@@ -2614,6 +2786,33 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
+
+
+
+
+
settings > theme mode > should change theme mode to light 1`] = `
-@keyframes animation-0 {
- 0% {
- -webkit-transform: scale(0);
- -moz-transform: scale(0);
- -ms-transform: scale(0);
- transform: scale(0);
- opacity: 0.1;
- }
-
- 100% {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- opacity: 0.3;
- }
-}
-
-@keyframes animation-1 {
- 0% {
- opacity: 1;
- }
-
- 100% {
- opacity: 0;
- }
-}
-
-@keyframes animation-2 {
- 0% {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- }
-
- 50% {
- -webkit-transform: scale(0.92);
- -moz-transform: scale(0.92);
- -ms-transform: scale(0.92);
- transform: scale(0.92);
- }
-
- 100% {
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- }
-}
-
.emotion-0 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
@@ -2783,65 +2931,6 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
}
.emotion-3 {
- opacity: 0;
- position: absolute;
-}
-
-.emotion-3.MuiTouchRipple-rippleVisible {
- opacity: 0.3;
- -webkit-transform: scale(1);
- -moz-transform: scale(1);
- -ms-transform: scale(1);
- transform: scale(1);
- -webkit-animation-name: animation-0;
- animation-name: animation-0;
- -webkit-animation-duration: 550ms;
- animation-duration: 550ms;
- -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.emotion-3.MuiTouchRipple-ripplePulsate {
- -webkit-animation-duration: 200ms;
- animation-duration: 200ms;
-}
-
-.emotion-3 .MuiTouchRipple-child {
- opacity: 1;
- display: block;
- width: 100%;
- height: 100%;
- border-radius: 50%;
- background-color: currentColor;
-}
-
-.emotion-3 .MuiTouchRipple-childLeaving {
- opacity: 0;
- -webkit-animation-name: animation-1;
- animation-name: animation-1;
- -webkit-animation-duration: 550ms;
- animation-duration: 550ms;
- -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
-}
-
-.emotion-3 .MuiTouchRipple-childPulsate {
- position: absolute;
- left: 0px;
- top: 0;
- -webkit-animation-name: animation-2;
- animation-name: animation-2;
- -webkit-animation-duration: 2500ms;
- animation-duration: 2500ms;
- -webkit-animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- animation-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
- -webkit-animation-iteration-count: infinite;
- animation-iteration-count: infinite;
- -webkit-animation-delay: 200ms;
- animation-delay: 200ms;
-}
-
-.emotion-4 {
position: fixed;
z-index: 1300;
right: 0;
@@ -2851,12 +2940,12 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
}
@media print {
- .emotion-4 {
+ .emotion-3 {
position: absolute!important;
}
}
-.emotion-5 {
+.emotion-4 {
position: fixed;
display: -webkit-box;
display: -webkit-flex;
@@ -2879,7 +2968,7 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
z-index: -1;
}
-.emotion-6 {
+.emotion-5 {
height: 100%;
outline: 0;
display: -webkit-box;
@@ -2897,12 +2986,12 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
}
@media print {
- .emotion-6 {
+ .emotion-5 {
height: auto;
}
}
-.emotion-7 {
+.emotion-6 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
-webkit-transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
@@ -2925,19 +3014,19 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
}
@media print {
- .emotion-7 {
+ .emotion-6 {
overflow-y: visible;
box-shadow: none;
}
}
@media (max-width:963.95px) {
- .emotion-7.MuiDialog-paperScrollBody {
+ .emotion-6.MuiDialog-paperScrollBody {
max-width: calc(100% - 64px);
}
}
-.emotion-8 {
+.emotion-7 {
margin: 0;
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-weight: 500;
@@ -2950,7 +3039,7 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
flex: 0 0 auto;
}
-.emotion-9 {
+.emotion-8 {
margin: 0;
-webkit-flex-shrink: 0;
-ms-flex-negative: 0;
@@ -2961,7 +3050,7 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
border-bottom-width: thin;
}
-.emotion-10 {
+.emotion-9 {
-webkit-flex: 1 1 auto;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
@@ -2970,11 +3059,11 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
padding: 20px 24px;
}
-.MuiDialogTitle-root+.emotion-10 {
+.MuiDialogTitle-root+.emotion-9 {
padding-top: 0;
}
-.emotion-11 {
+.emotion-10 {
display: -webkit-inline-box;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
@@ -2991,7 +3080,7 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
width: 100%;
}
-.emotion-12 {
+.emotion-11 {
color: rgba(0, 0, 0, 0.6);
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-weight: 400;
@@ -3023,19 +3112,19 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
user-select: none;
}
-.emotion-12.Mui-focused {
+.emotion-11.Mui-focused {
color: #1976d2;
}
-.emotion-12.Mui-disabled {
+.emotion-11.Mui-disabled {
color: rgba(0, 0, 0, 0.38);
}
-.emotion-12.Mui-error {
+.emotion-11.Mui-error {
color: #d32f2f;
}
-.emotion-13 {
+.emotion-12 {
font-family: "Roboto","Helvetica","Arial",sans-serif;
font-weight: 400;
font-size: 1rem;
@@ -3057,35 +3146,35 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
border-radius: 4px;
}
-.emotion-13.Mui-disabled {
+.emotion-12.Mui-disabled {
color: rgba(0, 0, 0, 0.38);
cursor: default;
}
-.emotion-13:hover .MuiOutlinedInput-notchedOutline {
+.emotion-12:hover .MuiOutlinedInput-notchedOutline {
border-color: rgba(0, 0, 0, 0.87);
}
@media (hover: none) {
- .emotion-13:hover .MuiOutlinedInput-notchedOutline {
+ .emotion-12:hover .MuiOutlinedInput-notchedOutline {
border-color: rgba(0, 0, 0, 0.23);
}
}
-.emotion-13.Mui-focused .MuiOutlinedInput-notchedOutline {
+.emotion-12.Mui-focused .MuiOutlinedInput-notchedOutline {
border-color: #1976d2;
border-width: 2px;
}
-.emotion-13.Mui-error .MuiOutlinedInput-notchedOutline {
+.emotion-12.Mui-error .MuiOutlinedInput-notchedOutline {
border-color: #d32f2f;
}
-.emotion-13.Mui-disabled .MuiOutlinedInput-notchedOutline {
+.emotion-12.Mui-disabled .MuiOutlinedInput-notchedOutline {
border-color: rgba(0, 0, 0, 0.26);
}
-.emotion-14 {
+.emotion-13 {
-moz-appearance: none;
-webkit-appearance: none;
-webkit-user-select: none;
@@ -3114,32 +3203,32 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
padding: 16.5px 14px;
}
-.emotion-14:focus {
+.emotion-13:focus {
border-radius: 4px;
}
-.emotion-14::-ms-expand {
+.emotion-13::-ms-expand {
display: none;
}
-.emotion-14.Mui-disabled {
+.emotion-13.Mui-disabled {
cursor: default;
}
-.emotion-14[multiple] {
+.emotion-13[multiple] {
height: auto;
}
-.emotion-14:not([multiple]) option,
-.emotion-14:not([multiple]) optgroup {
+.emotion-13:not([multiple]) option,
+.emotion-13:not([multiple]) optgroup {
background-color: #fff;
}
-.emotion-14.emotion-14.emotion-14 {
+.emotion-13.emotion-13.emotion-13 {
padding-right: 32px;
}
-.emotion-14.MuiSelect-select {
+.emotion-13.MuiSelect-select {
height: auto;
min-height: 1.4375em;
text-overflow: ellipsis;
@@ -3147,95 +3236,95 @@ exports[`settings > settings > theme mode > should change theme mode to light 1`
overflow: hidden;
}
-.emotion-14::-webkit-input-placeholder {
+.emotion-13::-webkit-input-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14::-moz-placeholder {
+.emotion-13::-moz-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14:-ms-input-placeholder {
+.emotion-13:-ms-input-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14::-ms-input-placeholder {
+.emotion-13::-ms-input-placeholder {
color: currentColor;
opacity: 0.42;
-webkit-transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
transition: opacity 200ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
}
-.emotion-14:focus {
+.emotion-13:focus {
outline: 0;
}
-.emotion-14:invalid {
+.emotion-13:invalid {
box-shadow: none;
}
-.emotion-14::-webkit-search-decoration {
+.emotion-13::-webkit-search-decoration {
-webkit-appearance: none;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14::-webkit-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13::-webkit-input-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14::-moz-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13::-moz-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:-ms-input-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14::-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13::-ms-input-placeholder {
opacity: 0!important;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-webkit-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus::-webkit-input-placeholder {
opacity: 0.42;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-moz-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus::-moz-placeholder {
opacity: 0.42;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus:-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus:-ms-input-placeholder {
opacity: 0.42;
}
-label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-placeholder {
+label[data-shrink=false]+.MuiInputBase-formControl .emotion-13:focus::-ms-input-placeholder {
opacity: 0.42;
}
-.emotion-14.Mui-disabled {
+.emotion-13.Mui-disabled {
opacity: 1;
-webkit-text-fill-color: rgba(0, 0, 0, 0.38);
}
-.emotion-14:-webkit-autofill {
+.emotion-13:-webkit-autofill {
-webkit-animation-duration: 5000s;
animation-duration: 5000s;
-webkit-animation-name: mui-auto-fill;
animation-name: mui-auto-fill;
}
-.emotion-14:-webkit-autofill {
+.emotion-13:-webkit-autofill {
border-radius: inherit;
}
-.emotion-15 {
+.emotion-14 {
bottom: 0;
left: 0;
position: absolute;
@@ -3245,7 +3334,7 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
box-sizing: border-box;
}
-.emotion-16 {
+.emotion-15 {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@@ -3267,11 +3356,11 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
color: rgba(0, 0, 0, 0.54);
}
-.emotion-16.Mui-disabled {
+.emotion-15.Mui-disabled {
color: rgba(0, 0, 0, 0.26);
}
-.emotion-17 {
+.emotion-16 {
text-align: left;
position: absolute;
bottom: 0;
@@ -3289,7 +3378,7 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
border-color: rgba(0, 0, 0, 0.23);
}
-.emotion-18 {
+.emotion-17 {
float: unset;
width: auto;
overflow: hidden;
@@ -3304,7 +3393,7 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
white-space: nowrap;
}
-.emotion-18>span {
+.emotion-17>span {
padding-left: 5px;
padding-right: 5px;
display: inline-block;
@@ -3312,6 +3401,85 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
visibility: visible;
}
+.emotion-20 {
+ display: -webkit-inline-box;
+ display: -webkit-inline-flex;
+ display: -ms-inline-flexbox;
+ display: inline-flex;
+ -webkit-align-items: center;
+ -webkit-box-align: center;
+ -ms-flex-align: center;
+ align-items: center;
+ -webkit-box-pack: center;
+ -ms-flex-pack: center;
+ -webkit-justify-content: center;
+ justify-content: center;
+ position: relative;
+ box-sizing: border-box;
+ -webkit-tap-highlight-color: transparent;
+ background-color: transparent;
+ outline: 0;
+ border: 0;
+ margin: 0;
+ border-radius: 0;
+ padding: 0;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ vertical-align: middle;
+ -moz-appearance: none;
+ -webkit-appearance: none;
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ color: inherit;
+ font-family: "Roboto","Helvetica","Arial",sans-serif;
+ font-weight: 500;
+ font-size: 0.875rem;
+ line-height: 1.75;
+ letter-spacing: 0.02857em;
+ text-transform: uppercase;
+ min-width: 64px;
+ padding: 6px 8px;
+ border-radius: 4px;
+ -webkit-transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
+ color: #1976d2;
+}
+
+.emotion-20::-moz-focus-inner {
+ border-style: none;
+}
+
+.emotion-20.Mui-disabled {
+ pointer-events: none;
+ cursor: default;
+}
+
+@media print {
+ .emotion-20 {
+ -webkit-print-color-adjust: exact;
+ color-adjust: exact;
+ }
+}
+
+.emotion-20:hover {
+ -webkit-text-decoration: none;
+ text-decoration: none;
+ background-color: rgba(25, 118, 210, 0.04);
+}
+
+@media (hover: none) {
+ .emotion-20:hover {
+ background-color: transparent;
+ }
+}
+
+.emotion-20.Mui-disabled {
+ color: rgba(0, 0, 0, 0.26);
+}
+
@@ -3338,25 +3506,16 @@ label[data-shrink=false]+.MuiInputBase-formControl .emotion-14:focus::-ms-input-
-
-
-
-
+ />
}> = ({ sx }) => {
diff --git a/src/components/settings/modal.tsx b/src/components/settings/modal.tsx
index fc2d310..3236fb8 100644
--- a/src/components/settings/modal.tsx
+++ b/src/components/settings/modal.tsx
@@ -1,5 +1,5 @@
import type { SelectChangeEvent } from '@mui/material';
-import { Divider, Dialog, DialogContent, DialogTitle, FormControl, InputLabel, MenuItem, Select } from '@mui/material';
+import { Button, Divider, Dialog, DialogContent, DialogTitle, FormControl, InputLabel, MenuItem, Select } from '@mui/material';
import type { FC } from 'react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
@@ -8,7 +8,7 @@ import { useSelector } from 'react-redux';
import type { ThemeMode } from '@@data/config';
import { actions as configActions } from '@@data/config';
import { getIsDialogOpen, getThemeMode } from '@@data/config/selectors';
-import { useAppDispatch } from '@@data/index';
+import { useAppDispatch } from '@@data/redux-toolkit';
export const SettingsModal: FC = () => {
@@ -27,6 +27,14 @@ export const SettingsModal: FC = () => {
dispatch(configActions.closeModal());
}, [dispatch]);
+ const onExport = useCallback(() => {
+ void dispatch(configActions.exportSettings());
+ }, [dispatch]);
+
+ const onImport = useCallback(() => {
+ void dispatch(configActions.importSettings());
+ }, [dispatch]);
+
return (
);
};
diff --git a/src/components/speeddial/components/__tests__/__snapshots__/add-tile.test.tsx.snap b/src/components/speeddial/components/__tests__/__snapshots__/add-tile.test.tsx.snap
index db25164..1dca0e9 100644
--- a/src/components/speeddial/components/__tests__/__snapshots__/add-tile.test.tsx.snap
+++ b/src/components/speeddial/components/__tests__/__snapshots__/add-tile.test.tsx.snap
@@ -1,6 +1,6 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`speeddial/add-tile > should render add icon without button for 'non-root' parent 1`] = `
+exports[`speeddial/add-tile > should render add icon without button 1`] = `
.emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
@@ -82,7 +82,7 @@ exports[`speeddial/add-tile > should render add icon without button for 'non-roo
>
`;
-exports[`speeddial/add-tile > should render add icon without button for 'root' parent 1`] = `
+exports[`speeddial/add-tile > should render add icon without button 2`] = `
.emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
@@ -196,7 +196,7 @@ exports[`speeddial/add-tile > should render add icon without button for 'root' p
`;
-exports[`speeddial/add-tile > should render expected buttons on hover for 'non-root' parent 1`] = `
+exports[`speeddial/add-tile > should render expected buttons on hover 1`] = `
.emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
@@ -347,15 +347,20 @@ exports[`speeddial/add-tile > should render expected buttons on hover for 'non-r
}
.emotion-4 {
- overflow: hidden;
- pointer-events: none;
- position: absolute;
- z-index: 0;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- border-radius: inherit;
+ margin: 0;
+ -webkit-flex-shrink: 0;
+ -ms-flex-negative: 0;
+ flex-shrink: 0;
+ border-width: 0;
+ border-style: solid;
+ border-color: rgba(0, 0, 0, 0.12);
+ border-bottom-width: thin;
+ margin-left: 16px;
+ margin-right: 16px;
+ -webkit-align-self: stretch;
+ -ms-flex-item-align: stretch;
+ align-self: stretch;
+ height: auto;
}
@@ -384,16 +389,36 @@ exports[`speeddial/add-tile > should render expected buttons on hover for 'non-r
d="M7,7H11V9H7A3,3 0 0,0 4,12A3,3 0 0,0 7,15H11V17H7A5,5 0 0,1 2,12A5,5 0 0,1 7,7M17,7A5,5 0 0,1 22,12H20A3,3 0 0,0 17,9H13V7H17M8,11H16V13H8V11M17,12H19V15H22V17H19V20H17V17H14V15H17V12Z"
/>
-
+
+
+
`;
-exports[`speeddial/add-tile > should render expected buttons on hover for 'root' parent 1`] = `
+exports[`speeddial/add-tile > should render expected buttons on hover 2`] = `
.emotion-0 {
background-color: #fff;
color: rgba(0, 0, 0, 0.87);
@@ -543,35 +568,6 @@ exports[`speeddial/add-tile > should render expected buttons on hover for 'root'
font-size: 2.1875rem;
}
-.emotion-4 {
- overflow: hidden;
- pointer-events: none;
- position: absolute;
- z-index: 0;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- border-radius: inherit;
-}
-
-.emotion-5 {
- margin: 0;
- -webkit-flex-shrink: 0;
- -ms-flex-negative: 0;
- flex-shrink: 0;
- border-width: 0;
- border-style: solid;
- border-color: rgba(0, 0, 0, 0.12);
- border-bottom-width: thin;
- margin-left: 16px;
- margin-right: 16px;
- -webkit-align-self: stretch;
- -ms-flex-item-align: stretch;
- align-self: stretch;
- height: auto;
-}
-
should render expected buttons on hover for 'root'
d="M7,7H11V9H7A3,3 0 0,0 4,12A3,3 0 0,0 7,15H11V17H7A5,5 0 0,1 2,12A5,5 0 0,1 7,7M17,7A5,5 0 0,1 22,12H20A3,3 0 0,0 17,9H13V7H17M8,11H16V13H8V11M17,12H19V15H22V17H19V20H17V17H14V15H17V12Z"
/>
-
-
-
-
diff --git a/src/components/speeddial/components/__tests__/add-tile.test.tsx b/src/components/speeddial/components/__tests__/add-tile.test.tsx
index aff6b4c..3965419 100644
--- a/src/components/speeddial/components/__tests__/add-tile.test.tsx
+++ b/src/components/speeddial/components/__tests__/add-tile.test.tsx
@@ -1,107 +1,105 @@
import userEvent from '@testing-library/user-event';
-import { render } from '@@config/test-utils';
-import { useAppDispatch } from '@@data/index';
-import { actions as speeddialActions, ROOT_SPEEDDIAL_ID } from '@@data/speeddial/slice';
+import { render, screen } from '@@config/test-utils';
import { AddNewTile } from '../add-tile';
-// MUST be full or relative path, vite-tsconfig-paths doesn't resolve vitest.mock paths using `compilerOptions.paths` setting
-vitest.mock('src/data/index', async importOriginal => {
- const module = await importOriginal();
- return {
- ...module,
- useAppDispatch: vitest.fn(module.useAppDispatch)
- };
-});
describe('speeddial/add-tile', () => {
- const mockDispatch = vitest.fn();
- vitest.mocked(useAppDispatch).mockReturnValue(mockDispatch);
-
beforeEach(() => {
vitest.clearAllMocks();
});
it.each([
- { type: 'root', parentId: ROOT_SPEEDDIAL_ID },
- { type: 'non-root', parentId: 'some other id' }
- ])('should render add icon without button for $type parent', ({ parentId }) => {
- const { container, queryAllByRole, queryByLabelText } = render();
+ {
+ expectedNotToExist: ['tooltips.add_link_or_group', 'tooltips.add_group'],
+ hover: 'tooltips.add_link',
+ onAddLink: vitest.fn(),
+ onAddGroup: undefined
+ },
+ {
+ expectedNotToExist: ['tooltips.add_link', 'tooltips.add_group'],
+ hover: 'tooltips.add_link_or_group',
+ onAddLink: vitest.fn(),
+ onAddGroup: vitest.fn()
+ }
+ ])('should render add icon without button', ({ expectedNotToExist, hover, onAddGroup, onAddLink }) => {
+ const { container } = render();
- expect(queryAllByRole('button')).toHaveLength(0);
+ expect(screen.queryAllByRole('button')).toHaveLength(0);
expect(container.querySelectorAll('svg')).toHaveLength(1);
- expect(queryByLabelText('tooltips.add_link_or_group')).toBeInTheDocument();
- expect(queryByLabelText('tooltips.add_link')).not.toBeInTheDocument();
- expect(queryByLabelText('tooltips.add_group')).not.toBeInTheDocument();
+ expect(screen.getByLabelText(hover)).toBeInTheDocument();
+
+ expectedNotToExist.forEach(label => {
+ expect(screen.queryByLabelText(label)).not.toBeInTheDocument();
+ });
+
expect(container).toMatchSnapshot();
});
it.each([
{
- type: 'root',
- parentId: ROOT_SPEEDDIAL_ID,
+ hover: 'tooltips.add_link_or_group',
+ onAddLink: vitest.fn(),
+ onAddGroup: vitest.fn(),
expectedButtonLabels: ['tooltips.add_link', 'tooltips.add_group'],
notExpectedButtonLabels: ['tooltips.add_link_or_group']
},
{
- type: 'non-root',
- parentId: 'some other id',
+ hover: 'tooltips.add_link',
+ onAddLink: vitest.fn(),
+ onAddGroup: undefined,
expectedButtonLabels: ['tooltips.add_link'],
notExpectedButtonLabels: ['tooltips.add_link_or_group', 'tooltips.add_group']
}
- ])('should render expected buttons on hover for $type parent', async ({ parentId, expectedButtonLabels, notExpectedButtonLabels }) => {
+ ])('should render expected buttons on hover', async ({ hover, expectedButtonLabels, notExpectedButtonLabels, ...props }) => {
const user = userEvent.setup();
- const { container, queryAllByRole, queryByLabelText } = render();
+ const { container } = render();
- await user.hover(queryByLabelText('tooltips.add_link_or_group') as HTMLElement);
- const buttons = queryAllByRole('button');
+ await user.hover(screen.getByLabelText(hover));
+ const buttons = screen.getAllByRole('button');
expect(buttons).toHaveLength(expectedButtonLabels.length);
expectedButtonLabels.forEach(label => {
- expect(queryByLabelText(label)).toBeInTheDocument();
+ expect(screen.getByLabelText(label)).toBeInTheDocument();
});
notExpectedButtonLabels.forEach(label => {
- expect(queryByLabelText(label)).not.toBeInTheDocument();
+ expect(screen.queryByLabelText(label)).not.toBeInTheDocument();
});
expect(container).toMatchSnapshot();
});
- it.each([
- {
- actionName: 'add link',
- type: 'root',
- parentId: ROOT_SPEEDDIAL_ID,
- expectedAction: speeddialActions.createLink({ parentId: ROOT_SPEEDDIAL_ID }),
- clickLabel: 'tooltips.add_link'
- },
- {
- actionName: 'add link',
- type: 'non-root',
- parentId: 'some other id',
- expectedAction: speeddialActions.createLink({ parentId: 'some other id' }),
- clickLabel: 'tooltips.add_link'
- },
- {
- actionName: 'add group',
- type: 'root',
- parentId: ROOT_SPEEDDIAL_ID,
- expectedAction: speeddialActions.createGroup(),
- clickLabel: 'tooltips.add_group'
- }
- ])('$clickLabel should dispatch expected action for $type parent upon clicking', async ({ clickLabel, expectedAction, parentId }) => {
- const user = userEvent.setup();
- const { queryByLabelText } = render();
-
- await user.hover(queryByLabelText('tooltips.add_link_or_group') as HTMLElement);
- const button = queryByLabelText(clickLabel);
-
- expect(button).toBeInTheDocument();
-
- await user.click(button as HTMLElement);
-
- expect(mockDispatch).toHaveBeenCalledWith(expectedAction);
+ describe('callbacks', () => {
+ const onAddLink = vitest.fn();
+ const onAddGroup = vitest.fn();
+
+ it.each([
+ {
+ clickLabel: 'tooltips.add_link',
+ expectation: () => {
+ expect(onAddLink).toHaveBeenCalled();
+ }
+ },
+ {
+ clickLabel: 'tooltips.add_group',
+ expectation: () => {
+ expect(onAddGroup).toHaveBeenCalled();
+ }
+ }
+ ])('$clickLabel should call back expected function', async ({ clickLabel, expectation }) => {
+ const user = userEvent.setup();
+ render();
+
+ await user.hover(screen.getByLabelText('tooltips.add_link_or_group'));
+ const button = await screen.findByLabelText(clickLabel);
+
+ expect(button).toBeInTheDocument();
+
+ await user.click(button);
+
+ expectation();
+ });
});
});
diff --git a/src/components/speeddial/components/add-tile.tsx b/src/components/speeddial/components/add-tile.tsx
index b742347..4b0e677 100644
--- a/src/components/speeddial/components/add-tile.tsx
+++ b/src/components/speeddial/components/add-tile.tsx
@@ -5,9 +5,6 @@ import type { FC } from 'react';
import { useState, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
-import { useAppDispatch } from '@@data/index';
-import { actions as speeddialActions, ROOT_SPEEDDIAL_ID } from '@@data/speeddial/slice';
-
import { TILE_WIDTH } from './_constants';
@@ -29,18 +26,9 @@ const AddTileContent = styled(CardContent)({
'flex': 1
});
-export const AddNewTile: FC = ({ parentId }) => {
+export const AddNewTile: FC = ({ onAddGroup, onAddLink }) => {
const { t } = useTranslation();
- const dispatch = useAppDispatch();
-
- const onAddLink = useCallback(() => {
- dispatch(speeddialActions.createLink({ parentId }));
- }, [dispatch, parentId]);
- const onAddGroup = useCallback(() => {
- dispatch(speeddialActions.createGroup());
- }, [dispatch]);
-
const [isMouseOver, setIsMouseOver] = useState(false);
const onMouseOver = useCallback(() => {
setIsMouseOver(true);
@@ -51,7 +39,7 @@ export const AddNewTile: FC = ({ parentId }) => {
const defaultContent = (
@@ -65,19 +53,20 @@ export const AddNewTile: FC = ({ parentId }) => {
>
- {/* eslint-disable-next-line no-warning-comments */}
- {parentId === ROOT_SPEEDDIAL_ID && ( // TODO: make universal
- <>
-
-
-
-
- >
- )}
+ {onAddGroup
+ ? (
+ <>
+
+
+
+
+ >
+ )
+ : null}
>
);
@@ -95,5 +84,6 @@ export const AddNewTile: FC = ({ parentId }) => {
};
interface Props {
- parentId: string;
+ onAddGroup?: () => void;
+ onAddLink: () => void;
}
diff --git a/src/components/speeddial/components/tile-image-placeholder.tsx b/src/components/speeddial/components/tile-image-placeholder.tsx
new file mode 100644
index 0000000..93b55fc
--- /dev/null
+++ b/src/components/speeddial/components/tile-image-placeholder.tsx
@@ -0,0 +1,15 @@
+import { Typography } from '@mui/material';
+import { memo } from 'react';
+
+
+export const TileImagePlaceholder = memo<{ name: string; fontSize?: number }>(({ name, fontSize }) => (
+
+ {name
+ .split(' ')
+ .filter(Boolean)
+ .splice(0, 2)
+ .map(word => word[0])
+ .join('')}
+
+));
+TileImagePlaceholder.displayName = 'TileImagePlaceholder';
diff --git a/src/components/speeddial/edit-group-dialog.tsx b/src/components/speeddial/edit-group-dialog.tsx
index 7d5d2da..9fe35e3 100644
--- a/src/components/speeddial/edit-group-dialog.tsx
+++ b/src/components/speeddial/edit-group-dialog.tsx
@@ -5,10 +5,11 @@ import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import type { RootState } from '@@data/index';
-import { useAppDispatch } from '@@data/index';
+import { useAppDispatch } from '@@data/redux-toolkit';
import { getGroupEditTile, groupAdapterSelectors } from '@@data/speeddial/selectors';
import { actions as speeddialActions } from '@@data/speeddial/slice';
+import { useAutofocus } from './hooks/use-autofocus';
import { useDraft } from './hooks/use-draft';
import { useFormHandlers } from './hooks/use-form-handlers';
@@ -32,6 +33,8 @@ export const GroupEditDialog: FC = () => {
const formHandlers = useFormHandlers({ cancel: onCloseWithoutSave, submit: onSave });
+ const inputRef = useAutofocus(Boolean(tile));
+
return (