feat: show/hide table columns (#1078)

Closes #813
This commit is contained in:
Thaïs
2023-08-04 19:44:46 +02:00
committed by GitHub
parent 417ca3d131
commit c6bec40c90
20 changed files with 434 additions and 147 deletions

View File

@ -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>
);

View File

@ -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) => (
<StyledItem {...props}>
{children}
{actions && (
<StyledActions
className={styledIconButtonGroupClassName}
variant="transparent"
size="small"
>
{actions}
</StyledActions>
)}
</StyledItem>
);

View File

@ -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%;
`;

View File

@ -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<typeof DropdownMenu> = {
title: 'UI/Dropdown/DropdownMenu',
@ -59,47 +62,36 @@ const MenuAbsolutePositionWrapper = styled.div`
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 = [
{
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) => (
<DropdownMenu {...args}>
<DropdownMenuItemsContainer hasMaxHeight>
<FakeMenuItemList />
{mockSelectArray.map(({ name }) => (
<DropdownMenuItem>{name}</DropdownMenuItem>
))}
</DropdownMenuItemsContainer>
</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 = {
...WithContentBelow,
render: () => (
@ -215,25 +272,9 @@ export const Search: Story = {
<DropdownMenuSearch />
<DropdownMenuSeparator />
<DropdownMenuItemsContainer hasMaxHeight>
<FakeMenuItemList />
</DropdownMenuItemsContainer>
</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 />
{mockSelectArray.map(({ name }) => (
<DropdownMenuItem>{name}</DropdownMenuItem>
))}
</DropdownMenuItemsContainer>
</DropdownMenu>
),