feat: create content layout with emotion

This commit is contained in:
Sonny
2024-04-27 20:00:46 +02:00
committed by Sonny
parent df4185bd62
commit 08dcd7455f
23 changed files with 630 additions and 52 deletions

View File

@@ -3,14 +3,14 @@
import { resolvePageComponent } from '@adonisjs/inertia/helpers';
import { createInertiaApp } from '@inertiajs/react';
import { hydrateRoot } from 'react-dom/client';
import '../css/app.css';
import { theme } from '~/styles/theme';
const appName = import.meta.env.VITE_APP_NAME || 'AdonisJS';
const appName = import.meta.env.VITE_APP_NAME || 'MyLinks';
createInertiaApp({
progress: { color: '#5468FF' },
progress: { color: theme.colors.primary },
title: (title) => `${title} - ${appName}`,
title: (title) => `${appName}${title && ` - ${title}`}`,
resolve: (name) => {
return resolvePageComponent(`../pages/${name}.tsx`, import.meta.glob('../pages/**/*.tsx'));

View File

@@ -0,0 +1,18 @@
import { AnchorHTMLAttributes, CSSProperties, ReactNode } from 'react';
export default function ExternalLink({
children,
title,
...props
}: AnchorHTMLAttributes<HTMLAnchorElement> & {
children: ReactNode;
style?: CSSProperties;
title?: string;
className?: string;
}) {
return (
<a target="_blank" rel="noreferrer" title={title} {...props}>
{children}
</a>
);
}

View File

@@ -0,0 +1,11 @@
import styled from '@emotion/styled';
const RoundedImage = styled.img({
'borderRadius': '50%',
'&:hover': {
boxShadow: '0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1)',
},
});
export default RoundedImage;

View File

@@ -0,0 +1,12 @@
import styled from '@emotion/styled';
const UnstyledList = styled.ul({
'&, & li': {
listStyleType: 'none',
margin: 0,
padding: 0,
border: 0,
},
});
export default UnstyledList;

View File

@@ -0,0 +1,14 @@
import { Global, ThemeProvider } from '@emotion/react';
import { ReactNode } from 'react';
import documentStyle from '~/styles/document';
import { cssReset } from '~/styles/reset';
import { theme } from '~/styles/theme';
export default function BaseLayout({ children }: { children: ReactNode }) {
return (
<>
<ThemeProvider theme={theme}>{children}</ThemeProvider>
<Global styles={[cssReset, documentStyle]} />
</>
);
}

View File

@@ -0,0 +1,22 @@
import styled from '@emotion/styled';
import { ReactNode } from 'react';
import Navbar from '../navbar/navbar';
import BaseLayout from './_base_layout';
const ContentLayoutStyle = styled.div(({ theme }) => ({
height: 'auto',
width: theme.media.small_desktop,
maxWidth: '100%',
padding: '1em',
}));
const ContentLayout = ({ children }: { children: ReactNode }) => (
<BaseLayout>
<ContentLayoutStyle>
<Navbar />
<main>{children}</main>
</ContentLayoutStyle>
</BaseLayout>
);
export default ContentLayout;

View File

@@ -0,0 +1,72 @@
import styled from '@emotion/styled';
import { Link } from '@inertiajs/react';
import ExternalLink from '~/components/common/external_link';
import RoundedImage from '~/components/common/rounded_image';
import UnstyledList from '~/components/common/unstyled/unstyled_list';
import useUser from '~/hooks/use_user';
import PATHS from '../../../app/constants/paths';
type NavbarListDirection = {
right?: boolean;
};
const Nav = styled.nav({
width: '100%',
padding: '0.75em 0',
display: 'flex',
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 UserCard = styled.div({
display: 'flex',
gap: '0.35em',
alignItems: 'center',
justifyContent: 'center',
});
export default function Navbar() {
const { isAuthenticated, user } = useUser();
return (
<Nav>
<NavList>
<li>
<Link href={PATHS.HOME} css={{ fontSize: '24px' }}>
MyLinks
</Link>
</li>
</NavList>
<NavList right>
<li>
<select>
<option>EN</option>
</select>
</li>
<li>
<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.AUTH.LOGIN}>Login</Link>
</li>
)}
</NavList>
</Nav>
);
}

View File

@@ -1,5 +1,5 @@
import { InferPageProps } from '@adonisjs/inertia/types';
import { Head } from '@inertiajs/react';
import ContentLayout from '~/components/layouts/content_layout';
import useUser from '~/hooks/use_user';
import type AppsController from '../../app/controllers/apps_controller';
@@ -7,9 +7,7 @@ export default function Home(_: InferPageProps<AppsController, 'index'>) {
const { isAuthenticated, user } = useUser();
return (
<>
<Head title="Homepage" />
<ContentLayout>
<div className="container">
<div className="title">AdonisJS x Inertia x React</div>
@@ -19,11 +17,8 @@ export default function Home(_: InferPageProps<AppsController, 'index'>) {
</span>
<span>{isAuthenticated ? 'Authenticated' : 'Not authenticated'}</span>
<span>
{isAuthenticated ? <a href="/auth/logout">Logout</a> : <a href="/auth/login">Login</a>}
</span>
<pre>{JSON.stringify(user, null, 2)}</pre>
</div>
</>
</ContentLayout>
);
}

10
inertia/pages/login.tsx Normal file
View File

@@ -0,0 +1,10 @@
import ContentLayout from '~/components/layouts/content_layout';
import PATHS from '../../app/constants/paths';
export default function LoginPage() {
return (
<ContentLayout>
<a href={PATHS.AUTH.GOOGLE}>Continue with Google</a>
</ContentLayout>
);
}

View File

@@ -0,0 +1,19 @@
import { css } from '@emotion/react';
import { theme } from './theme';
const documentStyle = css({
'html, body, #app': {
height: '100svh',
width: '100svw',
fontFamily: "'Segoe UI', Tahoma, Geneva, Verdana, sans-serif",
fontSize: '17px',
color: theme.colors.font,
backgroundColor: theme.colors.background,
display: 'flex',
alignItems: 'center',
flexDirection: 'column',
overflow: 'hidden',
},
});
export default documentStyle;

View File

@@ -0,0 +1,10 @@
import { keyframes } from '@emotion/react';
export const fadeIn = keyframes({
'0%': {
opacity: 0,
},
'100%': {
opacity: 1,
},
});

36
inertia/styles/reset.ts Normal file
View File

@@ -0,0 +1,36 @@
import { css } from '@emotion/react';
export const cssReset = css({
'*': {
boxSizing: 'border-box',
outline: 0,
margin: 0,
padding: 0,
scrollBehavior: 'smooth',
},
'.reset': {
backgroundColor: 'inherit',
color: 'inherit',
padding: 0,
margin: 0,
border: 0,
},
'a': {
width: 'fit-content',
color: '#3f88c5',
textDecoration: 'none',
borderBottom: '1px solid transparent',
},
'b': {
fontWeight: 600,
letterSpacing: '0.5px',
},
'h1, h2, h3, h4, h5, h6': {
fontWeight: '400',
margin: '1em 0',
},
});

44
inertia/styles/theme.ts Normal file
View File

@@ -0,0 +1,44 @@
import { Theme } from '@emotion/react';
const lightBlack = '#555';
const black = '#333';
const darkBlack = '#111';
const white = '#fff';
const gray = '#7c7c7c';
const blue = '#3f88c5';
export const theme: Theme = {
colors: {
font: black,
background: white,
primary: blue,
lightBlack,
black,
darkBlack,
white,
gray,
blue,
},
border: {
radius: '5px',
},
media: {
mobile: '768px',
tablet: '1024px',
small_desktop: '1280px',
medium_desktop: '1440px',
large_desktop: '1920px',
xlarge_desktop: '2560px',
},
transition: {
delay: '0.15s',
},
};

View File

@@ -2,11 +2,17 @@
"extends": "@adonisjs/tsconfig/tsconfig.client.json",
"compilerOptions": {
"baseUrl": ".",
"lib": ["DOM", "ESNext", "DOM.Iterable", "ES2020"],
"jsxImportSource": "@emotion/react",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"paths": {
"~/*": ["./*"]
}
},
"types": ["vite/client", "@emotion/styled", "@emotion/react"]
},
"include": ["./**/*.ts", "./**/*.tsx"]
}

37
inertia/types/emotion.d.ts vendored Normal file
View File

@@ -0,0 +1,37 @@
import '@emotion/react';
declare module '@emotion/react' {
export interface Theme extends theme {
colors: {
font: string;
background: string;
primary: string;
lightBlack: string;
black: string;
darkBlack: string;
white: string;
gray: string;
blue: string;
};
border: {
radius: string;
};
media: {
mobile: string;
tablet: string;
small_desktop: string;
medium_desktop: string;
large_desktop: string;
xlarge_desktop: string;
};
transition: {
delay: string;
};
}
}