Port over more UI elements.

This commit is contained in:
Paul 2021-06-18 14:20:57 +01:00
parent 7eee2cd7c6
commit 9ac2316ed0
11 changed files with 352 additions and 3 deletions

View file

@ -11,6 +11,7 @@
"devDependencies": { "devDependencies": {
"@fontsource/open-sans": "^4.4.5", "@fontsource/open-sans": "^4.4.5",
"@preact/preset-vite": "^2.0.0", "@preact/preset-vite": "^2.0.0",
"@styled-icons/bootstrap": "^10.34.0",
"@styled-icons/feather": "^10.34.0", "@styled-icons/feather": "^10.34.0",
"@types/styled-components": "^5.1.10", "@types/styled-components": "^5.1.10",
"preact-i18n": "^1.5.0", "preact-i18n": "^1.5.0",

View file

@ -6,6 +6,10 @@ import { Banner } from './components/ui/Banner';
import { Checkbox } from './components/ui/Checkbox'; import { Checkbox } from './components/ui/Checkbox';
import { ComboBox } from './components/ui/ComboBox'; import { ComboBox } from './components/ui/ComboBox';
import { InputBox } from './components/ui/InputBox'; import { InputBox } from './components/ui/InputBox';
import { ColourSwatches } from './components/ui/ColourSwatches';
import { Tip } from './components/ui/Tip';
import { Radio } from './components/ui/Radio';
import { Overline } from './components/ui/Overline';
// ! TEMP START // ! TEMP START
let a = {"light":false,"accent":"#FD6671","background":"#191919","foreground":"#F6F6F6","block":"#2D2D2D","message-box":"#363636","mention":"rgba(251, 255, 0, 0.06)","success":"#65E572","warning":"#FAA352","error":"#F06464","hover":"rgba(0, 0, 0, 0.1)","sidebar-active":"#FD6671","scrollbar-thumb":"#CA525A","scrollbar-track":"transparent","primary-background":"#242424","primary-header":"#363636","secondary-background":"#1E1E1E","secondary-foreground":"#C8C8C8","secondary-header":"#2D2D2D","tertiary-background":"#4D4D4D","tertiary-foreground":"#848484","status-online":"#3ABF7E","status-away":"#F39F00","status-busy":"#F84848","status-streaming":"#977EFF","status-invisible":"#A5A5A5"}; let a = {"light":false,"accent":"#FD6671","background":"#191919","foreground":"#F6F6F6","block":"#2D2D2D","message-box":"#363636","mention":"rgba(251, 255, 0, 0.06)","success":"#65E572","warning":"#FAA352","error":"#F06464","hover":"rgba(0, 0, 0, 0.1)","sidebar-active":"#FD6671","scrollbar-thumb":"#CA525A","scrollbar-track":"transparent","primary-background":"#242424","primary-header":"#363636","secondary-background":"#1E1E1E","secondary-foreground":"#C8C8C8","secondary-header":"#2D2D2D","tertiary-background":"#4D4D4D","tertiary-foreground":"#848484","status-online":"#3ABF7E","status-away":"#F39F00","status-busy":"#F84848","status-streaming":"#977EFF","status-invisible":"#A5A5A5"};
@ -32,6 +36,8 @@ export const UIDemo = styled.div`
export function App() { export function App() {
let [checked, setChecked] = useState(false); let [checked, setChecked] = useState(false);
let [colour, setColour] = useState('#FD6671');
let [selected, setSelected] = useState<'a' | 'b' | 'c'>('a');
return ( return (
<> <>
@ -53,6 +59,16 @@ export function App() {
<InputBox placeholder="Contrast input box..." contrast /> <InputBox placeholder="Contrast input box..." contrast />
<InputBox value="Input box with value" /> <InputBox value="Input box with value" />
<InputBox value="Contrast with value" contrast /> <InputBox value="Contrast with value" contrast />
<ColourSwatches value={colour} onChange={v => setColour(v)} />
<Tip>I am a tip! I provide valuable information.</Tip>
<Radio checked={selected === 'a'} onSelect={() => setSelected('a')}>First option</Radio>
<Radio checked={selected === 'b'} onSelect={() => setSelected('b')}>Second option</Radio>
<Radio checked={selected === 'c'} onSelect={() => setSelected('c')}>Last option</Radio>
<Overline>Normal overline</Overline>
<Overline type="subtle">Subtle overline</Overline>
<Overline type="error">Error overline</Overline>
<Overline error="with error">Normal overline</Overline>
<Overline type="subtle" error="with error">Subtle overline</Overline>
</UIDemo> </UIDemo>
</> </>
) )

View file

@ -1,6 +1,6 @@
import { Check } from '@styled-icons/feather'; import { Check } from '@styled-icons/feather';
import { Children } from "../../types/Preact";
import styled, { css } from 'styled-components'; import styled, { css } from 'styled-components';
import { VNode } from 'preact';
const CheckboxBase = styled.label` const CheckboxBase = styled.label`
gap: 4px; gap: 4px;
@ -62,8 +62,8 @@ interface Props {
checked: boolean; checked: boolean;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
children: VNode | string; children: Children;
description?: VNode | string; description?: Children;
onChange: (state: boolean) => void; onChange: (state: boolean) => void;
} }

View file

@ -0,0 +1,118 @@
import { useRef } from 'preact/hooks';
import { Check } from '@styled-icons/feather';
import styled, { css } from 'styled-components';
import { Pencil } from '@styled-icons/bootstrap';
interface Props {
value: string;
onChange: (value: string) => void;
}
const presets = [
[
"#7B68EE",
"#3498DB",
"#1ABC9C",
"#F1C40F",
"#FF7F50",
"#FD6671",
"#E91E63",
"#D468EE"
],
[
"#594CAD",
"#206694",
"#11806A",
"#C27C0E",
"#CD5B45",
"#FF424F",
"#AD1457",
"#954AA8"
]
];
const SwatchesBase = styled.div`
gap: 8px;
display: flex;
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 };
display: grid;
place-items: center;
&:hover {
border: 3px solid var(--foreground);
transition: border ease-in-out .07s;
}
svg {
color: white;
}
${ props => props.type === 'small' ? css`
width: 30px;
height: 30px;
svg {
stroke-width: 2;
}
` : css`
width: 68px;
height: 68px;
` }
`;
const Rows = styled.div`
gap: 8px;
display: flex;
flex-direction: column;
> div {
gap: 8px;
display: flex;
flex-direction: row;
}
`;
export function ColourSwatches({ value, onChange }: Props) {
const ref = useRef<HTMLInputElement>();
return (
<SwatchesBase>
<Swatch colour={value} type='large'
onClick={() => ref.current.click()}>
<Pencil size={32} />
</Swatch>
<input
type="color"
value={value}
ref={ref}
onChange={ev => onChange(ev.currentTarget.value)}
/>
<Rows>
{presets.map(row => (
<div>
{ row.map(swatch => (
<Swatch colour={swatch} type='small'
onClick={() => onChange(swatch)}>
{swatch === value && <Check size={18} strokeWidth={2} />}
</Swatch>
)) }
</div>
))}
</Rows>
</SwatchesBase>
)
}

View file

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

View file

@ -0,0 +1,47 @@
import styled, { css } from 'styled-components';
import { Children } from '../../types/Preact';
interface Props {
block?: boolean;
error?: Children;
children?: Children;
type?: "default" | "subtle" | "error";
}
const OverlineBase = styled.div<Omit<Props, 'children' | 'error'>>`
display: inline;
margin: 0.4em 0;
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 function Overline(props: Props) {
return (
<OverlineBase {...props}>
{ props.children }
{ props.children && props.error && <> &middot; </> }
{ props.error && (
<Overline type="error">
{ props.error }
</Overline>
) }
</OverlineBase>
)
}

View file

@ -0,0 +1,3 @@
export function Preloader() {
return <span>LOADING</span>
}

108
src/components/ui/Radio.tsx Normal file
View file

@ -0,0 +1,108 @@
import { Children } from "../../types/Preact";
import styled, { css } from 'styled-components';
import { CircleFill } from "@styled-icons/bootstrap";
interface Props {
children: Children;
description?: Children;
checked: boolean;
disabled?: boolean;
onSelect: () => void;
}
interface BaseProps {
selected: boolean
}
const RadioBase = styled.label<BaseProps>`
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: .2s ease all;
&:hover {
background: var(--hover);
}
> input {
display: none;
}
> 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;
}
}
${ props => props.selected && css`
color: white;
cursor: default;
background: var(--accent);
> div {
background: white;
}
> div svg {
color: var(--accent);
}
&:hover {
background: var(--accent);
}
` }
`;
const RadioDescription = styled.span<BaseProps>`
font-size: 0.8em;
font-weight: 400;
color: var(--secondary-foreground);
${ props => props.selected && css`
color: white;
` }
`;
export function Radio(props: Props) {
return (
<RadioBase
selected={props.checked}
disabled={props.disabled}
onClick={() => !props.disabled && props.onSelect && props.onSelect()}
>
<div>
<CircleFill 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>
);
}

36
src/components/ui/Tip.tsx Normal file
View file

@ -0,0 +1,36 @@
import styled from "styled-components";
import { Info } from "@styled-icons/feather";
import { Children } from "../../types/Preact";
export const TipBase = styled.div`
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);
a {
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
svg {
flex-shrink: 0;
margin-right: 10px;
}
`;
export function Tip(props: { children: Children }) {
return (
<TipBase>
<Info size={20} strokeWidth={2} />
<span>{ props.children }</span>
</TipBase>
)
}

3
src/types/Preact.ts Normal file
View file

@ -0,0 +1,3 @@
import { VNode } from 'preact';
export type Children = VNode | (VNode | string)[] | string;

View file

@ -286,6 +286,14 @@
estree-walker "^2.0.1" estree-walker "^2.0.1"
picomatch "^2.2.2" picomatch "^2.2.2"
"@styled-icons/bootstrap@^10.34.0":
version "10.34.0"
resolved "https://registry.yarnpkg.com/@styled-icons/bootstrap/-/bootstrap-10.34.0.tgz#d9142e9eb70dc437f7ef62ffc40168e1ae13ab12"
integrity sha512-UpzdVUR7r9BNqEfPrMchJdgMZEg9eXQxLQJUXM0ouvbI5o9j21/y1dGameO4PZtYbutT/dWv5O6y24z5JWzd5w==
dependencies:
"@babel/runtime" "^7.14.0"
"@styled-icons/styled-icon" "^10.6.3"
"@styled-icons/feather@^10.34.0": "@styled-icons/feather@^10.34.0":
version "10.34.0" version "10.34.0"
resolved "https://registry.yarnpkg.com/@styled-icons/feather/-/feather-10.34.0.tgz#fdef1b4231e1ff6cfe454da741161f532788177b" resolved "https://registry.yarnpkg.com/@styled-icons/feather/-/feather-10.34.0.tgz#fdef1b4231e1ff6cfe454da741161f532788177b"