diff --git a/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx b/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx index 02ab85c77..5619a8e06 100644 --- a/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx +++ b/src/app/(main)/boards/[boardId]/BoardComponentSelect.tsx @@ -1,5 +1,14 @@ -import { Button, Column, Focusable, ListItem, Row, Select, Text } from '@umami/react-zen'; -import { useState } from 'react'; +import { + Button, + Column, + Focusable, + ListItem, + Row, + Select, + Text, + TextField, +} from '@umami/react-zen'; +import { useEffect, useMemo, useState } from 'react'; import { Panel } from '@/components/common/Panel'; import { useMessages } from '@/components/hooks'; import type { BoardComponentConfig } from '@/lib/types'; @@ -13,29 +22,66 @@ import { BoardComponentRenderer } from './BoardComponentRenderer'; export function BoardComponentSelect({ websiteId, + initialConfig, onSelect, onClose, }: { websiteId: string; + initialConfig?: BoardComponentConfig; onSelect: (config: BoardComponentConfig) => void; onClose: () => void; }) { const { t, labels, messages } = useMessages(); const [selectedDef, setSelectedDef] = useState(null); const [configValues, setConfigValues] = useState>({}); + const [title, setTitle] = useState(''); + const [description, setDescription] = useState(''); - const handleSelectComponent = (def: ComponentDefinition) => { - setSelectedDef(def); + const allDefinitions = useMemo( + () => CATEGORIES.flatMap(category => getComponentsByCategory(category.key)), + [], + ); + + const getDefaultConfigValues = (def: ComponentDefinition, config?: BoardComponentConfig) => { const defaults: Record = {}; - if (def.configFields) { - for (const field of def.configFields) { - defaults[field.name] = field.defaultValue; - } + + for (const field of def.configFields ?? []) { + defaults[field.name] = field.defaultValue; } + if (def.defaultProps) { Object.assign(defaults, def.defaultProps); } - setConfigValues(defaults); + + if (config?.props) { + Object.assign(defaults, config.props); + } + + return defaults; + }; + + useEffect(() => { + if (!initialConfig) { + return; + } + + const definition = allDefinitions.find(def => def.type === initialConfig.type); + + if (!definition) { + return; + } + + setSelectedDef(definition); + setConfigValues(getDefaultConfigValues(definition, initialConfig)); + setTitle(initialConfig.title || definition.name); + setDescription(initialConfig.description || ''); + }, [initialConfig, allDefinitions]); + + const handleSelectComponent = (def: ComponentDefinition) => { + setSelectedDef(def); + setConfigValues(getDefaultConfigValues(def)); + setTitle(def.name); + setDescription(''); }; const handleConfigChange = (name: string, value: any) => { @@ -46,16 +92,25 @@ export function BoardComponentSelect({ if (!selectedDef) return; const props: Record = {}; + if (selectedDef.defaultProps) { Object.assign(props, selectedDef.defaultProps); } + Object.assign(props, configValues); - if (props.limit) { - props.limit = Number(props.limit); + for (const field of selectedDef.configFields ?? []) { + if (field.type === 'number' && props[field.name] != null && props[field.name] !== '') { + props[field.name] = Number(props[field.name]); + } } - const config: BoardComponentConfig = { type: selectedDef.type }; + const config: BoardComponentConfig = { + type: selectedDef.type, + title: title || selectedDef.name, + description, + }; + if (Object.keys(props).length > 0) { config.props = props; } @@ -66,6 +121,8 @@ export function BoardComponentSelect({ const previewConfig: BoardComponentConfig | null = selectedDef ? { type: selectedDef.type, + title, + description, props: { ...selectedDef.defaultProps, ...configValues }, } : null; @@ -73,12 +130,13 @@ export function BoardComponentSelect({ return ( - - {CATEGORIES.map(cat => { - const components = getComponentsByCategory(cat.key); + + {CATEGORIES.map(category => { + const components = getComponentsByCategory(category.key); + return ( - - {cat.name} + + {category.name} {components.map(def => ( + - {selectedDef?.configFields && selectedDef.configFields.length > 0 && ( - - {selectedDef.configFields.map((field: ConfigField) => ( - - - {field.label} - - {field.type === 'select' && ( - - )} - - ))} - - )} {previewConfig && websiteId ? ( @@ -147,7 +183,66 @@ export function BoardComponentSelect({ )} + + + {t(labels.properties)} + + + + {t(labels.title)} + + + + + + + {t(labels.description)} + + + + + {selectedDef?.configFields && selectedDef.configFields.length > 0 && ( + + {selectedDef.configFields.map((field: ConfigField) => ( + + + {field.label} + + + {field.type === 'select' && ( + + )} + + {field.type === 'text' && ( + handleConfigChange(field.name, value)} + /> + )} + + {field.type === 'number' && ( + handleConfigChange(field.name, value)} + /> + )} + + ))} + + )} + + - {hasComponent ? 'Remove component' : 'Remove column'} - - - )} - {renderedComponent ? ( - <> - - {renderedComponent} - - {canEdit && ( - + + {hasComponent && ( - Change component + {t(labels.edit)} - - )} - + )} + + + {t(labels.remove)} + + + + )} + {renderedComponent ? ( + + {renderedComponent} + ) : ( canEdit && ( - + + + ) )} @@ -127,6 +126,7 @@ export function BoardEditColumn({ {() => ( setShowSelect(false)} /> diff --git a/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx b/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx index 399a8f30a..61876498d 100644 --- a/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx +++ b/src/app/(main)/boards/[boardId]/BoardViewColumn.tsx @@ -13,10 +13,11 @@ export function BoardViewColumn({ component }: { component?: BoardComponentConfi return null; } - const title = getComponentDefinition(component.type)?.name; + const title = component.title || getComponentDefinition(component.type)?.name; + const description = component.description; return ( - + diff --git a/src/components/common/Panel.tsx b/src/components/common/Panel.tsx index 0af322bc6..ce2bbba30 100644 --- a/src/components/common/Panel.tsx +++ b/src/components/common/Panel.tsx @@ -5,6 +5,7 @@ import { Heading, Icon, Row, + Text, Tooltip, TooltipTrigger, } from '@umami/react-zen'; @@ -14,6 +15,7 @@ import { Maximize, X } from '@/components/icons'; export interface PanelProps extends ColumnProps { title?: string; + description?: string; allowFullscreen?: boolean; } @@ -29,6 +31,7 @@ const fullscreenStyles = { export function Panel({ title, + description, allowFullscreen, style, children, @@ -56,6 +59,7 @@ export function Panel({ style={{ ...style, ...(isFullscreen ? fullscreenStyles : { height, width }) }} > {title && {title}} + {description && {description}} {allowFullscreen && ( diff --git a/src/lib/types.ts b/src/lib/types.ts index c06fe0e35..e2501742d 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -146,6 +146,8 @@ export interface ApiError extends Error { export interface BoardComponentConfig { type: string; + title?: string; + description?: string; props?: Record; }