Use tabWidth 4 without actual tabs.

This commit is contained in:
Paul
2021-07-05 11:25:20 +01:00
parent 7bd33d8d34
commit b5a11d5c8f
180 changed files with 16619 additions and 16622 deletions

View File

@@ -1,10 +1,10 @@
import styled from "styled-components";
export default styled.div`
padding: 8px;
font-size: 14px;
text-align: center;
padding: 8px;
font-size: 14px;
text-align: center;
color: var(--accent);
background: var(--primary-background);
color: var(--accent);
background: var(--primary-background);
`;

View File

@@ -1,71 +1,71 @@
import styled, { css } from "styled-components";
interface Props {
readonly contrast?: boolean;
readonly error?: boolean;
readonly contrast?: boolean;
readonly error?: boolean;
}
export default styled.button<Props>`
z-index: 1;
padding: 8px;
font-size: 16px;
text-align: center;
font-family: inherit;
z-index: 1;
padding: 8px;
font-size: 16px;
text-align: center;
font-family: inherit;
transition: 0.2s ease opacity;
transition: 0.2s ease background-color;
transition: 0.2s ease opacity;
transition: 0.2s ease background-color;
background: var(--primary-background);
color: var(--foreground);
background: var(--primary-background);
color: var(--foreground);
border-radius: 6px;
cursor: pointer;
border: none;
border-radius: 6px;
cursor: pointer;
border: none;
&:hover {
background: var(--secondary-header);
}
&:hover {
background: var(--secondary-header);
}
&:disabled {
background: var(--primary-background);
}
&:disabled {
background: var(--primary-background);
}
&:active {
background: var(--secondary-background);
}
&:active {
background: var(--secondary-background);
}
${(props) =>
props.contrast &&
css`
padding: 4px 8px;
background: var(--secondary-header);
${(props) =>
props.contrast &&
css`
padding: 4px 8px;
background: var(--secondary-header);
&:hover {
background: var(--primary-header);
}
&:hover {
background: var(--primary-header);
}
&:disabled {
background: var(--secondary-header);
}
&:disabled {
background: var(--secondary-header);
}
&:active {
background: var(--secondary-background);
}
`}
&:active {
background: var(--secondary-background);
}
`}
${(props) =>
props.error &&
css`
color: white;
background: var(--error);
${(props) =>
props.error &&
css`
color: white;
background: var(--error);
&:hover {
filter: brightness(1.2);
background: var(--error);
}
&:hover {
filter: brightness(1.2);
background: var(--error);
}
&:disabled {
background: var(--error);
}
`}
&:disabled {
background: var(--error);
}
`}
`;

View File

@@ -4,52 +4,52 @@ import styled, { css } from "styled-components";
import { Children } from "../../types/Preact";
const CategoryBase = styled.div<Pick<Props, "variant">>`
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
margin-top: 4px;
padding: 6px 0;
margin-bottom: 4px;
white-space: nowrap;
margin-top: 4px;
padding: 6px 0;
margin-bottom: 4px;
white-space: nowrap;
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
display: flex;
align-items: center;
flex-direction: row;
justify-content: space-between;
svg {
cursor: pointer;
}
svg {
cursor: pointer;
}
&:first-child {
margin-top: 0;
padding-top: 0;
}
&:first-child {
margin-top: 0;
padding-top: 0;
}
${(props) =>
props.variant === "uniform" &&
css`
padding-top: 6px;
`}
${(props) =>
props.variant === "uniform" &&
css`
padding-top: 6px;
`}
`;
type Props = Omit<
JSX.HTMLAttributes<HTMLDivElement>,
"children" | "as" | "action"
JSX.HTMLAttributes<HTMLDivElement>,
"children" | "as" | "action"
> & {
text: Children;
action?: () => void;
variant?: "default" | "uniform";
text: Children;
action?: () => void;
variant?: "default" | "uniform";
};
export default function Category(props: Props) {
let { text, action, ...otherProps } = props;
let { text, action, ...otherProps } = props;
return (
<CategoryBase {...otherProps}>
{text}
{action && <Plus size={16} onClick={action} />}
</CategoryBase>
);
return (
<CategoryBase {...otherProps}>
{text}
{action && <Plus size={16} onClick={action} />}
</CategoryBase>
);
}

View File

