diff options
| -rw-r--r-- | src/NodeEditor.tsx | 4 | ||||
| -rw-r--r-- | src/components/ArrowButton.css | 9 | ||||
| -rw-r--r-- | src/components/ArrowButton.tsx | 12 | ||||
| -rw-r--r-- | src/components/Button.css | 54 | ||||
| -rw-r--r-- | src/components/Button.tsx | 19 | ||||
| -rw-r--r-- | src/components/ContainedList.css | 21 | ||||
| -rw-r--r-- | src/components/ContainedList.tsx | 16 | ||||
| -rw-r--r-- | src/components/Content.css | 5 | ||||
| -rw-r--r-- | src/components/Content.tsx | 16 | ||||
| -rw-r--r-- | src/components/Form.css | 8 | ||||
| -rw-r--r-- | src/components/Form.tsx | 17 | ||||
| -rw-r--r-- | src/components/FormLabel.css | 8 | ||||
| -rw-r--r-- | src/components/FormLabel.tsx | 16 | ||||
| -rw-r--r-- | src/components/Header.css | 21 | ||||
| -rw-r--r-- | src/components/Header.tsx | 22 | ||||
| -rw-r--r-- | src/components/TextInput.css | 17 | ||||
| -rw-r--r-- | src/components/TextInput.tsx | 26 | ||||
| -rw-r--r-- | src/components/index.ts | 8 | ||||
| -rw-r--r-- | src/context.ts (renamed from src/pb.ts) | 0 | ||||
| -rw-r--r-- | src/index.css | 135 | ||||
| -rw-r--r-- | src/index.tsx | 18 | ||||
| -rw-r--r-- | src/pages/Home.tsx | 16 | ||||
| -rw-r--r-- | src/pages/LogIn.tsx | 45 | ||||
| -rw-r--r-- | src/pages/ProjectsList.tsx | 35 | ||||
| -rw-r--r-- | src/pages/SignUp.tsx | 61 | ||||
| -rw-r--r-- | src/pages/index.ts | 8 |
26 files changed, 397 insertions, 220 deletions
diff --git a/src/NodeEditor.tsx b/src/NodeEditor.tsx index 3a80825..0c0f106 100644 --- a/src/NodeEditor.tsx +++ b/src/NodeEditor.tsx @@ -56,7 +56,7 @@ interface LinkData extends LinkProps { to: { nodeId: number, socket: string }; } -export const NodeEditor = ({ user, project }) => { +const NodeEditor = ({ user, project }) => { const pb = useContext(Pb); const offsetX = useSignal(0); @@ -270,3 +270,5 @@ export const NodeEditor = ({ user, project }) => { </> ); }; + +export default NodeEditor;
\ No newline at end of file diff --git a/src/components/ArrowButton.css b/src/components/ArrowButton.css new file mode 100644 index 0000000..a39f44a --- /dev/null +++ b/src/components/ArrowButton.css @@ -0,0 +1,9 @@ +@scope (.__ArrowButton) { + :scope { + margin-top: 1rem; + padding: 0 1rem; + background-image: url('../icons/arrow-right.svg'); + background-position: right 1rem center; + background-repeat: no-repeat; + } +}
\ No newline at end of file diff --git a/src/components/ArrowButton.tsx b/src/components/ArrowButton.tsx new file mode 100644 index 0000000..ce0c898 --- /dev/null +++ b/src/components/ArrowButton.tsx @@ -0,0 +1,12 @@ +import Button, { ButtonProps } from './Button.tsx'; +import './ArrowButton.css'; + +const ArrowButton = ({ children, ...props }: ButtonProps) => { + return ( + <Button {...props} class={(props.class || '') + ' __ArrowButton'}> + {children} + </Button> + ); +}; + +export default ArrowButton;
\ No newline at end of file diff --git a/src/components/Button.css b/src/components/Button.css new file mode 100644 index 0000000..210d131 --- /dev/null +++ b/src/components/Button.css @@ -0,0 +1,54 @@ +@scope (.__Button) { + :scope { + width: 100%; + min-width: fit-content; + height: 3rem; + padding: 0 1rem; + + /* Necessary for <a> */ + display: flex; + align-items: center; + + font-size: 1rem; + text-align: start; + text-decoration: none; + + cursor: pointer; + + border: 1px solid var(--button-border, var(--button-base)); + background-color: var(--button-base); + color: var(--button-text); + + &:hover { + background-color: var(--button-hover); + color: var(--button-hover-text, var(--button-text)); + } + + &:focus { + outline: 2px solid var(--primary); + border-color: var(--base); + background-color: var(--button-focus, var(--button-base)); + color: var(--button-focus-text, var(--button-text)); + } + + &.primary { + --button-base: var(--primary); + --button-text: var(--text); + --button-hover: color-mix(in srgb, var(--text) 10%, var(--button-base)); + } + &.outline { + --button-base: transparent; + --button-text: var(--primary); + --button-border: var(--primary); + --button-hover: var(--primary); + --button-hover-text: var(--text); + --button-focus: var(--button-hover); + --button-focus-text: var(--button-hover-text); + } + &.ghost { + --button-base: transprent; + --button-text: var(--text); + --button-hover: var(--surface); + } + } +}
\ No newline at end of file diff --git a/src/components/Button.tsx b/src/components/Button.tsx new file mode 100644 index 0000000..60f42c7 --- /dev/null +++ b/src/components/Button.tsx @@ -0,0 +1,19 @@ +import type { ComponentChildren } from 'preact'; +import './Button.css'; + +export interface ButtonProps { + children: ComponentChildren; + kind?: 'primary' | 'outline' | 'ghost'; + props: Record<string, any>; +} + +const Button = ({ children, kind = 'primary', ...props }: ButtonProps) => { + const Elem = props.href ? 'a' : 'button'; + return ( + <Elem {...props} class={(props.class || '') + ' __Button ' + kind}> + {children} + </Elem> + ); +}; + +export default Button;
\ No newline at end of file diff --git a/src/components/ContainedList.css b/src/components/ContainedList.css new file mode 100644 index 0000000..97c98cc --- /dev/null +++ b/src/components/ContainedList.css @@ -0,0 +1,21 @@ +@scope (.__ContainedList) { + :scope { + list-style: none; + padding: 0; + display: flex; + flex-direction: column; + + li { + --border: 2px solid var(--surface); + border-top: var(--border); + &:last-child { + border-bottom: var(--border); + } + height: 3rem; + display: flex; + > * { + flex-grow: 1; + } + } + } +}
\ No newline at end of file diff --git a/src/components/ContainedList.tsx b/src/components/ContainedList.tsx new file mode 100644 index 0000000..e514e54 --- /dev/null +++ b/src/components/ContainedList.tsx @@ -0,0 +1,16 @@ +import type { ComponentChildren } from 'preact'; +import './ContainedList.css'; + +export interface ContainedListProps { + children: ComponentChildren; +} + +const ContainedList = ({ children }: ContainedListProps) => { + return ( + <ul class="__ContainedList"> + {children} + </ul> + ); +}; + +export default ContainedList;
\ No newline at end of file diff --git a/src/components/Content.css b/src/components/Content.css new file mode 100644 index 0000000..ec3836b --- /dev/null +++ b/src/components/Content.css @@ -0,0 +1,5 @@ +@scope (.__Content) { + :scope { + padding: 2rem; + } +}
\ No newline at end of file diff --git a/src/components/Content.tsx b/src/components/Content.tsx new file mode 100644 index 0000000..6beb82b --- /dev/null +++ b/src/components/Content.tsx @@ -0,0 +1,16 @@ +import { ComponentChildren } from 'preact'; +import './Content.css'; + +export interface ContentProps { + children: ComponentChildren; +} + +const Content = ({ children }: ContentProps) => { + return ( + <main class="__Content"> + {children} + </main> + ); +}; + +export default Content;
\ No newline at end of file diff --git a/src/components/Form.css b/src/components/Form.css new file mode 100644 index 0000000..f47b737 --- /dev/null +++ b/src/components/Form.css @@ -0,0 +1,8 @@ +@scope (.__Form) { + :scope { + display: flex; + flex-direction: column; + gap: 1rem; + max-width: 26rem; + } +}
\ No newline at end of file diff --git a/src/components/Form.tsx b/src/components/Form.tsx new file mode 100644 index 0000000..595e323 --- /dev/null +++ b/src/components/Form.tsx @@ -0,0 +1,17 @@ +import { ComponentChildren } from 'preact'; +import './Form.css'; + +export interface FormProps { + children: ComponentChildren; + onSubmit?: (event: SubmitEvent) => void; +} + +const Form = ({ onSubmit, children }: FormProps) => { + return ( + <form class="__Form" onSubmit={onSubmit}> + {children} + </form> + ); +}; + +export default Form;
\ No newline at end of file diff --git a/src/components/FormLabel.css b/src/components/FormLabel.css new file mode 100644 index 0000000..32f9c9b --- /dev/null +++ b/src/components/FormLabel.css @@ -0,0 +1,8 @@ +@scope (.__FormLabel) { + :scope { + display: flex; + flex-direction: column; + gap: 0.5rem; + color: var(--subtext); + } +}
\ No newline at end of file diff --git a/src/components/FormLabel.tsx b/src/components/FormLabel.tsx new file mode 100644 index 0000000..951bb61 --- /dev/null +++ b/src/components/FormLabel.tsx @@ -0,0 +1,16 @@ +import { ComponentChildren } from 'preact'; +import './FormLabel.css'; + +export interface FormLabelProps { + children: ComponentChildren; +} + +const FormLabel = ({ children }: FormLabelProps) => { + return ( + <label class="__FormLabel"> + {children} + </label> + ); +}; + +export default FormLabel;
\ No newline at end of file diff --git a/src/components/Header.css b/src/components/Header.css new file mode 100644 index 0000000..ef1dd60 --- /dev/null +++ b/src/components/Header.css @@ -0,0 +1,21 @@ +@scope (.__Header) { + :scope { + height: 3rem; + border-bottom: 1px solid var(--overlay); + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + > .title { + font-weight: 500; + width: fit-content; + } + + nav { + height: 100%; + display: flex; + flex-direction: row; + } + } +}
\ No newline at end of file diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..32fbce2 --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,22 @@ +import { ComponentChildren } from 'preact'; +import Button from './Button.tsx'; +import './Header.css'; + +export interface HeaderProps { + children: ComponentChildren; +} + +const Header = ({ children }: HeaderProps) => { + return ( + <header class="__Header"> + <div class="title"> + <Button kind="ghost" href="/">DataNodes</Button> + </div> + <nav> + {children} + </nav> + </header> + ); +}; + +export default Header;
\ No newline at end of file diff --git a/src/components/TextInput.css b/src/components/TextInput.css new file mode 100644 index 0000000..bb04415 --- /dev/null +++ b/src/components/TextInput.css @@ -0,0 +1,17 @@ +@scope (.__TextInput) { + :scope { + background-color: var(--surface); + color: var(--text); + height: 2.5rem; + padding: 0 0.5rem; + border: none; + border-bottom: 2px solid color-mix(in srgb, white 20%, var(--surface)); + font-size: 1rem; + + &:focus { + outline: 2px solid var(--primary); + border-bottom: none; + margin-bottom: 2px; + } + } +}
\ No newline at end of file diff --git a/src/components/TextInput.tsx b/src/components/TextInput.tsx new file mode 100644 index 0000000..026b062 --- /dev/null +++ b/src/components/TextInput.tsx @@ -0,0 +1,26 @@ +import { useCallback } from 'preact/hooks'; +import type { Signal } from '@preact/signals'; +import './TextInput.css'; + +export interface TextInputProps { + signal?: Signal<string>; + props: Record<string, any>; +} + +const TextInput = ({ signal, ...props }: TextInputProps) => { + const onInputSignal = useCallback((event: InputEvent) => { + signal.value = event.target.value; + }, [signal]); + + return ( + <input + type="text" + {...props} + class={(props.class || '') + ' __TextInput'} + value={signal || props.value} + onInput={signal ? onInputSignal : props.onInput} + /> + ); +}; + +export default TextInput;
\ No newline at end of file diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 0000000..1beca4d --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1,8 @@ +export { default as Header } from './Header.tsx'; +export { default as TextInput } from './TextInput.tsx'; +export { default as Button } from './Button.tsx'; +export { default as ArrowButton } from './ArrowButton.tsx'; +export { default as ContainedList } from './ContainedList.tsx'; +export { default as FormLabel } from './FormLabel.tsx'; +export { default as Content } from './Content.tsx'; +export { default as Form } from './Form.tsx';
\ No newline at end of file diff --git a/src/pb.ts b/src/context.ts index 2e77787..2e77787 100644 --- a/src/pb.ts +++ b/src/context.ts diff --git a/src/index.css b/src/index.css index 370f29b..51b106c 100644 --- a/src/index.css +++ b/src/index.css @@ -9,6 +9,9 @@ --subtext: #dddddd; font-family: 'Inter Variable', Helvetica, sans-serif; +} + +* { box-sizing: border-box; } @@ -18,10 +21,6 @@ body { margin: 0; } -main { - margin: 2rem; -} - h1 { margin: 0; margin-bottom: 1rem; @@ -32,23 +31,6 @@ h1, h2 { font-weight: 500; } -.container { - width: 50%; - margin: 1rem auto; -} - -.grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 2rem; - grid-auto-rows: minmax(100px, auto); -} - -.row { - display: flex; - flex-direction: row; -} - hr { border: 1px solid var(--overlay); width: 100%; @@ -60,115 +42,4 @@ p { a { color: color-mix(in srgb, var(--text) 40%, var(--primary)); -} - -a.action { - color: var(--text); - text-decoration: none; - padding: 0 1rem; - height: 100%; - display: flex; - align-items: center; - - &:hover { - background-color: var(--surface); - } - - &:focus { - outline: 2px solid var(--primary); - } -} - -article { - background-color: var(--surface); - border-radius: 1rem; - padding: 1rem; -} - -ul.contained { - list-style: none; - padding: 0; - display: flex; - flex-direction: column; - - li { - --border: 2px solid var(--surface); - border-top: var(--border); - &:last-child { - border-bottom: var(--border); - } - height: 3rem; - display: flex; - > * { - flex-grow: 1; - } - } -} - -form { - display: flex; - flex-direction: column; - gap: 1rem; - max-width: 26rem; -} - -fieldset.group { - border: none; - display: flex; - flex-direction: row; - align-items: baseline; - padding: 0; - - button, input[type="submit"] { - height: calc(2.5rem + 2px); - background-image: none; - } -} - -label { - display: flex; - flex-direction: column; - gap: 0.5rem; - color: var(--subtext); -} - -input { - background-color: var(--surface); - color: var(--text); - height: 2.5rem; - padding: 0 0.5rem; - border: none; - border-bottom: 2px solid color-mix(in srgb, white 20%, var(--surface)); - font-size: 1rem; - - &:focus { - outline: 2px solid var(--primary); - border-bottom: none; - margin-bottom: 2px; - } -} - -button, input[type="submit"] { - background-color: var(--primary); - color: var(--text); - width: 100%; - height: 3rem; - font-size: 1rem; - border: 1px solid var(--primary); - cursor: pointer; - - &:focus { - border-color: var(--base); - outline: 2px solid var(--primary); - } -} - -input[type="submit"] { - margin-top: 1rem; - padding: 0 1rem; - text-align: left; - align-self: flex-end; - background-image: url('icons/arrow-right.svg'); - background-position: right 1rem center; - background-repeat: no-repeat; }
\ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx index bed8b66..9bcdfe9 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,23 +2,21 @@ import { render } from 'preact'; import { useEffect, useMemo } from 'preact/hooks'; import { Router } from 'preact-router'; import PocketBase from 'pocketbase'; -import { Pb } from './pb.ts'; +import { Pb } from './context.ts'; import { Home, SignUp, LogIn, ProjectsList } from './pages'; -import { NodeEditor } from './NodeEditor.tsx'; +import NodeEditor from './NodeEditor.tsx'; +import { Header, Button } from './components'; import './index.css'; export const App = () => { const pb = useMemo(() => new PocketBase(`/`)); return ( <Pb.Provider value={pb}> - <header> - <a class="title action" href="/">DataNodes</a> - <nav> - < a class="action" href="/play">Try Now</a> - <a class="action" href="/login">Log In</a> - <a class="action" href="/signup">Sign Up</a> - </nav> - </header> + <Header> + <Button kind="ghost" href="/play">Try Now</Button> + <Button kind="ghost" href="/login">Log In</Button> + <Button kind="ghost" href="/signup">Sign Up</Button> + </Header> <Router> <Home path="/" /> <SignUp path="/signup" /> diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 7762636..f177a75 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,8 +1,12 @@ -export const Home = () => { +import { Content } from '../components'; + +const Home = () => { return ( - <header> - <a href="/signup">Sign Up</a> - <a href="/login">Log In</a> - </header> + <Content> + <p><a href="/play">Try Now</a></p> + <p><a href="/signup">Create Account</a></p> + </Content> ); -};
\ No newline at end of file +}; + +export default Home;
\ No newline at end of file diff --git a/src/pages/LogIn.tsx b/src/pages/LogIn.tsx index 55b2109..7e83f98 100644 --- a/src/pages/LogIn.tsx +++ b/src/pages/LogIn.tsx @@ -1,9 +1,10 @@ import { useContext } from 'preact/hooks'; import { useSignal } from '@preact/signals'; import { route } from 'preact-router'; -import { Pb } from '../pb.ts'; +import { Pb } from '../context.ts'; +import { TextInput, ArrowButton, FormLabel, Content, Form } from '../components'; -export const LogIn = () => { +const LogIn = () => { const pb = useContext(Pb); const email = useSignal(''); @@ -18,23 +19,25 @@ export const LogIn = () => { }; return ( - <main> - <form onSubmit={onSubmit}> - <h1>Log In</h1> - <p> - Don't have an account? <a href="/signup">Sign up</a> - </p> - <hr /> - <label> - Email - <input type="text" placeholder="Email" value={email} onInput={e => email.value = e.target.value} /> - </label> - <label> - Password - <input type="password" placeholder="Password" value={password} onInput={e => password.value = e.target.value} /> - </label> - <input type="submit" value="Continue" /> - </form> - </main> + <Content> + <Form onSubmit={onSubmit}> + <h1>Log In</h1> + <p> + Don't have an account? <a href="/signup">Sign up</a> + </p> + <hr /> + <FormLabel> + Email + <TextInput placeholder="Email" signal={email} /> + </FormLabel> + <FormLabel> + Password + <TextInput type="password" placeholder="Email" signal={email} /> + </FormLabel> + <ArrowButton>Continue</ArrowButton> + </Form> + </Content> ); -};
\ No newline at end of file +}; + +export default LogIn;
\ No newline at end of file diff --git a/src/pages/ProjectsList.tsx b/src/pages/ProjectsList.tsx index 820c457..d548aa7 100644 --- a/src/pages/ProjectsList.tsx +++ b/src/pages/ProjectsList.tsx @@ -1,9 +1,10 @@ import { useContext, useEffect } from 'preact/hooks'; import { useSignal } from '@preact/signals'; import { route } from 'preact-router'; -import { Pb } from '../pb.ts'; +import { Pb } from '../context.ts'; +import { TextInput, Button, Form, FormLabel, ContainedList, Content } from '../components'; -export const ProjectsList = ({ user }) => { +const ProjectsList = ({ user }) => { console.log(user); const pb = useContext(Pb); const projects = useSignal(null); @@ -30,22 +31,22 @@ export const ProjectsList = ({ user }) => { ); } return ( - <main class="container"> + <Content> <h1>{user}'s Projects</h1> - <form onSubmit={onCreateProject}> - <fieldset class="group"> - <label> - Name - <input type="text" placeholder="Project name" value={projectName} onInput={e => projectName.value = e.target.value} /> - </label> - <input type="submit" value="Create project" /> - </fieldset> - </form> - <ul class="contained"> + <Form onSubmit={onCreateProject}> + <FormLabel> + Name + <TextInput placeholder="Project name" signal={projectName} /> + </FormLabel> + <Button>Create project</Button> + </Form> + <ContainedList> {projects.value.items.map(p => ( - <li><a class="action" href={`/${user}/${p.name}`}>{p.name}</a></li> + <li><Button kind="ghost" href={`/${user}/${p.name}`}>{p.name}</Button></li> ))} - </ul> - </main> + </ContainedList> + </Content> ); -};
\ No newline at end of file +}; + +export default ProjectsList;
\ No newline at end of file diff --git a/src/pages/SignUp.tsx b/src/pages/SignUp.tsx index b707b7d..8bdf35b 100644 --- a/src/pages/SignUp.tsx +++ b/src/pages/SignUp.tsx @@ -1,9 +1,10 @@ import { useContext } from 'preact/hooks'; import { useSignal } from '@preact/signals'; import { route } from 'preact-router'; -import { Pb } from '../pb.ts'; +import { Pb } from '../context.ts'; +import { TextInput, ArrowButton, Form, FormLabel, Content } from '../components'; -export const SignUp = () => { +const SignUp = () => { const pb = useContext(Pb); const username = useSignal(''); @@ -26,31 +27,33 @@ export const SignUp = () => { }; return ( - <main> - <form onSubmit={onSubmit}> - <h1>Sign Up</h1> - <p> - Already have an account? <a href="/login">Log in</a> - </p> - <hr /> - <label> - Username - <input type="text" placeholder="Username" value={username} onInput={e => username.value = e.target.value} /> - </label> - <label> - Email - <input type="text" placeholder="Email" value={email} onInput={e => email.value = e.target.value} /> - </label> - <label> - Password - <input type="password" placeholder="Password" value={password} onInput={e => password.value = e.target.value} /> - </label> - <label> - Confirm password - <input type="password" placeholder="Confirm password" value={confirm} onInput={e => confirm.value = e.target.value} /> - </label> - <input type="submit" value="Continue" /> - </form> - </main> + <Content> + <Form onSubmit={onSubmit}> + <h1>Sign Up</h1> + <p> + Already have an account? <a href="/login">Log in</a> + </p> + <hr /> + <FormLabel> + Username + <TextInput placeholder="Username" signal={username} /> + </FormLabel> + <FormLabel> + Email + <TextInput placeholder="Email" signal={email} /> + </FormLabel> + <FormLabel> + Password + <TextInput type="password" placeholder="Password" signal={password} /> + </FormLabel> + <FormLabel> + Confirm password + <TextInput type="password" placeholder="Confirm password" signal={confirm} /> + </FormLabel> + <ArrowButton>Continue</ArrowButton> + </Form> + </Content> ); -};
\ No newline at end of file +}; + +export default SignUp;
\ No newline at end of file diff --git a/src/pages/index.ts b/src/pages/index.ts index 8b28ea6..bd162cf 100644 --- a/src/pages/index.ts +++ b/src/pages/index.ts @@ -1,4 +1,4 @@ -export { Home } from './Home.tsx'; -export { SignUp } from './SignUp.tsx'; -export { LogIn } from './LogIn.tsx'; -export { ProjectsList } from './ProjectsList.tsx';
\ No newline at end of file +export { default as Home } from './Home.tsx'; +export { default as SignUp } from './SignUp.tsx'; +export { default as LogIn } from './LogIn.tsx'; +export { default as ProjectsList } from './ProjectsList.tsx';
\ No newline at end of file |
