diff --git a/package.json b/package.json index c54f63bc..96d4b973 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "devDependencies": { "@fontsource/open-sans": "^4.4.5", "@preact/preset-vite": "^2.0.0", + "@styled-icons/bootstrap": "^10.34.0", "@styled-icons/feather": "^10.34.0", "@types/styled-components": "^5.1.10", "preact-i18n": "^1.5.0", diff --git a/src/app.tsx b/src/app.tsx index 9bff1aa2..5b9d0496 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -6,6 +6,10 @@ import { Banner } from './components/ui/Banner'; import { Checkbox } from './components/ui/Checkbox'; import { ComboBox } from './components/ui/ComboBox'; 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 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() { let [checked, setChecked] = useState(false); + let [colour, setColour] = useState('#FD6671'); + let [selected, setSelected] = useState<'a' | 'b' | 'c'>('a'); return ( <> @@ -53,6 +59,16 @@ export function App() { + setColour(v)} /> + I am a tip! I provide valuable information. + setSelected('a')}>First option + setSelected('b')}>Second option + setSelected('c')}>Last option + Normal overline + Subtle overline + Error overline + Normal overline + Subtle overline > ) diff --git a/src/components/ui/Checkbox.tsx b/src/components/ui/Checkbox.tsx index 732b6efa..bccbefb1 100644 --- a/src/components/ui/Checkbox.tsx +++ b/src/components/ui/Checkbox.tsx @@ -1,6 +1,6 @@ import { Check } from '@styled-icons/feather'; +import { Children } from "../../types/Preact"; import styled, { css } from 'styled-components'; -import { VNode } from 'preact'; const CheckboxBase = styled.label` gap: 4px; @@ -62,8 +62,8 @@ interface Props { checked: boolean; disabled?: boolean; className?: string; - children: VNode | string; - description?: VNode | string; + children: Children; + description?: Children; onChange: (state: boolean) => void; } diff --git a/src/components/ui/ColourSwatches.tsx b/src/components/ui/ColourSwatches.tsx new file mode 100644 index 00000000..4c33340b --- /dev/null +++ b/src/components/ui/ColourSwatches.tsx @@ -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(); + + return ( + + ref.current.click()}> + + + onChange(ev.currentTarget.value)} + /> + + {presets.map(row => ( + + { row.map(swatch => ( + onChange(swatch)}> + {swatch === value && } + + )) } + + ))} + + + ) +} diff --git a/src/components/ui/LineDivider.tsx b/src/components/ui/LineDivider.tsx new file mode 100644 index 00000000..93fbcdfc --- /dev/null +++ b/src/components/ui/LineDivider.tsx @@ -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); +`; diff --git a/src/components/ui/Overline.tsx b/src/components/ui/Overline.tsx new file mode 100644 index 00000000..4aaef59f --- /dev/null +++ b/src/components/ui/Overline.tsx @@ -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>` + 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 ( + + { props.children } + { props.children && props.error && <> · > } + { props.error && ( + + { props.error } + + ) } + + ) +} diff --git a/src/components/ui/Preloader.tsx b/src/components/ui/Preloader.tsx new file mode 100644 index 00000000..79cd39c5 --- /dev/null +++ b/src/components/ui/Preloader.tsx @@ -0,0 +1,3 @@ +export function Preloader() { + return LOADING +} diff --git a/src/components/ui/Radio.tsx b/src/components/ui/Radio.tsx new file mode 100644 index 00000000..0ca1828d --- /dev/null +++ b/src/components/ui/Radio.tsx @@ -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` + 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` + font-size: 0.8em; + font-weight: 400; + color: var(--secondary-foreground); + + ${ props => props.selected && css` + color: white; + ` } +`; + +export function Radio(props: Props) { + return ( + !props.disabled && props.onSelect && props.onSelect()} + > + + + + + + {props.children} + {props.description && ( + + {props.description} + + )} + + + ); +} \ No newline at end of file diff --git a/src/components/ui/Tip.tsx b/src/components/ui/Tip.tsx new file mode 100644 index 00000000..cc4a1da0 --- /dev/null +++ b/src/components/ui/Tip.tsx @@ -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 ( + + + { props.children } + + ) +} diff --git a/src/types/Preact.ts b/src/types/Preact.ts new file mode 100644 index 00000000..1563a6c1 --- /dev/null +++ b/src/types/Preact.ts @@ -0,0 +1,3 @@ +import { VNode } from 'preact'; + +export type Children = VNode | (VNode | string)[] | string; diff --git a/yarn.lock b/yarn.lock index 975b71e5..85009143 100644 --- a/yarn.lock +++ b/yarn.lock @@ -286,6 +286,14 @@ estree-walker "^2.0.1" 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": version "10.34.0" resolved "https://registry.yarnpkg.com/@styled-icons/feather/-/feather-10.34.0.tgz#fdef1b4231e1ff6cfe454da741161f532788177b"