mirror of
https://github.com/pds-nest/nest.git
synced 2024-11-25 06:24:19 +00:00
📔 Document _everything_ and refactor some things
This commit is contained in:
parent
449611f805
commit
2147e6c374
18 changed files with 192 additions and 66 deletions
|
@ -13,6 +13,12 @@ import PageSandbox from "./routes/PageSandbox"
|
|||
import useSavedTheme from "./hooks/useSavedTheme"
|
||||
|
||||
|
||||
/**
|
||||
* The main component of the webapp, the root of the render tree, what is displayed when the web page is visited.
|
||||
*
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function App() {
|
||||
const [theme, setAndSaveTheme] = useSavedTheme();
|
||||
|
||||
|
|
|
@ -3,6 +3,15 @@ import Style from "./BoxHeaderOnly.module.css"
|
|||
import classNames from "classnames"
|
||||
|
||||
|
||||
/**
|
||||
* A box with only the header part and with no body.
|
||||
*
|
||||
* @param children - What should be displayed inside the header.
|
||||
* @param className - Additional class(es) that should be added to the box.
|
||||
* @param props - Additional props to pass to the box.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function BoxHeaderOnly({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.BoxHeaderOnly, className)} {...props}>
|
||||
|
|
|
@ -1,47 +1,26 @@
|
|||
import React, {isValidElement} from "react"
|
||||
import React from "react"
|
||||
import Style from "./BoxWithHeader.module.css"
|
||||
import classNames from "classnames"
|
||||
import isString from "is-string"
|
||||
|
||||
|
||||
export default function BoxWithHeader({ header, body, children, className, ...props }) {
|
||||
|
||||
if(isValidElement(header) || isString(header)) {
|
||||
header = {
|
||||
children: header
|
||||
}
|
||||
}
|
||||
|
||||
if(isValidElement(body) || isString(body)) {
|
||||
body = {
|
||||
children: body
|
||||
}
|
||||
}
|
||||
|
||||
if(header === undefined) {
|
||||
header = {}
|
||||
}
|
||||
|
||||
if(body === undefined) {
|
||||
body = {}
|
||||
}
|
||||
|
||||
if(children) {
|
||||
|
||||
if(body.children) {
|
||||
throw new Error("If directly passing `children` to BoxWithHeader, body.children should not be set.")
|
||||
}
|
||||
|
||||
body["children"] = children
|
||||
}
|
||||
|
||||
/**
|
||||
* A box with both a header and a body.
|
||||
*
|
||||
* @param header - The contents of the box header.
|
||||
* @param children - The contents of the box body.
|
||||
* @param className - Additional class(es) that should be added to the outer `<div>` of the box.
|
||||
* @param props - Additional props to pass to the box.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function BoxWithHeader({ header, children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.BoxWithHeader, className)} {...props}>
|
||||
<div className={classNames(Style.BoxHeader, header.className)}>
|
||||
{header.children}
|
||||
<div className={Style.BoxHeader}>
|
||||
{header}
|
||||
</div>
|
||||
<div className={classNames(Style.BoxBody, body.className)}>
|
||||
{body.children}
|
||||
<div className={Style.BoxBody}>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -4,8 +4,18 @@ import classNames from "classnames"
|
|||
import make_icon from "../utils/make_icon"
|
||||
|
||||
|
||||
/**
|
||||
* A clickable button.
|
||||
*
|
||||
* @param children - The contents of the button.
|
||||
* @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.
|
||||
* @param props - Additional props to pass to the button.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Button({ children, className, color, icon, ...props }) {
|
||||
|
||||
return (
|
||||
<button type={"button"} className={classNames(Style.Button, Style[`Button${color}`], className)} {...props}>
|
||||
{children} {make_icon(icon, Style.Icon)}
|
||||
|
|
|
@ -6,6 +6,17 @@ import { Link } from "react-router-dom"
|
|||
import { useRouteMatch } from "react-router"
|
||||
|
||||
|
||||
/**
|
||||
* A button residing in the sidebar, used to switch between pages.
|
||||
*
|
||||
* @param icon - The FontAwesome IconDefinition of the icon that should be rendered in the button.
|
||||
* @param children - The contents of the button.
|
||||
* @param to - The path of the page the user should be redirected to when clicking on the button.
|
||||
* @param className - Additional class(es) to add to the button.
|
||||
* @param props - Additional props to pass to the button.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function ButtonSidebar({ icon, children, to, className, ...props }) {
|
||||
const match = useRouteMatch({
|
||||
path: to,
|
||||
|
|
|
@ -3,10 +3,16 @@ import Style from "./Checkbox.module.css"
|
|||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function Checkbox({ children, className, ...props }) {
|
||||
/**
|
||||
* A checkbox.
|
||||
*
|
||||
* @param className - Additional class(es) to add to the checkbox.
|
||||
* @param props - Additional props to pass to the checkbox.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Checkbox({ className, ...props }) {
|
||||
return (
|
||||
<input type={"checkbox"} className={classNames(Style.Checkbox, className)} {...props}>
|
||||
{children}
|
||||
</input>
|
||||
<input type={"checkbox"} className={classNames(Style.Checkbox, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,10 +3,16 @@ import Style from "./Input.module.css"
|
|||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function Input({ children, className, ...props }) {
|
||||
/**
|
||||
* A single-line box where the user can enter text.
|
||||
*
|
||||
* @param className - Additional class(es) to add to the element.
|
||||
* @param props - Additional props to pass to the element.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Input({ className, ...props }) {
|
||||
return (
|
||||
<input className={classNames(Style.Input, className)} {...props}>
|
||||
{children}
|
||||
</input>
|
||||
<input className={classNames(Style.Input, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,29 +4,41 @@ import classNames from "classnames"
|
|||
import make_icon from "../utils/make_icon"
|
||||
|
||||
|
||||
export default function InputWithIcon({ icon, className, style, onFocus, onBlur, ...props }) {
|
||||
/**
|
||||
* Like an {@link Input}, but with an icon on the left side.
|
||||
*
|
||||
* Has a state which stores whether the inner input element is focused or not to change the style of the whole element
|
||||
* on focus.
|
||||
*
|
||||
* @param icon - The FontAwesome IconDefinition of the icon that should be rendered in the input.
|
||||
* @param className - Additional class(es) to be added to the outer container.
|
||||
* @param props - Additional props to be passed to the outer container.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function InputWithIcon({ icon, className, ...props }) {
|
||||
const [isFocused, setFocused] = useState(false);
|
||||
|
||||
return (
|
||||
<div className={classNames(Style.InputWithIcon, isFocused ? Style.Focused : null, className)} style={style}>
|
||||
<div className={classNames(Style.IconPart)}>
|
||||
<div className={classNames(Style.InputWithIcon, isFocused ? Style.Focused : null, className)}>
|
||||
<div className={Style.IconPart}>
|
||||
{make_icon(icon)}
|
||||
</div>
|
||||
<input
|
||||
className={classNames(Style.InputPart)}
|
||||
className={Style.InputPart}
|
||||
onFocus={
|
||||
event => {
|
||||
setFocused(true)
|
||||
if(onFocus) {
|
||||
onFocus(event)
|
||||
if(props.onFocus) {
|
||||
props.onFocus(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
onBlur={
|
||||
event => {
|
||||
setFocused(false)
|
||||
if(onBlur) {
|
||||
onBlur(event)
|
||||
if(props.onBlur) {
|
||||
props.onBlur(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import React, { Fragment } from "react"
|
||||
import Style from "./Label.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
/**
|
||||
* A row of a {@link LabelledForm}.
|
||||
* It displays a label on the first column and a container for the labelled element on the second column.
|
||||
*
|
||||
* @param children - The labelled element.
|
||||
* @param text - Text to be displayed in the label.
|
||||
* @param for_ - The `[id]` of the labelled element.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Label({ children, text, for_ }) {
|
||||
return (
|
||||
<Fragment>
|
||||
|
|
|
@ -3,6 +3,17 @@ import Style from "./LabelledForm.module.css"
|
|||
import classNames from "classnames"
|
||||
|
||||
|
||||
/**
|
||||
* A form with two columns: the leftmost one contains labels, while the rightmost one contains input elements.
|
||||
*
|
||||
* The {@link Label} element can be used to quickly generate labelled input elements.
|
||||
*
|
||||
* @param children - The contents of the form.
|
||||
* @param className - Additional class(es) to be added to the form.
|
||||
* @param props - Additional props to be passed to the form.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function LabelledForm({ children, className, ...props }) {
|
||||
return (
|
||||
<form className={classNames(Style.LabelledForm, className)} {...props}>
|
||||
|
|
|
@ -4,6 +4,15 @@ import classNames from "classnames"
|
|||
import Sidebar from "./Sidebar"
|
||||
|
||||
|
||||
/**
|
||||
* The base page layout, consisting of a {@link Sidebar} on the left and the page contents on the remaining space.
|
||||
*
|
||||
* @param children - The page contents.
|
||||
* @param className - Additional class(es) to be added to the grid container.
|
||||
* @param props - Additional props to be passed to the grid container.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Layout({ children, className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.Layout, className)} {...props}>
|
||||
|
|
|
@ -1,10 +1,24 @@
|
|||
import React, {useContext} from "react"
|
||||
import Style from "./Logo.module.css"
|
||||
import LogoDark from "../media/LogoDark.png"
|
||||
import LogoLight from "../media/LogoLight.png"
|
||||
import ContextTheme from "../contexts/ContextTheme"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function Logo({ children, className, ...props }) {
|
||||
/**
|
||||
* The N.E.S.T. logo.
|
||||
*
|
||||
* It loads a different image based on the currently selected theme.
|
||||
*
|
||||
* @param className - Additional class(es) to add to the image.
|
||||
* @param props - Additional props to pass to the image.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Logo({ className, ...props }) {
|
||||
// I have no idea why IntelliJ is complaining about this line
|
||||
// It's perfectly fine!
|
||||
const [theme, ] = useContext(ContextTheme)
|
||||
|
||||
let logo;
|
||||
|
@ -19,6 +33,6 @@ export default function Logo({ children, className, ...props }) {
|
|||
}
|
||||
|
||||
return (
|
||||
<img src={logo} alt={"N.E.S.T."} {...props}/>
|
||||
<img src={logo} className={classNames(Style.Logo, className)} alt={"N.E.S.T."} {...props}/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,11 +2,16 @@ import React from "react"
|
|||
import Style from "./Radio.module.css"
|
||||
import classNames from "classnames"
|
||||
|
||||
|
||||
export default function Radio({ children, className, ...props }) {
|
||||
/**
|
||||
* A radio button.
|
||||
*
|
||||
* @param className - Additional class(es) to add to the checkbox.
|
||||
* @param props - Additional props to pass to the checkbox.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Radio({ className, ...props }) {
|
||||
return (
|
||||
<input type={"radio"} className={classNames(Style.Radio, className)} {...props}>
|
||||
{children}
|
||||
</input>
|
||||
<input type={"radio"} className={classNames(Style.Radio, className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,15 @@ import Style from "./Select.module.css"
|
|||
import classNames from "classnames"
|
||||
|
||||
|
||||
/**
|
||||
* A dropdown list.
|
||||
*
|
||||
* @param children - The contents of the dropdown list, which usually should be `<option>` or `<optgroup>` elements.
|
||||
* @param className - Additional class(es) to add to the element.
|
||||
* @param props - Additional props to pass to the element.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Select({ children, className, ...props }) {
|
||||
return (
|
||||
<select className={classNames(Style.Select, className)} {...props}>
|
||||
|
|
|
@ -3,7 +3,15 @@ import Select from "./Select"
|
|||
import ContextTheme from "../contexts/ContextTheme"
|
||||
|
||||
|
||||
export default function SelectTheme({ children, ...props }) {
|
||||
/**
|
||||
* A {@link Select} which allows the user to choose between the various available themes, switching between them as soon
|
||||
* as the user selects a different one.
|
||||
*
|
||||
* @param props - Additional props to pass to the {@link Select}.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function SelectTheme({ ...props }) {
|
||||
const [theme, setTheme] = useContext(ContextTheme);
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,10 +6,18 @@ import ButtonSidebar from "./ButtonSidebar"
|
|||
import { faCog, faExclamationTriangle, faFolder, faHome, faWrench } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
|
||||
export default function Sidebar({ children, className, ...props }) {
|
||||
/**
|
||||
* The sidebar of the website, with the logo, buttons to switch between the various pages and a notification counter.
|
||||
*
|
||||
* @todo The notification counter is still missing.
|
||||
* @param className - Additional class(es) to be added to the outer container.
|
||||
* @param props - Additional props to be passed to the outer container.
|
||||
* @returns {JSX.Element}
|
||||
* @constructor
|
||||
*/
|
||||
export default function Sidebar({ className, ...props }) {
|
||||
return (
|
||||
<div className={classNames(Style.Sidebar, className)} {...props}>
|
||||
{/* TODO: Aggiungere il logo qui! */}
|
||||
<Logo/>
|
||||
<ButtonSidebar to={"/"} icon={faHome}>Dashboard</ButtonSidebar>
|
||||
<ButtonSidebar to={"/repositories"} icon={faFolder}>Repositories</ButtonSidebar>
|
||||
|
@ -20,7 +28,6 @@ export default function Sidebar({ children, className, ...props }) {
|
|||
<ButtonSidebar to={"/sandbox"} icon={faWrench}>Sandbox</ButtonSidebar>
|
||||
: null
|
||||
}
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,4 +1,12 @@
|
|||
import {createContext} from "react";
|
||||
|
||||
|
||||
/**
|
||||
* A context containing a list with the currently selected theme and the function to set a different one.
|
||||
*
|
||||
* The function can be `undefined` if run outside a provider.
|
||||
*
|
||||
* @type {React.Context}
|
||||
*/
|
||||
const ContextTheme = createContext(["ThemeDark", undefined])
|
||||
export default ContextTheme
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
import { useState } from "react"
|
||||
|
||||
|
||||
/**
|
||||
* Hook with the same API as {@link React.useState} which stores the user's current theme setting, and syncs it to the
|
||||
* browser's {@link localStorage}.
|
||||
*
|
||||
* @todo Perhaps this could be refactored into a general "useLocalStorageState" component?
|
||||
* @returns {[string, function]}
|
||||
*/
|
||||
export default function useSavedTheme() {
|
||||
const loadTheme = () => {
|
||||
if(localStorage) {
|
||||
|
|
Loading…
Reference in a new issue