1
Fork 0
mirror of https://github.com/pds-nest/nest.git synced 2024-11-25 14:34:19 +00:00

Merge remote-tracking branch 'origin/main'

This commit is contained in:
@uni-chiara 2021-05-18 18:50:25 +02:00
commit 2678dc66c6
37 changed files with 509 additions and 319 deletions

View file

@ -0,0 +1,40 @@
import React from "react"
import Style from "./Badge.module.css"
import classNames from "classnames"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import ButtonSmallX from "./ButtonSmallX"
/**
* A small colored badge.
*
* @param icon - The icon that the badge should have on the left.
* @param color - The color that the badge should have.
* @param onClickDelete - The action to perform when the X button is clicked. If undefined, the X button won't be shown.
* @param children - The text of the badge.
* @param className - Additional class(es) to append to the badge.
* @param props - Additional props to pass to the badge.
* @returns {JSX.Element}
* @constructor
*/
export default function Badge({ icon, color, onClickDelete, children, className, ...props }) {
return (
<div className={classNames(Style.Badge, Style[`Badge${color}`], className)} {...props}>
<div className={Style.Icon}>
<FontAwesomeIcon icon={icon}/>
</div>
<div className={Style.Text}>
{children}
</div>
{
onClickDelete ?
<div>
<ButtonSmallX
onClick={onClickDelete}
/>
</div>
: null
}
</div>
)
}

View file

@ -0,0 +1,38 @@
.Badge {
display: inline-flex;
gap: 5px;
padding: 0 5px;
border-radius: 25px;
margin: 0 2px;
}
.BadgeRed {
background-color: var(--bg-red);
color: var(--fg-red)
}
.BadgeYellow {
background-color: var(--bg-yellow);
color: var(--fg-yellow)
}
.BadgeGrey {
background-color: var(--bg-grey);
color: var(--fg-grey)
}
.BadgeGreen {
background-color: var(--bg-green);
color: var(--fg-green)
}
.Text {
max-width: 350px;
overflow-x: hidden;
}
.Icon {
width: 15px;
text-align: center;
}

View file

@ -0,0 +1,33 @@
import React from "react"
import Style from "./BoxMap.module.css"
import BoxFull from "./BoxFull"
import { MapContainer, TileLayer } from "react-leaflet"
export default function BoxMap({ header, setMap, startingPosition, startingZoom, button, children, additions, ...props }) {
return (
<BoxFull
header={header}
childrenClassName={Style.BoxMapContents}
{...props}
>
<MapContainer
center={startingPosition}
zoom={startingZoom}
className={Style.MapContainer}
whenCreated={setMap}
>
<TileLayer
attribution='(c) <a href="https://osm.org/copyright">OpenStreetMap contributors</a>'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{additions}
<div className={"leaflet-top leaflet-right"}>
<div className={"leaflet-control"}>
{button}
</div>
</div>
</MapContainer>
</BoxFull>
)
}

View file

@ -1,4 +1,4 @@
.BoxConditionMapContents {
.BoxMapContents {
display: flex;
flex-direction: column;
padding: 0;
@ -9,6 +9,3 @@
height: 300px;
border-radius: 0 0 25px 25px;
}
.Button {
}

View file

@ -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>
)

View file

@ -45,8 +45,6 @@
.Icon {
font-size: large;
/* TODO: non so quanto sia una buona idea, ma funziona accettabilmente */
line-height: 0;
vertical-align: sub;
}

View file

@ -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}

View file

