diff --git a/.github/dependabot.yml b/.github/dependabot.yml index ef45db2e103..45cd46cc80a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,16 +8,12 @@ updates: open-pull-requests-limit: 3 versioning-strategy: lockfile-only groups: - storybook: - patterns: - - 'storybook*' - - '@storybook/*' angular: patterns: - '@angular*' fullcalendar: patterns: - - '@fullcalendar*' + - '@fullcalendar*' - package-ecosystem: "bundler" directory: "/" schedule: diff --git a/.github/workflows/cd-storybook.yml b/.github/workflows/cd-storybook.yml deleted file mode 100644 index e2404762ffc..00000000000 --- a/.github/workflows/cd-storybook.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: cd-storybook -on: - push: - branches: - - dev - -permissions: - contents: read - -jobs: - trigger_design_system_workflow: - permissions: - contents: none - if: github.repository == 'opf/openproject' - runs-on: ubuntu-latest - steps: - - name: Trigger downstream workflow - env: - TOKEN: ${{ secrets.OPENPROJECT_CI_TOKEN }} - DS_CD_WORKFLOW_ID: build-docs.yml - DS_REPOSITORY: opf/design-system - run: | - curl -i --fail-with-body -H"authorization: Bearer $TOKEN" \ - -XPOST -H"Accept: application/vnd.github.v3+json" \ - https://api.github.com/repos/$DS_REPOSITORY/actions/workflows/$DS_CD_WORKFLOW_ID/dispatches \ - -d '{ "ref": "dev", "inputs": { "ref": "${{ github.ref }}" }}' diff --git a/.github/workflows/pullpreview.yml b/.github/workflows/pullpreview.yml index a7a049b71e7..b60c278a68e 100644 --- a/.github/workflows/pullpreview.yml +++ b/.github/workflows/pullpreview.yml @@ -41,7 +41,6 @@ jobs: run: | cp ./docker/pullpreview/docker-compose.yml ./docker-compose.pullpreview.yml cp ./docker/prod/Dockerfile ./Dockerfile - cp ./docker/pullpreview-storybook/Dockerfile ./Dockerfile-storybook - uses: pullpreview/action@v5 with: admins: crohr,HDinger,machisuji,oliverguenther,ulferts,wielinde,cbliard diff --git a/.gitignore b/.gitignore index 80b94292914..f258caa4173 100644 --- a/.gitignore +++ b/.gitignore @@ -111,12 +111,8 @@ npm-debug.log* /frontend/npm-debug.log* /frontend/dist/ /frontend/tests/*.gif -/frontend/storybook-static node_modules/ -# Storybook data -/frontend/documentation.json - # Ignore global package-lock.json that generates /package-lock.json plaintext.yml diff --git a/docker-compose.override.example.yml b/docker-compose.override.example.yml index d41f34fcd83..f1bf691b9fb 100644 --- a/docker-compose.override.example.yml +++ b/docker-compose.override.example.yml @@ -12,10 +12,6 @@ services: ports: - "${PORT}:4200" - storybook: - ports: - - '6006:6006' - db: ports: - '5432:5432' diff --git a/docker-compose.yml b/docker-compose.yml index 46b8fbdc89a..5c77c0aa4a3 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -87,15 +87,6 @@ services: depends_on: - backend - storybook: - build: - <<: *frontend-build - command: "npm run storybook:serve" - volumes: - - ".:/home/dev/openproject" - networks: - - network - db: image: postgres:13 <<: *restart_policy diff --git a/docker/pullpreview-storybook/Dockerfile b/docker/pullpreview-storybook/Dockerfile deleted file mode 100644 index 4373ca9f451..00000000000 --- a/docker/pullpreview-storybook/Dockerfile +++ /dev/null @@ -1,21 +0,0 @@ -FROM node:20.9 as build - -COPY . /build -WORKDIR /build/frontend -RUN npm ci -RUN touch ./src/app/features/plugins/linked-plugins.styles.sass -RUN cp ./src/app/features/plugins/linked-plugins.module.ts.example ./src/app/features/plugins/linked-plugins.module.ts -RUN npm run storybook:build - -FROM caddy:2-alpine as prod - -ARG DOMAIN=my.pullpreview.com -ENV DOMAIN=$DOMAIN - -RUN mkdir -p /srv -COPY --from=build /build/frontend/storybook-static /srv/storybook -WORKDIR /srv/storybook -CMD caddy file-server \ - --root /srv/storybook \ - --listen 0.0.0.0:8080 - diff --git a/docs/development/design-system/README.md b/docs/development/design-system/README.md index cf7982b85ab..1e1eb721859 100644 --- a/docs/development/design-system/README.md +++ b/docs/development/design-system/README.md @@ -9,4 +9,6 @@ keywords: Design system, Primer, styles, design, components Starting in OpenProject 13.0., the [Primer Design System](https://primer.style/design/) is being used in OpenProject. Relevant reusable components from Primer as well as common patterns and compositions of these components will be documented in our [Lookbook](https://qa.openproject-edge.com/lookbook/). -Prior to 13.0., components were defined in its own Design System called SPOT which is slowly being replaced by Primer. Components still defined for SPOT are documented in a storybook found here: https://opf.github.io/design-system +Prior to 13.0., components were defined in its own Design System called SPOT which is slowly being replaced by Primer. +Components still defined for SPOT are documented in the last build of storybook found +here: https://opf.github.io/design-system diff --git a/docs/development/development-environment-docker/README.md b/docs/development/development-environment-docker/README.md index 4256becccb1..f6678caca7e 100644 --- a/docs/development/development-environment-docker/README.md +++ b/docs/development/development-environment-docker/README.md @@ -161,18 +161,6 @@ Again the first request to the server can take some time too. But subsequent req Changes you make to the code will be picked up automatically. No need to restart the containers. -### Storybook - -There is a service to launch the storybook of the SPOT design system in the local development environment. To run it, -simply use: - -```shell -# Start the worker and let them run continuously -docker compose up -d storybook -``` - -If you used the default overrides you will access the storybook now under `http://localhost:6006`. - ### Volumes There are volumes for diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js index 570f43ab25b..ee61c52ebdc 100644 --- a/frontend/.eslintrc.js +++ b/frontend/.eslintrc.js @@ -1,7 +1,6 @@ module.exports = { extends: [ "eslint:recommended", - "plugin:storybook/recommended", ], env: { browser: true, diff --git a/frontend/.storybook/SpotTheme.js b/frontend/.storybook/SpotTheme.js deleted file mode 100644 index 97e5c542a21..00000000000 --- a/frontend/.storybook/SpotTheme.js +++ /dev/null @@ -1,10 +0,0 @@ -import { create } from '@storybook/theming'; - -export default create({ - base: 'light', - - brandTitle: 'OpenProject Design System', - brandUrl: '/', - brandImage: '/assets/frontend/logo_openproject_spot.png', - brandTarget: '_self', -}); diff --git a/frontend/.storybook/main.ts b/frontend/.storybook/main.ts deleted file mode 100644 index 694ee3c026c..00000000000 --- a/frontend/.storybook/main.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as path from 'path'; -import remarkGfm from 'remark-gfm'; -import type { StorybookConfig } from '@storybook/angular'; - -const config:StorybookConfig = { - stories: [ - "../src/**/*.mdx", - "../src/**/*.stories.@(js|jsx|ts|tsx)", - ], - - addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - { - name: '@storybook/addon-docs', - options: { - mdxPluginOptions: { - mdxCompileOptions: { - remarkPlugins: [remarkGfm], - }, - }, - }, - }, - "@storybook/preset-scss", - "storybook-addon-designs", - "./plugin-iframe/src/preset.js", - '@storybook/addon-mdx-gfm', - ], - - framework: { - name: '@storybook/angular', - options: {} - }, - - core: { - disableTelemetry: true - }, - - features: { - }, - - staticDirs: [ - // Copy local static assets - '../src/stories/assets/logo_openproject.png', - '../src/stories/assets/logo_openproject_spot.png', - // Copy font files to specific locations so the normal core SASS - // will load the files correctly without having to use variables - '../src/assets/fonts/openproject_icon/openproject-icon-font.ttf', - '../src/assets/fonts/openproject_icon/openproject-icon-font.svg', - '../src/assets/fonts/openproject_icon/openproject-icon-font.eot', - '../src/assets/fonts/openproject_icon/openproject-icon-font.woff', - '../src/assets/fonts/openproject_icon/openproject-icon-font.woff2', - '../src/assets/fonts/lato/Lato-Regular.woff', - '../src/assets/fonts/lato/Lato-Regular.woff2', - '../src/assets/fonts/lato/Lato-Bold.woff', - '../src/assets/fonts/lato/Lato-Bold.woff2', - '../src/assets/fonts/lato/Lato-Light.woff', - '../src/assets/fonts/lato/Lato-Light.woff2', - '../src/assets/fonts/lato/Lato-Italic.woff', - '../src/assets/fonts/lato/Lato-Italic.woff2', - '../src/assets/fonts/lato/Lato-BoldItalic.woff', - '../src/assets/fonts/lato/Lato-BoldItalic.woff2', - '../src/assets/fonts/lato/Lato-LightItalic.woff', - '../src/assets/fonts/lato/Lato-LightItalic.woff2', - ].map(from => ({ - from, - to: path.join('/assets/frontend/', path.basename(from)) - })), - - docs: { - autodocs: true - } -}; - -export default config; diff --git a/frontend/.storybook/manager.js b/frontend/.storybook/manager.js deleted file mode 100644 index 5c6520bd6b6..00000000000 --- a/frontend/.storybook/manager.js +++ /dev/null @@ -1,6 +0,0 @@ -import { addons } from '@storybook/addons'; -import spotTheme from './SpotTheme'; - -addons.setConfig({ - theme: spotTheme, -}); diff --git a/frontend/.storybook/plugin-iframe/.babelrc.js b/frontend/.storybook/plugin-iframe/.babelrc.js deleted file mode 100644 index e8ca8aa71ea..00000000000 --- a/frontend/.storybook/plugin-iframe/.babelrc.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: ["@babel/preset-env", "@babel/preset-react"], -}; diff --git a/frontend/.storybook/plugin-iframe/package-lock.json b/frontend/.storybook/plugin-iframe/package-lock.json deleted file mode 100644 index 81a9774ac9f..00000000000 --- a/frontend/.storybook/plugin-iframe/package-lock.json +++ /dev/null @@ -1,336 +0,0 @@ -{ - "name": "storybook-plugin-iframe", - "version": "1.0.0", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/cli": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.18.10.tgz", - "integrity": "sha512-dLvWH+ZDFAkd2jPBSghrsFBuXrREvFwjpDycXbmUoeochqKYe4zNSLEJYErpLg8dvxvZYe79/MkN461XCwpnGw==", - "requires": { - "@jridgewell/trace-mapping": "^0.3.8", - "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents.3", - "chokidar": "^3.4.0", - "commander": "^4.0.1", - "convert-source-map": "^1.1.0", - "fs-readdir-recursive": "^1.1.0", - "glob": "^7.2.0", - "make-dir": "^2.1.0", - "slash": "^2.0.0" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==" - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, - "@jridgewell/trace-mapping": { - "version": "0.3.15", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.15.tgz", - "integrity": "sha512-oWZNOULl+UbhsgB51uuZzglikfIKSUBO/M9W2OfEjn7cmqoAiCgmv9lyACTUacZwBz0ITnJ2NqjU8Tx0DHL88g==", - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@nicolo-ribaudo/chokidar-2": { - "version": "2.1.8-no-fsevents.3", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz", - "integrity": "sha512-s88O1aVtXftvp5bCPB7WnmXc5IwOZZ7YPuwNPt+GtOOXpPvad1LfbmjYv+qII7zP6RU2QGnqve27dnLycEnyEQ==", - "optional": true - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "optional": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "optional": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "optional": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==" - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" - }, - "convert-source-map": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "optional": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "fs-readdir-recursive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", - "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==" - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "optional": true - }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "optional": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "optional": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "optional": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "optional": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "optional": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "requires": { - "js-tokens": "^3.0.0 || ^4.0.0" - } - }, - "make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" - } - }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "optional": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "requires": { - "wrappy": "1" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "optional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "react": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react/-/react-18.2.0.tgz", - "integrity": "sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "react-dom": { - "version": "18.2.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz", - "integrity": "sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==", - "requires": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.0" - } - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "optional": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "scheduler": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.0.tgz", - "integrity": "sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==", - "requires": { - "loose-envify": "^1.1.0" - } - }, - "semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==" - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==" - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "optional": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" - } - } -} diff --git a/frontend/.storybook/plugin-iframe/package.json b/frontend/.storybook/plugin-iframe/package.json deleted file mode 100644 index 05fed05a819..00000000000 --- a/frontend/.storybook/plugin-iframe/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "storybook-plugin-iframe", - "version": "1.0.0", - "description": "", - "main": "index.js", - "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", - "build": "babel ./src --out-dir ./dist" - }, - "author": "", - "license": "GPL-3.0", - "dependencies": { - "@babel/cli": "^7.18.10", - "react": "^18.2.0", - "react-dom": "^18.2.0" - } -} diff --git a/frontend/.storybook/plugin-iframe/src/preset.js b/frontend/.storybook/plugin-iframe/src/preset.js deleted file mode 100644 index 58769e3098b..00000000000 --- a/frontend/.storybook/plugin-iframe/src/preset.js +++ /dev/null @@ -1,5 +0,0 @@ -function managerEntries(entry = []) { - return [...entry, require.resolve("./register")]; //👈 Addon implementation -} - -module.exports = { managerEntries } diff --git a/frontend/.storybook/plugin-iframe/src/register.js b/frontend/.storybook/plugin-iframe/src/register.js deleted file mode 100644 index 4e24de28d0c..00000000000 --- a/frontend/.storybook/plugin-iframe/src/register.js +++ /dev/null @@ -1,16 +0,0 @@ -import { addons } from '@storybook/addons'; - -const ADDON_ID = 'iframe'; - -if (window && window.parent) { - addons.register(ADDON_ID, () => { - let previousLocation = window.location.toString(); - document.body.addEventListener('click', () => { - const newLocation = window.location.toString(); - if (previousLocation !== newLocation) { - window.parent.postMessage(newLocation, '*'); - previousLocation = newLocation; - } - }); - }); -} diff --git a/frontend/.storybook/preview.js b/frontend/.storybook/preview.js deleted file mode 100644 index da393962995..00000000000 --- a/frontend/.storybook/preview.js +++ /dev/null @@ -1,32 +0,0 @@ -import { setCompodocJson } from "@storybook/addon-docs/angular"; -import { themes } from '@storybook/theming'; -import docJson from "../documentation.json"; - -setCompodocJson(docJson); - -export const parameters = { - parameters: { - viewMode: 'docs', - }, - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - docs: { - inlineStories: true, - theme: themes.light, - }, - options: { - storySort: { - method: 'alphabetical', - order: [ - 'OpenProject Angular SPOT components', - 'Blocks', - // TODO: Add manual sort order for components and patterns - ], - }, - }, -} diff --git a/frontend/.storybook/tsconfig.json b/frontend/.storybook/tsconfig.json deleted file mode 100644 index 68270d5c5e4..00000000000 --- a/frontend/.storybook/tsconfig.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "extends": "../src/tsconfig.app.json", - "compilerOptions": { - "types": [ - "node" - ], - "allowSyntheticDefaultImports": true - }, - "exclude": [ - "../src/test.ts", - "../src/**/*.spec.ts", - "../src/**/spec/**/*.ts" - ], - "include": [ - "../src/**/*", - "../projects/**/*" - ], - "files": [ - "./typings.d.ts" - ] -} diff --git a/frontend/.storybook/typings.d.ts b/frontend/.storybook/typings.d.ts deleted file mode 100644 index f73d61b396c..00000000000 --- a/frontend/.storybook/typings.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module '*.md' { - const content: string; - export default content; -} diff --git a/frontend/angular.json b/frontend/angular.json index 5c9e5bd2c63..a873bf0d4cf 100644 --- a/frontend/angular.json +++ b/frontend/angular.json @@ -177,41 +177,6 @@ ], "preserveSymlinks": true } - }, - "storybook": { - "builder": "@storybook/angular:start-storybook", - "options": { - "configDir": ".storybook", - "browserTarget": "OpenProject:build", - "compodoc": true, - "compodocArgs": [ - "-e", - "json", - "-d", - ".", - "-p", - "src/tsconfig.compodoc.json" - ], - "port": 6006, - "host": "0.0.0.0" - } - }, - "build-storybook": { - "builder": "@storybook/angular:build-storybook", - "options": { - "configDir": ".storybook", - "browserTarget": "OpenProject:build", - "compodoc": true, - "compodocArgs": [ - "-e", - "json", - "-d", - ".", - "-p", - "src/tsconfig.compodoc.json" - ], - "outputDir": "storybook-static" - } } } } diff --git a/frontend/package.json b/frontend/package.json index 82e84f79603..d994c0cd1f3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -17,16 +17,6 @@ "@html-eslint/eslint-plugin": "^0.15.0", "@html-eslint/parser": "^0.15.0", "@jsdevtools/coverage-istanbul-loader": "3.0.5", - "@storybook/addon-actions": "^7.0.18", - "@storybook/addon-essentials": "^7.0.18", - "@storybook/addon-interactions": "^7.0.18", - "@storybook/addon-knobs": "^7.0.2", - "@storybook/addon-links": "^7.0.18", - "@storybook/addon-mdx-gfm": "^7.0.18", - "@storybook/angular": "^7.2.2", - "@storybook/mdx2-csf": "^1.1.0", - "@storybook/preset-scss": "^1.0.3", - "@storybook/testing-library": "^0.1.0", "@types/codemirror": "5.60.5", "@types/dragula": "^3.7.0", "@types/hammerjs": "^2.0.36", @@ -55,7 +45,6 @@ "eslint-plugin-jsx-a11y": "^6.6.1", "eslint-plugin-react": "^7.31.11", "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-storybook": "^0.6.12", "esprint": "^3.1.0", "jasmine-core": "~3.6.0", "jasmine-spec-reporter": "~5.0.0", @@ -72,8 +61,6 @@ "sass": "^1.52.3", "sass-loader": "^13.0.0", "source-map-explorer": "^2.5.2", - "storybook": "^7.0.18", - "storybook-addon-designs": "^7.0.0-beta.2", "style-loader": "^3.3.1", "theo": "^8.1.5", "ts-node": "~8.3.0", @@ -193,9 +180,7 @@ "lint": "esprint check", "lint:fix": "esprint check --fix", "lint:eslint": "eslint", - "generate-typings": "tsc -d -p src/tsconfig.app.json", - "storybook:serve": "ng run OpenProject:storybook", - "storybook:build": "ng run OpenProject:build-storybook" + "generate-typings": "tsc -d -p src/tsconfig.app.json" }, "overrides": { "@primer/view-components": "npm:@openproject/primer-view-components@^0.13.1" diff --git a/frontend/src/app/spot/components/checkbox/stories/Checkbox.mdx b/frontend/src/app/spot/components/checkbox/stories/Checkbox.mdx deleted file mode 100644 index 95e2727af39..00000000000 --- a/frontend/src/app/spot/components/checkbox/stories/Checkbox.mdx +++ /dev/null @@ -1,18 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotCheckboxComponent } from '../checkbox.component'; -import * as CheckboxStories from './Checkbox.stories'; - - - -# Checkboxes - -This component describes only the actual checkbox, without the label. For the full component, please refer to Selector field component, which provides a label. - -## States - -The selector field itself only has two states, *enabled* and *disabled*. - - - - diff --git a/frontend/src/app/spot/components/checkbox/stories/Checkbox.stories.ts b/frontend/src/app/spot/components/checkbox/stories/Checkbox.stories.ts deleted file mode 100644 index 710324ccdb5..00000000000 --- a/frontend/src/app/spot/components/checkbox/stories/Checkbox.stories.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { OpSpotModule } from '../../../spot.module'; - -const meta:Meta = { - title: 'Components/Checkbox', - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Basic:Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), -}; diff --git a/frontend/src/app/spot/components/drop-modal/stories/DropModal.mdx b/frontend/src/app/spot/components/drop-modal/stories/DropModal.mdx deleted file mode 100644 index 5be6c0d3692..00000000000 --- a/frontend/src/app/spot/components/drop-modal/stories/DropModal.mdx +++ /dev/null @@ -1,31 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotDropModalComponent } from '../drop-modal.component'; -import * as DropModalStories from './DropModal.stories'; - - - -# Drop Modal - -Drop modals are in-between dropdowns and modals. In fact, on mobile they're indistinguishable from a normal spot-modal. -The key difference however, is that they're tied to a specific context. On desktop views, the modal will open -in-context, meaning that it will attach to the button or input field that opened the modal. - - - -## Usage - -To use drop-modals, make sure to also add the drop-modal portal to the base of your application HTML: - -``` - -``` - -Even though drop-modals look like they are attached to an element, they're actually rendered in this portal to make sure -they're not cut off my scrolling context or `overflow: visible` rules on parent elements. - - diff --git a/frontend/src/app/spot/components/drop-modal/stories/DropModal.stories.ts b/frontend/src/app/spot/components/drop-modal/stories/DropModal.stories.ts deleted file mode 100644 index 81d688697f2..00000000000 --- a/frontend/src/app/spot/components/drop-modal/stories/DropModal.stories.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { I18nService } from '../../../../core/i18n/i18n.service'; -import { I18nServiceStub } from '../../../../../stories/i18n.service.stub'; - -import { OpSpotModule } from '../../../spot.module'; -import SpotDropAlignmentOption from '../../../drop-alignment-options'; - -import { SpotDropModalComponent } from '../drop-modal.component'; - -const meta:Meta = { - title: 'Patterns/DropModal', - component: SpotDropModalComponent, - decorators: [ - moduleMetadata({ - imports: [ - OpSpotModule, - ], - providers: [ - { - provide: I18nService, - useFactory: () => I18nServiceStub, - }, - ], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default:Story = { - render: (args) => ({ - props: { - ...args, - dropModalOpen: false, - alignment: SpotDropAlignmentOption.BottomCenter, - }, - template: ` - - - - - - -
-
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- -
-
- -
-
-
-
-
- `, - }), -}; diff --git a/frontend/src/app/spot/components/form-field/stories/FormField.mdx b/frontend/src/app/spot/components/form-field/stories/FormField.mdx deleted file mode 100644 index 98981c7a667..00000000000 --- a/frontend/src/app/spot/components/form-field/stories/FormField.mdx +++ /dev/null @@ -1,83 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotFormFieldComponent } from '../form-field.component'; -import * as FormFieldStories from './FormField.stories'; - - - -# Form Field - -Form fields are a smart wrapper around input fields. Form fields handle labels, validation messages, help texts, and -extra actions around an input field. - -## Basic usage - -Form fields work with `ng-content` a lot, which we call `slot` in SPOT. There are four slots in which content can be -fitted: - -1. `input` holds the input. It can be a text input or select dropdown, or anything else that is not a - selection toggle. -2. `help-text` holds the attribute help text. To achieve separation of concerns, if you want to show an attribute help - text this is where the `attribute-help-text` component goes. -3. `errors` holds validation errors. -4. `description` holds a short description of the input field. Oftentimes these are short, helpful guidelines about how - or what to input into the field. -5. `action` holds extra input actions. Most often, these are `button` elements styled as a `spot-link` that allow the user to - perform an action related to the input. - -Some of these require adding an extra class on that element, so watch the examples carefully! - -## Adding an input - -Use `slot="input"` to select the input element: - - - -## Adding a description - -Use `slot="description"` to select the input element: - - - - -## Adding a help text - -Use `slot="help-text"` to select the `attribute-help-text` component: - -```html - - - - -``` - -The example above cannot currently be rendered in Storybook, because the `attribute-help-text` component requires the -API to be available. They are in use in the project and work package dynamic form. So if you add an attribute help text, -they will show up there. - -## Handling validation - -SPOT works best with reactive forms. If you have a `FormControl` for your input, `spot-form-field` will be able to -figure out the validation state automagically. - -Use `slot="errors"` to set validation errors. You can set multiple errors independently by all giving them the `slot` -attribute. - - - -As you can see, by default validation messages will only be shown on submit. You can change this behavior by changing -the `showValidationErrorOn` input to the `spot-form-field` component. - -The `required` input only makes sure the asterisk at the end of the label is shown, it does not add any validation to -the form or input. - -## Adding extra input actions - -Form field actions allow you to add extra action buttons to the context of an input field. - - - - diff --git a/frontend/src/app/spot/components/form-field/stories/FormField.stories.ts b/frontend/src/app/spot/components/form-field/stories/FormField.stories.ts deleted file mode 100644 index 1f83f77fa38..00000000000 --- a/frontend/src/app/spot/components/form-field/stories/FormField.stories.ts +++ /dev/null @@ -1,142 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; -import { - UntypedFormControl, - UntypedFormGroup, - Validators, -} from '@angular/forms'; -import { ReactiveFormsModule } from '@angular/forms'; - -import { I18nService } from '../../../../core/i18n/i18n.service'; -import { I18nServiceStub } from '../../../../../stories/i18n.service.stub'; - -import { OpSpotModule } from '../../../spot.module'; - -import { SpotFormFieldComponent } from '../form-field.component'; - -const meta:Meta = { - title: 'Patterns/FormField', - component: SpotFormFieldComponent, - decorators: [ - moduleMetadata({ - imports: [ - OpSpotModule, - ReactiveFormsModule, - ], - providers: [ - { - provide: I18nService, - useFactory: () => I18nServiceStub, - }, - ], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const InputSlot:Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), -}; - -export const DescriptionSlot:Story = { - render: (args) => ({ - props: args, - template: ` - - - - Helpful guidelines so the user can be confident about their input. - - - `, - }), -}; - -export const BasicValidation:Story = { - render: (args) => ({ - props: { - ...args, - myForm: new UntypedFormGroup({ - myInput: new UntypedFormControl(null, [Validators.required, Validators.minLength(8)]), - }), - onSubmit: (event:any) => console.log('onSubmit', event), - }, - template: ` -
- - - -
- This input is required. -
- -
- This input needs to be at least 8 characters long. -
-
- -
-
- -
-
-
- `, - }), -}; - -export const ActionSlot:Story = { - render: (args) => ({ - props: { - ...args, - alert: (s:string) => console.log(s), - }, - template: ` - - - - - - `, - }), -}; diff --git a/frontend/src/app/spot/components/selector-field/stories/SelectorField.mdx b/frontend/src/app/spot/components/selector-field/stories/SelectorField.mdx deleted file mode 100644 index 62fdb9c8996..00000000000 --- a/frontend/src/app/spot/components/selector-field/stories/SelectorField.mdx +++ /dev/null @@ -1,51 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotSelectorFieldComponent } from '../selector-field.component'; -import * as SelectorFieldStories from './SelectorField.stories'; - - - -# Selector Field - -Selector fields are used to offer the user a number of different options. - -The selector field consists of either a checkbox or a radio button along with a label. - -Checkboxes offer the possibility to make multiple selections. -Radio buttons require the user to choose only one from a list of options. - - - -## Behaviour - -The selector field extends the clickable zone of the checkbox or radio button to the entire label. Clicking on the label is then the same as clicking on the control element itself. - -If the label text is particular long, it should wrap within the container, but be top-aligned, like so: - - - -## Options - -Selector labels can be **bold** or regular. However, these two styles should not be mixed in a single set of selector fields. - - - -## Basic usage - -Selector fields work with `ng-content` a lot, which we call `slot` in SPOT. There are four slots in which content can be -fitted: - -1. `input` holds the input. It can be any selector toggle -2. `help-text` holds the attribute help text. To achieve separation of concerns, if you want to show an attribute help - text this is where the `attribute-help-text` component goes. -3. `errors` holds validation errors. -4. `description` holds a short description of the input field. Oftentimes these are short, helpful guidelines about how - or what to input into the field. - -Some of these require adding an extra class on that element, so watch the examples carefully! - -## Positioning and Margins - -Because the selector field itself is often placed in containers that have their own margins (like the action bar), it does not inherently have any margins of its own. - - diff --git a/frontend/src/app/spot/components/selector-field/stories/SelectorField.stories.ts b/frontend/src/app/spot/components/selector-field/stories/SelectorField.stories.ts deleted file mode 100644 index bd03f323b69..00000000000 --- a/frontend/src/app/spot/components/selector-field/stories/SelectorField.stories.ts +++ /dev/null @@ -1,105 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; -import { FormsModule, ReactiveFormsModule } from '@angular/forms'; - -import { I18nService } from '../../../../core/i18n/i18n.service'; -import { I18nServiceStub } from '../../../../../stories/i18n.service.stub'; - -import { OpSpotModule } from '../../../spot.module'; - -import { SpotSelectorFieldComponent } from '../selector-field.component'; - -const meta:Meta = { - title: 'Patterns/SelectorField', - component: SpotSelectorFieldComponent, - decorators: [ - moduleMetadata({ - imports: [ - OpSpotModule, - ReactiveFormsModule, - FormsModule, - ], - providers: [ - { - provide: I18nService, - useFactory: () => I18nServiceStub, - }, - ], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default:Story = { - render: (args) => ({ - props: { - ...args, - mixed: null, - }, - template: ` -
- - - - - - - - -
- - - - - - - - -
- `, - }), -}; - -export const LongLabel:Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), -}; - -export const FontWeight:Story = { - render: (args) => ({ - props: args, - template: ` -
- - - - - - - -
- `, - }), -}; diff --git a/frontend/src/app/spot/components/switch/stories/Switch.mdx b/frontend/src/app/spot/components/switch/stories/Switch.mdx deleted file mode 100644 index 070367fc066..00000000000 --- a/frontend/src/app/spot/components/switch/stories/Switch.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotSwitchComponent } from '../switch.component'; -import * as SwitchStories from './Switch.stories'; - - - -# Switches - -This component describes only the actual switch, without the label. For the full component, please refer to Selector field component, which provides a label. - -## States - -The selector field itself only has two states, *enabled* and *disabled*. - -### Enabled - - - - -### Disabled - - - - - diff --git a/frontend/src/app/spot/components/switch/stories/Switch.stories.ts b/frontend/src/app/spot/components/switch/stories/Switch.stories.ts deleted file mode 100644 index 1d05cd34074..00000000000 --- a/frontend/src/app/spot/components/switch/stories/Switch.stories.ts +++ /dev/null @@ -1,46 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { OpSpotModule } from '../../../spot.module'; -import { SpotSwitchComponent } from '../switch.component'; - -const meta:Meta = { - title: 'Components/Switch', - component: SpotSwitchComponent, - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Checked:Story = { - args: { - checked: true, - disabled: false, - }, -}; - -export const Unchecked:Story = { - args: { - checked: false, - disabled: false, - }, -}; - -export const Disabled:Story = { - args: { - checked: false, - disabled: true, - }, -}; - -export const DisabledChecked:Story = { - args: { - checked: true, - disabled: true, - }, -}; diff --git a/frontend/src/app/spot/components/text-field/stories/TextField.mdx b/frontend/src/app/spot/components/text-field/stories/TextField.mdx deleted file mode 100644 index 270d0f67646..00000000000 --- a/frontend/src/app/spot/components/text-field/stories/TextField.mdx +++ /dev/null @@ -1,67 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import * as TextFieldStories from './TextField.stories'; -import { SpotTextFieldComponent } from '../text-field.component'; - - - -# Text Field - -The text field is one of the most common input elements used in a wide range of scenarios: fill a form, enter work package details, search for something, pick a date. - -## Structure and Options - -The most basic text field is simply an input field where the user can type in text. - - - -The text field component affords a few optional elements: - -An optional icon can be added to the left edge to provide additional context when this is necessary (in a search field, for example): - - - -An icon can also optionally be placed on the right edge. This arrow can provide additional hints on how the text input field will behave; for example, a right arrow might suggest that the value will be immediately submitted, or a dropdown arrow might indicate that the text field will open a drop modal underneath it (a list of assignees, for example). - -The right icon can also be converted to an icon-only link that allows the user to clear the field. - - - -## Behaviour - -Clicking on a text field will move it to the focused state. - -A text field can have placeholder text to provide additionanl context or information; this text is replaced by actual text that the user inputs, and is shown again only if the field is cleared. - - - -The clear icon-only link on the right only appears on some text fields. - -The width of a text field is fixed and defined either manually, or by the width of the containing element. When text exceeds the available width, it scrolls with the cursor within the text field (and clips on the other end). - -## States - -The **Default** state indicates that a text field is ready to accept text input. - - - -The **Focus** state has a coloured outline, the same as any focused element in OpenProject. - -The **Hover** element has a darker border (Basic/Gray 1). - -The **Disabled** state shows all elements (including icons and any placeholder or input text) in grey, and makes any interaction impossible. - - - - -The **Error** state puts a red outline around the text input. (_Note_: This state is only used when the text input is part of a _Form element_, which supplements this red outline with an message error message). The error state is usually temporary; fixing the cause of the error (via asyncronous form validation) usually allows it to revert back to the focus state. - -> Example: Error state - -## Margins and Spacing - -The text field has 0.5 rem left and right margins, and a 0.25 rem top and bottom margins. - -There is also a 0.25 rem spacing between items (icon and text). - - diff --git a/frontend/src/app/spot/components/text-field/stories/TextField.stories.ts b/frontend/src/app/spot/components/text-field/stories/TextField.stories.ts deleted file mode 100644 index 2643b994fb2..00000000000 --- a/frontend/src/app/spot/components/text-field/stories/TextField.stories.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { OpSpotModule } from '../../../spot.module'; -import { SpotTextFieldComponent } from '../text-field.component'; - -const meta:Meta = { - title: 'Components/TextField', - component: SpotTextFieldComponent, - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default:Story = { - args: { - value: '', - disabled: false, - placeholder: '', - showClearButton: true, - name: 'my-input', - }, -}; - -export const SearchWithIcon:Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), -}; - -export const SearchWithIconAndValue:Story = { - render: (args) => ({ - props: args, - template: ` - - - - `, - }), -}; - -export const Placeholder:Story = { - args: { - placeholder: 'Enter a value here', - }, -}; - -export const WithValue:Story = { - args: { - value: 'Some value', - }, -}; - -export const Disabled:Story = { - args: { - disabled: true, - }, -}; - -export const DisabledWithValue:Story = { - args: { - value: 'Disabled with value', - disabled: true, - }, -}; diff --git a/frontend/src/app/spot/components/toggle/stories/Toggle.mdx b/frontend/src/app/spot/components/toggle/stories/Toggle.mdx deleted file mode 100644 index 38f7d653cd3..00000000000 --- a/frontend/src/app/spot/components/toggle/stories/Toggle.mdx +++ /dev/null @@ -1,16 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotToggleComponent } from '../toggle.component'; -import * as ToggleStories from './Toggle.stories'; - - - -# Toggles - -All of these examples are static. - - - - - - diff --git a/frontend/src/app/spot/components/toggle/stories/Toggle.stories.ts b/frontend/src/app/spot/components/toggle/stories/Toggle.stories.ts deleted file mode 100644 index 0d2390c20bf..00000000000 --- a/frontend/src/app/spot/components/toggle/stories/Toggle.stories.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { OpSpotModule } from '../../../spot.module'; -import { SpotToggleComponent } from '../toggle.component'; - -const meta:Meta = { - title: 'Components/Toggle', - component: SpotToggleComponent, - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default:Story = { - args: { - name: 'my-toggle', - value: null, - options: [ - { value: 'first', title: 'Unread' }, - { value: 'second', title: 'All' }, - ], - }, -}; - -export const WithValue:Story = { - args: { - value: 'first', - options: [ - { value: 'first', title: 'Unread' }, - { value: 'second', title: 'All' }, - ], - }, -}; - -export const FourOptions:Story = { - args: { - name: 'my-toggle', - value: 'first', - options: [ - { value: 'first', title: 'First option' }, - { value: 'second', title: 'Second option' }, - { value: 'third', title: 'Third option' }, - { value: 'best', title: 'Best option' }, - ], - }, -}; diff --git a/frontend/src/app/spot/components/tooltip/stories/Tooltip.mdx b/frontend/src/app/spot/components/tooltip/stories/Tooltip.mdx deleted file mode 100644 index ac00ec93ca5..00000000000 --- a/frontend/src/app/spot/components/tooltip/stories/Tooltip.mdx +++ /dev/null @@ -1,57 +0,0 @@ -import { Canvas, Meta, ArgsTable } from '@storybook/blocks'; - -import { SpotTooltipComponent } from '../tooltip.component'; -import * as TooltipStories from './Tooltip.stories'; - - - -# Tooltip - - - -The tooltip provides additional textual context on hover over interactive elements. - -This context can be used to provide additional information (hover over a "help" icon) or to signal state information (for example, if a certain item is disabled). - -By default, the tooltip takes 80% of the width of the container. This can be overridden if needed. - - - -## Structure and Options - -Tooltips have a sky blue background in the default theme (_Feedback/Info/Light_). A darker version is available is but currently not used. - - - -## Behaviour - -The tooltip should appear either above or below the item being hovered. By default, it will be displayed above, left-aligned. This can be manually changed to these alternative positions: - -- Above, left-aligned -- Above, center-aligned -- Above, right-aligned -- Below, left-aligned -- Below, center-aligned -- Below, right- aligned - -The item over which the user is hovering itself must never be covered. A tooltip' does not follow the cursor; its position is fixed as long as the user is hovering over the item. Hovering out of the element will cause the tooltip to disappear. - -The tooltip will appear 200 ms after the user starts hovering and disappear 200 ms after the user has moved the cursor away, to avoid jittery behaviour. - -The tooltip will automatically become multi-line if necessary (i.e, if the container does not have sufficient space). - - - -## Dos and Don'ts - -A tooltip is, in its basic form, a line of text. However, it can also contain icons. - -It must never contain interactive elements (a mouse pointer will not always be able to reach it). - -## Margins and Spacing - -A tooltip will never hug either side of the container; it will respect the internal padding of the that container. - -> Position, in-tree or out-of-tree to be decided - -Due to technical limitations, the minimum width of a tooltip is defined by its container (i.e, 80% of it). Tooltips with content shorter this width will have additional space on the right. diff --git a/frontend/src/app/spot/components/tooltip/stories/Tooltip.stories.ts b/frontend/src/app/spot/components/tooltip/stories/Tooltip.stories.ts deleted file mode 100644 index a6e627b0ac9..00000000000 --- a/frontend/src/app/spot/components/tooltip/stories/Tooltip.stories.ts +++ /dev/null @@ -1,126 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { OpSpotModule } from '../../../spot.module'; -import { SpotTooltipComponent } from '../tooltip.component'; - -const meta:Meta = { - title: 'Components/Tooltip', - component: SpotTooltipComponent, - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default:Story = { - render: (args) => ({ - props: args, - template: ` - - - Hover me to see the tooltip. By default, tooltips take a maximum of 80% of their parents' width - -

- This is an example tooltip. -

-
- `, - }), -}; - -export const InList:Story = { - render: (args) => ({ - props: args, - template: ` -
    -
  • - - - - - -

    - This is a great checkbox. -

    -
    -
  • -
  • - - - - - -

    - This tooltip is not going to show. -

    -
    -
  • -
  • - -
  • -
- `, - }), -}; - -export const Dark:Story = { - render: (args) => ({ - props: args, - template: ` - - Hover me to see the tooltip. -

- This is a dark tooltip. -

-
- `, - }), -}; - -export const Multiline:Story = { - render: (args) => ({ - props: args, - template: ` - - Hover me to see the tooltip. -

- This is a tooltip with a very long text
- that has a break in the middle so that we
- can get some multiline action. -

-
- `, - }), -}; diff --git a/frontend/src/stories/ActionBar.mdx b/frontend/src/stories/ActionBar.mdx deleted file mode 100644 index f938e71b6cb..00000000000 --- a/frontend/src/stories/ActionBar.mdx +++ /dev/null @@ -1,76 +0,0 @@ -import { Canvas, Meta } from '@storybook/blocks'; - -import * as ActionBarStories from './ActionBar.stories'; - - - -# Action Bar - -The action bar is generally used at the bottom of modals to present the user with a set of actions. These actions are often relative to choices or selections made in the containing modal. - -The most common choices are "Save/apply" and "Cancel".  At least one button (Cancel) is the absolute minimum. - -Above, you see an action bar embedded inside a modal dialogue. - - - -## Composition - -The action bar is composed of: - -**Button set** - -These are a set of buttons that form the main action choices presented to the user. Although a two-button set is most common, there can be a maximum of three buttons (see “[Options](#)” below). - -**Side option (optional)** - -The side option allows for a third action (usually a checkbox, but can also be a button, a toggle or any other control). Ideally, the text should be clear and concise. - -## Variants - - - -By default: -- the primary button set is on the right side of the action bar -- the side option on the left -- the bar has a grey background - -There are however alternative variants: - -**Left buttons** - -The button set can be moved to the left, placing the side action on the right. This should be used sparingly and only if absolutely necessary. - - - -**Transparent background** - -The transparent background is useful for when having the action bar in grey (default) does not work visually. The transparent version will simply take the background colour of the element in which it is contained, usually white. - - - -## Options - -**Side options** - -The side option is, as the name suggests, optional. An action bar can simply be empty on one side, like so: - - - -**More buttons** - -The action bar can also be composed of up to three action buttons. Should there be more than three actions to be made available to the user, the third button will be a “More” button opens a drop-down with additional options. - - - -## Behaviour - -At the very minimum there should be one action, ideally two: a primary action like ‘Save’ or ‘Delete’ and a secondary action like ‘Cancel’. - -When the action bar is use as a toolbar with more three buttons (or with a “More” button), all actions can be secondary. - -## Line breaks and wrapping - -When the text is too long, the button set will remain in one line, and the side option (if present) will move to a new line. - -If the text in the side option is too long, that will itself also wrap in multiple lines. diff --git a/frontend/src/stories/ActionBar.stories.ts b/frontend/src/stories/ActionBar.stories.ts deleted file mode 100644 index ac7f3dc58a4..00000000000 --- a/frontend/src/stories/ActionBar.stories.ts +++ /dev/null @@ -1,187 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; -import { OpSpotModule } from '../app/spot/spot.module'; - -const meta:Meta = { - title: 'Patterns/ActionBar', - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const InModal:Story = { - render: (args) => ({ - props: args, - template: ` -
-
Delete attachment
-
- Are you sure you want to delete this file? This action is not reversible. -
-
-
- - -
-
-
- `, - }), -}; - -export const Default:Story = { - render: (args) => ({ - props: args, - template: ` -
-
- - - -
-
- -
-
- `, - }), -}; - -export const LeftButtons:Story = { - render: (args) => ({ - props: args, - template: ` -
-
- - -
-
- - - -
-
- `, - }), -}; - -export const Transparent:Story = { - render: (args) => ({ - props: args, - template: ` -
-
- - - -
-
- - -
-
- `, - }), -}; - -export const NoSideOption:Story = { - render: (args) => ({ - props: args, - template: ` -
-
- - -
-
- `, - }), -}; - -export const MoreButtons:Story = { - render: (args) => ({ - props: args, - template: ` -
-
- - - -
-
- `, - }), -}; diff --git a/frontend/src/stories/Breadcrumbs.mdx b/frontend/src/stories/Breadcrumbs.mdx deleted file mode 100644 index 32b6aef01ff..00000000000 --- a/frontend/src/stories/Breadcrumbs.mdx +++ /dev/null @@ -1,56 +0,0 @@ -import { Canvas, Meta } from '@storybook/blocks'; - -import * as BreadcrumbsStories from './Breadcrumbs.stories'; - - - -# Breadcrumbs - -Breadcrumbs provide important semantic context to information being presented on the screen by allowing users to identify where they are in a hierarchical structure. - -Breadcrumbs allow users to: - -- identify their current location (in a folder structure or hierarchy of work packages) -- navigate up to the parent(s) - - - -## Structure and Options - -Breadcrumbs consist of a series of "levels" presented horizontally, separated by a right arrow (▶). Each level is a child of the level preceding it, except the first level, which is the root. - - - -## Behaviour - -The last level is always the current level; this is never clickable. All other levels (the parents) are links, and clicking on them will take users to that level. - -Because the length of the breadcrumb can vary significantly (depending on the title of each level and the available space), it can condense to fit the available space: - -- The breadcrumb can show four (4) levels in full, space permitting, before going to "collapsed" mode. -- If the available space in the parent container not does afford sufficient width to display the four levels in full, the middle levels are truncated with ellipses ("Root ▶ Second le... ▶ Third le... ▶ Fourth level"). - - - -There are special rules on mobile: - -- The root level shows only an icon and ellipses ("...", fully truncated). -- The direct parent (...) is displayed as non-shrinkable ellipses that are clickable -- Further parents (between the direct parent and the root) are not displayed -- The current level maybe be truncated with ellipses ("[Icon] ... ▶ ... ▶ Current le...) if needed. - -To view the mobile style, reduce the viewport width of your browser below 680px. - -## Options - -Each level can consist of: - -- an icon -- a text - -The icon is almost always only used for the first item, although the component does allow it to be present at any level. - -The text for each level is always a link, except for: - -- current level -- collapsed levels between the direct parent and root in collapsed mode diff --git a/frontend/src/stories/Breadcrumbs.stories.ts b/frontend/src/stories/Breadcrumbs.stories.ts deleted file mode 100644 index e4145640a30..00000000000 --- a/frontend/src/stories/Breadcrumbs.stories.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; - -import { OpSpotModule } from '../app/spot/spot.module'; - -import { BreadcrumbsContent } from '../app/spot/components/breadcrumbs/breadcrumbs-content'; -import { SpotBreadcrumbsComponent } from '../app/spot/components/breadcrumbs/breadcrumbs.component'; - -const meta:Meta = { - title: 'Patterns/Breadcrumbs', - component: SpotBreadcrumbsComponent, - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Default:Story = { - render: (args) => ({ - props: { - ...args, - content: new BreadcrumbsContent([ - { icon: 'folder', text: 'OpenProject storage' }, - { text: 'Public' }, - { text: 'Shared' }, - ]), - }, - }), -}; - -export const FourLevels:Story = { - render: (args) => ({ - props: { - ...args, - content: new BreadcrumbsContent([ - { icon: 'folder', text: 'Root' }, - { text: 'Second level' }, - { text: 'Third level' }, - { text: 'Current level' }, - ]), - }, - }), -}; - -export const FiveLevels:Story = { - render: (args) => ({ - props: { - ...args, - content: new BreadcrumbsContent([ - { icon: 'folder', text: 'Root folder with a long name' }, - { text: 'Second level' }, - { text: 'Third level with an even longer name' }, - { text: 'Fourth level with the longest name from all' }, - { text: 'Current level and even this one has a long name' }, - ]), - }, - }), -}; diff --git a/frontend/src/stories/Buttons.mdx b/frontend/src/stories/Buttons.mdx deleted file mode 100644 index 44d946642c9..00000000000 --- a/frontend/src/stories/Buttons.mdx +++ /dev/null @@ -1,129 +0,0 @@ -import { Meta, Story, Canvas } from '@storybook/addon-docs'; -import tokens from '../app/spot/styles/tokens/dist/tokens.json'; - - - -# Buttons - -> Example: White more button with right icon, Main Save button, Accent Create button with left icon, danger Delete button with icon, Disabled Save button with left icon - -Buttons allow users to perform a specific action related to the present context. Compared to links, these actions tend to carry some weight: confirm an action, save settings, delete something, cancel an action. - -Buttons have a number of different options (presence of icons/text), styles (white, main, accent, danger, disabled) and states (regular, hover, clicked) that are described below. - -Buttons can be used in toolbars, in action bars, in button sets on pages, at the end of a settings page and in modals. They can also be used in combination with links to give certain actions more prominence than others. - -## Behaviour - -Buttons react immediately on click. They may submit a form, open a link, save or change state, launch a dropdown or a modal. - -## Styles - -There are four button styles and a disabled one. - -**Basic** - -The basic style is grey by default. Use it for secondary actions or in a group of buttons where there are no primary actions (for example, in a toolbar). - -> Example: Open in Nextcloud with right icon, Settings with left icon, More with right icon - -**Main** - -The main style is used to represent the primary action, like Save or Confirm. - -> Example: Save with left icon, More with right icon - -**Accent** - -The accent style allows an action to stand out (considerably) from the normal colour set. It should be used sparingly to draw special attention to an action that the user might otherwise not notice. - -> Example: Create button with left and right icon, Icon-only create button - -**Danger** - -The danger style should also be used sparingly to draw attention to actions that might be destructive, like delete. - -> Example: Delete button left icon - -**Disabled** - -A button can be *enabled* or *disabled*. The disabled style is the same for all of the above-described styles. - -> Example: Disabled Open in Nextcloud button with right icon, disabled Settings button with left icon, disabled More icon with right button - -## Options - -A button can optionally have: - -- A left-icon -- A right-icon -- Text - -These can be combined. The most common combinations are: - -**Text-only** - -> Example - -A button does not require any icons if the text is sufficient context. - -**Text with left icon** - -> Example - -The left icon provides additional context when necessary. - -**Text with right icon** - -> Example - -The right icon is not used as much but is available. The most common use case is to have a down-pointing arrow to signal the presence of drop-down menu, a right-pointing arrow to signal forward movement (in a multi-step process) or an ‘external’ icon to indicate that the link opens in a separate tab. - -**Text with both icons** - -> Example - -Using both left and right icons is less common, but can be useful in certain contexts, like when you need a left icon for context and a right icon to indicate a drop-down list. - -**Icon-only** - -> Example - -Some buttons are so common in OpenProject that they do not necessarily need a text label. However, these icons need an alt-text for accessibility. - -_Note: We discourage using icon-only buttons if the icon unfamiliar to the user, or the action is one that the user would not have previously encountered. The only exception is when space is very tight and there is immediate feedback (eg. “configure” button on a search bar)._ - -## States - -**Regular** - ->Example: Basic More with left icon, Main Save, Accent Create with left icon, Danger Delete with left icon - -**Hover** - ->Example: Basic More with left icon, Main Save, Accent Create with left icon, Danger Delete with left icon - -**Clicked** - ->Example: Basic More with left icon, Main Save, Accent Create with left icon, Danger Delete with left icon - -## Truncation/Variable width - -The labels on buttons should ideally be as short as possible. - -Nevertheless, there will be time when the width of a label will be longer than the available space. This can happen for a number of reasons: - -- The label being longer in another language (“Add assignee” vs “AbtretungsempfĂ€nger hinzufĂŒgen”) -- Changes to the layout due to window resizing or a new pane that compresses previously wider space - -There are two possible solutions, depending on the context: - -**Truncate label within a fixed-width button** -If the button has to be fixed-width, and the text is too long, the button should wrap around to fit the entire label. - -**Resize the button to fit the label** -If space allows, the button can have a max-width setting, so that it can first stretch vertically to allow the entire label to fit in one line, failing which, it should wrap to the next line. - -## Margin and Spacing - -The width of the button is generally set by the contents (notably by the length of the text and the presence of icons). In certain situations, the button might have a fixed length. \ No newline at end of file diff --git a/frontend/src/stories/Divider.mdx b/frontend/src/stories/Divider.mdx deleted file mode 100644 index c6da480140d..00000000000 --- a/frontend/src/stories/Divider.mdx +++ /dev/null @@ -1,39 +0,0 @@ -import { Canvas, Meta } from '@storybook/blocks'; - -import * as DividerStories from './Divider.stories'; - - - -# Divider - -A divider is a non-interactive visual element that allows for better grouping, organisation and hierarchy of elements on a page or a modal. - -The divider should only be used when the absence of such a separation can lead to a view looking too busy or unstructured. - -## Behaviour - -The divider is not interactive. - -The divider can either be full-width (in the [Modal Dialogue](#) header, for example) or span only a part of the width of the parent (in the activity split screen). - -This is determined by the parent element within which the divider is contained. - -Divider are usually placed horizontally, although they can also be placed vertically. - -## Options - -A divider can be soft or strong. The correct one to use depends on the structure and contrast of surrounding elements: - -**Soft** - -1 px, Grey-5 (#E0E0E0) - - - -**Strong** - -1px, Grey-4 (#CCCCCC) - - - -_Example: In the work package details view, the main three-way split (top header, left-side description, right-side split screen) is done using strong dividers, but on the Activity tab, dates are separated with soft ones._ diff --git a/frontend/src/stories/Divider.stories.ts b/frontend/src/stories/Divider.stories.ts deleted file mode 100644 index 79c51e98ff5..00000000000 --- a/frontend/src/stories/Divider.stories.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; -import { OpSpotModule } from '../app/spot/spot.module'; - -const meta:Meta = { - title: 'Components/Divider', - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Soft:Story = { - render: (args) => ({ - props: args, - template: ` -
-

