feat: create formlayout and create collection form

This commit is contained in:
Sonny
2024-04-28 19:34:15 +02:00
committed by Sonny
parent 602813ec05
commit 97044907ee
13 changed files with 333 additions and 42 deletions

View File

@@ -0,0 +1,27 @@
import styled from '@emotion/styled';
const Button = styled.button(({ theme }) => ({
cursor: 'pointer',
width: '100%',
textTransform: 'uppercase',
fontSize: '14px',
color: theme.colors.white,
background: theme.colors.primary,
padding: '0.75em',
border: `1px solid ${theme.colors.primary}`,
borderRadius: theme.border.radius,
transition: theme.transition.delay,
'&:disabled': {
cursor: 'not-allowed',
opacity: '0.75',
},
'&:not(:disabled):hover': {
boxShadow: `${theme.colors.darkBlue} 0 0 3px 1px`,
background: theme.colors.darkBlue,
color: theme.colors.white,
},
}));
export default Button;

View File

@@ -0,0 +1,10 @@
import styled from '@emotion/styled';
const Form = styled.form({
width: '100%',
display: 'flex',
gap: '0.5em',
flexDirection: 'column',
});
export default Form;

View File

@@ -0,0 +1,25 @@
import styled from '@emotion/styled';
const FormField = styled('div', {
shouldForwardProp: (propName) => propName !== 'required',
})<{ required?: boolean }>(({ required, theme }) => ({
display: 'flex',
gap: '0.25em',
flexDirection: 'column',
'& label': {
position: 'relative',
userSelect: 'none',
width: 'fit-content',
},
'& label::after': {
position: 'absolute',
top: 0,
right: '-0.75em',
color: theme.colors.red,
content: (required ? '"*"' : '""') as any,
},
}));
export default FormField;

View File

@@ -0,0 +1,23 @@
import styled from '@emotion/styled';
const Input = styled.input(({ theme }) => ({
width: '100%',
color: theme.colors.font,
backgroundColor: theme.colors.white,
padding: '0.75em',
border: `1px solid ${theme.colors.lightestGrey}`,
borderBottom: `2px solid ${theme.colors.lightestGrey}`,
borderRadius: theme.border.radius,
transition: theme.transition.delay,
'&:focus': {
borderBottom: `2px solid ${theme.colors.primary}`,
},
'&::placeholder': {
fontStyle: 'italic',
color: theme.colors.lightestGrey,
},
}));
export default Input;

View File

@@ -0,0 +1,44 @@
import { ChangeEvent, InputHTMLAttributes, useState } from 'react';
import FormField from '~/components/common/form/_form_field';
import Input from '~/components/common/form/_input';
interface InputProps
extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange'> {
label: string;
name: string;
value?: string;
onChange?: (name: string, value: string) => void;
}
export default function TextBox({
name,
label,
value = '',
onChange,
required = false,
...props
}: InputProps): JSX.Element {
const [inputValue, setInputValue] = useState<string>(value);
function _onChange({ target }: ChangeEvent<HTMLInputElement>) {
setInputValue(target.value);
if (onChange) {
onChange(target.name, target.value);
}
}
return (
<FormField required={required}>
<label htmlFor={name} title={label}>
{label}
</label>
<Input
{...props}
name={name}
onChange={_onChange}
value={inputValue}
placeholder={props.placeholder ?? 'Type something...'}
/>
</FormField>
);
}

View File

@@ -0,0 +1,55 @@
import PATHS from '#constants/paths';
import styled from '@emotion/styled';
import { Link } from '@inertiajs/react';
import { FormEvent, ReactNode } from 'react';
import Button from '~/components/common/form/_button';
import Form from '~/components/common/form/_form';
import BaseLayout from './_base_layout';
const FormLayoutStyle = styled.div(({ theme }) => ({
height: 'fit-content',
width: theme.media.mobile,
maxWidth: '100%',
marginTop: '10em',
display: 'flex',
gap: '0.75em',
flexDirection: 'column',
}));
interface FormLayoutProps {
title: string;
children: ReactNode;
canSubmit: boolean;
handleSubmit: (event: FormEvent<HTMLFormElement>) => void;
textSubmitButton?: string;
disableHomeLink?: boolean;
}
const FormLayout = ({
title,
children,
canSubmit,
handleSubmit,
textSubmitButton = 'Confirm',
disableHomeLink = false,
}: FormLayoutProps) => (
<BaseLayout>
<FormLayoutStyle>
<h2>{title}</h2>
<Form onSubmit={handleSubmit}>
{children}
<Button type="submit" disabled={!canSubmit}>
{textSubmitButton}
</Button>
</Form>
{!disableHomeLink && (
// <Link href={categoryId ? `/?categoryId=${categoryId}` : '/'}>{t('common:back-home')}</Link>
<Link href={PATHS.APP}> Revenir à l'accueil</Link>
)}
</FormLayoutStyle>
</BaseLayout>
);
export default FormLayout;

View File

