mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-12 16:35:49 +01:00
✨ Tooltip for widget options
This commit is contained in:
@@ -13,16 +13,17 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
TextInput,
|
TextInput,
|
||||||
Title,
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
useMantineTheme,
|
||||||
} from '@mantine/core';
|
} from '@mantine/core';
|
||||||
import { ContextModalProps } from '@mantine/modals';
|
import { ContextModalProps } from '@mantine/modals';
|
||||||
import { IconAlertTriangle, IconPlaylistX, IconPlus } from '@tabler/icons-react';
|
import { IconAlertTriangle, IconPlaylistX, IconPlus, IconAlertCircle } from '@tabler/icons-react';
|
||||||
import { Trans, useTranslation } from 'next-i18next';
|
import { Trans, useTranslation } from 'next-i18next';
|
||||||
import { FC, useState } from 'react';
|
import { FC, useState } from 'react';
|
||||||
|
|
||||||
import { useConfigContext } from '../../../../config/provider';
|
import { useConfigContext } from '../../../../config/provider';
|
||||||
import { useConfigStore } from '../../../../config/store';
|
import { useConfigStore } from '../../../../config/store';
|
||||||
import { mapObject } from '../../../../tools/client/objects';
|
import { mapObject } from '../../../../tools/client/objects';
|
||||||
import { useColorTheme } from '../../../../tools/color';
|
|
||||||
import Widgets from '../../../../widgets';
|
import Widgets from '../../../../widgets';
|
||||||
import type { IDraggableListInputValue, IWidgetOptionValue } from '../../../../widgets/widgets';
|
import type { IDraggableListInputValue, IWidgetOptionValue } from '../../../../widgets/widgets';
|
||||||
import { IWidget } from '../../../../widgets/widgets';
|
import { IWidget } from '../../../../widgets/widgets';
|
||||||
@@ -135,70 +136,120 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
handleChange: (key: string, value: IntegrationOptionsValueType) => void;
|
handleChange: (key: string, value: IntegrationOptionsValueType) => void;
|
||||||
}> = ({ option, widgetId, propName: key, value, handleChange }) => {
|
}> = ({ option, widgetId, propName: key, value, handleChange }) => {
|
||||||
const { t } = useTranslation([`modules/${widgetId}`, 'common']);
|
const { t } = useTranslation([`modules/${widgetId}`, 'common']);
|
||||||
const { primaryColor } = useColorTheme();
|
const { fn } = useMantineTheme();
|
||||||
|
|
||||||
switch (option.type) {
|
switch (option.type) {
|
||||||
case 'switch':
|
case 'switch':
|
||||||
return (
|
return (
|
||||||
<Switch
|
<Group align="center" spacing="sm">
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
<Switch
|
||||||
checked={value as boolean}
|
label={t(`descriptor.settings.${key}.label`)}
|
||||||
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
checked={value as boolean}
|
||||||
/>
|
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
||||||
|
{...option.inputProps}
|
||||||
|
/>
|
||||||
|
{option.info?
|
||||||
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
|
</Tooltip.Floating> : undefined
|
||||||
|
}
|
||||||
|
</Group>
|
||||||
);
|
);
|
||||||
case 'text':
|
case 'text':
|
||||||
return (
|
return (
|
||||||
<TextInput
|
<Stack spacing={0}>
|
||||||
color={primaryColor}
|
<Group align="center" spacing="sm">
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
value={value as string}
|
{option.info?
|
||||||
onChange={(ev) => handleChange(key, ev.currentTarget.value)}
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
/>
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
|
</Tooltip.Floating> : undefined
|
||||||
|
}
|
||||||
|
</Group>
|
||||||
|
<TextInput
|
||||||
|
value={value as string}
|
||||||
|
onChange={(ev) => handleChange(key, ev.currentTarget.value)}
|
||||||
|
{...option.inputProps}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
case 'multi-select':
|
case 'multi-select':
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<Stack spacing={0}>
|
||||||
color={primaryColor}
|
<Group align="center" spacing="sm">
|
||||||
data={option.data}
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
{option.info?
|
||||||
value={value as string[]}
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
defaultValue={option.defaultValue}
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
onChange={(v) => handleChange(key, v)}
|
</Tooltip.Floating> : undefined
|
||||||
/>
|
}
|
||||||
|
</Group>
|
||||||
|
<MultiSelect
|
||||||
|
data={option.data}
|
||||||
|
value={value as string[]}
|
||||||
|
defaultValue={option.defaultValue}
|
||||||
|
onChange={(v) => handleChange(key, v)}
|
||||||
|
{...option.inputProps}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
case 'select':
|
case 'select':
|
||||||
return (
|
return (
|
||||||
<Select
|
<Stack spacing={0}>
|
||||||
color={primaryColor}
|
<Group align="center" spacing="sm">
|
||||||
defaultValue={option.defaultValue}
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
data={option.data}
|
{option.info?
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
value={value as string}
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
onChange={(v) => handleChange(key, v ?? option.defaultValue)}
|
</Tooltip.Floating> : undefined
|
||||||
/>
|
}
|
||||||
|
</Group>
|
||||||
|
<Select
|
||||||
|
defaultValue={option.defaultValue}
|
||||||
|
data={option.data}
|
||||||
|
value={value as string}
|
||||||
|
onChange={(v) => handleChange(key, v ?? option.defaultValue)}
|
||||||
|
{...option.inputProps}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
case 'number':
|
case 'number':
|
||||||
return (
|
return (
|
||||||
<NumberInput
|
<Stack spacing={0}>
|
||||||
color={primaryColor}
|
<Group align="center" spacing="sm">
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
value={value as number}
|
{option.info?
|
||||||
onChange={(v) => handleChange(key, v!)}
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
{...option.inputProps}
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
/>
|
</Tooltip.Floating> : undefined
|
||||||
|
}
|
||||||
|
</Group>
|
||||||
|
<NumberInput
|
||||||
|
value={value as number}
|
||||||
|
onChange={(v) => handleChange(key, v!)}
|
||||||
|
{...option.inputProps}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
case 'slider':
|
case 'slider':
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack spacing={0}>
|
||||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
<Group align="center" spacing="sm">
|
||||||
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
|
{option.info?
|
||||||
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
|
</Tooltip.Floating> : undefined
|
||||||
|
}
|
||||||
|
</Group>
|
||||||
<Slider
|
<Slider
|
||||||
color={primaryColor}
|
|
||||||
label={value}
|
label={value}
|
||||||
value={value as number}
|
value={value as number}
|
||||||
min={option.min}
|
min={option.min}
|
||||||
max={option.max}
|
max={option.max}
|
||||||
step={option.step}
|
step={option.step}
|
||||||
onChange={(v) => handleChange(key, v)}
|
onChange={(v) => handleChange(key, v)}
|
||||||
|
{...option.inputProps}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
@@ -237,7 +288,14 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
<Group align="center" spacing="sm">
|
||||||
|
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
|
{option.info?
|
||||||
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
|
</Tooltip.Floating> : undefined
|
||||||
|
}
|
||||||
|
</Group>
|
||||||
<StaticDraggableList
|
<StaticDraggableList
|
||||||
value={typedVal}
|
value={typedVal}
|
||||||
onChange={(v) => handleChange(key, v)}
|
onChange={(v) => handleChange(key, v)}
|
||||||
@@ -262,28 +320,44 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
);
|
);
|
||||||
case 'multiple-text':
|
case 'multiple-text':
|
||||||
return (
|
return (
|
||||||
<MultiSelect
|
<Stack spacing={0}>
|
||||||
data={value.map((name: any) => ({ value: name, label: name }))}
|
<Group align="center" spacing="sm">
|
||||||
label={t(`descriptor.settings.${key}.label`)}
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
description={t(`descriptor.settings.${key}.description`)}
|
{option.info?
|
||||||
defaultValue={value as string[]}
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
withinPortal
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
searchable
|
</Tooltip.Floating> : undefined
|
||||||
creatable
|
}
|
||||||
getCreateLabel={(query) => t('common:createItem', { item: query })}
|
</Group>
|
||||||
onChange={(values) =>
|
<MultiSelect
|
||||||
handleChange(
|
data={value.map((name: any) => ({ value: name, label: name }))}
|
||||||
key,
|
description={t(`descriptor.settings.${key}.description`)}
|
||||||
values.map((item: string) => item)
|
defaultValue={value as string[]}
|
||||||
)
|
withinPortal
|
||||||
}
|
searchable
|
||||||
/>
|
creatable
|
||||||
|
getCreateLabel={(query) => t('common:createItem', { item: query })}
|
||||||
|
onChange={(values) =>
|
||||||
|
handleChange(
|
||||||
|
key,
|
||||||
|
values.map((item: string) => item)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
);
|
);
|
||||||
case 'draggable-editable-list':
|
case 'draggable-editable-list':
|
||||||
const { t: translateDraggableList } = useTranslation('widgets/draggable-list');
|
const { t: translateDraggableList } = useTranslation('widgets/draggable-list');
|
||||||
return (
|
return (
|
||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
<Group align="center" spacing="sm">
|
||||||
|
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
|
{option.info?
|
||||||
|
<Tooltip.Floating label={t(`descriptor.settings.${key}.info`)} position="right-start" color={fn.darken(fn.primaryColor(), 0.5)}>
|
||||||
|
<IconAlertCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
|
</Tooltip.Floating> : undefined
|
||||||
|
}
|
||||||
|
</Group>
|
||||||
<DraggableList
|
<DraggableList
|
||||||
items={Array.from(value).map((v: any) => ({
|
items={Array.from(value).map((v: any) => ({
|
||||||
data: v,
|
data: v,
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ interface DataType {
|
|||||||
export type IMultiSelectOptionValue = {
|
export type IMultiSelectOptionValue = {
|
||||||
type: 'multi-select';
|
type: 'multi-select';
|
||||||
defaultValue: string[];
|
defaultValue: string[];
|
||||||
|
info?: boolean;
|
||||||
data: DataType[];
|
data: DataType[];
|
||||||
inputProps?: Partial<MultiSelectProps>;
|
inputProps?: Partial<MultiSelectProps>;
|
||||||
};
|
};
|
||||||
@@ -61,6 +62,7 @@ export type IMultiSelectOptionValue = {
|
|||||||
export type ISelectOptionValue = {
|
export type ISelectOptionValue = {
|
||||||
type: 'select';
|
type: 'select';
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
|
info?: boolean;
|
||||||
data: DataType[];
|
data: DataType[];
|
||||||
inputProps?: Partial<SelectProps>;
|
inputProps?: Partial<SelectProps>;
|
||||||
};
|
};
|
||||||
@@ -69,6 +71,7 @@ export type ISelectOptionValue = {
|
|||||||
export type ISwitchOptionValue = {
|
export type ISwitchOptionValue = {
|
||||||
type: 'switch';
|
type: 'switch';
|
||||||
defaultValue: boolean;
|
defaultValue: boolean;
|
||||||
|
info?: boolean;
|
||||||
inputProps?: Partial<SwitchProps>;
|
inputProps?: Partial<SwitchProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -76,6 +79,7 @@ export type ISwitchOptionValue = {
|
|||||||
export type ITextInputOptionValue = {
|
export type ITextInputOptionValue = {
|
||||||
type: 'text';
|
type: 'text';
|
||||||
defaultValue: string;
|
defaultValue: string;
|
||||||
|
info?: boolean;
|
||||||
inputProps?: Partial<TextInputProps>;
|
inputProps?: Partial<TextInputProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,6 +87,7 @@ export type ITextInputOptionValue = {
|
|||||||
export type INumberInputOptionValue = {
|
export type INumberInputOptionValue = {
|
||||||
type: 'number';
|
type: 'number';
|
||||||
defaultValue: number;
|
defaultValue: number;
|
||||||
|
info?: boolean;
|
||||||
inputProps?: Partial<NumberInputProps>;
|
inputProps?: Partial<NumberInputProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -90,6 +95,7 @@ export type INumberInputOptionValue = {
|
|||||||
export type ISliderInputOptionValue = {
|
export type ISliderInputOptionValue = {
|
||||||
type: 'slider';
|
type: 'slider';
|
||||||
defaultValue: number;
|
defaultValue: number;
|
||||||
|
info?: boolean;
|
||||||
min: number;
|
min: number;
|
||||||
max: number;
|
max: number;
|
||||||
step: number;
|
step: number;
|
||||||
@@ -108,6 +114,7 @@ export type IDraggableListInputValue = {
|
|||||||
key: string;
|
key: string;
|
||||||
subValues?: Record<string, any>;
|
subValues?: Record<string, any>;
|
||||||
}[];
|
}[];
|
||||||
|
info?: boolean;
|
||||||
items: Record<
|
items: Record<
|
||||||
string,
|
string,
|
||||||
Record<string, Omit<Exclude<IWidgetOptionValue, IDraggableListInputValue>, 'defaultValue'>>
|
Record<string, Omit<Exclude<IWidgetOptionValue, IDraggableListInputValue>, 'defaultValue'>>
|
||||||
@@ -117,6 +124,7 @@ export type IDraggableListInputValue = {
|
|||||||
export type IDraggableEditableListInputValue<TData extends { id: string }> = {
|
export type IDraggableEditableListInputValue<TData extends { id: string }> = {
|
||||||
type: 'draggable-editable-list';
|
type: 'draggable-editable-list';
|
||||||
defaultValue: TData[];
|
defaultValue: TData[];
|
||||||
|
info?: boolean;
|
||||||
create: () => TData;
|
create: () => TData;
|
||||||
getLabel: (data: TData) => string | JSX.Element;
|
getLabel: (data: TData) => string | JSX.Element;
|
||||||
itemComponent: (props: {
|
itemComponent: (props: {
|
||||||
@@ -130,6 +138,7 @@ export type IDraggableEditableListInputValue<TData extends { id: string }> = {
|
|||||||
export type IMultipleTextInputOptionValue = {
|
export type IMultipleTextInputOptionValue = {
|
||||||
type: 'multiple-text';
|
type: 'multiple-text';
|
||||||
defaultValue: string[];
|
defaultValue: string[];
|
||||||
|
info?: boolean;
|
||||||
inputProps?: Partial<TextInputProps>;
|
inputProps?: Partial<TextInputProps>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user