Some header with a soft divider below

-
-

- Lorem ipsum goes here but I'm too lazy to copy paste it from somewhere so I'll just ramble on - until I think it has been enough. That was a very long sentence so I'll do one shorter one. Blablabla is what I say. -

-
- `, - }), -}; - -export const Strong:Story = { - render: (args) => ({ - props: args, - template: ` -
-

Strong divider below

-
-

- Lorem ipsum goes here but I'm too lazy to copy paste it from somewhere so I'll just ramble on - until I think it has been enough. That was a very long sentence so I'll do one shorter one. Blablabla is what I say. -

-
- `, - }), -}; diff --git a/frontend/src/stories/DropdownButton.mdx b/frontend/src/stories/DropdownButton.mdx deleted file mode 100644 index 3c43a9a6f42..00000000000 --- a/frontend/src/stories/DropdownButton.mdx +++ /dev/null @@ -1,26 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# Dropdown button - -> Example: Filter button with a badge value of 3. - -## Description - -The dropdown button is a special type of button with a permanent down arrow on the right edge (suggesting further actions) and an optional badge. - -## Behaviour - -Clicking on a dropdown button generally opens the dropdown menu, or a drop modal. When the menu or modal is open, clicking on the button again closes it. - -> Example: A more button that opens a dropdown menu - -In some (rarer) cases, it can open a panel. An example of this is the Filter button. - -## Options - -The badge is optional and can hold a number from 0 to 99. After 99, the number simply reads "99+". - - - diff --git a/frontend/src/stories/DropdownMenu.mdx b/frontend/src/stories/DropdownMenu.mdx deleted file mode 100644 index fdf5641a476..00000000000 --- a/frontend/src/stories/DropdownMenu.mdx +++ /dev/null @@ -1,35 +0,0 @@ -import { Meta, Story, Canvas } from '@storybook/addon-docs'; - - - -# Dropdown menu - -> Example: A dropdown menu with four items - -The dropdown menu is a contextual menu triggered by clicking on a dropdown button. It provides options to the use. - -## Structure and Options - -The dropdown menu consists of a list of items, generally *menu rows* and an optional title. - -The title is not interactive. It serves to provide additional context, if necessary. - -A menu item is interactive and generally represents an action. The menu item can optionally have an icon on the left side. - -> Example of an existing more menu that has icons next to each menu row - - -## States - -Each menu item has three states: regular, hover and clicked. - -## Margins and Spacing - -Each item has a height of 1.5 rem. There is a 0.25 rem spacing between the optional icon and the label, and a 0.5 rem rem spacing to the left and right. - -The width is determined by the longest object, or can be defined manually. If defined manually, items that are very long and exceed this length are truncated. - -> Eg: a menu item that's very long and truncated - - - diff --git a/frontend/src/stories/Introduction.mdx b/frontend/src/stories/Introduction.mdx deleted file mode 100644 index 6e9ac0e5ab1..00000000000 --- a/frontend/src/stories/Introduction.mdx +++ /dev/null @@ -1,8 +0,0 @@ -import { Meta } from '@storybook/addon-docs'; - - - -# OpenProject Angular SPOT components - -This page contains documentation for OpenProject angular components and is mostly deprecated. -Documentation will be kept updated instead in Lookbok, which you can find in a development server under http://localhost:3000/lookbook diff --git a/frontend/src/stories/Link.mdx b/frontend/src/stories/Link.mdx deleted file mode 100644 index 42c6a3602a5..00000000000 --- a/frontend/src/stories/Link.mdx +++ /dev/null @@ -1,62 +0,0 @@ -import { Canvas, Meta } from '@storybook/blocks'; - -import * as LinkStories from './Link.stories'; - - - -# Links - -The link is used for contextual actions where a button would take too much space, or break the flow of existing content. - -Links are usually in-line. - -## Behaviour - -The link works like a classic HTML link. The action is triggered immediately on click. - -## Options - -A link can optionally have: - -- A left-icon -- A right-icon -- Text - -These can be combined. The most common combinations are: - -**Text only** - -This is the most basic link. - - - -**Text with left icon** - -The left icon provides additional context when necessary. - - - -**Text with right icon** - -The right icon is not used as much but is available. The most common use case is to have a down-pointing arrow to signal the presence of drop-down menu, a right-pointing arrow to signal forward movement (in a multi-step process) or an ‘external’ icon to indicate that the link opens in a separate tab. - - - -**Text with both icons** - -This is available but we discourage its use. - - - -**Icon-only** - -This is essentially just an icon, but is offered here as a way of degrading a link with icon to just an icon when there are spatial constraints. - -_Note: We discourage using icon-only links if the icon unfamiliar to the user, or the action is one that the user would not have previously encountered. The only exception is when space is very tight and there is immediate feedback (eg. “configure” button on a search bar)._ - -## Margins and Spacing - -Contrary to buttons, links do not inherently have margin and padding. - -When there are icons present, there is a 0.25 rem margin between the text and the icon. - diff --git a/frontend/src/stories/Link.stories.ts b/frontend/src/stories/Link.stories.ts deleted file mode 100644 index 3701b0390df..00000000000 --- a/frontend/src/stories/Link.stories.ts +++ /dev/null @@ -1,73 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; -import { OpSpotModule } from '../app/spot/spot.module'; - -const meta:Meta = { - title: 'Components/Link', - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const Basic:Story = { - render: (args) => ({ - props: args, - template: ` - This is a spot-link - `, - }), -}; - -export const LeftIcon:Story = { - render: (args) => ({ - props: args, - template: ` - - This is a spot-link - - - `, - }), -}; - -export const RightIcon:Story = { - render: (args) => ({ - props: args, - template: ` - - - This is a spot-link - - `, - }), -}; - -export const BothIcons:Story = { - render: (args) => ({ - props: args, - template: ` - - - This is a spot-link - - - `, - }), -}; diff --git a/frontend/src/stories/List.mdx b/frontend/src/stories/List.mdx deleted file mode 100644 index 0d189026811..00000000000 --- a/frontend/src/stories/List.mdx +++ /dev/null @@ -1,36 +0,0 @@ -import { Canvas, Meta } from '@storybook/blocks'; - -import * as ListStories from './List.stories'; - - - -# List - -Lists are simply a collection of components in a vertical list. They can be used inside modals like dropdowns, within a small scrollable module, or anywhere else a series of items needs to be presented. - -This component does not have a Figma object associated with it, since a group of elements itself is the list. This component represents lists that are interactive (checkboxes and drop down selections), or when the list is generated as a result of interaction. The items in the list are all individual components (or list primitives). - -This list is not to be confused with a standard HTML list element, which generates a bullet list of text. For this, a component is not needed in the Design System. - - - -# Behaviour - -Items are displayed stacked vertically. The behaviour of each individual element is inherited from properties of that element. - -Lists also allow nesting. Each nested item has an additional 16px padding to the left in relation to its parent. - -The dimensions of the list are defined by the dimensions of the containing element, and its overflow rules. - -# Actions - -List items have a primary action attached to them. This can be linking somewhere, a button listener, or acting -as a label for a checkbox. - - - -# Compact - -The compact version makes the items a little bit less tall. - - diff --git a/frontend/src/stories/List.stories.ts b/frontend/src/stories/List.stories.ts deleted file mode 100644 index 6aefd9accdf..00000000000 --- a/frontend/src/stories/List.stories.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/angular'; -import { moduleMetadata } from '@storybook/angular'; -import { OpSpotModule } from '../app/spot/spot.module'; - -const meta:Meta = { - title: 'Components/List', - decorators: [ - moduleMetadata({ - imports: [OpSpotModule], - }), - ], -}; - -export default meta; -type Story = StoryObj; - -export const WithLinks:Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), -}; - -export const WithCheckboxes:Story = { - render: (args) => ({ - props: args, - template: ` -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
- `, - }), -}; - -export const Compact:Story = { - render: (args) => ({ - props: args, - template: ` - - `, - }), -}; diff --git a/frontend/src/stories/SectionHeader.mdx b/frontend/src/stories/SectionHeader.mdx deleted file mode 100644 index 880c751851e..00000000000 --- a/frontend/src/stories/SectionHeader.mdx +++ /dev/null @@ -1,51 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# Section Headers - -> Example: Section header example - -The section header is used to separate different sections of a page or a view, or different grouping of similar functionality. - -In the Files tab for example, the section header separates the Attachments section and the the different file storages that may be available to a work package. - -## Structure and Options - -Section headers are at their most basic form a label (in all-caps) with a line underneath. - -> Example of a basic section header - -However, they can be extended with a few additional features: - -**Left icon** -This icon can be added to provide context. In a files list, for example, the left icon is used to indicate to which storage provider (for example, Nextcloud) that file storage is linked. - -In some parts of the application, this icon is also used to indicate open/closed state when the section header functions as an expandable/collapsible section (see *Behaviour*). - -*Note: Since this element is always all-caps, the baseline of the font requires the icon to be raised by a few pixels. See* Margins and Spacing *for more details.* - -**Right icon** -The icon can be placed to the right of the text too, if required. It can optionally also be used as a link (in icon-only mode). This should nevertheless be used sparingly. - -**Action** -This is usually a button on the far edge (by default the right edge) of the component, but can also be a link. This is used to provide additional contextual action related to that whole section (to access the home directory in Nextcloud in a new tab, for example). - -> Example of a section header with a left icon and an action (like a Nextcloud file storage) - -**Right position** -The entire structure can be inverted, with the action buton on the left and the title and icons on the right. This should ideally never be used, but exists because such a format already exists in some places. Use sparingly. - -## Behaviour - -The section header behaves either as a regular header (static) or as an expandable/collapsible section, where clicking on the entire section header will expand and collapse the content underneath. - -> Example: "Group by" expandable/collapsible section, like a cost report page - -## Margins and Spacing - -The headers have an 0.5 rem top and bottom margin. - -Because the label is all-caps, the baseline of the font is (visually) slightly higher than it otherwise would have been with mixed case. If the optional icons are used, the CSS of this component automatically pushed them up by a few pixels for better alignment. - - diff --git a/frontend/src/stories/SectionHeader.stories.ts b/frontend/src/stories/SectionHeader.stories.ts deleted file mode 100644 index 1f927626900..00000000000 --- a/frontend/src/stories/SectionHeader.stories.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Meta } from '@storybook/angular'; - -const meta:Meta = { - title: 'Section Header', -}; - -export default meta; diff --git a/frontend/src/stories/Toast.mdx b/frontend/src/stories/Toast.mdx deleted file mode 100644 index 3c2e050c435..00000000000 --- a/frontend/src/stories/Toast.mdx +++ /dev/null @@ -1,51 +0,0 @@ -import { Meta } from '@storybook/blocks'; - - - -# Toast - -> Example: A success toast - -Toasts are small generally one-line messages that appear to inform the user of the result of an action, or an update to the current view. - -## Structure and Options - -The toast consists of: - -- An optional icon -- A text -- A close button - -There are four types of toasts, each with a specific icon and colour associated with it: - -An **Information** toast is blue (background: Feedback/Info/Light, stroke: Feedback/Info/Dark) and provides the user with helpful information about the current view. - -> Eg: a blue notification toast (as it exists today) - -A **Success** toast is green (background: Feedback/Success/Light, stroke: Feedback/Success/Dark)and indicates to the user that an action has been successfully carried out. - -> Eg: a green success toast (as it exists today) - -A **Warning** toast is orange (background: Feedback/Warning/Light, stroke: Feedback/Warning/Dark) and indicates that there are potential risks or possibility of error. - -> Eg: an orange toast (is this used somewhere?) - -An **Error** toast is red (background: Feedback/Error/Light, stroke: Feedback/Error/Dark) and indicates that something did not not go as expected, and that there is a problem. - -> Eg: a red toast (as it exists today, when moving an WP in GANTT view is not possible) - -## Behaviour - -The toast appears either as result of user action (a work package attribute is modified, for example) or a background action (there are new notifications). - -The text is generally merely informational, although there might sometimes be a link with an approproate action. - -> Eg. Blue notification toast with "Load new notifications" link. - -Toasts generally disappear automatically; they must be manually closed by the user. The only exception is the Success toast, which _can_ disappear automatically, if the user has enabled this in their account settings. - -Ideally, only one toast should be visible at any time. Hoever, there are circumstances where multiple toasts might be generated and visible to the user; in this case, the toasts must "pile" vertically with a 1 rem (?) spacing between them. - -## Margins and Spacing - -The toast has a height of 2.5 rem, with a 0.5 rem padding on all sides, and a 0.5 rem spacing between the icon, text and the close button. diff --git a/frontend/src/stories/assets/logo_openproject.png b/frontend/src/stories/assets/logo_openproject.png deleted file mode 100644 index 807075ca80d..00000000000 Binary files a/frontend/src/stories/assets/logo_openproject.png and /dev/null differ diff --git a/frontend/src/stories/assets/logo_openproject_spot.png b/frontend/src/stories/assets/logo_openproject_spot.png deleted file mode 100644 index 65bad98ad4f..00000000000 Binary files a/frontend/src/stories/assets/logo_openproject_spot.png and /dev/null differ diff --git a/frontend/src/stories/configuration.service.stub.ts b/frontend/src/stories/configuration.service.stub.ts deleted file mode 100644 index 0eff0efcbd7..00000000000 --- a/frontend/src/stories/configuration.service.stub.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const ConfigurationServiceStub = { - startOfWeek: () => 1, - startOfWeekPresent: () => true, -}; diff --git a/frontend/src/stories/i18n.service.stub.ts b/frontend/src/stories/i18n.service.stub.ts deleted file mode 100644 index febd8fcbe5f..00000000000 --- a/frontend/src/stories/i18n.service.stub.ts +++ /dev/null @@ -1,52 +0,0 @@ -export const I18nServiceStub = { - t(name:string):any { - return { - 'date.abbr_day_names': [ - 'Mon', - 'Tue', - 'Wed', - 'Thu', - 'Fri', - 'Sat', - 'Sun', - ], - 'date.day_names': [ - 'Monday', - 'Tuesday', - 'Wednesday', - 'Thursday', - 'Friday', - 'Saturday', - 'Sunday', - ], - 'date.abbr_month_names': [ - 'Jan', - 'Feb', - 'Mar', - 'Apr', - 'May', - 'Jun', - 'Jul', - 'Aug', - 'Sep', - 'Oct', - 'Nov', - 'Dec', - ], - 'date.month_names': [ - 'January', - 'February', - 'March', - 'April', - 'May', - 'June', - 'July', - 'August', - 'September', - 'October', - 'November', - 'December', - ], - }[name] || name as T; - }, -}; diff --git a/frontend/src/stories/timezone.service.stub.ts b/frontend/src/stories/timezone.service.stub.ts deleted file mode 100644 index ba2a9eea225..00000000000 --- a/frontend/src/stories/timezone.service.stub.ts +++ /dev/null @@ -1,2 +0,0 @@ -export const TimezoneServiceStub = { -}; diff --git a/frontend/src/stories/weekday.service.stub.ts b/frontend/src/stories/weekday.service.stub.ts deleted file mode 100644 index f7662710391..00000000000 --- a/frontend/src/stories/weekday.service.stub.ts +++ /dev/null @@ -1,59 +0,0 @@ -// -- copyright -// OpenProject is an open source project management software. -// Copyright (C) 2012-2023 the OpenProject GmbH -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License version 3. -// -// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: -// Copyright (C) 2006-2013 Jean-Philippe Lang -// Copyright (C) 2010-2013 the ChiliProject Team -// -// This program is free software; you can redistribute it and/or -// modify it under the terms of the GNU General Public License -// as published by the Free Software Foundation; either version 2 -// of the License, or (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program; if not, write to the Free Software -// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -// -// See COPYRIGHT and LICENSE files for more details. -//++ - -import { - Injectable, -} from '@angular/core'; -import * as moment from 'moment'; -import { IWeekday } from 'core-app/core/state/days/weekday.model'; -import { - Observable, - of, -} from 'rxjs'; - -@Injectable({ providedIn: 'root' }) -export class WeekdayServiceStub { - private weekdays:IWeekday[] = []; - - /** - * @param date The iso day number (1-7) or a date instance - * @return {boolean} whether the given iso day is working or not - */ - public isNonWorkingDay(date:Date|number):boolean { - const isoDayOfWeek = (typeof date === 'number') ? date : moment(date).isoWeekday(); - return !!(this.weekdays || []).find((wd) => wd.day === isoDayOfWeek && !wd.working); - } - - public get nonWorkingDays():IWeekday[] { - return this.weekdays.filter((day) => !day.working); - } - - loadWeekdays():Observable { - return of(this.weekdays); - } -}