diff --git a/.gitignore b/.gitignore index 52552352c3..ba9ee3b799 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ rebel.xml .cache .turbo +storybook-static diff --git a/package.json b/package.json index e9cdda49c1..2455b30856 100644 --- a/package.json +++ b/package.json @@ -18,20 +18,21 @@ "set-version": "ui-scripts version" }, "dependencies": { - "@scm-manager/eslint-config": "^2.15.1" + "@scm-manager/eslint-config": "2.16.0" }, "devDependencies": { "@scm-manager/remark-preset-lint": "^1.0.0", "babel-plugin-reflow": "^0.2.7", + "cross-env": "^7.0.3", "husky": "^4.2.5", "lint-staged": "^10.2.11", "remark-cli": "^9.0.0", - "turbo": "^1.2.5", - "cross-env": "^7.0.3" + "turbo": "^1.2.5" }, "resolutions": { "babel-core": "7.0.0-bridge.0", - "gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#420d6cfa17a6a8f9bf1a517a2c629dcb332dbe13" + "gitdiff-parser": "https://github.com/scm-manager/gitdiff-parser#420d6cfa17a6a8f9bf1a517a2c629dcb332dbe13", + "@types/react": "17.0.47" }, "babel": { "presets": [ diff --git a/scm-it/gradle.lockfile b/scm-it/gradle.lockfile index a1d8bb208c..603078d0b6 100644 --- a/scm-it/gradle.lockfile +++ b/scm-it/gradle.lockfile @@ -10,7 +10,7 @@ com.cloudogu.legman.support:micrometer:2.0.0=testRuntimeClasspath,testRuntimeCla com.cloudogu.legman.support:shiro:2.0.0=testRuntimeClasspath,testRuntimeClasspathCopy com.cloudogu.legman:core:2.0.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.cloudogu.spotter:spotter-core:4.0.0=testRuntimeClasspath,testRuntimeClasspathCopy -com.cronutils:cron-utils:9.1.6=testRuntimeClasspath,testRuntimeClasspathCopy +com.cronutils:cron-utils:9.1.8=testRuntimeClasspath,testRuntimeClasspathCopy com.damnhandy:handy-uri-templates:2.1.7=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.fasterxml.jackson.core:jackson-annotations:2.11.3=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.fasterxml.jackson.core:jackson-core:2.11.3=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy @@ -34,8 +34,7 @@ com.github.sdorra:web-resources:1.1.1=testRuntimeClasspath,testRuntimeClasspathC com.github.spullara.mustache.java:compiler:0.9.7=testRuntimeClasspath,testRuntimeClasspathCopy com.google.auto:auto-common:0.11=testRuntimeClasspath,testRuntimeClasspathCopy com.google.code.findbugs:jsr305:3.0.2=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy -com.google.code.gson:gson:2.8.6=testRuntimeClasspath -com.google.code.gson:gson:2.8.7=testRuntimeClasspathCopy +com.google.code.gson:gson:2.8.7=testRuntimeClasspath,testRuntimeClasspathCopy com.google.errorprone:error_prone_annotations:2.3.4=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.google.guava:failureaccess:1.0.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.google.guava:guava:30.1-jre=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy @@ -46,8 +45,7 @@ com.google.inject.extensions:guice-servlet:5.0.1=testCompileClasspath,testCompil com.google.inject.extensions:guice-throwingproviders:5.0.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.google.inject:guice:5.0.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.google.j2objc:j2objc-annotations:1.3=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy -com.googlecode.javaewah:JavaEWAH:1.1.12=testCompileClasspathCopy,testRuntimeClasspathCopy -com.googlecode.javaewah:JavaEWAH:1.1.7=testCompileClasspath,testRuntimeClasspath +com.googlecode.javaewah:JavaEWAH:1.1.12=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.ibm.async:asyncutil:0.1.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy com.jcraft:jsch.agentproxy.connector-factory:0.0.7=testRuntimeClasspath,testRuntimeClasspathCopy com.jcraft:jsch.agentproxy.core:0.0.7=testRuntimeClasspath,testRuntimeClasspathCopy @@ -147,13 +145,10 @@ org.apache.sling:org.apache.sling.javax.activation:0.1.0=testCompileClasspath,te org.apache.tika:tika-core:1.25=testRuntimeClasspath,testRuntimeClasspathCopy org.apiguardian:apiguardian-api:1.1.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.assertj:assertj-core:3.18.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy -org.bouncycastle:bcpg-jdk15on:1.67=testRuntimeClasspath -org.bouncycastle:bcpg-jdk15on:1.69=testRuntimeClasspathCopy -org.bouncycastle:bcpkix-jdk15on:1.67=testRuntimeClasspath -org.bouncycastle:bcpkix-jdk15on:1.69=testRuntimeClasspathCopy -org.bouncycastle:bcprov-jdk15on:1.67=testRuntimeClasspath -org.bouncycastle:bcprov-jdk15on:1.69=testRuntimeClasspathCopy -org.bouncycastle:bcutil-jdk15on:1.69=testRuntimeClasspathCopy +org.bouncycastle:bcpg-jdk15on:1.69=testRuntimeClasspath,testRuntimeClasspathCopy +org.bouncycastle:bcpkix-jdk15on:1.69=testRuntimeClasspath,testRuntimeClasspathCopy +org.bouncycastle:bcprov-jdk15on:1.69=testRuntimeClasspath,testRuntimeClasspathCopy +org.bouncycastle:bcutil-jdk15on:1.69=testRuntimeClasspath,testRuntimeClasspathCopy org.ccil.cowan.tagsoup:tagsoup:1.2.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.checkerframework:checker-qual:3.5.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.codehaus.groovy:groovy-json:3.0.2=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy @@ -162,6 +157,7 @@ org.codehaus.groovy:groovy:3.0.2=testCompileClasspath,testCompileClasspathCopy,t org.eclipse.microprofile.config:microprofile-config-api:2.0=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.glassfish.jaxb:jaxb-runtime:2.3.3=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.glassfish.jaxb:txw2:2.3.3=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy +org.glassfish:jakarta.el:3.0.4=testRuntimeClasspath,testRuntimeClasspathCopy org.glassfish:javax.el:3.0.1-b11=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.glassfish:javax.json:1.1.4=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.hamcrest:hamcrest-core:2.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy @@ -174,7 +170,6 @@ org.jacoco:org.jacoco.ant:0.8.7=jacocoAntCopy org.jacoco:org.jacoco.core:0.8.7=jacocoAntCopy org.jacoco:org.jacoco.report:0.8.7=jacocoAntCopy org.javahg:javahg:1.0.0=testRuntimeClasspath,testRuntimeClasspathCopy -org.javassist:javassist:3.27.0-GA=testRuntimeClasspath,testRuntimeClasspathCopy org.jboss.logging:jboss-logging:3.4.2.Final=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.jboss.resteasy:resteasy-client-api:4.7.5.Final=testRuntimeClasspath,testRuntimeClasspathCopy org.jboss.resteasy:resteasy-client:4.7.5.Final=testRuntimeClasspath,testRuntimeClasspathCopy @@ -215,18 +210,12 @@ org.reactivestreams:reactive-streams:1.0.3=testCompileClasspath,testCompileClass org.slf4j:jcl-over-slf4j:1.7.30=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.slf4j:slf4j-api:1.7.30=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy org.tmatesoft.sqljet:sqljet:1.1.14=testRuntimeClasspath,testRuntimeClasspathCopy -sonia.jgit:org.eclipse.jgit.gpg.bc:5.11.1.202105131744-r-scm2=testRuntimeClasspath -sonia.jgit:org.eclipse.jgit.gpg.bc:5.13.0.202109080827-r-scm1=testRuntimeClasspathCopy -sonia.jgit:org.eclipse.jgit.http.apache:5.11.1.202105131744-r-scm2=testRuntimeClasspath -sonia.jgit:org.eclipse.jgit.http.apache:5.13.0.202109080827-r-scm1=testRuntimeClasspathCopy -sonia.jgit:org.eclipse.jgit.http.server:5.11.1.202105131744-r-scm2=testRuntimeClasspath -sonia.jgit:org.eclipse.jgit.http.server:5.13.0.202109080827-r-scm1=testRuntimeClasspathCopy -sonia.jgit:org.eclipse.jgit.lfs.server:5.11.1.202105131744-r-scm2=testRuntimeClasspath -sonia.jgit:org.eclipse.jgit.lfs.server:5.13.0.202109080827-r-scm1=testRuntimeClasspathCopy -sonia.jgit:org.eclipse.jgit.lfs:5.11.1.202105131744-r-scm2=testRuntimeClasspath -sonia.jgit:org.eclipse.jgit.lfs:5.13.0.202109080827-r-scm1=testRuntimeClasspathCopy -sonia.jgit:org.eclipse.jgit:5.11.1.202105131744-r-scm2=testCompileClasspath,testRuntimeClasspath -sonia.jgit:org.eclipse.jgit:5.13.0.202109080827-r-scm1=testCompileClasspathCopy,testRuntimeClasspathCopy +sonia.jgit:org.eclipse.jgit.gpg.bc:5.13.0.202109080827-r-scm1=testRuntimeClasspath,testRuntimeClasspathCopy +sonia.jgit:org.eclipse.jgit.http.apache:5.13.0.202109080827-r-scm1=testRuntimeClasspath,testRuntimeClasspathCopy +sonia.jgit:org.eclipse.jgit.http.server:5.13.0.202109080827-r-scm1=testRuntimeClasspath,testRuntimeClasspathCopy +sonia.jgit:org.eclipse.jgit.lfs.server:5.13.0.202109080827-r-scm1=testRuntimeClasspath,testRuntimeClasspathCopy +sonia.jgit:org.eclipse.jgit.lfs:5.13.0.202109080827-r-scm1=testRuntimeClasspath,testRuntimeClasspathCopy +sonia.jgit:org.eclipse.jgit:5.13.0.202109080827-r-scm1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy sonia.svnkit:svnkit-dav:1.10.3-scm2=testRuntimeClasspath,testRuntimeClasspathCopy sonia.svnkit:svnkit:1.10.3-scm2=testRuntimeClasspath,testRuntimeClasspathCopy empty=annotationProcessor,annotationProcessorCopy,archives,archivesCopy,compileClasspath,compileClasspathCopy,corePlugin,corePluginCopy,default,defaultCopy,itPlugin,itPluginCopy,itWebApp,itWebAppCopy,runtimeClasspath,runtimeClasspathCopy,testAnnotationProcessor,testAnnotationProcessorCopy diff --git a/scm-plugins/scm-git-plugin/package.json b/scm-plugins/scm-git-plugin/package.json index eb22ed05f5..6d61086592 100644 --- a/scm-plugins/scm-git-plugin/package.json +++ b/scm-plugins/scm-git-plugin/package.json @@ -15,7 +15,7 @@ }, "devDependencies": { "@scm-manager/babel-preset": "^2.13.1", - "@scm-manager/eslint-config": "^2.15.1", + "@scm-manager/eslint-config": "^2.16.0", "@scm-manager/jest-preset": "^2.13.0", "@scm-manager/plugin-scripts": "^1.2.2", "@scm-manager/prettier-config": "^2.11.1", @@ -33,4 +33,4 @@ "eslintConfig": { "extends": "@scm-manager/eslint-config" } -} \ No newline at end of file +} diff --git a/scm-plugins/scm-hg-plugin/package.json b/scm-plugins/scm-hg-plugin/package.json index c0111a9226..421e31fc8f 100644 --- a/scm-plugins/scm-hg-plugin/package.json +++ b/scm-plugins/scm-hg-plugin/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@scm-manager/babel-preset": "^2.13.1", - "@scm-manager/eslint-config": "^2.15.1", + "@scm-manager/eslint-config": "^2.16.0", "@scm-manager/jest-preset": "^2.13.0", "@scm-manager/plugin-scripts": "^1.2.2", "@scm-manager/prettier-config": "^2.11.1", @@ -32,4 +32,4 @@ "eslintConfig": { "extends": "@scm-manager/eslint-config" } -} \ No newline at end of file +} diff --git a/scm-plugins/scm-legacy-plugin/package.json b/scm-plugins/scm-legacy-plugin/package.json index fdd92b946c..cb927b4a9b 100644 --- a/scm-plugins/scm-legacy-plugin/package.json +++ b/scm-plugins/scm-legacy-plugin/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@scm-manager/babel-preset": "^2.13.1", - "@scm-manager/eslint-config": "^2.15.1", + "@scm-manager/eslint-config": "^2.16.0", "@scm-manager/jest-preset": "^2.13.0", "@scm-manager/plugin-scripts": "^1.2.2", "@scm-manager/prettier-config": "^2.11.1", @@ -32,4 +32,4 @@ "eslintConfig": { "extends": "@scm-manager/eslint-config" } -} \ No newline at end of file +} diff --git a/scm-plugins/scm-svn-plugin/package.json b/scm-plugins/scm-svn-plugin/package.json index 5087d30d9b..a7d6251716 100644 --- a/scm-plugins/scm-svn-plugin/package.json +++ b/scm-plugins/scm-svn-plugin/package.json @@ -14,7 +14,7 @@ }, "devDependencies": { "@scm-manager/babel-preset": "^2.13.1", - "@scm-manager/eslint-config": "^2.15.1", + "@scm-manager/eslint-config": "^2.16.0", "@scm-manager/jest-preset": "^2.13.0", "@scm-manager/plugin-scripts": "^1.2.2", "@scm-manager/prettier-config": "^2.11.1", @@ -32,4 +32,4 @@ "eslintConfig": { "extends": "@scm-manager/eslint-config" } -} \ No newline at end of file +} diff --git a/scm-ui/build.gradle b/scm-ui/build.gradle index 78e6a09e57..95acc300d3 100644 --- a/scm-ui/build.gradle +++ b/scm-ui/build.gradle @@ -279,6 +279,7 @@ license { exclude '**/*.snap' exclude '**/*.iml' exclude '**/.babelrc' + exclude '**/storybook-static/**' tasks { modules { diff --git a/scm-ui/e2e-tests/package.json b/scm-ui/e2e-tests/package.json index 4315bc8404..77d00eb193 100644 --- a/scm-ui/e2e-tests/package.json +++ b/scm-ui/e2e-tests/package.json @@ -17,7 +17,7 @@ "fluent-ffmpeg": "^2.1.2" }, "devDependencies": { - "@scm-manager/eslint-config": "^2.15.1" + "@scm-manager/eslint-config": "^2.16.0" }, "prettier": "@scm-manager/prettier-config", "eslintConfig": { @@ -26,4 +26,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/scm-ui/ui-api/package.json b/scm-ui/ui-api/package.json index 967ffef61d..4309afac0a 100644 --- a/scm-ui/ui-api/package.json +++ b/scm-ui/ui-api/package.json @@ -19,7 +19,7 @@ }, "devDependencies": { "@scm-manager/babel-preset": "^2.13.1", - "@scm-manager/eslint-config": "^2.15.1", + "@scm-manager/eslint-config": "^2.16.0", "@scm-manager/jest-preset": "^2.13.0", "@scm-manager/prettier-config": "^2.10.1", "@scm-manager/tsconfig": "^2.13.0", @@ -52,4 +52,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/scm-ui/ui-api/src/plugins.ts b/scm-ui/ui-api/src/plugins.ts index d6e27e1ec2..44163000b5 100644 --- a/scm-ui/ui-api/src/plugins.ts +++ b/scm-ui/ui-api/src/plugins.ts @@ -23,12 +23,15 @@ */ import { ApiResult, useIndexLink, useRequiredIndexLink } from "./base"; -import { isPluginCollection, PendingPlugins, Plugin, PluginCollection } from "@scm-manager/ui-types"; +import type { PendingPlugins, Plugin, PluginCollection, HalRepresentation } from "@scm-manager/ui-types"; import { useMutation, useQuery, useQueryClient } from "react-query"; import { apiClient } from "./apiclient"; import { requiredLink } from "./links"; import { BadGatewayError } from "./errors"; +const isPluginCollection = (input: HalRepresentation): input is PluginCollection => + input._embedded ? "plugins" in input._embedded : false; + type WaitForRestartOptions = { initialDelay?: number; timeout?: number; diff --git a/scm-ui/ui-buttons/.storybook/.babelrc b/scm-ui/ui-buttons/.storybook/.babelrc new file mode 100644 index 0000000000..2fa1f1e174 --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["@scm-manager/babel-preset"], + "plugins": ["@babel/plugin-syntax-dynamic-import"] +} diff --git a/scm-ui/ui-buttons/.storybook/RemoveThemesPlugin.js b/scm-ui/ui-buttons/.storybook/RemoveThemesPlugin.js new file mode 100644 index 0000000000..9976554354 --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/RemoveThemesPlugin.js @@ -0,0 +1,57 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const HtmlWebpackPlugin = require('html-webpack-plugin'); + +class RemoveThemesPlugin { + apply (compiler) { + compiler.hooks.compilation.tap('RemoveThemesPlugin', (compilation) => { + + HtmlWebpackPlugin.getHooks(compilation).beforeAssetTagGeneration.tapAsync( + 'RemoveThemesPlugin', + (data, cb) => { + + // remove generated style-loader bundles from the page + // there should be a better way, which does not generate the bundles at all + // but for now it works + if (data.assets.js) { + data.assets.js = data.assets.js.filter(bundle => !bundle.startsWith("ui-theme-")) + .filter(bundle => !bundle.startsWith("runtime~ui-theme-")) + } + + // remove css links to avoid conflicts with the themes + // so we remove all and add our own via preview-head.html + if (data.assets.css) { + data.assets.css = data.assets.css.filter(css => !css.startsWith("ui-theme-")) + } + + // Tell webpack to move on + cb(null, data) + } + ) + }) + } +} + +module.exports = RemoveThemesPlugin diff --git a/scm-ui/ui-buttons/.storybook/index.css b/scm-ui/ui-buttons/.storybook/index.css new file mode 100644 index 0000000000..903dbfaa56 --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/index.css @@ -0,0 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/scm-ui/ui-buttons/.storybook/main.js b/scm-ui/ui-buttons/.storybook/main.js new file mode 100644 index 0000000000..dd8037ff73 --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/main.js @@ -0,0 +1,109 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const path = require("path"); +const fs = require("fs"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const RemoveThemesPlugin = require("./RemoveThemesPlugin"); +const ReactDOM = require("react-dom"); + +const root = path.resolve(".."); + +const themedir = path.join(root, "ui-styles", "src"); + +ReactDOM.createPortal = (node) => node; + +const themes = fs + .readdirSync(themedir) + .map((filename) => path.parse(filename)) + .filter((p) => p.ext === ".scss") + .reduce((entries, current) => ({ ...entries, [`ui-theme-${current.name}`]: path.join(themedir, current.base) }), {}); + +module.exports = { + typescript: { reactDocgen: false }, + core: { + builder: "webpack5", + }, + stories: ["../docs/**/*.stories.mdx", "../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + addons: [ + "storybook-addon-i18next", + "storybook-addon-themes", + "@storybook/addon-links", + "@storybook/addon-essentials", + "@storybook/addon-interactions", + "@storybook/addon-a11y", + "storybook-addon-pseudo-states" + ], + framework: "@storybook/react", + webpackFinal: async (config) => { + // add our themes to webpack entry points + config.entry = { + main: config.entry, + ...themes, + }; + + // create separate css files for our themes + config.plugins.push( + new MiniCssExtractPlugin({ + filename: "[name].css", + ignoreOrder: false, + }) + ); + + config.module.rules.push({ + test: /\.scss$/, + use: [MiniCssExtractPlugin.loader, "css-loader", "sass-loader"], + }); + + config.module.rules.push({ + test: /\.css$/, + use: [ + { + loader: "postcss-loader", + options: { + postcssOptions: { + plugins: { + tailwindcss: { config: require("./tailwind.config") }, + autoprefixer: {}, + }, + }, + }, + }, + ], + include: path.resolve(__dirname, "../"), + }); + + // the html-webpack-plugin adds the generated css and js files to the iframe, + // which overrides our manually loaded css files. + // So we use a custom plugin which uses a hook of html-webpack-plugin + // to filter our themes from the output. + config.plugins.push(new RemoveThemesPlugin()); + + // force cjs instead of esm + // https://github.com/tannerlinsley/react-query/issues/3513 + config.resolve.alias["react-query/devtools"] = require.resolve("react-query/devtools"); + + return config; + }, +}; diff --git a/scm-ui/ui-buttons/.storybook/preview-head.html b/scm-ui/ui-buttons/.storybook/preview-head.html new file mode 100644 index 0000000000..d1c34ac7ef --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/preview-head.html @@ -0,0 +1,26 @@ + + + + diff --git a/scm-ui/ui-buttons/.storybook/preview.js b/scm-ui/ui-buttons/.storybook/preview.js new file mode 100644 index 0000000000..fb70b7769b --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/preview.js @@ -0,0 +1,97 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import "./index.css"; + +import i18next from "i18next"; +import { initReactI18next } from "react-i18next"; +import { withI18next } from "storybook-addon-i18next"; +import React, {useEffect} from "react"; +import withApiProvider from "./withApiProvider"; +import { withThemes } from 'storybook-addon-themes/react'; + +let i18n = i18next; + +// only use fetch backend for storybook +// and not for storyshots +if (!process.env.JEST_WORKER_ID) { + const Backend = require("i18next-fetch-backend"); + i18n = i18n.use(Backend.default); +} + +i18n.use(initReactI18next).init({ + whitelist: ["en", "de", "es"], + lng: "en", + fallbackLng: "en", + interpolation: { + escapeValue: false, + }, + react: { + useSuspense: false, + }, + backend: { + loadPath: "/locales/{{lng}}/{{ns}}.json", + init: { + credentials: "same-origin", + }, + }, +}); + +export const decorators = [ + withI18next({ + i18n, + languages: { + en: "English", + de: "Deutsch", + es: "Spanisch", + }, + }), + withApiProvider, + withThemes +]; + +const Decorator = ({children, themeName}) => { + useEffect(() => { + const link = document.querySelector("#ui-theme"); + if (link && link["data-theme"] !== themeName) { + link.href = `ui-theme-${themeName}.css`; + link["data-theme"] = themeName; + } + }, [themeName]); + return <>{children} +}; + +export const parameters = { + actions: { argTypesRegex: "^on[A-Z].*" }, + themes: { + Decorator, + clearable: false, + default: "light", + list: [ + { name: "light", color: "#fff" }, + { name: "highcontrast", color: "#050514" }, + { name: "dark", color: "#121212" }, + ], + } +}; diff --git a/scm-ui/ui-buttons/.storybook/tailwind.config.js b/scm-ui/ui-buttons/.storybook/tailwind.config.js new file mode 100644 index 0000000000..165f3fef58 --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/tailwind.config.js @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const path = require("path"); + +module.exports = { + presets: [ + // eslint-disable-next-line global-require,import/no-extraneous-dependencies + require("@scm-manager/ui-scripts/src/tailwind.config"), + ], + content: [path.join(__dirname, "../{src,docs}/**/*.{tsx,mdx}")], +}; diff --git a/scm-ui/ui-buttons/.storybook/withApiProvider.js b/scm-ui/ui-buttons/.storybook/withApiProvider.js new file mode 100644 index 0000000000..8aa561d39c --- /dev/null +++ b/scm-ui/ui-buttons/.storybook/withApiProvider.js @@ -0,0 +1,45 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import * as React from "react"; +import { ApiProvider } from "@scm-manager/ui-api"; + +const withApiProvider = (storyFn) => { + return React.createElement(ApiProvider, { + index: { + version: "x.y.z", + _links: {} + }, + me: { + name: "trillian", + displayName: "Trillian McMillan", + mail: "trillian@hitchhiker.com", + groups: [], + _links: {} + }, + devtools: false, + children: storyFn() + }); +} + +export default withApiProvider; diff --git a/scm-ui/ui-buttons/docs/introduction.stories.mdx b/scm-ui/ui-buttons/docs/introduction.stories.mdx new file mode 100644 index 0000000000..bae9510291 --- /dev/null +++ b/scm-ui/ui-buttons/docs/introduction.stories.mdx @@ -0,0 +1,64 @@ +import { Meta } from "@storybook/addon-docs"; +import { Button } from "../src"; + + + +# Buttons + +The `@scm-manager/ui-buttons` library provides [atoms](https://atomicdesign.bradfrost.com/chapter-2/#atoms) implemented +as minimal wrappers around native html elements styled to match the general SCM-Manager aesthetic. + +## Components + +There are three actionable components available. Styling is consistent amongst them and all have the required `variant` property. + +1. [Button](?path=/story/components--button) +2. [Link Button](?path=/story/components--link-button) +3. [External Link Button](?path=/story/components--external-link-button) + +## Usage + +Actionable components serve a dedicated purpose. It is therefore important to know when and how to use them. + +### Variants + +There are four variants available to each of the three button types, varying in importance. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EmphasisButton VariantUsage Examples
Very HighDestructive actions
HighForm submit
NormalCancel action in dialog
LowCircumstantially relevant action on page with many actionable elements
+ +## Content + +Buttons exclusively contain text and no icons. Icons tend to be ambiguous and not always applicable which leads to inconsistent +and cluttered layouts. + +Button text should be short, concise and describe the action performed. diff --git a/scm-ui/ui-buttons/docs/usage.stories.mdx b/scm-ui/ui-buttons/docs/usage.stories.mdx new file mode 100644 index 0000000000..79057e1b02 --- /dev/null +++ b/scm-ui/ui-buttons/docs/usage.stories.mdx @@ -0,0 +1,22 @@ +import { Meta, Story } from "@storybook/addon-docs"; +import { Button } from "../src"; + + + +In confirmation dialogs, there are two actions.
+One to cancel the current process and one to confirm it.
+Aborting is always the secondary action, confirmation always the primary. +Focus is always on the cancelling action. + + +
+

Delete User

+

Do you really want to delete this user ?

+
+ + +
+
+
diff --git a/scm-ui/ui-buttons/package.json b/scm-ui/ui-buttons/package.json new file mode 100644 index 0000000000..45d6f501a9 --- /dev/null +++ b/scm-ui/ui-buttons/package.json @@ -0,0 +1,85 @@ +{ + "name": "@scm-manager/ui-buttons", + "version": "2.37.3-SNAPSHOT", + "private": true, + "main": "build/index.js", + "module": "build/index.mjs", + "types": "build/index.d.ts", + "files": [ + "build" + ], + "scripts": { + "build": "tsup ./src/index.ts -d build --format esm,cjs --dts", + "dev": "tsup ./src/index.ts -d build --format esm,cjs --dts --watch", + "lint": "eslint src", + "typecheck": "tsc", + "storybook": "start-storybook -p 6006 -s ../ui-webapp/public", + "build-storybook": "build-storybook", + "image-snapshots": "jest \"image-snapshot.test.ts\"", + "a11y-check": "jest \"a11y.test.ts\"", + "depcheck": "depcheck --ignores=@scm-manager/prettier-config,@scm-manager/tsconfig,@babel/core,sass-loader,autoprefixer,babel-loader,postcss-loader,tailwindcss,storybook-addon-*,@storybook/*,webpack" + }, + "peerDependencies": { + "react": "^17.0.1", + "react-dom": "^17.0.1", + "react-router-dom": "^5.3.1", + "classnames": "^2.2.6" + }, + "devDependencies": { + "@scm-manager/ui-scripts": "2.37.3-SNAPSHOT", + "@scm-manager/ui-styles": "2.37.3-SNAPSHOT", + "@scm-manager/ui-api": "2.37.3-SNAPSHOT", + "@scm-manager/eslint-config": "^2.16.0", + "@babel/core": "^7.17.8", + "@scm-manager/tsconfig": "^2.12.0", + "@storybook/addon-essentials": "^6.4.20", + "@storybook/addon-interactions": "^6.4.20", + "@storybook/addon-a11y": "^6.4.20", + "@storybook/addon-links": "^6.4.20", + "@storybook/builder-webpack5": "^6.4.20", + "@storybook/manager-webpack5": "^6.4.20", + "@storybook/react": "^6.4.20", + "@storybook/addon-storyshots-puppeteer": "^6.4.20", + "@storybook/addon-storyshots": "^6.4.20", + "@storybook/testing-library": "^0.0.9", + "jest-transform-css": "^4.0.1", + "puppeteer": "^15.5.0", + "storybook-addon-pseudo-states": "^1.15.1", + "storybook-react-router": "^1.0.8", + "@types/storybook-react-router": "^1.0.2", + "sass-loader": "^12.3.0", + "storybook-addon-themes": "^6.1.0", + "autoprefixer": "^10.4.4", + "babel-loader": "^8.2.4", + "postcss": "^8.4.12", + "postcss-loader": "^6.2.1", + "tailwindcss": "^3.0.23", + "webpack": "5", + "tsup": "^6.1.2", + "mini-css-extract-plugin": "^1.6.2", + "html-webpack-plugin": "^5.5.0", + "react-query": "^3.25.1", + "i18next": "^19.9.2", + "react-i18next": "^10.13.2", + "i18next-fetch-backend": "^2.3.1" + }, + "babel": { + "presets": [ + "@scm-manager/babel-preset" + ] + }, + "jest": { + "transform": { + "^.+\\.[tj]sx?$": "babel-jest", + "^.+\\.(css|less|scss)$": "jest-transform-css", + "^.+\\.mdx?$": "@storybook/addon-docs/jest-transform-mdx" + } + }, + "prettier": "@scm-manager/prettier-config", + "eslintConfig": { + "extends": "@scm-manager/eslint-config" + }, + "publishConfig": { + "access": "restricted" + } +} diff --git a/scm-ui/ui-buttons/postcss.config.js b/scm-ui/ui-buttons/postcss.config.js new file mode 100644 index 0000000000..14a7a4674d --- /dev/null +++ b/scm-ui/ui-buttons/postcss.config.js @@ -0,0 +1,30 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-dark-states-1-snap.png b/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-dark-states-1-snap.png new file mode 100644 index 0000000000..f9ebfc3806 Binary files /dev/null and b/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-dark-states-1-snap.png differ diff --git a/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-high-contrast-states-1-snap.png b/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-high-contrast-states-1-snap.png new file mode 100644 index 0000000000..f2291e4d0a Binary files /dev/null and b/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-high-contrast-states-1-snap.png differ diff --git a/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-light-states-1-snap.png b/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-light-states-1-snap.png new file mode 100644 index 0000000000..4b14c63bb5 Binary files /dev/null and b/scm-ui/ui-buttons/src/__image_snapshots__/image-snapshot-test-ts-image-snapshots-tests-light-states-1-snap.png differ diff --git a/scm-ui/ui-buttons/src/a11y.test.ts b/scm-ui/ui-buttons/src/a11y.test.ts new file mode 100644 index 0000000000..99e8c009b2 --- /dev/null +++ b/scm-ui/ui-buttons/src/a11y.test.ts @@ -0,0 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import initStoryshots from "@storybook/addon-storyshots"; +import { axeTest } from "@storybook/addon-storyshots-puppeteer"; +import path from "path"; + +initStoryshots({ + suite: "A11y checks", + test: axeTest({ + storybookUrl: `file://${path.resolve(__dirname, "../storybook-static")}`, + }), + storyNameRegex: /High-Contrast States/, +}); diff --git a/scm-ui/ui-buttons/src/button.stories.tsx b/scm-ui/ui-buttons/src/button.stories.tsx new file mode 100644 index 0000000000..b5cddfd31f --- /dev/null +++ b/scm-ui/ui-buttons/src/button.stories.tsx @@ -0,0 +1,91 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React from "react"; + +import { + Button as ButtonComponent, + ButtonVariantList, + ButtonVariants, + ExternalLinkButton as ExternalLinkButtonComponent, + LinkButton as LinkButtonComponent, +} from "./button"; +import StoryRouter from "storybook-react-router"; +import { StoryFn } from "@storybook/react"; + +type ExtractProps = T extends React.ComponentType ? U : never; + +// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export +export default { + title: "Components", + component: null, + subcomponents: { + Button: ButtonComponent, + LinkButton: LinkButtonComponent, + ExternalLinkButton: ExternalLinkButtonComponent, + }, + argTypes: { + variant: { + options: ButtonVariantList, + control: { type: "select" }, + }, + }, + decorators: [StoryRouter()], + parameters: { + storyshots: { disable: true }, + }, +}; + +// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args +const ButtonTemplate: StoryFn> = (args) => ; +const LinkButtonTemplate: StoryFn> = (args) => ( + +); +const ExternalLinkButtonTemplate: StoryFn> = (args) => ( + +); + +export const Button = ButtonTemplate.bind({}); +// More on args: https://storybook.js.org/docs/react/writing-stories/args +Button.args = { + children: "Button", + variant: ButtonVariants.PRIMARY, + disabled: false, +}; + +export const LinkButton = LinkButtonTemplate.bind({}); +// More on args: https://storybook.js.org/docs/react/writing-stories/args +LinkButton.args = { + children: "Link Button", + to: "/repos", + variant: ButtonVariants.PRIMARY, +}; + +export const ExternalLinkButton = ExternalLinkButtonTemplate.bind({}); +// More on args: https://storybook.js.org/docs/react/writing-stories/args +ExternalLinkButton.args = { + children: "External Link Button", + href: "https://scm-manager.org", + variant: ButtonVariants.PRIMARY, +}; diff --git a/scm-ui/ui-buttons/src/button.test.stories.mdx b/scm-ui/ui-buttons/src/button.test.stories.mdx new file mode 100644 index 0000000000..83a22a4f4a --- /dev/null +++ b/scm-ui/ui-buttons/src/button.test.stories.mdx @@ -0,0 +1,73 @@ +import { Meta, Story } from "@storybook/addon-docs"; +import { Button, ButtonVariantList } from "./button"; + + + + `#${variant}-Hover`), + focus: ButtonVariantList.map(variant => `#${variant}-Focus`), + active: ButtonVariantList.map(variant => `#${variant}-Active`), + }, + themes: { + default: 'light', + }, +}}> + + + + {ButtonVariantList.map(variant => )} + + {["Normal", "Hover", "Active", "Focus", "Disabled"].map(state => + + {ButtonVariantList.map(variant => )} + )} +
STATE{variant.toUpperCase()}
{state}
+
+ + `#${variant}-Hover`), + focus: ButtonVariantList.map(variant => `#${variant}-Focus`), + active: ButtonVariantList.map(variant => `#${variant}-Active`), + }, + themes: { + default: 'dark', + }, +}}> + + + + {ButtonVariantList.map(variant => )} + + {["Normal", "Hover", "Active", "Focus", "Disabled"].map(state => + + {ButtonVariantList.map(variant => )} + )} +
STATE{variant.toUpperCase()}
{state}
+
+ + `#${variant}-Hover`), + focus: ButtonVariantList.map(variant => `#${variant}-Focus`), + active: ButtonVariantList.map(variant => `#${variant}-Active`), + }, + themes: { + default: 'highcontrast', + }, +}}> + + + + {ButtonVariantList.map(variant => )} + + {["Normal", "Hover", "Active", "Focus", "Disabled"].map(state => + + {ButtonVariantList.map(variant => )} + )} +
STATE{variant.toUpperCase()}
{state}
+
diff --git a/scm-ui/ui-buttons/src/button.tsx b/scm-ui/ui-buttons/src/button.tsx new file mode 100644 index 0000000000..8796a4113f --- /dev/null +++ b/scm-ui/ui-buttons/src/button.tsx @@ -0,0 +1,109 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from "react"; +import { Link as ReactRouterLink, LinkProps as ReactRouterLinkProps } from "react-router-dom"; +import classNames from "classnames"; + +export const ButtonVariants = { + PRIMARY: "primary", + SECONDARY: "secondary", + TERTIARY: "tertiary", + SIGNAL: "signal", +} as const; + +export const ButtonVariantList = Object.values(ButtonVariants); + +type ButtonVariant = typeof ButtonVariants[keyof typeof ButtonVariants]; + +const BASE_BUTTON_CLASSES = classNames( + "inline-block whitespace-nowrap rounded border py-2 px-6 text-center font-semibold focus:z-10 focus:outline focus:outline-offset-2 focus:outline-purple-500 disabled:cursor-not-allowed" +); +const DEFAULT_BUTTON_CLASSES = classNames( + "border-gray-200 hover:border-gray-400 active:shadow-inner disabled:hover:border-gray-200 disabled:active:shadow-none" +); +const PRIMARY_BUTTON_CLASSES = classNames( + "border-transparent bg-primary text-primary-contrast hover:bg-primary-hover active:bg-primary-active disabled:bg-primary-disabled disabled:text-primary-disabled-contrast " +); +const SECONDARY_BUTTON_CLASSES = classNames( + "border-primary text-primary hover:border-primary-hover hover:text-primary-hover active:border-primary-active active:text-primary-active disabled:border-primary-disabled disabled:text-primary-disabled" +); +const TERTIARY_BUTTON_CLASSES = classNames( + "border-transparent text-primary hover:text-primary-hover active:text-primary-active disabled:text-primary-disabled" +); +const SIGNAL_BUTTON_CLASSES = classNames( + "border-transparent bg-signal text-signal-contrast hover:bg-signal-hover hover:text-signal-hover-contrast active:bg-signal-active active:text-signal-active-contrast disabled:bg-signal-disabled disabled:text-signal-disabled-contrast" +); + +const createButtonClasses = (variant?: ButtonVariant) => + classNames(BASE_BUTTON_CLASSES, { + [DEFAULT_BUTTON_CLASSES]: !variant, + [PRIMARY_BUTTON_CLASSES]: variant === "primary", + [SECONDARY_BUTTON_CLASSES]: variant === "secondary", + [TERTIARY_BUTTON_CLASSES]: variant === "tertiary", + [SIGNAL_BUTTON_CLASSES]: variant === "signal", + }); + +type BaseButtonProps = { + variant: ButtonVariant; +}; + +type ButtonProps = BaseButtonProps & ButtonHTMLAttributes; + +/** + * Styled html button + */ +export const Button = React.forwardRef( + ({ className, variant, children, ...props }, ref) => ( + + ) +); + +type LinkButtonProps = BaseButtonProps & ReactRouterLinkProps; + +/** + * Styled react router link + */ +export const LinkButton = React.forwardRef( + ({ className, variant, children, ...props }, ref) => ( + + {children} + + ) +); + +type ExternalLinkButtonProps = BaseButtonProps & AnchorHTMLAttributes; + +/** + * Styled html anchor + */ +export const ExternalLinkButton = React.forwardRef( + ({ className, variant, children, ...props }, ref) => ( + + {children} + + ) +); diff --git a/scm-ui/ui-buttons/src/image-snapshot.test.ts b/scm-ui/ui-buttons/src/image-snapshot.test.ts new file mode 100644 index 0000000000..d480f887d8 --- /dev/null +++ b/scm-ui/ui-buttons/src/image-snapshot.test.ts @@ -0,0 +1,33 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +import path from "path"; +import initStoryshots from "@storybook/addon-storyshots"; +import { imageSnapshot } from "@storybook/addon-storyshots-puppeteer"; + +initStoryshots({ + suite: "Image snapshots", + test: imageSnapshot({ + storybookUrl: `file://${path.resolve(__dirname, "../storybook-static")}`, + }) +}); diff --git a/scm-ui/ui-buttons/src/index.css b/scm-ui/ui-buttons/src/index.css new file mode 100644 index 0000000000..7332595077 --- /dev/null +++ b/scm-ui/ui-buttons/src/index.css @@ -0,0 +1,26 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +@tailwind components; +@tailwind utilities; diff --git a/scm-ui/ui-buttons/src/index.ts b/scm-ui/ui-buttons/src/index.ts new file mode 100644 index 0000000000..be546f0875 --- /dev/null +++ b/scm-ui/ui-buttons/src/index.ts @@ -0,0 +1,27 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import "./index.css"; + +export { Button, LinkButton, ExternalLinkButton, ButtonVariants } from "./button"; diff --git a/scm-ui/ui-buttons/tailwind.config.js b/scm-ui/ui-buttons/tailwind.config.js new file mode 100644 index 0000000000..5801157821 --- /dev/null +++ b/scm-ui/ui-buttons/tailwind.config.js @@ -0,0 +1,34 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const path = require("path"); + +module.exports = { + presets: [ + // eslint-disable-next-line global-require,import/no-extraneous-dependencies + require("@scm-manager/ui-styles/src/tailwind.config.preset"), + ], + content: [path.join(__dirname, "src/**/*.tsx")], + important: true, +}; diff --git a/scm-ui/ui-buttons/tsconfig.json b/scm-ui/ui-buttons/tsconfig.json new file mode 100644 index 0000000000..cb06a28de9 --- /dev/null +++ b/scm-ui/ui-buttons/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "@scm-manager/tsconfig", + "include": [ + "./src", + "./docs" + ] +} diff --git a/scm-ui/ui-components/package.json b/scm-ui/ui-components/package.json index 282af272ed..6e0e959934 100644 --- a/scm-ui/ui-components/package.json +++ b/scm-ui/ui-components/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@scm-manager/babel-preset": "^2.13.1", - "@scm-manager/eslint-config": "^2.15.1", + "@scm-manager/eslint-config": "^2.16.0", "@scm-manager/jest-preset": "^2.13.0", "@scm-manager/prettier-config": "^2.10.1", "@scm-manager/tsconfig": "^2.13.0", @@ -109,4 +109,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/scm-ui/ui-components/src/OverviewPageActions.tsx b/scm-ui/ui-components/src/OverviewPageActions.tsx index 18c8959975..c1a3ac81db 100644 --- a/scm-ui/ui-components/src/OverviewPageActions.tsx +++ b/scm-ui/ui-components/src/OverviewPageActions.tsx @@ -23,9 +23,10 @@ */ import React, { FC, useState } from "react"; import { useHistory, useLocation } from "react-router-dom"; -import classNames from "classnames"; -import { Button, urls } from "./index"; +import { urls } from "./index"; import { FilterInput, Select } from "./forms"; +import { ButtonVariants, LinkButton } from "@scm-manager/ui-buttons"; +import classNames from "classnames"; type Props = { showCreateButton: boolean; @@ -54,7 +55,7 @@ const OverviewPageActions: FC = ({ label, testId, searchPlaceholder, - groupAriaLabelledby + groupAriaLabelledby, }) => { const history = useHistory(); const location = useLocation(); @@ -66,7 +67,7 @@ const OverviewPageActions: FC = ({ - - - One - -
- -
- - -
+
+
- - - -
- -
-

- Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows +

+ +
+ + + +
+

+ Mind-paralyzing change needed improbability vortex machine sorts sought same theory upending job just allows hostess’s really oblong Infinite Improbability thing into the starship against which behavior accordance.with Kakrafoon humanoid undergarment ship powered by GPP-guided bowl of petunias nothing was frequently away incredibly ordinary mob. -

-
-
-