mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-08 14:35:45 +01:00
added toast component to display information in an asynchronous manner
This commit is contained in:
2
scm-ui/ui-components/.storybook/preview-body.html
Normal file
2
scm-ui/ui-components/.storybook/preview-body.html
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
<div id="modalRoot"></div>
|
||||||
|
<div id="toastRoot"></div>
|
||||||
@@ -66,6 +66,7 @@ export * from "./modals";
|
|||||||
export * from "./navigation";
|
export * from "./navigation";
|
||||||
export * from "./repos";
|
export * from "./repos";
|
||||||
export * from "./table";
|
export * from "./table";
|
||||||
|
export * from "./toast";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
File,
|
File,
|
||||||
|
|||||||
61
scm-ui/ui-components/src/toast/Toast.tsx
Normal file
61
scm-ui/ui-components/src/toast/Toast.tsx
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import React, { FC } from "react";
|
||||||
|
import { createPortal } from "react-dom";
|
||||||
|
import styled from "styled-components";
|
||||||
|
import { getTheme, Themeable, ToastThemeContext, Type } from "./themes";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
type: Type;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rootElement = document.getElementById("toastRoot");
|
||||||
|
|
||||||
|
const Container = styled.div<Themeable>`
|
||||||
|
z-index: 99999;
|
||||||
|
position: fixed;
|
||||||
|
padding: 1.5rem;
|
||||||
|
right: 1.5rem;
|
||||||
|
bottom: 1.5rem;
|
||||||
|
color: ${props => props.theme.primary};
|
||||||
|
background-color: ${props => props.theme.secondary};
|
||||||
|
max-width: 18rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
border-radius: 5px;
|
||||||
|
animation: 0.5s slide-up;
|
||||||
|
|
||||||
|
& > p {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slide-up {
|
||||||
|
from {
|
||||||
|
bottom: -10rem;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Title = styled.h1<Themeable>`
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
font-weight: bold;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const Toast: FC<Props> = ({ children, title, type }) => {
|
||||||
|
if (!rootElement) {
|
||||||
|
throw new Error("could not find toast container #toastRoot");
|
||||||
|
}
|
||||||
|
|
||||||
|
const theme = getTheme(type);
|
||||||
|
const content = (
|
||||||
|
<Container theme={theme}>
|
||||||
|
<Title theme={theme}>{title}</Title>
|
||||||
|
<ToastThemeContext.Provider value={theme}>{children}</ToastThemeContext.Provider>
|
||||||
|
</Container>
|
||||||
|
);
|
||||||
|
|
||||||
|
return createPortal(content, rootElement);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Toast;
|
||||||
37
scm-ui/ui-components/src/toast/ToastButton.tsx
Normal file
37
scm-ui/ui-components/src/toast/ToastButton.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import React, { FC, useContext } from "react";
|
||||||
|
import { ToastThemeContext, Themeable } from "./themes";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
icon?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemedButton = styled.div.attrs(props => ({
|
||||||
|
className: "button"
|
||||||
|
}))<Themeable>`
|
||||||
|
color: ${props => props.theme.primary};
|
||||||
|
border-color: ${props => props.theme.primary};
|
||||||
|
background-color: ${props => props.theme.secondary};
|
||||||
|
font-size: 0.75rem;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: ${props => props.theme.primary};
|
||||||
|
border-color: ${props => props.theme.tertiary};
|
||||||
|
background-color: ${props => props.theme.tertiary};
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ToastButtonIcon = styled.i`
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ToastButton: FC<Props> = ({ icon, children }) => {
|
||||||
|
const theme = useContext(ToastThemeContext);
|
||||||
|
return (
|
||||||
|
<ThemedButton theme={theme}>
|
||||||
|
{icon && <ToastButtonIcon className={`fas fa-fw fa-${icon}`} />} {children}
|
||||||
|
</ThemedButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ToastButton;
|
||||||
20
scm-ui/ui-components/src/toast/ToastButtons.tsx
Normal file
20
scm-ui/ui-components/src/toast/ToastButtons.tsx
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import React, { FC } from "react";
|
||||||
|
import styled from "styled-components";
|
||||||
|
|
||||||
|
const Buttons = styled.div`
|
||||||
|
display: flex;
|
||||||
|
padding-top: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
& > *:not(:last-child) {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const ToastButtons: FC = ({ children }) => <Buttons>{children}</Buttons>;
|
||||||
|
|
||||||
|
export default ToastButtons;
|
||||||
42
scm-ui/ui-components/src/toast/index.stories.tsx
Normal file
42
scm-ui/ui-components/src/toast/index.stories.tsx
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { storiesOf } from "@storybook/react";
|
||||||
|
import Toast from "./Toast";
|
||||||
|
import ToastButtons from "./ToastButtons";
|
||||||
|
import ToastButton from "./ToastButton";
|
||||||
|
import { types } from "./themes";
|
||||||
|
|
||||||
|
const toastStories = storiesOf("Toast", module);
|
||||||
|
|
||||||
|
const AnimatedToast = () => (
|
||||||
|
<Toast type="primary" title="Animated">
|
||||||
|
Awesome animated Toast
|
||||||
|
</Toast>
|
||||||
|
);
|
||||||
|
|
||||||
|
const Animator = () => {
|
||||||
|
const [display, setDisplay] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ padding: "2rem" }}>
|
||||||
|
{display && <AnimatedToast />}
|
||||||
|
<button className="button is-primary" onClick={() => setDisplay(!display)}>
|
||||||
|
{display ? "Close" : "Open"} Toast
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
toastStories.add("Open/Close", () => <Animator />);
|
||||||
|
|
||||||
|
types.forEach(type => {
|
||||||
|
toastStories.add(type.charAt(0).toUpperCase() + type.slice(1), () => (
|
||||||
|
<Toast type={type} title="New Changes">
|
||||||
|
<p>The underlying Pull-Request has changed. Press reload to see the changes.</p>
|
||||||
|
<p>Warning: Non saved modification will be lost.</p>
|
||||||
|
<ToastButtons>
|
||||||
|
<ToastButton icon="redo">Reload</ToastButton>
|
||||||
|
<ToastButton icon="times">Ignore</ToastButton>
|
||||||
|
</ToastButtons>
|
||||||
|
</Toast>
|
||||||
|
));
|
||||||
|
});
|
||||||
3
scm-ui/ui-components/src/toast/index.ts
Normal file
3
scm-ui/ui-components/src/toast/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export { default as Toast } from "./Toast";
|
||||||
|
export { default as ToastButton } from "./ToastButton";
|
||||||
|
export { default as ToastButtons } from "./ToastButtons";
|
||||||
53
scm-ui/ui-components/src/toast/themes.ts
Normal file
53
scm-ui/ui-components/src/toast/themes.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
export type ToastTheme = {
|
||||||
|
primary: string;
|
||||||
|
secondary: string;
|
||||||
|
tertiary: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Themeable = {
|
||||||
|
theme: ToastTheme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Type = "info" | "primary" | "success" | "warning" | "danger";
|
||||||
|
|
||||||
|
export const types: Type[] = ["info", "primary", "success", "warning", "danger"];
|
||||||
|
|
||||||
|
const themes: { [name in Type]: ToastTheme } = {
|
||||||
|
info: {
|
||||||
|
primary: "#363636",
|
||||||
|
secondary: "#99d8f3",
|
||||||
|
tertiary: "white"
|
||||||
|
},
|
||||||
|
primary: {
|
||||||
|
primary: "#363636",
|
||||||
|
secondary: "#7fe8ef",
|
||||||
|
tertiary: "white"
|
||||||
|
},
|
||||||
|
success: {
|
||||||
|
primary: "#363636",
|
||||||
|
secondary: "#7fe3cd",
|
||||||
|
tertiary: "white"
|
||||||
|
},
|
||||||
|
warning: {
|
||||||
|
primary: "#905515",
|
||||||
|
secondary: "#ffeeab",
|
||||||
|
tertiary: "white"
|
||||||
|
},
|
||||||
|
danger: {
|
||||||
|
primary: "#363636",
|
||||||
|
secondary: "#ff9baf",
|
||||||
|
tertiary: "white"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getTheme = (name: Type) => {
|
||||||
|
const theme = themes[name];
|
||||||
|
if (!theme) {
|
||||||
|
throw new Error(`could not find theme with name ${name}`);
|
||||||
|
}
|
||||||
|
return theme;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ToastThemeContext = React.createContext(themes.warning);
|
||||||
@@ -34,6 +34,7 @@
|
|||||||
</noscript>
|
</noscript>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
<div id="modalRoot"></div>
|
<div id="modalRoot"></div>
|
||||||
|
<div id="toastRoot"></div>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
This HTML file is a template.
|
This HTML file is a template.
|
||||||
|
|||||||
Reference in New Issue
Block a user