Refactor/new menu item (#1448)

* wip

* finished

* Added disabled

* Fixed disabled

* Finished cleaning

* Minor fixes from merge

* Added docs

* Added PascalCase

* Fix from review

* Fixes from merge

* Fix lint

* Fixed storybook tests
This commit is contained in:
Lucas Bordeau
2023-09-06 16:41:26 +02:00
committed by GitHub
parent 5c7660f588
commit 28ca9a9e49
96 changed files with 816 additions and 918 deletions

View File

@ -1,9 +1,11 @@
import { ReactNode } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { IconComponent } from '@/ui/icon/types/IconComponent';
type OwnProps = {
viewName: string;
viewIcon?: ReactNode;
ViewIcon?: IconComponent;
};
const StyledTitle = styled.div`
@ -32,10 +34,13 @@ const StyledText = styled.span`
white-space: nowrap;
`;
export function ColumnHead({ viewName, viewIcon }: OwnProps) {
export function ColumnHead({ viewName, ViewIcon }: OwnProps) {
const theme = useTheme();
return (
<StyledTitle>
<StyledIcon>{viewIcon}</StyledIcon>
<StyledIcon>
{ViewIcon && <ViewIcon size={theme.icon.size.md} />}
</StyledIcon>
<StyledText>{viewName}</StyledText>
</StyledTitle>
);

View File

@ -1,13 +1,11 @@
import { cloneElement, type ComponentProps, useCallback, useRef } from 'react';
import { useTheme } from '@emotion/react';
import { type ComponentProps, useCallback, useRef } from 'react';
import styled from '@emotion/styled';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { IconPlus } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useListenClickOutside } from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@ -31,7 +29,6 @@ export const EntityTableColumnMenu = ({
...props
}: EntityTableColumnMenuProps) => {
const ref = useRef<HTMLDivElement>(null);
const theme = useTheme();
const hiddenTableColumns = useRecoilScopedValue(
hiddenTableColumnsScopedSelector,
@ -57,22 +54,17 @@ export const EntityTableColumnMenu = ({
<StyledColumnMenu {...props} ref={ref}>
<StyledDropdownMenuItemsContainer>
{hiddenTableColumns.map((column) => (
<DropdownMenuItem
<MenuItem
key={column.key}
actions={[
<IconButton
key={`add-${column.key}`}
icon={<IconPlus size={theme.icon.size.sm} />}
onClick={() => handleAddColumn(column)}
/>,
iconButtons={[
{
Icon: IconPlus,
onClick: () => handleAddColumn(column),
},
]}
>
{column.icon &&
cloneElement(column.icon, {
size: theme.icon.size.md,
})}
{column.name}
</DropdownMenuItem>
LeftIcon={column.Icon}
text={column.name}
/>
))}
</StyledDropdownMenuItemsContainer>
</StyledColumnMenu>

View File

@ -173,7 +173,7 @@ export function EntityTableHeader() {
COLUMN_MIN_WIDTH,
)}
>
<ColumnHead viewName={column.name} viewIcon={column.icon} />
<ColumnHead viewName={column.name} ViewIcon={column.Icon} />
<StyledResizeHandler
className="cursor-col-resize"
role="separator"

View File

@ -1,35 +1,25 @@
import { type FormEvent, useCallback, useRef, useState } from 'react';
import { useTheme } from '@emotion/react';
import { useRecoilCallback, useRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { v4 } from 'uuid';
import { IconButton } from '@/ui/button/components/IconButton';
import { DropdownMenuHeader } from '@/ui/dropdown/components/DropdownMenuHeader';
import { DropdownMenuInput } from '@/ui/dropdown/components/DropdownMenuInput';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenu } from '@/ui/dropdown/components/StyledDropdownMenu';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import type { ViewFieldMetadata } from '@/ui/editable-field/types/ViewField';
import { filtersScopedState } from '@/ui/filter-n-sort/states/filtersScopedState';
import { savedFiltersScopedState } from '@/ui/filter-n-sort/states/savedFiltersScopedState';
import { savedSortsScopedState } from '@/ui/filter-n-sort/states/savedSortsScopedState';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import {
IconChevronLeft,
IconFileImport,
IconMinus,
IconPlus,
IconTag,
} from '@/ui/icon';
import { IconChevronLeft, IconFileImport, IconTag } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { tableColumnsScopedState } from '@/ui/table/states/tableColumnsScopedState';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
import { useTableColumns } from '../../hooks/useTableColumns';
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
import { savedTableColumnsScopedState } from '../../states/savedTableColumnsScopedState';
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
@ -41,10 +31,9 @@ import {
tableViewsByIdState,
tableViewsState,
} from '../../states/tableViewsState';
import type { ColumnDefinition } from '../../types/ColumnDefinition';
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
import { TableOptionsDropdownSection } from './TableOptionsDropdownSection';
import { TableOptionsDropdownColumnVisibility } from './TableOptionsDropdownSection';
type TableOptionsDropdownButtonProps = {
onViewsChange?: (views: TableView[]) => void;
@ -59,8 +48,6 @@ export function TableOptionsDropdownContent({
onViewsChange,
onImport,
}: TableOptionsDropdownButtonProps) {
const theme = useTheme();
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
const { closeDropdownButton } = useDropdownButton({ key: 'options' });
@ -87,33 +74,6 @@ export function TableOptionsDropdownContent({
TableRecoilScopeContext,
);
const { handleColumnVisibilityChange } = useTableColumns();
const renderFieldActions = useCallback(
(column: ColumnDefinition<ViewFieldMetadata>) =>
// Do not allow hiding last visible column
!column.isVisible || visibleTableColumns.length > 1
? [
<IconButton
key={`action-${column.key}`}
icon={
column.isVisible ? (
<IconMinus size={theme.icon.size.sm} />
) : (
<IconPlus size={theme.icon.size.sm} />
)
}
onClick={() => handleColumnVisibilityChange(column)}
/>,
]
: undefined,
[
handleColumnVisibilityChange,
theme.icon.size.sm,
visibleTableColumns.length,
],
);
const resetViewEditMode = useCallback(() => {
setTableViewEditMode({ mode: undefined, viewId: undefined });
@ -232,17 +192,17 @@ export function TableOptionsDropdownContent({
)}
<StyledDropdownMenuSeparator />
<StyledDropdownMenuItemsContainer>
<DropdownMenuItem
<MenuItem
onClick={() => handleSelectOption(Option.Properties)}
>
<IconTag size={theme.icon.size.md} />
Properties
</DropdownMenuItem>
LeftIcon={IconTag}
text="Properties"
/>
{onImport && (
<DropdownMenuItem onClick={onImport}>
<IconFileImport size={theme.icon.size.md} />
Import
</DropdownMenuItem>
<MenuItem
onClick={onImport}
LeftIcon={IconFileImport}
text="Import"
/>
)}
</StyledDropdownMenuItemsContainer>
</>
@ -250,22 +210,20 @@ export function TableOptionsDropdownContent({
{selectedOption === Option.Properties && (
<>
<DropdownMenuHeader
startIcon={<IconChevronLeft size={theme.icon.size.md} />}
StartIcon={IconChevronLeft}
onClick={resetSelectedOption}
>
Properties
</DropdownMenuHeader>
<StyledDropdownMenuSeparator />
<TableOptionsDropdownSection
renderActions={renderFieldActions}
<TableOptionsDropdownColumnVisibility
title="Visible"
columns={visibleTableColumns}
/>
{hiddenTableColumns.length > 0 && (
<>
<StyledDropdownMenuSeparator />
<TableOptionsDropdownSection
renderActions={renderFieldActions}
<TableOptionsDropdownColumnVisibility
title="Hidden"
columns={hiddenTableColumns}
/>

View File

@ -1,43 +1,39 @@
import { cloneElement } from 'react';
import { useTheme } from '@emotion/react';
import {
DropdownMenuItem,
DropdownMenuItemProps,
} from '@/ui/dropdown/components/DropdownMenuItem';
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 TableOptionsDropdownSectionProps = {
renderActions: (
column: ColumnDefinition<ViewFieldMetadata>,
) => DropdownMenuItemProps['actions'];
type OwnProps = {
title: string;
columns: ColumnDefinition<ViewFieldMetadata>[];
};
export function TableOptionsDropdownSection({
renderActions,
export function TableOptionsDropdownColumnVisibility({
title,
columns,
}: TableOptionsDropdownSectionProps) {
const theme = useTheme();
}: OwnProps) {
const { handleColumnVisibilityChange } = useTableColumns();
return (
<>
<StyledDropdownMenuSubheader>{title}</StyledDropdownMenuSubheader>
<StyledDropdownMenuItemsContainer>
{columns.map((column) => (
<DropdownMenuItem key={column.key} actions={renderActions(column)}>
{column.icon &&
cloneElement(column.icon, {
size: theme.icon.size.md,
})}
{column.name}
</DropdownMenuItem>
<MenuItem
key={column.key}
LeftIcon={column.Icon}
iconButtons={[
{
Icon: column.isVisible ? IconMinus : IconPlus,
onClick: () => handleColumnVisibilityChange(column),
},
]}
text={column.name}
/>
))}
</StyledDropdownMenuItemsContainer>
</>

View File

@ -1,12 +1,10 @@
import { useCallback, useState } from 'react';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilValue, useSetRecoilState } from 'recoil';
import { Key } from 'ts-key-enum';
import { Button } from '@/ui/button/components/Button';
import { ButtonGroup } from '@/ui/button/components/ButtonGroup';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
import { DropdownMenuContainer } from '@/ui/filter-n-sort/components/DropdownMenuContainer';
@ -17,6 +15,7 @@ import { canPersistFiltersScopedSelector } from '@/ui/filter-n-sort/states/selec
import { canPersistSortsScopedSelector } from '@/ui/filter-n-sort/states/selectors/canPersistSortsScopedSelector';
import { sortsScopedState } from '@/ui/filter-n-sort/states/sortsScopedState';
import { IconChevronDown, IconPlus } from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import { useScopedHotkeys } from '@/ui/utilities/hotkey/hooks/useScopedHotkeys';
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
@ -45,8 +44,6 @@ export const TableUpdateViewButtonGroup = ({
onViewSubmit,
HotkeyScope,
}: TableUpdateViewButtonGroupProps) => {
const theme = useTheme();
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
@ -153,10 +150,11 @@ export const TableUpdateViewButtonGroup = ({
{isDropdownOpen && (
<DropdownMenuContainer onClose={handleDropdownClose}>
<StyledDropdownMenuItemsContainer>
<DropdownMenuItem onClick={handleCreateViewButtonClick}>
<IconPlus size={theme.icon.size.md} />
Create view
</DropdownMenuItem>
<MenuItem
onClick={handleCreateViewButtonClick}
LeftIcon={IconPlus}
text="Create view"
/>
</StyledDropdownMenuItemsContainer>
</DropdownMenuContainer>
)}

View File

@ -3,8 +3,6 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
import { useRecoilCallback, useSetRecoilState } from 'recoil';
import { FloatingIconButton } from '@/ui/button/components/FloatingIconButton';
import { DropdownMenuItem } from '@/ui/dropdown/components/DropdownMenuItem';
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
@ -20,6 +18,7 @@ import {
IconPlus,
IconTrash,
} from '@/ui/icon';
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
import {
currentTableViewIdState,
currentTableViewState,
@ -204,37 +203,35 @@ export const TableViewsDropdownButton = ({
>
<StyledDropdownMenuItemsContainer>
{tableViews.map((view) => (
<DropdownMenuItem
<MenuItem
key={view.id}
actions={[
<FloatingIconButton
key="edit"
onClick={(event) => handleEditViewButtonClick(event, view.id)}
icon={<IconPencil size={theme.icon.size.sm} />}
/>,
tableViews.length > 1 ? (
<FloatingIconButton
key="delete"
onClick={(event) =>
handleDeleteViewButtonClick(event, view.id)
iconButtons={[
{
Icon: IconPencil,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleEditViewButtonClick(event, view.id),
},
tableViews.length > 1
? {
Icon: IconTrash,
onClick: (event: MouseEvent<HTMLButtonElement>) =>
handleDeleteViewButtonClick(event, view.id),
}
icon={<IconTrash size={theme.icon.size.sm} />}
/>
) : null,
: null,
].filter(assertNotNull)}
onClick={() => handleViewSelect(view.id)}
>
<IconList size={theme.icon.size.md} />
<StyledViewName>{view.name}</StyledViewName>
</DropdownMenuItem>
LeftIcon={IconList}
text={view.name}
/>
))}
</StyledDropdownMenuItemsContainer>
<StyledDropdownMenuSeparator />
<StyledBoldDropdownMenuItemsContainer>
<DropdownMenuItem onClick={handleAddViewButtonClick}>
<IconPlus size={theme.icon.size.md} />
Add view
</DropdownMenuItem>
<MenuItem
onClick={handleAddViewButtonClick}
LeftIcon={IconPlus}
text="Add view"
/>
</StyledBoldDropdownMenuItemsContainer>
</DropdownButton>
);