diff --git a/.storybook/modes.js b/.storybook/modes.js index 9b616bd10..ed5837415 100644 --- a/.storybook/modes.js +++ b/.storybook/modes.js @@ -9,6 +9,11 @@ export const allModes = { }, // for snapshotting scrollable areas with all content, like modals desktopLargeHeight: { + theme: 'ltr', + viewport: 'largeHeight', + }, + 'desktopLargeHeight rtl': { viewport: 'largeHeight', + theme: 'rtl', }, }; diff --git a/.storybook/preview.js b/.storybook/preview.js index a6bf329ea..4740f1920 100644 --- a/.storybook/preview.js +++ b/.storybook/preview.js @@ -1,6 +1,8 @@ /** @type { import('@storybook/vue3').Preview } */ import {withThemeByDataAttribute} from '@storybook/addon-themes'; +import {mockDateDecorator} from 'storybook-mock-date-decorator'; + import {setup} from '@storybook/vue3'; import GlobalMixins from '@/mixins/global.js'; import emitter from 'tiny-emitter/instance'; @@ -21,7 +23,6 @@ import Tabs from '@/components/Tabs/Tabs.vue'; import FloatingVue from 'floating-vue'; import PkpDialog from '@/components/Modal/Dialog.vue'; -import {useDialogStore} from '@/stores/dialogStore'; import VueScrollTo from 'vue-scrollto'; @@ -106,7 +107,7 @@ const preview = { * is too late fort tinyMCE which needs to detect it on first render correctly * */ - document.body.setAttribute('dir', globals.theme); + document.body.setAttribute('dir', globals.theme || 'ltr'); return story(); }, (story) => ({ @@ -115,22 +116,18 @@ const preview = { }), /** Globally Available Dialog */ (story) => ({ - setup() { - const dialogStore = useDialogStore(); - return {dialogStore}; - }, + setup() {}, components: {story, PkpDialog}, template: `
- +
`, }), + mockDateDecorator, ], parameters: { + // remove default storybook padding as it likely cuts off modals + layout: 'fullscreen', actions: {argTypesRegex: '^on[A-Z].*'}, controls: { expanded: true, @@ -159,7 +156,7 @@ const preview = { /** For scrollable scenarios */ largeHeight: { name: 'Large', - styles: {width: '1024px', height: '1500px'}, + styles: {width: '1280px', height: '1600px'}, }, }, }, diff --git a/package-lock.json b/package-lock.json index 59ebd81ad..649ee775e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63,6 +63,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "storybook": "^7.6.5", + "storybook-mock-date-decorator": "^1.0.1", "tailwindcss": "3.4", "vite": "^4.4.9", "vitest": "^1.0.4" @@ -4278,6 +4279,287 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/addons": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/addons/-/addons-6.5.16.tgz", + "integrity": "sha512-p3DqQi+8QRL5k7jXhXmJZLsE/GqHqyY6PcoA1oNTJr0try48uhTGUOYkgzmqtDaa/qPFO5LP+xCPzZXckGtquQ==", + "dev": true, + "dependencies": { + "@storybook/api": "6.5.16", + "@storybook/channels": "6.5.16", + "@storybook/client-logger": "6.5.16", + "@storybook/core-events": "6.5.16", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/router": "6.5.16", + "@storybook/theming": "6.5.16", + "@types/webpack-env": "^1.16.0", + "core-js": "^3.8.2", + "global": "^4.4.0", + "regenerator-runtime": "^0.13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/addons/node_modules/@storybook/channels": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", + "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", + "dev": true, + "dependencies": { + "core-js": "^3.8.2", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/addons/node_modules/@storybook/client-logger": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", + "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "dev": true, + "dependencies": { + "core-js": "^3.8.2", + "global": "^4.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/addons/node_modules/@storybook/core-events": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", + "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "dev": true, + "dependencies": { + "core-js": "^3.8.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/addons/node_modules/@storybook/csf": { + "version": "0.0.2--canary.4566f4d.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", + "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/@storybook/addons/node_modules/@storybook/router": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", + "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "6.5.16", + "core-js": "^3.8.2", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/addons/node_modules/@storybook/theming": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", + "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "6.5.16", + "core-js": "^3.8.2", + "memoizerific": "^1.11.3", + "regenerator-runtime": "^0.13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/addons/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/@storybook/api": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/api/-/api-6.5.16.tgz", + "integrity": "sha512-HOsuT8iomqeTMQJrRx5U8nsC7lJTwRr1DhdD0SzlqL4c80S/7uuCy4IZvOt4sYQjOzW5fOo/kamcoBXyLproTA==", + "dev": true, + "dependencies": { + "@storybook/channels": "6.5.16", + "@storybook/client-logger": "6.5.16", + "@storybook/core-events": "6.5.16", + "@storybook/csf": "0.0.2--canary.4566f4d.1", + "@storybook/router": "6.5.16", + "@storybook/semver": "^7.3.2", + "@storybook/theming": "6.5.16", + "core-js": "^3.8.2", + "fast-deep-equal": "^3.1.3", + "global": "^4.4.0", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3", + "regenerator-runtime": "^0.13.7", + "store2": "^2.12.0", + "telejson": "^6.0.8", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/api/node_modules/@storybook/channels": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/channels/-/channels-6.5.16.tgz", + "integrity": "sha512-VylzaWQZaMozEwZPJdyJoz+0jpDa8GRyaqu9TGG6QGv+KU5POoZaGLDkRE7TzWkyyP0KQLo80K99MssZCpgSeg==", + "dev": true, + "dependencies": { + "core-js": "^3.8.2", + "ts-dedent": "^2.0.0", + "util-deprecate": "^1.0.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/api/node_modules/@storybook/client-logger": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.5.16.tgz", + "integrity": "sha512-pxcNaCj3ItDdicPTXTtmYJE3YC1SjxFrBmHcyrN+nffeNyiMuViJdOOZzzzucTUG0wcOOX8jaSyak+nnHg5H1Q==", + "dev": true, + "dependencies": { + "core-js": "^3.8.2", + "global": "^4.4.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/api/node_modules/@storybook/core-events": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.5.16.tgz", + "integrity": "sha512-qMZQwmvzpH5F2uwNUllTPg6eZXr2OaYZQRRN8VZJiuorZzDNdAFmiVWMWdkThwmyLEJuQKXxqCL8lMj/7PPM+g==", + "dev": true, + "dependencies": { + "core-js": "^3.8.2" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + } + }, + "node_modules/@storybook/api/node_modules/@storybook/csf": { + "version": "0.0.2--canary.4566f4d.1", + "resolved": "https://registry.npmjs.org/@storybook/csf/-/csf-0.0.2--canary.4566f4d.1.tgz", + "integrity": "sha512-9OVvMVh3t9znYZwb0Svf/YQoxX2gVOeQTGe2bses2yj+a3+OJnCrUF3/hGv6Em7KujtOdL2LL+JnG49oMVGFgQ==", + "dev": true, + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/@storybook/api/node_modules/@storybook/router": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/router/-/router-6.5.16.tgz", + "integrity": "sha512-ZgeP8a5YV/iuKbv31V8DjPxlV4AzorRiR8OuSt/KqaiYXNXlOoQDz/qMmiNcrshrfLpmkzoq7fSo4T8lWo2UwQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "6.5.16", + "core-js": "^3.8.2", + "memoizerific": "^1.11.3", + "qs": "^6.10.0", + "regenerator-runtime": "^0.13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/api/node_modules/@storybook/theming": { + "version": "6.5.16", + "resolved": "https://registry.npmjs.org/@storybook/theming/-/theming-6.5.16.tgz", + "integrity": "sha512-hNLctkjaYLRdk1+xYTkC1mg4dYz2wSv6SqbLpcKMbkPHTE0ElhddGPHQqB362md/w9emYXNkt1LSMD8Xk9JzVQ==", + "dev": true, + "dependencies": { + "@storybook/client-logger": "6.5.16", + "core-js": "^3.8.2", + "memoizerific": "^1.11.3", + "regenerator-runtime": "^0.13.7" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/storybook" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@storybook/api/node_modules/isobject": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-4.0.0.tgz", + "integrity": "sha512-S/2fF5wH8SJA/kmwr6HYhK/RI/OkhD84k8ntalo0iJjZikgq1XFvR5M8NPT1x5F7fBwCG3qHfnzeP/Vh/ZxCUA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@storybook/api/node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "dev": true + }, + "node_modules/@storybook/api/node_modules/telejson": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/telejson/-/telejson-6.0.8.tgz", + "integrity": "sha512-nerNXi+j8NK1QEfBHtZUN/aLdDcyupA//9kAboYLrtzZlPLpUfqbVGWb9zz91f/mIjRbAYhbgtnJHY8I1b5MBg==", + "dev": true, + "dependencies": { + "@types/is-function": "^1.0.0", + "global": "^4.4.0", + "is-function": "^1.0.2", + "is-regex": "^1.1.2", + "is-symbol": "^1.0.3", + "isobject": "^4.0.0", + "lodash": "^4.17.21", + "memoizerific": "^1.11.3" + } + }, "node_modules/@storybook/blocks": { "version": "7.6.5", "resolved": "https://registry.npmjs.org/@storybook/blocks/-/blocks-7.6.5.tgz", @@ -5004,6 +5286,74 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-SWeszlsiPsMI0Ps0jVNtH64cI5c0UF3f7KgjVKJoNP30crQ6wUSddY2hsdeczZXEKVJGEn50Q60flcGsQGIcrg==", + "dev": true, + "dependencies": { + "core-js": "^3.6.5", + "find-up": "^4.1.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@storybook/semver/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/semver/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@storybook/semver/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@storybook/semver/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@storybook/telemetry": { "version": "7.6.5", "resolved": "https://registry.npmjs.org/@storybook/telemetry/-/telemetry-7.6.5.tgz", @@ -5330,6 +5680,12 @@ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", "dev": true }, + "node_modules/@types/is-function": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@types/is-function/-/is-function-1.0.3.tgz", + "integrity": "sha512-/CLhCW79JUeLKznI6mbVieGbl4QU5Hfn+6udw1YHZoofASjbQ5zaP5LzAUZYDpRYEjS4/P+DhEgyJ/PQmGGTWw==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -5527,6 +5883,12 @@ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz", "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==" }, + "node_modules/@types/webpack-env": { + "version": "1.18.4", + "resolved": "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.18.4.tgz", + "integrity": "sha512-I6e+9+HtWADAWeeJWDFQtdk4EVSAbj6Rtz4q8fJ7mSr1M0jzlFcs8/HZ+Xb5SHzVm1dxH7aUiI+A8kA8Gcrm0A==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -7550,7 +7912,6 @@ "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.33.3.tgz", "integrity": "sha512-lo0kOocUlLKmm6kv/FswQL8zbkH7mVsLJ/FULClOhv8WRVmKLVcs6XPNQAzstfeJTCHMyButEwG+z1kHxHoDZw==", "hasInstallScript": true, - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/core-js" @@ -8073,6 +8434,12 @@ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", "dev": true }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -9594,6 +9961,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dev": true, + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "node_modules/globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -10313,6 +10690,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==", + "dev": true + }, "node_modules/is-generator-function": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", @@ -12445,6 +12828,15 @@ "node": ">=6" } }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dev": true, + "dependencies": { + "dom-walk": "^0.1.0" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -12536,6 +12928,12 @@ "ufo": "^1.3.0" } }, + "node_modules/mockdate": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/mockdate/-/mockdate-3.0.5.tgz", + "integrity": "sha512-iniQP4rj1FhBdBYS/+eQv7j1tadJ9lJtdzgOpvsOHng/GbcDh2Fhdeq+ZRldrPYdXvCyfFUmFeEwEGXZB5I/AQ==", + "dev": true + }, "node_modules/moment": { "version": "2.29.4", "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", @@ -15068,6 +15466,16 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/storybook-mock-date-decorator": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/storybook-mock-date-decorator/-/storybook-mock-date-decorator-1.0.1.tgz", + "integrity": "sha512-ZHrl8ZdB++g1d3VmPYzUqguqwiXc6HOF6eUrC4m2xjAtTq9aDhHKqAiQu90TMatrZwkMJULfHpmG2qPLl3fRQQ==", + "dev": true, + "dependencies": { + "@storybook/addons": "^6.4.19", + "mockdate": "^3.0.5" + } + }, "node_modules/stream-shift": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", diff --git a/package.json b/package.json index 610705d4e..e375c074b 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "storybook": "^7.6.5", + "storybook-mock-date-decorator": "^1.0.1", "tailwindcss": "3.4", "vite": "^4.4.9", "vitest": "^1.0.4" diff --git a/src/App.vue b/src/App.vue index 89fa187fd..606f1f07d 100644 --- a/src/App.vue +++ b/src/App.vue @@ -1,10 +1,6 @@ +``` + +### Controlling Modal Component + +```html +/** SubmissionModal.vue */ + + +``` + +## SideModal Props + + + +## SideModalBody Slots + + + + diff --git a/src/components/Modal/SideModal.stories.js b/src/components/Modal/SideModal.stories.js new file mode 100644 index 000000000..57852d794 --- /dev/null +++ b/src/components/Modal/SideModal.stories.js @@ -0,0 +1,281 @@ +import {within, userEvent} from '@storybook/testing-library'; +import {ref} from 'vue'; +import SideModal from './SideModal.vue'; +import SideModalBody from './SideModalBody.vue'; +import PkpForm from '@/components/Form/Form.vue'; +import cloneDeep from 'clone-deep'; +import FormMock from '@/docs/components/Form/helpers/form-announcement'; +import Tabs from '@/components/Tabs/Tabs.vue'; +import Tab from '@/components/Tabs/Tab.vue'; + +import {allModes} from '../../../.storybook/modes.js'; + +export default { + title: 'Components/SideModal', + component: SideModal, +}; + +export const Base = { + render: (args) => ({ + components: {SideModal, SideModalBody}, + setup() { + const isModalOpened = ref(false); + function closeModal() { + isModalOpened.value = false; + } + return {isModalOpened, closeModal}; + }, + template: ` + + Open Modal + + + + + + + + + +
+
CONTENT
+
+
+
+ `, + }), + decorators: [ + () => ({ + template: '
', + }), + ], + + args: {}, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Open Modal')); + }, +}; + +export const WithForm = { + render: (args) => ({ + components: {SideModal, SideModalBody, PkpForm}, + setup() { + const isModalOpened = ref(false); + function closeModal() { + isModalOpened.value = false; + } + + const form = ref({ + ...cloneDeep(FormMock), + action: 'https://httpbin.org', + method: 'GET', + }); + + return {isModalOpened, closeModal, form}; + }, + template: ` + + Modal with Form + + + + + +
+
+ +
+
+
+
+ `, + }), + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Modal with Form')); + }, + + decorators: [ + () => ({ + template: '
', + }), + ], + parameters: { + chromatic: { + modes: { + desktop: {disable: true}, + 'desktop rtl': {disable: true}, + desktopLargeHeight: allModes['desktopLargeHeight'], + 'desktopLargeHeight rtl': allModes['desktopLargeHeight rtl'], + }, + }, + }, + + args: {}, +}; + +export const WithTabs = { + render: (args) => ({ + components: {SideModal, SideModalBody, Tab, Tabs}, + setup() { + const isModalOpened = ref(false); + function closeModal() { + isModalOpened.value = false; + } + return {isModalOpened, closeModal}; + }, + template: ` + + Modal with Tabs + + + + + +
+
+

