mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-10 07:25:48 +01:00
✨App tile UI change (#1231)
* 💄 Rework the App tile UI * 🤡 Forgot one * Make it so the app title gets hidden properly Now if the value is missing it won't by "hover" or "hidden" so it won't hide * Turn the `Tooltip` into `HoverCard` * Make save and cancel button not wrap anymore * 💄 Used InfoCard in options + translations * ♻️ Remove fallback value for label translations --------- Co-authored-by: Thomas Camlong <49837342+ajnart@users.noreply.github.com> Co-authored-by: Meier Lukas <meierschlumpf@gmail.com>
This commit is contained in:
@@ -211,7 +211,7 @@ export const EditAppModal = ({
|
||||
<IntegrationTab form={form} />
|
||||
</Tabs>
|
||||
|
||||
<Group position="right" mt="md">
|
||||
<Group noWrap position="right" mt="md">
|
||||
<Button onClick={closeModal} px={50} variant="light" color="gray">
|
||||
{t('common:cancel')}
|
||||
</Button>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Flex, Tabs } from '@mantine/core';
|
||||
import { Flex, Select, Stack, Switch, Tabs } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { useDebouncedValue } from '@mantine/hooks';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
import { useEffect, useRef } from 'react';
|
||||
|
||||
import { AppType } from '../../../../../../types/app';
|
||||
@@ -19,6 +20,7 @@ export const AppearanceTab = ({
|
||||
}: AppearanceTabProps) => {
|
||||
const iconSelectorRef = useRef();
|
||||
const [debouncedValue] = useDebouncedValue(form.values.name, 500);
|
||||
const { t } = useTranslation('layout/modals/add-app');
|
||||
|
||||
useEffect(() => {
|
||||
if (allowAppNamePropagation !== true) {
|
||||
@@ -38,17 +40,54 @@ export const AppearanceTab = ({
|
||||
|
||||
return (
|
||||
<Tabs.Panel value="appearance" pt="lg">
|
||||
<Flex gap={5}>
|
||||
<IconSelector
|
||||
defaultValue={form.values.appearance.iconUrl}
|
||||
<Stack spacing="xs">
|
||||
<Flex gap={5} mb="xs">
|
||||
<IconSelector
|
||||
defaultValue={form.values.appearance.iconUrl}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('appearance.iconUrl', value);
|
||||
disallowAppNameProgagation();
|
||||
}}
|
||||
value={form.values.appearance.iconUrl}
|
||||
ref={iconSelectorRef}
|
||||
/>
|
||||
</Flex>
|
||||
<Select
|
||||
label={t('appearance.appNameStatus.label')}
|
||||
description={t('appearance.appNameStatus.description')}
|
||||
data={[
|
||||
{ value: 'normal', label: t('appearance.appNameStatus.dropdown.normal') as string },
|
||||
{ value: 'hover', label: t('appearance.appNameStatus.dropdown.hover') as string },
|
||||
{ value: 'hidden', label: t('appearance.appNameStatus.dropdown.hidden') as string },
|
||||
]}
|
||||
{...form.getInputProps('appearance.appNameStatus')}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('appearance.iconUrl', value);
|
||||
disallowAppNameProgagation();
|
||||
form.setFieldValue('appearance.appNameStatus', value);
|
||||
}}
|
||||
value={form.values.appearance.iconUrl}
|
||||
ref={iconSelectorRef}
|
||||
/>
|
||||
</Flex>
|
||||
{form.values.appearance.appNameStatus === 'normal' && (
|
||||
<Select
|
||||
label={t('appearance.positionAppName.label')}
|
||||
description={t('appearance.positionAppName.description')}
|
||||
data={[
|
||||
{ value: 'column', label: t('appearance.positionAppName.dropdown.top') as string },
|
||||
{
|
||||
value: 'row-reverse',
|
||||
label: t('appearance.positionAppName.dropdown.right') as string,
|
||||
},
|
||||
{
|
||||
value: 'column-reverse',
|
||||
label: t('appearance.positionAppName.dropdown.bottom') as string,
|
||||
},
|
||||
{ value: 'row', label: t('appearance.positionAppName.dropdown.left') as string },
|
||||
]}
|
||||
{...form.getInputProps('appearance.positionAppName')}
|
||||
onChange={(value) => {
|
||||
form.setFieldValue('appearance.positionAppName', value);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { Switch, Tabs } from '@mantine/core';
|
||||
import { Text, TextInput, Tooltip, Stack, Switch, Tabs, Group, useMantineTheme, HoverCard } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
import { AppType } from '../../../../../../types/app';
|
||||
import { InfoCard } from '~/components/InfoCard/InfoCard'
|
||||
|
||||
interface BehaviourTabProps {
|
||||
form: UseFormReturnType<AppType, (values: AppType) => AppType>;
|
||||
@@ -10,14 +11,29 @@ interface BehaviourTabProps {
|
||||
|
||||
export const BehaviourTab = ({ form }: BehaviourTabProps) => {
|
||||
const { t } = useTranslation('layout/modals/add-app');
|
||||
const { primaryColor } = useMantineTheme();
|
||||
|
||||
return (
|
||||
<Tabs.Panel value="behaviour" pt="xs">
|
||||
<Switch
|
||||
label={t('behaviour.isOpeningNewTab.label')}
|
||||
description={t('behaviour.isOpeningNewTab.description')}
|
||||
{...form.getInputProps('behaviour.isOpeningNewTab', { type: 'checkbox' })}
|
||||
/>
|
||||
<Stack spacing="xs">
|
||||
<Switch
|
||||
label={t('behaviour.isOpeningNewTab.label')}
|
||||
description={t('behaviour.isOpeningNewTab.description')}
|
||||
styles={{ label: { fontWeight: 500, }, description: { marginTop: 0, }, }}
|
||||
{...form.getInputProps('behaviour.isOpeningNewTab', { type: 'checkbox' })}
|
||||
/>
|
||||
<Stack spacing="0.25rem">
|
||||
<Group>
|
||||
<Text size="0.875rem" weight={500}>
|
||||
{t('behaviour.tooltipDescription.label')}
|
||||
</Text>
|
||||
<InfoCard message={t('behaviour.tooltipDescription.description')}/>
|
||||
</Group>
|
||||
<TextInput
|
||||
{...form.getInputProps('behaviour.tooltipDescription')}
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Tabs, Text, TextInput } from '@mantine/core';
|
||||
import { Stack, Tabs, Text, TextInput } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { IconClick, IconCursorText, IconLink } from '@tabler/icons-react';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
@@ -15,41 +15,44 @@ export const GeneralTab = ({ form, openTab }: GeneralTabProps) => {
|
||||
const { t } = useTranslation('layout/modals/add-app');
|
||||
return (
|
||||
<Tabs.Panel value="general" pt="sm">
|
||||
<TextInput
|
||||
icon={<IconCursorText size={16} />}
|
||||
label={t('general.appname.label')}
|
||||
description={t('general.appname.description')}
|
||||
placeholder="My example app"
|
||||
variant="default"
|
||||
withAsterisk
|
||||
mb="md"
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<TextInput
|
||||
icon={<IconLink size={16} />}
|
||||
label={t('general.internalAddress.label')}
|
||||
description={t('general.internalAddress.description')}
|
||||
placeholder="https://google.com"
|
||||
variant="default"
|
||||
withAsterisk
|
||||
mb="md"
|
||||
{...form.getInputProps('url')}
|
||||
/>
|
||||
<TextInput
|
||||
icon={<IconClick size={16} />}
|
||||
label={t('general.externalAddress.label')}
|
||||
description={t('general.externalAddress.description')}
|
||||
placeholder="https://homarr.mywebsite.com/"
|
||||
variant="default"
|
||||
{...form.getInputProps('behaviour.externalUrl')}
|
||||
/>
|
||||
<Stack spacing="xs">
|
||||
<TextInput
|
||||
icon={<IconCursorText size={16} />}
|
||||
label={t('general.appname.label')}
|
||||
description={t('general.appname.description')}
|
||||
placeholder="My example app"
|
||||
variant="default"
|
||||
withAsterisk
|
||||
{...form.getInputProps('name')}
|
||||
/>
|
||||
<TextInput
|
||||
icon={<IconLink size={16} />}
|
||||
label={t('general.internalAddress.label')}
|
||||
description={t('general.internalAddress.description')}
|
||||
placeholder="https://google.com"
|
||||
variant="default"
|
||||
withAsterisk
|
||||
{...form.getInputProps('url')}
|
||||
onChange={(e) => {
|
||||
form.setFieldValue('url', e.target.value);
|
||||
}}
|
||||
/>
|
||||
<TextInput
|
||||
icon={<IconClick size={16} />}
|
||||
label={t('general.externalAddress.label')}
|
||||
description={t('general.externalAddress.description')}
|
||||
placeholder="https://homarr.mywebsite.com/"
|
||||
variant="default"
|
||||
{...form.getInputProps('behaviour.externalUrl')}
|
||||
/>
|
||||
|
||||
{!form.values.behaviour.externalUrl.startsWith('https://') &&
|
||||
!form.values.behaviour.externalUrl.startsWith('http://') && (
|
||||
<Text color="red" mt="sm" size="sm">
|
||||
{t('behaviour.customProtocolWarning')}
|
||||
</Text>
|
||||
{!form.values.behaviour.externalUrl.startsWith('https://') &&
|
||||
!form.values.behaviour.externalUrl.startsWith('http://') && (
|
||||
<Text color="red" mt="sm" size="sm">
|
||||
{t('behaviour.customProtocolWarning')}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MultiSelect, Switch, Tabs } from '@mantine/core';
|
||||
import { MultiSelect, Stack, Switch, Tabs } from '@mantine/core';
|
||||
import { UseFormReturnType } from '@mantine/form';
|
||||
import { useTranslation } from 'next-i18next';
|
||||
|
||||
@@ -16,26 +16,28 @@ export const NetworkTab = ({ form }: NetworkTabProps) => {
|
||||
);
|
||||
return (
|
||||
<Tabs.Panel value="network" pt="lg">
|
||||
<Switch
|
||||
label={t('network.statusChecker.label')}
|
||||
description={t('network.statusChecker.description')}
|
||||
mb="md"
|
||||
defaultChecked={form.values.network.enabledStatusChecker}
|
||||
{...form.getInputProps('network.enabledStatusChecker')}
|
||||
/>
|
||||
{form.values.network.enabledStatusChecker && (
|
||||
<MultiSelect
|
||||
required
|
||||
label={t('network.statusCodes.label')}
|
||||
description={t('network.statusCodes.description')}
|
||||
data={StatusCodes}
|
||||
clearable
|
||||
searchable
|
||||
defaultValue={acceptableStatusCodes}
|
||||
variant="default"
|
||||
{...form.getInputProps('network.statusCodes')}
|
||||
<Stack spacing="xs">
|
||||
<Switch
|
||||
label={t('network.statusChecker.label')}
|
||||
description={t('network.statusChecker.description')}
|
||||
styles={{ label: { fontWeight: 500, }, description: { marginTop: 0, }, }}
|
||||
defaultChecked={form.values.network.enabledStatusChecker}
|
||||
{...form.getInputProps('network.enabledStatusChecker')}
|
||||
/>
|
||||
)}
|
||||
{form.values.network.enabledStatusChecker && (
|
||||
<MultiSelect
|
||||
required
|
||||
label={t('network.statusCodes.label')}
|
||||
description={t('network.statusCodes.description')}
|
||||
data={StatusCodes}
|
||||
clearable
|
||||
searchable
|
||||
defaultValue={acceptableStatusCodes}
|
||||
variant="default"
|
||||
{...form.getInputProps('network.statusCodes')}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</Tabs.Panel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -93,6 +93,8 @@ export const AvailableElementTypes = ({
|
||||
url: 'https://homarr.dev',
|
||||
appearance: {
|
||||
iconUrl: '/imgs/logo/logo.png',
|
||||
appNameStatus: 'normal',
|
||||
positionAppName: 'column',
|
||||
},
|
||||
network: {
|
||||
enabledStatusChecker: true,
|
||||
@@ -164,4 +166,4 @@ const ElementItem = ({ name, icon, onClick }: ElementItemProps) => {
|
||||
</Stack>
|
||||
</UnstyledButton>
|
||||
);
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user