Updated dependencies.
Added prettier and formatted. Fixed bug causing back button to not show at all times.
This commit is contained in:
@ -1,57 +1,59 @@
|
||||
/* @refresh reload */
|
||||
import { type Component, createSignal } from "solid-js";
|
||||
import { type Component, createSignal } from "solid-js"
|
||||
|
||||
interface SwitchProps extends TitleProps {
|
||||
defaultValue?: boolean,
|
||||
onChange?: (value: boolean) => void,
|
||||
defaultValue?: boolean
|
||||
onChange?: (value: boolean) => void
|
||||
}
|
||||
|
||||
export const MySwitch: Component<SwitchProps> = (
|
||||
{
|
||||
defaultValue = false,
|
||||
title,
|
||||
onChange,
|
||||
className,
|
||||
name,
|
||||
id
|
||||
}) => {
|
||||
export const MySwitch: Component<SwitchProps> = ({
|
||||
defaultValue = false,
|
||||
title,
|
||||
onChange,
|
||||
className,
|
||||
name,
|
||||
id
|
||||
}) => {
|
||||
const [checked, setChecked] = createSignal(defaultValue)
|
||||
|
||||
const [checked, setChecked] = createSignal(defaultValue);
|
||||
|
||||
function handleChange() {
|
||||
const newChecked = !checked();
|
||||
setChecked(newChecked);
|
||||
if (onChange) {
|
||||
onChange(newChecked);
|
||||
}
|
||||
function handleChange() {
|
||||
const newChecked = !checked()
|
||||
setChecked(newChecked)
|
||||
if (onChange) {
|
||||
onChange(newChecked)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<button id={ id }
|
||||
onClick={ handleChange }
|
||||
title={ title }
|
||||
class={ `${ checked() ? "bg-cyan-900" : "bg-gray-500" }
|
||||
relative inline-flex h-6 w-11 items-center rounded-full my-2 ${ className }` }>
|
||||
<span class={ "sr-only" }>{ name }</span>
|
||||
<span class={ `${ checked() ? 'translate-x-6' : 'translate-x-1' }
|
||||
inline-block h-4 w-4 transform rounded-full bg-white transition-all` } />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export const Button: Component<ButtonProps> = (
|
||||
{
|
||||
className,
|
||||
title,
|
||||
children,
|
||||
id,
|
||||
onClick,
|
||||
type = "button",
|
||||
}
|
||||
) => (
|
||||
<button title={ title } id={ id } type={ type }
|
||||
class={ `border-rounded bg-cyan-900 px-2 cursor-pointer ${ className }` }
|
||||
onClick={ onClick }>
|
||||
{ children }
|
||||
return (
|
||||
<button
|
||||
id={id}
|
||||
onClick={handleChange}
|
||||
title={title}
|
||||
class={`${checked() ? "bg-cyan-900" : "bg-gray-500"} relative my-2 inline-flex h-6 w-11 items-center rounded-full ${className}`}
|
||||
>
|
||||
<span class={"sr-only"}>{name}</span>
|
||||
<span
|
||||
class={`${checked() ? "translate-x-6" : "translate-x-1"} inline-block h-4 w-4 transform rounded-full bg-white transition-all`}
|
||||
/>
|
||||
</button>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export const Button: Component<ButtonProps> = ({
|
||||
className,
|
||||
title,
|
||||
children,
|
||||
id,
|
||||
onClick,
|
||||
type = "button"
|
||||
}) => (
|
||||
<button
|
||||
title={title}
|
||||
id={id}
|
||||
type={type}
|
||||
class={`border-rounded cursor-pointer bg-cyan-900 px-2 ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
)
|
||||
|
@ -1,23 +1,18 @@
|
||||
/* @refresh reload */
|
||||
import { type Component } from "solid-js";
|
||||
import { Link } from "./link";
|
||||
import { type Component } from "solid-js"
|
||||
import { Link } from "./link"
|
||||
|
||||
const Card: Component<CardProps> = (
|
||||
{
|
||||
children,
|
||||
className,
|
||||
title,
|
||||
to,
|
||||
newTab = false
|
||||
}) => (
|
||||
<div class={ `relative bg-gradient-to-r from-cyan-600 to-cyan-500 h-32 w-72 rounded-2xl ${ className }` }>
|
||||
<div class={ "relative p-5" }>
|
||||
<Link className={ "text-white" } to={ to } newTab={ newTab }>
|
||||
<h3 class={ "text-center w-fit mx-auto before:content-['↗']" }>{ title }</h3>
|
||||
</Link>
|
||||
{ children }
|
||||
</div>
|
||||
const Card: Component<CardProps> = ({ children, className, title, to, newTab = false }) => (
|
||||
<div
|
||||
class={`relative h-32 w-72 rounded-2xl bg-gradient-to-r from-cyan-600 to-cyan-500 ${className}`}
|
||||
>
|
||||
<div class={"relative p-5"}>
|
||||
<Link className={"text-white"} to={to} newTab={newTab}>
|
||||
<h3 class={"mx-auto w-fit text-center before:content-['↗']"}>{title}</h3>
|
||||
</Link>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Card;
|
||||
export default Card
|
||||
|
@ -1,104 +1,100 @@
|
||||
/* @refresh reload */
|
||||
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless";
|
||||
import { Component, createEffect, createSignal, JSX } from "solid-js";
|
||||
import { Button } from "./button";
|
||||
import { Portal } from "solid-js/web";
|
||||
import { getElementById } from "../utils/dom";
|
||||
import { Dialog, DialogDescription, DialogPanel, DialogTitle } from "solid-headless"
|
||||
import { Component, createEffect, createSignal, JSX } from "solid-js"
|
||||
import { Button } from "./button"
|
||||
import { Portal } from "solid-js/web"
|
||||
import { getElementById } from "../utils/dom"
|
||||
|
||||
interface MyDialog extends TitleProps {
|
||||
description?: string,
|
||||
button?: JSX.Element,
|
||||
acceptButtonName?: string,
|
||||
acceptButtonId?: string,
|
||||
cancelButtonName?: string,
|
||||
callback?: () => void,
|
||||
buttonClass?: string,
|
||||
buttonTitle?: string | null,
|
||||
description?: string
|
||||
button?: JSX.Element
|
||||
acceptButtonName?: string
|
||||
acceptButtonId?: string
|
||||
cancelButtonName?: string
|
||||
callback?: () => void
|
||||
buttonClass?: string
|
||||
buttonTitle?: string | null
|
||||
}
|
||||
|
||||
const MyDialog: Component<MyDialog> = (
|
||||
{
|
||||
title,
|
||||
description,
|
||||
button,
|
||||
acceptButtonName = "Ok",
|
||||
cancelButtonName = "Cancel",
|
||||
children,
|
||||
callback,
|
||||
className,
|
||||
buttonClass,
|
||||
buttonTitle,
|
||||
acceptButtonId,
|
||||
}) => {
|
||||
const MyDialog: Component<MyDialog> = ({
|
||||
title,
|
||||
description,
|
||||
button,
|
||||
acceptButtonName = "Ok",
|
||||
cancelButtonName = "Cancel",
|
||||
children,
|
||||
callback,
|
||||
className,
|
||||
buttonClass,
|
||||
buttonTitle,
|
||||
acceptButtonId
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = createSignal(false)
|
||||
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
function callbackAndClose(): void {
|
||||
callback?.()
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
function callbackAndClose(): void {
|
||||
if (callback) {
|
||||
callback();
|
||||
}
|
||||
setIsOpen(false);
|
||||
function setupKeyPress(): () => void {
|
||||
let isMounted = true
|
||||
|
||||
/**
|
||||
* Pressing "Enter" when the modal is open, will click the accept button
|
||||
* @param e KeyboardEvent of keypress
|
||||
*/
|
||||
function click(e: KeyboardEvent): void {
|
||||
if (isMounted && e.key === "Enter" && acceptButtonId) {
|
||||
getElementById<HTMLButtonElement>(acceptButtonId)?.click()
|
||||
}
|
||||
}
|
||||
|
||||
function setupKeyPress(): () => void {
|
||||
let isMounted = true;
|
||||
if (isOpen()) {
|
||||
const id = "cl-6"
|
||||
const el = getElementById(id)
|
||||
el?.addEventListener("keypress", click)
|
||||
return () => {
|
||||
el?.removeEventListener("keypress", click)
|
||||
isMounted = false
|
||||
}
|
||||
} else return () => undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Pressing "Enter" when the modal is open, will click the accept button
|
||||
* @param e KeyboardEvent of keypress
|
||||
*/
|
||||
function click(e: KeyboardEvent): void {
|
||||
if (isMounted && e.key === "Enter" && acceptButtonId) {
|
||||
getElementById<HTMLButtonElement>(acceptButtonId)?.click();
|
||||
}
|
||||
}
|
||||
createEffect(setupKeyPress, isOpen())
|
||||
|
||||
if (isOpen()) {
|
||||
const id = "cl-6"
|
||||
const el = getElementById(id);
|
||||
el?.addEventListener("keypress", click);
|
||||
return () => {
|
||||
el?.removeEventListener("keypress", click);
|
||||
isMounted = false;
|
||||
}
|
||||
}
|
||||
else return () => undefined;
|
||||
}
|
||||
return (
|
||||
<div class={"h-fit w-fit"}>
|
||||
<button onClick={() => setIsOpen(true)} class={buttonClass} title={buttonTitle ?? undefined}>
|
||||
{button}
|
||||
</button>
|
||||
|
||||
createEffect(setupKeyPress, isOpen());
|
||||
<Portal>
|
||||
<Dialog
|
||||
isOpen={isOpen()}
|
||||
onClose={() => setIsOpen(false)}
|
||||
class={`flex-row-center fixed inset-0 z-50 justify-center overflow-auto text-white ${className}`}
|
||||
>
|
||||
<div class={"fixed inset-0 bg-black/40" /*Backdrop*/} aria-hidden={true} />
|
||||
|
||||
return (
|
||||
<div class={ "w-fit h-fit" }>
|
||||
<DialogPanel class={"border-rounded relative w-fit border-gray-500 bg-default-bg p-2"}>
|
||||
<DialogTitle class={"border-b"}>{title}</DialogTitle>
|
||||
<DialogDescription class={"mb-4 mt-1"}>{description}</DialogDescription>
|
||||
|
||||
<button onClick={ () => setIsOpen(true) } class={ buttonClass } title={ buttonTitle ?? undefined }>
|
||||
{ button }
|
||||
</button>
|
||||
{children}
|
||||
|
||||
<Portal>
|
||||
<Dialog isOpen={ isOpen() } onClose={ () => setIsOpen(false) }
|
||||
class={ `fixed inset-0 flex-row-center justify-center z-50 overflow-auto text-white ${ className }` }>
|
||||
|
||||
<div class={ "fixed inset-0 bg-black/40" /*Backdrop*/ } aria-hidden={ true } />
|
||||
|
||||
<DialogPanel class={ "w-fit relative bg-default-bg border-rounded border-gray-500 p-2" }>
|
||||
<DialogTitle class={ "border-b" }>{ title }</DialogTitle>
|
||||
<DialogDescription class={ "mb-4 mt-1" }>{ description }</DialogDescription>
|
||||
|
||||
{ children }
|
||||
|
||||
<div class={ "my-3" }>
|
||||
<Button onClick={ callbackAndClose } className={ "h-10 mr-2" }
|
||||
id={ acceptButtonId }>{ acceptButtonName }</Button>
|
||||
<Button onClick={ () => setIsOpen(false) }
|
||||
className={ "h-10" }>{ cancelButtonName }</Button>
|
||||
</div>
|
||||
|
||||
</DialogPanel>
|
||||
|
||||
</Dialog>
|
||||
</Portal>
|
||||
</div>
|
||||
);
|
||||
<div class={"my-3"}>
|
||||
<Button onClick={callbackAndClose} className={"mr-2 h-10"} id={acceptButtonId}>
|
||||
{acceptButtonName}
|
||||
</Button>
|
||||
<Button onClick={() => setIsOpen(false)} className={"h-10"}>
|
||||
{cancelButtonName}
|
||||
</Button>
|
||||
</div>
|
||||
</DialogPanel>
|
||||
</Dialog>
|
||||
</Portal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyDialog;
|
||||
export default MyDialog
|
||||
|
@ -1,11 +1,13 @@
|
||||
/* @refresh reload */
|
||||
import { type Component } from "solid-js";
|
||||
import { Link } from "./link";
|
||||
import { type Component } from "solid-js"
|
||||
import { Link } from "./link"
|
||||
|
||||
const Footer: Component<SimpleProps> = ({ className }) => (
|
||||
<footer class={ `text-center py-5 absolute bottom-0 container ${ className }` }>
|
||||
<p>Kildekode på <Link to={ "https://github.com/h600878/martials.no" }>GitHub</Link></p>
|
||||
</footer>
|
||||
);
|
||||
<footer class={`container absolute bottom-0 py-5 text-center ${className}`}>
|
||||
<p>
|
||||
Kildekode på <Link to={"https://github.com/h600878/martials.no"}>GitHub</Link>
|
||||
</p>
|
||||
</footer>
|
||||
)
|
||||
|
||||
export default Footer;
|
||||
export default Footer
|
||||
|
@ -1,25 +1,29 @@
|
||||
/* @refresh reload */
|
||||
import { type Component, Show } from "solid-js";
|
||||
import { Icon } from "solid-heroicons";
|
||||
import { chevronLeft } from "solid-heroicons/solid";
|
||||
import { Link } from "./link";
|
||||
import { type Component, Show } from "solid-js"
|
||||
import { Icon } from "solid-heroicons"
|
||||
import { chevronLeft } from "solid-heroicons/solid"
|
||||
import { Link } from "./link"
|
||||
import { useLocation } from "@solidjs/router"
|
||||
|
||||
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => (
|
||||
<header class={ className }>
|
||||
<div class={ "flex-row-center mx-auto w-fit" }>
|
||||
const Header: Component<TitleProps> = ({ className, title = "Title goes here" }) => {
|
||||
const location = useLocation()
|
||||
|
||||
<Show when={ typeof location !== "undefined" && location.pathname !== "/" } keyed>
|
||||
<Link to={ "/" } newTab={ false } title={ "Back to homepage" }>
|
||||
<Icon path={ chevronLeft } class={ "text-cyan-500" } />
|
||||
</Link>
|
||||
</Show>
|
||||
return (
|
||||
<header class={className}>
|
||||
<div class={"flex-row-center mx-auto w-fit"}>
|
||||
<Show when={location.pathname !== "/"} keyed>
|
||||
<Link to={"/"} newTab={false} title={"Back to homepage"}>
|
||||
<Icon path={chevronLeft} class={"text-cyan-500"} />
|
||||
</Link>
|
||||
</Show>
|
||||
|
||||
<h1 class={ "text-center text-cyan-500" }>{ title }</h1>
|
||||
</div>
|
||||
<div class={ "mx-auto w-fit" }>
|
||||
<p>Av Martin Berg Alstad</p>
|
||||
</div>
|
||||
<h1 class={"text-center text-cyan-500"}>{title}</h1>
|
||||
</div>
|
||||
<div class={"mx-auto w-fit"}>
|
||||
<p>Av Martin Berg Alstad</p>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export default Header;
|
||||
export default Header
|
||||
|
@ -1,28 +1,28 @@
|
||||
/* @refresh reload */
|
||||
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js";
|
||||
import Row from "./row";
|
||||
import { Icon } from "solid-heroicons";
|
||||
import { magnifyingGlass, xMark } from "solid-heroicons/solid";
|
||||
import { getElementById } from "../utils/dom";
|
||||
import { type Component, createSignal, JSX, onMount, Setter, Show } from "solid-js"
|
||||
import Row from "./row"
|
||||
import { Icon } from "solid-heroicons"
|
||||
import { magnifyingGlass, xMark } from "solid-heroicons/solid"
|
||||
import { getElementById } from "../utils/dom"
|
||||
|
||||
function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void {
|
||||
let isMounted = true;
|
||||
let isMounted = true
|
||||
|
||||
function hover(hover: boolean): void {
|
||||
if (isMounted) {
|
||||
setIsHover(hover);
|
||||
}
|
||||
function hover(hover: boolean): void {
|
||||
if (isMounted) {
|
||||
setIsHover(hover)
|
||||
}
|
||||
}
|
||||
|
||||
const el = getElementById(id);
|
||||
el?.addEventListener("pointerenter", () => hover(true));
|
||||
el?.addEventListener("pointerleave", () => hover(false));
|
||||
const el = getElementById(id)
|
||||
el?.addEventListener("pointerenter", () => hover(true))
|
||||
el?.addEventListener("pointerleave", () => hover(false))
|
||||
|
||||
return () => {
|
||||
el?.removeEventListener("pointerenter", () => hover(true));
|
||||
el?.removeEventListener("pointerleave", () => hover(false));
|
||||
isMounted = false;
|
||||
}
|
||||
return () => {
|
||||
el?.removeEventListener("pointerenter", () => hover(true))
|
||||
el?.removeEventListener("pointerleave", () => hover(false))
|
||||
isMounted = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -30,149 +30,152 @@ function setupEventListener(id: string, setIsHover: Setter<boolean>): () => void
|
||||
* if the value of the input element is not empty and it's different from the current value
|
||||
*/
|
||||
function setSetIsText(id: string | undefined, isText: boolean, setIsText: Setter<boolean>): void {
|
||||
if (id) {
|
||||
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id);
|
||||
if (el && el.value !== "" !== isText) {
|
||||
setIsText(el.value !== "");
|
||||
}
|
||||
if (id) {
|
||||
const el = getElementById<HTMLInputElement | HTMLTextAreaElement>(id)
|
||||
if (el && (el.value !== "") !== isText) {
|
||||
setIsText(el.value !== "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface Input<T extends HTMLElement> extends InputProps<T> {
|
||||
leading?: JSX.Element,
|
||||
trailing?: JSX.Element,
|
||||
onChange?: () => void,
|
||||
inputClass?: string,
|
||||
leading?: JSX.Element
|
||||
trailing?: JSX.Element
|
||||
onChange?: () => void
|
||||
inputClass?: string
|
||||
}
|
||||
|
||||
export const Input: Component<Input<HTMLInputElement>> = ( // TODO remove leading and trailing from component
|
||||
{
|
||||
className,
|
||||
id,
|
||||
name,
|
||||
type = "text",
|
||||
title,
|
||||
placeholder,
|
||||
required = false,
|
||||
onChange,
|
||||
leading,
|
||||
trailing,
|
||||
inputClass,
|
||||
ref
|
||||
}): JSX.Element => {
|
||||
export const Input: Component<Input<HTMLInputElement>> = (
|
||||
// TODO remove leading and trailing from component
|
||||
{
|
||||
className,
|
||||
id,
|
||||
name,
|
||||
type = "text",
|
||||
title,
|
||||
placeholder,
|
||||
required = false,
|
||||
onChange,
|
||||
leading,
|
||||
trailing,
|
||||
inputClass,
|
||||
ref
|
||||
}
|
||||
): JSX.Element => {
|
||||
/**
|
||||
* Is 'true' if the input element is in focus
|
||||
*/
|
||||
const [isFocused, setIsFocused] = createSignal(false)
|
||||
/**
|
||||
* Is 'true' if the user is hovering over the input element
|
||||
*/
|
||||
const [isHover, setIsHover] = createSignal(false)
|
||||
/**
|
||||
* Is 'true' if the input element contains any characters
|
||||
*/
|
||||
const [isText, setIsText] = createSignal(false)
|
||||
|
||||
/**
|
||||
* Is 'true' if the input element is in focus
|
||||
*/
|
||||
const [isFocused, setIsFocused] = createSignal(false);
|
||||
/**
|
||||
* Is 'true' if the user is hovering over the input element
|
||||
*/
|
||||
const [isHover, setIsHover] = createSignal(false);
|
||||
/**
|
||||
* Is 'true' if the input element contains any characters
|
||||
*/
|
||||
const [isText, setIsText] = createSignal(false);
|
||||
|
||||
onMount(() => {
|
||||
if (id && title) {
|
||||
setupEventListener(id, setIsHover);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Row className={ `relative ${ className }` }>
|
||||
{ leading }
|
||||
<HoverTitle title={ title } isActive={ isFocused() || isHover() || isText() } htmlFor={ id } />
|
||||
<input
|
||||
class={ `bg-default-bg focus:border-cyan-500 outline-none border-2 border-gray-500
|
||||
hover:border-t-cyan-400 ${ inputClass }` }
|
||||
id={ id }
|
||||
ref={ ref }
|
||||
onFocus={ () => setIsFocused(true) }
|
||||
onBlur={ () => setIsFocused(false) }
|
||||
name={ name ?? undefined }
|
||||
type={ type }
|
||||
placeholder={ placeholder ?? undefined }
|
||||
required={ required }
|
||||
onInput={ () => {
|
||||
setSetIsText(id, isText(), setIsText);
|
||||
if (onChange) {
|
||||
onChange();
|
||||
}
|
||||
} } />
|
||||
{ trailing }
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
const HoverTitle: Component<{ title?: string, isActive?: boolean, htmlFor?: string }> = (
|
||||
{
|
||||
title,
|
||||
isActive = false,
|
||||
htmlFor
|
||||
}) => (
|
||||
<label class={ `absolute pointer-events-none
|
||||
${ isActive ? "-top-2 left-3 default-bg text-sm" : "left-2 top-1" }
|
||||
transition-all duration-150 text-gray-600 dark:text-gray-400` }
|
||||
for={ htmlFor }>
|
||||
<div class={ "z-50 relative" }>{ title }</div>
|
||||
<div class={ "w-full h-2 default-bg absolute bottom-1/3 z-10" } />
|
||||
</label>
|
||||
);
|
||||
|
||||
interface SearchProps extends InputProps<HTMLInputElement> {
|
||||
typingDefault?: boolean
|
||||
}
|
||||
|
||||
export const Search: Component<SearchProps> = (
|
||||
{
|
||||
typingDefault = false,
|
||||
id = "search",
|
||||
className,
|
||||
ref
|
||||
}) => {
|
||||
|
||||
const [typing, setTyping] = createSignal(typingDefault);
|
||||
|
||||
function getInputElement() {
|
||||
return getElementById<HTMLInputElement>(id);
|
||||
onMount(() => {
|
||||
if (id && title) {
|
||||
setupEventListener(id, setIsHover)
|
||||
}
|
||||
})
|
||||
|
||||
function clearSearch(): void {
|
||||
const el = getInputElement();
|
||||
if (el) {
|
||||
el.value = "";
|
||||
setTyping(false);
|
||||
history.replaceState(null, "", location.pathname);
|
||||
el.focus();
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(): void {
|
||||
const el = getInputElement();
|
||||
if (el && (el.value !== "") !== typing()) {
|
||||
setTyping(el.value !== "");
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Input inputClass={ `rounded-xl pl-7 h-10 w-full pr-8` } className={ `w-full ${ className }` }
|
||||
id={ id }
|
||||
ref={ ref }
|
||||
placeholder={ "¬A & B -> C" }
|
||||
type={ "text" }
|
||||
onChange={ onChange }
|
||||
leading={ <Icon path={ magnifyingGlass } aria-label={ "Magnifying glass" }
|
||||
class={ "pl-2 absolute" } /> }
|
||||
trailing={ <Show when={ typing() } keyed>
|
||||
<button class={ "absolute right-2" }
|
||||
title={ "Clear" }
|
||||
type={ "reset" }
|
||||
onClick={ clearSearch }>
|
||||
<Icon path={ xMark } aria-label={ "The letter X" } />
|
||||
</button>
|
||||
</Show> }
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<Row className={`relative ${className}`}>
|
||||
{leading}
|
||||
<HoverTitle title={title} isActive={isFocused() || isHover() || isText()} htmlFor={id} />
|
||||
<input
|
||||
class={`border-2 border-gray-500 bg-default-bg outline-none hover:border-t-cyan-400
|
||||
focus:border-cyan-500 ${inputClass}`}
|
||||
id={id}
|
||||
ref={ref}
|
||||
onFocus={() => setIsFocused(true)}
|
||||
onBlur={() => setIsFocused(false)}
|
||||
name={name ?? undefined}
|
||||
type={type}
|
||||
placeholder={placeholder ?? undefined}
|
||||
required={required}
|
||||
onInput={() => {
|
||||
setSetIsText(id, isText(), setIsText)
|
||||
if (onChange) {
|
||||
onChange()
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{trailing}
|
||||
</Row>
|
||||
)
|
||||
}
|
||||
|
||||
const HoverTitle: Component<{ title?: string; isActive?: boolean; htmlFor?: string }> = ({
|
||||
title,
|
||||
isActive = false,
|
||||
htmlFor
|
||||
}) => (
|
||||
<label
|
||||
class={`pointer-events-none absolute
|
||||
${isActive ? "default-bg -top-2 left-3 text-sm" : "left-2 top-1"}
|
||||
text-gray-600 transition-all duration-150 dark:text-gray-400`}
|
||||
for={htmlFor}
|
||||
>
|
||||
<div class={"relative z-50"}>{title}</div>
|
||||
<div class={"default-bg absolute bottom-1/3 z-10 h-2 w-full"} />
|
||||
</label>
|
||||
)
|
||||
|
||||
interface SearchProps extends InputProps {
|
||||
typingDefault?: boolean
|
||||
}
|
||||
|
||||
export const Search: Component<SearchProps> = ({
|
||||
typingDefault = false,
|
||||
id = "search",
|
||||
className,
|
||||
ref
|
||||
}) => {
|
||||
const [typing, setTyping] = createSignal(typingDefault)
|
||||
|
||||
function getInputElement() {
|
||||
return getElementById<HTMLInputElement>(id)
|
||||
}
|
||||
|
||||
function clearSearch(): void {
|
||||
const el = getInputElement()
|
||||
if (el) {
|
||||
el.value = ""
|
||||
setTyping(false)
|
||||
history.replaceState(null, "", location.pathname)
|
||||
el.focus()
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(): void {
|
||||
const el = getInputElement()
|
||||
if (el && (el.value !== "") !== typing()) {
|
||||
setTyping(el.value !== "")
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Input
|
||||
inputClass={`rounded-xl pl-7 h-10 w-full pr-8`}
|
||||
className={`w-full ${className}`}
|
||||
id={id}
|
||||
ref={ref}
|
||||
placeholder={"¬A & B -> C"}
|
||||
type={"text"}
|
||||
onChange={onChange}
|
||||
leading={
|
||||
<Icon path={magnifyingGlass} aria-label={"Magnifying glass"} class={"absolute pl-2"} />
|
||||
}
|
||||
trailing={
|
||||
<Show when={typing()} keyed>
|
||||
<button class={"absolute right-2"} title={"Clear"} type={"reset"} onClick={clearSearch}>
|
||||
<Icon path={xMark} aria-label={"The letter X"} />
|
||||
</button>
|
||||
</Show>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,23 +1,18 @@
|
||||
/* @refresh reload */
|
||||
import { type Component } from "solid-js";
|
||||
import Header from "./header";
|
||||
import Footer from "./footer";
|
||||
import { type Component } from "solid-js"
|
||||
import Header from "./header"
|
||||
import Footer from "./footer"
|
||||
|
||||
const Layout: Component<TitleProps> = (
|
||||
{
|
||||
children,
|
||||
title,
|
||||
className
|
||||
}) => (
|
||||
<div class={ `bg-default-bg text-white min-h-screen relative font-mono ${ className }` }>
|
||||
<div class="container mx-auto">
|
||||
<Header className={ "py-3" } title={ title } />
|
||||
<main>
|
||||
<div class={ "pb-28" }>{ children }</div>
|
||||
<Footer />
|
||||
</main>
|
||||
</div>
|
||||
const Layout: Component<TitleProps> = ({ children, title, className }) => (
|
||||
<div class={`relative min-h-screen bg-default-bg font-mono text-white ${className}`}>
|
||||
<div class="container mx-auto">
|
||||
<Header className={"py-3"} title={title} />
|
||||
<main>
|
||||
<div class={"pb-28"}>{children}</div>
|
||||
<Footer />
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
</div>
|
||||
)
|
||||
|
||||
export default Layout;
|
||||
export default Layout
|
||||
|
@ -1,20 +1,17 @@
|
||||
/* @refresh reload */
|
||||
import { type Component } from "solid-js";
|
||||
import { type Component } from "solid-js"
|
||||
|
||||
export const Link: Component<LinkProps> = (
|
||||
{
|
||||
to,
|
||||
rel,
|
||||
children,
|
||||
className,
|
||||
id,
|
||||
newTab = true,
|
||||
title,
|
||||
}) => ( // TODO <A/> throws exception
|
||||
<a href={ to } id={ id } title={ title }
|
||||
rel={ `${ rel } ${ newTab ? "noreferrer" : undefined }` }
|
||||
target={ newTab ? "_blank" : undefined }
|
||||
class={ `link ${ className }` }>
|
||||
{ children }
|
||||
</a>
|
||||
);
|
||||
{ to, rel, children, className, id, newTab = true, title } // TODO <A/> throws exception
|
||||
) => (
|
||||
<a
|
||||
href={to}
|
||||
id={id}
|
||||
title={title}
|
||||
rel={`${rel} ${newTab ? "noreferrer" : undefined}`}
|
||||
target={newTab ? "_blank" : undefined}
|
||||
class={`link ${className}`}
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
|
@ -1,78 +1,72 @@
|
||||
/* @refresh reload */
|
||||
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js";
|
||||
import { Button } from "./button";
|
||||
import { type Component, createEffect, createSignal, JSX, Show } from "solid-js"
|
||||
import { Button } from "./button"
|
||||
|
||||
interface MenuProps extends TitleProps {
|
||||
button?: JSX.Element,
|
||||
buttonClassName?: string,
|
||||
itemsClassName?: string,
|
||||
button?: JSX.Element
|
||||
buttonClassName?: string
|
||||
itemsClassName?: string
|
||||
}
|
||||
|
||||
const MyMenu: Component<MenuProps> = (
|
||||
{
|
||||
title,
|
||||
button,
|
||||
children,
|
||||
id,
|
||||
className,
|
||||
buttonClassName,
|
||||
itemsClassName,
|
||||
}) => {
|
||||
const MyMenu: Component<MenuProps> = ({
|
||||
title,
|
||||
button,
|
||||
children,
|
||||
id,
|
||||
className,
|
||||
buttonClassName,
|
||||
itemsClassName
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = createSignal(false)
|
||||
|
||||
const [isOpen, setIsOpen] = createSignal(false);
|
||||
function closeMenu(): void {
|
||||
setIsOpen(false)
|
||||
}
|
||||
|
||||
function closeMenu(): void {
|
||||
setIsOpen(false);
|
||||
function toggleMenu(): void {
|
||||
setIsOpen(!isOpen())
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
function click(e: MouseEvent): void {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
if (e.target.closest(`#${id}`) === null) {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toggleMenu(): void {
|
||||
setIsOpen(!isOpen());
|
||||
function keypress(e: KeyboardEvent): void {
|
||||
if (e.key === "Escape") {
|
||||
closeMenu()
|
||||
}
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
if (isOpen()) {
|
||||
document.addEventListener("click", click)
|
||||
document.addEventListener("keyup", keypress)
|
||||
} else {
|
||||
document.removeEventListener("click", click)
|
||||
document.removeEventListener("keyup", keypress)
|
||||
}
|
||||
})
|
||||
|
||||
function click(e: MouseEvent): void {
|
||||
if (e.target instanceof HTMLElement) {
|
||||
if (e.target.closest(`#${ id }`) === null) {
|
||||
closeMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function keypress(e: KeyboardEvent): void {
|
||||
if (e.key === "Escape") {
|
||||
closeMenu();
|
||||
}
|
||||
}
|
||||
|
||||
if (isOpen()) {
|
||||
document.addEventListener("click", click);
|
||||
document.addEventListener("keyup", keypress);
|
||||
}
|
||||
else {
|
||||
document.removeEventListener("click", click);
|
||||
document.removeEventListener("keyup", keypress);
|
||||
}
|
||||
});
|
||||
|
||||
return ( // TODO transition
|
||||
<div class={ `${ className }` } id={ id }>
|
||||
|
||||
<Button title={ title }
|
||||
onClick={ toggleMenu }
|
||||
className={ `flex-row-center ${ buttonClassName }` }>
|
||||
{ button }
|
||||
</Button>
|
||||
|
||||
<Show when={ isOpen() } keyed>
|
||||
<div
|
||||
class={ `absolute bg-default-bg border border-gray-500 rounded-b-xl mt-1 w-max z-50 ${ itemsClassName }` }>
|
||||
<div class={ "mx-1" }>{ children }</div>
|
||||
</div>
|
||||
</Show>
|
||||
return (
|
||||
// TODO transition
|
||||
<div class={`${className}`} id={id}>
|
||||
<Button title={title} onClick={toggleMenu} className={`flex-row-center ${buttonClassName}`}>
|
||||
{button}
|
||||
</Button>
|
||||
|
||||
<Show when={isOpen()} keyed>
|
||||
<div
|
||||
class={`absolute z-50 mt-1 w-max rounded-b-xl border border-gray-500 bg-default-bg ${itemsClassName}`}
|
||||
>
|
||||
<div class={"mx-1"}>{children}</div>
|
||||
</div>
|
||||
);
|
||||
</Show>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyMenu;
|
||||
export default MyMenu
|
||||
|
@ -1,74 +1,65 @@
|
||||
/* @refresh reload */
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless";
|
||||
import { Icon } from "solid-heroicons";
|
||||
import { type Component, JSX } from "solid-js";
|
||||
import { chevronUp } from "solid-heroicons/solid";
|
||||
import { Disclosure, DisclosureButton, DisclosurePanel, Transition } from "solid-headless"
|
||||
import { Icon } from "solid-heroicons"
|
||||
import { type Component, JSX } from "solid-js"
|
||||
import { chevronUp } from "solid-heroicons/solid"
|
||||
|
||||
interface InfoBoxProps extends TitleProps {
|
||||
error?: boolean,
|
||||
error?: boolean
|
||||
}
|
||||
|
||||
export const InfoBox: Component<InfoBoxProps> = (
|
||||
{
|
||||
title,
|
||||
children,
|
||||
error = false,
|
||||
className
|
||||
}) => (
|
||||
<div class={ `border-rounded ${ error ? "border-red-500" : "border-gray-500" } ${ className }` }>
|
||||
<p class={ `border-b px-2 ${ error ? "border-red-500" : "border-gray-500" }` }>{ title }</p>
|
||||
<div class={ "mx-2" }>{ children }</div>
|
||||
</div>
|
||||
);
|
||||
export const InfoBox: Component<InfoBoxProps> = ({ title, children, error = false, className }) => (
|
||||
<div class={`border-rounded ${error ? "border-red-500" : "border-gray-500"} ${className}`}>
|
||||
<p class={`border-b px-2 ${error ? "border-red-500" : "border-gray-500"}`}>{title}</p>
|
||||
<div class={"mx-2"}>{children}</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
interface MyDisclosureProps extends TitleProps {
|
||||
defaultOpen?: boolean,
|
||||
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>,
|
||||
defaultOpen?: boolean
|
||||
onClick?: JSX.EventHandlerUnion<HTMLButtonElement, MouseEvent>
|
||||
}
|
||||
|
||||
export const MyDisclosure: Component<MyDisclosureProps> = (
|
||||
{
|
||||
title,
|
||||
children,
|
||||
defaultOpen = false,
|
||||
className,
|
||||
id,
|
||||
onClick
|
||||
}): JSX.Element => (
|
||||
<div id={ id } class={ `border-rounded bg-default-bg ${ className }` }>
|
||||
<Disclosure defaultOpen={ defaultOpen }>
|
||||
{ ({ isOpen }) =>
|
||||
<>
|
||||
<DisclosureButton onClick={ onClick }
|
||||
class={ `flex-row-center w-full justify-between px-2` }>
|
||||
<p class={ `py-1` }>{ title }</p>
|
||||
<Icon path={ chevronUp }
|
||||
class={ `w-5 ${ isOpen() && "transform rotate-180" } transition` } />
|
||||
</DisclosureButton>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="transform scale-95 opacity-0"
|
||||
enterTo="transform scale-100 opacity-100"
|
||||
leave="transition duration-75 ease-out"
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0" show>
|
||||
<DisclosurePanel>
|
||||
<div class={ "px-2 pb-2 text-gray-300" }>{ children }</div>
|
||||
</DisclosurePanel>
|
||||
</Transition>
|
||||
</>
|
||||
}
|
||||
</Disclosure>
|
||||
</div>
|
||||
);
|
||||
export const MyDisclosure: Component<MyDisclosureProps> = ({
|
||||
title,
|
||||
children,
|
||||
defaultOpen = false,
|
||||
className,
|
||||
id,
|
||||
onClick
|
||||
}): JSX.Element => (
|
||||
<div id={id} class={`border-rounded bg-default-bg ${className}`}>
|
||||
<Disclosure defaultOpen={defaultOpen}>
|
||||
{({ isOpen }) => (
|
||||
<>
|
||||
<DisclosureButton onClick={onClick} class={`flex-row-center w-full justify-between px-2`}>
|
||||
<p class={`py-1`}>{title}</p>
|
||||
<Icon path={chevronUp} class={`w-5 ${isOpen() && "rotate-180 transform"} transition`} />
|
||||
</DisclosureButton>
|
||||
<Transition
|
||||
enter="transition duration-100 ease-out"
|
||||
enterFrom="transform scale-95 opacity-0"
|
||||
enterTo="transform scale-100 opacity-100"
|
||||
leave="transition duration-75 ease-out"
|
||||
leaveFrom="transform scale-100 opacity-100"
|
||||
leaveTo="transform scale-95 opacity-0"
|
||||
show
|
||||
>
|
||||
<DisclosurePanel>
|
||||
<div class={"px-2 pb-2 text-gray-300"}>{children}</div>
|
||||
</DisclosurePanel>
|
||||
</Transition>
|
||||
</>
|
||||
)}
|
||||
</Disclosure>
|
||||
</div>
|
||||
)
|
||||
|
||||
export const MyDisclosureContainer: Component<ChildProps> = (
|
||||
{
|
||||
children,
|
||||
className
|
||||
}) => (
|
||||
<div class={ `bg-cyan-900 border-rounded dark:border-gray-800 p-2 mb-2
|
||||
flex flex-col gap-1 ${ className }` }>
|
||||
{ children }
|
||||
</div>
|
||||
);
|
||||
export const MyDisclosureContainer: Component<ChildProps> = ({ children, className }) => (
|
||||
<div
|
||||
class={`border-rounded mb-2 flex flex-col gap-1
|
||||
bg-cyan-900 p-2 dark:border-gray-800 ${className}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* @refresh reload */
|
||||
import { type Component } from "solid-js";
|
||||
import { type Component } from "solid-js"
|
||||
|
||||
/**
|
||||
* A row that centers its children
|
||||
@ -8,7 +8,7 @@ import { type Component } from "solid-js";
|
||||
* @returns The row
|
||||
*/
|
||||
const Row: Component<ChildProps> = ({ children, className }) => (
|
||||
<div class={ `flex-row-center ${ className }` }>{ children }</div>
|
||||
);
|
||||
<div class={`flex-row-center ${className}`}>{children}</div>
|
||||
)
|
||||
|
||||
export default Row;
|
||||
export default Row
|
||||
|
@ -1,51 +1,54 @@
|
||||
/* @refresh reload */
|
||||
import { For } from "solid-js/web";
|
||||
import { type Component } from "solid-js";
|
||||
import { For } from "solid-js/web"
|
||||
import { type Component } from "solid-js"
|
||||
|
||||
interface TruthTableProps extends SimpleProps {
|
||||
table?: Table,
|
||||
header?: string[],
|
||||
table?: Table
|
||||
header?: string[]
|
||||
}
|
||||
|
||||
const TruthTable: Component<TruthTableProps> = (
|
||||
{
|
||||
table,
|
||||
header,
|
||||
className,
|
||||
style,
|
||||
id,
|
||||
}) => (
|
||||
<table class={ `border-2 border-gray-500 border-collapse table z-10 ${ className }` } id={ id } style={ style }>
|
||||
<thead>
|
||||
<tr>
|
||||
<For each={ header }>
|
||||
{ (exp) => (
|
||||
<th scope={ "col" }
|
||||
class={ `bg-default-bg text-center sticky top-0 [position:-webkit-sticky;]
|
||||
outline outline-2 outline-offset-[-1px] outline-gray-500` /*TODO sticky header at the top of the screen */ }>
|
||||
<p class={ "px-2 w-max" }>{ exp }</p>
|
||||
</th>
|
||||
) }
|
||||
</For>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={ table }>
|
||||
{ (row) =>
|
||||
<tr class={ "hover:text-black" }>
|
||||
<For each={ row }>
|
||||
{ (value) =>
|
||||
<td class={ `text-center border border-gray-500 last:underline
|
||||
${ value ? "bg-green-700" : "bg-red-700" }` }>
|
||||
<p>{ value ? "T" : "F" }</p>
|
||||
</td>
|
||||
}
|
||||
</For>
|
||||
</tr>
|
||||
}
|
||||
const TruthTable: Component<TruthTableProps> = ({ table, header, className, style, id }) => (
|
||||
<table
|
||||
class={`z-10 table border-collapse border-2 border-gray-500 ${className}`}
|
||||
id={id}
|
||||
style={style}
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
<For each={header}>
|
||||
{(exp) => (
|
||||
<th
|
||||
scope={"col"}
|
||||
class={
|
||||
`sticky top-0 bg-default-bg text-center outline
|
||||
outline-2 outline-offset-[-1px] outline-gray-500 [position:-webkit-sticky;]` /*TODO sticky header at the top of the screen */
|
||||
}
|
||||
>
|
||||
<p class={"w-max px-2"}>{exp}</p>
|
||||
</th>
|
||||
)}
|
||||
</For>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<For each={table}>
|
||||
{(row) => (
|
||||
<tr class={"hover:text-black"}>
|
||||
<For each={row}>
|
||||
{(value) => (
|
||||
<td
|
||||
class={`border border-gray-500 text-center last:underline
|
||||
${value ? "bg-green-700" : "bg-red-700"}`}
|
||||
>
|
||||
<p>{value ? "T" : "F"}</p>
|
||||
</td>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
</tr>
|
||||
)}
|
||||
</For>
|
||||
</tbody>
|
||||
</table>
|
||||
)
|
||||
|
||||
export default TruthTable;
|
||||
export default TruthTable
|
||||
|
Reference in New Issue
Block a user