add modularity to the ListConfigMenu

This commit is contained in:
jeffvli
2025-11-16 14:39:38 -08:00
parent ef1483c99e
commit ad5eb7be4e
3 changed files with 126 additions and 16 deletions
@@ -46,9 +46,20 @@ type GridConfigProps = {
}[]; }[];
gridRowsData: { label: string; value: string }[]; gridRowsData: { label: string; value: string }[];
listKey: ItemListKey; listKey: ItemListKey;
optionsConfig?: {
[key: string]: {
disabled?: boolean;
hidden?: boolean;
};
};
}; };
export const GridConfig = ({ extraOptions, gridRowsData, listKey }: GridConfigProps) => { export const GridConfig = ({
extraOptions,
gridRowsData,
listKey,
optionsConfig,
}: GridConfigProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings; const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
@@ -56,7 +67,7 @@ export const GridConfig = ({ extraOptions, gridRowsData, listKey }: GridConfigPr
const { setList } = useSettingsStoreActions(); const { setList } = useSettingsStoreActions();
const options = useMemo(() => { const options = useMemo(() => {
return [ const allOptions = [
{ {
component: ( component: (
<SegmentedControl <SegmentedControl
@@ -214,7 +225,24 @@ export const GridConfig = ({ extraOptions, gridRowsData, listKey }: GridConfigPr
...(extraOptions || []), ...(extraOptions || []),
]; ];
}, [list, t, grid, extraOptions, setList, listKey]);
// Filter and apply config (hidden/disabled)
return allOptions
.map((option) => {
const config = optionsConfig?.[option.id];
if (config?.hidden) {
return null;
}
return {
...option,
disabled: config?.disabled || false,
};
})
.filter(
(option): option is typeof allOptions[0] & { disabled?: boolean } =>
option !== null,
);
}, [list, t, grid, extraOptions, optionsConfig, setList, listKey]);
return ( return (
<> <>
@@ -1,4 +1,4 @@
import { ReactNode } from 'react'; import { ReactNode, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import i18n from '/@/i18n/i18n'; import i18n from '/@/i18n/i18n';
@@ -77,8 +77,30 @@ export const ListConfigBooleanControl = ({
); );
}; };
export interface ListConfigMenuDisplayTypeConfig {
disabled?: boolean;
hidden?: boolean;
value: ListDisplayType;
}
export interface ListConfigMenuOptionConfig {
disabled?: boolean;
hidden?: boolean;
}
export interface ListConfigMenuOptionsConfig {
grid?: {
[key: string]: ListConfigMenuOptionConfig;
};
table?: {
[key: string]: ListConfigMenuOptionConfig;
};
}
interface ListConfigMenuProps { interface ListConfigMenuProps {
displayTypes?: ListConfigMenuDisplayTypeConfig[];
listKey: ItemListKey; listKey: ItemListKey;
optionsConfig?: ListConfigMenuOptionsConfig;
tableColumnsData: { label: string; value: string }[]; tableColumnsData: { label: string; value: string }[];
} }
@@ -90,6 +112,29 @@ export const ListConfigMenu = (props: ListConfigMenuProps) => {
const { setList } = useSettingsStoreActions(); const { setList } = useSettingsStoreActions();
const [isOpen, handlers] = useDisclosure(false); const [isOpen, handlers] = useDisclosure(false);
// Filter display types based on config
const availableDisplayTypes = useMemo(() => {
if (!props.displayTypes) {
return DISPLAY_TYPES;
}
const filtered = DISPLAY_TYPES.map((type) => {
const config = props.displayTypes?.find((c) => c.value === type.value);
if (config?.hidden) {
return null;
}
const result: (typeof DISPLAY_TYPES)[0] & { disabled?: boolean } = {
...type,
};
if (config?.disabled) {
result.disabled = true;
}
return result;
}).filter((type): type is NonNullable<typeof type> => type !== null);
return filtered;
}, [props.displayTypes]);
return ( return (
<> <>
<SettingsButton onClick={handlers.toggle} /> <SettingsButton onClick={handlers.toggle} />
@@ -101,9 +146,7 @@ export const ListConfigMenu = (props: ListConfigMenuProps) => {
> >
<Stack> <Stack>
<SegmentedControl <SegmentedControl
data={DISPLAY_TYPES.map((type) => ({ data={availableDisplayTypes}
...type,
}))}
fullWidth fullWidth
onChange={(value) => { onChange={(value) => {
setList(props.listKey, { setList(props.listKey, {
@@ -122,15 +165,28 @@ export const ListConfigMenu = (props: ListConfigMenuProps) => {
const Config = ({ const Config = ({
displayType, displayType,
optionsConfig,
tableColumnsData, tableColumnsData,
...props ...props
}: ListConfigMenuProps & { displayType: ListDisplayType }) => { }: ListConfigMenuProps & { displayType: ListDisplayType }) => {
switch (displayType) { switch (displayType) {
case ListDisplayType.GRID: case ListDisplayType.GRID:
return <GridConfig {...props} gridRowsData={tableColumnsData} />; return (
<GridConfig
{...props}
gridRowsData={tableColumnsData}
optionsConfig={optionsConfig?.grid}
/>
);
case ListDisplayType.TABLE: case ListDisplayType.TABLE:
return <TableConfig {...props} tableColumnsData={tableColumnsData} />; return (
<TableConfig
{...props}
optionsConfig={optionsConfig?.table}
tableColumnsData={tableColumnsData}
/>
);
default: default:
return null; return null;
@@ -11,7 +11,7 @@ import {
import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview'; import { disableNativeDragPreview } from '@atlaskit/pragmatic-drag-and-drop/element/disable-native-drag-preview';
import { useDebouncedState } from '@mantine/hooks'; import { useDebouncedState } from '@mantine/hooks';
import clsx from 'clsx'; import clsx from 'clsx';
import Fuse from 'fuse.js'; import Fuse, { type FuseResultMatch } from 'fuse.js';
import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -45,17 +45,28 @@ interface TableConfigProps {
label: string; label: string;
}[]; }[];
listKey: ItemListKey; listKey: ItemListKey;
optionsConfig?: {
[key: string]: {
disabled?: boolean;
hidden?: boolean;
};
};
tableColumnsData: { label: string; value: string }[]; tableColumnsData: { label: string; value: string }[];
} }
export const TableConfig = ({ extraOptions, listKey, tableColumnsData }: TableConfigProps) => { export const TableConfig = ({
extraOptions,
listKey,
optionsConfig,
tableColumnsData,
}: TableConfigProps) => {
const { t } = useTranslation(); const { t } = useTranslation();
const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings; const list = useSettingsStore((state) => state.lists[listKey]) as ItemListSettings;
const { setList } = useSettingsStoreActions(); const { setList } = useSettingsStoreActions();
const options = useMemo(() => { const options = useMemo(() => {
return [ const allOptions = [
{ {
component: ( component: (
<SegmentedControl <SegmentedControl
@@ -222,7 +233,18 @@ export const TableConfig = ({ extraOptions, listKey, tableColumnsData }: TableCo
...(extraOptions || []), ...(extraOptions || []),
]; ];
}, [extraOptions, listKey, setList, t, list]);
// Filter and apply config (hidden/disabled)
return allOptions
.map((option) => {
const config = optionsConfig?.[option.id];
if (config?.hidden) {
return null;
}
return option;
})
.filter((option): option is NonNullable<typeof option> => option !== null);
}, [extraOptions, listKey, optionsConfig, setList, t, list]);
return ( return (
<> <>
@@ -497,14 +519,18 @@ const TableColumnConfig = ({
); );
}; };
const DragHandle = ({ dragHandleRef }: { dragHandleRef: React.RefObject<HTMLButtonElement> }) => { const DragHandle = ({
dragHandleRef,
}: {
dragHandleRef: React.RefObject<HTMLButtonElement | null>;
}) => {
return ( return (
<ActionIcon <ActionIcon
icon="dragVertical" icon="dragVertical"
iconProps={{ iconProps={{
size: 'md', size: 'md',
}} }}
ref={dragHandleRef} ref={dragHandleRef as React.RefObject<HTMLButtonElement>}
size="xs" size="xs"
style={{ cursor: 'grab' }} style={{ cursor: 'grab' }}
variant="default" variant="default"
@@ -542,7 +568,7 @@ const TableColumnItem = memo(
handleRowWidth: (item: ItemTableListColumnConfig, number: number | string) => void; handleRowWidth: (item: ItemTableListColumnConfig, number: number | string) => void;
item: ItemTableListColumnConfig; item: ItemTableListColumnConfig;
label: string; label: string;
matches: null | readonly Fuse.FuseResultMatch[]; matches: null | readonly FuseResultMatch[];
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const ref = useRef<HTMLDivElement>(null); const ref = useRef<HTMLDivElement>(null);