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"
|
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() {
|
export default function App() {
|
||||||
const [theme, setAndSaveTheme] = useSavedTheme();
|
const [theme, setAndSaveTheme] = useSavedTheme();
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,15 @@ import Style from "./BoxHeaderOnly.module.css"
|
||||||
import classNames from "classnames"
|
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 }) {
|
export default function BoxHeaderOnly({ children, className, ...props }) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.BoxHeaderOnly, className)} {...props}>
|
<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 Style from "./BoxWithHeader.module.css"
|
||||||
import classNames from "classnames"
|
import classNames from "classnames"
|
||||||
import isString from "is-string"
|
|
||||||
|
|
||||||
|
|
||||||
export default function BoxWithHeader({ header, body, children, className, ...props }) {
|
/**
|
||||||
|
* A box with both a header and a body.
|
||||||
if(isValidElement(header) || isString(header)) {
|
*
|
||||||
header = {
|
* @param header - The contents of the box header.
|
||||||
children: 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}
|
||||||
if(isValidElement(body) || isString(body)) {
|
* @constructor
|
||||||
body = {
|
*/
|
||||||
children: body
|
export default function BoxWithHeader({ header, children, className, ...props }) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.BoxWithHeader, className)} {...props}>
|
<div className={classNames(Style.BoxWithHeader, className)} {...props}>
|
||||||
<div className={classNames(Style.BoxHeader, header.className)}>
|
<div className={Style.BoxHeader}>
|
||||||
{header.children}
|
{header}
|
||||||
</div>
|
</div>
|
||||||
<div className={classNames(Style.BoxBody, body.className)}>
|
<div className={Style.BoxBody}>
|
||||||
{body.children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,18 @@ import classNames from "classnames"
|
||||||
import make_icon from "../utils/make_icon"
|
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 }) {
|
export default function Button({ children, className, color, icon, ...props }) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button type={"button"} className={classNames(Style.Button, Style[`Button${color}`], className)} {...props}>
|
<button type={"button"} className={classNames(Style.Button, Style[`Button${color}`], className)} {...props}>
|
||||||
{children} {make_icon(icon, Style.Icon)}
|
{children} {make_icon(icon, Style.Icon)}
|
||||||
|
|
|
@ -6,6 +6,17 @@ import { Link } from "react-router-dom"
|
||||||
import { useRouteMatch } from "react-router"
|
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 }) {
|
export default function ButtonSidebar({ icon, children, to, className, ...props }) {
|
||||||
const match = useRouteMatch({
|
const match = useRouteMatch({
|
||||||
path: to,
|
path: to,
|
||||||
|
|
|
@ -3,10 +3,16 @@ import Style from "./Checkbox.module.css"
|
||||||
import classNames from "classnames"
|
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 (
|
return (
|
||||||
<input type={"checkbox"} className={classNames(Style.Checkbox, className)} {...props}>
|
<input type={"checkbox"} className={classNames(Style.Checkbox, className)} {...props} />
|
||||||
{children}
|
|
||||||
</input>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,16 @@ import Style from "./Input.module.css"
|
||||||
import classNames from "classnames"
|
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 (
|
return (
|
||||||
<input className={classNames(Style.Input, className)} {...props}>
|
<input className={classNames(Style.Input, className)} {...props} />
|
||||||
{children}
|
|
||||||
</input>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,29 +4,41 @@ import classNames from "classnames"
|
||||||
import make_icon from "../utils/make_icon"
|
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);
|
const [isFocused, setFocused] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.InputWithIcon, isFocused ? Style.Focused : null, className)} style={style}>
|
<div className={classNames(Style.InputWithIcon, isFocused ? Style.Focused : null, className)}>
|
||||||
<div className={classNames(Style.IconPart)}>
|
<div className={Style.IconPart}>
|
||||||
{make_icon(icon)}
|
{make_icon(icon)}
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
className={classNames(Style.InputPart)}
|
className={Style.InputPart}
|
||||||
onFocus={
|
onFocus={
|
||||||
event => {
|
event => {
|
||||||
setFocused(true)
|
setFocused(true)
|
||||||
if(onFocus) {
|
if(props.onFocus) {
|
||||||
onFocus(event)
|
props.onFocus(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onBlur={
|
onBlur={
|
||||||
event => {
|
event => {
|
||||||
setFocused(false)
|
setFocused(false)
|
||||||
if(onBlur) {
|
if(props.onBlur) {
|
||||||
onBlur(event)
|
props.onBlur(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
import React, { Fragment } from "react"
|
import React, { Fragment } from "react"
|
||||||
import Style from "./Label.module.css"
|
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_ }) {
|
export default function Label({ children, text, for_ }) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
|
|
|
@ -3,6 +3,17 @@ import Style from "./LabelledForm.module.css"
|
||||||
import classNames from "classnames"
|
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 }) {
|
export default function LabelledForm({ children, className, ...props }) {
|
||||||
return (
|
return (
|
||||||
<form className={classNames(Style.LabelledForm, className)} {...props}>
|
<form className={classNames(Style.LabelledForm, className)} {...props}>
|
||||||
|
|
|
@ -4,6 +4,15 @@ import classNames from "classnames"
|
||||||
import Sidebar from "./Sidebar"
|
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 }) {
|
export default function Layout({ children, className, ...props }) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(Style.Layout, className)} {...props}>
|
<div className={classNames(Style.Layout, className)} {...props}>
|
||||||
|
|
|
@ -1,10 +1,24 @@
|
||||||
import React, {useContext} from "react"
|
import React, {useContext} from "react"
|
||||||
|
import Style from "./Logo.module.css"
|
||||||
import LogoDark from "../media/LogoDark.png"
|
import LogoDark from "../media/LogoDark.png"
|
||||||
import LogoLight from "../media/LogoLight.png"
|
import LogoLight from "../media/LogoLight.png"
|
||||||
import ContextTheme from "../contexts/ContextTheme"
|
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)
|
const [theme, ] = useContext(ContextTheme)
|
||||||
|
|
||||||
let logo;
|
let logo;
|
||||||
|
@ -19,6 +33,6 @@ export default function Logo({ children, className, ...props }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 Style from "./Radio.module.css"
|
||||||
import classNames from "classnames"
|
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 (
|
return (
|
||||||
<input type={"radio"} className={classNames(Style.Radio, className)} {...props}>
|
<input type={"radio"} className={classNames(Style.Radio, className)} {...props} />
|
||||||
{children}
|
|
||||||
</input>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,15 @@ import Style from "./Select.module.css"
|
||||||
import classNames from "classnames"
|
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 }) {
|
export default function Select({ children, className, ...props }) {
|
||||||
return (
|
return (
|
||||||
<select className={classNames(Style.Select, className)} {...props}>
|
<select className={classNames(Style.Select, className)} {...props}>
|
||||||
|
|
|
@ -3,7 +3,15 @@ import Select from "./Select"
|
||||||
import ContextTheme from "../contexts/ContextTheme"
|
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);
|
const [theme, setTheme] = useContext(ContextTheme);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -6,10 +6,18 @@ import ButtonSidebar from "./ButtonSidebar"
|
||||||
import { faCog, faExclamationTriangle, faFolder, faHome, faWrench } from "@fortawesome/free-solid-svg-icons"
|
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 (
|
return (
|
||||||
<div className={classNames(Style.Sidebar, className)} {...props}>
|
<div className={classNames(Style.Sidebar, className)} {...props}>
|
||||||
{/* TODO: Aggiungere il logo qui! */}
|
|
||||||
<Logo/>
|
<Logo/>
|
||||||
<ButtonSidebar to={"/"} icon={faHome}>Dashboard</ButtonSidebar>
|
<ButtonSidebar to={"/"} icon={faHome}>Dashboard</ButtonSidebar>
|
||||||
<ButtonSidebar to={"/repositories"} icon={faFolder}>Repositories</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>
|
<ButtonSidebar to={"/sandbox"} icon={faWrench}>Sandbox</ButtonSidebar>
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
{children}
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import {createContext} from "react";
|
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])
|
const ContextTheme = createContext(["ThemeDark", undefined])
|
||||||
export default ContextTheme
|
export default ContextTheme
|
||||||
|
|
|
@ -1,6 +1,13 @@
|
||||||
import { useState } from "react"
|
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() {
|
export default function useSavedTheme() {
|
||||||
const loadTheme = () => {
|
const loadTheme = () => {
|
||||||
if(localStorage) {
|
if(localStorage) {
|
||||||
|
|
Loading…
Reference in a new issue