diff --git a/front/src/modules/ui/board/components/BoardColumnMenu.tsx b/front/src/modules/ui/board/components/BoardColumnMenu.tsx
index 00fbc6660..9ba79dbca 100644
--- a/front/src/modules/ui/board/components/BoardColumnMenu.tsx
+++ b/front/src/modules/ui/board/components/BoardColumnMenu.tsx
@@ -5,7 +5,6 @@ import { IconPencil } from '@tabler/icons-react';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
-import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
import { icon } from '@/ui/theme/constants/icon';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
@@ -46,9 +45,7 @@ export function BoardColumnMenu({
{openMenu === 'actions' && (
setOpenMenu('title')}>
-
-
-
+
Rename
diff --git a/front/src/modules/ui/button/components/IconButtonGroup.tsx b/front/src/modules/ui/button/components/IconButtonGroup.tsx
index 5fe4cbc32..030e2c820 100644
--- a/front/src/modules/ui/button/components/IconButtonGroup.tsx
+++ b/front/src/modules/ui/button/components/IconButtonGroup.tsx
@@ -12,7 +12,7 @@ const StyledIconButtonGroupContainer = styled.div`
padding: ${({ theme }) => theme.spacing(0.5)};
`;
-type IconButtonGroupProps = Omit, 'children'> & {
+export type IconButtonGroupProps = Omit, 'children'> & {
variant: IconButtonVariant;
size: IconButtonSize;
children: React.ReactElement | React.ReactElement[];
diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx
new file mode 100644
index 000000000..e35b14707
--- /dev/null
+++ b/front/src/modules/ui/dropdown/components/DropdownMenuHeader.tsx
@@ -0,0 +1,58 @@
+import { ComponentProps, ReactElement } from 'react';
+import styled from '@emotion/styled';
+
+const StyledHeader = styled.li`
+ align-items: center;
+ color: ${({ theme }) => theme.font.color.primary};
+ display: flex;
+ font-size: ${({ theme }) => theme.font.size.sm};
+ font-weight: ${({ theme }) => theme.font.weight.medium};
+
+ padding: calc(${({ theme }) => theme.spacing(2)})
+ calc(${({ theme }) => theme.spacing(2)});
+
+ user-select: none;
+
+ ${({ onClick, theme }) => {
+ if (onClick) {
+ return `
+ cursor: pointer;
+
+ &:hover {
+ background: ${theme.background.transparent.light};
+ }
+ `;
+ }
+ }}
+`;
+
+const StyledStartIconWrapper = styled.span`
+ color: ${({ theme }) => theme.font.color.tertiary};
+ display: inline-flex;
+ margin-right: ${({ theme }) => theme.spacing(2)};
+`;
+
+const StyledEndIconWrapper = styled(StyledStartIconWrapper)`
+ color: ${({ theme }) => theme.font.color.tertiary};
+ display: inline-flex;
+ margin-left: auto;
+ margin-right: 0;
+`;
+
+type DropdownMenuHeaderProps = ComponentProps<'li'> & {
+ startIcon?: ReactElement;
+ endIcon?: ReactElement;
+};
+
+export const DropdownMenuHeader = ({
+ children,
+ startIcon,
+ endIcon,
+ ...props
+}: DropdownMenuHeaderProps) => (
+
+ {startIcon && {startIcon}}
+ {children}
+ {endIcon && {endIcon}}
+
+);
diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx
index 0f9ca0807..c060ef44f 100644
--- a/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx
+++ b/front/src/modules/ui/dropdown/components/DropdownMenuItem.tsx
@@ -1,8 +1,15 @@
+import { ComponentProps } from 'react';
import styled from '@emotion/styled';
+import {
+ IconButtonGroup,
+ type IconButtonGroupProps,
+} from '@/ui/button/components/IconButtonGroup';
import { hoverBackground } from '@/ui/theme/constants/effects';
-export const DropdownMenuItem = styled.li`
+const styledIconButtonGroupClassName = 'dropdown-menu-item-actions';
+
+const StyledItem = styled.li`
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
--vertical-padding: ${({ theme }) => theme.spacing(2)};
@@ -25,6 +32,43 @@ export const DropdownMenuItem = styled.li`
${hoverBackground};
+ position: relative;
user-select: none;
+
width: calc(100% - 2 * var(--horizontal-padding));
+
+ &:hover {
+ .${styledIconButtonGroupClassName} {
+ display: flex;
+ }
+ }
`;
+
+const StyledActions = styled(IconButtonGroup)`
+ display: none;
+ position: absolute;
+ right: ${({ theme }) => theme.spacing(1)};
+`;
+
+export type DropdownMenuItemProps = ComponentProps<'li'> & {
+ actions?: IconButtonGroupProps['children'];
+};
+
+export const DropdownMenuItem = ({
+ actions,
+ children,
+ ...props
+}: DropdownMenuItemProps) => (
+
+ {children}
+ {actions && (
+
+ {actions}
+
+ )}
+
+);
diff --git a/front/src/modules/ui/dropdown/components/DropdownMenuSubheader.tsx b/front/src/modules/ui/dropdown/components/DropdownMenuSubheader.tsx
new file mode 100644
index 000000000..8f9d12712
--- /dev/null
+++ b/front/src/modules/ui/dropdown/components/DropdownMenuSubheader.tsx
@@ -0,0 +1,11 @@
+import styled from '@emotion/styled';
+
+export const DropdownMenuSubheader = styled.div`
+ background-color: ${({ theme }) => theme.background.transparent.lighter};
+ color: ${({ theme }) => theme.font.color.light};
+ font-size: ${({ theme }) => theme.font.size.xxs};
+ font-weight: ${({ theme }) => theme.font.weight.semiBold};
+ padding: ${({ theme }) => `${theme.spacing(1)} ${theme.spacing(2)}`};
+ text-transform: uppercase;
+ width: 100%;
+`;
diff --git a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx
index 9f0b4cca9..6526157d3 100644
--- a/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx
+++ b/front/src/modules/ui/dropdown/components/__stories__/DropdownMenu.stories.tsx
@@ -2,18 +2,21 @@ import { useState } from 'react';
import styled from '@emotion/styled';
import type { Meta, StoryObj } from '@storybook/react';
-import { IconPlus } from '@/ui/icon/index';
+import { IconButton } from '@/ui/button/components/IconButton';
+import { IconPlus, IconUser } from '@/ui/icon';
import { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
import { Avatar } from '@/users/components/Avatar';
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
import { DropdownMenu } from '../DropdownMenu';
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
+import { DropdownMenuHeader } from '../DropdownMenuHeader';
import { DropdownMenuItem } from '../DropdownMenuItem';
import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
import { DropdownMenuSearch } from '../DropdownMenuSearch';
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
+import { DropdownMenuSubheader } from '../DropdownMenuSubheader';
const meta: Meta = {
title: 'UI/Dropdown/DropdownMenu',
@@ -59,47 +62,36 @@ const MenuAbsolutePositionWrapper = styled.div`
width: fit-content;
`;
-const FakeMenuItemList = () => (
- <>
- Company A
- Company B
- Company C
- Person 2
- Company D
- Person 1
- >
-);
-
const mockSelectArray = [
{
id: '1',
name: 'Company A',
- avatarUrl: avatarUrl,
+ avatarUrl,
},
{
id: '2',
name: 'Company B',
- avatarUrl: avatarUrl,
+ avatarUrl,
},
{
id: '3',
name: 'Company C',
- avatarUrl: avatarUrl,
+ avatarUrl,
},
{
id: '4',
name: 'Person 2',
- avatarUrl: avatarUrl,
+ avatarUrl,
},
{
id: '5',
name: 'Company D',
- avatarUrl: avatarUrl,
+ avatarUrl,
},
{
id: '6',
name: 'Person 1',
- avatarUrl: avatarUrl,
+ avatarUrl,
},
];
@@ -189,12 +181,77 @@ export const SimpleMenuItem: Story = {
render: (args) => (
-
+ {mockSelectArray.map(({ name }) => (
+ {name}
+ ))}
),
};
+export const WithHeaders: Story = {
+ ...WithContentBelow,
+ render: (args) => (
+
+ Header
+
+ Subheader 1
+
+ {mockSelectArray.slice(0, 3).map(({ name }) => (
+ {name}
+ ))}
+
+
+ Subheader 2
+
+ {mockSelectArray.slice(3).map(({ name }) => (
+ {name}
+ ))}
+
+
+ ),
+};
+
+export const WithIcons: Story = {
+ ...WithContentBelow,
+ render: (args) => (
+
+
+ {mockSelectArray.map(({ name }) => (
+
+
+ {name}
+
+ ))}
+
+
+ ),
+};
+
+export const WithActions: Story = {
+ ...WithContentBelow,
+ render: (args) => (
+
+
+ {mockSelectArray.map(({ name }, index) => (
+ } />,
+ } />,
+ ]}
+ >
+ {name}
+
+ ))}
+
+
+ ),
+ parameters: {
+ pseudo: { hover: ['.hover'] },
+ },
+};
+
export const LoadingMenu: Story = {
...WithContentBelow,
render: () => (
@@ -215,25 +272,9 @@ export const Search: Story = {
-
-
-
- ),
-};
-
-export const Button: Story = {
- ...WithContentBelow,
- render: (args) => (
-
-
-
-
- Create new
-
-
-
-
-
+ {mockSelectArray.map(({ name }) => (
+ {name}
+ ))}
),
diff --git a/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx
index 1d4094d88..c3e3861ed 100644
--- a/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx
+++ b/front/src/modules/ui/filter-n-sort/components/DropdownButton.tsx
@@ -2,7 +2,6 @@ import { ReactNode } from 'react';
import styled from '@emotion/styled';
import { Key } from 'ts-key-enum';
-import { IconChevronDown } from '@/ui/icon/index';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
@@ -50,29 +49,6 @@ const StyledDropdownButton = styled.div`
}
`;
-const StyledDropdownTopOption = styled.li`
- color: ${({ theme }) => theme.font.color.primary};
- cursor: pointer;
- display: flex;
- font-size: ${({ theme }) => theme.font.size.sm};
- font-weight: ${({ theme }) => theme.font.weight.medium};
- justify-content: space-between;
-
- padding: calc(${({ theme }) => theme.spacing(2)})
- calc(${({ theme }) => theme.spacing(2)});
- &:hover {
- background: ${({ theme }) => theme.background.transparent.light};
- }
- user-select: none;
-`;
-
-const StyledIcon = styled.div`
- display: flex;
- justify-content: center;
- margin-right: ${({ theme }) => theme.spacing(1)};
- min-width: ${({ theme }) => theme.spacing(4)};
-`;
-
function DropdownButton({
label,
isActive,
@@ -117,23 +93,4 @@ function DropdownButton({
);
}
-const StyleAngleDownContainer = styled.div`
- color: ${({ theme }) => theme.font.color.tertiary};
- display: flex;
- height: 100%;
- justify-content: center;
- margin-left: auto;
-`;
-
-function DropdownTopOptionAngleDown() {
- return (
-
-
-
- );
-}
-DropdownButton.StyledDropdownTopOption = StyledDropdownTopOption;
-DropdownButton.StyledDropdownTopOptionAngleDown = DropdownTopOptionAngleDown;
-DropdownButton.StyledIcon = StyledIcon;
-
export default DropdownButton;
diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx
index 0083e2528..00b8f6d13 100644
--- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx
+++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownFilterSelect.tsx
@@ -13,8 +13,6 @@ import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSe
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
-import DropdownButton from './DropdownButton';
-
export function FilterDropdownFilterSelect({
context,
}: {
@@ -61,9 +59,7 @@ export function FilterDropdownFilterSelect({
setFilterDropdownSearchInput('');
}}
>
-
- {availableFilter.icon}
-
+ {availableFilter.icon}
{availableFilter.label}
))}
diff --git a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx
index c82cbd78f..d75d151f6 100644
--- a/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx
+++ b/front/src/modules/ui/filter-n-sort/components/FilterDropdownOperandButton.tsx
@@ -1,18 +1,21 @@
import { Context } from 'react';
+import { useTheme } from '@emotion/react';
+import { IconChevronDown } from '@tabler/icons-react';
+import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
import { getOperandLabel } from '../utils/getOperandLabel';
-import DropdownButton from './DropdownButton';
-
export function FilterDropdownOperandButton({
context,
}: {
context: Context;
}) {
+ const theme = useTheme();
+
const [selectedOperandInDropdown] = useRecoilScopedState(
selectedOperandInDropdownScopedState,
context,
@@ -29,12 +32,12 @@ export function FilterDropdownOperandButton({
}
return (
- }
onClick={() => setIsOperandSelectionUnfolded(true)}
>
{getOperandLabel(selectedOperandInDropdown)}
-
-
+
);
}
diff --git a/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx b/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx
index ee8654fe4..363f3fd2d 100644
--- a/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx
+++ b/front/src/modules/ui/filter-n-sort/components/SortDropdownButton.tsx
@@ -1,5 +1,8 @@
import { useCallback, useState } from 'react';
+import { useTheme } from '@emotion/react';
+import { IconChevronDown } from '@tabler/icons-react';
+import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
@@ -24,10 +27,10 @@ export function SortDropdownButton({
onSortSelect,
HotkeyScope,
}: OwnProps) {
+ const theme = useTheme();
+
const [isUnfolded, setIsUnfolded] = useState(false);
-
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
-
const [selectedSortDirection, setSelectedSortDirection] =
useState['order']>('asc');
@@ -76,13 +79,12 @@ export function SortDropdownButton({
) : (
<>
- }
onClick={() => setIsOptionUnfolded(true)}
>
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
-
-
-
+
@@ -94,9 +96,7 @@ export function SortDropdownButton({
onSortItemSelect(sort);
}}
>
-
- {sort.icon}
-
+ {sort.icon}
{sort.label}
))}
diff --git a/front/src/modules/ui/icon/index.ts b/front/src/modules/ui/icon/index.ts
index d5e23d09e..a13b69b26 100644
--- a/front/src/modules/ui/icon/index.ts
+++ b/front/src/modules/ui/icon/index.ts
@@ -51,3 +51,4 @@ export { IconUserCircle } from '@tabler/icons-react';
export { IconCalendar } from '@tabler/icons-react';
export { IconPencil } from '@tabler/icons-react';
export { IconCircleDot } from '@tabler/icons-react';
+export { IconTag } from '@tabler/icons-react';
diff --git a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
index 244506674..522b2caf5 100644
--- a/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
+++ b/front/src/modules/ui/table/components/EntityTableColumnMenu.tsx
@@ -1,9 +1,8 @@
-import React, { ComponentProps, useRef } from 'react';
+import { cloneElement, ComponentProps, useRef } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconButton } from '@/ui/button/components/IconButton';
-import { IconButtonGroup } from '@/ui/button/components/IconButtonGroup';
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
@@ -19,23 +18,6 @@ const StyledColumnMenu = styled(DropdownMenu)`
font-weight: ${({ theme }) => theme.font.weight.regular};
`;
-const StyledIconButtonGroup = styled(IconButtonGroup)`
- display: none;
- position: absolute;
- right: ${({ theme }) => theme.spacing(1)};
-`;
-const styledIconButtonGroupClassName = 'column-menu-item-icon-button-group';
-
-const StyledColumnMenuItem = styled(DropdownMenuItem)`
- position: relative;
-
- &:hover {
- .${styledIconButtonGroupClassName} {
- display: flex;
- }
- }
-`;
-
type EntityTableColumnMenuProps = {
onAddViewField: (
viewFieldDefinition: ViewFieldDefinition,
@@ -62,23 +44,21 @@ export const EntityTableColumnMenu = ({
{viewFieldDefinitions.map((viewFieldDefinition) => (
-
- {viewFieldDefinition.columnIcon &&
- React.cloneElement(viewFieldDefinition.columnIcon, {
- size: theme.icon.size.md,
- })}
- {viewFieldDefinition.columnLabel}
-
+ }
onClick={() => onAddViewField(viewFieldDefinition)}
/>
-
-
+ }
+ >
+ {viewFieldDefinition.columnIcon &&
+ cloneElement(viewFieldDefinition.columnIcon, {
+ size: theme.icon.size.md,
+ })}
+ {viewFieldDefinition.columnLabel}
+
))}
diff --git a/front/src/modules/ui/table/components/EntityTableHeader.tsx b/front/src/modules/ui/table/components/EntityTableHeader.tsx
index 02f212d08..6edbccad5 100644
--- a/front/src/modules/ui/table/components/EntityTableHeader.tsx
+++ b/front/src/modules/ui/table/components/EntityTableHeader.tsx
@@ -19,6 +19,7 @@ import {
addableViewFieldDefinitionsState,
columnWidthByViewFieldIdState,
viewFieldsState,
+ visibleViewFieldsState,
} from '../states/viewFieldsState';
import type {
ViewFieldDefinition,
@@ -87,8 +88,8 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
export function EntityTableHeader() {
const theme = useTheme();
- const [{ objectName, viewFields }, setViewFieldsState] =
- useRecoilState(viewFieldsState);
+ const [{ objectName }, setViewFieldsState] = useRecoilState(viewFieldsState);
+ const viewFields = useRecoilValue(visibleViewFieldsState);
const columnWidths = useRecoilValue(columnWidthByViewFieldIdState);
const addableViewFieldDefinitions = useRecoilValue(
addableViewFieldDefinitionsState,
diff --git a/front/src/modules/ui/table/components/EntityTableRow.tsx b/front/src/modules/ui/table/components/EntityTableRow.tsx
index fdccf9379..503900110 100644
--- a/front/src/modules/ui/table/components/EntityTableRow.tsx
+++ b/front/src/modules/ui/table/components/EntityTableRow.tsx
@@ -2,7 +2,7 @@ import styled from '@emotion/styled';
import { useRecoilValue } from 'recoil';
import { ViewFieldContext } from '../states/ViewFieldContext';
-import { viewFieldsState } from '../states/viewFieldsState';
+import { visibleViewFieldsState } from '../states/viewFieldsState';
import { CheckboxCell } from './CheckboxCell';
import { EntityTableCell } from './EntityTableCell';
@@ -13,7 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>`
`;
export function EntityTableRow({ rowId }: { rowId: string }) {
- const { viewFields } = useRecoilValue(viewFieldsState);
+ const viewFields = useRecoilValue(visibleViewFieldsState);
return (
@@ -22,10 +22,7 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
{viewFields.map((viewField, columnIndex) => {
return (
-
+
);
diff --git a/front/src/modules/ui/table/hooks/useLoadView.ts b/front/src/modules/ui/table/hooks/useLoadView.ts
index 900908c88..2030f9cc9 100644
--- a/front/src/modules/ui/table/hooks/useLoadView.ts
+++ b/front/src/modules/ui/table/hooks/useLoadView.ts
@@ -28,7 +28,7 @@ export const toViewFieldInput = (
) => ({
fieldName: viewFieldDefinition.columnLabel,
index: viewFieldDefinition.columnOrder,
- isVisible: true,
+ isVisible: viewFieldDefinition.isVisible ?? true,
objectName,
sizeInPx: viewFieldDefinition.columnSize,
});
@@ -64,6 +64,7 @@ export const useLoadView = ({
columnLabel: viewField.fieldName,
columnOrder: viewField.index,
columnSize: viewField.sizeInPx,
+ isVisible: viewField.isVisible,
}));
setViewFieldsState({ objectName, viewFields });
diff --git a/front/src/modules/ui/table/states/viewFieldsState.ts b/front/src/modules/ui/table/states/viewFieldsState.ts
index 327857372..47f1a007d 100644
--- a/front/src/modules/ui/table/states/viewFieldsState.ts
+++ b/front/src/modules/ui/table/states/viewFieldsState.ts
@@ -47,3 +47,15 @@ export const addableViewFieldDefinitionsState = selector({
);
},
});
+
+export const visibleViewFieldsState = selector({
+ key: 'visibleViewFieldsState',
+ get: ({ get }) =>
+ get(viewFieldsState).viewFields.filter((viewField) => viewField.isVisible),
+});
+
+export const hiddenViewFieldsState = selector({
+ key: 'hiddenViewFieldsState',
+ get: ({ get }) =>
+ get(viewFieldsState).viewFields.filter((viewField) => !viewField.isVisible),
+});
diff --git a/front/src/modules/ui/table/table-header/components/TableHeader.tsx b/front/src/modules/ui/table/table-header/components/TableHeader.tsx
index dc17b0773..193479411 100644
--- a/front/src/modules/ui/table/table-header/components/TableHeader.tsx
+++ b/front/src/modules/ui/table/table-header/components/TableHeader.tsx
@@ -7,6 +7,7 @@ import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownBu
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
import { TopBar } from '@/ui/top-bar/TopBar';
+import { OptionsDropdownButton } from '@/views/components/OptionsDropdownButton';
import { TableContext } from '../../states/TableContext';
@@ -76,6 +77,9 @@ export function TableHeader({
onSortSelect={sortSelect}
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
/>
+
>
}
bottomComponent={
diff --git a/front/src/modules/ui/table/types/ViewField.ts b/front/src/modules/ui/table/types/ViewField.ts
index d5af391dd..eaa6d06fd 100644
--- a/front/src/modules/ui/table/types/ViewField.ts
+++ b/front/src/modules/ui/table/types/ViewField.ts
@@ -91,6 +91,7 @@ export type ViewFieldDefinition = {
columnOrder: number;
columnIcon?: JSX.Element;
filterIcon?: JSX.Element;
+ isVisible?: boolean;
metadata: T;
};
diff --git a/front/src/modules/views/components/OptionsDropdownButton.tsx b/front/src/modules/views/components/OptionsDropdownButton.tsx
new file mode 100644
index 000000000..8400b005a
--- /dev/null
+++ b/front/src/modules/views/components/OptionsDropdownButton.tsx
@@ -0,0 +1,137 @@
+import { useCallback, useState } from 'react';
+import { getOperationName } from '@apollo/client/utilities';
+import { useTheme } from '@emotion/react';
+import { useRecoilValue } from 'recoil';
+
+import { IconButton } from '@/ui/button/components/IconButton';
+import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
+import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
+import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
+import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
+import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
+import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
+import { IconChevronLeft, IconMinus, IconPlus, IconTag } from '@/ui/icon';
+import {
+ hiddenViewFieldsState,
+ visibleViewFieldsState,
+} from '@/ui/table/states/viewFieldsState';
+import {
+ ViewFieldDefinition,
+ ViewFieldMetadata,
+} from '@/ui/table/types/ViewField';
+import { useUpdateViewFieldMutation } from '~/generated/graphql';
+
+import { GET_VIEW_FIELDS } from '../queries/select';
+
+import { OptionsDropdownSection } from './OptionsDropdownSection';
+
+type OptionsDropdownButtonProps = {
+ HotkeyScope: FiltersHotkeyScope;
+};
+
+enum Option {
+ Properties = 'Properties',
+}
+
+export const OptionsDropdownButton = ({
+ HotkeyScope,
+}: OptionsDropdownButtonProps) => {
+ const theme = useTheme();
+ const [isUnfolded, setIsUnfolded] = useState(false);
+ const [selectedOption, setSelectedOption] = useState