@@ -4,105 +4,105 @@ import styled, { css } from "styled-components";
import { Children } from "../../types/Preact";
const CheckboxBase = styled.label`
margin-top: 20px;
gap: 4px;
z-index: 1;
display: flex;
border-radius: 4px;
align-items: center;
margin-top: 20px;
gap: 4px;
z-index: 1;
display: flex;
border-radius: 4px;
align-items: center;
cursor: pointer;
font-size: 18px;
user-select: none;
cursor: pointer;
font-size: 18px;
user-select: none;
transition: 0.2s ease all;
transition: 0.2s ease all;
input {
display: none;
}
input {
display: none;
}
&:hover {
.check {
background: var(--background);
}
}
&:hover {
.check {
background: var(--background);
}
}
&[disabled] {
opacity: 0.5;
cursor: not-allowed;
&[disabled] {
opacity: 0.5;
cursor: not-allowed;
&:hover {
background: unset;
}
}
&:hover {
background: unset;
}
}
`;
const CheckboxContent = styled.span`
display: flex;
flex-grow: 1;
font-size: 1rem;
font-weight: 600;
flex-direction: column;
display: flex;
flex-grow: 1;
font-size: 1rem;
font-weight: 600;
flex-direction: column;
`;
const CheckboxDescription = styled.span`
font-size: 0.75rem;
font-weight: 400;
color: var(--secondary-foreground);
font-size: 0.75rem;
font-weight: 400;
color: var(--secondary-foreground);
`;
const Checkmark = styled.div<{ checked: boolean }>`
margin: 4px;
width: 24px;
height: 24px;
display: grid;
flex-shrink: 0;
border-radius: 4px;
place-items: center;
transition: 0.2s ease all;
background: var(--secondary-background);
margin: 4px;
width: 24px;
height: 24px;
display: grid;
flex-shrink: 0;
border-radius: 4px;
place-items: center;
transition: 0.2s ease all;
background: var(--secondary-background);
svg {
color: var(--secondary-background);
}
svg {
color: var(--secondary-background);
}
${(props) =>
props.checked &&
css`
background: var(--accent) !important;
`}
${(props) =>
props.checked &&
css`
background: var(--accent) !important;
`}
`;
export interface CheckboxProps {
checked: boolean;
disabled?: boolean;
className?: string;
children: Children;
description?: Children;
onChange: (state: boolean) => void;
checked: boolean;
disabled?: boolean;
className?: string;
children: Children;
description?: Children;
onChange: (state: boolean) => void;
}
export default function Checkbox(props: CheckboxProps) {
return (
<CheckboxBase disabled={props.disabled} className={props.className}>
<CheckboxContent>
<span>{props.children}</span>
{props.description && (
<CheckboxDescription>
{props.description}
</CheckboxDescription>
)}
</CheckboxContent>
<input
type="checkbox"
checked={props.checked}
onChange={() =>
!props.disabled && props.onChange(!props.checked)
}
/>
<Checkmark checked={props.checked} className="check">
<Check size={20} />
</Checkmark>
</CheckboxBase>
);
return (
<CheckboxBase disabled={props.disabled} className={props.className}>
<CheckboxContent>
<span>{props.children}</span>
{props.description && (
<CheckboxDescription>
{props.description}
</CheckboxDescription>
)}
</CheckboxContent>
<input
type="checkbox"
checked={props.checked}
onChange={() =>
!props.disabled && props.onChange(!props.checked)
}
/>
<Checkmark checked={props.checked} className="check">
<Check size={20} />
</Checkmark>
</CheckboxBase>
);
}

View File

@@ -5,123 +5,123 @@ import styled, { css } from "styled-components";
import { useRef } from "preact/hooks";
interface Props {
value: string;
onChange: (value: string) => void;
value: string;
onChange: (value: string) => void;
}
const presets = [
[
"#7B68EE",
"#3498DB",
"#1ABC9C",
"#F1C40F",
"#FF7F50",
"#FD6671",
"#E91E63",
"#D468EE",
],
[
"#594CAD",
"#206694",
"#11806A",
"#C27C0E",
"#CD5B45",
"#FF424F",
"#AD1457",
"#954AA8",
],
[
"#7B68EE",
"#3498DB",
"#1ABC9C",
"#F1C40F",
"#FF7F50",
"#FD6671",
"#E91E63",
"#D468EE",
],
[
"#594CAD",
"#206694",
"#11806A",
"#C27C0E",
"#CD5B45",
"#FF424F",
"#AD1457",
"#954AA8",
],
];
const SwatchesBase = styled.div`
gap: 8px;
display: flex;
gap: 8px;
display: flex;
input {
opacity: 0;
margin-top: 44px;
position: absolute;
pointer-events: none;
}
input {
opacity: 0;
margin-top: 44px;
position: absolute;
pointer-events: none;
}
`;
const Swatch = styled.div<{ type: "small" | "large"; colour: string }>`
flex-shrink: 0;
cursor: pointer;
border-radius: 4px;
background-color: ${(props) => props.colour};
flex-shrink: 0;
cursor: pointer;
border-radius: 4px;
background-color: ${(props) => props.colour};
display: grid;
place-items: center;
display: grid;
place-items: center;
&:hover {
border: 3px solid var(--foreground);
transition: border ease-in-out 0.07s;
}
&:hover {
border: 3px solid var(--foreground);
transition: border ease-in-out 0.07s;
}
svg {
color: white;
}
svg {
color: white;
}
${(props) =>
props.type === "small"
? css`
width: 30px;
height: 30px;
${(props) =>
props.type === "small"
? css`
width: 30px;
height: 30px;
svg {
/*stroke-width: 2;*/
}
`
: css`
width: 68px;
height: 68px;
`}
svg {
/*stroke-width: 2;*/
}
`
: css`
width: 68px;
height: 68px;
`}
`;
const Rows = styled.div`
gap: 8px;
display: flex;
flex-direction: column;
gap: 8px;
display: flex;
flex-direction: column;
> div {
gap: 8px;
display: flex;
flex-direction: row;
}
> div {
gap: 8px;
display: flex;
flex-direction: row;
}
`;
export default function ColourSwatches({ value, onChange }: Props) {
const ref = useRef<HTMLInputElement>();
const ref = useRef<HTMLInputElement>();
return (
<SwatchesBase>
<Swatch
colour={value}
type="large"
onClick={() => ref.current.click()}>
<Palette size={32} />
</Swatch>
<input
type="color"
value={value}
ref={ref}
onChange={(ev) => onChange(ev.currentTarget.value)}
/>
<Rows>
{presets.map((row, i) => (
<div key={i}>
{row.map((swatch, i) => (
<Swatch
colour={swatch}
type="small"
key={i}
onClick={() => onChange(swatch)}>
{swatch === value && <Check size={18} />}
</Swatch>
))}
</div>
))}
</Rows>
</SwatchesBase>
);
return (
<SwatchesBase>
<Swatch
colour={value}
type="large"
onClick={() => ref.current.click()}>
<Palette size={32} />
</Swatch>
<input
type="color"
value={value}
ref={ref}
onChange={(ev) => onChange(ev.currentTarget.value)}
/>
<Rows>
{presets.map((row, i) => (
<div key={i}>
{row.map((swatch, i) => (
<Swatch
colour={swatch}
type="small"
key={i}
onClick={() => onChange(swatch)}>
{swatch === value && <Check size={18} />}
</Swatch>
))}
</div>
))}
</Rows>
</SwatchesBase>
);
}

