mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 15:35:55 +01:00
✨ Add mantine context modals
This commit is contained in:
@@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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>
|
||||
);
|
||||
};
|
||||
@@ -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',
|
||||
},
|
||||
}));
|
||||
@@ -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'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>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -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 <></>;
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user