import DescriptionOutlinedIcon from '@mui/icons-material/DescriptionOutlined'; import EditIcon from '@mui/icons-material/Edit'; import InfoOutlinedIcon from '@mui/icons-material/InfoOutlined'; import VisibilityIcon from '@mui/icons-material/Visibility'; import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'; import type { Meta, StoryObj } from '@storybook/react'; import { useState } from 'react'; import buildListingEndpoint from '../../api/buildListingEndpoint'; import type { Listing } from '../../api/models'; import type { SelectEntry } from '../../InputField/Select'; import type { GetEndpointParams } from '../../InputField/Select/Autocomplete/Connected'; import SingleConnectedAutocompleteField from '../../InputField/Select/Autocomplete/Connected/Single'; import TextField from '../../InputField/Text'; import type { RenderRowParams } from './models'; import { SortableEntriesList, type Props as SortableEntriesListProps } from './SortableEntriesList'; const meta: Meta = { component: SortableEntriesList }; export default meta; type Story = StoryObj; const ValuesPreview = ({ values }: { values: Array }): JSX.Element => (
    {JSON.stringify(values, null, 2)}
  
); // Templates: one connected autocomplete per row, order matters (inheritance) type TemplateRow = SelectEntry | null; const hostTemplateOptions: Array = [ { id: 1, name: 'generic-active-host' }, { id: 2, name: 'generic-passive-host' }, { id: 3, name: 'generic-host-custom' }, { id: 4, name: 'linux-server-standard' }, { id: 5, name: 'windows-server-standard' } ]; const templatesEndpoint = '/configuration/hosts/templates'; const getTemplatesEndpoint = (parameters: GetEndpointParams): string => buildListingEndpoint({ baseEndpoint: templatesEndpoint, parameters }); const templatesMockData = [ { delay: 300, method: 'GET', response: (): Listing => ({ meta: { limit: 10, page: 1, total: hostTemplateOptions.length }, result: hostTemplateOptions }), status: 200, url: `${templatesEndpoint}?page=` } ]; const createTemplateRow = (): TemplateRow => null; const renderTemplateRow = ({ setValue, value, values }: RenderRowParams): JSX.Element => { // A template already chosen in another row cannot be chosen twice. const selectedElsewhere = values .map((template) => template?.id) .filter((id) => id !== value?.id); return ( selectedElsewhere.includes(option.id) } label="Select a template" onChange={(_event, template): void => setValue(template as TemplateRow)} value={value} /> ); }; type TemplatesListArgs = Partial< Pick< SortableEntriesListProps, 'addLabel' | 'draggable' | 'maxVisibleRows' > >; const TemplatesList = (args: TemplatesListArgs): JSX.Element => { const [templates, setTemplates] = useState>([ hostTemplateOptions[0], hostTemplateOptions[3] ]); return (
actions={({ value }) => [ { icon: , id: 'edit-template', isDisabled: !value, label: 'Edit template', onClick: () => undefined }, { icon: , id: 'template-info', isDisabled: !value, label: 'Template information', onClick: () => undefined } ]} addLabel="Add new entry" createValue={createTemplateRow} label="Templates" onChange={setTemplates} renderRow={renderTemplateRow} values={templates} {...args} />
); }; export const Templates: Story = { args: { draggable: true, maxVisibleRows: 10 }, argTypes: { addLabel: { control: 'text' }, draggable: { control: 'boolean' }, maxVisibleRows: { control: 'number' } }, parameters: { mockData: templatesMockData }, render: (args) => }; // Custom macros: several inputs per row, row styled from its own value interface MacroRow { isPassword: boolean; name: string; origin: 'direct' | 'fromCommand' | 'fromTpl'; value: string; } const createMacroRow = (): MacroRow => ({ isPassword: false, name: '', origin: 'direct', value: '' }); const macroRowClassName: Record = { direct: 'rounded p-1', fromCommand: 'rounded bg-teal-50 p-1 dark:bg-teal-950', fromTpl: 'rounded bg-purple-50 p-1 dark:bg-purple-950' }; const renderMacroRow = ({ setField, value: macro }: RenderRowParams): JSX.Element => (
setField('name', event.target.value)} value={macro.name} /> setField('value', event.target.value)} type={macro.isPassword ? 'password' : 'text'} value={macro.value} />
); const initialMacros: Array = [ { isPassword: false, name: 'HOST_IP', origin: 'direct', value: '10.0.0.1' }, { isPassword: true, name: 'API_TOKEN', origin: 'fromTpl', value: 'secret-token' }, { isPassword: false, name: 'PORT', origin: 'fromCommand', value: '443' } ]; const buildMacros = (count: number): Array => Array.from({ length: count }, (_, index) => ({ isPassword: false, name: `MACRO_${index + 1}`, origin: 'direct', value: `value ${index + 1}` })); const MacrosList = ({ draggable, initialValues = initialMacros, maxVisibleRows }: { draggable: boolean; initialValues?: Array; maxVisibleRows?: number; }): JSX.Element => { const [macros, setMacros] = useState(initialValues); const toggleIsPassword = (index: number): void => setMacros((current) => current.map((macro, macroIndex) => macroIndex === index ? { ...macro, isPassword: !macro.isPassword } : macro ) ); return ( <> actions={({ index, value }) => [ { icon: value.isPassword ? : , id: 'toggle-password', label: value.isPassword ? 'Show value' : 'Hide value', onClick: () => toggleIsPassword(index) }, { icon: , id: 'macro-description', label: 'Description', onClick: () => undefined } ]} createValue={createMacroRow} draggable={draggable} getRowClassName={({ value }) => macroRowClassName[value.origin]} label="Custom macros" maxVisibleRows={maxVisibleRows} onChange={setMacros} renderRow={renderMacroRow} values={macros} /> ); }; export const CustomMacros: Story = { render: () => (
) }; export const NonDraggable: Story = { render: () => (
) }; export const Empty: Story = { render: () => (
) }; // Capped list in a resizable container: drag the bottom-right corner or use the // presets to check the row layout against the list width, not the viewport. const panelWidths = [1400, 900, 720, 560]; const ScrollableMacrosList = (): JSX.Element => { const [width, setWidth] = useState(panelWidths[0]); return (
{panelWidths.map((panelWidth) => ( ))}
); }; export const ScrollableAndResizable: Story = { render: () => };