Add project base
This commit is contained in:
134
components/AppShelf/AppShelf.tsx
Normal file
134
components/AppShelf/AppShelf.tsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import React, { useState } from 'react';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import {
|
||||
ActionIcon,
|
||||
createStyles,
|
||||
Grid,
|
||||
Group,
|
||||
Text,
|
||||
Title,
|
||||
Paper,
|
||||
Tooltip,
|
||||
Image,
|
||||
ThemeIcon,
|
||||
useMantineTheme,
|
||||
Anchor,
|
||||
Box,
|
||||
Menu,
|
||||
AspectRatio,
|
||||
} from '@mantine/core';
|
||||
import { ArrowBack, Trash } from 'tabler-icons-react';
|
||||
|
||||
const AppShelf = () => {
|
||||
const Services = loadServices();
|
||||
const [hovering, setHovering] = useState('none');
|
||||
const theme = useMantineTheme();
|
||||
return (
|
||||
<Grid m={'xl'} gutter={'xl'}>
|
||||
{Services.map((service, i) => (
|
||||
<Grid.Col span={4} lg={2} sm={3} key={i}>
|
||||
<motion.div
|
||||
onHoverStart={(e) => {
|
||||
setHovering(service.name);
|
||||
}}
|
||||
onHoverEnd={(e) => {
|
||||
setHovering('none');
|
||||
}}
|
||||
>
|
||||
<AspectRatio ratio={4 / 3}>
|
||||
<Box
|
||||
sx={(theme) => ({
|
||||
backgroundColor:
|
||||
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
||||
textAlign: 'center',
|
||||
padding: theme.spacing.xl,
|
||||
borderRadius: theme.radius.md,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor:
|
||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
|
||||
},
|
||||
})}
|
||||
>
|
||||
<motion.div animate={{opacity: hovering == service.name ? 1 : 0}}>
|
||||
<Menu sx={{ position: 'absolute', top: 3, right: 3 }}>
|
||||
<Menu.Label>Settings</Menu.Label>
|
||||
|
||||
<Menu.Label>Danger zone</Menu.Label>
|
||||
<Menu.Item color="red" icon={<Trash size={14} />}>
|
||||
Delete
|
||||
</Menu.Item>
|
||||
</Menu>
|
||||
</motion.div>
|
||||
<Group position="center">
|
||||
<Anchor href={service.url} target="_blank">
|
||||
<motion.div whileHover={{ scale: 1.2 }}>
|
||||
<Image height={60} src={service.icon} alt={service.name} />
|
||||
</motion.div>
|
||||
</Anchor>
|
||||
<Text>{service.name}</Text>
|
||||
</Group>
|
||||
</Box>
|
||||
</AspectRatio>
|
||||
</motion.div>
|
||||
</Grid.Col>
|
||||
))}
|
||||
</Grid>
|
||||
);
|
||||
};
|
||||
|
||||
export default AppShelf;
|
||||
function loadServices() {
|
||||
return [
|
||||
{
|
||||
name: 'Radarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Radarr/icon.png',
|
||||
url: 'http://server:7878/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
{
|
||||
name: 'Sonarr',
|
||||
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/Sonarr/icon.png',
|
||||
url: 'http://server:8989/',
|
||||
},
|
||||
];
|
||||
}
|
||||
@@ -1,26 +1,28 @@
|
||||
import { ActionIcon, Group, useMantineColorScheme } from '@mantine/core';
|
||||
import { SunIcon, MoonIcon } from '@modulz/radix-icons';
|
||||
import { ActionIcon, useMantineColorScheme } from '@mantine/core';
|
||||
import { Sun, MoonStars } from 'tabler-icons-react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
export function ColorSchemeToggle() {
|
||||
const { colorScheme, toggleColorScheme } = useMantineColorScheme();
|
||||
|
||||
return (
|
||||
<Group position="center" mt="xl">
|
||||
<motion.div
|
||||
whileHover={{ scale: 1.2, rotate: 90 }}
|
||||
whileTap={{
|
||||
scale: 0.8,
|
||||
rotate: -90,
|
||||
borderRadius: '100%',
|
||||
}}
|
||||
>
|
||||
<ActionIcon
|
||||
onClick={() => toggleColorScheme()}
|
||||
size="xl"
|
||||
sx={(theme) => ({
|
||||
backgroundColor:
|
||||
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.yellow[4] : theme.colors.blue[6],
|
||||
})}
|
||||
>
|
||||
{colorScheme === 'dark' ? (
|
||||
<SunIcon width={20} height={20} />
|
||||
) : (
|
||||
<MoonIcon width={20} height={20} />
|
||||
)}
|
||||
{colorScheme === 'dark' ? <Sun size={24} /> : <MoonStars size={24} />}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
</motion.div>
|
||||
);
|
||||
}
|
||||
|
||||
82
components/layout/Footer.tsx
Normal file
82
components/layout/Footer.tsx
Normal file
@@ -0,0 +1,82 @@
|
||||
import React from 'react';
|
||||
import { createStyles, Anchor, Text, Group, ActionIcon } from '@mantine/core';
|
||||
import { BrandGithub, Phone, BrandGmail } from 'tabler-icons-react';
|
||||
import { posix } from 'path';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
footer: {
|
||||
borderTop: `1px solid ${
|
||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[2]
|
||||
}`,
|
||||
},
|
||||
|
||||
inner: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: `${theme.spacing.md}px ${theme.spacing.md}px`,
|
||||
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
flexDirection: 'column',
|
||||
},
|
||||
},
|
||||
|
||||
links: {
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
marginTop: theme.spacing.lg,
|
||||
marginBottom: theme.spacing.sm,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
interface FooterCenteredProps {
|
||||
links: { link: string; label: string }[];
|
||||
}
|
||||
|
||||
export function Footer({ links }: FooterCenteredProps) {
|
||||
const { classes } = useStyles();
|
||||
const items = links.map((link) => (
|
||||
<Anchor<'a'>
|
||||
color="dimmed"
|
||||
key={link.label}
|
||||
href={link.link}
|
||||
sx={{ lineHeight: 1 }}
|
||||
onClick={(event) => event.preventDefault()}
|
||||
size="sm"
|
||||
>
|
||||
{link.label}
|
||||
</Anchor>
|
||||
));
|
||||
|
||||
return (
|
||||
<Group
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 15,
|
||||
}}
|
||||
direction="row"
|
||||
align="center"
|
||||
mb={15}
|
||||
>
|
||||
<Group className={classes.links}>{items}</Group>
|
||||
<Group spacing={'xs'} position="right" noWrap>
|
||||
<ActionIcon<'a'> component="a" href={`https://github.com/ajnart/myhomepage`} size="lg">
|
||||
<BrandGithub size={18} />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
<Text
|
||||
style={{
|
||||
fontSize: '0.75rem',
|
||||
textAlign: 'center',
|
||||
color: '#a0aec0',
|
||||
}}
|
||||
>
|
||||
Made with ❤️ by @
|
||||
<Anchor href="https://github.com/ajnart" style={{ color: 'inherit', fontStyle: 'inherit' }}>
|
||||
ajnart
|
||||
</Anchor>
|
||||
</Text>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
146
components/layout/Header.tsx
Normal file
146
components/layout/Header.tsx
Normal file
@@ -0,0 +1,146 @@
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
createStyles,
|
||||
Header as Head,
|
||||
Container,
|
||||
Group,
|
||||
Burger,
|
||||
Paper,
|
||||
Transition,
|
||||
} from '@mantine/core';
|
||||
import { useBooleanToggle } from '@mantine/hooks';
|
||||
import { NextLink } from '@mantine/next';
|
||||
import { Logo } from './Logo';
|
||||
import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle';
|
||||
|
||||
const HEADER_HEIGHT = 60;
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
root: {
|
||||
position: 'relative',
|
||||
zIndex: 1,
|
||||
},
|
||||
|
||||
dropdown: {
|
||||
position: 'absolute',
|
||||
top: HEADER_HEIGHT,
|
||||
left: 0,
|
||||
right: 0,
|
||||
zIndex: 0,
|
||||
borderTopRightRadius: 0,
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopWidth: 0,
|
||||
overflow: 'hidden',
|
||||
|
||||
[theme.fn.largerThan('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
header: {
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
height: '100%',
|
||||
},
|
||||
|
||||
links: {
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
burger: {
|
||||
[theme.fn.largerThan('sm')]: {
|
||||
display: 'none',
|
||||
},
|
||||
},
|
||||
|
||||
link: {
|
||||
display: 'block',
|
||||
lineHeight: 1,
|
||||
padding: '8px 12px',
|
||||
borderRadius: theme.radius.sm,
|
||||
textDecoration: 'none',
|
||||
color: theme.colorScheme === 'dark' ? theme.colors.dark[0] : theme.colors.gray[7],
|
||||
fontSize: theme.fontSizes.sm,
|
||||
fontWeight: 500,
|
||||
|
||||
'&:hover': {
|
||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
||||
},
|
||||
|
||||
[theme.fn.smallerThan('sm')]: {
|
||||
borderRadius: 0,
|
||||
padding: theme.spacing.md,
|
||||
},
|
||||
},
|
||||
|
||||
linkActive: {
|
||||
'&, &:hover': {
|
||||
backgroundColor:
|
||||
theme.colorScheme === 'dark'
|
||||
? theme.fn.rgba(theme.colors[theme.primaryColor][9], 0.25)
|
||||
: theme.colors[theme.primaryColor][0],
|
||||
color: theme.colors[theme.primaryColor][theme.colorScheme === 'dark' ? 3 : 7],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
interface HeaderResponsiveProps {
|
||||
links: { link: string; label: string }[];
|
||||
}
|
||||
|
||||
export function Header({ links }: HeaderResponsiveProps) {
|
||||
const [opened, toggleOpened] = useBooleanToggle(false);
|
||||
const [active, setActive] = useState('/');
|
||||
const { classes, cx } = useStyles();
|
||||
|
||||
const items = (
|
||||
<>
|
||||
{links.map((link) => (
|
||||
<NextLink
|
||||
key={link.label}
|
||||
href={link.link}
|
||||
className={cx(classes.link, { [classes.linkActive]: active === link.link })}
|
||||
onClick={(event) => {
|
||||
setActive(link.link);
|
||||
toggleOpened(false);
|
||||
}}
|
||||
>
|
||||
{link.label}
|
||||
</NextLink>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
return (
|
||||
<Head height={HEADER_HEIGHT} mb={10} className={classes.root}>
|
||||
<Container className={classes.header}>
|
||||
<Group>
|
||||
<ColorSchemeToggle />
|
||||
<NextLink style={{ textDecoration: 'none' }} href="/">
|
||||
<Logo style={{ fontSize: 22 }} />
|
||||
</NextLink>
|
||||
</Group>
|
||||
<Group spacing={5} className={classes.links}>
|
||||
{items}
|
||||
</Group>
|
||||
|
||||
<Burger
|
||||
opened={opened}
|
||||
onClick={() => toggleOpened()}
|
||||
className={classes.burger}
|
||||
size="sm"
|
||||
/>
|
||||
|
||||
<Transition transition="pop-top-right" duration={200} mounted={opened}>
|
||||
{(styles) => (
|
||||
<Paper className={classes.dropdown} withBorder style={{ zIndex: 99 }}>
|
||||
{items}
|
||||
</Paper>
|
||||
)}
|
||||
</Transition>
|
||||
</Container>
|
||||
</Head>
|
||||
);
|
||||
}
|
||||
32
components/layout/Layout.tsx
Normal file
32
components/layout/Layout.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { AppShell, Center, createStyles } from '@mantine/core';
|
||||
import { Header } from './Header';
|
||||
import { Footer } from './Footer';
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
main: {
|
||||
[theme.fn.smallerThan('md')]: {
|
||||
maxWidth: '90vw',
|
||||
},
|
||||
[theme.fn.largerThan('md')]: {
|
||||
width: 1200,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
export default function Layout({ children, style }: any) {
|
||||
const { classes, cx } = useStyles();
|
||||
return (
|
||||
<AppShell header={<Header links={[]} />} footer={<Footer links={[]} />}>
|
||||
<Center>
|
||||
<main
|
||||
className={cx(classes.main)}
|
||||
style={{
|
||||
...style,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</main>
|
||||
</Center>
|
||||
</AppShell>
|
||||
);
|
||||
}
|
||||
15
components/layout/Logo.tsx
Normal file
15
components/layout/Logo.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Text } from '@mantine/core';
|
||||
import * as React from 'react';
|
||||
|
||||
export function Logo({ style }: any) {
|
||||
return (
|
||||
<Text
|
||||
sx={style}
|
||||
weight="bold"
|
||||
variant="gradient"
|
||||
gradient={{ from: 'red', to: 'orange', deg: 145 }}
|
||||
>
|
||||
MyHomePage
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user