View File

@@ -1,20 +1,20 @@
import styled from "styled-components";
export default styled.select`
padding: 8px;
border-radius: 6px;
font-family: inherit;
color: var(--secondary-foreground);
background: var(--secondary-background);
font-size: 0.875rem;
border: none;
outline: 2px solid transparent;
transition: outline-color 0.2s ease-in-out;
transition: box-shadow 0.3s;
cursor: pointer;
width: 100%;
padding: 8px;
border-radius: 6px;
font-family: inherit;
color: var(--secondary-foreground);
background: var(--secondary-background);
font-size: 0.875rem;
border: none;
outline: 2px solid transparent;
transition: outline-color 0.2s ease-in-out;
transition: box-shadow 0.3s;
cursor: pointer;
width: 100%;
&:focus {
box-shadow: 0 0 0 2pt var(--accent);
}
&:focus {
box-shadow: 0 0 0 2pt var(--accent);
}
`;

View File

@@ -2,47 +2,47 @@ import dayjs from "dayjs";
import styled, { css } from "styled-components";
const Base = styled.div<{ unread?: boolean }>`
height: 0;
display: flex;
user-select: none;
align-items: center;
margin: 17px 12px 5px;
border-top: thin solid var(--tertiary-foreground);
height: 0;
display: flex;
user-select: none;
align-items: center;
margin: 17px 12px 5px;
border-top: thin solid var(--tertiary-foreground);
time {
margin-top: -2px;
font-size: 0.6875rem;
line-height: 0.6875rem;
padding: 2px 5px 2px 0;
color: var(--tertiary-foreground);
background: var(--primary-background);
}
time {
margin-top: -2px;
font-size: 0.6875rem;
line-height: 0.6875rem;
padding: 2px 5px 2px 0;
color: var(--tertiary-foreground);
background: var(--primary-background);
}
${(props) =>
props.unread &&
css`
border-top: thin solid var(--accent);
`}
${(props) =>
props.unread &&
css`
border-top: thin solid var(--accent);
`}
`;
const Unread = styled.div`
background: var(--accent);
color: white;
padding: 5px 8px;
border-radius: 60px;
font-weight: 600;
background: var(--accent);
color: white;
padding: 5px 8px;
border-radius: 60px;
font-weight: 600;
`;
interface Props {
date: Date;
unread?: boolean;
date: Date;
unread?: boolean;
}
export default function DateDivider(props: Props) {
return (
<Base unread={props.unread}>
{props.unread && <Unread>NEW</Unread>}
<time>{dayjs(props.date).format("LL")}</time>
</Base>
);
return (
<Base unread={props.unread}>
{props.unread && <Unread>NEW</Unread>}
<time>{dayjs(props.date).format("LL")}</time>
</Base>
);
}

View File

@@ -1,74 +1,74 @@
import styled, { css } from "styled-components";
export default styled.details<{ sticky?: boolean; large?: boolean }>`
summary {
${(props) =>
props.sticky &&
css`
top: -1px;
z-index: 10;
position: sticky;
`}
summary {
${(props) =>
props.sticky &&
css`
top: -1px;
z-index: 10;
position: sticky;
`}
${(props) =>
props.large &&
css`
/*padding: 5px 0;*/
background: var(--primary-background);
color: var(--secondary-foreground);
${(props) =>
props.large &&
css`
/*padding: 5px 0;*/
background: var(--primary-background);
color: var(--secondary-foreground);
.padding {
/*TOFIX: make this applicable only for the friends list menu, DO NOT REMOVE.*/
display: flex;
align-items: center;
padding: 5px 0;
margin: 0.8em 0px 0.4em;
cursor: pointer;
}
`}
.padding {
/*TOFIX: make this applicable only for the friends list menu, DO NOT REMOVE.*/
display: flex;
align-items: center;
padding: 5px 0;
margin: 0.8em 0px 0.4em;
cursor: pointer;
}
`}
outline: none;
cursor: pointer;
list-style: none;
align-items: center;
transition: 0.2s opacity;
cursor: pointer;
list-style: none;
align-items: center;
transition: 0.2s opacity;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
&::marker,
&::-webkit-details-marker {
display: none;
}
&::marker,
&::-webkit-details-marker {
display: none;
}
.title {
flex-grow: 1;
margin-top: 1px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.title {
flex-grow: 1;
margin-top: 1px;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.padding {
display: flex;
align-items: center;
.padding {
display: flex;
align-items: center;
> svg {
flex-shrink: 0;
margin-inline-end: 4px;
transition: 0.2s ease transform;
}
}
}
> svg {
flex-shrink: 0;
margin-inline-end: 4px;
transition: 0.2s ease transform;
}
}
}
&:not([open]) {
summary {
opacity: 0.7;
}
&:not([open]) {
summary {
opacity: 0.7;
}
summary svg {
transform: rotateZ(-90deg);
}
}
summary svg {
transform: rotateZ(-90deg);
}
}
`;

