Integrate tailwind css and create new button library (#2098)

Introduce tailwind as new frontend styling library to replace bulma in the longer run. Also create the first new ui library `ui-buttons` which will be the new standard for buttons ins SCM-Manager. In this library we reconsidered which types of buttons should be used to create a clean and consistent ui.

Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
Konstantin Schaper
2022-08-02 08:39:37 +02:00
committed by GitHub
parent 09beb8cd3b
commit 27dbcbf28d
67 changed files with 8592 additions and 7519 deletions

1
.gitignore vendored
View File

@@ -40,3 +40,4 @@ rebel.xml
.cache
.turbo
storybook-static

View File

@@ -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": [

View File

@@ -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

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -279,6 +279,7 @@ license {
exclude '**/*.snap'
exclude '**/*.iml'
exclude '**/.babelrc'
exclude '**/storybook-static/**'
tasks {
modules {

View File

@@ -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"
}
}
}

View File

@@ -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"
}
}
}

View File

@@ -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;

View File

@@ -0,0 +1,4 @@
{
"presets": ["@scm-manager/babel-preset"],
"plugins": ["@babel/plugin-syntax-dynamic-import"]
}

View File

@@ -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

View File

@@ -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;

View File

@@ -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;
},
};

View File

@@ -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.
-->
<link id="ui-theme" data-theme="light" rel="stylesheet" type="text/css" href="/ui-theme-light.css">

View File

@@ -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" },
],
}
};

View File

@@ -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}")],
};

View File

@@ -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;

View File

@@ -0,0 +1,64 @@
import { Meta } from "@storybook/addon-docs";
import { Button } from "../src";
<Meta title="Introduction"/>
# 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.
<table>
<thead>
<tr>
<th>Emphasis</th>
<th>Button Variant</th>
<th>Usage Examples</th>
</tr>
</thead>
<tbody>
<tr>
<td>Very High</td>
<td><Button variant="signal" className="w-full">Signal</Button></td>
<td>Destructive actions</td>
</tr>
<tr>
<td>High</td>
<td><Button variant="primary" className="w-full">Primary</Button></td>
<td>Form submit</td>
</tr>
<tr>
<td>Normal</td>
<td><Button variant="secondary" className="w-full">Secondary</Button></td>
<td>Cancel action in dialog</td>
</tr>
<tr>
<td>Low</td>
<td><Button variant="tertiary" className="w-full">Tertiary</Button></td>
<td>Circumstantially relevant action on page with many actionable elements</td>
</tr>
</tbody>
</table>
## 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.

View File

@@ -0,0 +1,22 @@
import { Meta, Story } from "@storybook/addon-docs";
import { Button } from "../src";
<Meta title="Usage" parameters={{
storyshots: { disable: true }
}} />
In confirmation dialogs, there are two actions.<br/>
One to cancel the current process and one to confirm it.<br/>
Aborting is always the secondary action, confirmation always the primary.
Focus is always on the cancelling action.
<Story name="Confirmation Dialog">
<div className="max-w-2xl rounded border p-4">
<h4 className="mb-2 font-bold">Delete User</h4>
<p>Do you really want to delete this user ?</p>
<div className="flex justify-end gap-2">
<Button variant="secondary">Cancel</Button>
<Button variant="primary">Delete</Button>
</div>
</div>
</Story>

View File

@@ -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"
}
}

View File

@@ -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: {},
},
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@@ -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/,
});

View File

