import { Alert, Box, Button, Card, Flex, Group, Image, ScrollArea, Stack, Switch, Text, TextInput, Title, createStyles, useMantineTheme, } from '@mantine/core'; import { useForm } from '@mantine/form'; import { IconAlertTriangle, IconBookmark, IconLink, IconPlaylistX, IconTrash, IconTypography, } from '@tabler/icons-react'; import { useTranslation } from 'next-i18next'; import { useEffect } from 'react'; import { v4 } from 'uuid'; import { z } from 'zod'; import { useEditModeStore } from '../../components/Dashboard/Views/useEditModeStore'; import { IconSelector } from '../../components/IconSelector/IconSelector'; import { defineWidget } from '../helper'; import { IDraggableEditableListInputValue, IWidget } from '../widgets'; interface BookmarkItem { id: string; name: string; href: string; iconUrl: string; openNewTab: boolean; hideLink: boolean; } const definition = defineWidget({ id: 'bookmark', icon: IconBookmark, options: { items: { type: 'draggable-editable-list', defaultValue: [], getLabel(data) { return data.name; }, create() { return { id: v4(), name: 'Homarr Documentation', href: 'https://homarr.dev', iconUrl: '/imgs/logo/logo.png', openNewTab: false, hideLink: false, }; }, itemComponent({ data, onChange, delete: deleteData }) { const form = useForm({ initialValues: data, validate: { name: (value) => { const validation = z.string().min(1).max(100).safeParse(value); if (validation.success) { return undefined; } return 'Length must be between 1 and 100'; }, href: (value) => { if (!z.string().min(1).max(200).safeParse(value).success) { return 'Length must be between 1 and 200'; } if (!z.string().url().safeParse(value).success) { return 'Not a valid link'; } return undefined; }, iconUrl: (value) => { if (z.string().min(1).max(400).safeParse(value).success) { return undefined; } return 'Length must be between 1 and 100'; }, }, validateInputOnChange: true, validateInputOnBlur: true, }); useEffect(() => { if (!form.isValid()) { return; } onChange({ ...form.values, openNewTab: form.values.openNewTab }); }, [form.values]); return (
} {...form.getInputProps('name')} label="Name" withAsterisk /> } {...form.getInputProps('href')} label="URL" withAsterisk /> { form.setFieldValue('iconUrl', value ?? ''); }} /> {!form.isValid() && ( }> Did not save, because there were validation errors. Please adust your inputs )}
); }, } satisfies IDraggableEditableListInputValue, layout: { type: 'select', data: [ { label: 'Auto Grid', value: 'autoGrid', }, { label: 'Horizontal', value: 'horizontal', }, { label: 'Vertical', value: 'vertical', }, ], defaultValue: 'autoGrid', }, }, gridstack: { minWidth: 1, minHeight: 1, maxWidth: 24, maxHeight: 24, }, component: BookmarkWidgetTile, }); export type IBookmarkWidget = IWidget<(typeof definition)['id'], typeof definition>; interface BookmarkWidgetTileProps { widget: IBookmarkWidget; } function BookmarkWidgetTile({ widget }: BookmarkWidgetTileProps) { const { t } = useTranslation('modules/bookmark'); const { classes } = useStyles(); const { enabled: isEditModeEnabled } = useEditModeStore(); const { fn, colors, colorScheme } = useMantineTheme(); if (widget.properties.items.length === 0) { return ( {t('card.noneFound.title')} {t('card.noneFound.text')} ); } switch (widget.properties.layout) { case 'autoGrid': return ( {widget.properties.items.map((item: BookmarkItem, index) => ( ))} ); case 'horizontal': case 'vertical': return ( {widget.properties.items.map((item: BookmarkItem, index) => ( ))} ); default: return null; } } const BookmarkItemContent = ({ item }: { item: BookmarkItem }) => { const { colorScheme } = useMantineTheme(); return ( {item.name} )}; const useStyles = createStyles(() => ({ grid: { display: 'grid', gap: 10, gridTemplateColumns: 'repeat(auto-fit, minmax(150px, 1fr))', }, autoGridItem: { flex: '1 1 auto', }, })); export default definition;