diff --git a/.eslintrc.json b/.eslintrc.json index 80311ca60f..425974a61f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,41 +1,44 @@ { - "globals": { - "EMOJIS": true, - "PRODUCTION": true, - "SCOPE_VERSION": true, - "TRANSLATIONS": true, - "oc_userconfig": true, - "appName": true, - "appVersion": true - }, - "extends": [ - "@nextcloud/eslint-config/vue3" - ], - "parserOptions": { - "babelOptions": { - "plugins": [ - "@babel/plugin-syntax-import-assertions" - ] - } - }, - "rules": { - "@nextcloud/no-deprecations": [ - "error", - { - "parseAppInfo": false - } - ], - "@nextcloud/no-removed-apis": [ - "error", - { - "parseAppInfo": false - } - ], - "import/no-unresolved": [ - "error", - { - "ignore": ["\\?raw$"] - } - ] - } + "globals": { + "EMOJIS": true, + "PRODUCTION": true, + "SCOPE_VERSION": true, + "TRANSLATIONS": true, + "oc_userconfig": true, + "appName": true, + "appVersion": true + }, + "extends": [ + "@nextcloud/eslint-config/vue3", + "plugin:storybook/recommended" + ], + "parserOptions": { + "babelOptions": { + "plugins": [ + "@babel/plugin-syntax-import-assertions" + ] + } + }, + "rules": { + "@nextcloud/no-deprecations": [ + "error", + { + "parseAppInfo": false + } + ], + "@nextcloud/no-removed-apis": [ + "error", + { + "parseAppInfo": false + } + ], + "import/no-unresolved": [ + "error", + { + "ignore": [ + "\\?raw$" + ] + } + ] + } } diff --git a/.gitignore b/.gitignore index 38b2456656..c94a5d1e4e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,5 @@ styleguide/index.html /playwright-report/ /blob-report/ /tests/component/setup/.cache/ + +*storybook.log \ No newline at end of file diff --git a/.storybook/globals.mock.ts b/.storybook/globals.mock.ts new file mode 100644 index 0000000000..dc13ce8663 --- /dev/null +++ b/.storybook/globals.mock.ts @@ -0,0 +1,110 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +/** + * From server util.js + * + * @param {string} t The string to chunkify + * @return {Array} + */ +function chunkify(t) { + // Adapted from http://my.opera.com/GreyWyvern/blog/show.dml/1671288 + const tz = [] + let x = 0 + let y = -1 + let n = 0 + let c + + while (x < t.length) { + c = t.charAt(x) // only include the dot in strings + + const m = !n && (c === '.' || (c >= '0' && c <= '9')) + + if (m !== n) { + // next chunk + y++ + tz[y] = '' + n = m + } + + tz[y] += c + x++ + } + + return tz +} + +// Global variables +window._oc_webroot = '' + +window.OC = { + debug: true, + getCurrentUser() { + return { + uid: 'admin', + displayName: 'Administrator', + } + }, + generateUrl() { + return 'https://raw.githubusercontent.com/nextcloud/promo/master/nextcloud-icon.png' + }, + getLanguage() { + return 'en' + }, + isUserAdmin() { + return true + }, + config: {}, + Util: { + /** + * Compare two strings to provide a natural sort + * + * @param {string} a first string to compare + * @param {string} b second string to compare + * @return {number} -1 if b comes before a, 1 if a comes before b + * or 0 if the strings are identical + */ + naturalSortCompare: function naturalSortCompare(a, b) { + let x + const aa = chunkify(a) + const bb = chunkify(b) + + for (x = 0; aa[x] && bb[x]; x++) { + if (aa[x] !== bb[x]) { + const aNum = Number(aa[x]) + const bNum = Number(bb[x]) // note: == is correct here + + /* eslint-disable-next-line */ + if (aNum == aa[x] && bNum == bb[x]) { + return aNum - bNum + } else { + // Note: This locale setting isn't supported by all browsers but for the ones + // that do there will be more consistency between client-server sorting + return aa[x].localeCompare(bb[x], OC.getLanguage()) + } + } + } + + return aa.length - bb.length + }, + }, + coreApps: [ + '', + 'admin', + 'log', + 'core/search', + 'core', + '3rdparty', + ], + appswebroots: { + calendar: '/apps/calendar', + deck: '/apps/deck', + files: '/apps/files', + spreed: '/apps/spreed', + }, + webroot: '', +} +window.OCA = {} +window.appName = 'nextcloud-vue' diff --git a/.storybook/http.mock.ts b/.storybook/http.mock.ts new file mode 100644 index 0000000000..bb68582215 --- /dev/null +++ b/.storybook/http.mock.ts @@ -0,0 +1,41 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +// A copy of styleguide/global.requires.js +// TODO: use proper mocking with msw.io +// @ts-nocheck + +import axios from '@nextcloud/axios' + +const USER_GROUPS = [ + { id: 'admin', displayname: 'The administrators' }, + { id: 'accounting', displayname: 'Accounting team' }, + { id: 'developer', displayname: 'Engineering team' }, + { id: 'support', displayname: 'Support crew' }, + { id: 'users', displayname: 'users' }, +] + +/** + * Mock some requests for docs + * + * @param {object} error Axios error + */ +function mockRequests(error) { + const { request } = error + let data = null + + // Mock requesting groups + const requestGroups = request.responseURL.match(/cloud\/groups\/details\?search=([^&]*)&limit=\d+$/) + if (requestGroups) { + data = { groups: USER_GROUPS.filter(e => !requestGroups[1] || e.displayname.startsWith(requestGroups[1]) || e.id.startsWith(requestGroups[1])) } + } + + if (data) { + return Promise.resolve({ data: { ocs: { data } } }) + } + return Promise.reject(error) +} + +axios.interceptors.response.use((r) => r, e => mockRequests(e)) diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000000..ac2769755f --- /dev/null +++ b/.storybook/main.ts @@ -0,0 +1,24 @@ +import type { StorybookConfig } from '@storybook/vue3-vite' + +const config: StorybookConfig = { + stories: ['../docs/**/*.mdx', '../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'], + + addons: [ + // Collection of essential addons like actions, controls, backgrounds, viewport, etc. + '@storybook/addon-essentials', + // Links between stories + '@storybook/addon-links', + // Allows to see and edit the source code of a story + '@storybook/addon-storysource', + ], + + framework: { + name: '@storybook/vue3-vite', + options: { + // vue-component-meta is recommended for Vue 3 and will be the default in the future + docgen: 'vue-component-meta', + }, + }, +} + +export default config diff --git a/.storybook/preview.ts b/.storybook/preview.ts new file mode 100644 index 0000000000..66d88ba110 --- /dev/null +++ b/.storybook/preview.ts @@ -0,0 +1,34 @@ +/** + * SPDX-FileCopyrightText: 2024 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +import type { Preview } from '@storybook/vue3' + +import './styles.mock.ts' +import './http.mock.ts' +import './globals.mock.ts' + +const preview: Preview = { + parameters: { + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/i, + }, + }, + }, + + decorators: [ + (_, { parameters }) => { + const { pageLayout } = parameters + // TODO: add layouts for app navigation/sidebar + switch (pageLayout) { + default: + return { template: `
- |
-
- |
-
---|---|
Lorem ipsum | -8 MiB | -
Tertiary, no background
+Tertiary
+Secondary
+Primary
+Success
+Warning
+Error
+-
+-
+
+ |
+
+ |
+
---|---|
+ Lorem ipsum + | ++ 8 MiB + | +