feat(directory): add layout scaffold
This commit is contained in:
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
)
|
||||
98
directory/src/component/dropdown.css
Normal file
98
directory/src/component/dropdown.css
Normal 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;
|
||||
}
|
||||
}
|
||||
47
directory/src/component/dropdown.jsx
Normal file
47
directory/src/component/dropdown.jsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
68
directory/src/component/popup.css
Normal file
68
directory/src/component/popup.css
Normal 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;
|
||||
}
|
||||
38
directory/src/component/popup.jsx
Normal file
38
directory/src/component/popup.jsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
50
directory/src/component/return_button.css
Normal file
50
directory/src/component/return_button.css
Normal 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);
|
||||
}
|
||||
33
directory/src/component/return_button.jsx
Normal file
33
directory/src/component/return_button.jsx
Normal 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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
27
directory/src/context/useLanguageContext.jsx
Normal file
27
directory/src/context/useLanguageContext.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
25
directory/src/context/useTitleContext.jsx
Normal file
25
directory/src/context/useTitleContext.jsx
Normal 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
39
directory/src/i18n.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
]
|
||||
@@ -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
|
||||
|
||||
0
directory/src/routes/path/news.css
Normal file
0
directory/src/routes/path/news.css
Normal file
27
directory/src/routes/path/news.jsx
Normal file
27
directory/src/routes/path/news.jsx
Normal 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>
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>
|
||||
</>
|
||||
|
||||
Reference in New Issue
Block a user