Rename frontend directory (#472)

* Rename directory

* Cleanup

* Recover lost images

* remove lint
This commit is contained in:
Andrey Pokhilko
2023-09-26 10:04:44 +01:00
committed by GitHub
parent 133eef6745
commit dd7aca70ff
146 changed files with 595 additions and 309 deletions
@@ -0,0 +1,40 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Button } from "./Button";
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
export default {
title: "Example/Button",
component: Button,
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
argTypes: {
backgroundColor: { control: "color" },
},
} as ComponentMeta<typeof Button>;
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;
export const Primary = Template.bind({});
// More on args: https://storybook.js.org/docs/react/writing-stories/args
Primary.args = {
primary: true,
label: "Button",
};
export const Secondary = Template.bind({});
Secondary.args = {
label: "Button",
};
export const Large = Template.bind({});
Large.args = {
size: "large",
label: "Button",
};
export const Small = Template.bind({});
Small.args = {
size: "small",
label: "Button",
};
@@ -0,0 +1,51 @@
import "./button.css";
interface ButtonProps {
/**
* Is this the principal call to action on the page?
*/
primary?: boolean;
/**
* What background color to use
*/
backgroundColor?: string;
/**
* How large should the button be?
*/
size?: "small" | "medium" | "large";
/**
* Button contents
*/
label: string;
/**
* Optional click handler
*/
onClick?: () => void;
}
/**
* Primary UI component for user interaction
*/
export const Button = ({
primary = false,
size = "medium",
backgroundColor,
label,
...props
}: ButtonProps) => {
const mode = primary
? "storybook-button--primary"
: "storybook-button--secondary";
return (
<button
type="button"
className={["storybook-button", `storybook-button--${size}`, mode].join(
" "
)}
style={{ backgroundColor }}
{...props}
>
{label}
</button>
);
};
@@ -0,0 +1,30 @@
.storybook-button {
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-weight: 700;
border: 0;
border-radius: 3em;
cursor: pointer;
display: inline-block;
line-height: 1;
}
.storybook-button--primary {
color: white;
background-color: #1ea7fd;
}
.storybook-button--secondary {
color: #333;
background-color: transparent;
box-shadow: rgba(0, 0, 0, 0.15) 0px 0px 0px 1px inset;
}
.storybook-button--small {
font-size: 12px;
padding: 10px 16px;
}
.storybook-button--medium {
font-size: 14px;
padding: 11px 20px;
}
.storybook-button--large {
font-size: 16px;
padding: 12px 24px;
}
@@ -0,0 +1,35 @@
/* eslint-disable no-console */
// DropDown.stories.ts|tsx
import { ComponentStory, ComponentMeta } from "@storybook/react";
import DropDown from "./DropDown";
import { BsSlack, BsGithub } from "react-icons/bs";
//👇 This default export determines where your story goes in the story list
export default {
/* 👇 The title prop is optional.
* See https://storybook.js.org/docs/react/configure/overview#configure-story-loading
* to learn how to generate automatic titles
*/
title: "DropDown",
component: DropDown,
} as ComponentMeta<typeof DropDown>;
//👇 We create a “template” of how args map to rendering
const Template: ComponentStory<typeof DropDown> = (args) => (
<DropDown {...args} />
);
export const Default = Template.bind({});
const onClick = () => {
console.log("drop down clicked");
};
Default.args = {
items: [
{ id: "1", text: "Menu Item 1", onClick: onClick, icon: <BsSlack /> },
{ id: "2 ", isSeparator: true },
{ id: "3", text: "Menu Item 3", isDisabled: true, icon: <BsGithub /> },
],
};
+107
View File
@@ -0,0 +1,107 @@
import { ReactNode, useEffect, useRef, useState } from "react";
import ArrowDownIcon from "../../assets/arrow-down-icon.svg";
export type DropDownItem = {
id: string;
text?: string;
icon?: ReactNode;
onClick?: () => void;
isSeparator?: boolean;
isDisabled?: boolean;
};
export type DropDownProps = {
items: DropDownItem[];
};
type PopupState = {
isOpen: boolean;
X: number;
Y: number;
};
function DropDown({ items }: DropDownProps) {
const [popupState, setPopupState] = useState<PopupState>({
isOpen: false,
X: 0,
Y: 0,
});
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (popupState.isOpen) {
document.addEventListener("mousedown", handleClickOutside);
} else {
document.removeEventListener("mousedown", handleClickOutside);
}
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [popupState.isOpen]);
const handleClickOutside = (event: MouseEvent) => {
if (modalRef.current && !modalRef.current.contains(event.target as Node)) {
setPopupState((prev) => ({
...prev,
isOpen: false,
}));
}
};
return (
<>
<div className="relative flex flex-col items-center">
<button
onClick={(e) => {
setPopupState((prev) => ({
...prev,
isOpen: !prev.isOpen,
X: e.pageX,
Y: e.pageY,
}));
}}
className="flex items-center justify-between"
>
Help
<img src={ArrowDownIcon} className="ml-2 w-[10px] h-[10px]" />
</button>
</div>
{popupState.isOpen && (
<div
ref={modalRef}
className={`z-10 flex flex-col py-1 gap-1 bg-white mt-3 absolute rounded border top-[${popupState.Y}] left-[${popupState.X}] border-gray-200`}
>
{items.map((item) => (
<>
{item.isSeparator ? (
<div className="bg-gray-300 h-[1px]" />
) : (
<div
onClick={() => {
item.onClick?.();
setPopupState((prev) => ({
...prev,
isOpen: false,
}));
}}
className={`cursor-pointer font-normal flex items-center gap-2 py-1 pl-3 pr-7 hover:bg-dropdown ${
item.isDisabled
? "cursor-default hover:bg-transparent text-gray-400"
: ""
}`}
>
{item.icon && <span> {item.icon ?? null}</span>}
<span>{item.text}</span>
</div>
)}
</>
))}
</div>
)}
</>
);
}
export default DropDown;
@@ -0,0 +1,24 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { Header } from "./Header";
export default {
title: "Example/Header",
component: Header,
parameters: {
// More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen",
},
} as ComponentMeta<typeof Header>;
const Template: ComponentStory<typeof Header> = (args) => <Header {...args} />;
export const LoggedIn = Template.bind({});
LoggedIn.args = {
user: {
name: "Jane Doe",
},
};
export const LoggedOut = Template.bind({});
LoggedOut.args = {};
@@ -0,0 +1,69 @@
import { Button } from "../Button/Button";
import "./header.css";
type User = {
name: string;
};
interface HeaderProps {
user?: User;
onLogin: () => void;
onLogout: () => void;
onCreateAccount: () => void;
}
export const Header = ({
user,
onLogin,
onLogout,
onCreateAccount,
}: HeaderProps) => (
<header>
<div className="wrapper">
<div>
<svg
width="32"
height="32"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<path
d="M10 0h12a10 10 0 0110 10v12a10 10 0 01-10 10H10A10 10 0 010 22V10A10 10 0 0110 0z"
fill="#FFF"
/>
<path
d="M5.3 10.6l10.4 6v11.1l-10.4-6v-11zm11.4-6.2l9.7 5.5-9.7 5.6V4.4z"
fill="#555AB9"
/>
<path
d="M27.2 10.6v11.2l-10.5 6V16.5l10.5-6zM15.7 4.4v11L6 10l9.7-5.5z"
fill="#91BAF8"
/>
</g>
</svg>
<h1>Acme</h1>
</div>
<div>
{user ? (
<>
<span className="welcome">
Welcome, <b>{user.name}</b>!
</span>
<Button size="small" onClick={onLogout} label="Log out" />
</>
) : (
<>
<Button size="small" onClick={onLogin} label="Log in" />
<Button
primary
size="small"
onClick={onCreateAccount}
label="Sign up"
/>
</>
)}
</div>
</div>
</header>
);
@@ -0,0 +1,32 @@
.wrapper {
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
padding: 15px 20px;
display: flex;
align-items: center;
justify-content: space-between;
}
svg {
display: inline-block;
vertical-align: top;
}
h1 {
font-weight: 900;
font-size: 20px;
line-height: 1;
margin: 6px 0 6px 10px;
display: inline-block;
vertical-align: top;
}
button + button {
margin-left: 10px;
}
.welcome {
color: #333;
font-size: 14px;
margin-right: 10px;
}
@@ -0,0 +1,25 @@
import { ComponentStory, ComponentMeta } from "@storybook/react";
import { within, userEvent } from "@storybook/testing-library";
import { Page } from "./Page";
export default {
title: "Example/Page",
component: Page,
parameters: {
// More on Story layout: https://storybook.js.org/docs/react/configure/story-layout
layout: "fullscreen",
},
} as ComponentMeta<typeof Page>;
const Template: ComponentStory<typeof Page> = (args) => <Page {...args} />;
export const LoggedOut = Template.bind({});
export const LoggedIn = Template.bind({});
// More on interaction testing: https://storybook.js.org/docs/react/writing-tests/interaction-testing
LoggedIn.play = async ({ canvasElement }) => {
const canvas = within(canvasElement);
const loginButton = await canvas.getByRole("button", { name: /Log in/i });
await userEvent.click(loginButton);
};
@@ -0,0 +1,92 @@
import React from "react";
import { Header } from "../Header/Header";
import "./page.css";
type User = {
name: string;
};
export const Page: React.VFC = () => {
const [user, setUser] = React.useState<User>();
return (
<article>
<Header
user={user}
onLogin={() => setUser({ name: "Jane Doe" })}
onLogout={() => setUser(undefined)}
onCreateAccount={() => setUser({ name: "Jane Doe" })}
/>
<section>
<h2>Pages in Storybook</h2>
<p>
We recommend building UIs with a{" "}
<a
href="https://componentdriven.org"
target="_blank"
rel="noopener noreferrer"
>
<strong>component-driven</strong>
</a>{" "}
process starting with atomic components and ending with pages.
</p>
<p>
Render pages with mock data. This makes it easy to build and review
page states without needing to navigate to them in your app. Here are
some handy patterns for managing page data in Storybook:
</p>
<ul>
<li>
Use a higher-level connected component. Storybook helps you compose
such data from the &quot;args&quot; of child component stories
</li>
<li>
Assemble data in the page component from your services. You can mock
these services out using Storybook.
</li>
</ul>
<p>
Get a guided tutorial on component-driven development at{" "}
<a
href="https://storybook.js.org/tutorials/"
target="_blank"
rel="noopener noreferrer"
>
Storybook tutorials
</a>
. Read more in the{" "}
<a
href="https://storybook.js.org/docs"
target="_blank"
rel="noopener noreferrer"
>
docs
</a>
.
</p>
<div className="tip-wrapper">
<span className="tip">Tip</span> Adjust the width of the canvas with
the{" "}
<svg
width="10"
height="10"
viewBox="0 0 12 12"
xmlns="http://www.w3.org/2000/svg"
>
<g fill="none" fillRule="evenodd">
<path
d="M1.5 5.2h4.8c.3 0 .5.2.5.4v5.1c-.1.2-.3.3-.4.3H1.4a.5.5 0 01-.5-.4V5.7c0-.3.2-.5.5-.5zm0-2.1h6.9c.3 0 .5.2.5.4v7a.5.5 0 01-1 0V4H1.5a.5.5 0 010-1zm0-2.1h9c.3 0 .5.2.5.4v9.1a.5.5 0 01-1 0V2H1.5a.5.5 0 010-1zm4.3 5.2H2V10h3.8V6.2z"
id="a"
fill="#999"
/>
</g>
</svg>
Viewports addon in the toolbar
</div>
</section>
</article>
);
};
@@ -0,0 +1,69 @@
section {
font-family: "Nunito Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 24px;
padding: 48px 20px;
margin: 0 auto;
max-width: 600px;
color: #333;
}
section h2 {
font-weight: 900;
font-size: 32px;
line-height: 1;
margin: 0 0 4px;
display: inline-block;
vertical-align: top;
}
section p {
margin: 1em 0;
}
section a {
text-decoration: none;
color: #1ea7fd;
}
section ul {
padding-left: 30px;
margin: 1em 0;
}
section li {
margin-bottom: 8px;
}
section .tip {
display: inline-block;
border-radius: 1em;
font-size: 11px;
line-height: 12px;
font-weight: 700;
background: #e7fdd8;
color: #66bf3c;
padding: 4px 12px;
margin-right: 10px;
vertical-align: top;
}
section .tip-wrapper {
font-size: 13px;
line-height: 20px;
margin-top: 40px;
margin-bottom: 40px;
}
section .tip-wrapper svg {
display: inline-block;
height: 12px;
width: 12px;
margin-right: 4px;
vertical-align: top;
margin-top: 3px;
}
section .tip-wrapper svg path {
fill: #1ea7fd;
}
@@ -0,0 +1,39 @@
import { ComponentStory } from "@storybook/react";
import StatusLabel, { DeploymentStatus } from "./StatusLabel";
export default {
title: "StatusLabel",
component: StatusLabel,
};
const Template: ComponentStory<typeof StatusLabel> = (args) => (
<StatusLabel {...args} />
);
export const Deployed = Template.bind({});
Deployed.args = {
status: DeploymentStatus.DEPLOYED,
isRollback: false,
};
export const Failed = Template.bind({});
Failed.args = {
status: DeploymentStatus.FAILED,
isRollback: false,
};
export const Pending = Template.bind({});
Pending.args = {
status: DeploymentStatus.PENDING,
isRollback: false,
};
export const Superseded = Template.bind({});
Superseded.args = {
status: DeploymentStatus.SUPERSEDED,
isRollback: false,
};
@@ -0,0 +1,42 @@
import { AiOutlineReload } from "react-icons/ai";
type StatusLabelProps = {
status: string;
isRollback?: boolean;
};
export enum DeploymentStatus {
DEPLOYED = "deployed",
FAILED = "failed",
PENDING = "pending-install",
SUPERSEDED = "superseded",
}
export function getStatusColor(status: DeploymentStatus) {
if (status === DeploymentStatus.DEPLOYED) return "text-deployed";
if (status === DeploymentStatus.FAILED) return "text-failed";
if (status === DeploymentStatus.PENDING) return "text-pending";
else return "text-superseded";
}
function StatusLabel({ status, isRollback }: StatusLabelProps) {
const statusColor = getStatusColor(status as DeploymentStatus);
return (
<div
style={{
minWidth: "100px",
display: "flex",
fontSize: "14px",
justifyContent: "space-between",
}}
>
<span className={`${statusColor} font-bold text-xs`}>
{status.toUpperCase()}
</span>
{isRollback && <AiOutlineReload size={14} />}
</div>
);
}
export default StatusLabel;