mirror of
https://github.com/ajnart/homarr.git
synced 2025-11-14 17:26:26 +01:00
✨ General Element + Tooltip to HoverCard
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
"@mantine/modals": "^6.0.0",
|
"@mantine/modals": "^6.0.0",
|
||||||
"@mantine/next": "^6.0.0",
|
"@mantine/next": "^6.0.0",
|
||||||
"@mantine/notifications": "^6.0.0",
|
"@mantine/notifications": "^6.0.0",
|
||||||
|
"@mantine/tiptap": "^6.0.17",
|
||||||
"@nivo/core": "^0.83.0",
|
"@nivo/core": "^0.83.0",
|
||||||
"@nivo/line": "^0.83.0",
|
"@nivo/line": "^0.83.0",
|
||||||
"@react-native-async-storage/async-storage": "^1.18.1",
|
"@react-native-async-storage/async-storage": "^1.18.1",
|
||||||
@@ -48,6 +49,10 @@
|
|||||||
"@tanstack/react-query": "^4.2.1",
|
"@tanstack/react-query": "^4.2.1",
|
||||||
"@tanstack/react-query-devtools": "^4.24.4",
|
"@tanstack/react-query-devtools": "^4.24.4",
|
||||||
"@tanstack/react-query-persist-client": "^4.28.0",
|
"@tanstack/react-query-persist-client": "^4.28.0",
|
||||||
|
"@tiptap/extension-link": "^2.0.4",
|
||||||
|
"@tiptap/pm": "^2.0.4",
|
||||||
|
"@tiptap/react": "^2.0.4",
|
||||||
|
"@tiptap/starter-kit": "^2.0.4",
|
||||||
"@trpc/client": "^10.29.1",
|
"@trpc/client": "^10.29.1",
|
||||||
"@trpc/next": "^10.29.1",
|
"@trpc/next": "^10.29.1",
|
||||||
"@trpc/react-query": "^10.29.1",
|
"@trpc/react-query": "^10.29.1",
|
||||||
|
|||||||
@@ -6,9 +6,7 @@
|
|||||||
"title": "Bookmark settings",
|
"title": "Bookmark settings",
|
||||||
"name": {
|
"name": {
|
||||||
"label": "Widget Title",
|
"label": "Widget Title",
|
||||||
"placeholder": {
|
"info": "Leave empty to keep the title hidden. <a href=\"https://homarr.dev/docs/widgets/bookmarks/\" target=\"_blank\">See More...</a>"
|
||||||
"label" : "Leave empty to keep the title hidden"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"items": {
|
"items": {
|
||||||
"label": "Items"
|
"label": "Items"
|
||||||
|
|||||||
@@ -13,14 +13,13 @@ 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, IconInfoCircle } from '@tabler/icons-react';
|
import { IconAlertTriangle, IconPlaylistX, IconPlus, IconInfoCircle } 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 { InfoCard } from '../../../InfoCard/InfoCard';
|
||||||
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';
|
||||||
@@ -148,7 +147,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
onChange={(ev) => handleChange(key, ev.currentTarget.checked)}
|
||||||
{...option.inputProps}
|
{...option.inputProps}
|
||||||
/>
|
/>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
);
|
);
|
||||||
case 'text':
|
case 'text':
|
||||||
@@ -156,7 +155,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<TextInput
|
<TextInput
|
||||||
value={value as string}
|
value={value as string}
|
||||||
@@ -170,7 +169,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
data={option.data}
|
data={option.data}
|
||||||
@@ -187,7 +186,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<Select
|
<Select
|
||||||
defaultValue={option.defaultValue}
|
defaultValue={option.defaultValue}
|
||||||
@@ -204,7 +203,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<NumberInput
|
<NumberInput
|
||||||
value={value as number}
|
value={value as number}
|
||||||
@@ -218,7 +217,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<Slider
|
<Slider
|
||||||
label={value}
|
label={value}
|
||||||
@@ -268,7 +267,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<StaticDraggableList
|
<StaticDraggableList
|
||||||
value={typedVal}
|
value={typedVal}
|
||||||
@@ -297,7 +296,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing={0}>
|
<Stack spacing={0}>
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text size="0.875rem" weight="500">{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
data={value.map((name: any) => ({ value: name, label: name }))}
|
data={value.map((name: any) => ({ value: name, label: name }))}
|
||||||
@@ -322,7 +321,7 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
<Stack spacing="xs">
|
<Stack spacing="xs">
|
||||||
<Group align="center" spacing="sm">
|
<Group align="center" spacing="sm">
|
||||||
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
<Text>{t(`descriptor.settings.${key}.label`)}</Text>
|
||||||
{info && <InfoTooltip label={t(`descriptor.settings.${key}.info`)}/>}
|
{info && <InfoCard content={t(`descriptor.settings.${key}.info`)}/>}
|
||||||
</Group>
|
</Group>
|
||||||
<DraggableList
|
<DraggableList
|
||||||
items={Array.from(value).map((v: any) => ({
|
items={Array.from(value).map((v: any) => ({
|
||||||
@@ -364,23 +363,3 @@ const WidgetOptionTypeSwitch: FC<{
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
interface InfoTooltipProps {
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const InfoTooltip = ({ label }: InfoTooltipProps) => {
|
|
||||||
const { colorScheme } =useMantineTheme();
|
|
||||||
return (
|
|
||||||
<Tooltip.Floating
|
|
||||||
label={label}
|
|
||||||
position="right-start"
|
|
||||||
c={ colorScheme === 'light' ? "black" : "dark.0" }
|
|
||||||
color={ colorScheme === 'light' ? "dark.0" : "dark.6" }
|
|
||||||
styles={{ tooltip: { maxWidth: 300, }, }}
|
|
||||||
multiline
|
|
||||||
>
|
|
||||||
<IconInfoCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
|
||||||
</Tooltip.Floating>
|
|
||||||
)
|
|
||||||
};
|
|
||||||
44
src/components/InfoCard/InfoCard.tsx
Normal file
44
src/components/InfoCard/InfoCard.tsx
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import { HoverCard, useMantineTheme} from '@mantine/core';
|
||||||
|
import { RichTextEditor, Link } from '@mantine/tiptap';
|
||||||
|
import { useEditor } from '@tiptap/react';
|
||||||
|
import StarterKit from '@tiptap/starter-kit';
|
||||||
|
import { IconInfoCircle } from '@tabler/icons-react';
|
||||||
|
|
||||||
|
interface InfoCardProps {
|
||||||
|
content: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InfoCard = ({ content }: InfoCardProps) => {
|
||||||
|
const { colorScheme } = useMantineTheme();
|
||||||
|
const editor = useEditor({
|
||||||
|
content,
|
||||||
|
editable: false,
|
||||||
|
editorProps:{ attributes:{ style: 'padding: 0;' }, },
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Link,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<HoverCard withinPortal position="top">
|
||||||
|
<HoverCard.Target>
|
||||||
|
<IconInfoCircle size="1.25rem" style={{ display: 'block', opacity: 0.5 }} />
|
||||||
|
</HoverCard.Target>
|
||||||
|
<HoverCard.Dropdown
|
||||||
|
c='transparent'
|
||||||
|
maw={400}
|
||||||
|
p={0}
|
||||||
|
style={{ border:"0", }}
|
||||||
|
>
|
||||||
|
<RichTextEditor editor={editor}>
|
||||||
|
<RichTextEditor.Content
|
||||||
|
bg={colorScheme === 'light' ? "gray.2" : "dark.8"}
|
||||||
|
px="10px"
|
||||||
|
py="5px"
|
||||||
|
/>
|
||||||
|
</RichTextEditor>
|
||||||
|
</HoverCard.Dropdown>
|
||||||
|
</HoverCard>
|
||||||
|
)
|
||||||
|
};
|
||||||
@@ -54,6 +54,7 @@ const definition = defineWidget({
|
|||||||
name: {
|
name: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
defaultValue: '',
|
defaultValue: '',
|
||||||
|
info: true,
|
||||||
},
|
},
|
||||||
items: {
|
items: {
|
||||||
type: 'draggable-editable-list',
|
type: 'draggable-editable-list',
|
||||||
|
|||||||
Reference in New Issue
Block a user