@ -12,12 +12,15 @@
top: -1px;
}
.ButtonSmallX:focus {
outline: none;
.ButtonSmallX:hover {
background-color: var(--bg-field-on);
color: var(--fg-field-on);
}
.ButtonSmallX:focus {
outline: none;
}
.ButtonSmallX:focus-visible {
outline: 2px dashed var(--outline);
}

View file

@ -6,7 +6,7 @@ import classNames from "classnames"
/**
* A slider that allows to select a numeric value in a range.
*
* @todo Custom styling only works on Firefox!
* Custom styling only works on Firefox!
*
* @param className - Additional class(es) to add to the element.
* @param props - Additional props to pass to the element.

View file

@ -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>
)
}

View file

@ -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;
}

View 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>
)
}

View file

@ -0,0 +1,8 @@
.SummaryBase {
width: 100%;
height: 60px;
margin: 10px 0;
display: flex;
}

View 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}/>
)
}

View file

@ -0,0 +1,8 @@
.SummaryButton {
width: 90px;
box-shadow: none;
border-radius: 0;
margin: 0;
}

View 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>
)
}

View file

@ -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;
}

View 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>
)
}

View 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;
}

View 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>
)
}

View file

@ -0,0 +1,7 @@
.SummaryRight {
min-width: 30px;
padding: 5px;
background-color: var(--bg-accent);
border-radius: 0 30px 30px 0;
}

View 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>
)
}

View file

@ -1,10 +1,7 @@
import React, { useContext } from "react"
import Style from "./ConditionBadge.module.css"
import classNames from "classnames"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import ButtonSmallX from "../base/ButtonSmallX"
import { faAt, faClock, faGlobe, faHashtag, faMapPin } from "@fortawesome/free-solid-svg-icons"
import ContextRepositoryEditor from "../../contexts/ContextRepositoryEditor"
import Badge from "../base/Badge"
const CONDITION_COLORS = {
@ -26,13 +23,13 @@ const CONDITION_ICONS = {
/**
* A small colored badge representing a Condition for a filter.
* A {@link Badge} representing a Condition for a filter.
*
* @param condition - The Condition that this badge represents.
* @returns {JSX.Element}
* @constructor
*/
export default function ConditionBadge({ ...condition }) {
export default function BadgeCondition({ ...condition }) {
const { id, type, content } = condition
const color = CONDITION_COLORS[type]
const icon = CONDITION_ICONS[type]
@ -53,24 +50,16 @@ export default function ConditionBadge({ ...condition }) {
}
return (
<div
<Badge
title={id ? `💠 Condition ID: ${id}` : "✨ New Condition"}
className={classNames(Style.ConditionBadge, Style[`ConditionBadge${color}`])}
>
<div className={Style.Icon}>
<FontAwesomeIcon icon={icon}/>
</div>
<div className={Style.Text}>
{displayedContent}
</div>
<div>
<ButtonSmallX
onClick={() => {
color={color}
icon={icon}
onClickDelete={() => {
console.debug(`Removing Condition: `, condition)
removeRawCondition(condition)
}}
/>
</div>
</div>
>
{displayedContent}
</Badge>
)
}

View file

@ -1,19 +1,16 @@
import React, { useCallback, useContext, useEffect, useState } from "react"
import BoxFull from "../base/BoxFull"
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
import { faMapPin, faPlus } from "@fortawesome/free-solid-svg-icons"
import Style from "./BoxConditionMap.module.css"
import ButtonIconOnly from "../base/ButtonIconOnly"
import { MapContainer, TileLayer } from "react-leaflet"
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
import Condition from "../../utils/Condition"
import ContextLanguage from "../../contexts/ContextLanguage"
import BoxMap from "../base/BoxMap"
const STARTING_POSITION = { lat: 41.89309, lng: 12.48289 }
const STARTING_ZOOM = 3
// FIXME: this only works correctly at the equator!
/**
* https://wiki.openstreetmap.org/wiki/Zoom_levels
*/
@ -99,7 +96,7 @@ export default function BoxConditionMap({ ...props }) {
}
return (
<BoxFull
<BoxMap
header={
<span>
{strings.searchBy}
@ -109,30 +106,17 @@ export default function BoxConditionMap({ ...props }) {
{strings.byZone}
</span>
}
childrenClassName={Style.BoxConditionMapContents}
{...props}
>
<MapContainer
center={STARTING_POSITION}
zoom={STARTING_ZOOM}
className={Style.MapContainer}
whenCreated={setMap}
>
<TileLayer
attribution='(c) <a href="https://osm.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<div className={"leaflet-top leaflet-right"}>
<div className={"leaflet-control"}>
startingPosition={STARTING_POSITION}
startingZoom={STARTING_ZOOM}
setMap={setMap}
button={
<ButtonIconOnly
className={Style.Button}
icon={faPlus}
color={"Green"}
onClick={onButtonClick}
/>
</div>
</div>
</MapContainer>
</BoxFull>
}
{...props}
/>
)
}

View file

@ -1,12 +1,12 @@
import React, { useContext } from "react"
import BoxFull from "../base/BoxFull"
import ConditionBadge from "./ConditionBadge"
import BadgeCondition from "./BadgeCondition"
import useRepositoryEditor from "../../hooks/useRepositoryEditor"
import ContextLanguage from "../../contexts/ContextLanguage"
/**
* A box which renders all conditions of the {@link RepositoryEditor} as {@link ConditionBadge}s.
* A box which renders all conditions of the {@link RepositoryEditor} as {@link BadgeCondition}s.
*
* @param props
* @returns {JSX.Element}
@ -16,7 +16,7 @@ export default function BoxConditions({ ...props }) {
const { conditions } = useRepositoryEditor()
const { strings } = useContext(ContextLanguage)
const badges = conditions.map((cond, pos) => <ConditionBadge key={pos} {...cond}/>)
const badges = conditions.map((cond, pos) => <BadgeCondition key={pos} {...cond}/>)
return (
<BoxFull header={strings.conditions} {...props}>

View file

@ -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}
/>
))

View file

@ -11,7 +11,6 @@ import ContextLanguage from "../../contexts/ContextLanguage"
/**
* 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}

View file

@ -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 = <>
return (
<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 ?
<Button
<SummaryButton
color={"Red"}
icon={faTrash}
onClick={deleteSelf}
disabled={running}
>
{strings.delete}
</Button>
</SummaryButton>
: null}
{canEdit ?
<Button
<SummaryButton
color={"Yellow"}
icon={faPencilAlt}
onClick={onEditClick}
disabled={running}
>
{strings.edit}
</Button>
</SummaryButton>
: null}
{canArchive ?
<Button
<SummaryButton
color={"Grey"}
icon={faArchive}
onClick={archiveSelf}
disabled={running}
>
{strings.archive}
</Button>
</SummaryButton>
: 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}
/>
<SummaryRight/>
</SummaryBase>
)
}

View file

@ -1,18 +1,28 @@
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
return (
<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 => {
@ -23,19 +33,8 @@ export default function SummaryUser({ user, destroyUser, running, ...props }) {
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}
/>
</SummaryButton>
<SummaryRight/>
</SummaryBase>
)
}

View file

@ -0,0 +1,22 @@
import React from "react"
import Style from "./RepositoryViewer.module.css"
import classNames from "classnames"
import ContextRepositoryViewer from "../../contexts/ContextRepositoryViewer"
export default function RepositoryViewer({
id,
}) {
return (
<ContextRepositoryViewer.Provider
value={{
id,
}}
>
<div className={classNames(Style.RepositoryViewer, className)}>
</div>
</ContextRepositoryViewer.Provider>
)
}

View file

@ -0,0 +1,16 @@
.RepositoryViewer {
display: grid;
grid-template-areas:
"b c d"
"b e e"
"b f f"
"b g g";
grid-template-columns: 400px 1fr 1fr;
grid-template-rows: auto auto 1fr auto;
grid-gap: 10px;
width: 100%;
height: 100%;
}

View file

@ -2,6 +2,6 @@ import { createContext } from "react"
/**
* @todo Document this.
* Context to quickly pass props to the children of {@link RepositoryEditor}.
*/
export default createContext(null)

View file

@ -0,0 +1,7 @@
import { createContext } from "react"
/**
* Context to quickly pass props to the children of {@link RepositoryViewer}.
*/
export default createContext(null)

View file

@ -3,12 +3,12 @@ import ContextRepositoryEditor from "../contexts/ContextRepositoryEditor"
/**
* @todo Document this.
* Hook to quickly use {@link ContextRepositoryEditor}.
*/
export default function useRepositoryEditor() {
const context = useContext(ContextRepositoryEditor)
if(!context) {
throw new Error("Questo componente deve essere messo in un RepositoryEditor.")
throw new Error("This component must be placed inside a RepositoryEditor.")
}
return context
}

View file

@ -0,0 +1,14 @@
import { useContext } from "react"
import ContextRepositoryViewer from "../contexts/ContextRepositoryViewer"
/**
* Hook to quickly use {@link ContextRepositoryViewer}.
*/
export default function useRepositoryViewer() {
const context = useContext(ContextRepositoryViewer)
if(!context) {
throw new Error("This component must be placed inside a RepositoryViewer.")
}
return context
}

View file

@ -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%);
}