View File

@@ -1,57 +1,57 @@
import styled, { css } from "styled-components";
interface Props {
borders?: boolean;
background?: boolean;
placement: "primary" | "secondary";
borders?: boolean;
background?: boolean;
placement: "primary" | "secondary";
}
export default styled.div<Props>`
gap: 6px;
height: 48px;
flex: 0 auto;
display: flex;
flex-shrink: 0;
padding: 0 16px;
font-weight: 600;
user-select: none;
align-items: center;
gap: 6px;
height: 48px;
flex: 0 auto;
display: flex;
flex-shrink: 0;
padding: 0 16px;
font-weight: 600;
user-select: none;
align-items: center;
background-size: cover !important;
background-position: center !important;
background-color: var(--primary-header);
background-size: cover !important;
background-position: center !important;
background-color: var(--primary-header);
svg {
flex-shrink: 0;
}
svg {
flex-shrink: 0;
}
/*@media only screen and (max-width: 768px) {
/*@media only screen and (max-width: 768px) {
padding: 0 12px;
}*/
@media (pointer: coarse) {
height: 56px;
}
${(props) =>
props.background &&
css`
height: 120px !important;
align-items: flex-end;
text-shadow: 0px 0px 1px black;
`}
${(props) =>
props.placement === "secondary" &&
css`
background-color: var(--secondary-header);
padding: 14px;
`}
@media (pointer: coarse) {
height: 56px;
}
${(props) =>
props.borders &&
css`
border-start-start-radius: 8px;
`}
props.background &&
css`
height: 120px !important;
align-items: flex-end;
text-shadow: 0px 0px 1px black;
`}
${(props) =>
props.placement === "secondary" &&
css`
background-color: var(--secondary-header);
padding: 14px;
`}
${(props) =>
props.borders &&
css`
border-start-start-radius: 8px;
`}
`;

View File

@@ -1,46 +1,46 @@
import styled, { css } from "styled-components";
interface Props {
type?: "default" | "circle";
type?: "default" | "circle";
}
const normal = `var(--secondary-foreground)`;
const hover = `var(--foreground)`;
export default styled.div<Props>`
z-index: 1;
display: grid;
cursor: pointer;
place-items: center;
transition: 0.1s ease background-color;
z-index: 1;
display: grid;
cursor: pointer;
place-items: center;
transition: 0.1s ease background-color;
fill: ${normal};
color: ${normal};
/*stroke: ${normal};*/
fill: ${normal};
color: ${normal};
/*stroke: ${normal};*/
a {
color: ${normal};
}
a {
color: ${normal};
}
&:hover {
fill: ${hover};
color: ${hover};
/*stroke: ${hover};*/
&:hover {
fill: ${hover};
color: ${hover};
/*stroke: ${hover};*/
a {
color: ${hover};
}
}
a {
color: ${hover};
}
}
${(props) =>
props.type === "circle" &&
css`
padding: 4px;
border-radius: 50%;
background-color: var(--secondary-header);
${(props) =>
props.type === "circle" &&
css`
padding: 4px;
border-radius: 50%;
background-color: var(--secondary-header);
&:hover {
background-color: var(--primary-header);
}
`}
&:hover {
background-color: var(--primary-header);
}
`}
`;

View File

@@ -1,39 +1,39 @@
import styled, { css } from "styled-components";
interface Props {
readonly contrast?: boolean;
readonly contrast?: boolean;
}
export default styled.input<Props>`
z-index: 1;
padding: 8px 16px;
border-radius: 6px;
z-index: 1;
padding: 8px 16px;
border-radius: 6px;
font-family: inherit;
color: var(--foreground);
background: var(--primary-background);
transition: 0.2s ease background-color;
font-family: inherit;
color: var(--foreground);
background: var(--primary-background);
transition: 0.2s ease background-color;
border: none;
outline: 2px solid transparent;
transition: outline-color 0.2s ease-in-out;
border: none;
outline: 2px solid transparent;
transition: outline-color 0.2s ease-in-out;
&:hover {
background: var(--secondary-background);
}
&:hover {
background: var(--secondary-background);
}
&:focus {
outline: 2px solid var(--accent);
}
&:focus {
outline: 2px solid var(--accent);
}
${(props) =>
props.contrast &&
css`
color: var(--secondary-foreground);
background: var(--secondary-background);
${(props) =>
props.contrast &&
css`
color: var(--secondary-foreground);
background: var(--secondary-background);
&:hover {
background: var(--hover);
}
`}
&:hover {
background: var(--hover);
}
`}
`;

