feat: toggle board field visibilities (#1547)
Closes #1537, Closes #1539
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { type ComponentProps, useCallback } from 'react';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
@ -29,18 +29,6 @@ export function BoardHeader<SortField>({
|
||||
availableSorts,
|
||||
defaultViewName,
|
||||
}: BoardHeaderProps<SortField>) {
|
||||
const OptionsDropdownButton = useCallback(
|
||||
() => (
|
||||
<BoardOptionsDropdown
|
||||
customHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
|
||||
onStageAdd={onStageAdd}
|
||||
onViewsChange={onViewsChange}
|
||||
scopeContext={scopeContext}
|
||||
/>
|
||||
),
|
||||
[onStageAdd, onViewsChange, scopeContext],
|
||||
);
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||
<ViewBar
|
||||
@ -48,8 +36,15 @@ export function BoardHeader<SortField>({
|
||||
defaultViewName={defaultViewName}
|
||||
onViewsChange={onViewsChange}
|
||||
onViewSubmit={onViewSubmit}
|
||||
optionsDropdownButton={
|
||||
<BoardOptionsDropdown
|
||||
customHotkeyScope={{ scope: BoardOptionsHotkeyScope.Dropdown }}
|
||||
onStageAdd={onStageAdd}
|
||||
onViewsChange={onViewsChange}
|
||||
scopeContext={scopeContext}
|
||||
/>
|
||||
}
|
||||
optionsDropdownKey={BoardOptionsDropdownKey}
|
||||
OptionsDropdownButton={OptionsDropdownButton}
|
||||
scopeContext={scopeContext}
|
||||
/>
|
||||
</RecoilScope>
|
||||
|
||||
@ -16,6 +16,7 @@ import {
|
||||
IconLayoutKanban,
|
||||
IconPlus,
|
||||
IconSettings,
|
||||
IconTag,
|
||||
} from '@/ui/icon';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { MenuItemNavigate } from '@/ui/menu-item/components/MenuItemNavigate';
|
||||
@ -23,12 +24,16 @@ import { ThemeColor } from '@/ui/theme/constants/colors';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/ui/view-bar/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView';
|
||||
import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
|
||||
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
|
||||
import type { View } from '@/ui/view-bar/types/View';
|
||||
|
||||
import { useBoardCardFields } from '../hooks/useBoardCardFields';
|
||||
import { boardColumnsState } from '../states/boardColumnsState';
|
||||
import { hiddenBoardCardFieldsScopedSelector } from '../states/selectors/hiddenBoardCardFieldsScopedSelector';
|
||||
import { visibleBoardCardFieldsScopedSelector } from '../states/selectors/visibleBoardCardFieldsScopedSelector';
|
||||
import type { BoardColumnDefinition } from '../types/BoardColumnDefinition';
|
||||
import { BoardOptionsDropdownKey } from '../types/BoardOptionsDropdownKey';
|
||||
|
||||
@ -43,10 +48,7 @@ const StyledIconSettings = styled(IconSettings)`
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
enum BoardOptionsMenu {
|
||||
StageCreation = 'StageCreation',
|
||||
Stages = 'Stages',
|
||||
}
|
||||
type BoardOptionsMenu = 'fields' | 'stage-creation' | 'stages';
|
||||
|
||||
type ColumnForCreate = {
|
||||
id: string;
|
||||
@ -72,14 +74,22 @@ export function BoardOptionsDropdownContent({
|
||||
|
||||
const [boardColumns, setBoardColumns] = useRecoilState(boardColumnsState);
|
||||
|
||||
const hiddenBoardCardFields = useRecoilScopedValue(
|
||||
hiddenBoardCardFieldsScopedSelector,
|
||||
scopeContext,
|
||||
);
|
||||
const hasHiddenFields = hiddenBoardCardFields.length > 0;
|
||||
const visibleBoardCardFields = useRecoilScopedValue(
|
||||
visibleBoardCardFieldsScopedSelector,
|
||||
scopeContext,
|
||||
);
|
||||
const hasVisibleFields = visibleBoardCardFields.length > 0;
|
||||
|
||||
const viewsById = useRecoilScopedValue(viewsByIdScopedSelector, scopeContext);
|
||||
const viewEditMode = useRecoilValue(viewEditModeState);
|
||||
|
||||
const handleStageSubmit = () => {
|
||||
if (
|
||||
currentMenu !== BoardOptionsMenu.StageCreation ||
|
||||
!stageInputRef?.current?.value
|
||||
)
|
||||
if (currentMenu !== 'stage-creation' || !stageInputRef?.current?.value)
|
||||
return;
|
||||
|
||||
const columnToCreate: ColumnForCreate = {
|
||||
@ -113,6 +123,8 @@ export function BoardOptionsDropdownContent({
|
||||
setCurrentMenu(menu);
|
||||
};
|
||||
|
||||
const { handleFieldVisibilityChange } = useBoardCardFields({ scopeContext });
|
||||
|
||||
const { closeDropdownButton } = useDropdownButton({
|
||||
key: BoardOptionsDropdownKey,
|
||||
});
|
||||
@ -161,14 +173,19 @@ export function BoardOptionsDropdownContent({
|
||||
<StyledDropdownMenuSeparator />
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<MenuItemNavigate
|
||||
onClick={() => handleMenuNavigate(BoardOptionsMenu.Stages)}
|
||||
onClick={() => handleMenuNavigate('stages')}
|
||||
LeftIcon={IconLayoutKanban}
|
||||
text="Stages"
|
||||
/>
|
||||
<MenuItemNavigate
|
||||
onClick={() => handleMenuNavigate('fields')}
|
||||
LeftIcon={IconTag}
|
||||
text="Fields"
|
||||
/>
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
{currentMenu === BoardOptionsMenu.Stages && (
|
||||
{currentMenu === 'stages' && (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||
Stages
|
||||
@ -176,20 +193,45 @@ export function BoardOptionsDropdownContent({
|
||||
<StyledDropdownMenuSeparator />
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => setCurrentMenu(BoardOptionsMenu.StageCreation)}
|
||||
onClick={() => setCurrentMenu('stage-creation')}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add stage"
|
||||
/>
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
{currentMenu === BoardOptionsMenu.StageCreation && (
|
||||
{currentMenu === 'stage-creation' && (
|
||||
<DropdownMenuInput
|
||||
autoFocus
|
||||
placeholder="New stage"
|
||||
ref={stageInputRef}
|
||||
/>
|
||||
)}
|
||||
{currentMenu === 'fields' && (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||
Fields
|
||||
</DropdownMenuHeader>
|
||||
<StyledDropdownMenuSeparator />
|
||||
{hasVisibleFields && (
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Visible"
|
||||
fields={visibleBoardCardFields}
|
||||
onVisibilityChange={handleFieldVisibilityChange}
|
||||
/>
|
||||
)}
|
||||
{hasVisibleFields && hasHiddenFields && (
|
||||
<StyledDropdownMenuSeparator />
|
||||
)}
|
||||
{hasHiddenFields && (
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Hidden"
|
||||
fields={hiddenBoardCardFields}
|
||||
onVisibilityChange={handleFieldVisibilityChange}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</StyledDropdownMenu>
|
||||
);
|
||||
}
|
||||
|
||||
@ -160,8 +160,9 @@ export function EntityBoard({
|
||||
<EntityBoardColumn
|
||||
boardOptions={boardOptions}
|
||||
column={column}
|
||||
onTitleEdit={onEditColumnTitle}
|
||||
onDelete={onColumnDelete}
|
||||
onTitleEdit={onEditColumnTitle}
|
||||
scopeContext={scopeContext}
|
||||
/>
|
||||
</RecoilScope>
|
||||
</BoardColumnIdContext.Provider>
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { Context } from 'react';
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
import { useSetRecoilState } from 'recoil';
|
||||
|
||||
@ -11,10 +12,12 @@ export function EntityBoardCard({
|
||||
boardOptions,
|
||||
cardId,
|
||||
index,
|
||||
scopeContext,
|
||||
}: {
|
||||
boardOptions: BoardOptions;
|
||||
cardId: string;
|
||||
index: number;
|
||||
scopeContext: Context<string | null>;
|
||||
}) {
|
||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||
@ -43,7 +46,7 @@ export function EntityBoardCard({
|
||||
data-select-disable
|
||||
onContextMenu={handleContextMenu}
|
||||
>
|
||||
{boardOptions.cardComponent}
|
||||
{<boardOptions.CardComponent scopeContext={scopeContext} />}
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useContext } from 'react';
|
||||
import { type Context, useContext } from 'react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Draggable, Droppable, DroppableProvided } from '@hello-pangea/dnd';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
@ -48,15 +48,17 @@ const BoardColumnCardsContainer = ({
|
||||
};
|
||||
|
||||
export function EntityBoardColumn({
|
||||
column,
|
||||
boardOptions,
|
||||
column,
|
||||
onDelete,
|
||||
onTitleEdit,
|
||||
scopeContext,
|
||||
}: {
|
||||
column: BoardColumnDefinition;
|
||||
boardOptions: BoardOptions;
|
||||
column: BoardColumnDefinition;
|
||||
onDelete?: (columnId: string) => void;
|
||||
onTitleEdit: (columnId: string, title: string, color: string) => void;
|
||||
scopeContext: Context<string | null>;
|
||||
}) {
|
||||
const boardColumnId = useContext(BoardColumnIdContext) ?? '';
|
||||
|
||||
@ -92,6 +94,7 @@ export function EntityBoardColumn({
|
||||
index={index}
|
||||
cardId={cardId}
|
||||
boardOptions={boardOptions}
|
||||
scopeContext={scopeContext}
|
||||
/>
|
||||
</BoardCardIdContext.Provider>
|
||||
))}
|
||||
|
||||
42
front/src/modules/ui/board/hooks/useBoardCardFields.ts
Normal file
42
front/src/modules/ui/board/hooks/useBoardCardFields.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import type { Context } from 'react';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
|
||||
import { boardCardFieldsScopedState } from '../states/boardCardFieldsScopedState';
|
||||
import { boardCardFieldsByKeyScopedSelector } from '../states/selectors/boardCardFieldsByKeyScopedSelector';
|
||||
|
||||
export const useBoardCardFields = ({
|
||||
scopeContext,
|
||||
}: {
|
||||
scopeContext: Context<string | null>;
|
||||
}) => {
|
||||
const [boardCardFields, setBoardCardFields] = useRecoilScopedState(
|
||||
boardCardFieldsScopedState,
|
||||
scopeContext,
|
||||
);
|
||||
const boardCardFieldsByKey = useRecoilScopedValue(
|
||||
boardCardFieldsByKeyScopedSelector,
|
||||
scopeContext,
|
||||
);
|
||||
|
||||
const handleFieldVisibilityChange = (
|
||||
field: ViewFieldDefinition<ViewFieldMetadata>,
|
||||
) => {
|
||||
const nextFields = boardCardFieldsByKey[field.key]
|
||||
? boardCardFields.map((previousField) =>
|
||||
previousField.key === field.key
|
||||
? { ...previousField, isVisible: !field.isVisible }
|
||||
: previousField,
|
||||
)
|
||||
: [...boardCardFields, { ...field, isVisible: true }];
|
||||
|
||||
setBoardCardFields(nextFields);
|
||||
};
|
||||
|
||||
return { handleFieldVisibilityChange };
|
||||
};
|
||||
@ -0,0 +1,14 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
export const availableBoardCardFieldsScopedState = atomFamily<
|
||||
ViewFieldDefinition<ViewFieldMetadata>[],
|
||||
string
|
||||
>({
|
||||
key: 'availableBoardCardFieldsScopedState',
|
||||
default: [],
|
||||
});
|
||||
@ -0,0 +1,14 @@
|
||||
import { atomFamily } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
export const boardCardFieldsScopedState = atomFamily<
|
||||
ViewFieldDefinition<ViewFieldMetadata>[],
|
||||
string
|
||||
>({
|
||||
key: 'boardCardFieldsScopedState',
|
||||
default: [],
|
||||
});
|
||||
@ -0,0 +1,18 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
|
||||
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
|
||||
|
||||
export const boardCardFieldsByKeyScopedSelector = selectorFamily({
|
||||
key: 'boardCardFieldsByKeyScopedSelector',
|
||||
get:
|
||||
(scopeId: string) =>
|
||||
({ get }) =>
|
||||
get(boardCardFieldsScopedState(scopeId)).reduce<
|
||||
Record<string, ViewFieldDefinition<ViewFieldMetadata>>
|
||||
>((result, field) => ({ ...result, [field.key]: field }), {}),
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { availableBoardCardFieldsScopedState } from '../availableBoardCardFieldsScopedState';
|
||||
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
|
||||
|
||||
export const hiddenBoardCardFieldsScopedSelector = selectorFamily({
|
||||
key: 'hiddenBoardCardFieldsScopedSelector',
|
||||
get:
|
||||
(scopeId: string) =>
|
||||
({ get }) => {
|
||||
const fields = get(boardCardFieldsScopedState(scopeId));
|
||||
const fieldKeys = fields.map(({ key }) => key);
|
||||
const otherAvailableKeys = get(
|
||||
availableBoardCardFieldsScopedState(scopeId),
|
||||
).filter(({ key }) => !fieldKeys.includes(key));
|
||||
|
||||
return [
|
||||
...fields.filter((field) => !field.isVisible),
|
||||
...otherAvailableKeys,
|
||||
];
|
||||
},
|
||||
});
|
||||
@ -0,0 +1,13 @@
|
||||
import { selectorFamily } from 'recoil';
|
||||
|
||||
import { boardCardFieldsScopedState } from '../boardCardFieldsScopedState';
|
||||
|
||||
export const visibleBoardCardFieldsScopedSelector = selectorFamily({
|
||||
key: 'visibleBoardCardFieldsScopedSelector',
|
||||
get:
|
||||
(scopeId: string) =>
|
||||
({ get }) =>
|
||||
get(boardCardFieldsScopedState(scopeId)).filter(
|
||||
(field) => field.isVisible,
|
||||
),
|
||||
});
|
||||
@ -1,13 +0,0 @@
|
||||
import { atom } from 'recoil';
|
||||
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '../../editable-field/types/ViewField';
|
||||
|
||||
export const viewFieldsDefinitionsState = atom<
|
||||
ViewFieldDefinition<ViewFieldMetadata>[]
|
||||
>({
|
||||
key: 'viewFieldsDefinitionState',
|
||||
default: [],
|
||||
});
|
||||
@ -1,3 +1,5 @@
|
||||
import type { ComponentType, Context } from 'react';
|
||||
|
||||
import { FilterDefinitionByEntity } from '@/ui/view-bar/types/FilterDefinitionByEntity';
|
||||
import { SortType } from '@/ui/view-bar/types/interface';
|
||||
import { PipelineProgress } from '~/generated/graphql';
|
||||
@ -5,7 +7,7 @@ import { PipelineProgressOrderByWithRelationInput as PipelineProgresses_Order_By
|
||||
|
||||
export type BoardOptions = {
|
||||
newCardComponent: React.ReactNode;
|
||||
cardComponent: React.ReactNode;
|
||||
CardComponent: ComponentType<{ scopeContext: Context<string | null> }>;
|
||||
filters: FilterDefinitionByEntity<PipelineProgress>[];
|
||||
sorts: Array<SortType<PipelineProgresses_Order_By>>;
|
||||
};
|
||||
|
||||
@ -49,6 +49,7 @@ export function FloatingIconButtonGroup({
|
||||
applyBlur={false}
|
||||
applyShadow={false}
|
||||
Icon={Icon}
|
||||
key={index}
|
||||
onClick={onClick}
|
||||
position={position}
|
||||
size={size}
|
||||
|
||||
@ -117,11 +117,12 @@ export type ViewFieldMetadata = { type: ViewFieldType } & (
|
||||
);
|
||||
|
||||
export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
|
||||
key: string;
|
||||
name: string;
|
||||
Icon?: IconComponent;
|
||||
index: number;
|
||||
isVisible?: boolean;
|
||||
key: string;
|
||||
metadata: T;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type ViewFieldTextValue = string;
|
||||
|
||||
@ -12,25 +12,25 @@ import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/icon';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { ViewFieldsVisibilityDropdownSection } from '@/ui/view-bar/components/ViewFieldsVisibilityDropdownSection';
|
||||
import { useUpsertView } from '@/ui/view-bar/hooks/useUpsertView';
|
||||
import { viewsByIdScopedSelector } from '@/ui/view-bar/states/selectors/viewsByIdScopedSelector';
|
||||
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
|
||||
import type { View } from '@/ui/view-bar/types/View';
|
||||
|
||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
|
||||
import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector';
|
||||
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
|
||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||
|
||||
import { TableOptionsDropdownColumnVisibility } from './TableOptionsDropdownSection';
|
||||
|
||||
type TableOptionsDropdownButtonProps = {
|
||||
onViewsChange?: (views: View[]) => void | Promise<void>;
|
||||
onImport?: () => void;
|
||||
};
|
||||
|
||||
type TableOptionsMenu = 'properties';
|
||||
type TableOptionsMenu = 'fields';
|
||||
|
||||
export function TableOptionsDropdownContent({
|
||||
onViewsChange,
|
||||
@ -40,9 +40,9 @@ export function TableOptionsDropdownContent({
|
||||
key: TableOptionsDropdownKey,
|
||||
});
|
||||
|
||||
const [selectedMenu, setSelectedMenu] = useState<
|
||||
TableOptionsMenu | undefined
|
||||
>(undefined);
|
||||
const [currentMenu, setCurrentMenu] = useState<TableOptionsMenu | undefined>(
|
||||
undefined,
|
||||
);
|
||||
|
||||
const viewEditInputRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
@ -65,6 +65,8 @@ export function TableOptionsDropdownContent({
|
||||
scopeContext: TableRecoilScopeContext,
|
||||
});
|
||||
|
||||
const { handleColumnVisibilityChange } = useTableColumns();
|
||||
|
||||
const handleViewNameSubmit = async () => {
|
||||
const name = viewEditInputRef.current?.value;
|
||||
await upsertView(name);
|
||||
@ -72,10 +74,10 @@ export function TableOptionsDropdownContent({
|
||||
|
||||
const handleSelectMenu = (option: TableOptionsMenu) => {
|
||||
handleViewNameSubmit();
|
||||
setSelectedMenu(option);
|
||||
setCurrentMenu(option);
|
||||
};
|
||||
|
||||
const resetMenu = () => setSelectedMenu(undefined);
|
||||
const resetMenu = () => setCurrentMenu(undefined);
|
||||
|
||||
useScopedHotkeys(
|
||||
Key.Escape,
|
||||
@ -97,7 +99,7 @@ export function TableOptionsDropdownContent({
|
||||
|
||||
return (
|
||||
<StyledDropdownMenu>
|
||||
{!selectedMenu && (
|
||||
{!currentMenu && (
|
||||
<>
|
||||
{!!viewEditMode.mode ? (
|
||||
<DropdownMenuInput
|
||||
@ -118,9 +120,9 @@ export function TableOptionsDropdownContent({
|
||||
<StyledDropdownMenuSeparator />
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={() => handleSelectMenu('properties')}
|
||||
onClick={() => handleSelectMenu('fields')}
|
||||
LeftIcon={IconTag}
|
||||
text="Properties"
|
||||
text="Fields"
|
||||
/>
|
||||
{onImport && (
|
||||
<MenuItem
|
||||
@ -132,22 +134,24 @@ export function TableOptionsDropdownContent({
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</>
|
||||
)}
|
||||
{selectedMenu === 'properties' && (
|
||||
{currentMenu === 'fields' && (
|
||||
<>
|
||||
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={resetMenu}>
|
||||
Properties
|
||||
Fields
|
||||
</DropdownMenuHeader>
|
||||
<StyledDropdownMenuSeparator />
|
||||
<TableOptionsDropdownColumnVisibility
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Visible"
|
||||
columns={visibleTableColumns}
|
||||
fields={visibleTableColumns}
|
||||
onVisibilityChange={handleColumnVisibilityChange}
|
||||
/>
|
||||
{hiddenTableColumns.length > 0 && (
|
||||
<>
|
||||
<StyledDropdownMenuSeparator />
|
||||
<TableOptionsDropdownColumnVisibility
|
||||
<ViewFieldsVisibilityDropdownSection
|
||||
title="Hidden"
|
||||
columns={hiddenTableColumns}
|
||||
fields={hiddenTableColumns}
|
||||
onVisibilityChange={handleColumnVisibilityChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||
import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader';
|
||||
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
|
||||
import { IconMinus, IconPlus } from '@/ui/icon';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
|
||||
import { useTableColumns } from '../../hooks/useTableColumns';
|
||||
import type { ColumnDefinition } from '../../types/ColumnDefinition';
|
||||
|
||||
type OwnProps = {
|
||||
title: string;
|
||||
columns: ColumnDefinition<ViewFieldMetadata>[];
|
||||
};
|
||||
|
||||
export function TableOptionsDropdownColumnVisibility({
|
||||
title,
|
||||
columns,
|
||||
}: OwnProps) {
|
||||
const { handleColumnVisibilityChange } = useTableColumns();
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{columns.map((column) => (
|
||||
<MenuItem
|
||||
key={column.key}
|
||||
LeftIcon={column.Icon}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: column.isVisible ? IconMinus : IconPlus,
|
||||
onClick: () => handleColumnVisibilityChange(column),
|
||||
},
|
||||
]}
|
||||
text={column.name}
|
||||
/>
|
||||
))}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -1,4 +1,3 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilCallback, useRecoilState, useRecoilValue } from 'recoil';
|
||||
|
||||
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||
@ -70,17 +69,6 @@ export function TableHeader<SortField>({
|
||||
await onViewSubmit?.();
|
||||
}
|
||||
|
||||
const OptionsDropdownButton = useCallback(
|
||||
() => (
|
||||
<TableOptionsDropdown
|
||||
onImport={onImport}
|
||||
onViewsChange={onViewsChange}
|
||||
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||
/>
|
||||
),
|
||||
[onImport, onViewsChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||
<ViewBar
|
||||
@ -89,7 +77,13 @@ export function TableHeader<SortField>({
|
||||
onReset={handleViewBarReset}
|
||||
onViewSelect={handleViewSelect}
|
||||
onViewSubmit={handleViewSubmit}
|
||||
OptionsDropdownButton={OptionsDropdownButton}
|
||||
optionsDropdownButton={
|
||||
<TableOptionsDropdown
|
||||
onImport={onImport}
|
||||
onViewsChange={onViewsChange}
|
||||
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||
/>
|
||||
}
|
||||
optionsDropdownKey={TableOptionsDropdownKey}
|
||||
scopeContext={TableRecoilScopeContext}
|
||||
/>
|
||||
|
||||
@ -6,5 +6,4 @@ import type {
|
||||
export type ColumnDefinition<T extends ViewFieldMetadata | unknown> =
|
||||
ViewFieldDefinition<T> & {
|
||||
size: number;
|
||||
index: number;
|
||||
};
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ComponentProps, ComponentType, Context } from 'react';
|
||||
import type { ComponentProps, Context, ReactNode } from 'react';
|
||||
|
||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||
import { TopBar } from '@/ui/top-bar/TopBar';
|
||||
@ -22,7 +22,7 @@ import {
|
||||
} from './ViewsDropdownButton';
|
||||
|
||||
export type ViewBarProps<SortField> = ComponentProps<'div'> & {
|
||||
OptionsDropdownButton: ComponentType;
|
||||
optionsDropdownButton: ReactNode;
|
||||
optionsDropdownKey: string;
|
||||
scopeContext: Context<string | null>;
|
||||
} & Pick<
|
||||
@ -41,7 +41,7 @@ export const ViewBar = <SortField,>({
|
||||
onViewsChange,
|
||||
onViewSelect,
|
||||
onViewSubmit,
|
||||
OptionsDropdownButton,
|
||||
optionsDropdownButton,
|
||||
optionsDropdownKey,
|
||||
scopeContext,
|
||||
...props
|
||||
@ -76,7 +76,7 @@ export const ViewBar = <SortField,>({
|
||||
hotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
isPrimaryButton
|
||||
/>
|
||||
<OptionsDropdownButton />
|
||||
{optionsDropdownButton}
|
||||
</>
|
||||
}
|
||||
bottomComponent={
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||
import { StyledDropdownMenuSubheader } from '@/ui/dropdown/components/StyledDropdownMenuSubheader';
|
||||
import type {
|
||||
ViewFieldDefinition,
|
||||
ViewFieldMetadata,
|
||||
} from '@/ui/editable-field/types/ViewField';
|
||||
import { IconMinus, IconPlus } from '@/ui/icon';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
|
||||
type OwnProps<Field> = {
|
||||
fields: Field[];
|
||||
onVisibilityChange: (field: Field) => void;
|
||||
title: string;
|
||||
};
|
||||
|
||||
export function ViewFieldsVisibilityDropdownSection<
|
||||
Field extends ViewFieldDefinition<ViewFieldMetadata>,
|
||||
>({ fields, onVisibilityChange, title }: OwnProps<Field>) {
|
||||
return (
|
||||
<>
|
||||
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{fields.map((field) => (
|
||||
<MenuItem
|
||||
key={field.key}
|
||||
LeftIcon={field.Icon}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: field.isVisible ? IconMinus : IconPlus,
|
||||
onClick: () => onVisibilityChange(field),
|
||||
},
|
||||
]}
|
||||
text={field.name}
|
||||
/>
|
||||
))}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user