Move frontendcomponents into ui-core

The new architecture has been implemented.

Co-authored-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com>
Committed-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com>
Pushed-by: Tarik Gürsoy<tarik.guersoy@cloudogu.com>
Committed-by: Tarik Gürsoy<tarik.guersoy@cloudogu.com>
Pushed-by: Eduard Heimbuch<eduard.heimbuch@cloudogu.com>
Co-authored-by: Tarik Gürsoy<tarik.guersoy@cloudogu.com>


Reviewed-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
Tarik Gürsoy
2024-01-24 10:38:17 +01:00
parent 0cc3d3f598
commit 3ffbdd8d17
184 changed files with 1356 additions and 1535 deletions

View File

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

View File

@@ -1,57 +0,0 @@
/*
* 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

@@ -1,26 +0,0 @@
<!--
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

@@ -1,3 +0,0 @@
## Bulma
See Bulma element: https://bulma.io/documentation/elements/button

View File

@@ -22,10 +22,4 @@
* SOFTWARE.
*/
module.exports = {
stories: ["../src/**/*.stories.@(ts|tsx)"],
framework: "@storybook/react",
core: {
builder: "webpack5",
},
};
export { Icon, Button, LinkButton, ExternalLinkButton, ExternalLink, ButtonVariants } from "@scm-manager/ui-core";

View File