+ Avoid complex interactions in modals whenever possible. Deeply nested + tabs like this are often a sign that a single modal is trying to do too + much. If there is more than one task that can be completed in a modal, + it is usually better to use a different UI pattern. +

+ + + + + Lorem ipsum dolor sit amet + + + Amet sit dolor ipsum Lorem + + + Dolor ipsum sit lorem amet + + + + This is the second tab. + This is the third tab. + +
+ Close +
+ +
+ +
+
+
+ `, + }), + decorators: [ + () => ({ + template: '
', + }), + ], + args: {}, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Modal with Tabs')); + }, +}; + +export const NestedModal = { + render: (args) => ({ + components: {SideModal, SideModalBody}, + setup() { + const isModalOpened = ref(false); + function closeModal() { + isModalOpened.value = false; + } + + const isModalOpened2 = ref(false); + function closeModal2() { + isModalOpened2.value = false; + } + + return {isModalOpened, closeModal, isModalOpened2, closeModal2}; + }, + template: ` + + Nested modal + + + + +
+
+ Open Second Modal +
+
+ + + +
+
+ Content +
+
+
+
+
+
+ `, + }), + decorators: [ + () => ({ + template: '
', + }), + ], + args: {}, + play: async ({canvasElement}) => { + // Get parent element, as modals are in canvasElement sibling element + const canvas = within(canvasElement.parentElement); + const user = userEvent.setup(); + console.log(canvasElement); + + await user.click(canvas.getByText('Nested modal')); + await user.click(await canvas.findByText('Open Second Modal')); + }, +}; diff --git a/src/components/Modal/SideModal.vue b/src/components/Modal/SideModal.vue index e6d1ec71c..b25fccf8d 100644 --- a/src/components/Modal/SideModal.vue +++ b/src/components/Modal/SideModal.vue @@ -51,11 +51,6 @@ defineProps({ type: Boolean, required: true, }, - - closeLabel: { - type: String, - required: true, - }, }); const emit = defineEmits(['close']); @@ -78,235 +73,3 @@ function handleClose() { provide('closeModal', handleClose); provide('registerCloseCallback', registerCloseCallback); - - diff --git a/src/components/Modal/SideModalBody.vue b/src/components/Modal/SideModalBody.vue index aed3cfd48..7d5f96589 100644 --- a/src/components/Modal/SideModalBody.vue +++ b/src/components/Modal/SideModalBody.vue @@ -1,18 +1,18 @@ +``` + + diff --git a/src/composables/useDialog.stories.js b/src/composables/useDialog.stories.js new file mode 100644 index 000000000..68856439c --- /dev/null +++ b/src/composables/useDialog.stories.js @@ -0,0 +1,99 @@ +import {within, userEvent} from '@storybook/testing-library'; + +import {useDialog} from './useDialog'; +import PkpButton from '@/components/Button/Button.vue'; + +export default { + title: 'composables/useDialog', + render: (args) => ({ + components: {PkpButton}, + setup() { + const {openDialog} = useDialog(); + + return {openDialog, args}; + }, + template: ` + {{args.buttonName}} + `, + }), +}; + +export const BasicExample = { + args: { + buttonName: 'Basic Example', + name: 'basic', + title: 'Submit Article', + message: 'Are you sure you want to submit this article?', + actions: [ + { + label: 'Confirm', + isPrimary: true, + callback: (close) => { + // Simulate a server request + setTimeout(() => close(), 2000); + }, + }, + { + label: 'Cancel', + isWarnable: true, + callback: (close) => close(), + }, + ], + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Basic Example')); + }, + decorators: [ + () => ({ + template: '
', + }), + ], +}; + +export const FullExample = { + args: { + buttonName: 'Full Example', + + name: 'full', + title: 'Several Actions', + message: + 'This dialog includes an action that is a link (Visit Page). It also logs something to the console when the modal is closed. Dialogs should have 2 or 3 actions at the most.', + actions: [ + { + label: 'Confirm', + isPrimary: true, + callback: (close) => { + // Simulate a server request + setTimeout(() => close(), 2000); + }, + }, + { + label: 'Visit Page', + element: 'a', + href: 'https://example.org', + }, + { + label: 'Cancel', + isWarnable: true, + callback: (close) => close(), + }, + ], + close: () => console.log('closed full example dialog'), // eslint-disable-line + }, + play: async ({canvasElement}) => { + // Assigns canvas to the component root element + const canvas = within(canvasElement); + const user = userEvent.setup(); + + await user.click(canvas.getByText('Full Example')); + }, + decorators: [ + () => ({ + template: '
', + }), + ], +}; diff --git a/src/composables/useFetch.js b/src/composables/useFetch.js index 4ec8906b5..c2816daba 100644 --- a/src/composables/useFetch.js +++ b/src/composables/useFetch.js @@ -1,6 +1,6 @@ import {ref, unref} from 'vue'; import {ofetch, createFetch} from 'ofetch'; -import {useDialogStore} from '@/stores/dialogStore'; +import {useModalStore} from '@/stores/modalStore'; let ofetchInstance = ofetch; @@ -53,7 +53,7 @@ export function useFetch(url, options = {}) { const query = ref(_query || {}); const body = ref(_body || undefined); - const dialogStore = useDialogStore(); + const modalStore = useModalStore(); const isLoading = ref(false); const data = ref(null); const validationError = ref(null); @@ -111,7 +111,7 @@ export function useFetch(url, options = {}) { return; } - dialogStore.openDialogNetworkError(e); + modalStore.openDialogNetworkError(e); } finally { lastRequestController = null; isLoading.value = false; diff --git a/src/composables/useFetch.test.js b/src/composables/useFetch.test.js index 5f9333697..27bd3a3e6 100644 --- a/src/composables/useFetch.test.js +++ b/src/composables/useFetch.test.js @@ -14,7 +14,7 @@ import {HttpResponse, http, delay} from 'msw'; import {setActivePinia, createPinia} from 'pinia'; import {useFetch} from './useFetch'; -import {useDialogStore} from '@/stores/dialogStore'; +import {useModalStore} from '@/stores/modalStore'; export const restHandlers = [ http.get('http://mock/get/status200', async ({request}) => { @@ -198,14 +198,14 @@ describe('features', () => { }); test('network dialog error is displayed if there is http code other than 2XX', async () => { const url = ref('http://mock/get/status500'); - const dialogStore = useDialogStore(); - expect(dialogStore.dialogOpened).toBe(false); + const modalStore = useModalStore(); + expect(modalStore.dialogOpened).toBe(false); const {fetch} = useFetch(url); await fetch(); - expect(dialogStore.dialogOpened).toBe(true); - dialogStore.closeDialog(); - expect(dialogStore.dialogOpened).toBe(false); + expect(modalStore.dialogOpened).toBe(true); + modalStore.closeDialog(); + expect(modalStore.dialogOpened).toBe(false); }); }); diff --git a/src/mixins/dialog.js b/src/mixins/dialog.js index 1b66f8cae..81eb2afb4 100644 --- a/src/mixins/dialog.js +++ b/src/mixins/dialog.js @@ -7,7 +7,7 @@ * @see https://vuejs.org/v2/guide/mixins.html */ -import {useDialogStore} from '@/stores/dialogStore'; +import {useModalStore} from '@/stores/modalStore'; export default { methods: { @@ -17,8 +17,8 @@ export default { * @param {Object} props Props to pass to the component */ openDialog(props, modalProps, modalEvents) { - const dialogStore = useDialogStore(); - dialogStore.openDialog(props); + const modalStore = useModalStore(); + modalStore.openDialog(props); }, }, }; diff --git a/src/pages/submissions/AssignEditorsModal.vue b/src/pages/submissions/AssignEditorsModal.vue index aff5a94b8..0ef7cbb3e 100644 --- a/src/pages/submissions/AssignEditorsModal.vue +++ b/src/pages/submissions/AssignEditorsModal.vue @@ -1,9 +1,9 @@