mirror of
https://github.com/Sonny93/my-links.git
synced 2025-12-08 22:53:25 +00:00
feat: create content layout with emotion
This commit is contained in:
@@ -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'));
|
||||
|
||||
18
inertia/components/common/external_link.tsx
Normal file
18
inertia/components/common/external_link.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
11
inertia/components/common/rounded_image.tsx
Normal file
11
inertia/components/common/rounded_image.tsx
Normal 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;
|
||||
12
inertia/components/common/unstyled/unstyled_list.tsx
Normal file
12
inertia/components/common/unstyled/unstyled_list.tsx
Normal 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;
|
||||
14
inertia/components/layouts/_base_layout.tsx
Normal file
14
inertia/components/layouts/_base_layout.tsx
Normal 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]} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
22
inertia/components/layouts/content_layout.tsx
Normal file
22
inertia/components/layouts/content_layout.tsx
Normal 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;
|
||||
72
inertia/components/navbar/navbar.tsx
Normal file
72
inertia/components/navbar/navbar.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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
10
inertia/pages/login.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
19
inertia/styles/document.ts
Normal file
19
inertia/styles/document.ts
Normal 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;
|
||||
10
inertia/styles/keyframes.ts
Normal file
10
inertia/styles/keyframes.ts
Normal 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
36
inertia/styles/reset.ts
Normal 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
44
inertia/styles/theme.ts
Normal 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',
|
||||
},
|
||||
};
|
||||
@@ -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
37
inertia/types/emotion.d.ts
vendored
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user