@ -5,7 +5,6 @@ import { IconPencil } from '@tabler/icons-react';
|
|||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
import DropdownButton from '@/ui/filter-n-sort/components/DropdownButton';
|
|
||||||
import { icon } from '@/ui/theme/constants/icon';
|
import { icon } from '@/ui/theme/constants/icon';
|
||||||
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||||
|
|
||||||
@ -46,9 +45,7 @@ export function BoardColumnMenu({
|
|||||||
{openMenu === 'actions' && (
|
{openMenu === 'actions' && (
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
<DropdownMenuSelectableItem onClick={() => setOpenMenu('title')}>
|
<DropdownMenuSelectableItem onClick={() => setOpenMenu('title')}>
|
||||||
<DropdownButton.StyledIcon>
|
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
||||||
<IconPencil size={icon.size.md} stroke={icon.stroke.sm} />
|
|
||||||
</DropdownButton.StyledIcon>
|
|
||||||
Rename
|
Rename
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const StyledIconButtonGroupContainer = styled.div`
|
|||||||
padding: ${({ theme }) => theme.spacing(0.5)};
|
padding: ${({ theme }) => theme.spacing(0.5)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
type IconButtonGroupProps = Omit<ComponentProps<'div'>, 'children'> & {
|
export type IconButtonGroupProps = Omit<ComponentProps<'div'>, 'children'> & {
|
||||||
variant: IconButtonVariant;
|
variant: IconButtonVariant;
|
||||||
size: IconButtonSize;
|
size: IconButtonSize;
|
||||||
children: React.ReactElement | React.ReactElement[];
|
children: React.ReactElement | React.ReactElement[];
|
||||||
|
|||||||
@ -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) => (
|
||||||
|
<StyledHeader {...props}>
|
||||||
|
{startIcon && <StyledStartIconWrapper>{startIcon}</StyledStartIconWrapper>}
|
||||||
|
{children}
|
||||||
|
{endIcon && <StyledEndIconWrapper>{endIcon}</StyledEndIconWrapper>}
|
||||||
|
</StyledHeader>
|
||||||
|
);
|
||||||
@ -1,8 +1,15 @@
|
|||||||
|
import { ComponentProps } from 'react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
|
import {
|
||||||
|
IconButtonGroup,
|
||||||
|
type IconButtonGroupProps,
|
||||||
|
} from '@/ui/button/components/IconButtonGroup';
|
||||||
import { hoverBackground } from '@/ui/theme/constants/effects';
|
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)};
|
--horizontal-padding: ${({ theme }) => theme.spacing(1)};
|
||||||
--vertical-padding: ${({ theme }) => theme.spacing(2)};
|
--vertical-padding: ${({ theme }) => theme.spacing(2)};
|
||||||
|
|
||||||
@ -25,6 +32,43 @@ export const DropdownMenuItem = styled.li`
|
|||||||
|
|
||||||
${hoverBackground};
|
${hoverBackground};
|
||||||
|
|
||||||
|
position: relative;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
|
|
||||||
width: calc(100% - 2 * var(--horizontal-padding));
|
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) => (
|
||||||
|
<StyledItem {...props}>
|
||||||
|
{children}
|
||||||
|
{actions && (
|
||||||
|
<StyledActions
|
||||||
|
className={styledIconButtonGroupClassName}
|
||||||
|
variant="transparent"
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
{actions}
|
||||||
|
</StyledActions>
|
||||||
|
)}
|
||||||
|
</StyledItem>
|
||||||
|
);
|
||||||
|
|||||||
@ -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%;
|
||||||
|
`;
|
||||||
@ -2,18 +2,21 @@ import { useState } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import type { Meta, StoryObj } from '@storybook/react';
|
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 { DropdownMenuSkeletonItem } from '@/ui/input/relation-picker/components/skeletons/DropdownMenuSkeletonItem';
|
||||||
import { Avatar } from '@/users/components/Avatar';
|
import { Avatar } from '@/users/components/Avatar';
|
||||||
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
||||||
|
|
||||||
import { DropdownMenu } from '../DropdownMenu';
|
import { DropdownMenu } from '../DropdownMenu';
|
||||||
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
|
import { DropdownMenuCheckableItem } from '../DropdownMenuCheckableItem';
|
||||||
|
import { DropdownMenuHeader } from '../DropdownMenuHeader';
|
||||||
import { DropdownMenuItem } from '../DropdownMenuItem';
|
import { DropdownMenuItem } from '../DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '../DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSearch } from '../DropdownMenuSearch';
|
import { DropdownMenuSearch } from '../DropdownMenuSearch';
|
||||||
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '../DropdownMenuSelectableItem';
|
||||||
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '../DropdownMenuSeparator';
|
||||||
|
import { DropdownMenuSubheader } from '../DropdownMenuSubheader';
|
||||||
|
|
||||||
const meta: Meta<typeof DropdownMenu> = {
|
const meta: Meta<typeof DropdownMenu> = {
|
||||||
title: 'UI/Dropdown/DropdownMenu',
|
title: 'UI/Dropdown/DropdownMenu',
|
||||||
@ -59,47 +62,36 @@ const MenuAbsolutePositionWrapper = styled.div`
|
|||||||
width: fit-content;
|
width: fit-content;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const FakeMenuItemList = () => (
|
|
||||||
<>
|
|
||||||
<DropdownMenuItem>Company A</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Company B</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Company C</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Person 2</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Company D</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem>Person 1</DropdownMenuItem>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const mockSelectArray = [
|
const mockSelectArray = [
|
||||||
{
|
{
|
||||||
id: '1',
|
id: '1',
|
||||||
name: 'Company A',
|
name: 'Company A',
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
id: '2',
|
||||||
name: 'Company B',
|
name: 'Company B',
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
id: '3',
|
||||||
name: 'Company C',
|
name: 'Company C',
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
id: '4',
|
||||||
name: 'Person 2',
|
name: 'Person 2',
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '5',
|
id: '5',
|
||||||
name: 'Company D',
|
name: 'Company D',
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '6',
|
id: '6',
|
||||||
name: 'Person 1',
|
name: 'Person 1',
|
||||||
avatarUrl: avatarUrl,
|
avatarUrl,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -189,12 +181,77 @@ export const SimpleMenuItem: Story = {
|
|||||||
render: (args) => (
|
render: (args) => (
|
||||||
<DropdownMenu {...args}>
|
<DropdownMenu {...args}>
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
<FakeMenuItemList />
|
{mockSelectArray.map(({ name }) => (
|
||||||
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const WithHeaders: Story = {
|
||||||
|
...WithContentBelow,
|
||||||
|
render: (args) => (
|
||||||
|
<DropdownMenu {...args}>
|
||||||
|
<DropdownMenuHeader>Header</DropdownMenuHeader>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuSubheader>Subheader 1</DropdownMenuSubheader>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{mockSelectArray.slice(0, 3).map(({ name }) => (
|
||||||
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuSubheader>Subheader 2</DropdownMenuSubheader>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{mockSelectArray.slice(3).map(({ name }) => (
|
||||||
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithIcons: Story = {
|
||||||
|
...WithContentBelow,
|
||||||
|
render: (args) => (
|
||||||
|
<DropdownMenu {...args}>
|
||||||
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
|
{mockSelectArray.map(({ name }) => (
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<IconUser size={16} />
|
||||||
|
{name}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WithActions: Story = {
|
||||||
|
...WithContentBelow,
|
||||||
|
render: (args) => (
|
||||||
|
<DropdownMenu {...args}>
|
||||||
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
|
{mockSelectArray.map(({ name }, index) => (
|
||||||
|
<DropdownMenuItem
|
||||||
|
className={index === 0 ? 'hover' : undefined}
|
||||||
|
actions={[
|
||||||
|
<IconButton icon={<IconUser />} />,
|
||||||
|
<IconButton icon={<IconPlus />} />,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</DropdownMenu>
|
||||||
|
),
|
||||||
|
parameters: {
|
||||||
|
pseudo: { hover: ['.hover'] },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
export const LoadingMenu: Story = {
|
export const LoadingMenu: Story = {
|
||||||
...WithContentBelow,
|
...WithContentBelow,
|
||||||
render: () => (
|
render: () => (
|
||||||
@ -215,25 +272,9 @@ export const Search: Story = {
|
|||||||
<DropdownMenuSearch />
|
<DropdownMenuSearch />
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
<DropdownMenuItemsContainer hasMaxHeight>
|
||||||
<FakeMenuItemList />
|
{mockSelectArray.map(({ name }) => (
|
||||||
</DropdownMenuItemsContainer>
|
<DropdownMenuItem>{name}</DropdownMenuItem>
|
||||||
</DropdownMenu>
|
))}
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Button: Story = {
|
|
||||||
...WithContentBelow,
|
|
||||||
render: (args) => (
|
|
||||||
<DropdownMenu {...args}>
|
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
|
||||||
<DropdownMenuItem>
|
|
||||||
<IconPlus size={16} />
|
|
||||||
<div>Create new</div>
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuItemsContainer>
|
|
||||||
<DropdownMenuSeparator />
|
|
||||||
<DropdownMenuItemsContainer hasMaxHeight>
|
|
||||||
<FakeSelectableMenuItemList />
|
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
),
|
),
|
||||||
|
|||||||
@ -2,7 +2,6 @@ import { ReactNode } from 'react';
|
|||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
import { Key } from 'ts-key-enum';
|
import { Key } from 'ts-key-enum';
|
||||||
|
|
||||||
import { IconChevronDown } from '@/ui/icon/index';
|
|
||||||
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
|
||||||
|
|
||||||
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
import { FiltersHotkeyScope } from '../types/FiltersHotkeyScope';
|
||||||
@ -50,29 +49,6 @@ const StyledDropdownButton = styled.div<StyledDropdownButtonProps>`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
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({
|
function DropdownButton({
|
||||||
label,
|
label,
|
||||||
isActive,
|
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 (
|
|
||||||
<StyleAngleDownContainer>
|
|
||||||
<IconChevronDown size={16} />
|
|
||||||
</StyleAngleDownContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
DropdownButton.StyledDropdownTopOption = StyledDropdownTopOption;
|
|
||||||
DropdownButton.StyledDropdownTopOptionAngleDown = DropdownTopOptionAngleDown;
|
|
||||||
DropdownButton.StyledIcon = StyledIcon;
|
|
||||||
|
|
||||||
export default DropdownButton;
|
export default DropdownButton;
|
||||||
|
|||||||
@ -13,8 +13,6 @@ import { filterDropdownSearchInputScopedState } from '../states/filterDropdownSe
|
|||||||
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
|
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
|
||||||
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
import { getOperandsForFilterType } from '../utils/getOperandsForFilterType';
|
||||||
|
|
||||||
import DropdownButton from './DropdownButton';
|
|
||||||
|
|
||||||
export function FilterDropdownFilterSelect({
|
export function FilterDropdownFilterSelect({
|
||||||
context,
|
context,
|
||||||
}: {
|
}: {
|
||||||
@ -61,9 +59,7 @@ export function FilterDropdownFilterSelect({
|
|||||||
setFilterDropdownSearchInput('');
|
setFilterDropdownSearchInput('');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownButton.StyledIcon>
|
{availableFilter.icon}
|
||||||
{availableFilter.icon}
|
|
||||||
</DropdownButton.StyledIcon>
|
|
||||||
{availableFilter.label}
|
{availableFilter.label}
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -1,18 +1,21 @@
|
|||||||
import { Context } from 'react';
|
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 { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||||
|
|
||||||
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
|
import { isFilterDropdownOperandSelectUnfoldedScopedState } from '../states/isFilterDropdownOperandSelectUnfoldedScopedState';
|
||||||
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
|
import { selectedOperandInDropdownScopedState } from '../states/selectedOperandInDropdownScopedState';
|
||||||
import { getOperandLabel } from '../utils/getOperandLabel';
|
import { getOperandLabel } from '../utils/getOperandLabel';
|
||||||
|
|
||||||
import DropdownButton from './DropdownButton';
|
|
||||||
|
|
||||||
export function FilterDropdownOperandButton({
|
export function FilterDropdownOperandButton({
|
||||||
context,
|
context,
|
||||||
}: {
|
}: {
|
||||||
context: Context<string | null>;
|
context: Context<string | null>;
|
||||||
}) {
|
}) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const [selectedOperandInDropdown] = useRecoilScopedState(
|
const [selectedOperandInDropdown] = useRecoilScopedState(
|
||||||
selectedOperandInDropdownScopedState,
|
selectedOperandInDropdownScopedState,
|
||||||
context,
|
context,
|
||||||
@ -29,12 +32,12 @@ export function FilterDropdownOperandButton({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DropdownButton.StyledDropdownTopOption
|
<DropdownMenuHeader
|
||||||
key={'selected-filter-operand'}
|
key={'selected-filter-operand'}
|
||||||
|
endIcon={<IconChevronDown size={theme.icon.size.md} />}
|
||||||
onClick={() => setIsOperandSelectionUnfolded(true)}
|
onClick={() => setIsOperandSelectionUnfolded(true)}
|
||||||
>
|
>
|
||||||
{getOperandLabel(selectedOperandInDropdown)}
|
{getOperandLabel(selectedOperandInDropdown)}
|
||||||
<DropdownButton.StyledDropdownTopOptionAngleDown />
|
</DropdownMenuHeader>
|
||||||
</DropdownButton.StyledDropdownTopOption>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
import { useCallback, useState } from 'react';
|
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 { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
import { DropdownMenuSelectableItem } from '@/ui/dropdown/components/DropdownMenuSelectableItem';
|
||||||
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
import { DropdownMenuSeparator } from '@/ui/dropdown/components/DropdownMenuSeparator';
|
||||||
@ -24,10 +27,10 @@ export function SortDropdownButton<SortField>({
|
|||||||
onSortSelect,
|
onSortSelect,
|
||||||
HotkeyScope,
|
HotkeyScope,
|
||||||
}: OwnProps<SortField>) {
|
}: OwnProps<SortField>) {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||||
|
|
||||||
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
const [isOptionUnfolded, setIsOptionUnfolded] = useState(false);
|
||||||
|
|
||||||
const [selectedSortDirection, setSelectedSortDirection] =
|
const [selectedSortDirection, setSelectedSortDirection] =
|
||||||
useState<SelectedSortType<SortField>['order']>('asc');
|
useState<SelectedSortType<SortField>['order']>('asc');
|
||||||
|
|
||||||
@ -76,13 +79,12 @@ export function SortDropdownButton<SortField>({
|
|||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<DropdownButton.StyledDropdownTopOption
|
<DropdownMenuHeader
|
||||||
|
endIcon={<IconChevronDown size={theme.icon.size.md} />}
|
||||||
onClick={() => setIsOptionUnfolded(true)}
|
onClick={() => setIsOptionUnfolded(true)}
|
||||||
>
|
>
|
||||||
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
{selectedSortDirection === 'asc' ? 'Ascending' : 'Descending'}
|
||||||
|
</DropdownMenuHeader>
|
||||||
<DropdownButton.StyledDropdownTopOptionAngleDown />
|
|
||||||
</DropdownButton.StyledDropdownTopOption>
|
|
||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
|
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
@ -94,9 +96,7 @@ export function SortDropdownButton<SortField>({
|
|||||||
onSortItemSelect(sort);
|
onSortItemSelect(sort);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<DropdownButton.StyledIcon>
|
{sort.icon}
|
||||||
{sort.icon}
|
|
||||||
</DropdownButton.StyledIcon>
|
|
||||||
{sort.label}
|
{sort.label}
|
||||||
</DropdownMenuSelectableItem>
|
</DropdownMenuSelectableItem>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@ -51,3 +51,4 @@ export { IconUserCircle } from '@tabler/icons-react';
|
|||||||
export { IconCalendar } from '@tabler/icons-react';
|
export { IconCalendar } from '@tabler/icons-react';
|
||||||
export { IconPencil } from '@tabler/icons-react';
|
export { IconPencil } from '@tabler/icons-react';
|
||||||
export { IconCircleDot } from '@tabler/icons-react';
|
export { IconCircleDot } from '@tabler/icons-react';
|
||||||
|
export { IconTag } from '@tabler/icons-react';
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import React, { ComponentProps, useRef } from 'react';
|
import { cloneElement, ComponentProps, useRef } from 'react';
|
||||||
import { useTheme } from '@emotion/react';
|
import { useTheme } from '@emotion/react';
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
|
||||||
import { IconButton } from '@/ui/button/components/IconButton';
|
import { IconButton } from '@/ui/button/components/IconButton';
|
||||||
import { IconButtonGroup } from '@/ui/button/components/IconButtonGroup';
|
|
||||||
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
import { DropdownMenu } from '@/ui/dropdown/components/DropdownMenu';
|
||||||
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||||
@ -19,23 +18,6 @@ const StyledColumnMenu = styled(DropdownMenu)`
|
|||||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
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 = {
|
type EntityTableColumnMenuProps = {
|
||||||
onAddViewField: (
|
onAddViewField: (
|
||||||
viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>,
|
viewFieldDefinition: ViewFieldDefinition<ViewFieldMetadata>,
|
||||||
@ -62,23 +44,21 @@ export const EntityTableColumnMenu = ({
|
|||||||
<StyledColumnMenu {...props} ref={ref}>
|
<StyledColumnMenu {...props} ref={ref}>
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{viewFieldDefinitions.map((viewFieldDefinition) => (
|
{viewFieldDefinitions.map((viewFieldDefinition) => (
|
||||||
<StyledColumnMenuItem key={viewFieldDefinition.id}>
|
<DropdownMenuItem
|
||||||
{viewFieldDefinition.columnIcon &&
|
key={viewFieldDefinition.id}
|
||||||
React.cloneElement(viewFieldDefinition.columnIcon, {
|
actions={
|
||||||
size: theme.icon.size.md,
|
|
||||||
})}
|
|
||||||
{viewFieldDefinition.columnLabel}
|
|
||||||
<StyledIconButtonGroup
|
|
||||||
className={styledIconButtonGroupClassName}
|
|
||||||
variant="transparent"
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<IconPlus size={theme.icon.size.sm} />}
|
icon={<IconPlus size={theme.icon.size.sm} />}
|
||||||
onClick={() => onAddViewField(viewFieldDefinition)}
|
onClick={() => onAddViewField(viewFieldDefinition)}
|
||||||
/>
|
/>
|
||||||
</StyledIconButtonGroup>
|
}
|
||||||
</StyledColumnMenuItem>
|
>
|
||||||
|
{viewFieldDefinition.columnIcon &&
|
||||||
|
cloneElement(viewFieldDefinition.columnIcon, {
|
||||||
|
size: theme.icon.size.md,
|
||||||
|
})}
|
||||||
|
{viewFieldDefinition.columnLabel}
|
||||||
|
</DropdownMenuItem>
|
||||||
))}
|
))}
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</StyledColumnMenu>
|
</StyledColumnMenu>
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
addableViewFieldDefinitionsState,
|
addableViewFieldDefinitionsState,
|
||||||
columnWidthByViewFieldIdState,
|
columnWidthByViewFieldIdState,
|
||||||
viewFieldsState,
|
viewFieldsState,
|
||||||
|
visibleViewFieldsState,
|
||||||
} from '../states/viewFieldsState';
|
} from '../states/viewFieldsState';
|
||||||
import type {
|
import type {
|
||||||
ViewFieldDefinition,
|
ViewFieldDefinition,
|
||||||
@ -87,8 +88,8 @@ const StyledEntityTableColumnMenu = styled(EntityTableColumnMenu)`
|
|||||||
export function EntityTableHeader() {
|
export function EntityTableHeader() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
||||||
const [{ objectName, viewFields }, setViewFieldsState] =
|
const [{ objectName }, setViewFieldsState] = useRecoilState(viewFieldsState);
|
||||||
useRecoilState(viewFieldsState);
|
const viewFields = useRecoilValue(visibleViewFieldsState);
|
||||||
const columnWidths = useRecoilValue(columnWidthByViewFieldIdState);
|
const columnWidths = useRecoilValue(columnWidthByViewFieldIdState);
|
||||||
const addableViewFieldDefinitions = useRecoilValue(
|
const addableViewFieldDefinitions = useRecoilValue(
|
||||||
addableViewFieldDefinitionsState,
|
addableViewFieldDefinitionsState,
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import styled from '@emotion/styled';
|
|||||||
import { useRecoilValue } from 'recoil';
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { ViewFieldContext } from '../states/ViewFieldContext';
|
import { ViewFieldContext } from '../states/ViewFieldContext';
|
||||||
import { viewFieldsState } from '../states/viewFieldsState';
|
import { visibleViewFieldsState } from '../states/viewFieldsState';
|
||||||
|
|
||||||
import { CheckboxCell } from './CheckboxCell';
|
import { CheckboxCell } from './CheckboxCell';
|
||||||
import { EntityTableCell } from './EntityTableCell';
|
import { EntityTableCell } from './EntityTableCell';
|
||||||
@ -13,7 +13,7 @@ const StyledRow = styled.tr<{ selected: boolean }>`
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
export function EntityTableRow({ rowId }: { rowId: string }) {
|
export function EntityTableRow({ rowId }: { rowId: string }) {
|
||||||
const { viewFields } = useRecoilValue(viewFieldsState);
|
const viewFields = useRecoilValue(visibleViewFieldsState);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
|
<StyledRow data-testid={`row-id-${rowId}`} selected={false}>
|
||||||
@ -22,10 +22,7 @@ export function EntityTableRow({ rowId }: { rowId: string }) {
|
|||||||
</td>
|
</td>
|
||||||
{viewFields.map((viewField, columnIndex) => {
|
{viewFields.map((viewField, columnIndex) => {
|
||||||
return (
|
return (
|
||||||
<ViewFieldContext.Provider
|
<ViewFieldContext.Provider value={viewField} key={viewField.id}>
|
||||||
value={viewField}
|
|
||||||
key={viewField.columnOrder}
|
|
||||||
>
|
|
||||||
<EntityTableCell cellIndex={columnIndex} />
|
<EntityTableCell cellIndex={columnIndex} />
|
||||||
</ViewFieldContext.Provider>
|
</ViewFieldContext.Provider>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export const toViewFieldInput = (
|
|||||||
) => ({
|
) => ({
|
||||||
fieldName: viewFieldDefinition.columnLabel,
|
fieldName: viewFieldDefinition.columnLabel,
|
||||||
index: viewFieldDefinition.columnOrder,
|
index: viewFieldDefinition.columnOrder,
|
||||||
isVisible: true,
|
isVisible: viewFieldDefinition.isVisible ?? true,
|
||||||
objectName,
|
objectName,
|
||||||
sizeInPx: viewFieldDefinition.columnSize,
|
sizeInPx: viewFieldDefinition.columnSize,
|
||||||
});
|
});
|
||||||
@ -64,6 +64,7 @@ export const useLoadView = ({
|
|||||||
columnLabel: viewField.fieldName,
|
columnLabel: viewField.fieldName,
|
||||||
columnOrder: viewField.index,
|
columnOrder: viewField.index,
|
||||||
columnSize: viewField.sizeInPx,
|
columnSize: viewField.sizeInPx,
|
||||||
|
isVisible: viewField.isVisible,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setViewFieldsState({ objectName, viewFields });
|
setViewFieldsState({ objectName, viewFields });
|
||||||
|
|||||||
@ -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),
|
||||||
|
});
|
||||||
|
|||||||
@ -7,6 +7,7 @@ import { SortDropdownButton } from '@/ui/filter-n-sort/components/SortDropdownBu
|
|||||||
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
import { FiltersHotkeyScope } from '@/ui/filter-n-sort/types/FiltersHotkeyScope';
|
||||||
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
import { SelectedSortType, SortType } from '@/ui/filter-n-sort/types/interface';
|
||||||
import { TopBar } from '@/ui/top-bar/TopBar';
|
import { TopBar } from '@/ui/top-bar/TopBar';
|
||||||
|
import { OptionsDropdownButton } from '@/views/components/OptionsDropdownButton';
|
||||||
|
|
||||||
import { TableContext } from '../../states/TableContext';
|
import { TableContext } from '../../states/TableContext';
|
||||||
|
|
||||||
@ -76,6 +77,9 @@ export function TableHeader<SortField>({
|
|||||||
onSortSelect={sortSelect}
|
onSortSelect={sortSelect}
|
||||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||||
/>
|
/>
|
||||||
|
<OptionsDropdownButton
|
||||||
|
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
bottomComponent={
|
bottomComponent={
|
||||||
|
|||||||
@ -91,6 +91,7 @@ export type ViewFieldDefinition<T extends ViewFieldMetadata | unknown> = {
|
|||||||
columnOrder: number;
|
columnOrder: number;
|
||||||
columnIcon?: JSX.Element;
|
columnIcon?: JSX.Element;
|
||||||
filterIcon?: JSX.Element;
|
filterIcon?: JSX.Element;
|
||||||
|
isVisible?: boolean;
|
||||||
metadata: T;
|
metadata: T;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
137
front/src/modules/views/components/OptionsDropdownButton.tsx
Normal file
137
front/src/modules/views/components/OptionsDropdownButton.tsx
Normal file
@ -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<Option | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const visibleFields = useRecoilValue(visibleViewFieldsState);
|
||||||
|
const hiddenFields = useRecoilValue(hiddenViewFieldsState);
|
||||||
|
|
||||||
|
const [updateViewFieldMutation] = useUpdateViewFieldMutation();
|
||||||
|
|
||||||
|
const handleViewFieldVisibilityChange = useCallback(
|
||||||
|
(viewFieldId: string, nextIsVisible: boolean) => {
|
||||||
|
updateViewFieldMutation({
|
||||||
|
variables: {
|
||||||
|
data: { isVisible: nextIsVisible },
|
||||||
|
where: { id: viewFieldId },
|
||||||
|
},
|
||||||
|
refetchQueries: [getOperationName(GET_VIEW_FIELDS) ?? ''],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[updateViewFieldMutation],
|
||||||
|
);
|
||||||
|
|
||||||
|
const renderFieldActions = useCallback(
|
||||||
|
(viewField: ViewFieldDefinition<ViewFieldMetadata>) =>
|
||||||
|
// Do not allow hiding last visible column
|
||||||
|
!viewField.isVisible || visibleFields.length > 1 ? (
|
||||||
|
<IconButton
|
||||||
|
icon={
|
||||||
|
viewField.isVisible ? (
|
||||||
|
<IconMinus size={theme.icon.size.sm} />
|
||||||
|
) : (
|
||||||
|
<IconPlus size={theme.icon.size.sm} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onClick={() =>
|
||||||
|
handleViewFieldVisibilityChange(viewField.id, !viewField.isVisible)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : undefined,
|
||||||
|
[handleViewFieldVisibilityChange, theme.icon.size.sm, visibleFields.length],
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetSelectedOption = useCallback(() => {
|
||||||
|
setSelectedOption(undefined);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownButton
|
||||||
|
label="Options"
|
||||||
|
isActive={false}
|
||||||
|
isUnfolded={isUnfolded}
|
||||||
|
onIsUnfoldedChange={setIsUnfolded}
|
||||||
|
HotkeyScope={HotkeyScope}
|
||||||
|
>
|
||||||
|
{!selectedOption && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuHeader>View settings</DropdownMenuHeader>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => setSelectedOption(Option.Properties)}
|
||||||
|
>
|
||||||
|
<IconTag size={theme.icon.size.md} />
|
||||||
|
Properties
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{selectedOption === Option.Properties && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuHeader
|
||||||
|
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
|
||||||
|
onClick={resetSelectedOption}
|
||||||
|
>
|
||||||
|
Properties
|
||||||
|
</DropdownMenuHeader>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<OptionsDropdownSection
|
||||||
|
renderActions={renderFieldActions}
|
||||||
|
title="Visible"
|
||||||
|
viewFields={visibleFields}
|
||||||
|
/>
|
||||||
|
{hiddenFields.length > 0 && (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSeparator />
|
||||||
|
<OptionsDropdownSection
|
||||||
|
renderActions={renderFieldActions}
|
||||||
|
title="Hidden"
|
||||||
|
viewFields={hiddenFields}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</DropdownButton>
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
import { cloneElement } from 'react';
|
||||||
|
import { useTheme } from '@emotion/react';
|
||||||
|
|
||||||
|
import {
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuItemProps,
|
||||||
|
} from '@/ui/dropdown/components/DropdownMenuItem';
|
||||||
|
import { DropdownMenuItemsContainer } from '@/ui/dropdown/components/DropdownMenuItemsContainer';
|
||||||
|
import { DropdownMenuSubheader } from '@/ui/dropdown/components/DropdownMenuSubheader';
|
||||||
|
import {
|
||||||
|
ViewFieldDefinition,
|
||||||
|
ViewFieldMetadata,
|
||||||
|
} from '@/ui/table/types/ViewField';
|
||||||
|
|
||||||
|
type OptionsDropdownSectionProps = {
|
||||||
|
renderActions: (
|
||||||
|
viewField: ViewFieldDefinition<ViewFieldMetadata>,
|
||||||
|
) => DropdownMenuItemProps['actions'];
|
||||||
|
title: string;
|
||||||
|
viewFields: ViewFieldDefinition<ViewFieldMetadata>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OptionsDropdownSection = ({
|
||||||
|
renderActions,
|
||||||
|
title,
|
||||||
|
viewFields,
|
||||||
|
}: OptionsDropdownSectionProps) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<DropdownMenuSubheader>{title}</DropdownMenuSubheader>
|
||||||
|
<DropdownMenuItemsContainer>
|
||||||
|
{viewFields.map((viewField) => (
|
||||||
|
<DropdownMenuItem actions={renderActions(viewField)}>
|
||||||
|
{viewField.columnIcon &&
|
||||||
|
cloneElement(viewField.columnIcon, {
|
||||||
|
size: theme.icon.size.md,
|
||||||
|
})}
|
||||||
|
{viewField.columnLabel}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
))}
|
||||||
|
</DropdownMenuItemsContainer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user