mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-17 13:30:00 -04:00
feat: framer motion settings
This commit is contained in:
parent
83f1083d75
commit
90a858d1e3
16 changed files with 461 additions and 3 deletions
7
framer-motion.d.ts
vendored
Normal file
7
framer-motion.d.ts
vendored
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import * as React from "preact/compat";
|
||||||
|
|
||||||
|
declare module "framer-motion" {
|
||||||
|
export interface AnimatePresenceProps {
|
||||||
|
children?: React.ReactNode;
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@titaniumnetwork-dev/ultraviolet": "^2.0.0",
|
"@titaniumnetwork-dev/ultraviolet": "^2.0.0",
|
||||||
"@tomphttp/bare-server-node": "^2.0.1",
|
"@tomphttp/bare-server-node": "^2.0.1",
|
||||||
|
"classnames": "^2.3.2",
|
||||||
|
"framer-motion": "^10.16.16",
|
||||||
"i18next": "^23.7.9",
|
"i18next": "^23.7.9",
|
||||||
"i18next-browser-languagedetector": "^7.2.0",
|
"i18next-browser-languagedetector": "^7.2.0",
|
||||||
"million": "^2.6.4",
|
"million": "^2.6.4",
|
||||||
|
|
42
pnpm-lock.yaml
generated
42
pnpm-lock.yaml
generated
|
@ -11,6 +11,12 @@ dependencies:
|
||||||
'@tomphttp/bare-server-node':
|
'@tomphttp/bare-server-node':
|
||||||
specifier: ^2.0.1
|
specifier: ^2.0.1
|
||||||
version: 2.0.1
|
version: 2.0.1
|
||||||
|
classnames:
|
||||||
|
specifier: ^2.3.2
|
||||||
|
version: 2.3.2
|
||||||
|
framer-motion:
|
||||||
|
specifier: ^10.16.16
|
||||||
|
version: 10.16.16(react@18.2.0)
|
||||||
i18next:
|
i18next:
|
||||||
specifier: ^23.7.9
|
specifier: ^23.7.9
|
||||||
version: 23.7.10
|
version: 23.7.10
|
||||||
|
@ -359,6 +365,20 @@ packages:
|
||||||
'@babel/helper-validator-identifier': 7.22.20
|
'@babel/helper-validator-identifier': 7.22.20
|
||||||
to-fast-properties: 2.0.0
|
to-fast-properties: 2.0.0
|
||||||
|
|
||||||
|
/@emotion/is-prop-valid@0.8.8:
|
||||||
|
resolution: {integrity: sha512-u5WtneEAr5IDG2Wv65yhunPSMLIpuKsbuOktRojfrEiEvRyC85LgPMZI63cr7NUqT8ZIGdSVg8ZKGxIug4lXcA==}
|
||||||
|
requiresBuild: true
|
||||||
|
dependencies:
|
||||||
|
'@emotion/memoize': 0.7.4
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
/@emotion/memoize@0.7.4:
|
||||||
|
resolution: {integrity: sha512-Ja/Vfqe3HpuzRsG1oBtWTHk2PGZ7GR+2Vz5iYGelAw8dx32K0y7PjVuxK6z1nMpZOqAFsRUPCkK1YjJ56qJlgw==}
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
/@esbuild/android-arm64@0.19.9:
|
/@esbuild/android-arm64@0.19.9:
|
||||||
resolution: {integrity: sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==}
|
resolution: {integrity: sha512-q4cR+6ZD0938R19MyEW3jEsMzbb/1rulLXiNAJQADD/XYp7pT+rOS5JGxvpRW8dFDEfjW4wLgC/3FXIw4zYglQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -1220,6 +1240,10 @@ packages:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.3
|
fsevents: 2.3.3
|
||||||
|
|
||||||
|
/classnames@2.3.2:
|
||||||
|
resolution: {integrity: sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cliui@8.0.1:
|
/cliui@8.0.1:
|
||||||
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
|
@ -1817,6 +1841,23 @@ packages:
|
||||||
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/framer-motion@10.16.16(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-je6j91rd7NmUX7L1XHouwJ4v3R+SO4umso2LUcgOct3rHZ0PajZ80ETYZTajzEXEl9DlKyzjyt4AvGQ+lrebOw==}
|
||||||
|
peerDependencies:
|
||||||
|
react: ^18.0.0
|
||||||
|
react-dom: ^18.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react:
|
||||||
|
optional: true
|
||||||
|
react-dom:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
react: 18.2.0
|
||||||
|
tslib: 2.6.2
|
||||||
|
optionalDependencies:
|
||||||
|
'@emotion/is-prop-valid': 0.8.8
|
||||||
|
dev: false
|
||||||
|
|
||||||
/fs-extra@11.2.0:
|
/fs-extra@11.2.0:
|
||||||
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
|
resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
|
||||||
engines: {node: '>=14.14'}
|
engines: {node: '>=14.14'}
|
||||||
|
@ -3252,7 +3293,6 @@ packages:
|
||||||
|
|
||||||
/tslib@2.6.2:
|
/tslib@2.6.2:
|
||||||
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/tsutils@3.21.0(typescript@5.3.3):
|
/tsutils@3.21.0(typescript@5.3.3):
|
||||||
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { Home } from "./pages/Home";
|
||||||
import { NotFound } from "./pages/_404.jsx";
|
import { NotFound } from "./pages/_404.jsx";
|
||||||
import { DiscordPage } from "./pages/discord.jsx";
|
import { DiscordPage } from "./pages/discord.jsx";
|
||||||
import { ProxyFrame } from "./ProxyFrame.js";
|
import { ProxyFrame } from "./ProxyFrame.js";
|
||||||
|
import { Settings } from "./pages/Settings/index.js";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
import "./themes/main.css";
|
import "./themes/main.css";
|
||||||
|
@ -18,6 +19,7 @@ export function App() {
|
||||||
<Route path="/" component={Home} />
|
<Route path="/" component={Home} />
|
||||||
<Route path="/discord" component={DiscordPage} />
|
<Route path="/discord" component={DiscordPage} />
|
||||||
<Route path="/proxyframe/:id" component={ProxyFrame} />
|
<Route path="/proxyframe/:id" component={ProxyFrame} />
|
||||||
|
<Route path="/settings" component={Settings} />
|
||||||
<Route default component={NotFound} />
|
<Route default component={NotFound} />
|
||||||
</Router>
|
</Router>
|
||||||
</LocationProvider>
|
</LocationProvider>
|
||||||
|
|
|
@ -17,5 +17,13 @@
|
||||||
"sub": "Would you like to open this via proxy?",
|
"sub": "Would you like to open this via proxy?",
|
||||||
"button1": "Open Normally",
|
"button1": "Open Normally",
|
||||||
"button2": "Use Proxy"
|
"button2": "Use Proxy"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"tabs": {
|
||||||
|
"proxy": "Proxy",
|
||||||
|
"tab": "Tab",
|
||||||
|
"custom": "Customization",
|
||||||
|
"misc": "Misc"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,13 @@
|
||||||
"sub": "プロキシ経由で開きますか?",
|
"sub": "プロキシ経由で開きますか?",
|
||||||
"button1": "通常通り開く",
|
"button1": "通常通り開く",
|
||||||
"button2": "プロキシを使用する"
|
"button2": "プロキシを使用する"
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"tabs": {
|
||||||
|
"proxy": "プロキシ",
|
||||||
|
"tab": "タブ",
|
||||||
|
"custom": "カスタマイズ",
|
||||||
|
"misc": "その他"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
19
src/pages/Settings/Customization.tsx
Normal file
19
src/pages/Settings/Customization.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||||
|
|
||||||
|
const Customization = ({ id, active }) => (
|
||||||
|
<motion.div
|
||||||
|
role="tabpanel"
|
||||||
|
id={id}
|
||||||
|
className="tab-content"
|
||||||
|
variants={tabContentVariant}
|
||||||
|
animate={active ? "active" : "inactive"}
|
||||||
|
initial="inactive"
|
||||||
|
>
|
||||||
|
<motion.div variants={settingsPageVariant} className="content-card">
|
||||||
|
<h1>Customization</h1>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Customization;
|
19
src/pages/Settings/Misc.tsx
Normal file
19
src/pages/Settings/Misc.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||||
|
|
||||||
|
const Misc = ({ id, active }) => (
|
||||||
|
<motion.div
|
||||||
|
role="tabpanel"
|
||||||
|
id={id}
|
||||||
|
className="tab-content"
|
||||||
|
variants={tabContentVariant}
|
||||||
|
animate={active ? "active" : "inactive"}
|
||||||
|
initial="inactive"
|
||||||
|
>
|
||||||
|
<motion.div variants={settingsPageVariant} className="content-card">
|
||||||
|
<h1>Misc settings</h1>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Misc;
|
19
src/pages/Settings/Proxy.tsx
Normal file
19
src/pages/Settings/Proxy.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||||
|
|
||||||
|
const Proxy = ({ id, active }) => (
|
||||||
|
<motion.div
|
||||||
|
role="tabpanel"
|
||||||
|
id={id}
|
||||||
|
className="tab-content"
|
||||||
|
variants={tabContentVariant}
|
||||||
|
animate={active ? "active" : "inactive"}
|
||||||
|
initial="inactive"
|
||||||
|
>
|
||||||
|
<motion.div variants={settingsPageVariant} className="content-card">
|
||||||
|
<h1>Porxy</h1>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default Proxy;
|
106
src/pages/Settings/TabComponent.tsx
Normal file
106
src/pages/Settings/TabComponent.tsx
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
import { useState, useEffect } from "preact/hooks";
|
||||||
|
import cn from "classnames";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import "./styles.css";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
|
||||||
|
const tabVariant = {
|
||||||
|
active: {
|
||||||
|
width: "55%",
|
||||||
|
transition: {
|
||||||
|
type: "tween",
|
||||||
|
duration: 0.4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
width: "15%",
|
||||||
|
transition: {
|
||||||
|
type: "tween",
|
||||||
|
duration: 0.4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabTextVariant = {
|
||||||
|
active: {
|
||||||
|
opacity: 1,
|
||||||
|
x: 0,
|
||||||
|
display: "block",
|
||||||
|
transition: {
|
||||||
|
type: "tween",
|
||||||
|
duration: 0.3,
|
||||||
|
delay: 0.3
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
opacity: 0,
|
||||||
|
x: -30,
|
||||||
|
transition: {
|
||||||
|
type: "tween",
|
||||||
|
duration: 0.3,
|
||||||
|
delay: 0.1
|
||||||
|
},
|
||||||
|
transitionEnd: { display: "none" }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const TabComponent = ({ tabs, defaultIndex = 0 }) => {
|
||||||
|
const [activeTabIndex, setActiveTabIndex] = useState(defaultIndex);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.style.setProperty(
|
||||||
|
"--active-color",
|
||||||
|
tabs[activeTabIndex].color
|
||||||
|
);
|
||||||
|
}, [activeTabIndex, tabs]);
|
||||||
|
|
||||||
|
// Default to a tab based on the URL hash value
|
||||||
|
useEffect(() => {
|
||||||
|
const tabFromHash = tabs.findIndex(
|
||||||
|
(tab) => `#${tab.id}` === window.location.hash
|
||||||
|
);
|
||||||
|
setActiveTabIndex(tabFromHash !== -1 ? tabFromHash : defaultIndex);
|
||||||
|
}, [tabs, defaultIndex]);
|
||||||
|
|
||||||
|
const onTabClick = (index) => {
|
||||||
|
setActiveTabIndex(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div class="flex flex-col items-center">
|
||||||
|
<div className="container h-full w-full">
|
||||||
|
<div className="tabs-component">
|
||||||
|
<ul className="tab-links" role="tablist">
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<motion.li
|
||||||
|
key={tab.id}
|
||||||
|
className={cn("tab", { active: activeTabIndex === index })}
|
||||||
|
role="presentation"
|
||||||
|
variants={tabVariant}
|
||||||
|
animate={activeTabIndex === index ? "active" : "inactive"}
|
||||||
|
>
|
||||||
|
<a href={`#${tab.id}`} onClick={() => onTabClick(index)}>
|
||||||
|
{tab.icon}
|
||||||
|
<motion.span variants={tabTextVariant}>
|
||||||
|
{t(tab.title)}
|
||||||
|
</motion.span>
|
||||||
|
</a>
|
||||||
|
</motion.li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
{tabs.map((tab, index) => (
|
||||||
|
<tab.content
|
||||||
|
key={tab.id}
|
||||||
|
id={`${tab.id}-content`}
|
||||||
|
active={activeTabIndex === index}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TabComponent;
|
19
src/pages/Settings/TabSettings.tsx
Normal file
19
src/pages/Settings/TabSettings.tsx
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import { tabContentVariant, settingsPageVariant } from "./Variants";
|
||||||
|
|
||||||
|
const TabSettings = ({ id, active }) => (
|
||||||
|
<motion.div
|
||||||
|
role="tabpanel"
|
||||||
|
id={id}
|
||||||
|
className="tab-content"
|
||||||
|
variants={tabContentVariant}
|
||||||
|
animate={active ? "active" : "inactive"}
|
||||||
|
initial="inactive"
|
||||||
|
>
|
||||||
|
<motion.div variants={settingsPageVariant} className="content-card">
|
||||||
|
<h1>Tab settings</h1>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default TabSettings;
|
30
src/pages/Settings/Variants.tsx
Normal file
30
src/pages/Settings/Variants.tsx
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
const tabContentVariant = {
|
||||||
|
active: {
|
||||||
|
display: "block",
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
display: "none"
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const settingsPageVariant = {
|
||||||
|
active: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
duration: 0.5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inactive: {
|
||||||
|
opacity: 0,
|
||||||
|
y: 10,
|
||||||
|
transition: {
|
||||||
|
duration: 0.5
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export { settingsPageVariant, tabContentVariant };
|
11
src/pages/Settings/index.tsx
Normal file
11
src/pages/Settings/index.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import TabComponent from "./TabComponent";
|
||||||
|
import { HeaderRoute } from "../../components/HeaderRoute";
|
||||||
|
import tabs from "./tabs";
|
||||||
|
|
||||||
|
export function Settings() {
|
||||||
|
return (
|
||||||
|
<HeaderRoute>
|
||||||
|
<TabComponent tabs={tabs} />
|
||||||
|
</HeaderRoute>
|
||||||
|
);
|
||||||
|
}
|
128
src/pages/Settings/styles.css
Normal file
128
src/pages/Settings/styles.css
Normal file
|
@ -0,0 +1,128 @@
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--white: #fff;
|
||||||
|
--black: #333;
|
||||||
|
--active-color: #f1f1f1;
|
||||||
|
--border-radius: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
|
background: var(--active-color);
|
||||||
|
transition: background 1.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs-component {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: auto;
|
||||||
|
padding: 40px;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-links {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0 auto 20px;
|
||||||
|
list-style: none;
|
||||||
|
max-width: 400px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--black);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab::before {
|
||||||
|
content: "";
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0.2;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: var(--border-radius);
|
||||||
|
background: none;
|
||||||
|
transition: background 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab svg {
|
||||||
|
height: 30px;
|
||||||
|
width: 30px;
|
||||||
|
min-width: 30px;
|
||||||
|
fill: var(--black);
|
||||||
|
transition: fill 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active::before {
|
||||||
|
background: var(--active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab span {
|
||||||
|
font-weight: 700;
|
||||||
|
margin-left: 10px;
|
||||||
|
transition: color 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active span {
|
||||||
|
color: var(--active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab.active svg {
|
||||||
|
fill: var(--active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab a {
|
||||||
|
padding: 16px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cards {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card {
|
||||||
|
width: 48%;
|
||||||
|
margin-bottom: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card .info::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 3px;
|
||||||
|
bottom: -5px;
|
||||||
|
background: var(--active-color);
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card img {
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card h3 {
|
||||||
|
margin: 0 0 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-card .info {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
42
src/pages/Settings/tabs.tsx
Normal file
42
src/pages/Settings/tabs.tsx
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import Proxy from "./Proxy";
|
||||||
|
import TabSettings from "./TabSettings";
|
||||||
|
import Misc from "./Misc";
|
||||||
|
import Customization from "./Customization";
|
||||||
|
|
||||||
|
import { GoBrowser } from "react-icons/go";
|
||||||
|
import { AiOutlineLaptop } from "react-icons/ai";
|
||||||
|
import { FaPalette } from "react-icons/fa";
|
||||||
|
import { FaGear } from "react-icons/fa6";
|
||||||
|
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
title: "settings.tabs.proxy",
|
||||||
|
id: "proxy",
|
||||||
|
icon: <AiOutlineLaptop />,
|
||||||
|
color: "#5d5dff",
|
||||||
|
content: Proxy
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "settings.tabs.tab",
|
||||||
|
id: "tab",
|
||||||
|
icon: <GoBrowser />,
|
||||||
|
color: "#67bb67",
|
||||||
|
content: TabSettings
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "settings.tabs.custom",
|
||||||
|
id: "custom",
|
||||||
|
icon: <FaPalette />,
|
||||||
|
color: "#63a7c7",
|
||||||
|
content: Customization
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "settings.tabs.misc",
|
||||||
|
id: "misc",
|
||||||
|
icon: <FaGear />,
|
||||||
|
color: "#f56868",
|
||||||
|
content: Misc
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default tabs;
|
|
@ -8,8 +8,6 @@
|
||||||
"jsxImportSource": "preact",
|
"jsxImportSource": "preact",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"react": ["./node_modules/preact/compat/"],
|
|
||||||
"react-dom": ["./node_modules/preact/compat/"]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue