refactor: add ViewBar and move view components to ui/view-bar (#1495)
Closes #1494
This commit is contained in:
@ -8,15 +8,16 @@ import {
|
||||
useListenClickOutsideByClassName,
|
||||
} from '@/ui/utilities/pointer-event/hooks/useListenClickOutside';
|
||||
import { ScrollWrapper } from '@/ui/utilities/scroll/components/ScrollWrapper';
|
||||
import { SortType } from '@/ui/view-bar/types/interface';
|
||||
import type { View } from '@/ui/view-bar/types/View';
|
||||
|
||||
import { EntityUpdateMutationContext } from '../contexts/EntityUpdateMutationHookContext';
|
||||
import { useLeaveTableFocus } from '../hooks/useLeaveTableFocus';
|
||||
import { useMapKeyboardToSoftFocus } from '../hooks/useMapKeyboardToSoftFocus';
|
||||
import { useResetTableRowSelection } from '../hooks/useResetTableRowSelection';
|
||||
import { useSetRowSelectedState } from '../hooks/useSetRowSelectedState';
|
||||
import { TableHeader } from '../table-header/components/TableHeader';
|
||||
import {
|
||||
TableHeader,
|
||||
type TableHeaderProps,
|
||||
} from '../table-header/components/TableHeader';
|
||||
import { TableHotkeyScope } from '../types/TableHotkeyScope';
|
||||
|
||||
import { EntityTableBody } from './EntityTableBody';
|
||||
@ -85,21 +86,22 @@ const StyledTableContainer = styled.div`
|
||||
`;
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
viewIcon?: React.ReactNode;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onViewsChange?: (views: View[]) => void;
|
||||
onViewSubmit?: () => void;
|
||||
onImport?: () => void;
|
||||
updateEntityMutation: any;
|
||||
};
|
||||
} & Pick<
|
||||
TableHeaderProps<SortField>,
|
||||
| 'availableSorts'
|
||||
| 'defaultViewName'
|
||||
| 'onImport'
|
||||
| 'onViewsChange'
|
||||
| 'onViewSubmit'
|
||||
>;
|
||||
|
||||
export function EntityTable<SortField>({
|
||||
viewName,
|
||||
availableSorts,
|
||||
defaultViewName,
|
||||
onImport,
|
||||
onViewsChange,
|
||||
onViewSubmit,
|
||||
onImport,
|
||||
updateEntityMutation,
|
||||
}: OwnProps<SortField>) {
|
||||
const tableBodyRef = useRef<HTMLDivElement>(null);
|
||||
@ -139,11 +141,11 @@ export function EntityTable<SortField>({
|
||||
<StyledTableWithHeader>
|
||||
<StyledTableContainer ref={tableBodyRef}>
|
||||
<TableHeader
|
||||
viewName={viewName}
|
||||
availableSorts={availableSorts}
|
||||
availableSorts={availableSorts ?? []}
|
||||
defaultViewName={defaultViewName}
|
||||
onImport={onImport}
|
||||
onViewsChange={onViewsChange}
|
||||
onViewSubmit={onViewSubmit}
|
||||
onImport={onImport}
|
||||
/>
|
||||
<ScrollWrapper>
|
||||
<div>
|
||||
|
||||
@ -2,6 +2,8 @@ import { DropdownButton } from '@/ui/dropdown/components/DropdownButton';
|
||||
import { HotkeyScope } from '@/ui/utilities/hotkey/types/HotkeyScope';
|
||||
import type { View } from '@/ui/view-bar/types/View';
|
||||
|
||||
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
|
||||
|
||||
import { TableOptionsDropdownButton } from './TableOptionsDropdownButton';
|
||||
import { TableOptionsDropdownContent } from './TableOptionsDropdownContent';
|
||||
|
||||
@ -20,7 +22,7 @@ export function TableOptionsDropdown({
|
||||
<DropdownButton
|
||||
buttonComponents={<TableOptionsDropdownButton />}
|
||||
dropdownHotkeyScope={customHotkeyScope}
|
||||
dropdownKey="options"
|
||||
dropdownKey={TableOptionsDropdownKey}
|
||||
dropdownComponents={
|
||||
<TableOptionsDropdownContent
|
||||
onImport={onImport}
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
import { StyledHeaderDropdownButton } from '@/ui/dropdown/components/StyledHeaderDropdownButton';
|
||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||
|
||||
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
|
||||
|
||||
export function TableOptionsDropdownButton() {
|
||||
const { isDropdownButtonOpen, toggleDropdownButton } = useDropdownButton({
|
||||
key: 'options',
|
||||
key: TableOptionsDropdownKey,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -29,6 +29,7 @@ import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/Tabl
|
||||
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
|
||||
import { hiddenTableColumnsScopedSelector } from '../../states/selectors/hiddenTableColumnsScopedSelector';
|
||||
import { visibleTableColumnsScopedSelector } from '../../states/selectors/visibleTableColumnsScopedSelector';
|
||||
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
|
||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||
|
||||
import { TableOptionsDropdownColumnVisibility } from './TableOptionsDropdownSection';
|
||||
@ -48,7 +49,9 @@ export function TableOptionsDropdownContent({
|
||||
}: TableOptionsDropdownButtonProps) {
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const { closeDropdownButton } = useDropdownButton({ key: 'options' });
|
||||
const { closeDropdownButton } = useDropdownButton({
|
||||
key: TableOptionsDropdownKey,
|
||||
});
|
||||
|
||||
const [selectedOption, setSelectedOption] = useState<Option | undefined>(
|
||||
undefined,
|
||||
|
||||
@ -1,159 +0,0 @@
|
||||
import { useCallback, useState } from '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 { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||
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';
|
||||
import { DropdownMenuContainer } from '@/ui/view-bar/components/DropdownMenuContainer';
|
||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
|
||||
import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';
|
||||
import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState';
|
||||
import { canPersistFiltersScopedFamilySelector } from '@/ui/view-bar/states/selectors/canPersistFiltersScopedFamilySelector';
|
||||
import { canPersistSortsScopedFamilySelector } from '@/ui/view-bar/states/selectors/canPersistSortsScopedFamilySelector';
|
||||
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState';
|
||||
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
|
||||
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
|
||||
import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector';
|
||||
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: inline-flex;
|
||||
margin-right: ${({ theme }) => theme.spacing(2)};
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
type TableUpdateViewButtonGroupProps = {
|
||||
onViewSubmit?: () => void;
|
||||
HotkeyScope: string;
|
||||
};
|
||||
|
||||
export const TableUpdateViewButtonGroup = ({
|
||||
onViewSubmit,
|
||||
HotkeyScope,
|
||||
}: TableUpdateViewButtonGroupProps) => {
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentViewIdScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
|
||||
const tableColumns = useRecoilScopedValue(
|
||||
tableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setSavedColumns = useSetRecoilState(
|
||||
savedTableColumnsFamilyState(currentViewId),
|
||||
);
|
||||
const canPersistColumns = useRecoilValue(
|
||||
canPersistTableColumnsScopedFamilySelector([tableScopeId, currentViewId]),
|
||||
);
|
||||
|
||||
const filters = useRecoilScopedValue(
|
||||
filtersScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setSavedFilters = useSetRecoilState(
|
||||
savedFiltersFamilyState(currentViewId),
|
||||
);
|
||||
const canPersistFilters = useRecoilValue(
|
||||
canPersistFiltersScopedFamilySelector([tableScopeId, currentViewId]),
|
||||
);
|
||||
|
||||
const sorts = useRecoilScopedValue(sortsScopedState, TableRecoilScopeContext);
|
||||
const setSavedSorts = useSetRecoilState(savedSortsFamilyState(currentViewId));
|
||||
const canPersistSorts = useRecoilValue(
|
||||
canPersistSortsScopedFamilySelector([tableScopeId, currentViewId]),
|
||||
);
|
||||
|
||||
const setViewEditMode = useSetRecoilState(viewEditModeState);
|
||||
|
||||
const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({
|
||||
key: 'options',
|
||||
});
|
||||
|
||||
const handleArrowDownButtonClick = useCallback(() => {
|
||||
setIsDropdownOpen((previousIsOpen) => !previousIsOpen);
|
||||
}, []);
|
||||
|
||||
const handleCreateViewButtonClick = useCallback(() => {
|
||||
setViewEditMode({ mode: 'create', viewId: undefined });
|
||||
openOptionsDropdownButton();
|
||||
setIsDropdownOpen(false);
|
||||
}, [setViewEditMode, openOptionsDropdownButton]);
|
||||
|
||||
const handleDropdownClose = useCallback(() => {
|
||||
setIsDropdownOpen(false);
|
||||
}, []);
|
||||
|
||||
const handleViewSubmit = useCallback(async () => {
|
||||
if (canPersistColumns) setSavedColumns(tableColumns);
|
||||
if (canPersistFilters) setSavedFilters(filters);
|
||||
if (canPersistSorts) setSavedSorts(sorts);
|
||||
|
||||
await Promise.resolve(onViewSubmit?.());
|
||||
}, [
|
||||
canPersistColumns,
|
||||
canPersistFilters,
|
||||
canPersistSorts,
|
||||
filters,
|
||||
onViewSubmit,
|
||||
setSavedColumns,
|
||||
setSavedFilters,
|
||||
setSavedSorts,
|
||||
sorts,
|
||||
tableColumns,
|
||||
]);
|
||||
|
||||
useScopedHotkeys(
|
||||
[Key.Enter, Key.Escape],
|
||||
handleDropdownClose,
|
||||
HotkeyScope,
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledContainer>
|
||||
<ButtonGroup size="small" accent="blue">
|
||||
<Button
|
||||
title="Update view"
|
||||
disabled={
|
||||
!currentViewId ||
|
||||
(!canPersistColumns && !canPersistFilters && !canPersistSorts)
|
||||
}
|
||||
onClick={handleViewSubmit}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
icon={<IconChevronDown />}
|
||||
onClick={handleArrowDownButtonClick}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
|
||||
{isDropdownOpen && (
|
||||
<DropdownMenuContainer onClose={handleDropdownClose}>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleCreateViewButtonClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Create view"
|
||||
/>
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
</DropdownMenuContainer>
|
||||
)}
|
||||
</StyledContainer>
|
||||
);
|
||||
};
|
||||
@ -1,229 +0,0 @@
|
||||
import { type MouseEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { StyledDropdownMenuItemsContainer } from '@/ui/dropdown/components/StyledDropdownMenuItemsContainer';
|
||||
import { StyledDropdownMenuSeparator } from '@/ui/dropdown/components/StyledDropdownMenuSeparator';
|
||||
import { useDropdownButton } from '@/ui/dropdown/hooks/useDropdownButton';
|
||||
import {
|
||||
IconChevronDown,
|
||||
IconList,
|
||||
IconPencil,
|
||||
IconPlus,
|
||||
IconTrash,
|
||||
} from '@/ui/icon';
|
||||
import { MenuItem } from '@/ui/menu-item/components/MenuItem';
|
||||
import { MOBILE_VIEWPORT } from '@/ui/theme/constants/theme';
|
||||
import { usePreviousHotkeyScope } from '@/ui/utilities/hotkey/hooks/usePreviousHotkeyScope';
|
||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import DropdownButton from '@/ui/view-bar/components/DropdownButton';
|
||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||
import { filtersScopedState } from '@/ui/view-bar/states/filtersScopedState';
|
||||
import { savedFiltersFamilyState } from '@/ui/view-bar/states/savedFiltersFamilyState';
|
||||
import { savedSortsFamilyState } from '@/ui/view-bar/states/savedSortsFamilyState';
|
||||
import { currentViewScopedSelector } from '@/ui/view-bar/states/selectors/currentViewScopedSelector';
|
||||
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState';
|
||||
import { viewEditModeState } from '@/ui/view-bar/states/viewEditModeState';
|
||||
import { viewsScopedState } from '@/ui/view-bar/states/viewsScopedState';
|
||||
import type { View } from '@/ui/view-bar/types/View';
|
||||
import { ViewsHotkeyScope } from '@/ui/view-bar/types/ViewsHotkeyScope';
|
||||
import { assertNotNull } from '~/utils/assert';
|
||||
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
|
||||
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
|
||||
|
||||
const StyledBoldDropdownMenuItemsContainer = styled(
|
||||
StyledDropdownMenuItemsContainer,
|
||||
)`
|
||||
font-weight: ${({ theme }) => theme.font.weight.regular};
|
||||
`;
|
||||
|
||||
const StyledDropdownLabelAdornments = styled.span`
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.grayScale.gray35};
|
||||
display: inline-flex;
|
||||
gap: ${({ theme }) => theme.spacing(1)};
|
||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledViewIcon = styled(IconList)`
|
||||
margin-right: ${({ theme }) => theme.spacing(1)};
|
||||
`;
|
||||
|
||||
const StyledViewName = styled.span`
|
||||
display: inline-block;
|
||||
max-width: 200px;
|
||||
@media (max-width: 375px) {
|
||||
max-width: 90px;
|
||||
}
|
||||
@media (min-width: 376px) and (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
max-width: 110px;
|
||||
}
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: middle;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
type TableViewsDropdownButtonProps = {
|
||||
defaultViewName: string;
|
||||
HotkeyScope: ViewsHotkeyScope;
|
||||
onViewsChange?: (views: View[]) => void;
|
||||
};
|
||||
|
||||
export const TableViewsDropdownButton = ({
|
||||
defaultViewName,
|
||||
HotkeyScope,
|
||||
onViewsChange,
|
||||
}: TableViewsDropdownButtonProps) => {
|
||||
const theme = useTheme();
|
||||
const [isUnfolded, setIsUnfolded] = useState(false);
|
||||
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const { openDropdownButton: openOptionsDropdownButton } = useDropdownButton({
|
||||
key: 'options',
|
||||
});
|
||||
|
||||
const [, setCurrentViewId] = useRecoilScopedState(
|
||||
currentViewIdScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const currentView = useRecoilScopedValue(
|
||||
currentViewScopedSelector,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [views, setViews] = useRecoilScopedState(
|
||||
viewsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setViewEditMode = useSetRecoilState(viewEditModeState);
|
||||
|
||||
const {
|
||||
goBackToPreviousHotkeyScope,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
} = usePreviousHotkeyScope();
|
||||
|
||||
const handleViewSelect = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (viewId: string) => {
|
||||
const savedColumns = await snapshot.getPromise(
|
||||
savedTableColumnsFamilyState(viewId),
|
||||
);
|
||||
const savedFilters = await snapshot.getPromise(
|
||||
savedFiltersFamilyState(viewId),
|
||||
);
|
||||
const savedSorts = await snapshot.getPromise(
|
||||
savedSortsFamilyState(viewId),
|
||||
);
|
||||
|
||||
set(tableColumnsScopedState(tableScopeId), savedColumns);
|
||||
set(filtersScopedState(tableScopeId), savedFilters);
|
||||
set(sortsScopedState(tableScopeId), savedSorts);
|
||||
set(currentViewIdScopedState(tableScopeId), viewId);
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[tableScopeId],
|
||||
);
|
||||
|
||||
const handleAddViewButtonClick = useCallback(() => {
|
||||
setViewEditMode({ mode: 'create', viewId: undefined });
|
||||
openOptionsDropdownButton();
|
||||
setIsUnfolded(false);
|
||||
}, [setViewEditMode, openOptionsDropdownButton]);
|
||||
|
||||
const handleEditViewButtonClick = useCallback(
|
||||
(event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
||||
event.stopPropagation();
|
||||
setViewEditMode({ mode: 'edit', viewId });
|
||||
openOptionsDropdownButton();
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[setViewEditMode, openOptionsDropdownButton],
|
||||
);
|
||||
|
||||
const handleDeleteViewButtonClick = useCallback(
|
||||
async (event: MouseEvent<HTMLButtonElement>, viewId: string) => {
|
||||
event.stopPropagation();
|
||||
|
||||
if (currentView?.id === viewId) setCurrentViewId(undefined);
|
||||
|
||||
const nextViews = views.filter((view) => view.id !== viewId);
|
||||
|
||||
setViews(nextViews);
|
||||
await Promise.resolve(onViewsChange?.(nextViews));
|
||||
setIsUnfolded(false);
|
||||
},
|
||||
[currentView?.id, onViewsChange, setCurrentViewId, setViews, views],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
isUnfolded
|
||||
? setHotkeyScopeAndMemorizePreviousScope(HotkeyScope)
|
||||
: goBackToPreviousHotkeyScope();
|
||||
}, [
|
||||
HotkeyScope,
|
||||
goBackToPreviousHotkeyScope,
|
||||
isUnfolded,
|
||||
setHotkeyScopeAndMemorizePreviousScope,
|
||||
]);
|
||||
|
||||
return (
|
||||
<DropdownButton
|
||||
label={
|
||||
<>
|
||||
<StyledViewIcon size={theme.icon.size.md} />
|
||||
<StyledViewName>
|
||||
{currentView?.name || defaultViewName}
|
||||
</StyledViewName>
|
||||
<StyledDropdownLabelAdornments>
|
||||
· {views.length} <IconChevronDown size={theme.icon.size.sm} />
|
||||
</StyledDropdownLabelAdornments>
|
||||
</>
|
||||
}
|
||||
isActive={false}
|
||||
isUnfolded={isUnfolded}
|
||||
onIsUnfoldedChange={setIsUnfolded}
|
||||
anchor="left"
|
||||
HotkeyScope={HotkeyScope}
|
||||
menuWidth="auto"
|
||||
>
|
||||
<StyledDropdownMenuItemsContainer>
|
||||
{views.map((view) => (
|
||||
<MenuItem
|
||||
key={view.id}
|
||||
iconButtons={[
|
||||
{
|
||||
Icon: IconPencil,
|
||||
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||
handleEditViewButtonClick(event, view.id),
|
||||
},
|
||||
views.length > 1
|
||||
? {
|
||||
Icon: IconTrash,
|
||||
onClick: (event: MouseEvent<HTMLButtonElement>) =>
|
||||
handleDeleteViewButtonClick(event, view.id),
|
||||
}
|
||||
: null,
|
||||
].filter(assertNotNull)}
|
||||
onClick={() => handleViewSelect(view.id)}
|
||||
LeftIcon={IconList}
|
||||
text={view.name}
|
||||
/>
|
||||
))}
|
||||
</StyledDropdownMenuItemsContainer>
|
||||
<StyledDropdownMenuSeparator />
|
||||
<StyledBoldDropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
onClick={handleAddViewButtonClick}
|
||||
LeftIcon={IconPlus}
|
||||
text="Add view"
|
||||
/>
|
||||
</StyledBoldDropdownMenuItemsContainer>
|
||||
</DropdownButton>
|
||||
);
|
||||
};
|
||||
@ -1,151 +1,90 @@
|
||||
import { useCallback } from 'react';
|
||||
import { useRecoilValue } from 'recoil';
|
||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
||||
|
||||
import { DropdownRecoilScopeContext } from '@/ui/dropdown/states/recoil-scope-contexts/DropdownRecoilScopeContext';
|
||||
import { TopBar } from '@/ui/top-bar/TopBar';
|
||||
import { RecoilScope } from '@/ui/utilities/recoil-scope/components/RecoilScope';
|
||||
import { useContextScopeId } from '@/ui/utilities/recoil-scope/hooks/useContextScopeId';
|
||||
import { useRecoilScopedState } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedState';
|
||||
import { useRecoilScopedValue } from '@/ui/utilities/recoil-scope/hooks/useRecoilScopedValue';
|
||||
import { FilterDropdownButton } from '@/ui/view-bar/components/FilterDropdownButton';
|
||||
import { SortDropdownButton } from '@/ui/view-bar/components/SortDropdownButton';
|
||||
import ViewBarDetails from '@/ui/view-bar/components/ViewBarDetails';
|
||||
import { ViewBar, type ViewBarProps } from '@/ui/view-bar/components/ViewBar';
|
||||
import { currentViewIdScopedState } from '@/ui/view-bar/states/currentViewIdScopedState';
|
||||
import { canPersistFiltersScopedFamilySelector } from '@/ui/view-bar/states/selectors/canPersistFiltersScopedFamilySelector';
|
||||
import { canPersistSortsScopedFamilySelector } from '@/ui/view-bar/states/selectors/canPersistSortsScopedFamilySelector';
|
||||
import { sortsScopedState } from '@/ui/view-bar/states/sortsScopedState';
|
||||
import { FiltersHotkeyScope } from '@/ui/view-bar/types/FiltersHotkeyScope';
|
||||
import { SelectedSortType, SortType } from '@/ui/view-bar/types/interface';
|
||||
import type { View } from '@/ui/view-bar/types/View';
|
||||
import { ViewsHotkeyScope } from '@/ui/view-bar/types/ViewsHotkeyScope';
|
||||
|
||||
import { TableOptionsDropdown } from '../../options/components/TableOptionsDropdown';
|
||||
import { TableUpdateViewButtonGroup } from '../../options/components/TableUpdateViewButtonGroup';
|
||||
import { TableViewsDropdownButton } from '../../options/components/TableViewsDropdownButton';
|
||||
import { TableRecoilScopeContext } from '../../states/recoil-scope-contexts/TableRecoilScopeContext';
|
||||
import { savedTableColumnsFamilyState } from '../../states/savedTableColumnsFamilyState';
|
||||
import { canPersistTableColumnsScopedFamilySelector } from '../../states/selectors/canPersistTableColumnsScopedFamilySelector';
|
||||
import { tableColumnsScopedState } from '../../states/tableColumnsScopedState';
|
||||
import { TableOptionsDropdownKey } from '../../types/TableOptionsDropdownKey';
|
||||
import { TableOptionsHotkeyScope } from '../../types/TableOptionsHotkeyScope';
|
||||
|
||||
type OwnProps<SortField> = {
|
||||
viewName: string;
|
||||
availableSorts?: Array<SortType<SortField>>;
|
||||
onViewsChange?: (views: View[]) => void;
|
||||
onViewSubmit?: () => void;
|
||||
export type TableHeaderProps<SortField> = {
|
||||
onImport?: () => void;
|
||||
};
|
||||
} & Pick<
|
||||
ViewBarProps<SortField>,
|
||||
'availableSorts' | 'defaultViewName' | 'onViewsChange' | 'onViewSubmit'
|
||||
>;
|
||||
|
||||
export function TableHeader<SortField>({
|
||||
viewName,
|
||||
availableSorts,
|
||||
onImport,
|
||||
onViewsChange,
|
||||
onViewSubmit,
|
||||
onImport,
|
||||
}: OwnProps<SortField>) {
|
||||
...props
|
||||
}: TableHeaderProps<SortField>) {
|
||||
const tableScopeId = useContextScopeId(TableRecoilScopeContext);
|
||||
|
||||
const currentViewId = useRecoilScopedValue(
|
||||
currentViewIdScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const [sorts, setSorts] = useRecoilScopedState<SelectedSortType<SortField>[]>(
|
||||
sortsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const canPersistTableColumns = useRecoilValue(
|
||||
canPersistTableColumnsScopedFamilySelector([tableScopeId, currentViewId]),
|
||||
);
|
||||
const canPersistFilters = useRecoilValue(
|
||||
canPersistFiltersScopedFamilySelector([tableScopeId, currentViewId]),
|
||||
const tableColumns = useRecoilScopedValue(
|
||||
tableColumnsScopedState,
|
||||
TableRecoilScopeContext,
|
||||
);
|
||||
const setSavedTableColumns = useSetRecoilState(
|
||||
savedTableColumnsFamilyState(currentViewId),
|
||||
);
|
||||
|
||||
const canPersistSorts = useRecoilValue(
|
||||
canPersistSortsScopedFamilySelector([tableScopeId, currentViewId]),
|
||||
const handleViewSelect = useRecoilCallback(
|
||||
({ set, snapshot }) =>
|
||||
async (viewId: string) => {
|
||||
const savedTableColumns = await snapshot.getPromise(
|
||||
savedTableColumnsFamilyState(viewId),
|
||||
);
|
||||
set(tableColumnsScopedState(tableScopeId), savedTableColumns);
|
||||
},
|
||||
[tableScopeId],
|
||||
);
|
||||
|
||||
const sortSelect = useCallback(
|
||||
(newSort: SelectedSortType<SortField>) => {
|
||||
const newSorts = updateSortOrFilterByKey(sorts, newSort);
|
||||
setSorts(newSorts);
|
||||
},
|
||||
[setSorts, sorts],
|
||||
);
|
||||
const handleViewSubmit = async () => {
|
||||
if (canPersistTableColumns) setSavedTableColumns(tableColumns);
|
||||
|
||||
const sortUnselect = useCallback(
|
||||
(sortKey: string) => {
|
||||
const newSorts = sorts.filter((sort) => sort.key !== sortKey);
|
||||
setSorts(newSorts);
|
||||
},
|
||||
[setSorts, sorts],
|
||||
await onViewSubmit?.();
|
||||
};
|
||||
|
||||
const OptionsDropdownButton = useCallback(
|
||||
() => (
|
||||
<TableOptionsDropdown
|
||||
onImport={onImport}
|
||||
onViewsChange={onViewsChange}
|
||||
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||
/>
|
||||
),
|
||||
[onImport, onViewsChange],
|
||||
);
|
||||
|
||||
return (
|
||||
<RecoilScope SpecificContext={DropdownRecoilScopeContext}>
|
||||
<TopBar
|
||||
leftComponent={
|
||||
<TableViewsDropdownButton
|
||||
defaultViewName={viewName}
|
||||
onViewsChange={onViewsChange}
|
||||
HotkeyScope={ViewsHotkeyScope.ListDropdown}
|
||||
/>
|
||||
}
|
||||
displayBottomBorder={false}
|
||||
rightComponent={
|
||||
<>
|
||||
<FilterDropdownButton
|
||||
context={TableRecoilScopeContext}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
isPrimaryButton
|
||||
/>
|
||||
<SortDropdownButton<SortField>
|
||||
context={TableRecoilScopeContext}
|
||||
isSortSelected={sorts.length > 0}
|
||||
availableSorts={availableSorts || []}
|
||||
onSortSelect={sortSelect}
|
||||
HotkeyScope={FiltersHotkeyScope.FilterDropdownButton}
|
||||
isPrimaryButton
|
||||
/>
|
||||
<TableOptionsDropdown
|
||||
onImport={onImport}
|
||||
onViewsChange={onViewsChange}
|
||||
customHotkeyScope={{ scope: TableOptionsHotkeyScope.Dropdown }}
|
||||
/>
|
||||
</>
|
||||
}
|
||||
bottomComponent={
|
||||
<ViewBarDetails
|
||||
canPersistView={
|
||||
canPersistTableColumns || canPersistFilters || canPersistSorts
|
||||
}
|
||||
context={TableRecoilScopeContext}
|
||||
sorts={sorts}
|
||||
onRemoveSort={sortUnselect}
|
||||
onCancelClick={() => setSorts([])}
|
||||
hasFilterButton
|
||||
rightComponent={
|
||||
<TableUpdateViewButtonGroup
|
||||
onViewSubmit={onViewSubmit}
|
||||
HotkeyScope={ViewsHotkeyScope.CreateDropdown}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
}
|
||||
<ViewBar
|
||||
{...props}
|
||||
canPersistViewFields={canPersistTableColumns}
|
||||
onViewSelect={handleViewSelect}
|
||||
onViewSubmit={handleViewSubmit}
|
||||
OptionsDropdownButton={OptionsDropdownButton}
|
||||
optionsDropdownKey={TableOptionsDropdownKey}
|
||||
scopeContext={TableRecoilScopeContext}
|
||||
/>
|
||||
</RecoilScope>
|
||||
);
|
||||
}
|
||||
|
||||
function updateSortOrFilterByKey<SortOrFilter extends { key: string }>(
|
||||
sorts: Readonly<SortOrFilter[]>,
|
||||
newSort: SortOrFilter,
|
||||
): SortOrFilter[] {
|
||||
const newSorts = [...sorts];
|
||||
const existingSortIndex = sorts.findIndex((sort) => sort.key === newSort.key);
|
||||
|
||||
if (existingSortIndex !== -1) {
|
||||
newSorts[existingSortIndex] = newSort;
|
||||
} else {
|
||||
newSorts.push(newSort);
|
||||
}
|
||||
|
||||
return newSorts;
|
||||
}
|
||||
|
||||
@ -0,0 +1 @@
|
||||
export const TableOptionsDropdownKey = 'table-options';
|
||||
Reference in New Issue
Block a user