@@ -17,13 +17,15 @@ const Nav = styled.nav({
alignItems: 'center',
});
const NavList = styled(UnstyledList)<NavbarListDirection>(({ theme, right }) => ({
display: 'flex',
flex: 1,
gap: '1.5em',
justifyContent: right ? 'flex-end' : 'flex-start',
transition: theme.transition.delay,
}));
const NavList = styled(UnstyledList)<NavbarListDirection>(
({ theme, right }) => ({
display: 'flex',
flex: 1,
gap: '1.5em',
justifyContent: right ? 'flex-end' : 'flex-start',
transition: theme.transition.delay,
})
);
const UserCard = styled.div({
display: 'flex',
@@ -53,14 +55,23 @@ export default function Navbar() {
<ExternalLink href={PATHS.REPO_GITHUB}>GitHub</ExternalLink>
</li>
{isAuthenticated && !!user ? (
<li>
<a href={PATHS.AUTH.LOGOUT}>
<UserCard>
<RoundedImage src={user.avatarUrl} width={22} referrerPolicy="no-referrer" />
{user.nickName}
</UserCard>
</a>
</li>
<>
<li>
<Link href={PATHS.APP}>Dashboard</Link>
</li>
<li>
<a href={PATHS.AUTH.LOGOUT}>
<UserCard>
<RoundedImage
src={user.avatarUrl}
width={22}
referrerPolicy="no-referrer"
/>
{user.nickName}
</UserCard>
</a>
</li>
</>
) : (
<li>
<Link href={PATHS.AUTH.LOGIN}>Login</Link>

9
inertia/pages/app.tsx Normal file
View File

@@ -0,0 +1,9 @@
import { Link } from '@inertiajs/react';
export default function AppPage() {
return (
<div>
<Link href="/collections/create">Add collection</Link>
</div>
);
}

View File

@@ -0,0 +1,61 @@
import { useForm } from '@inertiajs/react';
import { ChangeEvent, FormEvent, useMemo } from 'react';
import FormField from '~/components/common/form/_form_field';
import TextBox from '~/components/common/form/textbox';
import FormLayout from '~/components/layouts/form_layout';
import { Visibility } from '../../../app/enums/visibility';
export default function CreateCollectionPage() {
const { data, setData, post, processing, errors } = useForm({
name: '',
description: '',
visibility: Visibility.PRIVATE,
});
const isFormDisabled = useMemo(
() => processing || data.name.length === 0,
[processing, data]
);
function handleOnCheck({ target }: ChangeEvent<HTMLInputElement>) {
setData(
'visibility',
target.checked ? Visibility.PUBLIC : Visibility.PRIVATE
);
}
function handleSubmit(e: FormEvent<HTMLFormElement>) {
e.preventDefault();
post('/collections');
}
return (
<FormLayout
title="Create a collection"
handleSubmit={handleSubmit}
canSubmit={!isFormDisabled}
>
<TextBox
label="Collection name"
placeholder="Collection name"
name="name"
onChange={setData}
value={data.name}
required
autoFocus
/>
{errors.name && <div>{errors.name}</div>}
<TextBox
label="Collection description"
placeholder="Collection description"
name="description"
onChange={setData}
value={data.name}
/>
{errors.description && <div>{errors.description}</div>}
<FormField>
<label htmlFor="visibility">Public</label>
<input type="checkbox" onChange={handleOnCheck} id="visibility" />
</FormField>
</FormLayout>
);
}

View File

@@ -1,24 +1,7 @@
import { InferPageProps } from '@adonisjs/inertia/types';
import type { InferPageProps } from '@adonisjs/inertia/types';
import ContentLayout from '~/components/layouts/content_layout';
import useUser from '~/hooks/use_user';
import type AppsController from '../../app/controllers/apps_controller';
export default function Home(_: InferPageProps<AppsController, 'index'>) {
const { isAuthenticated, user } = useUser();
return (
<ContentLayout>
<div className="container">
<div className="title">AdonisJS x Inertia x React</div>
<span>
Learn more about AdonisJS and Inertia.js by visiting the{' '}
<a href="https://docs.adonisjs.com/guides/inertia">AdonisJS documentation</a>.
</span>
<span>{isAuthenticated ? 'Authenticated' : 'Not authenticated'}</span>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
</ContentLayout>
);
return <ContentLayout>blablabla welcome to MyLinks</ContentLayout>;
}

View File

@@ -1,4 +1,5 @@
import { css } from '@emotion/react';
import { theme } from '~/styles/theme';
export const cssReset = css({
'*': {
@@ -17,20 +18,20 @@ export const cssReset = css({
border: 0,
},
'a': {
a: {
width: 'fit-content',
color: '#3f88c5',
textDecoration: 'none',
borderBottom: '1px solid transparent',
},
'b': {
b: {
fontWeight: 600,
letterSpacing: '0.5px',
},
'h1, h2, h3, h4, h5, h6': {
fontWeight: '400',
margin: '1em 0',
fontWeight: '500',
color: theme.colors.primary,
},
});

View File

@@ -5,14 +5,28 @@ const black = '#333';
const darkBlack = '#111';
const white = '#fff';
const gray = '#7c7c7c';
const lightestGrey = '#dadce0';
const lightGrey = '#f0eef6';
const grey = '#aaa';
const darkGrey = '#4b5563';
const lightestBlue = '#d3e8fa';
const lightBlue = '#82c5fede';
const blue = '#3f88c5';
const darkBlue = '#005aa5';
const darkestBlue = '#1f2937';
const lightRed = '#ffbabab9';
const red = '#d8000c';
const lightGreen = '#c1ffbab9';
const green = 'green';
export const theme: Theme = {
colors: {
font: black,
background: white,
background: lightGrey,
primary: blue,
lightBlack,
@@ -20,9 +34,23 @@ export const theme: Theme = {
darkBlack,
white,
gray,
lightestGrey,
lightGrey,
grey,
darkGrey,
lightestBlue,
lightBlue,
blue,
darkBlue,
darkestBlue,
lightRed,
red,
lightGreen,
green,
},
border: {

View File

@@ -12,9 +12,23 @@ declare module '@emotion/react' {
darkBlack: string;
white: string;
gray: string;
lightestGrey: string;
lightGrey: string;
grey: string;
darkGrey: string;
lightestBlue: string;
lightBlue: string;
blue: string;
darkBlue: string;
darkestBlue: string;
lightRed: string;
red: string;
lightGreen: string;
green: string;
};
border: {