Add mantine context modals

This commit is contained in:
Manuel Ruwe
2022-12-04 21:19:40 +01:00
parent 99a3a4936e
commit 57d76d223f
24 changed files with 1023 additions and 1 deletions

View File

@@ -0,0 +1,69 @@
import { Stack, Text, Title } from '@mantine/core';
import {
IconArrowsUpDown,
IconCalendarTime,
IconClock,
IconCloudRain,
IconFileDownload,
} from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { GenericAvailableElementType } from '../Shared/GenericElementType';
import { SelectorBackArrow } from '../Shared/SelectorBackArrow';
interface AvailableIntegrationElementsProps {
onClickBack: () => void;
}
export const AvailableIntegrationElements = ({
onClickBack,
}: AvailableIntegrationElementsProps) => {
const { t } = useTranslation('layout/element-selector/selector');
return (
<>
<SelectorBackArrow onClickBack={onClickBack} />
<Text mb="md" color="dimmed">
Integrations interact with your services, to provide you with more control over your
applications. They usually require a few configurations before use.
</Text>
<Stack spacing="xs">
<GenericAvailableElementType
name="Usenet downloads"
description="Display and manage your Usenet downloads directly from Homarr"
image={<IconFileDownload />}
/>
<GenericAvailableElementType
name="BitTorrent downloads"
description="Display and manage your Torrent downloads directly from Homarr"
image={<IconFileDownload />}
/>
<GenericAvailableElementType
name="Calendar"
description="Integrate your Sonarr, Radarr, Lidarr and Readarr releases in a calendar"
image={<IconCalendarTime />}
/>
<GenericAvailableElementType
name="Date & Time"
description="Display the current date & time"
image={<IconClock />}
/>
<GenericAvailableElementType
name="Weather"
description="Display the current weather of a specified location"
image={<IconCloudRain />}
/>
<GenericAvailableElementType
name="Dash."
description="Display hardware data in realtime on your dashboard"
image="https://cdn.jsdelivr.net/gh/walkxcode/dashboard-icons/png/dashdot.png"
/>
<GenericAvailableElementType
name="Torrrent network traffic"
description="Display the current download & upload of your torrent clients"
image={<IconArrowsUpDown />}
/>
</Stack>
</>
);
};

View File

@@ -0,0 +1,91 @@
import { Group, Space, Stack, Text, UnstyledButton } from '@mantine/core';
import { IconBox, IconPlug, IconTextResize } from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { ReactNode } from 'react';
import { v4 as uuidv4 } from 'uuid';
import { openContextModalGeneric } from '../../../../../../tools/mantineModalManagerExtensions';
import { ServiceType } from '../../../../../../types/service';
import { useStyles } from '../Shared/styles';
interface AvailableElementTypesProps {
onOpenIntegrations: () => void;
onOpenStaticElements: () => void;
}
export const AvailableElementTypes = ({
onOpenIntegrations,
onOpenStaticElements,
}: AvailableElementTypesProps) => {
const { t } = useTranslation('layout/element-selector/selector');
return (
<>
<Text color="dimmed">{t('modal.text')}</Text>
<Space h="lg" />
<Group spacing="md" grow>
<ElementItem
name="Service"
icon={<IconBox size={40} strokeWidth={1.3} />}
onClick={() => {
openContextModalGeneric<{ service: ServiceType }>({
modal: 'editService',
innerProps: {
service: {
id: uuidv4(),
name: 'Your service',
url: 'https://homarr.dev',
appearance: {
iconUrl: '/imgs/logo/logo.png',
},
network: {
enabledStatusChecker: false,
okStatus: [],
},
integration: {
type: 'deluge',
properties: {},
},
},
},
size: 'xl',
});
}}
/>
<ElementItem
name="Integration"
icon={<IconPlug size={40} strokeWidth={1.3} />}
onClick={onOpenIntegrations}
/>
<ElementItem
name="Static Element"
icon={<IconTextResize size={40} strokeWidth={1.3} />}
onClick={onOpenStaticElements}
/>
</Group>
</>
);
};
interface ElementItemProps {
icon: ReactNode;
name: string;
onClick: () => void;
}
const ElementItem = ({ name, icon, onClick }: ElementItemProps) => {
const { classes, cx } = useStyles();
return (
<UnstyledButton
className={cx(classes.elementButton, classes.styledButton)}
onClick={onClick}
py="md"
>
<Stack className={classes.elementStack} align="center" spacing={5}>
{icon}
<Text className={classes.elementName} weight={500} size="sm">
{name}
</Text>
</Stack>
</UnstyledButton>
);
};

View File

