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:
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user