View File

@@ -1,9 +1,9 @@
import styled from "styled-components";
export default styled.div`
height: 0px;
opacity: 0.6;
flex-shrink: 0;
margin: 8px 10px;
border-top: 1px solid var(--tertiary-foreground);
height: 0px;
opacity: 0.6;
flex-shrink: 0;
margin: 8px 10px;
border-top: 1px solid var(--tertiary-foreground);
`;

View File

@@ -1,22 +1,22 @@
// This file must be imported and used at least once for SVG masks.
export default function Masks() {
return (
<svg width={0} height={0} style={{ position: "fixed" }}>
<defs>
<mask id="server">
<rect x="0" y="0" width="32" height="32" fill="white" />
<circle cx="27" cy="5" r="7" fill={"black"} />
</mask>
<mask id="user">
<rect x="0" y="0" width="32" height="32" fill="white" />
<circle cx="27" cy="27" r="7" fill={"black"} />
</mask>
<mask id="overlap">
<rect x="0" y="0" width="32" height="32" fill="white" />
<circle cx="32" cy="16" r="18" fill={"black"} />
</mask>
</defs>
</svg>
);
return (
<svg width={0} height={0} style={{ position: "fixed" }}>
<defs>
<mask id="server">
<rect x="0" y="0" width="32" height="32" fill="white" />
<circle cx="27" cy="5" r="7" fill={"black"} />
</mask>
<mask id="user">
<rect x="0" y="0" width="32" height="32" fill="white" />
<circle cx="27" cy="27" r="7" fill={"black"} />
</mask>
<mask id="overlap">
<rect x="0" y="0" width="32" height="32" fill="white" />
<circle cx="32" cy="16" r="18" fill={"black"} />
</mask>
</defs>
</svg>
);
}

View File

@@ -19,181 +19,181 @@ const zoomIn = keyframes`
`;
const ModalBase = styled.div`
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
position: fixed;
max-height: 100%;
user-select: none;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9999;
position: fixed;
max-height: 100%;
user-select: none;
animation-name: ${open};
animation-duration: 0.2s;
animation-name: ${open};
animation-duration: 0.2s;
display: grid;
overflow-y: auto;
place-items: center;
display: grid;
overflow-y: auto;
place-items: center;
color: var(--foreground);
background: rgba(0, 0, 0, 0.8);
color: var(--foreground);
background: rgba(0, 0, 0, 0.8);
`;
const ModalContainer = styled.div`
overflow: hidden;
border-radius: 8px;
max-width: calc(100vw - 20px);
overflow: hidden;
border-radius: 8px;
max-width: calc(100vw - 20px);
animation-name: ${zoomIn};
animation-duration: 0.25s;
animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1);
animation-name: ${zoomIn};
animation-duration: 0.25s;
animation-timing-function: cubic-bezier(0.3, 0.3, 0.18, 1.1);
`;
const ModalContent = styled.div<
{ [key in "attachment" | "noBackground" | "border" | "padding"]?: boolean }
{ [key in "attachment" | "noBackground" | "border" | "padding"]?: boolean }
>`
border-radius: 8px;
text-overflow: ellipsis;
border-radius: 8px;
text-overflow: ellipsis;
h3 {
margin-top: 0;
}
h3 {
margin-top: 0;
}
form {
display: flex;
flex-direction: column;
}
${(props) =>
!props.noBackground &&
css`
background: var(--secondary-header);
`}
${(props) =>
props.padding &&
css`
padding: 1.5em;
`}
form {
display: flex;
flex-direction: column;
}
${(props) =>
props.attachment &&
css`
border-radius: 8px 8px 0 0;
`}
!props.noBackground &&
css`
background: var(--secondary-header);
`}
${(props) =>
props.border &&
css`
border-radius: 10px;
border: 2px solid var(--secondary-background);
`}
props.padding &&
css`
padding: 1.5em;
`}
${(props) =>
props.attachment &&
css`
border-radius: 8px 8px 0 0;
`}
${(props) =>
props.border &&
css`
border-radius: 10px;
border: 2px solid var(--secondary-background);
`}
`;
const ModalActions = styled.div`
gap: 8px;
display: flex;
flex-direction: row-reverse;
gap: 8px;
display: flex;
flex-direction: row-reverse;
padding: 1em 1.5em;
border-radius: 0 0 8px 8px;
background: var(--secondary-background);
padding: 1em 1.5em;
border-radius: 0 0 8px 8px;
background: var(--secondary-background);
`;
export interface Action {
text: Children;
onClick: () => void;
confirmation?: boolean;
contrast?: boolean;
error?: boolean;
text: Children;
onClick: () => void;
confirmation?: boolean;
contrast?: boolean;
error?: boolean;
}
interface Props {
children?: Children;
title?: Children;
children?: Children;
title?: Children;
disallowClosing?: boolean;
noBackground?: boolean;
dontModal?: boolean;
padding?: boolean;
disallowClosing?: boolean;
noBackground?: boolean;
dontModal?: boolean;
padding?: boolean;
onClose: () => void;
actions?: Action[];
disabled?: boolean;
border?: boolean;
visible: boolean;
onClose: () => void;
actions?: Action[];
disabled?: boolean;
border?: boolean;
visible: boolean;
}
export default function Modal(props: Props) {
if (!props.visible) return null;
if (!props.visible) return null;
let content = (
<ModalContent
attachment={!!props.actions}
noBackground={props.noBackground}
border={props.border}
padding={props.padding ?? !props.dontModal}>
{props.title && <h3>{props.title}</h3>}
{props.children}
</ModalContent>
);
let content = (
<ModalContent
attachment={!!props.actions}
noBackground={props.noBackground}
border={props.border}
padding={props.padding ?? !props.dontModal}>
{props.title && <h3>{props.title}</h3>}
{props.children}
</ModalContent>
);
if (props.dontModal) {
return content;
}
if (props.dontModal) {
return content;
}
useEffect(() => {
if (props.disallowClosing) return;
useEffect(() => {
if (props.disallowClosing) return;
function keyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
props.onClose();
}
}
function keyDown(e: KeyboardEvent) {
if (e.key === "Escape") {
props.onClose();
}
}
document.body.addEventListener("keydown", keyDown);
return () => document.body.removeEventListener("keydown", keyDown);
}, [props.disallowClosing, props.onClose]);
document.body.addEventListener("keydown", keyDown);
return () => document.body.removeEventListener("keydown", keyDown);
}, [props.disallowClosing, props.onClose]);
let confirmationAction = props.actions?.find(
(action) => action.confirmation,
);
useEffect(() => {
if (!confirmationAction) return;
let confirmationAction = props.actions?.find(
(action) => action.confirmation,
);
useEffect(() => {
if (!confirmationAction) return;
// ! FIXME: this may be done better if we
// ! can focus the button although that
// ! doesn't seem to work...
function keyDown(e: KeyboardEvent) {
if (e.key === "Enter") {
confirmationAction!.onClick();
}
}
// ! FIXME: this may be done better if we
// ! can focus the button although that
// ! doesn't seem to work...
function keyDown(e: KeyboardEvent) {
if (e.key === "Enter") {
confirmationAction!.onClick();
}
}
document.body.addEventListener("keydown", keyDown);
return () => document.body.removeEventListener("keydown", keyDown);
}, [confirmationAction]);
document.body.addEventListener("keydown", keyDown);
return () => document.body.removeEventListener("keydown", keyDown);
}, [confirmationAction]);
return createPortal(
<ModalBase
onClick={(!props.disallowClosing && props.onClose) || undefined}>
<ModalContainer onClick={(e) => (e.cancelBubble = true)}>
{content}
{props.actions && (
<ModalActions>
{props.actions.map((x) => (
<Button
contrast={x.contrast ?? true}
error={x.error ?? false}
onClick={x.onClick}
disabled={props.disabled}>
{x.text}
</Button>
))}
</ModalActions>
)}
</ModalContainer>
</ModalBase>,
document.body,
);
return createPortal(
<ModalBase
onClick={(!props.disallowClosing && props.onClose) || undefined}>
<ModalContainer onClick={(e) => (e.cancelBubble = true)}>
{content}
{props.actions && (
<ModalActions>
{props.actions.map((x) => (
<Button
contrast={x.contrast ?? true}
error={x.error ?? false}
onClick={x.onClick}
disabled={props.disabled}>
{x.text}
</Button>
))}
</ModalActions>
)}
</ModalContainer>
</ModalBase>,
document.body,
);
}

