♿️ Work on responsiveness for the AppShelf
Fixes #1, Fixes #42, Fixes #82, Fixes #85
This commit is contained in:
2
.github/workflows/docker_dev.yml
vendored
2
.github/workflows/docker_dev.yml
vendored
@@ -18,7 +18,7 @@ env:
|
|||||||
# Use docker.io for Docker Hub if empty
|
# Use docker.io for Docker Hub if empty
|
||||||
REGISTRY: ghcr.io
|
REGISTRY: ghcr.io
|
||||||
# github.repository as <account>/<repo>
|
# github.repository as <account>/<repo>
|
||||||
IMAGE_NAME: ${{ github.repository }}
|
IMAGE_NAME: ${{ github.repository }}-test
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# Push image to GitHub Packages.
|
# Push image to GitHub Packages.
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Card,
|
Card,
|
||||||
LoadingOverlay,
|
LoadingOverlay,
|
||||||
|
ActionIcon,
|
||||||
|
Tooltip,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/form';
|
import { useForm } from '@mantine/form';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
@@ -19,13 +21,42 @@ import { useConfig } from '../../tools/state';
|
|||||||
import { ServiceTypeList } from '../../tools/types';
|
import { ServiceTypeList } from '../../tools/types';
|
||||||
import { AppShelfItemWrapper } from './AppShelfItemWrapper';
|
import { AppShelfItemWrapper } from './AppShelfItemWrapper';
|
||||||
|
|
||||||
|
export function AddItemShelfButton(props: any) {
|
||||||
|
const [opened, setOpened] = useState(false);
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
size="xl"
|
||||||
|
radius="md"
|
||||||
|
opened={props.opened || opened}
|
||||||
|
onClose={() => setOpened(false)}
|
||||||
|
title="Add a service"
|
||||||
|
>
|
||||||
|
<AddAppShelfItemForm setOpened={setOpened} />
|
||||||
|
</Modal>
|
||||||
|
<ActionIcon
|
||||||
|
variant="default"
|
||||||
|
radius="md"
|
||||||
|
size="xl"
|
||||||
|
color="blue"
|
||||||
|
style={props.style}
|
||||||
|
onClick={() => setOpened(true)}
|
||||||
|
>
|
||||||
|
<Tooltip label="Add a service">
|
||||||
|
<Apps />
|
||||||
|
</Tooltip>
|
||||||
|
</ActionIcon>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default function AddItemShelfItem(props: any) {
|
export default function AddItemShelfItem(props: any) {
|
||||||
const [opened, setOpened] = useState(false);
|
const [opened, setOpened] = useState(false);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
size="xl"
|
size="xl"
|
||||||
radius="lg"
|
radius="md"
|
||||||
opened={props.opened || opened}
|
opened={props.opened || opened}
|
||||||
onClose={() => setOpened(false)}
|
onClose={() => setOpened(false)}
|
||||||
title="Add a service"
|
title="Add a service"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { SimpleGrid } from '@mantine/core';
|
||||||
import AppShelf, { AppShelfItem } from './AppShelf';
|
import AppShelf, { AppShelfItem } from './AppShelf';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -16,3 +17,10 @@ export default {
|
|||||||
|
|
||||||
export const Default = (args: any) => <AppShelf {...args} />;
|
export const Default = (args: any) => <AppShelf {...args} />;
|
||||||
export const One = (args: any) => <AppShelfItem {...args} />;
|
export const One = (args: any) => <AppShelfItem {...args} />;
|
||||||
|
export const Ten = (args: any) => (
|
||||||
|
<SimpleGrid>
|
||||||
|
{Array.from(Array(10)).map((_, i) => (
|
||||||
|
<AppShelfItem {...args} key={i} />
|
||||||
|
))}
|
||||||
|
</SimpleGrid>
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,21 +1,29 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
import { Text, AspectRatio, SimpleGrid, Card, Image, Group, Space } from '@mantine/core';
|
import { Text, AspectRatio, SimpleGrid, Card, Image, useMantineTheme } from '@mantine/core';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { serviceItem } from '../../tools/types';
|
import { serviceItem } from '../../tools/types';
|
||||||
import AddItemShelfItem from './AddAppShelfItem';
|
|
||||||
import { AppShelfItemWrapper } from './AppShelfItemWrapper';
|
|
||||||
import AppShelfMenu from './AppShelfMenu';
|
import AppShelfMenu from './AppShelfMenu';
|
||||||
|
|
||||||
const AppShelf = () => {
|
const AppShelf = () => {
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SimpleGrid m="xl" cols={5} spacing="xl">
|
<SimpleGrid
|
||||||
|
cols={7}
|
||||||
|
spacing="xl"
|
||||||
|
breakpoints={[
|
||||||
|
{ maxWidth: 2400, cols: 6, spacing: 'xl' },
|
||||||
|
{ maxWidth: 1800, cols: 5, spacing: 'xl' },
|
||||||
|
{ maxWidth: 1500, cols: 4, spacing: 'lg' },
|
||||||
|
{ maxWidth: 800, cols: 3, spacing: 'md' },
|
||||||
|
{ maxWidth: 400, cols: 3, spacing: 'sm' },
|
||||||
|
{ maxWidth: 400, cols: 2, spacing: 'sm' },
|
||||||
|
]}
|
||||||
|
>
|
||||||
{config.services.map((service) => (
|
{config.services.map((service) => (
|
||||||
<AppShelfItem key={service.name} service={service} />
|
<AppShelfItem key={service.name} service={service} />
|
||||||
))}
|
))}
|
||||||
<AddItemShelfItem />
|
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -23,6 +31,7 @@ const AppShelf = () => {
|
|||||||
export function AppShelfItem(props: any) {
|
export function AppShelfItem(props: any) {
|
||||||
const { service }: { service: serviceItem } = props;
|
const { service }: { service: serviceItem } = props;
|
||||||
const [hovering, setHovering] = useState(false);
|
const [hovering, setHovering] = useState(false);
|
||||||
|
const theme = useMantineTheme();
|
||||||
return (
|
return (
|
||||||
<motion.div
|
<motion.div
|
||||||
key={service.name}
|
key={service.name}
|
||||||
@@ -33,39 +42,41 @@ export function AppShelfItem(props: any) {
|
|||||||
setHovering(false);
|
setHovering(false);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<AppShelfItemWrapper hovering={hovering}>
|
<Card
|
||||||
|
style={{
|
||||||
|
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
|
||||||
|
backgroundColor:
|
||||||
|
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
|
||||||
|
}}
|
||||||
|
radius="md"
|
||||||
|
>
|
||||||
<Card.Section>
|
<Card.Section>
|
||||||
<Group position="apart" mx="lg">
|
<Text mt="sm" align="center" lineClamp={1} weight={500}>
|
||||||
<Space />
|
{service.name}
|
||||||
<Text
|
</Text>
|
||||||
// TODO: #1 Remove this hack to get the text to be centered.
|
<motion.div
|
||||||
ml={15}
|
style={{
|
||||||
style={{
|
position: 'absolute',
|
||||||
alignSelf: 'center',
|
top: 5,
|
||||||
alignContent: 'center',
|
right: 5,
|
||||||
alignItems: 'center',
|
alignSelf: 'flex-end',
|
||||||
justifyContent: 'center',
|
}}
|
||||||
justifyItems: 'center',
|
animate={{
|
||||||
}}
|
opacity: hovering ? 1 : 0,
|
||||||
mt="sm"
|
}}
|
||||||
weight={500}
|
>
|
||||||
>
|
<AppShelfMenu service={service} />
|
||||||
{service.name}
|
</motion.div>
|
||||||
</Text>
|
|
||||||
<motion.div
|
|
||||||
style={{
|
|
||||||
alignSelf: 'flex-end',
|
|
||||||
}}
|
|
||||||
animate={{
|
|
||||||
opacity: hovering ? 1 : 0,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<AppShelfMenu service={service} />
|
|
||||||
</motion.div>
|
|
||||||
</Group>
|
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
<Card.Section>
|
<Card.Section>
|
||||||
<AspectRatio ratio={5 / 3} m="xl">
|
<AspectRatio
|
||||||
|
ratio={3 / 5}
|
||||||
|
m="xl"
|
||||||
|
style={{
|
||||||
|
width: 150,
|
||||||
|
height: 90,
|
||||||
|
}}
|
||||||
|
>
|
||||||
<motion.i
|
<motion.i
|
||||||
whileHover={{
|
whileHover={{
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
@@ -73,19 +84,19 @@ export function AppShelfItem(props: any) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Image
|
<Image
|
||||||
|
style={{
|
||||||
|
maxWidth: 80,
|
||||||
|
}}
|
||||||
|
fit="contain"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
window.open(service.url);
|
window.open(service.url);
|
||||||
}}
|
}}
|
||||||
style={{
|
|
||||||
maxWidth: '50%',
|
|
||||||
marginBottom: 10,
|
|
||||||
}}
|
|
||||||
src={service.icon}
|
src={service.icon}
|
||||||
/>
|
/>
|
||||||
</motion.i>
|
</motion.i>
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
</Card.Section>
|
</Card.Section>
|
||||||
</AppShelfItemWrapper>
|
</Card>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,6 @@ export function AppShelfItemWrapper(props: any) {
|
|||||||
style={{
|
style={{
|
||||||
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
|
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
|
||||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
|
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[1],
|
||||||
|
|
||||||
//TODO: #3 Fix this temporary fix and make the width and height dynamic / responsive
|
|
||||||
width: 200,
|
|
||||||
height: 180,
|
|
||||||
}}
|
}}
|
||||||
radius="md"
|
radius="md"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ export function SettingsMenuButton(props: any) {
|
|||||||
</Modal>
|
</Modal>
|
||||||
<ActionIcon
|
<ActionIcon
|
||||||
variant="default"
|
variant="default"
|
||||||
radius="xl"
|
radius="md"
|
||||||
size="xl"
|
size="xl"
|
||||||
color="blue"
|
color="blue"
|
||||||
style={props.style}
|
style={props.style}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ export function Footer({ links }: FooterCenteredProps) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Made with ❤️ by @
|
Made with ❤️ by @
|
||||||
<Anchor href="https://github.com/ajnart" style={{ color: 'inherit', fontStyle: 'inherit' }}>
|
<Anchor href="https://github.com/ajnart" style={{ color: 'inherit', fontStyle: 'inherit', fontSize: 'inherit' }}>
|
||||||
ajnart
|
ajnart
|
||||||
</Anchor>
|
</Anchor>
|
||||||
</Text>
|
</Text>
|
||||||
|
|||||||
@@ -1,18 +1,11 @@
|
|||||||
import React, { useState } from 'react';
|
import React from 'react';
|
||||||
import {
|
import { createStyles, Header as Head, Group, Drawer, Center } from '@mantine/core';
|
||||||
createStyles,
|
|
||||||
Header as Head,
|
|
||||||
Container,
|
|
||||||
Group,
|
|
||||||
Burger,
|
|
||||||
Drawer,
|
|
||||||
Center,
|
|
||||||
} from '@mantine/core';
|
|
||||||
import { useBooleanToggle } from '@mantine/hooks';
|
import { useBooleanToggle } from '@mantine/hooks';
|
||||||
import { NextLink } from '@mantine/next';
|
import { NextLink } from '@mantine/next';
|
||||||
import { Logo } from './Logo';
|
import { Logo } from './Logo';
|
||||||
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
|
||||||
import CalendarComponent from '../modules/calendar/CalendarModule';
|
import CalendarComponent from '../modules/calendar/CalendarModule';
|
||||||
|
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
||||||
|
import { AddItemShelfButton } from '../AppShelf/AddAppShelfItem';
|
||||||
|
|
||||||
const HEADER_HEIGHT = 60;
|
const HEADER_HEIGHT = 60;
|
||||||
|
|
||||||
@@ -40,8 +33,6 @@ const useStyles = createStyles((theme) => ({
|
|||||||
|
|
||||||
header: {
|
header: {
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
height: '100%',
|
height: '100%',
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -94,62 +85,33 @@ interface HeaderResponsiveProps {
|
|||||||
|
|
||||||
export function Header({ links }: HeaderResponsiveProps) {
|
export function Header({ links }: HeaderResponsiveProps) {
|
||||||
const [opened, toggleOpened] = useBooleanToggle(false);
|
const [opened, toggleOpened] = useBooleanToggle(false);
|
||||||
const [active, setActive] = useState('/');
|
|
||||||
const { classes, cx } = useStyles();
|
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 (
|
return (
|
||||||
<Head height={HEADER_HEIGHT} mb={10} className={classes.root}>
|
<Head height={HEADER_HEIGHT}>
|
||||||
<Container className={classes.header}>
|
<Group direction="row" align="center" position="apart" className={classes.header} mx="xl">
|
||||||
<Group>
|
<NextLink style={{ textDecoration: 'none' }} href="/">
|
||||||
<NextLink style={{ textDecoration: 'none' }} href="/">
|
<Logo style={{ fontSize: 22 }} />
|
||||||
<Logo style={{ fontSize: 22 }} />
|
</NextLink>
|
||||||
</NextLink>
|
|
||||||
</Group>
|
|
||||||
<Group spacing={5} className={classes.links}>
|
|
||||||
{items}
|
|
||||||
</Group>
|
|
||||||
<Group>
|
<Group>
|
||||||
<SettingsMenuButton />
|
<SettingsMenuButton />
|
||||||
|
<AddItemShelfButton />
|
||||||
<Burger
|
|
||||||
opened={opened}
|
|
||||||
onClick={() => toggleOpened()}
|
|
||||||
className={classes.burger}
|
|
||||||
size="sm"
|
|
||||||
/>
|
|
||||||
</Group>
|
</Group>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
opened={opened}
|
opened={opened}
|
||||||
overlayOpacity={0.55}
|
overlayOpacity={0.55}
|
||||||
overlayBlur={3}
|
overlayBlur={3}
|
||||||
onClose={() => toggleOpened()}
|
onClose={() => toggleOpened()}
|
||||||
position="right"
|
position="right"
|
||||||
>
|
>
|
||||||
{opened ?? (
|
{opened ?? (
|
||||||
<Center>
|
<Center>
|
||||||
<CalendarComponent />
|
<CalendarComponent />
|
||||||
</Center>
|
</Center>
|
||||||
)}
|
)}
|
||||||
</Drawer>
|
</Drawer>
|
||||||
</Container>
|
|
||||||
</Head>
|
</Head>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import Navbar from './Navbar';
|
|||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
main: {
|
main: {
|
||||||
[theme.fn.largerThan('md')]: {
|
[theme.fn.largerThan('md')]: {
|
||||||
maxWidth: 1200,
|
maxWidth: 1500,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -1,15 +1,29 @@
|
|||||||
import { Text } from '@mantine/core';
|
import { Group, Text } from '@mantine/core';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { CURRENT_VERSION } from '../../../data/constants';
|
||||||
|
|
||||||
export function Logo({ style }: any) {
|
export function Logo({ style }: any) {
|
||||||
return (
|
return (
|
||||||
<Text
|
<Group>
|
||||||
sx={style}
|
<Text
|
||||||
weight="bold"
|
sx={style}
|
||||||
variant="gradient"
|
weight="bold"
|
||||||
gradient={{ from: 'red', to: 'orange', deg: 145 }}
|
variant="gradient"
|
||||||
>
|
gradient={{ from: 'red', to: 'orange', deg: 145 }}
|
||||||
Homarr
|
>
|
||||||
</Text>
|
Homarr
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: 'gray',
|
||||||
|
fontStyle: 'inherit',
|
||||||
|
fontSize: 'inherit',
|
||||||
|
alignSelf: 'end',
|
||||||
|
alignContent: 'start',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{CURRENT_VERSION}
|
||||||
|
</Text>
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ export default function Navbar() {
|
|||||||
return (
|
return (
|
||||||
<MantineNavbar
|
<MantineNavbar
|
||||||
height="100%"
|
height="100%"
|
||||||
hiddenBreakpoint="md"
|
hiddenBreakpoint="lg"
|
||||||
hidden
|
hidden
|
||||||
width={{
|
width={{
|
||||||
base: 'auto',
|
base: 'auto',
|
||||||
|
|||||||
Reference in New Issue
Block a user