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