View File

@@ -5,60 +5,60 @@ import { Text } from "preact-i18n";
import { Children } from "../../types/Preact";
type Props = Omit<JSX.HTMLAttributes<HTMLDivElement>, "children" | "as"> & {
error?: string;
block?: boolean;
spaced?: boolean;
children?: Children;
type?: "default" | "subtle" | "error";
error?: string;
block?: boolean;
spaced?: boolean;
children?: Children;
type?: "default" | "subtle" | "error";
};
const OverlineBase = styled.div<Omit<Props, "children" | "error">>`
display: inline;
margin: 0.4em 0;
${(props) =>
props.spaced &&
css`
margin-top: 0.8em;
`}
font-size: 14px;
font-weight: 600;
color: var(--foreground);
text-transform: uppercase;
${(props) =>
props.type === "subtle" &&
css`
font-size: 12px;
color: var(--secondary-foreground);
`}
${(props) =>
props.type === "error" &&
css`
font-size: 12px;
font-weight: 400;
color: var(--error);
`}
display: inline;
margin: 0.4em 0;
${(props) =>
props.block &&
css`
display: block;
`}
props.spaced &&
css`
margin-top: 0.8em;
`}
font-size: 14px;
font-weight: 600;
color: var(--foreground);
text-transform: uppercase;
${(props) =>
props.type === "subtle" &&
css`
font-size: 12px;
color: var(--secondary-foreground);
`}
${(props) =>
props.type === "error" &&
css`
font-size: 12px;
font-weight: 400;
color: var(--error);
`}
${(props) =>
props.block &&
css`
display: block;
`}
`;
export default function Overline(props: Props) {
return (
<OverlineBase {...props}>
{props.children}
{props.children && props.error && <> &middot; </>}
{props.error && (
<Overline type="error">
<Text id={`error.${props.error}`}>{props.error}</Text>
</Overline>
)}
</OverlineBase>
);
return (
<OverlineBase {...props}>
{props.children}
{props.children && props.error && <> &middot; </>}
{props.error && (
<Overline type="error">
<Text id={`error.${props.error}`}>{props.error}</Text>
</Overline>
)}
</OverlineBase>
);
}

View File

@@ -21,83 +21,83 @@ const prRing = keyframes`
`;
const PreloaderBase = styled.div`
width: 100%;
height: 100%;
width: 100%;
height: 100%;
display: grid;
place-items: center;
display: grid;
place-items: center;
.spinner {
width: 58px;
display: flex;
text-align: center;
margin: 100px auto 0;
justify-content: space-between;
}
.spinner {
width: 58px;
display: flex;
text-align: center;
margin: 100px auto 0;
justify-content: space-between;
}
.spinner > div {
width: 14px;
height: 14px;
background-color: var(--tertiary-foreground);
.spinner > div {
width: 14px;
height: 14px;
background-color: var(--tertiary-foreground);
border-radius: 100%;
display: inline-block;
animation: ${skSpinner} 1.4s infinite ease-in-out both;
}
border-radius: 100%;
display: inline-block;
animation: ${skSpinner} 1.4s infinite ease-in-out both;
}
.spinner div:nth-child(1) {
animation-delay: -0.32s;
}
.spinner div:nth-child(1) {
animation-delay: -0.32s;
}
.spinner div:nth-child(2) {
animation-delay: -0.16s;
}
.spinner div:nth-child(2) {
animation-delay: -0.16s;
}
.ring {
display: inline-block;
position: relative;
width: 48px;
height: 52px;
}
.ring {
display: inline-block;
position: relative;
width: 48px;
height: 52px;
}
.ring div {
width: 32px;
margin: 8px;
height: 32px;
display: block;
position: absolute;
border-radius: 50%;
box-sizing: border-box;
border: 2px solid #fff;
animation: ${prRing} 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.ring div {
width: 32px;
margin: 8px;
height: 32px;
display: block;
position: absolute;
border-radius: 50%;
box-sizing: border-box;
border: 2px solid #fff;
animation: ${prRing} 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;
border-color: #fff transparent transparent transparent;
}
.ring div:nth-child(1) {
animation-delay: -0.45s;
}
.ring div:nth-child(1) {
animation-delay: -0.45s;
}
.ring div:nth-child(2) {
animation-delay: -0.3s;
}
.ring div:nth-child(2) {
animation-delay: -0.3s;
}
.ring div:nth-child(3) {
animation-delay: -0.15s;
}
.ring div:nth-child(3) {
animation-delay: -0.15s;
}
`;
interface Props {
type: "spinner" | "ring";
type: "spinner" | "ring";
}
export default function Preloader({ type }: Props) {
return (
<PreloaderBase>
<div class={type}>
<div />
<div />
<div />
</div>
</PreloaderBase>
);
return (
<PreloaderBase>
<div class={type}>
<div />
<div />
<div />
</div>
</PreloaderBase>
);
}

View File

@@ -4,108 +4,108 @@ import styled, { css } from "styled-components";
import { Children } from "../../types/Preact";
interface Props {
children: Children;
description?: Children;
children: Children;
description?: Children;
checked: boolean;
disabled?: boolean;
onSelect: () => void;
checked: boolean;
disabled?: boolean;
onSelect: () => void;
}
interface BaseProps {
selected: boolean;
selected: boolean;
}
const RadioBase = styled.label<BaseProps>`
gap: 4px;
z-index: 1;
padding: 4px;
display: flex;
cursor: pointer;
align-items: center;
gap: 4px;
z-index: 1;
padding: 4px;
display: flex;
cursor: pointer;
align-items: center;
font-size: 1rem;
font-weight: 600;
user-select: none;
border-radius: 4px;
transition: 0.2s ease all;
font-size: 1rem;
font-weight: 600;
user-select: none;
border-radius: 4px;
transition: 0.2s ease all;
&:hover {
background: var(--hover);
}
&:hover {
background: var(--hover);
}
> input {
display: none;
}
> input {
display: none;
}
> div {
margin: 4px;
width: 24px;
height: 24px;
display: grid;
border-radius: 50%;
place-items: center;
background: var(--foreground);
> div {
margin: 4px;
width: 24px;
height: 24px;
display: grid;
border-radius: 50%;
place-items: center;
background: var(--foreground);
svg {
color: var(--foreground);
/*stroke-width: 2;*/
}
}
svg {
color: var(--foreground);
/*stroke-width: 2;*/
}
}
${(props) =>
props.selected &&
css`
color: white;
cursor: default;
background: var(--accent);
${(props) =>
props.selected &&
css`
color: white;
cursor: default;
background: var(--accent);
> div {
background: white;
}
> div {
background: white;
}
> div svg {
color: var(--accent);
}
> div svg {
color: var(--accent);
}
&:hover {
background: var(--accent);
}
`}
&:hover {
background: var(--accent);
}
`}
`;
const RadioDescription = styled.span<BaseProps>`
font-size: 0.8em;
font-weight: 400;
color: var(--secondary-foreground);
font-size: 0.8em;
font-weight: 400;
color: var(--secondary-foreground);
${(props) =>
props.selected &&
css`
color: white;
`}
${(props) =>
props.selected &&
css`
color: white;
`}
`;
export default function Radio(props: Props) {
return (
<RadioBase
selected={props.checked}
disabled={props.disabled}
onClick={() =>
!props.disabled && props.onSelect && props.onSelect()
}>
<div>
<Circle size={12} />
</div>
<input type="radio" checked={props.checked} />
<span>
<span>{props.children}</span>
{props.description && (
<RadioDescription selected={props.checked}>
{props.description}
</RadioDescription>
)}
</span>
</RadioBase>
);
return (
<RadioBase
selected={props.checked}
disabled={props.disabled}
onClick={() =>
!props.disabled && props.onSelect && props.onSelect()
}>
<div>
<Circle size={12} />
</div>
<input type="radio" checked={props.checked} />
<span>
<span>{props.children}</span>
{props.description && (
<RadioDescription selected={props.checked}>
{props.description}
</RadioDescription>
)}
</span>
</RadioBase>
);
}

