feat(directory): add layout scaffold

This commit is contained in:
Haoyu Xu
2023-02-24 19:27:24 -05:00
parent 9c26de493a
commit e98bf8d7ea
18 changed files with 765 additions and 33 deletions

View File

@@ -1,17 +1,24 @@
@import url("https://fonts.googleapis.com/css2?family=Josefin+Sans&family=Noto+Sans+SC&display=swap");
@import url('https://fonts.googleapis.com/css2?family=Josefin+Sans:wght@400;500&family=Noto+Sans+SC&display=swap');
:root {
--text-color: rgba(255, 255, 255, 0.87);
--border-color: #707070;
--link-highlight-color: #33b5e5;
--drawer-background-color: rgba(0, 0, 0, 0.5);
--root-background-color: #131313;
font-family: "Josefin Sans", "Noto Sans SC", sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color: rgba(255, 255, 255, 0.87);
background-color: #131313;
color: var(--text-color);
background-color: var(--root-background-color);
min-height: 100vh;
font-synthesis: none;
text-rendering: optimizeLegibility;
}
#root {
@@ -20,6 +27,11 @@
flex-wrap: nowrap;
align-content: flex-start;
justify-content: flex-start;
align-items: flex-start;
align-items: stretch;
min-height: 100vh;
}
a {
color: var(--text-color);
text-decoration: none;
}

View File