@@ -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> = T extends React.ComponentType<infer U> ? 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<ExtractProps<typeof ButtonComponent>> = (args) => <ButtonComponent {...args} />;
const LinkButtonTemplate: StoryFn<ExtractProps<typeof LinkButtonComponent>> = (args) => (
<LinkButtonComponent {...args} />
);
const ExternalLinkButtonTemplate: StoryFn<ExtractProps<typeof ExternalLinkButtonComponent>> = (args) => (
<ExternalLinkButtonComponent {...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,
};

View File

@@ -0,0 +1,73 @@
import { Meta, Story } from "@storybook/addon-docs";
import { Button, ButtonVariantList } from "./button";
<Meta title="Tests"/>
<Story name="Light States" parameters={{
pseudo: {
hover: ButtonVariantList.map(variant => `#${variant}-Hover`),
focus: ButtonVariantList.map(variant => `#${variant}-Focus`),
active: ButtonVariantList.map(variant => `#${variant}-Active`),
},
themes: {
default: 'light',
},
}}>
<table className="border-separate border-spacing-4">
<tr>
<th>STATE</th>
{ButtonVariantList.map(variant => <th>{variant.toUpperCase()}</th>)}
</tr>
{["Normal", "Hover", "Active", "Focus", "Disabled"].map(state => <tr>
<td>{state}</td>
{ButtonVariantList.map(variant => <td><Button id={`${variant}-${state}`} disabled={state === "Disabled"}
variant={variant}>Button</Button></td>)}
</tr>)}
</table>
</Story>
<Story name="Dark States" parameters={{
pseudo: {
hover: ButtonVariantList.map(variant => `#${variant}-Hover`),
focus: ButtonVariantList.map(variant => `#${variant}-Focus`),
active: ButtonVariantList.map(variant => `#${variant}-Active`),
},
themes: {
default: 'dark',
},
}}>
<table className="border-separate border-spacing-4">
<tr>
<th>STATE</th>
{ButtonVariantList.map(variant => <th>{variant.toUpperCase()}</th>)}
</tr>
{["Normal", "Hover", "Active", "Focus", "Disabled"].map(state => <tr>
<td>{state}</td>
{ButtonVariantList.map(variant => <td><Button id={`${variant}-${state}`} disabled={state === "Disabled"}
variant={variant}>Button</Button></td>)}
</tr>)}
</table>
</Story>
<Story name="High-Contrast States" parameters={{
pseudo: {
hover: ButtonVariantList.map(variant => `#${variant}-Hover`),
focus: ButtonVariantList.map(variant => `#${variant}-Focus`),
active: ButtonVariantList.map(variant => `#${variant}-Active`),
},
themes: {
default: 'highcontrast',
},
}}>
<table className="border-separate border-spacing-4">
<tr>
<th>STATE</th>
{ButtonVariantList.map(variant => <th>{variant.toUpperCase()}</th>)}
</tr>
{["Normal", "Hover", "Active", "Focus", "Disabled"].map(state => <tr>
<td>{state}</td>
{ButtonVariantList.map(variant => <td><Button id={`${variant}-${state}`} disabled={state === "Disabled"}
variant={variant}>Button</Button></td>)}
</tr>)}
</table>
</Story>

View File

@@ -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<HTMLButtonElement>;
/**
* Styled html button
*/
export const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, children, ...props }, ref) => (
<button {...props} className={classNames(createButtonClasses(variant), className)} ref={ref}>
{children}
</button>
)
);
type LinkButtonProps = BaseButtonProps & ReactRouterLinkProps;
/**
* Styled react router link
*/
export const LinkButton = React.forwardRef<HTMLAnchorElement, LinkButtonProps>(
({ className, variant, children, ...props }, ref) => (
<ReactRouterLink {...props} className={classNames(createButtonClasses(variant), className)} ref={ref}>
{children}
</ReactRouterLink>
)
);
type ExternalLinkButtonProps = BaseButtonProps & AnchorHTMLAttributes<HTMLAnchorElement>;
/**
* Styled html anchor
*/
export const ExternalLinkButton = React.forwardRef<HTMLAnchorElement, ExternalLinkButtonProps>(
({ className, variant, children, ...props }, ref) => (
<a {...props} className={classNames(createButtonClasses(variant), className)} ref={ref}>
{children}
</a>
)
);

View File

@@ -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")}`,
})
});

View File

@@ -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;

View File

@@ -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";

View File

@@ -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,
};

View File

@@ -0,0 +1,7 @@
{
"extends": "@scm-manager/tsconfig",
"include": [
"./src",
"./docs"
]
}

View File

@@ -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"
}
}
}

View File

@@ -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<Props> = ({
label,
testId,
searchPlaceholder,
groupAriaLabelledby
groupAriaLabelledby,
}) => {
const history = useHistory();
const location = useLocation();
@@ -66,7 +67,7 @@ const OverviewPageActions: FC<Props> = ({
<Select
ariaLabelledby={groupAriaLabelledby}
className="is-fullwidth"
options={groups.map(g => ({ value: g, label: g }))}
options={groups.map((g) => ({ value: g, label: g }))}
value={currentGroup}
onChange={groupSelected}
/>
@@ -76,8 +77,10 @@ const OverviewPageActions: FC<Props> = ({
const renderCreateButton = () => {
if (showCreateButton) {
return (
<div className={classNames("input-button", "control", "column")}>
<Button label={label} link={createLink || `${link}create/`} color="primary" />
<div className={classNames("control", "column")}>
<LinkButton variant={ButtonVariants.PRIMARY} to={createLink || `${link}create/`}>
{label}
</LinkButton>
</div>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -29,7 +29,6 @@ import Button from "../buttons/Button";
import { MemoryRouter } from "react-router-dom";
import { useForm } from "react-hook-form";
import { SubmitButton } from "../buttons";
import { Person } from "@scm-manager/ui-types";
const Decorator = styled.div`
padding: 2rem;
@@ -74,9 +73,9 @@ const ReactHookForm: FC = () => {
handleSubmit,
formState: { errors },
} = useForm<Name>();
const [stored, setStored] = useState<Person>();
const [stored, setStored] = useState<Name>();
const onSubmit = (person: Person) => {
const onSubmit = (person: Name) => {
setStored(person);
};

View File

@@ -25,7 +25,7 @@
import { nameRegex } from "../validation";
import { TFunction } from "i18next";
import { AstPlugin } from "./PluginApi";
import { Node, Parent } from "unist";
import { Literal, Node, Parent } from "unist";
const namePartRegex = nameRegex.source.substring(1, nameRegex.source.length - 1).replace(/\[\^([^\]s]+)\]/, "[^$1\\s]");
@@ -45,11 +45,11 @@ function match(value: string): RegExpMatchArray[] {
export const createTransformer = (t: TFunction): AstPlugin => {
return ({ visit }) => {
visit("text", (node: Node, index: number, parent?: Parent) => {
if (!parent || parent.type === "link" || !node.value) {
if (!parent || parent.type === "link" || !(node as Literal).value) {
return;
}
let nodeText = node.value as string;
let nodeText = (node as Literal).value as string;
const matches = match(nodeText);
if (matches.length > 0) {
@@ -92,7 +92,7 @@ export const createTransformer = (t: TFunction): AstPlugin => {
parent.children[index] = {
type: "text",
children,
};
} as Node;
}
});
};

View File

@@ -23,7 +23,7 @@
*/
import { AstPlugin } from "./PluginApi";
import { Node, Parent } from "unist";
import { Literal, Node, Parent } from "unist";
/**
* Some existing remark plugins (e.g. changesetShortLinkParser or the plugin for issue tracker links) create
@@ -54,8 +54,8 @@ import { Node, Parent } from "unist";
export const createTransformer = (): AstPlugin => {
return ({ visit }) => {
visit("text", (node: Node, index: number, parent?: Parent) => {
if (node.value === undefined && Array.isArray(node.children) && node.children.length > 0) {
const children = node.children;
if ((node as Literal).value === undefined && Array.isArray((node as Parent).children) && (node as Parent).children.length > 0) {
const children = (node as Parent).children;
const preChildren = parent?.children.slice(0, index) || [];
const postChildren = parent?.children.slice(index + 1) || [];
parent!.children = [...preChildren, ...children, ...postChildren];

View File

@@ -50,8 +50,8 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
const sortFunctions: Comparator | undefined[] = [];
React.Children.forEach(children, (child, index) => {
if (child && isSortable(child)) {
sortFunctions.push(child.props.createComparator(child.props, index));
if (child && isSortable(child as ReactElement)) {
sortFunctions.push((child as ReactElement).props.createComparator((child as ReactElement).props, index));
} else {
sortFunctions.push(undefined);
}
@@ -61,9 +61,9 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
return (
<tr key={rowIndex}>
{React.Children.map(children, (child, columnIndex) => {
const { className: columnClassName, ...childProperties } = child.props;
const { className: columnClassName, ...childProperties } = (child as ReactElement).props;
return (
<td className={columnClassName}>{React.cloneElement(child, { ...childProperties, columnIndex, row })}</td>
<td className={columnClassName}>{React.cloneElement((child as ReactElement), { ...childProperties, columnIndex, row })}</td>
);
})}
</tr>
@@ -112,14 +112,14 @@ const Table: FC<Props> = ({ data, sortable, children, emptyMessage, className })
<tr>
{React.Children.map(children, (child, index) => (
<th
className={isSortable(child) && "is-clickable"}
onClick={isSortable(child) ? () => tableSort(index) : undefined}
className={isSortable((child as ReactElement)) && "is-clickable"}
onClick={isSortable((child as ReactElement)) ? () => tableSort(index) : undefined}
onMouseEnter={() => setHoveredColumnIndex(index)}
onMouseLeave={() => setHoveredColumnIndex(undefined)}
key={index}
>
{child.props.header}
{isSortable(child) && renderSortIcon(child, ascending, shouldShowIcon(index))}
{(child as ReactElement).props.header}
{isSortable((child as ReactElement)) && renderSortIcon((child as ReactElement), ascending, shouldShowIcon(index))}
</th>
))}
</tr>

View File

@@ -22,7 +22,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",
@@ -46,4 +46,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -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",
"@types/react": "^17.0.1",
@@ -43,4 +43,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -18,7 +18,7 @@
"@scm-manager/babel-preset": "^2.13.1",
"@scm-manager/jest-preset": "^2.13.0",
"@scm-manager/prettier-config": "^2.10.1",
"@scm-manager/eslint-config": "^2.15.1",
"@scm-manager/eslint-config": "^2.16.0",
"tsup": "^5.12.6"
},
"babel": {
@@ -36,4 +36,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -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/plugin-scripts": "^1.2.2",
"@scm-manager/prettier-config": "^2.10.1",
@@ -43,4 +43,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -10,6 +10,7 @@
"ui-scripts": "./bin/ui-scripts.js"
},
"dependencies": {
"@scm-manager/ui-styles": "2.37.3-SNAPSHOT",
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
"babel-loader": "^8.2.3",
"css-loader": "^6.5.0",
@@ -24,11 +25,15 @@
"style-loader": "^3.3.1",
"webpack": "^5.60.0",
"webpack-cli": "^4.9.1",
"webpack-dev-server": "^4.4.0"
"webpack-dev-server": "^4.4.0",
"tailwindcss": "^3.0.23",
"postcss": "^8.4.12",
"postcss-loader": "^6.2.1",
"autoprefixer": "^10.4.4"
},
"devDependencies": {
"@scm-manager/babel-preset": "^2.13.1",
"@scm-manager/eslint-config": "^2.15.1",
"@scm-manager/eslint-config": "^2.16.0",
"@scm-manager/prettier-config": "^2.10.1",
"webpack-bundle-analyzer": "^4.5.0"
},
@@ -41,4 +46,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -0,0 +1,41 @@
/*
* 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 root = path.resolve(process.cwd(), "scm-ui");
const sizes = [0, 1, 2, 3, 4, 5, 6, "auto"];
const helpers = ["m", "p"];
const variants = ["", "x", "y", "t", "r", "l", "b"];
const bulmaHelpers = helpers
.map((helper) => sizes.map((size) => variants.map((variant) => `${helper}${variant}-${size}`)))
.flat(3);
module.exports = {
// eslint-disable-next-line global-require
presets: [require("@scm-manager/ui-styles/src/tailwind.config.preset")],
content: [path.join(root, "ui-webapp", "src", "**", "*.tsx")],
safelist: bulmaHelpers,
};

View File

@@ -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;

View File

@@ -33,7 +33,13 @@ const isDevelopment = process.env.NODE_ENV === "development";
const root = path.resolve(process.cwd(), "..");
const babelPlugins = [];
const webpackPlugins = [];
const webpackPlugins = [
new MiniCssExtractPlugin({
filename: "webapp.tailwind.css",
chunkFilename: "webapp.tailwind.css",
ignoreOrder: false,
}),
];
if (process.env.ANALYZE_BUNDLES === "true") {
// it is ok to use require here, because we want to load the package conditionally
@@ -82,6 +88,7 @@ module.exports = [
// enable async/await
"regenerator-runtime/runtime",
"./ui-webapp/src/index.tsx",
"./ui-scripts/src/tailwind.css",
],
},
devtool: "eval-cheap-module-source-map",
@@ -101,8 +108,40 @@ module.exports = [
},
],
},
{
test: /tailwind\.css$/i,
exclude: /node_modules/,
use: [
{
loader: MiniCssExtractPlugin.loader,
},
{
loader: "css-loader",
options: {
importLoaders: 1,
},
},
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
[
"tailwindcss",
{
config: path.join(root, "ui-scripts", "src", "tailwind.config.js"),
},
],
["autoprefixer", {}],
],
},
},
},
],
},
{
test: /\.(css|scss|sass)$/i,
exclude: /tailwind\.css$/i,
use: [
// Creates `style` nodes from JS strings
"style-loader",

View File

@@ -15,7 +15,7 @@
"react-diff-view": "^2.4.10"
},
"devDependencies": {
"@scm-manager/eslint-config": "^2.15.1",
"@scm-manager/eslint-config": "^2.16.0",
"@scm-manager/prettier-config": "^2.11.1",
"css-loader": "^6.5.0",
"html-webpack-plugin": "^5.5.0",
@@ -31,4 +31,4 @@
"eslintConfig": {
"extends": "@scm-manager/eslint-config"
}
}
}

View File

@@ -229,6 +229,23 @@ $danger-25: scale-color($danger, $lightness: -75%);
--diff-code-delete-edit-background-color: #{desaturate(#000, 20%)};
--diff-code-selected-background-color: #{desaturate(#fffce0, 20%)};
--diff-omit-gutter-line-color: #cb2a1d;
// Tailwind
--scm-primary-contrast-color: #{lighten($text-strong, 2.5%)};
--scm-primary-hover-color: #{darken($primary, 5%)};
--scm-primary-hover-contrast-color: #{lighten($text-strong, 2.5%)};
--scm-primary-active-color: #{darken($primary, 10%)};
--scm-primary-active-contrast-color: #{lighten($text-strong, 2.5%)};
--scm-primary-disabled-color: #{rgba($primary, 0.5)};
--scm-primary-disabled-contrast-color: #{rgba(lighten($text-strong, 2.5%), 0.5)};
--scm-warning-contrast-color: #88550D;
--scm-warning-hover-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-hover-color: #{darken($warning, 10%)};
--scm-warning-active-color: #{darken($warning, 20%)};
--scm-warning-active-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-disabled-color: #{rgba($warning, 0.5)};
--scm-warning-disabled-contrast-color: #{rgba(0,0,0,0.7)};
}
.menu-list {

View File

@@ -127,6 +127,23 @@ $tooltip-color: $scheme-main;
--diff-code-delete-edit-background-color: #000;
--diff-code-selected-background-color: #fffce0;
--diff-omit-gutter-line-color: #cb2a1d;
// Tailwind
--scm-primary-contrast-color: #{$black-ter};
--scm-primary-hover-color: #{darken($primary, 5%)};
--scm-primary-hover-contrast-color: #{$black-ter};
--scm-primary-active-color: #{darken($primary, 10%)};
--scm-primary-active-contrast-color: #{$black-ter};
--scm-primary-disabled-color: #006970;
--scm-primary-disabled-contrast-color: #{$black-ter};
--scm-warning-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-hover-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-hover-color: #{darken($warning, 5%)};
--scm-warning-active-color: #{darken($warning, 10%)};
--scm-warning-active-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-disabled-color: #{darken($warning, 50%)};
--scm-warning-disabled-contrast-color: #{rgba(0,0,0,0.7)};
}
.button {

View File

@@ -78,6 +78,23 @@ $popover-background-color: $grey-light;
--sh-selected-color: #{$warning-25};
--sh-highlight-background: #f5f5f5;
--sh-highlight-accent: #99d8f3;
// Tailwind
--scm-primary-contrast-color: #{$white};
--scm-primary-hover-color: #{darken($primary, 10%)};
--scm-primary-hover-contrast-color: #{$white};
--scm-primary-active-color: #{darken($primary, 20%)};
--scm-primary-active-contrast-color: #{$white};
--scm-primary-disabled-color: #bff3f7;
--scm-primary-disabled-contrast-color: #{$white};
--scm-warning-contrast-color: #88550D;
--scm-warning-hover-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-hover-color: #{darken($warning, 10%)};
--scm-warning-active-color: #{darken($warning, 20%)};
--scm-warning-active-contrast-color: #{rgba(0,0,0,0.7)};
--scm-warning-disabled-color: #fff6d5;
--scm-warning-disabled-contrast-color: #e1d4c2;
}
.button {

View File

@@ -0,0 +1,55 @@
/*
* 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 = {
theme: {
extend: {
colors: {
primary: {
DEFAULT: "var(--scm-primary-color)",
contrast: "var(--scm-primary-contrast-color)",
hover: "var(--scm-primary-hover-color)",
"hover-contrast": "var(--scm-primary-hover-contrast-color)",
active: "var(--scm-primary-active-color)",
"active-contrast": "var(--scm-primary-active-contrast-color)",
disabled: "var(--scm-primary-disabled-color)",
"disabled-contrast": "var(--scm-primary-disabled-contrast-color)",
},
signal: {
DEFAULT: "var(--scm-warning-color)",
contrast: "var(--scm-warning-contrast-color)",
hover: "var(--scm-warning-hover-color)",
"hover-contrast": "var(--scm-warning-hover-contrast-color)",
active: "var(--scm-warning-active-color)",
"active-contrast": "var(--scm-warning-active-contrast-color)",
disabled: "var(--scm-warning-disabled-color)",
"disabled-contrast": "var(--scm-warning-disabled-contrast-color)",
},
},
},
},
important: true,
corePlugins: {
preflight: false,
}
};

View File

@@ -21,7 +21,21 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
@import "bulma/bulma";
@import "bulma/sass/utilities/_all";
@import "bulma/sass/base/_all";
@import "bulma/sass/elements/_all";
@import "bulma/sass/form/_all";
@import "bulma/sass/components/_all";
@import "bulma/sass/grid/_all";
@import "bulma/sass/helpers/color";
@import "bulma/sass/helpers/flexbox";
@import "bulma/sass/helpers/float";
@import "bulma/sass/helpers/other";
@import "bulma/sass/helpers/overflow";
@import "bulma/sass/helpers/position";
@import "bulma/sass/helpers/typography";
@import "bulma/sass/helpers/visibility";
@import "bulma/sass/layout/_all";
@import "../variables/_derived.scss";
@import "bulma-popover/css/bulma-popover";
@import "../components/_main.scss";

View File

@@ -24,7 +24,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",
@@ -54,4 +54,4 @@
"publishConfig": {
"access": "restricted"
}
}
}

View File

@@ -40,6 +40,7 @@ import type {
TokenizeSuccessResponse,
} from "../types";
import { isRefractorElement } from "../types";
import type { RefractorElement } from "refractor";
// the WorkerGlobalScope is assigned to self
// see https://developer.mozilla.org/en-US/docs/Web/API/WorkerGlobalScope/self
@@ -91,7 +92,7 @@ const countChildrenAndApplyMarkers = (node: RefractorNode, markedTexts?: string[
}
if (newChildren.length > 0) {
const el = node as RefractorNode;
const el = node as unknown as RefractorElement;
el.type = "element";
el.tagName = "span";
el.children = newChildren;

View File

@@ -23,7 +23,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",
@@ -51,4 +51,4 @@
"publishConfig": {
"access": "restricted"
}
}
}

View File

@@ -15,7 +15,7 @@
"lint": "eslint src"
},
"devDependencies": {
"@scm-manager/eslint-config": "^2.15.1",
"@scm-manager/eslint-config": "^2.16.0",
"@scm-manager/tsconfig": "^2.13.0"
},
"babel": {
@@ -30,4 +30,4 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -10,6 +10,7 @@
"@scm-manager/ui-modules": "2.37.3-SNAPSHOT",
"@scm-manager/ui-syntaxhighlighting": "2.37.3-SNAPSHOT",
"@scm-manager/ui-text": "2.37.3-SNAPSHOT",
"@scm-manager/ui-buttons": "2.37.3-SNAPSHOT",
"@scm-manager/ui-legacy": "2.37.3-SNAPSHOT",
"classnames": "^2.2.5",
"history": "^4.10.1",
@@ -35,7 +36,7 @@
"build": "webpack-cli --mode=production --config=../ui-scripts/src/webpack.config.js"
},
"devDependencies": {
"@scm-manager/eslint-config": "^2.15.1",
"@scm-manager/eslint-config": "^2.16.0",
"@scm-manager/jest-preset": "^2.13.0",
"@scm-manager/ui-tests": "2.37.3-SNAPSHOT",
"@testing-library/react": "^12.1.5",
@@ -49,7 +50,8 @@
"@types/styled-components": "^5.1.25",
"@types/systemjs": "^0.20.6",
"fetch-mock": "^7.5.1",
"react-test-renderer": "^17.0.1"
"react-test-renderer": "^17.0.1",
"tailwindcss": "^3.0.23"
},
"babel": {
"presets": [
@@ -66,4 +68,5 @@
"publishConfig": {
"access": "public"
}
}
}

View File

@@ -44,6 +44,7 @@
}
document.head.appendChild(linkElement);
</script>
<link rel="stylesheet" href="{{ contextPath }}/styles.bundle.css">
</head>
<body>
<noscript>

View File

@@ -37,6 +37,9 @@ import ChangesetShortLink from "./repos/components/changesets/ChangesetShortLink
import "./tokenExpired";
import { ApiProvider } from "@scm-manager/ui-api";
// eslint-disable-next-line no-restricted-imports
import "@scm-manager/ui-buttons/build/index.css";
binder.bind<extensionPoints.ChangesetDescriptionTokens>("changeset.description.tokens", ChangesetShortLink);
const root = document.getElementById("root");

View File

@@ -8,7 +8,8 @@ com.cloudogu.legman.support:micrometer:2.0.0=compileClasspath,compileClasspathCo
com.cloudogu.legman.support:shiro:2.0.0=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.cloudogu.legman:core:2.0.0=annotationProcessor,annotationProcessorCopy,compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.cloudogu.spotter:spotter-core:4.0.0=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.cronutils:cron-utils:9.1.6=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.cronutils:cron-utils:9.1.6=default
com.cronutils:cron-utils:9.1.8=compileClasspath,compileClasspathCopy,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.damnhandy:handy-uri-templates:2.1.7=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
com.fasterxml.jackson.core:jackson-annotations:2.12.1=swaggerDeps,swaggerDepsCopy
com.fasterxml.jackson.core:jackson-annotations:2.12.3=default
@@ -146,7 +147,8 @@ org.checkerframework:checker-qual:3.5.0=annotationProcessor,annotationProcessorC
org.eclipse.microprofile.config:microprofile-config-api:2.0=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.glassfish.jaxb:jaxb-runtime:2.3.3=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.glassfish.jaxb:txw2:2.3.3=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.glassfish:javax.el:3.0.1-b11=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.glassfish:jakarta.el:3.0.4=compileClasspath,compileClasspathCopy,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.glassfish:javax.el:3.0.1-b11=default,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.hamcrest:hamcrest-core:2.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.hamcrest:hamcrest-library:2.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.hamcrest:hamcrest:2.1=testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
@@ -156,8 +158,8 @@ org.jacoco:org.jacoco.agent:0.8.7=jacocoAgentCopy,jacocoAntCopy
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.javassist:javassist:3.25.0-GA=swaggerDeps,swaggerDepsCopy
org.javassist:javassist:3.27.0-GA=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.javassist:javassist:3.25.0-GA=compileClasspath,compileClasspathCopy,swaggerDeps,swaggerDepsCopy
org.javassist:javassist:3.27.0-GA=default
org.jboss.logging:jboss-logging:3.4.2.Final=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.jboss.resteasy:resteasy-client-api:4.7.5.Final=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy
org.jboss.resteasy:resteasy-client:4.7.5.Final=compileClasspath,compileClasspathCopy,default,defaultCopy,runtimeClasspath,runtimeClasspathCopy,testCompileClasspath,testCompileClasspathCopy,testRuntimeClasspath,testRuntimeClasspathCopy

View File

@@ -40,7 +40,7 @@ import javax.inject.Provider;
public class PushStateDispatcherProvider implements Provider<PushStateDispatcher> {
@VisibleForTesting
static final String PROPERTY_TARGET = "sonia.scm.ui.proxy";
public static final String PROPERTY_TARGET = "sonia.scm.ui.proxy";
private Provider<TemplatingPushStateDispatcher> templatingPushStateDispatcherProvider;

View File

@@ -0,0 +1,111 @@
/*
* 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.
*/
package sonia.scm.web.tailwind;
import com.github.sdorra.webresources.CacheControl;
import com.github.sdorra.webresources.WebResourceSender;
import com.google.common.io.ByteStreams;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.filter.WebElement;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.plugin.UberWebResourceLoader;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import static sonia.scm.PushStateDispatcherProvider.PROPERTY_TARGET;
@Singleton
@WebElement(value = "/styles.bundle.css")
public class StylesServlet extends HttpServlet {
private static final Logger LOG = LoggerFactory.getLogger(StylesServlet.class);
private final UberWebResourceLoader webResourceLoader;
private final String target = System.getProperty(PROPERTY_TARGET);
private final WebResourceSender sender = WebResourceSender.create()
.withGZIP()
.withGZIPMinLength(512)
.withBufferSize(16384)
.withCacheControl(CacheControl.create().noCache());
@Inject
public StylesServlet(PluginLoader pluginLoader) {
this.webResourceLoader = pluginLoader.getUberWebResourceLoader();
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try {
URL url = webResourceLoader.getResource("/assets/webapp.tailwind.css");
if (url != null) {
// TODO: Merge css
sender.resource(url).get(request, response);
} else {
getLocally(createProxyUrl("/assets/webapp.tailwind.css"), request, response);
}
} catch (IOException ex) {
LOG.error("Error on getting the tailwind stylesheet", ex);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
private void getLocally(URL url, HttpServletRequest request, HttpServletResponse response) throws IOException {
HttpURLConnection connection = openConnection(url);
connection.setRequestMethod(request.getMethod());
int responseCode = connection.getResponseCode();
response.setStatus(responseCode);
try (InputStream input = getConnectionInput(connection); OutputStream output = response.getOutputStream()) {
ByteStreams.copy(input, output);
}
}
private InputStream getConnectionInput(HttpURLConnection connection) throws IOException {
if (connection.getErrorStream() != null) {
return connection.getErrorStream();
}
return connection.getInputStream();
}
private URL createProxyUrl(String uri) throws MalformedURLException {
return new URL(target + uri);
}
private static HttpURLConnection openConnection(URL url) throws IOException {
return (HttpURLConnection) url.openConnection();
}
}

11323
yarn.lock

File diff suppressed because it is too large Load Diff