@@ -0,0 +1,61 @@
import { Button, Stack, Text } from '@mantine/core';
import { IconChevronRight } from '@tabler/icons';
import Image from 'next/image';
import React, { ReactNode } from 'react';
import { useStyles } from './styles';
interface GenericAvailableElementTypeProps {
name: string;
description?: string;
image: string | ReactNode;
disabled?: boolean;
}
export const GenericAvailableElementType = ({
name,
description,
image,
disabled,
}: GenericAvailableElementTypeProps) => {
const { classes } = useStyles();
const Icon = () => {
if (React.isValidElement(image)) {
return <>{image}</>;
}
return <Image src={image as string} width={24} height={24} />;
};
return (
<Button
className={classes.styledButton}
leftIcon={<Icon />}
rightIcon={<IconChevronRight opacity={0.5} />}
styles={{
root: {
height: 'auto',
padding: '8px 12px',
},
inner: {
justifyContent: 'left',
},
label: {
justifyContent: 'space-between',
width: '100%',
},
}}
disabled={disabled}
fullWidth
>
<Stack spacing={0}>
<Text className={classes.elementText}>{name}</Text>
{description && (
<Text className={classes.elementText} size="xs" color="dimmed">
{description}
</Text>
)}
</Stack>
</Button>
);
};

View File

@@ -0,0 +1,21 @@
import { ActionIcon, Button, Group, Text } from '@mantine/core';
import { IconArrowNarrowLeft } from '@tabler/icons';
interface SelectorBackArrowProps {
onClickBack: () => void;
}
export const SelectorBackArrow = ({ title, onClickBack }: SelectorBackArrowProps) => {
return (
<Button
leftIcon={<IconArrowNarrowLeft />}
onClick={onClickBack}
styles={{ inner: { width: 'fit-content' } }}
fullWidth
variant='default'
mb="md"
>
<Text>See all available elements</Text>
</Button>
);
};

View File

@@ -0,0 +1,28 @@
import { createStyles } from '@mantine/core';
export const useStyles = createStyles((theme) => ({
styledButton: {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.gray[9] : theme.colors.gray[2],
color: theme.colorScheme === 'dark' ? theme.colors.gray[0] : theme.colors.dark[9],
'&:hover': {
backgroundColor: theme.colorScheme === 'dark' ? theme.colors.gray[8] : theme.colors.gray[3],
},
},
elementButton: {
width: '100%',
height: '100%',
borderRadius: theme.radius.sm
},
elementStack: {
width: '100%',
},
elementName: {
whiteSpace: 'normal',
textAlign: 'center',
lineHeight: 1.2,
},
elementText: {
lineHeight: 1.2,
whiteSpace: 'normal',
},
}));

View File

@@ -0,0 +1,31 @@
import { Stack, Text, Title } from '@mantine/core';
import { IconCursorText } from '@tabler/icons';
import { useTranslation } from 'next-i18next';
import { GenericAvailableElementType } from '../Shared/GenericElementType';
import { SelectorBackArrow } from '../Shared/SelectorBackArrow';
interface AvailableStaticTypesProps {
onClickBack: () => void;
}
export const AvailableStaticTypes = ({ onClickBack }: AvailableStaticTypesProps) => {
const { t } = useTranslation('layout/element-selector/selector');
return (
<>
<SelectorBackArrow onClickBack={onClickBack} />
<Text mb="md" color="dimmed">
Static elements provide you additional control over your dashboard. They are static, because
they don&apos;t integrate with any services and their content never changes.
</Text>
<Stack>
<GenericAvailableElementType
name="Static Text"
description="Display a fixed string on your dashboard"
image={<IconCursorText />}
/>
</Stack>
</>
);
};

View File

@@ -0,0 +1,27 @@
import { ContextModalProps } from '@mantine/modals';
import { useState } from 'react';
import { AvailableIntegrationElements } from './Components/IntegrationsTab/AvailableIntegrationsTab';
import { AvailableElementTypes } from './Components/Overview/AvailableElementsOverview';
import { AvailableStaticTypes } from './Components/StaticElementsTab/AvailableStaticElementsTab';
export const SelectElementModal = ({ context, id }: ContextModalProps) => {
const [activeTab, setActiveTab] = useState<undefined | 'integrations' | 'static_elements'>();
switch (activeTab) {
case undefined:
return (
<AvailableElementTypes
onOpenIntegrations={() => setActiveTab('integrations')}
onOpenStaticElements={() => setActiveTab('static_elements')}
/>
);
case 'integrations':
return <AvailableIntegrationElements onClickBack={() => setActiveTab(undefined)} />;
case 'static_elements':
return <AvailableStaticTypes onClickBack={() => setActiveTab(undefined)} />;
default:
/* default to the main selection tab */
setActiveTab(undefined);
return <></>;
}
};