@@ -11,8 +11,8 @@ import ErrorPage from "@/routes/error-page";
import routes from "@/routes";
import '@/App.css';
import 'reset-css';
document.title = import.meta.env.VITE_APP_TITLE;
import { TitleProvider } from '@/context/useTitleContext';
import { LanguageProvider } from '@/context/useLanguageContext';
const router = createBrowserRouter(
createRoutesFromElements(
@@ -40,6 +40,10 @@ const router = createBrowserRouter(
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<LanguageProvider>
<TitleProvider>
<RouterProvider router={router} />
</TitleProvider>
</LanguageProvider>
</React.StrictMode>
)

View File

@@ -0,0 +1,98 @@
.dropdown {
position: relative;
display: inline-block;
user-select: none;
z-index: 2;
padding: 0.5rem;
}
.dropdown .text {
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
}
.dropdown .content {
padding-right: 0.5rem;
}
.dropdown .icon {
width: 0.5em;
height: 0.5em;
display: inline-block;
vertical-align: middle;
border-left: 0.15em solid var(--text-color);
border-bottom: 0.15em solid var(--text-color);
border-right: 0.15em solid var(--text-color);
border-top: 0.15em solid var(--text-color);
transform: translate(0, -0.15em) rotate(-45deg);
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.dropdown .icon.active {
animation: icon-flash 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
}
.dropdown .menu {
opacity: 0;
position: absolute;
background-color: var(--background-color);
color: var(--text-color);
width: max-content;
z-index: -1;
top: 2rem;
right: 0;
gap: 0.5rem;
display: flex;
align-items: center;
flex-direction: column;
flex-wrap: nowrap;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
overflow: hidden;
padding: 0.5rem;
border: 1px solid var(--border-color);
visibility: hidden;
}
.dropdown .menu.active {
visibility: visible;
opacity: 1;
z-index: 2;
}
.dropdown .menu .item {
cursor: pointer;
padding: 0.5rem;
font-size: 1rem;
width: max-content;
height: max-content;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
flex-wrap: nowrap;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.dropdown .menu .item:hover, .dropdown .menu .item:focus, .dropdown .menu .item.active {
color: var(--link-highlight-color);
}
.dropdown .overlay {
z-index: 1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100vw;
height: 100vh;
}
@keyframes icon-flash {
50% {
opacity: 0.2;
}
}

View File

@@ -0,0 +1,47 @@
import {
useState,
useEffect
} from 'react'
import './dropdown.css'
export default function Dropdown(props) {
const [hidden, setHidden] = useState(true)
const toggleDropdown = () => {
setHidden(!hidden)
}
return (
<>
<section className='dropdown'>
<section
className='text'
onClick={() => toggleDropdown()}
>
<span className='content'>{props.text}</span>
<span className={`icon ${hidden ? '' : 'active'}`}></span>
</section>
<ul className={`menu ${hidden ? '' : 'active'}`}>
{
props.menu.map((item) => {
return (
<li
key={item.name}
className={`item${item.name === props.text ? ' active' : ''}`}
onClick={() => props.onClick(item)}
>
{item.name}
</li>
)
})
}
</ul>
<section
className='overlay'
hidden={hidden}
onClick={() => toggleDropdown()}
/>
</section>
</>
)
}

View File

@@ -0,0 +1,68 @@
.popup-text {
cursor: pointer;
}
.popup {
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
overflow: hidden;
opacity: 0;
z-index: -1;
border: unset;
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: 1.2rem;
}
.popup .wrapper {
display: flex;
flex-direction: column;
align-items: stretch;
flex-wrap: nowrap;
width: 480px;
height: fit-content;
margin: 0 auto;
background-color: var(--root-background-color);
border: 1px solid var(--border-color);
padding: 2rem;
}
.popup.active {
opacity: 1;
z-index: 1;
}
.popup .title {
font-size: 3rem;
font-weight: 700;
margin-bottom: 1rem;
border-bottom: 1px solid var(--border-color);
display: flex;
flex-direction: row;
justify-content: space-between;
align-content: center;
align-items: center;
}
.popup .overlay {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
z-index: -1;
opacity: 0;
background-color: var(--root-background-color);
transition: all cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.popup .overlay.active {
opacity: 0.5;
visibility: visible;
}

View File

@@ -0,0 +1,38 @@
import {
useState,
useEffect
} from 'react'
import './popup.css'
import ReturnButton from '@/component/return_button';
export default function Popup(props) {
const [hidden, setHidden] = useState(true)
const toggle = () => {
setHidden(!hidden)
}
return (
<>
<section className={`popup ${hidden ? '' : 'active'}`}>
<section className='wrapper'>
<section className='title'>
<span>{props.title}</span>
<ReturnButton onClick={toggle} />
</section>
<section className='content'>
{props.children}
</section>
</section>
<section className={`overlay ${hidden ? '' : 'active'}`}
onClick={() => toggle()} />
</section>
<span
className="popup-text"
onClick={toggle}
>
{props.title}
</span>
</>
)
}

View File

@@ -0,0 +1,50 @@
.return-button {
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0.6rem 0;
width: 3rem;
cursor: pointer;
}
.return-button .bar-wrapper {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
}
.return-button .bar {
width: 1rem;
height: 0.4rem;
background-color: var(--text-color);
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.return-button .bar-arrow-left {
border-top: 0.24rem solid transparent;
border-bottom: 0.24rem solid transparent;
border-right: 0.3rem solid var(--text-color);
}
.return-button .bar-arrow-right {
border-top: 0.24rem solid transparent;
border-bottom: 0.24rem solid transparent;
border-left: 0.3rem solid var(--text-color);
}
.return-button .bar-wrapper:nth-child(1) {
transform: translateY(-0.1rem) rotate(45deg);
}
.return-button .bar-wrapper:nth-child(2) {
transform: translateY(-0.1rem) translate(90%, -100%) rotate(-45deg);
}
.return-button .bar-wrapper:nth-child(3) {
transform: translateY(-0.1rem) translateY(150%) rotate(315deg);
}
.return-button .bar-wrapper:nth-child(4) {
transform: translateY(-0.1rem) translate(90%, 50%) rotate(225deg);
}

View File

@@ -0,0 +1,33 @@
import './return_button.css'
export default function ReturnButton(props) {
return (
<>
<section className='return-button'
onClick={() => props.onClick()}
>
<section className='bar-wrapper'>
<section className='bar-arrow-left'></section>
<section className='bar'></section>
<section className='bar-arrow-right'></section>
</section>
<section className='bar-wrapper'>
<section className='bar-arrow-left'></section>
<section className='bar'></section>
<section className='bar-arrow-right'></section>
</section>
<section className='bar-wrapper'>
<section className='bar-arrow-left'></section>
<section className='bar'></section>
<section className='bar-arrow-right'></section>
</section>
<section className='bar-wrapper'>
<section className='bar-arrow-left'></section>
<section className='bar'></section>
<section className='bar-arrow-right'></section>
</section>
</section>
</>
)
}

View File

@@ -0,0 +1,27 @@
import { createContext, useState, useEffect } from "react"
import i18n from '@/i18n'
export const LanguageContext = createContext()
export function LanguageProvider(props) {
const drawerTextDefaultLang = "en-US"
const [language, setLanguage] = useState(i18n.available.includes(navigator.language) ? navigator.language : "en-US") // language will be default to en-US if not available
const [drawerAlternateLang, setDrawerAlternateLang] = useState(null) // drawerAlternateLang will be default to zh-CN if language is en-*
useEffect(() => {
setDrawerAlternateLang(language.startsWith("en") ? "zh-CN" : language)
}, [language])
return (
<LanguageContext.Provider
value={{
language, setLanguage,
drawerTextDefaultLang,
drawerAlternateLang,
i18n
}}
>
{props.children}
</LanguageContext.Provider>
)
}

View File

@@ -0,0 +1,25 @@
import { createContext, useState, useEffect, useContext } from "react"
import { LanguageContext } from '@/context/useLanguageContext';
export const TitleContext = createContext()
export function TitleProvider(props) {
const { i18n, language } = useContext(LanguageContext)
const [fakeTitle, setTitle] = useState('dynamic_compile')
const [title, setActualTitle] = useState(null)
useEffect(() => {
let newTitle = fakeTitle
if (i18n.key[fakeTitle]) {
newTitle = i18n.key[fakeTitle][language]
}
document.title = `${newTitle} - ${import.meta.env.VITE_APP_TITLE}`;
setActualTitle(newTitle)
}, [fakeTitle, language])
return (
<TitleContext.Provider value={{ title, setTitle }}>
{props.children}
</TitleContext.Provider>
)
}

39
directory/src/i18n.json Normal file
View File

@@ -0,0 +1,39 @@
{
"available": [
"zh-CN", "en-US"
],
"key": {
"dynamic_compile": {
"zh-CN": "干员动态集录",
"en-US": "Dynamic Compile"
},
"home": {
"zh-CN": "首页",
"en-US": "Home"
},
"news": {
"zh-CN": "新闻",
"en-US": "News"
},
"offical_page": {
"zh-CN": "官方页面",
"en-US": "Offical Page"
},
"privacy_policy": {
"zh-CN": "隐私政策",
"en-US": "Privacy Policy"
},
"contact_us": {
"zh-CN": "联系我们",
"en-US": "Contact Us"
},
"zh-CN": {
"zh-CN": "简体中文",
"en-US": "Chinese (Simplified)"
},
"en-US": {
"zh-CN": "英语",
"en-US": "English"
}
}
}

View File

@@ -1,16 +1,31 @@
import Home from "@/routes/path/home";
import Operator from "@/routes/path/operator";
import News from "@/routes/path/news";
export default [
{
path: "",
path: "/",
index: true,
name: "Home",
element: <Home />
name: "home",
element: <Home />,
inDrawer: true
}, {
path: "operator/:key",
index: false,
name: "Operator",
element: <Operator />
name: "operator",
element: <Operator />,
inDrawer: false
}, {
path: "news",
index: false,
name: "news",
element: <News />,
inDrawer: true
}, {
path: "https://ak.hypergryph.com/archive/dynamicCompile/",
index: false,
name: "offical_page",
element: <a/>,
inDrawer: true
}
]

View File

@@ -1,20 +1,28 @@
import {
useState,
useEffect
useEffect,
useContext
} from 'react'
import './home.css'
import { TitleContext } from '@/context/useTitleContext';
export default function Home(props) {
const { title, setTitle } = useContext(TitleContext)
useEffect(() => {
fetch("/_assets/directory.json").then(res => res.json()).then(data => {
console.log(data)
})
}, [])
useEffect(() => {
setTitle('dynamic_compile')
}, [])
return (
<section>
<section>
1233
{title}
<button onClick={() => setTitle('123')}>123</button>
</section>
<section>
22s

View File

View File

@@ -0,0 +1,27 @@
import {
useState,
useEffect,
useContext
} from 'react'
import './news.css'
import { TitleContext } from '@/context/useTitleContext';
export default function News(props) {
const { title, setTitle } = useContext(TitleContext)
useEffect(() => {
setTitle('news')
}, [])
return (
<section>
<section>
1
<button onClick={() => setTitle('123')}>123</button>
</section>
<section>
2
</section>
</section>
)
}

View File

@@ -1,15 +1,23 @@
import {
useState,
useEffect
useEffect,
useContext
} from 'react'
import './operator.css'
import { TitleContext } from '@/context/useTitleContext';
export default function Operator(props) {
const { title, setTitle } = useContext(TitleContext)
useEffect(() => {
setTitle('Chen')
}, [])
return (
<section>
<section>
1
<button onClick={() => setTitle('123')}>123</button>
</section>
<section>
2

View File

@@ -1,3 +1,149 @@
.main {
flex-grow: 1;
}
.header {
width: auto;
position: sticky;
top: 0;
right: 0;
padding: 1rem;
z-index: 1;
height: 3rem;
display: flex;
flex-direction: row;
align-items: center;
flex-wrap: nowrap;
justify-content: flex-start;
}
.header .dropdown {
margin-left: auto;
}
.navButton {
padding: 0.5rem;
font-size: 2rem;
width: 2rem;
height: 2rem;
cursor: pointer;
display: flex;
justify-content: center;
align-items: flex-start;
flex-direction: column;
z-index: 2;
}
.navButton .bar {
width: 2rem;
height: 0.2rem;
background-color: var(--text-color);
transition: transform cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
}
.navButton .bar:nth-child(1) {
transform: translate(0, -200%);
}
.navButton .bar:nth-child(3) {
transform: translate(0, 200%);
}
.navButton.active .bar:nth-child(1) {
transform: translate(0, 100%) rotateZ(45deg) scaleX(0.5) translate(-50%);
}
.navButton.active .bar:nth-child(2) {
transform: rotateZ(-45deg);
}
.navButton.active .bar:nth-child(3) {
transform: translate(0, -100%) rotateZ(45deg) scaleX(0.5) translate(50%);
}
.drawer {
position: fixed;
top: 0;
left: -15rem;
width: 15rem;
height: 100%;
z-index: 0;
pointer-events: none;
transition: left cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-align: center;
display: flex;
flex-direction: row;
align-items: flex-start;
gap: 1rem;
}
.drawer .links {
padding: 8rem 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 1rem;
background-color: var(--drawer-background-color);
height: 100%;
width: 15rem;
}
.drawer .overlay {
height: 100%;
flex-grow: 1;
z-index: 2;
}
.drawer.active {
pointer-events: all;
left: 0;
width: 100vw;
}
.drawer a {
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
color: var(--text-color);
font-size: 0.8rem;
font-weight: 500;
transition: color cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
text-transform: uppercase;
}
.drawer a:hover {
color: var(--link-highlight-color);
}
.main {
margin: 0 auto;
width: 90%;
max-width: 100rem;
}
.footer .section {
border-top: 1px solid var(--border-color);
padding: 1rem 0;
display: flex;
align-content: center;
justify-content: center;
align-items: center;
flex-wrap: nowrap;
}
.footer .links {
flex-direction: row;
gap: 1rem;
}
.footer .links .separator {
height: 1rem;
border-left: 2px solid var(--border-color);
}
.footer .copyright {
flex-direction: column;
gap: 0.5rem;
font-size: 12px;
}

View File

@@ -1,6 +1,7 @@
import {
useState,
useEffect
useEffect,
useContext
} from 'react'
import {
Outlet,
@@ -11,21 +12,100 @@ import {
useNavigate,
} from "react-router-dom";
import './root.css'
import routes from '@/routes'
import { TitleContext } from '@/context/useTitleContext';
import { LanguageContext } from '@/context/useLanguageContext';
import Dropdown from '@/component/dropdown';
import Popup from '@/component/popup';
import ReturnButton from '@/component/return_button';
export default function Root(props) {
const { title, setTitle } = useContext(TitleContext)
const [drawerHidden, setDrawerHidden] = useState(true)
const {
language, setLanguage,
drawerTextDefaultLang,
drawerAlternateLang,
i18n
} = useContext(LanguageContext)
const [drawerDestinations, setDrawerDestinations] = useState(null)
const toggleDrawer = () => {
setDrawerHidden(!drawerHidden)
}
const renderDrawerDestinations = () => {
return routes.filter((item) => item.inDrawer).map((item) => {
if (typeof item.element.type === 'string') {
return (
<a key={item.name}
href={item.path}
target="_blank">
<section>
{i18n.key[item.name][drawerTextDefaultLang]}
</section>
<section>
{i18n.key[item.name][drawerAlternateLang]}
</section>
</a>
)
} else {
return (
<NavLink to={item.path} key={item.name}>
<section>
{i18n.key[item.name][drawerTextDefaultLang]}
</section>
<section>
{i18n.key[item.name][drawerAlternateLang]}
</section>
</NavLink>
)
}
})
}
useEffect(() => {
setDrawerDestinations(renderDrawerDestinations())
}, [drawerAlternateLang])
return (
<>
<header>
<section>Nav button</section>
<nav>
<NavLink to="/">Home</NavLink>
<NavLink to="/operator/chen">About</NavLink>
<NavLink to="/contact">Contact</NavLink>
</nav>
<header className='header'>
<section
className={`navButton ${drawerHidden ? '' : 'active'}`}
onClick={() => toggleDrawer()}
>
<section className='bar'></section>
<section className='bar'></section>
<section className='bar'></section>
</section>
<Dropdown
text={i18n.key[language][language]}
menu={i18n.available.map((item) => {
return {
name: i18n.key[item][language],
value: item
}
})}
onClick={(item) => {
setLanguage(item.value)
}}
/>
</header>
<nav className={`drawer ${drawerHidden ? '' : 'active'}`}>
<section
className='links'
>
{drawerDestinations}
</section>
<section
className={`overlay ${drawerHidden ? '' : 'active'}`}
onClick={() => toggleDrawer()}
/>
</nav>
<main className='main'>
<section>
<section>Dynamic Compile</section>
<section>{title}</section>
<section>
{/* <NavLink to="/home">All</NavLink>
<NavLink to="/about">Operator</NavLink>
@@ -34,16 +114,23 @@ export default function Root(props) {
</section>
<Outlet />
</main>
<footer>
<section>
<p>Privacy Policy</p>
<p>Github</p>
<p>Contact Us</p>
<footer className='footer'>
<section className='links section'>
<a href="https://privacy.halyul.dev" target="_blank" className='item'>{i18n.key.privacy_policy[language]}</a>
<span className='separator' />
<a href="https://github.com/Halyul/aklive2d" target="_blank" className='item'>Github</a>
<span className='separator'/>
<Popup
className='item'
title={i18n.key.contact_us[language]}
>
ak#halyul.dev
</Popup>
</section>
<section>
<p>Spine Runtimes © 2013 - 2019, Esoteric Software LLC</p>
<p>Assets © 2017 - 2023 上海鹰角网络科技有限公司</p>
<p>Source Code © 2021 - 2023 Halyul</p>
<section className='copyright section'>
<span>Spine Runtimes © 2013 - 2019, Esoteric Software LLC</span>
<span>Assets © 2017 - 2023 上海鹰角网络科技有限公司</span>
<span>Source Code © 2021 - 2023 Halyul</span>
</section>
</footer>
</>