mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-14 09:25:47 +01:00
Improve UI
This commit is contained in:
@@ -12,6 +12,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
Grid,
|
Grid,
|
||||||
|
Card,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useForm } from '@mantine/hooks';
|
import { useForm } from '@mantine/hooks';
|
||||||
import { motion } from 'framer-motion';
|
import { motion } from 'framer-motion';
|
||||||
@@ -30,9 +31,8 @@ export default function AddItemShelfItem(props: any) {
|
|||||||
name: '',
|
name: '',
|
||||||
icon: '',
|
icon: '',
|
||||||
url: '',
|
url: '',
|
||||||
|
apiKey: undefined as unknown as string,
|
||||||
},
|
},
|
||||||
|
|
||||||
validationRules: {},
|
|
||||||
});
|
});
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@@ -85,7 +85,7 @@ export default function AddItemShelfItem(props: any) {
|
|||||||
placeholder="http://localhost:8989"
|
placeholder="http://localhost:8989"
|
||||||
value={form.values.url}
|
value={form.values.url}
|
||||||
onChange={(event) => form.setFieldValue('url', event.currentTarget.value)}
|
onChange={(event) => form.setFieldValue('url', event.currentTarget.value)}
|
||||||
error={form.errors.icon && 'Icon url is invalid'}
|
error={form.errors.url && 'Service url is invalid'}
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
label="Select the type of service (used for API calls)"
|
label="Select the type of service (used for API calls)"
|
||||||
@@ -97,6 +97,16 @@ export default function AddItemShelfItem(props: any) {
|
|||||||
onChange={(value) => form.setFieldValue('type', value ?? 'Other')}
|
onChange={(value) => form.setFieldValue('type', value ?? 'Other')}
|
||||||
data={ServiceTypeList}
|
data={ServiceTypeList}
|
||||||
/>
|
/>
|
||||||
|
{(form.values.type === 'Sonarr' || form.values.type === 'Radarr') && (
|
||||||
|
<TextInput
|
||||||
|
required
|
||||||
|
label="API key"
|
||||||
|
placeholder="Your API key"
|
||||||
|
value={form.values.apiKey}
|
||||||
|
onChange={(event) => form.setFieldValue('apiKey', event.currentTarget.value)}
|
||||||
|
error={form.errors.apiKey && 'Invalid API key'}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group grow position="center" mt="xl">
|
<Group grow position="center" mt="xl">
|
||||||
@@ -111,18 +121,15 @@ export default function AddItemShelfItem(props: any) {
|
|||||||
}}
|
}}
|
||||||
ratio={4 / 3}
|
ratio={4 / 3}
|
||||||
>
|
>
|
||||||
<Box
|
<Card
|
||||||
sx={{
|
style={{
|
||||||
backgroundColor:
|
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[6] : theme.colors.gray[0],
|
|
||||||
textAlign: 'center',
|
|
||||||
padding: theme.spacing.xl,
|
|
||||||
borderRadius: theme.radius.md,
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
|
theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
|
||||||
},
|
width: 200,
|
||||||
|
height: 180,
|
||||||
}}
|
}}
|
||||||
|
radius="md"
|
||||||
>
|
>
|
||||||
<Group direction="column" position="center">
|
<Group direction="column" position="center">
|
||||||
<motion.div whileHover={{ scale: 1.2 }}>
|
<motion.div whileHover={{ scale: 1.2 }}>
|
||||||
@@ -130,7 +137,7 @@ export default function AddItemShelfItem(props: any) {
|
|||||||
</motion.div>
|
</motion.div>
|
||||||
<Text>Add Service</Text>
|
<Text>Add Service</Text>
|
||||||
</Group>
|
</Group>
|
||||||
</Box>
|
</Card>
|
||||||
</AspectRatio>
|
</AspectRatio>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,18 @@
|
|||||||
import AppShelf from './AppShelf';
|
import AppShelf, { AppShelfItem } from './AppShelf';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
title: 'Item Shelf',
|
title: 'Item Shelf',
|
||||||
|
component: AppShelf,
|
||||||
|
args: {
|
||||||
|
service: {
|
||||||
|
name: 'qBittorrent',
|
||||||
|
url: 'http://',
|
||||||
|
icon: 'https://cdn.jsdelivr.net/gh/IceWhaleTech/CasaOS-AppStore@main/Apps/qBittorrent/icon.png',
|
||||||
|
type: 'qBittorrent',
|
||||||
|
apiKey: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Default = (args: any) => <AppShelf {...args} />;
|
export const Default = (args: any) => <AppShelf {...args} />;
|
||||||
|
export const One = (args: any) => <AppShelfItem {...args} />;
|
||||||
@@ -4,7 +4,6 @@ import {
|
|||||||
Grid,
|
Grid,
|
||||||
Group,
|
Group,
|
||||||
Text,
|
Text,
|
||||||
Image,
|
|
||||||
Anchor,
|
Anchor,
|
||||||
Box,
|
Box,
|
||||||
AspectRatio,
|
AspectRatio,
|
||||||
@@ -13,6 +12,10 @@ import {
|
|||||||
Container,
|
Container,
|
||||||
SimpleGrid,
|
SimpleGrid,
|
||||||
Space,
|
Space,
|
||||||
|
Card,
|
||||||
|
useMantineTheme,
|
||||||
|
Image,
|
||||||
|
Badge,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { showNotification } from '@mantine/notifications';
|
import { showNotification } from '@mantine/notifications';
|
||||||
import { AlertCircle, Cross, X } from 'tabler-icons-react';
|
import { AlertCircle, Cross, X } from 'tabler-icons-react';
|
||||||
@@ -20,27 +23,19 @@ import AppShelfMenu from './AppShelfMenu';
|
|||||||
import AddItemShelfItem from './AddAppShelfItem';
|
import AddItemShelfItem from './AddAppShelfItem';
|
||||||
import { useConfig } from '../../tools/state';
|
import { useConfig } from '../../tools/state';
|
||||||
import { pingQbittorrent } from '../../tools/api';
|
import { pingQbittorrent } from '../../tools/api';
|
||||||
import { Config } from '../../tools/types';
|
import { Config, serviceItem } from '../../tools/types';
|
||||||
|
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
||||||
|
|
||||||
const useStyles = createStyles((theme) => ({
|
const useStyles = createStyles((theme) => ({
|
||||||
main: {
|
main: {
|
||||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
|
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[5] : theme.colors.gray[1],
|
||||||
textAlign: 'center',
|
|
||||||
padding: theme.spacing.xl,
|
|
||||||
borderRadius: theme.radius.sm,
|
|
||||||
width: 200,
|
width: 200,
|
||||||
height: 180,
|
height: 180,
|
||||||
|
|
||||||
'&:hover': {
|
|
||||||
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.dark[4] : theme.colors.gray[2],
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const AppShelf = (props: any) => {
|
const AppShelf = (props: any) => {
|
||||||
const { config, addService, removeService, setConfig } = useConfig();
|
const { config, addService, removeService, setConfig } = useConfig();
|
||||||
const { classes } = useStyles();
|
|
||||||
const [hovering, setHovering] = useState('none');
|
|
||||||
|
|
||||||
/* A hook that is used to load the config from local storage. */
|
/* A hook that is used to load the config from local storage. */
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -60,42 +55,73 @@ const AppShelf = (props: any) => {
|
|||||||
return (
|
return (
|
||||||
<SimpleGrid m="xl" cols={4} spacing="xl">
|
<SimpleGrid m="xl" cols={4} spacing="xl">
|
||||||
{config.services.map((service, i) => (
|
{config.services.map((service, i) => (
|
||||||
<motion.div
|
<AppShelfItem service={service} />
|
||||||
onHoverStart={(e) => {
|
|
||||||
setHovering(service.name);
|
|
||||||
}}
|
|
||||||
onHoverEnd={(e) => {
|
|
||||||
setHovering('none');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Box className={classes.main}>
|
|
||||||
<Group position="center">
|
|
||||||
<Space />
|
|
||||||
<Text>{service.name}</Text>
|
|
||||||
<motion.div animate={{ opacity: hovering == service.name ? 1 : 0 }}>
|
|
||||||
<AppShelfMenu removeitem={removeService} name={service.name} />
|
|
||||||
</motion.div>
|
|
||||||
</Group>
|
|
||||||
<Group direction="column" position="center">
|
|
||||||
<Anchor href={service.url} target="_blank">
|
|
||||||
<motion.div whileHover={{ scale: 1.2 }}>
|
|
||||||
<Image
|
|
||||||
style={{
|
|
||||||
maxWidth: 80,
|
|
||||||
}}
|
|
||||||
fit="cover"
|
|
||||||
src={service.icon}
|
|
||||||
alt={service.name}
|
|
||||||
/>
|
|
||||||
</motion.div>
|
|
||||||
</Anchor>
|
|
||||||
</Group>
|
|
||||||
</Box>
|
|
||||||
</motion.div>
|
|
||||||
))}
|
))}
|
||||||
<AddItemShelfItem/>
|
<AddItemShelfItem />
|
||||||
</SimpleGrid>
|
</SimpleGrid>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function AppShelfItem(props: any) {
|
||||||
|
const { service }: { service: serviceItem } = props;
|
||||||
|
const theme = useMantineTheme();
|
||||||
|
const { removeService } = useConfig();
|
||||||
|
const { classes } = useStyles();
|
||||||
|
const [hovering, setHovering] = useState(false);
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
onHoverStart={(e) => {
|
||||||
|
setHovering(true);
|
||||||
|
}}
|
||||||
|
onHoverEnd={(e) => {
|
||||||
|
setHovering(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Card
|
||||||
|
className={classes.main}
|
||||||
|
style={{
|
||||||
|
boxShadow: hovering ? '0px 0px 3px rgba(0, 0, 0, 0.5)' : '0px 0px 1px rgba(0, 0, 0, 0.5)',
|
||||||
|
}}
|
||||||
|
radius={'md'}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
animate={{
|
||||||
|
opacity: hovering ? 1 : 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AppShelfMenu name={service.name} removeitem={removeService} />
|
||||||
|
</motion.div>
|
||||||
|
<Card.Section>
|
||||||
|
<Center>
|
||||||
|
<Text mt={'sm'} weight={500}>
|
||||||
|
{service.name}
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
</Card.Section>
|
||||||
|
<Card.Section>
|
||||||
|
<AspectRatio ratio={5 / 3} m="xl">
|
||||||
|
<motion.i
|
||||||
|
whileHover={{
|
||||||
|
cursor: 'pointer',
|
||||||
|
scale: 1.1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Image
|
||||||
|
onClick={() => {
|
||||||
|
window.open(service.url);
|
||||||
|
}}
|
||||||
|
style={{
|
||||||
|
maxWidth: '50%',
|
||||||
|
marginBottom: 10,
|
||||||
|
}}
|
||||||
|
src={service.icon}
|
||||||
|
/>
|
||||||
|
</motion.i>
|
||||||
|
</AspectRatio>
|
||||||
|
</Card.Section>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export default AppShelf;
|
export default AppShelf;
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ import { Check, Edit, Trash } from 'tabler-icons-react';
|
|||||||
export default function AppShelfMenu(props: any) {
|
export default function AppShelfMenu(props: any) {
|
||||||
const { name, removeitem: removeItem } = props;
|
const { name, removeitem: removeItem } = props;
|
||||||
return (
|
return (
|
||||||
<Menu position='right'>
|
<Menu style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 10,
|
||||||
|
right: 10,
|
||||||
|
}}>
|
||||||
<Menu.Label>Settings</Menu.Label>
|
<Menu.Label>Settings</Menu.Label>
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
color="primary"
|
color="primary"
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ import {
|
|||||||
Burger,
|
Burger,
|
||||||
Paper,
|
Paper,
|
||||||
Transition,
|
Transition,
|
||||||
|
Aside,
|
||||||
|
Drawer,
|
||||||
|
Center,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { useBooleanToggle } from '@mantine/hooks';
|
import { useBooleanToggle } from '@mantine/hooks';
|
||||||
import { NextLink } from '@mantine/next';
|
import { NextLink } from '@mantine/next';
|
||||||
@@ -14,6 +17,7 @@ import { Logo } from './Logo';
|
|||||||
import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle';
|
import { ColorSchemeToggle } from '../ColorSchemeToggle/ColorSchemeToggle';
|
||||||
import SaveConfigComponent from '../Config/SaveConfig';
|
import SaveConfigComponent from '../Config/SaveConfig';
|
||||||
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
import { SettingsMenuButton } from '../Settings/SettingsMenu';
|
||||||
|
import CalendarComponent from '../calendar/CalendarComponent';
|
||||||
|
|
||||||
const HEADER_HEIGHT = 60;
|
const HEADER_HEIGHT = 60;
|
||||||
|
|
||||||
@@ -34,7 +38,7 @@ const useStyles = createStyles((theme) => ({
|
|||||||
borderTopWidth: 0,
|
borderTopWidth: 0,
|
||||||
overflow: 'hidden',
|
overflow: 'hidden',
|
||||||
|
|
||||||
[theme.fn.largerThan('sm')]: {
|
[theme.fn.largerThan('md')]: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -47,13 +51,13 @@ const useStyles = createStyles((theme) => ({
|
|||||||
},
|
},
|
||||||
|
|
||||||
links: {
|
links: {
|
||||||
[theme.fn.smallerThan('sm')]: {
|
[theme.fn.smallerThan('md')]: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
burger: {
|
burger: {
|
||||||
[theme.fn.largerThan('sm')]: {
|
[theme.fn.largerThan('md')]: {
|
||||||
display: 'none',
|
display: 'none',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -127,6 +131,7 @@ export function Header({ links }: HeaderResponsiveProps) {
|
|||||||
<Group spacing={5} className={classes.links}>
|
<Group spacing={5} className={classes.links}>
|
||||||
{items}
|
{items}
|
||||||
</Group>
|
</Group>
|
||||||
|
<Group>
|
||||||
<SettingsMenuButton />
|
<SettingsMenuButton />
|
||||||
|
|
||||||
<Burger
|
<Burger
|
||||||
@@ -135,14 +140,19 @@ export function Header({ links }: HeaderResponsiveProps) {
|
|||||||
className={classes.burger}
|
className={classes.burger}
|
||||||
size="sm"
|
size="sm"
|
||||||
/>
|
/>
|
||||||
|
</Group>
|
||||||
|
|
||||||
<Transition transition="pop-top-right" duration={200} mounted={opened}>
|
<Drawer
|
||||||
{(styles) => (
|
opened={opened}
|
||||||
<Paper className={classes.dropdown} withBorder style={{ zIndex: 99 }}>
|
overlayOpacity={0.55}
|
||||||
{items}
|
overlayBlur={3}
|
||||||
</Paper>
|
onClose={() => toggleOpened()}
|
||||||
)}
|
position="right"
|
||||||
</Transition>
|
>
|
||||||
|
<Center>
|
||||||
|
<CalendarComponent />
|
||||||
|
</Center>
|
||||||
|
</Drawer>
|
||||||
</Container>
|
</Container>
|
||||||
</Head>
|
</Head>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -15,15 +15,13 @@ export default function Layout({ children, style }: any) {
|
|||||||
const { classes, cx } = useStyles();
|
const { classes, cx } = useStyles();
|
||||||
return (
|
return (
|
||||||
<AppShell
|
<AppShell
|
||||||
|
|
||||||
aside={
|
aside={
|
||||||
<Aside
|
<Aside
|
||||||
height={'auto'}
|
height={'auto'}
|
||||||
|
hiddenBreakpoint="md"
|
||||||
|
hidden
|
||||||
width={{
|
width={{
|
||||||
xs: 'auto',
|
base: 'auto',
|
||||||
md: 'auto',
|
|
||||||
lg: 'auto',
|
|
||||||
xl: 'auto',
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CalendarComponent />
|
<CalendarComponent />
|
||||||
|
|||||||
Reference in New Issue
Block a user