@@ -2,77 +2,14 @@
"name": "@scm-manager/ui-buttons",
"version": "3.0.0-SNAPSHOT",
"private": false,
"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"
},
"main": "index.ts",
"peerDependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.3.1",
"classnames": "^2.2.6",
"@scm-manager/ui-components": "3.0.0-SNAPSHOT"
"@scm-manager/ui-core": "3.0.0-SNAPSHOT"
},
"devDependencies": {
"@scm-manager/prettier-config": "^2.11.1",
"@scm-manager/ui-api": "3.0.0-SNAPSHOT",
"@scm-manager/eslint-config": "^2.17.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",
"babel-loader": "^8.2.4",
"postcss": "^8.4.12",
"postcss-loader": "^6.2.1",
"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": "11",
"i18next-fetch-backend": "^2.3.1",
"depcheck": "^1.4.3"
},
"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"
}
"@scm-manager/tsconfig": "^2.13.0"
},
"prettier": "@scm-manager/prettier-config",
"eslintConfig": {

View File

@@ -30,6 +30,10 @@ type Props = Omit<ComponentProps<typeof Notification>, "type" | "role"> & {
error: BackendError;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
const BackendErrorNotification: FC<Props> = ({ error, ...props }) => {
const [t] = useTranslation("plugins");

View File

@@ -50,6 +50,9 @@ const BasicErrorMessage: FC<Omit<ComponentProps<typeof Notification>, "type" | "
);
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
const ErrorNotification: FC<Props> = ({ error, ...props }) => {
const [t] = useTranslation("commons");
if (error) {

View File

@@ -31,6 +31,9 @@ type Props = {
className?: string;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
class Image extends React.Component<Props> {
createImageSrc = () => {
const { src } = this.props;

View File

@@ -40,6 +40,10 @@ const FixedSizedImage = styled(Image)`
height: 128px;
`;
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
class Loading extends React.Component<Props> {
render() {
const { message, t } = this.props;

View File

@@ -34,6 +34,10 @@ type Props = {
role?: string;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
const Notification: FC<Props> = ({ type = "info", onClose, className, children, role }) => {
const renderCloseButton = () => {
if (onClose) {

View File

@@ -71110,7 +71110,7 @@ exports[`Storyshots Repositories/RepositoryEntry Archived 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71238,7 +71238,7 @@ exports[`Storyshots Repositories/RepositoryEntry Avatar EP 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71354,7 +71354,7 @@ exports[`Storyshots Repositories/RepositoryEntry Before Title EP 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71473,7 +71473,7 @@ exports[`Storyshots Repositories/RepositoryEntry Default 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71589,7 +71589,7 @@ exports[`Storyshots Repositories/RepositoryEntry Exporting 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71717,7 +71717,7 @@ exports[`Storyshots Repositories/RepositoryEntry HealthCheck Failure 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71846,7 +71846,7 @@ exports[`Storyshots Repositories/RepositoryEntry MultiRepositoryTags 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -71986,7 +71986,7 @@ exports[`Storyshots Repositories/RepositoryEntry RepositoryFlag EP 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",
@@ -72115,7 +72115,7 @@ exports[`Storyshots Repositories/RepositoryEntry With long texts 1`] = `
</div>
</div>
<div
className="sc-iBkjds idsAPT is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
className="Card__RowsContainer-sc-16vnzfd-0 rKDzD is-flex is-flex-direction-column is-justify-content-center is-flex-grow-1 is-overflow-hidden is-overflow-wrap-anywhere"
style={
Object {
"gap": "0.25rem",

View File

@@ -26,6 +26,10 @@
// @ts-ignore scmStage is set on the index page
export const isDevBuild = () => (window.scmStage || "").toUpperCase() !== "PRODUCTION";
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
export const createAttributesForTesting = (testId?: string) => {
if (!testId) {
return undefined;

View File

@@ -31,6 +31,10 @@ type Props = {
right?: ReactNode;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
export default class Level extends React.Component<Props> {
render() {
const { className, left, children, right } = this.props;

View File

@@ -28,6 +28,10 @@ type Props = {
className?: string;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
class SubSubtitle extends React.Component<Props> {
render() {
const { className, children } = this.props;

View File

@@ -29,6 +29,10 @@ type Props = {
className?: string;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
const Subtitle: FC<Props> = ({ subtitle, className, children }) => {
if (subtitle) {
return <h2 className={classNames("subtitle", className)}>{subtitle}</h2>;

View File

@@ -31,6 +31,10 @@ type Props = {
className?: string;
};
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
const Title: FC<Props> = ({ title, preventRefreshingPageTitle, customPageTitle, className, children }) => {
useEffect(() => {
if (!preventRefreshingPageTitle) {

View File

@@ -22,10 +22,11 @@
* SOFTWARE.
*/
import { useMemo } from "react";
let counter = 0;
import { useAriaId } from "@scm-manager/ui-core";
/**
* @deprecated Please import the identical module from "@scm-manager/ui-core"
*/
export default function useGeneratedId(fallback?: string) {
return useMemo(() => fallback ?? `scm-id-${++counter}`, [fallback]);
return useAriaId(fallback);
}

View File

@@ -0,0 +1,3 @@
{
"presets": ["@scm-manager/babel-preset"]
}

View File

@@ -30,6 +30,7 @@ const ReactDOM = require("react-dom");
const root = path.resolve("..");
const themedir = path.join(root, "ui-styles", "src");
ReactDOM.createPortal = (node) => node;

View File

@@ -23,4 +23,3 @@
-->
<link id="ui-theme" data-theme="light" rel="stylesheet" type="text/css" href="/ui-theme-light.css">

View File

@@ -21,6 +21,7 @@
* 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";

View File

@@ -0,0 +1,81 @@
{
"name": "@scm-manager/ui-core",
"version": "3.0.0-SNAPSHOT",
"main": "./src/index.ts",
"license": "MIT",
"scripts": {
"storybook": "start-storybook -p 6006",
"build-storybook": "build-storybook"
},
"peerDependencies": {
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.3.1",
"classnames": "^2.3.1",
"react-hook-form": "7",
"react-i18next": "11",
"react-query": "3",
"styled-components": "5"
},
"dependencies": {
"@headlessui/react": "^1.7.15",
"@radix-ui/react-radio-group": "^1.1.3",
"@radix-ui/react-slot": "^1.0.1",
"@radix-ui/react-visually-hidden": "^1.0.3",
"@radix-ui/react-dialog": "1.0.4",
"@radix-ui/react-dropdown-menu": "2.0.5",
"@radix-ui/react-popover": "1.0.6",
"@radix-ui/react-tooltip": "1.0.2",
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
"mousetrap": "1.6.5"
},
"devDependencies": {
"@scm-manager/prettier-config": "^2.11.1",
"@scm-manager/eslint-config": "^2.17.0",
"@scm-manager/tsconfig": "^2.12.0",
"@scm-manager/babel-preset": "^2.13.1",
"@types/mousetrap": "1.6.5",
"@testing-library/react-hooks": "8.0.1",
"@testing-library/react": "12.1.5",
"@storybook/addon-actions": "^6.5.10",
"@storybook/addon-docs": "^6.5.14",
"@storybook/addon-essentials": "^6.5.10",
"@storybook/addon-interactions": "^6.5.10",
"@storybook/addon-links": "^6.5.10",
"@storybook/addon-a11y": "^6.5.10",
"storybook-addon-i18next": "^1.3.0",
"storybook-addon-pseudo-states": "^1.15.1",
"@storybook/builder-webpack5": "^6.5.10",
"@storybook/manager-webpack5": "^6.5.10",
"@storybook/react": "^6.5.10",
"@storybook/testing-library": "^0.0.13",
"@storybook/addon-storyshots-puppeteer": "^6.4.20",
"@storybook/addon-storyshots": "^6.4.20",
"storybook-addon-mock": "^3.2.0",
"storybook-addon-themes": "^6.1.0",
"storybook-react-router": "^1.0.8",
"mini-css-extract-plugin": "^1.6.2",
"html-webpack-plugin": "^5.5.0",
"webpack": "5",
"@babel/core": "^7.19.0",
"i18next": "^19.9.2",
"react-i18next": "11",
"i18next-fetch-backend": "^2.3.1",
"babel-loader": "^8.2.5",
"depcheck": "^1.4.3",
"jest-extended": "3.1.0"
},
"prettier": "@scm-manager/prettier-config",
"eslintConfig": {
"extends": "@scm-manager/eslint-config"
},
"publishConfig": {
"access": "public"
},
"jest": {
"setupFilesAfterEnv": [
"jest-extended/all"
]
}
}

View File

@@ -25,7 +25,7 @@
import React, { AnchorHTMLAttributes, ButtonHTMLAttributes } from "react";
import { Link as ReactRouterLink, LinkProps as ReactRouterLinkProps } from "react-router-dom";
import classNames from "classnames";
import { createAttributesForTesting } from "@scm-manager/ui-components";
import { createAttributesForTesting } from "../helpers";
/**
* @beta

View File

@@ -0,0 +1,64 @@
import { Meta } from "@storybook/addon-docs";
import { Button } from "../";
<Meta title="Introduction"/>
# Buttons
The `@scm-manager/ui-core` 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">Signal</Button></td>
<td>Destructive actions</td>
</tr>
<tr>
<td>High</td>
<td><Button variant="primary">Primary</Button></td>
<td>Form submit</td>
</tr>
<tr>
<td>Normal</td>
<td><Button variant="secondary">Secondary</Button></td>
<td>Cancel action in dialog</td>
</tr>
<tr>
<td>Low</td>
<td><Button variant="tertiary">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 "../";
<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>
<h4>Delete User</h4>
<p>Do you really want to delete this user ?</p>
<div>
<Button variant="secondary">Cancel</Button>
<Button variant="primary">Delete</Button>
</div>
</div>
</Story>

View File

@@ -26,7 +26,7 @@ import React, { useCallback, useEffect } from "react";
import { ScmFormPathContextProvider, useScmFormPathContext } from "./FormPathContext";
import { ScmFormContextProvider, useScmFormContext } from "./ScmFormContext";
import { DeepPartial, UseFieldArrayReturn, useForm, UseFormReturn } from "react-hook-form";
import { Button } from "@scm-manager/ui-buttons";
import { Button } from "../buttons";
import { prefixWithoutIndices } from "./helpers";
import { useScmFormListContext } from "./ScmFormListContext";
import { useTranslation } from "react-i18next";

View File

@@ -23,7 +23,7 @@
*/
import { useConfigLink } from "@scm-manager/ui-api";
import { Loading } from "@scm-manager/ui-components";
import { Loading } from "../misc";
import React, { ComponentProps } from "react";
import { HalRepresentation } from "@scm-manager/ui-types";
import Form from "./Form";

View File

@@ -24,10 +24,11 @@
import React, { FC, useCallback, useEffect, useState } from "react";
import { DeepPartial, SubmitHandler, useForm, UseFormReturn } from "react-hook-form";
import { ErrorNotification, Level } from "@scm-manager/ui-components";
import { ErrorNotification } from "../notifications";
import { Level } from "../misc";
import { ScmFormContextProvider } from "./ScmFormContext";
import { useTranslation } from "react-i18next";
import { Button } from "@scm-manager/ui-buttons";
import { Button } from "../buttons";
import styled from "styled-components";
import { setValues } from "./helpers";

View File

@@ -23,7 +23,7 @@
*/
import React from "react";
import { Tooltip } from "@scm-manager/ui-overlays";
import { Tooltip } from "../../../overlays";
type Props = { text?: string; className?: string };
const Help = ({ text, className }: Props) => (

View File

@@ -23,7 +23,7 @@
*/
import React, { InputHTMLAttributes } from "react";
import { createAttributesForTesting } from "@scm-manager/ui-components";
import { createAttributesForTesting } from "../../helpers";
import Help from "../base/help/Help";
import styled from "styled-components";
import classNames from "classnames";

View File

@@ -22,8 +22,8 @@
* SOFTWARE.
*/
import React, { KeyboardEventHandler, PropsWithRef, ReactElement, Ref, RefObject, useCallback } from "react";
import { createAttributesForTesting, useGeneratedId } from "@scm-manager/ui-components";
import React, { KeyboardEventHandler, PropsWithRef, ReactElement, Ref, useCallback } from "react";
import { createAttributesForTesting, useAriaId } from "../../helpers";
import Field from "../base/Field";
import Label from "../base/label/Label";
import Help from "../base/help/Help";
@@ -114,9 +114,9 @@ const ChipInputField = function ChipInputField<T>(
(item) => (createDeleteText ? createDeleteText(item) : t("delete", { item })),
[createDeleteText, t]
);
const inputId = useGeneratedId(id ?? testId);
const labelId = useGeneratedId();
const inputDescriptionId = useGeneratedId();
const inputId = useAriaId(id ?? testId);
const labelId = useAriaId();
const inputDescriptionId = useAriaId();
const variant = error ? "danger" : undefined;
return (
<Field className={className} aria-owns={inputId}>

View File

@@ -36,7 +36,7 @@ import { Combobox as HeadlessCombobox } from "@headlessui/react";
import classNames from "classnames";
import styled from "styled-components";
import { withForwardRef } from "../helpers";
import { createAttributesForTesting } from "@scm-manager/ui-components";
import { createAttributesForTesting } from "../../helpers";
import { Option } from "@scm-manager/ui-types";
const OptionsWrapper = styled(HeadlessCombobox.Options).attrs({

View File

@@ -26,7 +26,7 @@ import Field from "../base/Field";
import Label from "../base/label/Label";
import Help from "../base/help/Help";
import React from "react";
import { useGeneratedId } from "@scm-manager/ui-components";
import { useAriaId } from "../../helpers";
import { withForwardRef } from "../helpers";
import Combobox, { ComboboxProps } from "./Combobox";
import classNames from "classnames";
@@ -46,7 +46,7 @@ const ComboboxField = function ComboboxField<T>(
}: ComboboxProps<T> & { label: string; helpText?: string; error?: string; isLoading?: boolean },
ref: React.ForwardedRef<HTMLInputElement>
) {
const labelId = useGeneratedId();
const labelId = useAriaId();
return (
<Field className={className}>
<Label id={labelId}>

View File

@@ -41,7 +41,7 @@ import React, {
import { Slot } from "@radix-ui/react-slot";
import { Option } from "@scm-manager/ui-types";
import { mergeRefs, withForwardRef } from "../helpers";
import { Button } from "@scm-manager/ui-buttons";
import { Button } from "../../buttons";
type ChipInputContextType<T> = {
add(newValue: Option<T>): void;

View File

@@ -25,7 +25,7 @@
import React, { InputHTMLAttributes } from "react";
import classNames from "classnames";
import { createVariantClass, Variant } from "../variants";
import { createAttributesForTesting } from "@scm-manager/ui-components";
import { createAttributesForTesting } from "../../helpers";
type Props = {
variant?: Variant;

View File

@@ -29,7 +29,7 @@ import Label from "../base/label/Label";
import FieldMessage from "../base/field-message/FieldMessage";
import Input from "./Input";
import Help from "../base/help/Help";
import { useGeneratedId } from "@scm-manager/ui-components";
import { useAriaId } from "../../helpers";
type InputFieldProps = {
label: string;
@@ -42,7 +42,7 @@ type InputFieldProps = {
*/
const InputField = React.forwardRef<HTMLInputElement, InputFieldProps>(
({ label, helpText, error, className, id, ...props }, ref) => {
const inputId = useGeneratedId(id ?? props.testId);
const inputId = useAriaId(id ?? props.testId);
const variant = error ? "danger" : undefined;
return (
<Field className={className}>

View File

@@ -25,7 +25,7 @@
import React, { InputHTMLAttributes } from "react";
import classNames from "classnames";
import { createVariantClass, Variant } from "../variants";
import { createAttributesForTesting } from "@scm-manager/ui-components";
import { createAttributesForTesting } from "../../helpers";
type Props = {
variant?: Variant;

View File

@@ -26,10 +26,10 @@ import React from "react";
import { Path, PathValue } from "react-hook-form";
import { useScmFormContext } from "../ScmFormContext";
import { ScmFormPathContextProvider, useScmFormPathContext } from "../FormPathContext";
import { Button } from "@scm-manager/ui-buttons";
import { prefixWithoutIndices } from "../helpers";
import { useScmFormListContext } from "../ScmFormListContext";
import { useTranslation } from "react-i18next";
import { Button } from "../../buttons";
type ArrayItemType<T> = T extends Array<infer U> ? U : unknown;

View File

@@ -26,7 +26,7 @@ import React, { ComponentProps } from "react";
import classNames from "classnames";
import Help from "../base/help/Help";
import * as RadioGroup from "@radix-ui/react-radio-group";
import { createAttributesForTesting, useGeneratedId } from "@scm-manager/ui-components";
import { createAttributesForTesting, useAriaId } from "../../helpers";
import styled from "styled-components";
import { useRadioButtonContext } from "./RadioButtonContext";
@@ -90,7 +90,7 @@ type Props = {
const RadioButton = React.forwardRef<HTMLButtonElement, Props>(
({ id, testId, indicatorClassName, label, labelClassName, className, helpText, value, ...props }, ref) => {
const context = useRadioButtonContext();
const inputId = useGeneratedId(id);
const inputId = useAriaId(id);
const labelKey = `${context?.prefix}.radio.${value}`;
return (

View File

@@ -25,7 +25,7 @@
import React, { InputHTMLAttributes, Key, OptionHTMLAttributes } from "react";
import classNames from "classnames";
import { createVariantClass, Variant } from "../variants";
import { createAttributesForTesting } from "@scm-manager/ui-components";
import { createAttributesForTesting } from "../../helpers";
type Props = {
variant?: Variant;

View File

@@ -29,7 +29,7 @@ import Label from "../base/label/Label";
import FieldMessage from "../base/field-message/FieldMessage";
import Help from "../base/help/Help";
import Select from "./Select";
import { useGeneratedId } from "@scm-manager/ui-components";
import { useAriaId } from "../../helpers";
type Props = {
label: string;
@@ -44,7 +44,7 @@ type Props = {
*/
const SelectField = React.forwardRef<HTMLSelectElement, Props>(
({ label, helpText, error, className, id, ...props }, ref) => {
const selectId = useGeneratedId(id ?? props.testId);
const selectId = useAriaId(id ?? props.testId);
const variant = error ? "danger" : undefined;
return (
<Field className={className}>

View File

@@ -26,12 +26,12 @@ import React, { ReactElement } from "react";
import { Path, PathValue } from "react-hook-form";
import { useScmFormContext } from "../ScmFormContext";
import { ScmFormPathContextProvider, useScmFormPathContext } from "../FormPathContext";
import { Button } from "@scm-manager/ui-buttons";
import { Button } from "../../buttons";
import { prefixWithoutIndices } from "../helpers";
import classNames from "classnames";
import { useScmFormListContext } from "../ScmFormListContext";
import { useTranslation } from "react-i18next";
import { Notification } from "@scm-manager/ui-components";
import { Notification } from "../../notifications";
type RenderProps<T extends Record<string, unknown>, PATH extends Path<T>> = {
value: PathValue<T, PATH>;

View File

@@ -22,46 +22,23 @@
* SOFTWARE.
*/
import React, { useEffect } from "react";
import { initReactI18next } from "react-i18next";
import i18n from "i18next";
import { withThemes } from "storybook-addon-themes";
export const isDevBuild = () =>
((window as unknown as { scmStage: string }).scmStage || "").toUpperCase() !== "PRODUCTION";
i18n.use(initReactI18next).init({
whitelist: ["en", "de"],
lng: "en",
fallbackLng: "en",
interpolation: {
escapeValue: false,
},
react: {
useSuspense: false,
},
});
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;
export const createAttributesForTesting = (testId?: string) => {
if (!testId) {
return undefined;
}
}, [themeName]);
return <>{children}</>;
return {
"data-testid": normalizeTestId(testId),
};
};
export const decorators = [withThemes];
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" },
],
},
// keep this weird function instead of replaceAll because of browser compatibility
const normalizeTestId = (testId?: string) => {
let id = testId?.toLowerCase();
while (id?.includes(" ")) {
id = id.replace(" ", "-");
}
return id;
};

View File

@@ -22,15 +22,5 @@
* SOFTWARE.
*/
export const parameters = {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
options: {
storySort: (a, b) => (a[1].kind === b[1].kind ? 0 : a[1].id.localeCompare(b[1].id, undefined, { numeric: true })),
},
};
export { default as useAriaId } from "./useAriaId";
export { createAttributesForTesting, isDevBuild } from "./devbuild";

View File

@@ -0,0 +1,31 @@
/*
* 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 { useMemo } from "react";
let counter = 0;
export default function useAriaId(id?: string) {
return useMemo(() => id ?? `scm-id-${++counter}`, [id]);
}

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.
*/
export * from "./buttons"
export * from "./forms"
export * from "./helpers"
export * from "./misc"
export * from "./layout"
export * from "./notifications"
export * from "./overlays"
export * from "./shortcuts"
export * from "./text"

View File

@@ -28,8 +28,8 @@ import React from "react";
import { Link } from "react-router-dom";
import CardList, { CardListBox, CardListCard } from "./CardList";
import CardTitle from "../card/CardTitle";
import { Menu } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
import { Menu } from "../../overlays";
import { Icon } from "../../buttons";
import CardRow, { SecondaryRow } from "../card/CardRow";
import { CardDetail, CardDetailLabel, CardDetails, CardDetailTag, CardLinkDetail } from "../card/CardDetail";

View File

@@ -38,8 +38,8 @@ import {
CardLinkDetail,
} from "./CardDetail";
import Card from "./Card";
import { Popover } from "@scm-manager/ui-overlays";
import { Icon } from "@scm-manager/ui-buttons";
import { Popover } from "../../overlays";
import { Icon } from "../../buttons";
export default {
title: "Card",

View File

@@ -30,10 +30,10 @@ import React, {
ReactNode,
} from "react";
import classNames from "classnames";
import { useGeneratedId } from "@scm-manager/ui-components";
import { useAriaId } from "../../helpers";
import styled from "styled-components";
import { Link } from "react-router-dom";
import { Icon } from "@scm-manager/ui-buttons";
import { Icon } from "../../buttons";
export const CardVariants = {
LIGHT: "light",
@@ -62,7 +62,7 @@ type CardVariantProps = {
*/
export const CardDetail = React.forwardRef<HTMLSpanElement, CardDetailProps>(
({ children, className, ...props }, ref) => {
const labelId = useGeneratedId();
const labelId = useAriaId();
return (
<span {...props} className={classNames("is-flex is-align-items-center has-gap-1 p-1", className)} ref={ref}>
{typeof children === "function" ? children({ labelId }) : children}

Some files were not shown because too many files have changed in this diff Show More