mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-24 22:14:18 +00:00
🔧 Refactor summary in multiple components
This commit is contained in:
parent
d38caffb0a
commit
f6bf948742
20 changed files with 301 additions and 259 deletions
|
@ -8,6 +8,8 @@ import make_icon from "../../utils/make_icon"
|
|||
* A clickable button.
|
||||
*
|
||||
* @param children - The contents of the button.
|
||||
* @param disabled - Whether the button is disabled or not.
|
||||
* @param onClick - Function to call when the button is clicked. Won't be called if the button is disabled.
|
||||
* @param className - Additional class(es) that should be added to the button.
|
||||
* @param color - The color of the button. Either `Red`, `Grey`, `Green` or `Yellow`.
|
||||
* @param icon - The FontAwesome IconDefinition of the icon that should be rendered in the button.
|
||||
|
@ -15,9 +17,15 @@ import make_icon from "../../utils/make_icon"
|
|||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Button({ children, className, color, icon, ...props }) {
|
||||
export default function Button({ children, disabled, onClick, className, color, icon, ...props }) {
|
||||
return (
|
||||
<button type={"button"} className={classNames(Style.Button, Style[`Button${color}`], className)} {...props}>
|
||||
<button
|
||||
type={"button"}
|
||||
className={classNames(Style.Button, Style[`Button${color}`], disabled ? null : "Clickable", className)}
|
||||
onClick={disabled ? null : onClick}
|
||||
disabled={disabled}
|
||||
{...props}
|
||||
>
|
||||
{children} {make_icon(icon, Style.Icon)}
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -15,14 +15,6 @@
|
|||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.Button:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
.Button:active {
|
||||
filter: brightness(125%);
|
||||
}
|
||||
|
||||
.Button:focus-visible {
|
||||
outline: 4px solid var(--outline);
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@ export default function ButtonSidebar({ icon, children, to, className, ...props
|
|||
|
||||
return (
|
||||
<Link to={to} className={Style.ButtonLink}>
|
||||
<div className={classNames(Style.ButtonSidebar, className)} {...props}>
|
||||
<div className={classNames(Style.ButtonSidebar, "Clickable", className)} {...props}>
|
||||
{make_icon(icon, Style.ButtonIcon)}
|
||||
<div className={Style.ButtonText}>
|
||||
{children}
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
import React from "react"
|
||||
import Style from "./Summary.module.css"
|
||||
import classNames from "classnames"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
||||
/**
|
||||
* A long line displaying the summary of a certain entity, such as a repository or an user.
|
||||
*
|
||||
* @param icon - The icon of the summary.
|
||||
* @param title - The title of the summary.
|
||||
* @param subtitle - The subtitle of the summary.
|
||||
* @param onClick - A function to call when the summary is clicked.
|
||||
* @param upperLabel - The label for the upper value.
|
||||
* @param upperValue - The upper value.
|
||||
* @param lowerLabel - The label for the lower value.
|
||||
* @param lowerValue - The lower value.
|
||||
* @param buttons - Buttons to add to the right of the summary.
|
||||
* @param className - Additional class(es) to add to the summary.
|
||||
* @param props - Additional props to pass to the summary.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Summary(
|
||||
{ icon, title, subtitle, onClick, upperLabel, upperValue, lowerLabel, lowerValue, buttons, className, ...props },
|
||||
) {
|
||||
return (
|
||||
<div className={classNames(Style.Summary, className)} {...props}>
|
||||
<div className={classNames(Style.Left, onClick ? Style.Clickable : null)} onClick={onClick}>
|
||||
<div className={Style.IconContainer}>
|
||||
<FontAwesomeIcon icon={icon}/>
|
||||
</div>
|
||||
<div className={Style.Title}>
|
||||
{title}
|
||||
</div>
|
||||
<div className={Style.Subtitle}>
|
||||
{subtitle}
|
||||
</div>
|
||||
</div>
|
||||
<div className={Style.Middle}>
|
||||
<div className={classNames(Style.Label, Style.Upper)}>
|
||||
{upperLabel}
|
||||
</div>
|
||||
<div className={classNames(Style.Value, Style.Upper)}>
|
||||
{upperValue}
|
||||
</div>
|
||||
<div className={classNames(Style.Label, Style.Lower)}>
|
||||
{lowerLabel}
|
||||
</div>
|
||||
<div className={classNames(Style.Value, Style.Lower)}>
|
||||
{lowerValue}
|
||||
</div>
|
||||
</div>
|
||||
<div className={Style.Right}>
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
.Summary {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
|
||||
margin: 10px 0;
|
||||
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.Clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Clickable:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
.Left {
|
||||
width: 280px;
|
||||
height: 60px;
|
||||
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"a b"
|
||||
"a c"
|
||||
"a d"
|
||||
"a e";
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 5px 1fr 1fr 5px;
|
||||
|
||||
background-color: var(--bg-accent);
|
||||
border-radius: 30px 0 0 30px;
|
||||
}
|
||||
|
||||
.IconContainer {
|
||||
margin: 5px 15px 5px 5px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background-color: var(--bg-light);
|
||||
color: var(--fg-primary);
|
||||
|
||||
font-size: x-large;
|
||||
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.Title {
|
||||
grid-area: c;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.Subtitle {
|
||||
grid-area: d;
|
||||
font-size: small;
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
.Middle {
|
||||
flex-grow: 1;
|
||||
height: 60px;
|
||||
padding: 5px 20px;
|
||||
|
||||
background-color: var(--bg-light);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-column-gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
font-size: small;
|
||||
|
||||
}
|
||||
|
||||
.Middle .Label {
|
||||
grid-column: 1;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Middle .Value {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.Middle .Upper {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.Middle .Lower {
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.Right {
|
||||
width: 280px;
|
||||
height: 60px;
|
||||
padding: 5px;
|
||||
|
||||
background-color: var(--bg-accent);
|
||||
border-radius: 0 30px 30px 0;
|
||||
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
}
|
12
nest_frontend/components/base/summary/SummaryBase.js
Normal file
12
nest_frontend/components/base/summary/SummaryBase.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from "react"
|
||||
import Style from "./SummaryBase.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function SummaryBase({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.SummaryBase, className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.SummaryBase {
|
||||
width: 100%;
|
||||
height: 60px;
|
||||
|
||||
margin: 10px 0;
|
||||
|
||||
display: flex;
|
||||
}
|
11
nest_frontend/components/base/summary/SummaryButton.js
Normal file
11
nest_frontend/components/base/summary/SummaryButton.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import React from "react"
|
||||
import Style from "./SummaryButton.module.css"
|
||||
import classNames from "classnames"
|
||||
import Button from "../Button"
|
||||
|
||||
|
||||
export default function SummaryButton({ className, ...props }) {
|
||||
return (
|
||||
<Button className={classNames(Style.SummaryButton, className)} {...props}/>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
.SummaryButton {
|
||||
width: 90px;
|
||||
|
||||
box-shadow: none;
|
||||
border-radius: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
23
nest_frontend/components/base/summary/SummaryLabels.js
Normal file
23
nest_frontend/components/base/summary/SummaryLabels.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import React from "react"
|
||||
import Style from "./SummaryLabels.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function SummaryLabels({ children, upperLabel, upperValue, lowerLabel, lowerValue, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.SummaryLabels, className)} {...props}>
|
||||
<div className={classNames(Style.Label, Style.Upper)}>
|
||||
{upperLabel}
|
||||
</div>
|
||||
<div className={classNames(Style.Value, Style.Upper)}>
|
||||
{upperValue}
|
||||
</div>
|
||||
<div className={classNames(Style.Label, Style.Lower)}>
|
||||
{lowerLabel}
|
||||
</div>
|
||||
<div className={classNames(Style.Value, Style.Lower)}>
|
||||
{lowerValue}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
.SummaryLabels {
|
||||
flex-grow: 3;
|
||||
|
||||
padding: 5px 20px;
|
||||
|
||||
background-color: var(--bg-light);
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
grid-column-gap: 10px;
|
||||
align-items: center;
|
||||
|
||||
font-size: small;
|
||||
}
|
||||
|
||||
.Label {
|
||||
grid-column: 1;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.Value {
|
||||
grid-column: 2;
|
||||
}
|
||||
|
||||
.Upper {
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.Lower {
|
||||
grid-row: 2;
|
||||
}
|
25
nest_frontend/components/base/summary/SummaryLeft.js
Normal file
25
nest_frontend/components/base/summary/SummaryLeft.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import React from "react"
|
||||
import Style from "./SummaryLeft.module.css"
|
||||
import classNames from "classnames"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
|
||||
|
||||
export default function SummaryLeft({ icon, title, subtitle, className, onClick, ...props }) {
|
||||
return (
|
||||
<div
|
||||
className={classNames(Style.SummaryLeft, onClick ? "Clickable" : null, className)}
|
||||
onClick={onClick}
|
||||
{...props}
|
||||
>
|
||||
<div className={Style.IconContainer}>
|
||||
<FontAwesomeIcon icon={icon}/>
|
||||
</div>
|
||||
<div className={Style.Title}>
|
||||
{title}
|
||||
</div>
|
||||
<div className={Style.Subtitle}>
|
||||
{subtitle}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
46
nest_frontend/components/base/summary/SummaryLeft.module.css
Normal file
46
nest_frontend/components/base/summary/SummaryLeft.module.css
Normal file
|
@ -0,0 +1,46 @@
|
|||
.SummaryLeft {
|
||||
width: 275px;
|
||||
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
"a b"
|
||||
"a c"
|
||||
"a d"
|
||||
"a e";
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-rows: 5px 1fr 1fr 5px;
|
||||
justify-content: stretch;
|
||||
align-items: stretch;
|
||||
|
||||
background-color: var(--bg-accent);
|
||||
border-radius: 30px 0 0 30px;
|
||||
}
|
||||
|
||||
.IconContainer {
|
||||
margin: 5px 15px 5px 5px;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border-radius: 50px;
|
||||
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
background-color: var(--bg-light);
|
||||
color: var(--fg-primary);
|
||||
|
||||
font-size: x-large;
|
||||
|
||||
grid-area: a;
|
||||
}
|
||||
|
||||
.Title {
|
||||
grid-area: c;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.Subtitle {
|
||||
grid-area: d;
|
||||
font-size: small;
|
||||
align-self: flex-start;
|
||||
}
|
12
nest_frontend/components/base/summary/SummaryRight.js
Normal file
12
nest_frontend/components/base/summary/SummaryRight.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from "react"
|
||||
import Style from "./SummaryRight.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function SummaryRight({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.SummaryRight, className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
.SummaryRight {
|
||||
min-width: 30px;
|
||||
padding: 5px;
|
||||
|
||||
background-color: var(--bg-accent);
|
||||
border-radius: 0 30px 30px 0;
|
||||
}
|
12
nest_frontend/components/base/summary/SummaryText.js
Normal file
12
nest_frontend/components/base/summary/SummaryText.js
Normal file
|
@ -0,0 +1,12 @@
|
|||
import React from "react"
|
||||
import Style from "./SummaryText.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function SummaryText({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.SummaryText, className)} {...props}>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
|
@ -45,7 +45,7 @@ export default function BoxRepositoriesActive({
|
|||
deleteSelf={() => destroyRepository(repo["id"])}
|
||||
canArchive={true}
|
||||
canEdit={true}
|
||||
canDelete={repo["owner"]["username"] === user["username"]}
|
||||
canDelete={false}
|
||||
running={running}
|
||||
/>
|
||||
))
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import React, { useContext } from "react"
|
||||
import Button from "../base/Button"
|
||||
import { faArchive, faFolder, faFolderOpen, faPencilAlt, faTrash } from "@fortawesome/free-solid-svg-icons"
|
||||
import { useHistory } from "react-router"
|
||||
import Summary from "../base/Summary"
|
||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||
import SummaryBase from "../base/summary/SummaryBase"
|
||||
import SummaryLeft from "../base/summary/SummaryLeft"
|
||||
import SummaryLabels from "../base/summary/SummaryLabels"
|
||||
import SummaryButton from "../base/summary/SummaryButton"
|
||||
import SummaryRight from "../base/summary/SummaryRight"
|
||||
|
||||
|
||||
/**
|
||||
|
@ -36,51 +39,51 @@ export default function SummaryRepository(
|
|||
history.push(`/repositories/${repo.id}/edit`)
|
||||
}
|
||||
|
||||
const buttons = <>
|
||||
{canDelete ?
|
||||
<Button
|
||||
color={"Red"}
|
||||
icon={faTrash}
|
||||
onClick={deleteSelf}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.delete}
|
||||
</Button>
|
||||
: null}
|
||||
{canEdit ?
|
||||
<Button
|
||||
color={"Yellow"}
|
||||
icon={faPencilAlt}
|
||||
onClick={onEditClick}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.edit}
|
||||
</Button>
|
||||
: null}
|
||||
{canArchive ?
|
||||
<Button
|
||||
color={"Grey"}
|
||||
icon={faArchive}
|
||||
onClick={archiveSelf}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.archive}
|
||||
</Button>
|
||||
: null}
|
||||
</>
|
||||
|
||||
return (
|
||||
<Summary
|
||||
icon={repo.is_active ? faFolderOpen : faFolder}
|
||||
title={repo.name}
|
||||
subtitle={repo.owner ? repo.owner.username : null}
|
||||
onClick={onRepoClick}
|
||||
upperLabel={strings.created}
|
||||
upperValue={repo.start ? new Date(repo.start).toLocaleString() : null}
|
||||
lowerLabel={strings.archived}
|
||||
lowerValue={repo.end ? new Date(repo.end).toLocaleString() : null}
|
||||
buttons={buttons}
|
||||
{...props}
|
||||
/>
|
||||
<SummaryBase>
|
||||
<SummaryLeft
|
||||
icon={repo.is_active ? faFolderOpen : faFolder}
|
||||
title={repo.name}
|
||||
subtitle={repo.owner ? repo.owner.username : null}
|
||||
onClick={onRepoClick}
|
||||
/>
|
||||
<SummaryLabels
|
||||
upperLabel={strings.created}
|
||||
upperValue={repo.start ? new Date(repo.start).toLocaleString() : null}
|
||||
lowerLabel={strings.archived}
|
||||
lowerValue={repo.end ? new Date(repo.end).toLocaleString() : null}
|
||||
/>
|
||||
{canDelete ?
|
||||
<SummaryButton
|
||||
color={"Red"}
|
||||
icon={faTrash}
|
||||
onClick={deleteSelf}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.delete}
|
||||
</SummaryButton>
|
||||
: null}
|
||||
{canEdit ?
|
||||
<SummaryButton
|
||||
color={"Yellow"}
|
||||
icon={faPencilAlt}
|
||||
onClick={onEditClick}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.edit}
|
||||
</SummaryButton>
|
||||
: null}
|
||||
{canArchive ?
|
||||
<SummaryButton
|
||||
color={"Grey"}
|
||||
icon={faArchive}
|
||||
onClick={archiveSelf}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.archive}
|
||||
</SummaryButton>
|
||||
: null}
|
||||
<SummaryRight/>
|
||||
</SummaryBase>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,41 +1,40 @@
|
|||
import React, { useContext } from "react"
|
||||
import Summary from "../base/Summary"
|
||||
import { faStar, faTrash, faUser } from "@fortawesome/free-solid-svg-icons"
|
||||
import Button from "../base/Button"
|
||||
import ContextUser from "../../contexts/ContextUser"
|
||||
import ContextLanguage from "../../contexts/ContextLanguage"
|
||||
import SummaryBase from "../base/summary/SummaryBase"
|
||||
import SummaryLeft from "../base/summary/SummaryLeft"
|
||||
import SummaryLabels from "../base/summary/SummaryLabels"
|
||||
import SummaryButton from "../base/summary/SummaryButton"
|
||||
import SummaryRight from "../base/summary/SummaryRight"
|
||||
|
||||
|
||||
export default function SummaryUser({ user, destroyUser, running, ...props }) {
|
||||
const { user: loggedUser } = useContext(ContextUser)
|
||||
const { strings } = useContext(ContextLanguage)
|
||||
|
||||
const buttons = <>
|
||||
{loggedUser.email !== user.email ?
|
||||
<Button
|
||||
color={"Red"}
|
||||
icon={faTrash}
|
||||
onClick={async event => {
|
||||
event.stopPropagation()
|
||||
// TODO: Errors are not caught here. Where should they be displayed?
|
||||
await destroyUser(user["email"])
|
||||
}}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.delete}
|
||||
</Button>
|
||||
: null}
|
||||
</>
|
||||
|
||||
return (
|
||||
<Summary
|
||||
icon={user.isAdmin ? faStar : faUser}
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
upperLabel={strings.type}
|
||||
upperValue={user.isAdmin ? strings.admin : strings.user}
|
||||
buttons={buttons}
|
||||
{...props}
|
||||
/>
|
||||
<SummaryBase>
|
||||
<SummaryLeft
|
||||
icon={user.isAdmin ? faStar : faUser}
|
||||
title={user.username}
|
||||
subtitle={user.email}
|
||||
/>
|
||||
<SummaryLabels
|
||||
upperLabel={strings.type}
|
||||
upperValue={user.isAdmin ? strings.admin : strings.user}
|
||||
/>
|
||||
<SummaryButton
|
||||
color={"Red"}
|
||||
icon={faTrash}
|
||||
onClick={async event => {
|
||||
event.stopPropagation()
|
||||
// TODO: Errors are not caught here. Where should they be displayed?
|
||||
await destroyUser(user["email"])
|
||||
}}
|
||||
disabled={running}
|
||||
>
|
||||
{strings.delete}
|
||||
</SummaryButton>
|
||||
<SummaryRight/>
|
||||
</SummaryBase>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -83,3 +83,15 @@ h1, h2, h3, h4, h5, h6 {
|
|||
|
||||
--outline: black;
|
||||
}
|
||||
|
||||
.Clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.Clickable:hover {
|
||||
filter: brightness(110%);
|
||||
}
|
||||
|
||||
.Clickable:active {
|
||||
filter: brightness(125%);
|
||||
}
|
Loading…
Reference in a new issue