View File

@@ -1,10 +1,10 @@
import styled, { css } from "styled-components";
export interface TextAreaProps {
code?: boolean;
padding?: number;
lineHeight?: number;
hideBorder?: boolean;
code?: boolean;
padding?: number;
lineHeight?: number;
hideBorder?: boolean;
}
export const TEXT_AREA_BORDER_WIDTH = 2;
@@ -12,46 +12,46 @@ export const DEFAULT_TEXT_AREA_PADDING = 16;
export const DEFAULT_LINE_HEIGHT = 20;
export default styled.textarea<TextAreaProps>`
width: 100%;
resize: none;
display: block;
color: var(--foreground);
background: var(--secondary-background);
padding: ${(props) => props.padding ?? DEFAULT_TEXT_AREA_PADDING}px;
line-height: ${(props) => props.lineHeight ?? DEFAULT_LINE_HEIGHT}px;
width: 100%;
resize: none;
display: block;
color: var(--foreground);
background: var(--secondary-background);
padding: ${(props) => props.padding ?? DEFAULT_TEXT_AREA_PADDING}px;
line-height: ${(props) => props.lineHeight ?? DEFAULT_LINE_HEIGHT}px;
${(props) =>
props.hideBorder &&
css`
border: none;
`}
${(props) =>
props.hideBorder &&
css`
border: none;
`}
${(props) =>
!props.hideBorder &&
css`
border-radius: 4px;
transition: border-color 0.2s ease-in-out;
border: ${TEXT_AREA_BORDER_WIDTH}px solid transparent;
`}
${(props) =>
!props.hideBorder &&
css`
border-radius: 4px;
transition: border-color 0.2s ease-in-out;
border: ${TEXT_AREA_BORDER_WIDTH}px solid transparent;
`}
&:focus {
outline: none;
outline: none;
${(props) =>
!props.hideBorder &&
css`
border: ${TEXT_AREA_BORDER_WIDTH}px solid var(--accent);
`}
}
${(props) =>
!props.hideBorder &&
css`
border: ${TEXT_AREA_BORDER_WIDTH}px solid var(--accent);
`}
}
${(props) =>
props.code
? css`
font-family: var(--monoscape-font-font), monospace;
`
: css`
font-family: inherit;
`}
${(props) =>
props.code
? css`
font-family: var(--monoscape-font-font), monospace;
`
: css`
font-family: inherit;
`}
font-variant-ligatures: var(--ligatures);
font-variant-ligatures: var(--ligatures);
`;

View File

@@ -4,66 +4,66 @@ import styled, { css } from "styled-components";
import { Children } from "../../types/Preact";
interface Props {
warning?: boolean;
error?: boolean;
warning?: boolean;
error?: boolean;
}
export const Separator = styled.div<Props>`
height: 1px;
width: calc(100% - 10px);
background: var(--secondary-header);
margin: 18px auto;
height: 1px;
width: calc(100% - 10px);
background: var(--secondary-header);
margin: 18px auto;
`;
export const TipBase = styled.div<Props>`
display: flex;
padding: 12px;
overflow: hidden;
align-items: center;
display: flex;
padding: 12px;
overflow: hidden;
align-items: center;
font-size: 14px;
border-radius: 7px;
background: var(--primary-header);
border: 2px solid var(--secondary-header);
font-size: 14px;
border-radius: 7px;
background: var(--primary-header);
border: 2px solid var(--secondary-header);
a {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
a {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
svg {
flex-shrink: 0;
margin-inline-end: 10px;
}
svg {
flex-shrink: 0;
margin-inline-end: 10px;
}
${(props) =>
props.warning &&
css`
color: var(--warning);
border: 2px solid var(--warning);
background: var(--secondary-header);
`}
${(props) =>
props.warning &&
css`
color: var(--warning);
border: 2px solid var(--warning);
background: var(--secondary-header);
`}
${(props) =>
props.error &&
css`
color: var(--error);
border: 2px solid var(--error);
background: var(--secondary-header);
`}
${(props) =>
props.error &&
css`
color: var(--error);
border: 2px solid var(--error);
background: var(--secondary-header);
`}
`;
export default function Tip(props: Props & { children: Children }) {
const { children, ...tipProps } = props;
return (
<>
<Separator />
<TipBase {...tipProps}>
<InfoCircle size={20} />
<span>{props.children}</span>
</TipBase>
</>
);
const { children, ...tipProps } = props;
return (
<>
<Separator />
<TipBase {...tipProps}>
<InfoCircle size={20} />
<span>{props.children}</span>
</TipBase>
</>
);
}