Add board Action bar and context menu (#3680)
* Add board Action bar and context menu * Fix according to review
This commit is contained in:
@ -1,226 +0,0 @@
|
|||||||
import { useCallback } from 'react';
|
|
||||||
import { isNonEmptyString } from '@sniptt/guards';
|
|
||||||
import { useRecoilCallback, useRecoilValue, useSetRecoilState } from 'recoil';
|
|
||||||
|
|
||||||
import { useOpenCreateActivityDrawerForSelectedRowIds } from '@/activities/hooks/useOpenCreateActivityDrawerForSelectedRowIds';
|
|
||||||
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
|
||||||
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
|
||||||
import { useExecuteQuickActionOnOneRecord } from '@/object-record/hooks/useExecuteQuickActionOnOneRecord';
|
|
||||||
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
|
||||||
import { useRecordTableStates } from '@/object-record/record-table/hooks/internal/useRecordTableStates';
|
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
|
||||||
import {
|
|
||||||
IconCheckbox,
|
|
||||||
IconClick,
|
|
||||||
IconHeart,
|
|
||||||
IconHeartOff,
|
|
||||||
IconMail,
|
|
||||||
IconNotes,
|
|
||||||
IconPuzzle,
|
|
||||||
IconTrash,
|
|
||||||
} from '@/ui/display/icon';
|
|
||||||
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
|
||||||
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
|
||||||
import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry';
|
|
||||||
import { getSnapshotValue } from '@/ui/utilities/recoil-scope/utils/getSnapshotValue';
|
|
||||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
|
||||||
|
|
||||||
type useRecordTableContextMenuEntriesProps = {
|
|
||||||
objectNamePlural: string;
|
|
||||||
recordTableId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: refactor this
|
|
||||||
export const useRecordTableContextMenuEntries = (
|
|
||||||
props: useRecordTableContextMenuEntriesProps,
|
|
||||||
) => {
|
|
||||||
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
|
||||||
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
|
|
||||||
|
|
||||||
const { getSelectedRowIdsSelector } = useRecordTableStates(
|
|
||||||
props?.recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());
|
|
||||||
|
|
||||||
const { resetTableRowSelection } = useRecordTable({
|
|
||||||
recordTableId: props?.recordTableId,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { objectNameSingular } = useObjectNameSingularFromPlural({
|
|
||||||
objectNamePlural: props.objectNamePlural,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { createFavorite, favorites, deleteFavorite } = useFavorites();
|
|
||||||
|
|
||||||
const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
|
|
||||||
const selectedRowIds = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
getSelectedRowIdsSelector(),
|
|
||||||
);
|
|
||||||
|
|
||||||
const selectedRowId = selectedRowIds.length === 1 ? selectedRowIds[0] : '';
|
|
||||||
|
|
||||||
const selectedRecord = snapshot
|
|
||||||
.getLoadable(recordStoreFamilyState(selectedRowId))
|
|
||||||
.getValue();
|
|
||||||
|
|
||||||
const foundFavorite = favorites?.find(
|
|
||||||
(favorite) => favorite.recordId === selectedRowId,
|
|
||||||
);
|
|
||||||
|
|
||||||
const isFavorite = !!selectedRowId && !!foundFavorite;
|
|
||||||
|
|
||||||
resetTableRowSelection();
|
|
||||||
|
|
||||||
if (isFavorite) {
|
|
||||||
deleteFavorite(foundFavorite.id);
|
|
||||||
} else if (selectedRecord) {
|
|
||||||
createFavorite(selectedRecord, objectNameSingular);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const { deleteManyRecords } = useDeleteManyRecords({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const { executeQuickActionOnOneRecord } = useExecuteQuickActionOnOneRecord({
|
|
||||||
objectNameSingular,
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDeleteClick = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
async () => {
|
|
||||||
const rowIdsToDelete = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
getSelectedRowIdsSelector(),
|
|
||||||
);
|
|
||||||
|
|
||||||
resetTableRowSelection();
|
|
||||||
await deleteManyRecords(rowIdsToDelete);
|
|
||||||
},
|
|
||||||
[deleteManyRecords, resetTableRowSelection, getSelectedRowIdsSelector],
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleExecuteQuickActionOnClick = useRecoilCallback(
|
|
||||||
({ snapshot }) =>
|
|
||||||
async () => {
|
|
||||||
const rowIdsToExecuteQuickActionOn = getSnapshotValue(
|
|
||||||
snapshot,
|
|
||||||
getSelectedRowIdsSelector(),
|
|
||||||
);
|
|
||||||
|
|
||||||
resetTableRowSelection();
|
|
||||||
await Promise.all(
|
|
||||||
rowIdsToExecuteQuickActionOn.map(async (rowId) => {
|
|
||||||
await executeQuickActionOnOneRecord(rowId);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
[
|
|
||||||
executeQuickActionOnOneRecord,
|
|
||||||
resetTableRowSelection,
|
|
||||||
getSelectedRowIdsSelector,
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
const dataExecuteQuickActionOnmentEnabled = useIsFeatureEnabled(
|
|
||||||
'IS_QUICK_ACTIONS_ENABLED',
|
|
||||||
);
|
|
||||||
|
|
||||||
const openCreateActivityDrawer = useOpenCreateActivityDrawerForSelectedRowIds(
|
|
||||||
props.recordTableId,
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
setContextMenuEntries: useCallback(() => {
|
|
||||||
const selectedRowId =
|
|
||||||
selectedRowIds.length === 1 ? selectedRowIds[0] : '';
|
|
||||||
|
|
||||||
const isFavorite =
|
|
||||||
isNonEmptyString(selectedRowId) &&
|
|
||||||
!!favorites?.find((favorite) => favorite.recordId === selectedRowId);
|
|
||||||
|
|
||||||
const contextMenuEntries = [
|
|
||||||
// {
|
|
||||||
// label: 'New task',
|
|
||||||
// Icon: IconCheckbox,
|
|
||||||
// onClick: () => {},
|
|
||||||
// },
|
|
||||||
// {
|
|
||||||
// label: 'New note',
|
|
||||||
// Icon: IconNotes,
|
|
||||||
// onClick: () => {},
|
|
||||||
// },
|
|
||||||
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
Icon: IconTrash,
|
|
||||||
accent: 'danger',
|
|
||||||
onClick: () => handleDeleteClick(),
|
|
||||||
},
|
|
||||||
] as ContextMenuEntry[];
|
|
||||||
|
|
||||||
if (selectedRowIds.length === 1) {
|
|
||||||
contextMenuEntries.unshift({
|
|
||||||
label: isFavorite ? 'Remove from favorites' : 'Add to favorites',
|
|
||||||
Icon: isFavorite ? IconHeartOff : IconHeart,
|
|
||||||
onClick: () => handleFavoriteButtonClick(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setContextMenuEntries(contextMenuEntries);
|
|
||||||
}, [
|
|
||||||
selectedRowIds,
|
|
||||||
favorites,
|
|
||||||
handleDeleteClick,
|
|
||||||
handleFavoriteButtonClick,
|
|
||||||
setContextMenuEntries,
|
|
||||||
]),
|
|
||||||
|
|
||||||
setActionBarEntries: useRecoilCallback(() => () => {
|
|
||||||
setActionBarEntriesState([
|
|
||||||
{
|
|
||||||
label: 'Task',
|
|
||||||
Icon: IconCheckbox,
|
|
||||||
onClick: () => {
|
|
||||||
openCreateActivityDrawer('Task', objectNameSingular);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Note',
|
|
||||||
Icon: IconNotes,
|
|
||||||
onClick: () => {
|
|
||||||
openCreateActivityDrawer('Note', objectNameSingular);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
...(dataExecuteQuickActionOnmentEnabled
|
|
||||||
? [
|
|
||||||
{
|
|
||||||
label: 'Actions',
|
|
||||||
Icon: IconClick,
|
|
||||||
subActions: [
|
|
||||||
{
|
|
||||||
label: 'Enrich',
|
|
||||||
Icon: IconPuzzle,
|
|
||||||
onClick: () => handleExecuteQuickActionOnClick(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: 'Send to mailjet',
|
|
||||||
Icon: IconMail,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
: []),
|
|
||||||
{
|
|
||||||
label: 'Delete',
|
|
||||||
Icon: IconTrash,
|
|
||||||
accent: 'danger',
|
|
||||||
onClick: () => handleDeleteClick(),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -0,0 +1,168 @@
|
|||||||
|
import { useCallback, useMemo } from 'react';
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
|
import { useRecoilCallback, useSetRecoilState } from 'recoil';
|
||||||
|
|
||||||
|
import { useFavorites } from '@/favorites/hooks/useFavorites';
|
||||||
|
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||||
|
import { useDeleteManyRecords } from '@/object-record/hooks/useDeleteManyRecords';
|
||||||
|
import { useExecuteQuickActionOnOneRecord } from '@/object-record/hooks/useExecuteQuickActionOnOneRecord';
|
||||||
|
import { recordStoreFamilyState } from '@/object-record/record-store/states/recordStoreFamilyState';
|
||||||
|
import {
|
||||||
|
IconClick,
|
||||||
|
IconHeart,
|
||||||
|
IconHeartOff,
|
||||||
|
IconMail,
|
||||||
|
IconPuzzle,
|
||||||
|
IconTrash,
|
||||||
|
} from '@/ui/display/icon';
|
||||||
|
import { actionBarEntriesState } from '@/ui/navigation/action-bar/states/actionBarEntriesState';
|
||||||
|
import { contextMenuEntriesState } from '@/ui/navigation/context-menu/states/contextMenuEntriesState';
|
||||||
|
import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry';
|
||||||
|
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||||
|
|
||||||
|
type useRecordActionBarProps = {
|
||||||
|
objectMetadataItem: ObjectMetadataItem;
|
||||||
|
selectedRecordIds: string[];
|
||||||
|
callback?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRecordActionBar = ({
|
||||||
|
objectMetadataItem,
|
||||||
|
selectedRecordIds,
|
||||||
|
callback,
|
||||||
|
}: useRecordActionBarProps) => {
|
||||||
|
const setContextMenuEntries = useSetRecoilState(contextMenuEntriesState);
|
||||||
|
const setActionBarEntriesState = useSetRecoilState(actionBarEntriesState);
|
||||||
|
|
||||||
|
const { createFavorite, favorites, deleteFavorite } = useFavorites();
|
||||||
|
|
||||||
|
const { deleteManyRecords } = useDeleteManyRecords({
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { executeQuickActionOnOneRecord } = useExecuteQuickActionOnOneRecord({
|
||||||
|
objectNameSingular: objectMetadataItem.nameSingular,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleFavoriteButtonClick = useRecoilCallback(({ snapshot }) => () => {
|
||||||
|
if (selectedRecordIds.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedRecordId = selectedRecordIds[0];
|
||||||
|
const selectedRecord = snapshot
|
||||||
|
.getLoadable(recordStoreFamilyState(selectedRecordId))
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
const foundFavorite = favorites?.find(
|
||||||
|
(favorite) => favorite.recordId === selectedRecordId,
|
||||||
|
);
|
||||||
|
|
||||||
|
const isFavorite = !!selectedRecordId && !!foundFavorite;
|
||||||
|
|
||||||
|
if (isFavorite) {
|
||||||
|
deleteFavorite(foundFavorite.id);
|
||||||
|
} else if (selectedRecord) {
|
||||||
|
createFavorite(selectedRecord, objectMetadataItem.nameSingular);
|
||||||
|
}
|
||||||
|
callback?.();
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleDeleteClick = useCallback(async () => {
|
||||||
|
callback?.();
|
||||||
|
await deleteManyRecords(selectedRecordIds);
|
||||||
|
}, [callback, deleteManyRecords, selectedRecordIds]);
|
||||||
|
|
||||||
|
const handleExecuteQuickActionOnClick = useCallback(async () => {
|
||||||
|
callback?.();
|
||||||
|
await Promise.all(
|
||||||
|
selectedRecordIds.map(async (recordId) => {
|
||||||
|
await executeQuickActionOnOneRecord(recordId);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}, [callback, executeQuickActionOnOneRecord, selectedRecordIds]);
|
||||||
|
|
||||||
|
const baseActions: ContextMenuEntry[] = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: 'Delete',
|
||||||
|
Icon: IconTrash,
|
||||||
|
accent: 'danger',
|
||||||
|
onClick: () => handleDeleteClick(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[handleDeleteClick],
|
||||||
|
);
|
||||||
|
|
||||||
|
const dataExecuteQuickActionOnmentEnabled = useIsFeatureEnabled(
|
||||||
|
'IS_QUICK_ACTIONS_ENABLED',
|
||||||
|
);
|
||||||
|
|
||||||
|
const hasOnlyOneRecordSelected = selectedRecordIds.length === 1;
|
||||||
|
|
||||||
|
const isFavorite =
|
||||||
|
isNonEmptyString(selectedRecordIds[0]) &&
|
||||||
|
!!favorites?.find((favorite) => favorite.recordId === selectedRecordIds[0]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
setContextMenuEntries: useCallback(() => {
|
||||||
|
setContextMenuEntries([
|
||||||
|
...baseActions,
|
||||||
|
...(isFavorite && hasOnlyOneRecordSelected
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'Remove from favorites',
|
||||||
|
Icon: IconHeartOff,
|
||||||
|
onClick: handleFavoriteButtonClick,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...(!isFavorite && hasOnlyOneRecordSelected
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'Add to favorites',
|
||||||
|
Icon: IconHeart,
|
||||||
|
onClick: handleFavoriteButtonClick,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
]);
|
||||||
|
}, [
|
||||||
|
baseActions,
|
||||||
|
handleFavoriteButtonClick,
|
||||||
|
hasOnlyOneRecordSelected,
|
||||||
|
isFavorite,
|
||||||
|
setContextMenuEntries,
|
||||||
|
]),
|
||||||
|
|
||||||
|
setActionBarEntries: useCallback(() => {
|
||||||
|
setActionBarEntriesState([
|
||||||
|
...(dataExecuteQuickActionOnmentEnabled
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
label: 'Actions',
|
||||||
|
Icon: IconClick,
|
||||||
|
subActions: [
|
||||||
|
{
|
||||||
|
label: 'Enrich',
|
||||||
|
Icon: IconPuzzle,
|
||||||
|
onClick: handleExecuteQuickActionOnClick,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Send to mailjet',
|
||||||
|
Icon: IconMail,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
...baseActions,
|
||||||
|
]);
|
||||||
|
}, [
|
||||||
|
baseActions,
|
||||||
|
dataExecuteQuickActionOnmentEnabled,
|
||||||
|
handleExecuteQuickActionOnClick,
|
||||||
|
setActionBarEntriesState,
|
||||||
|
]),
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -7,5 +7,10 @@ import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar';
|
|||||||
export const RecordBoardDeprecatedActionBar = () => {
|
export const RecordBoardDeprecatedActionBar = () => {
|
||||||
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
||||||
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
||||||
return <ActionBar selectedIds={selectedCardIds}></ActionBar>;
|
|
||||||
|
if (!selectedCardIds.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ActionBar />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,5 +6,9 @@ import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu
|
|||||||
export const RecordBoardDeprecatedContextMenu = () => {
|
export const RecordBoardDeprecatedContextMenu = () => {
|
||||||
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
const { selectedCardIdsSelector } = useRecordBoardDeprecatedScopedStates();
|
||||||
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
const selectedCardIds = useRecoilValue(selectedCardIdsSelector);
|
||||||
return <ContextMenu selectedIds={selectedCardIds}></ContextMenu>;
|
|
||||||
|
if (!selectedCardIds.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return <ContextMenu />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
|
import { ActionBar } from '@/ui/navigation/action-bar/components/ActionBar';
|
||||||
|
|
||||||
|
type RecordBoardActionBarProps = {
|
||||||
|
recordBoardId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordBoardActionBar = ({
|
||||||
|
recordBoardId,
|
||||||
|
}: RecordBoardActionBarProps) => {
|
||||||
|
const { getSelectedRecordIdsSelector } = useRecordBoardStates(recordBoardId);
|
||||||
|
|
||||||
|
const selectedRecordIds = useRecoilValue(getSelectedRecordIdsSelector());
|
||||||
|
|
||||||
|
if (!selectedRecordIds.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ActionBar />;
|
||||||
|
};
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
|
import { ContextMenu } from '@/ui/navigation/context-menu/components/ContextMenu';
|
||||||
|
|
||||||
|
type RecordBoardContextMenuProps = {
|
||||||
|
recordBoardId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const RecordBoardContextMenu = ({
|
||||||
|
recordBoardId,
|
||||||
|
}: RecordBoardContextMenuProps) => {
|
||||||
|
const { getSelectedRecordIdsSelector } = useRecordBoardStates(recordBoardId);
|
||||||
|
|
||||||
|
const selectedRecordIds = useRecoilValue(getSelectedRecordIdsSelector());
|
||||||
|
|
||||||
|
if (!selectedRecordIds.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ContextMenu />;
|
||||||
|
};
|
||||||
@ -10,6 +10,7 @@ import { recordBoardObjectSingularNameStateScopeMap } from '@/object-record/reco
|
|||||||
import { recordBoardRecordIdsByColumnIdFamilyStateScopeMap } from '@/object-record/record-board/states/recordBoardRecordIdsByColumnIdFamilyStateScopeMap';
|
import { recordBoardRecordIdsByColumnIdFamilyStateScopeMap } from '@/object-record/record-board/states/recordBoardRecordIdsByColumnIdFamilyStateScopeMap';
|
||||||
import { recordBoardSortsStateScopeMap } from '@/object-record/record-board/states/recordBoardSortsStateScopeMap';
|
import { recordBoardSortsStateScopeMap } from '@/object-record/record-board/states/recordBoardSortsStateScopeMap';
|
||||||
import { recordBoardColumnsFamilySelectorScopeMap } from '@/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap';
|
import { recordBoardColumnsFamilySelectorScopeMap } from '@/object-record/record-board/states/selectors/recordBoardColumnsFamilySelectorScopeMap';
|
||||||
|
import { recordBoardSelectedRecordIdsSelectorScopeMap } from '@/object-record/record-board/states/selectors/recordBoardSelectedRecordIdsSelectorScopeMap';
|
||||||
import { recordBoardVisibleFieldDefinitionsScopedSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsScopedSelector';
|
import { recordBoardVisibleFieldDefinitionsScopedSelector } from '@/object-record/record-board/states/selectors/recordBoardVisibleFieldDefinitionsScopedSelector';
|
||||||
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
import { useAvailableScopeIdOrThrow } from '@/ui/utilities/recoil-scope/scopes-internal/hooks/useAvailableScopeId';
|
||||||
import { getFamilyState } from '@/ui/utilities/recoil-scope/utils/getFamilyState';
|
import { getFamilyState } from '@/ui/utilities/recoil-scope/utils/getFamilyState';
|
||||||
@ -54,7 +55,7 @@ export const useRecordBoardStates = (recordBoardId?: string) => {
|
|||||||
scopeId,
|
scopeId,
|
||||||
),
|
),
|
||||||
|
|
||||||
recordBoardRecordIdsByColumnIdFamilyState: getFamilyState(
|
recordIdsByColumnIdFamilyState: getFamilyState(
|
||||||
recordBoardRecordIdsByColumnIdFamilyStateScopeMap,
|
recordBoardRecordIdsByColumnIdFamilyStateScopeMap,
|
||||||
scopeId,
|
scopeId,
|
||||||
),
|
),
|
||||||
@ -62,6 +63,10 @@ export const useRecordBoardStates = (recordBoardId?: string) => {
|
|||||||
isRecordBoardCardSelectedFamilyStateScopeMap,
|
isRecordBoardCardSelectedFamilyStateScopeMap,
|
||||||
scopeId,
|
scopeId,
|
||||||
),
|
),
|
||||||
|
getSelectedRecordIdsSelector: getSelectorReadOnly(
|
||||||
|
recordBoardSelectedRecordIdsSelectorScopeMap,
|
||||||
|
scopeId,
|
||||||
|
),
|
||||||
|
|
||||||
getIsCompactModeActiveState: getState(
|
getIsCompactModeActiveState: getState(
|
||||||
isRecordBoardCompactModeActiveStateScopeMap,
|
isRecordBoardCompactModeActiveStateScopeMap,
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { isDeeplyEqual } from '~/utils/isDeeplyEqual';
|
|||||||
export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
||||||
const {
|
const {
|
||||||
scopeId,
|
scopeId,
|
||||||
recordBoardRecordIdsByColumnIdFamilyState,
|
recordIdsByColumnIdFamilyState,
|
||||||
columnsFamilySelector,
|
columnsFamilySelector,
|
||||||
getColumnIdsState,
|
getColumnIdsState,
|
||||||
} = useRecordBoardStates(recordBoardId);
|
} = useRecordBoardStates(recordBoardId);
|
||||||
@ -23,7 +23,7 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
|||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
const existingColumnRecordIds = snapshot
|
const existingColumnRecordIds = snapshot
|
||||||
.getLoadable(recordBoardRecordIdsByColumnIdFamilyState(columnId))
|
.getLoadable(recordIdsByColumnIdFamilyState(columnId))
|
||||||
.getValue();
|
.getValue();
|
||||||
|
|
||||||
const columnRecordIds = records
|
const columnRecordIds = records
|
||||||
@ -31,18 +31,11 @@ export const useSetRecordBoardRecordIds = (recordBoardId?: string) => {
|
|||||||
.map((record) => record.id);
|
.map((record) => record.id);
|
||||||
|
|
||||||
if (!isDeeplyEqual(existingColumnRecordIds, columnRecordIds)) {
|
if (!isDeeplyEqual(existingColumnRecordIds, columnRecordIds)) {
|
||||||
set(
|
set(recordIdsByColumnIdFamilyState(columnId), columnRecordIds);
|
||||||
recordBoardRecordIdsByColumnIdFamilyState(columnId),
|
|
||||||
columnRecordIds,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[
|
[columnsFamilySelector, getColumnIdsState, recordIdsByColumnIdFamilyState],
|
||||||
columnsFamilySelector,
|
|
||||||
getColumnIdsState,
|
|
||||||
recordBoardRecordIdsByColumnIdFamilyState,
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -5,8 +5,12 @@ import { useSetRecordBoardColumns } from '@/object-record/record-board/hooks/int
|
|||||||
import { useSetRecordBoardRecordIds } from '@/object-record/record-board/hooks/internal/useSetRecordBoardRecordIds';
|
import { useSetRecordBoardRecordIds } from '@/object-record/record-board/hooks/internal/useSetRecordBoardRecordIds';
|
||||||
|
|
||||||
export const useRecordBoard = (recordBoardId?: string) => {
|
export const useRecordBoard = (recordBoardId?: string) => {
|
||||||
const { scopeId, getFieldDefinitionsState, getObjectSingularNameState } =
|
const {
|
||||||
useRecordBoardStates(recordBoardId);
|
scopeId,
|
||||||
|
getFieldDefinitionsState,
|
||||||
|
getObjectSingularNameState,
|
||||||
|
getSelectedRecordIdsSelector,
|
||||||
|
} = useRecordBoardStates(recordBoardId);
|
||||||
|
|
||||||
const { setColumns } = useSetRecordBoardColumns(recordBoardId);
|
const { setColumns } = useSetRecordBoardColumns(recordBoardId);
|
||||||
const { setRecordIds } = useSetRecordBoardRecordIds(recordBoardId);
|
const { setRecordIds } = useSetRecordBoardRecordIds(recordBoardId);
|
||||||
@ -19,5 +23,6 @@ export const useRecordBoard = (recordBoardId?: string) => {
|
|||||||
setRecordIds,
|
setRecordIds,
|
||||||
setFieldDefinitions,
|
setFieldDefinitions,
|
||||||
setObjectSingularName,
|
setObjectSingularName,
|
||||||
|
getSelectedRecordIdsSelector,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
import { useRecoilCallback } from 'recoil';
|
||||||
|
|
||||||
|
import { useRecordBoardStates } from '@/object-record/record-board/hooks/internal/useRecordBoardStates';
|
||||||
|
|
||||||
|
export const useResetBoardRecordSelection = (recordBoardId?: string) => {
|
||||||
|
const { getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState } =
|
||||||
|
useRecordBoardStates(recordBoardId);
|
||||||
|
|
||||||
|
const resetRecordSelection = useRecoilCallback(
|
||||||
|
({ snapshot, set }) =>
|
||||||
|
() => {
|
||||||
|
const recordIds = snapshot
|
||||||
|
.getLoadable(getSelectedRecordIdsSelector())
|
||||||
|
.getValue();
|
||||||
|
|
||||||
|
for (const recordId of recordIds) {
|
||||||
|
set(isRecordBoardCardSelectedFamilyState(recordId), false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[getSelectedRecordIdsSelector, isRecordBoardCardSelectedFamilyState],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { resetRecordSelection };
|
||||||
|
};
|
||||||
@ -32,7 +32,7 @@ export const RecordBoardColumn = ({
|
|||||||
isFirstColumnFamilyState,
|
isFirstColumnFamilyState,
|
||||||
isLastColumnFamilyState,
|
isLastColumnFamilyState,
|
||||||
columnsFamilySelector,
|
columnsFamilySelector,
|
||||||
recordBoardRecordIdsByColumnIdFamilyState,
|
recordIdsByColumnIdFamilyState,
|
||||||
} = useRecordBoardStates();
|
} = useRecordBoardStates();
|
||||||
const columnDefinition = useRecoilValue(
|
const columnDefinition = useRecoilValue(
|
||||||
columnsFamilySelector(recordBoardColumnId),
|
columnsFamilySelector(recordBoardColumnId),
|
||||||
@ -47,7 +47,7 @@ export const RecordBoardColumn = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const recordIds = useRecoilValue(
|
const recordIds = useRecoilValue(
|
||||||
recordBoardRecordIdsByColumnIdFamilyState(recordBoardColumnId),
|
recordIdsByColumnIdFamilyState(recordBoardColumnId),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!columnDefinition) {
|
if (!columnDefinition) {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ const StyledHeader = styled.div`
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
height: 24px;
|
|
||||||
justify-content: left;
|
justify-content: left;
|
||||||
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
margin-bottom: ${({ theme }) => theme.spacing(2)};
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -31,7 +30,7 @@ const StyledNumChildren = styled.div`
|
|||||||
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
border-radius: ${({ theme }) => theme.border.radius.rounded};
|
||||||
color: ${({ theme }) => theme.font.color.tertiary};
|
color: ${({ theme }) => theme.font.color.tertiary};
|
||||||
display: flex;
|
display: flex;
|
||||||
height: 20px;
|
height: 24px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
line-height: ${({ theme }) => theme.text.lineHeight.lg};
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|||||||
@ -0,0 +1,35 @@
|
|||||||
|
import { isRecordBoardCardSelectedFamilyStateScopeMap } from '@/object-record/record-board/states/isRecordBoardCardSelectedFamilyStateScopeMap';
|
||||||
|
import { recordBoardColumnIdsStateScopeMap } from '@/object-record/record-board/states/recordBoardColumnIdsStateScopeMap';
|
||||||
|
import { recordBoardRecordIdsByColumnIdFamilyStateScopeMap } from '@/object-record/record-board/states/recordBoardRecordIdsByColumnIdFamilyStateScopeMap';
|
||||||
|
import { createSelectorReadOnlyScopeMap } from '@/ui/utilities/recoil-scope/utils/createSelectorReadOnlyScopeMap';
|
||||||
|
|
||||||
|
export const recordBoardSelectedRecordIdsSelectorScopeMap =
|
||||||
|
createSelectorReadOnlyScopeMap<string[]>({
|
||||||
|
key: 'recordBoardSelectedRecordIdsSelectorScopeMap',
|
||||||
|
get:
|
||||||
|
({ scopeId }) =>
|
||||||
|
({ get }) => {
|
||||||
|
const columnIds = get(recordBoardColumnIdsStateScopeMap({ scopeId }));
|
||||||
|
|
||||||
|
const recordIdsByColumn = columnIds.map((columnId) =>
|
||||||
|
get(
|
||||||
|
recordBoardRecordIdsByColumnIdFamilyStateScopeMap({
|
||||||
|
scopeId,
|
||||||
|
familyKey: columnId,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
const recordIds = recordIdsByColumn.flat();
|
||||||
|
|
||||||
|
return recordIds.filter(
|
||||||
|
(recordId) =>
|
||||||
|
get(
|
||||||
|
isRecordBoardCardSelectedFamilyStateScopeMap({
|
||||||
|
scopeId,
|
||||||
|
familyKey: recordId,
|
||||||
|
}),
|
||||||
|
) === true,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
@ -2,7 +2,9 @@ import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadata
|
|||||||
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
|
||||||
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
|
||||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||||
|
import { RecordBoardActionBar } from '@/object-record/record-board/action-bar/components/RecordBoardActionBar';
|
||||||
import { RecordBoard } from '@/object-record/record-board/components/RecordBoard';
|
import { RecordBoard } from '@/object-record/record-board/components/RecordBoard';
|
||||||
|
import { RecordBoardContextMenu } from '@/object-record/record-board/context-menu/components/RecordBoardContextMenu';
|
||||||
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
import { RecordBoardContext } from '@/object-record/record-board/contexts/RecordBoardContext';
|
||||||
|
|
||||||
type RecordIndexBoardContainerProps = {
|
type RecordIndexBoardContainerProps = {
|
||||||
@ -32,6 +34,8 @@ export const RecordIndexBoardContainer = ({
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<RecordBoard recordBoardId={recordBoardId} />
|
<RecordBoard recordBoardId={recordBoardId} />
|
||||||
|
<RecordBoardActionBar recordBoardId={recordBoardId} />
|
||||||
|
<RecordBoardContextMenu recordBoardId={recordBoardId} />
|
||||||
</RecordBoardContext.Provider>
|
</RecordBoardContext.Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
import { useCallback, useEffect } from 'react';
|
import { useCallback, useEffect } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
|
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
|
||||||
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
import { useRecordBoard } from '@/object-record/record-board/hooks/useRecordBoard';
|
||||||
|
import { useResetBoardRecordSelection } from '@/object-record/record-board/hooks/useResetBoardRecordSelection';
|
||||||
import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard';
|
import { useLoadRecordIndexBoard } from '@/object-record/record-index/hooks/useLoadRecordIndexBoard';
|
||||||
import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata';
|
import { computeRecordBoardColumnDefinitionsFromObjectMetadata } from '@/object-record/utils/computeRecordBoardColumnDefinitionsFromObjectMetadata';
|
||||||
|
|
||||||
@ -28,7 +31,9 @@ export const RecordIndexBoardContainerEffect = ({
|
|||||||
navigate(`/settings/objects/${objectMetadataItem.namePlural}`);
|
navigate(`/settings/objects/${objectMetadataItem.namePlural}`);
|
||||||
}, [navigate, objectMetadataItem.namePlural]);
|
}, [navigate, objectMetadataItem.namePlural]);
|
||||||
|
|
||||||
const { setColumns, setObjectSingularName } = useRecordBoard(recordBoardId);
|
const { setColumns, setObjectSingularName, getSelectedRecordIdsSelector } =
|
||||||
|
useRecordBoard(recordBoardId);
|
||||||
|
const { resetRecordSelection } = useResetBoardRecordSelection(recordBoardId);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setObjectSingularName(objectNameSingular);
|
setObjectSingularName(objectNameSingular);
|
||||||
@ -48,5 +53,18 @@ export const RecordIndexBoardContainerEffect = ({
|
|||||||
setColumns,
|
setColumns,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const selectedRecordIds = useRecoilValue(getSelectedRecordIdsSelector());
|
||||||
|
|
||||||
|
const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({
|
||||||
|
objectMetadataItem,
|
||||||
|
selectedRecordIds,
|
||||||
|
callback: resetRecordSelection,
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setActionBarEntries?.();
|
||||||
|
setContextMenuEntries?.();
|
||||||
|
}, [setActionBarEntries, setContextMenuEntries]);
|
||||||
|
|
||||||
return <></>;
|
return <></>;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect } from 'react';
|
||||||
|
import { useRecoilValue } from 'recoil';
|
||||||
|
|
||||||
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
|
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||||
import { useViewBar } from '@/views/hooks/useViewBar';
|
import { useViewBar } from '@/views/hooks/useViewBar';
|
||||||
@ -18,7 +19,12 @@ export const RecordIndexTableContainerEffect = ({
|
|||||||
recordTableId,
|
recordTableId,
|
||||||
viewBarId,
|
viewBarId,
|
||||||
}: RecordIndexTableContainerEffectProps) => {
|
}: RecordIndexTableContainerEffectProps) => {
|
||||||
const { setAvailableTableColumns, setOnEntityCountChange } = useRecordTable({
|
const {
|
||||||
|
setAvailableTableColumns,
|
||||||
|
setOnEntityCountChange,
|
||||||
|
resetTableRowSelection,
|
||||||
|
getSelectedRowIdsSelector,
|
||||||
|
} = useRecordTable({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -47,11 +53,13 @@ export const RecordIndexTableContainerEffect = ({
|
|||||||
setAvailableTableColumns,
|
setAvailableTableColumns,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { setActionBarEntries, setContextMenuEntries } =
|
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());
|
||||||
useRecordTableContextMenuEntries({
|
|
||||||
objectNamePlural: objectMetadataItem.namePlural,
|
const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({
|
||||||
recordTableId,
|
objectMetadataItem,
|
||||||
});
|
selectedRecordIds: selectedRowIds,
|
||||||
|
callback: resetTableRowSelection,
|
||||||
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActionBarEntries?.();
|
setActionBarEntries?.();
|
||||||
|
|||||||
@ -12,5 +12,9 @@ export const RecordTableActionBar = ({
|
|||||||
|
|
||||||
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());
|
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());
|
||||||
|
|
||||||
return <ActionBar selectedIds={selectedRowIds} />;
|
if (!selectedRowIds.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ActionBar />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -12,5 +12,9 @@ export const RecordTableContextMenu = ({
|
|||||||
|
|
||||||
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());
|
const selectedRowIds = useRecoilValue(getSelectedRowIdsSelector());
|
||||||
|
|
||||||
return <ContextMenu selectedIds={selectedRowIds} />;
|
if (!selectedRowIds.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <ContextMenu />;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
|||||||
|
|
||||||
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
|
||||||
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
|
||||||
import { useRecordTableContextMenuEntries } from '@/object-record/hooks/useRecordTableContextMenuEntries';
|
import { useRecordActionBar } from '@/object-record/record-action-bar/hooks/useRecordActionBar';
|
||||||
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
|
||||||
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
import { filterAvailableTableColumns } from '@/object-record/utils/filterAvailableTableColumns';
|
||||||
import {
|
import {
|
||||||
@ -30,6 +30,7 @@ export const SignInBackgroundMockContainerEffect = ({
|
|||||||
setOnEntityCountChange,
|
setOnEntityCountChange,
|
||||||
setRecordTableData,
|
setRecordTableData,
|
||||||
setTableColumns,
|
setTableColumns,
|
||||||
|
resetTableRowSelection,
|
||||||
} = useRecordTable({
|
} = useRecordTable({
|
||||||
recordTableId,
|
recordTableId,
|
||||||
});
|
});
|
||||||
@ -80,11 +81,11 @@ export const SignInBackgroundMockContainerEffect = ({
|
|||||||
setTableColumns,
|
setTableColumns,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { setActionBarEntries, setContextMenuEntries } =
|
const { setActionBarEntries, setContextMenuEntries } = useRecordActionBar({
|
||||||
useRecordTableContextMenuEntries({
|
objectMetadataItem,
|
||||||
objectNamePlural,
|
selectedRecordIds: [],
|
||||||
recordTableId,
|
callback: resetTableRowSelection,
|
||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setActionBarEntries?.();
|
setActionBarEntries?.();
|
||||||
|
|||||||
@ -9,10 +9,6 @@ import { actionBarOpenState } from '../states/actionBarIsOpenState';
|
|||||||
|
|
||||||
import { ActionBarItem } from './ActionBarItem';
|
import { ActionBarItem } from './ActionBarItem';
|
||||||
|
|
||||||
type ActionBarProps = {
|
|
||||||
selectedIds: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
const StyledContainerActionBar = styled.div`
|
const StyledContainerActionBar = styled.div`
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: ${({ theme }) => theme.background.secondary};
|
background: ${({ theme }) => theme.background.secondary};
|
||||||
@ -33,30 +29,24 @@ const StyledContainerActionBar = styled.div`
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionBar = ({ selectedIds }: ActionBarProps) => {
|
export const ActionBar = () => {
|
||||||
const actionBarOpen = useRecoilValue(actionBarOpenState);
|
const actionBarOpen = useRecoilValue(actionBarOpenState);
|
||||||
const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
|
const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
|
||||||
const actionBarEntries = useRecoilValue(actionBarEntriesState);
|
const actionBarEntries = useRecoilValue(actionBarEntriesState);
|
||||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
if (selectedIds.length === 0 || !actionBarOpen || contextMenuIsOpen) {
|
if (!actionBarOpen || contextMenuIsOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StyledContainerActionBar
|
<StyledContainerActionBar
|
||||||
data-select-disable
|
data-select-disable
|
||||||
className="action-bar"
|
className="action-bar"
|
||||||
ref={wrapperRef}
|
ref={wrapperRef}
|
||||||
>
|
>
|
||||||
{actionBarEntries.map((item) => (
|
{actionBarEntries.map((item, index) => (
|
||||||
<ActionBarItem
|
<ActionBarItem key={index} item={item} />
|
||||||
Icon={item.Icon}
|
|
||||||
accent={item.accent}
|
|
||||||
label={item.label}
|
|
||||||
onClick={item.onClick}
|
|
||||||
key={item.label}
|
|
||||||
subActions={item?.subActions}
|
|
||||||
/>
|
|
||||||
))}
|
))}
|
||||||
</StyledContainerActionBar>
|
</StyledContainerActionBar>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -3,22 +3,17 @@ import styled from '@emotion/styled';
|
|||||||
import { MenuItem } from 'tsup.ui.index';
|
import { MenuItem } from 'tsup.ui.index';
|
||||||
|
|
||||||
import { IconChevronDown } from '@/ui/display/icon';
|
import { IconChevronDown } from '@/ui/display/icon';
|
||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
|
||||||
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
import { Dropdown } from '@/ui/layout/dropdown/components/Dropdown';
|
||||||
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/DropdownMenuItemsContainer';
|
||||||
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
import { useDropdown } from '@/ui/layout/dropdown/hooks/useDropdown';
|
||||||
|
import { ActionBarEntry } from '@/ui/navigation/action-bar/types/ActionBarEntry';
|
||||||
import { ActionBarItemAccent } from '../types/ActionBarItemAccent';
|
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
||||||
|
|
||||||
type ActionBarItemProps = {
|
type ActionBarItemProps = {
|
||||||
Icon: IconComponent;
|
item: ActionBarEntry;
|
||||||
label: string;
|
|
||||||
accent?: ActionBarItemAccent;
|
|
||||||
onClick?: () => void;
|
|
||||||
subActions?: ActionBarItemProps[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const StyledButton = styled.div<{ accent: ActionBarItemAccent }>`
|
const StyledButton = styled.div<{ accent: MenuItemAccent }>`
|
||||||
border-radius: ${({ theme }) => theme.border.radius.sm};
|
border-radius: ${({ theme }) => theme.border.radius.sm};
|
||||||
color: ${(props) =>
|
color: ${(props) =>
|
||||||
props.accent === 'danger'
|
props.accent === 'danger'
|
||||||
@ -45,19 +40,13 @@ const StyledButtonLabel = styled.div`
|
|||||||
margin-left: ${({ theme }) => theme.spacing(1)};
|
margin-left: ${({ theme }) => theme.spacing(1)};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ActionBarItem = ({
|
export const ActionBarItem = ({ item }: ActionBarItemProps) => {
|
||||||
label,
|
|
||||||
Icon,
|
|
||||||
accent = 'standard',
|
|
||||||
onClick,
|
|
||||||
subActions,
|
|
||||||
}: ActionBarItemProps) => {
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const dropdownId = `action-bar-item-${label}`;
|
const dropdownId = `action-bar-item-${item.label}`;
|
||||||
const { toggleDropdown, closeDropdown } = useDropdown(dropdownId);
|
const { toggleDropdown, closeDropdown } = useDropdown(dropdownId);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Array.isArray(subActions) ? (
|
{Array.isArray(item.subActions) ? (
|
||||||
<Dropdown
|
<Dropdown
|
||||||
dropdownId={dropdownId}
|
dropdownId={dropdownId}
|
||||||
dropdownPlacement="top-start"
|
dropdownPlacement="top-start"
|
||||||
@ -65,15 +54,18 @@ export const ActionBarItem = ({
|
|||||||
scope: dropdownId,
|
scope: dropdownId,
|
||||||
}}
|
}}
|
||||||
clickableComponent={
|
clickableComponent={
|
||||||
<StyledButton accent={accent} onClick={toggleDropdown}>
|
<StyledButton
|
||||||
{Icon && <Icon size={theme.icon.size.md} />}
|
accent={item.accent ?? 'default'}
|
||||||
<StyledButtonLabel>{label}</StyledButtonLabel>
|
onClick={toggleDropdown}
|
||||||
|
>
|
||||||
|
{item.Icon && <item.Icon size={theme.icon.size.md} />}
|
||||||
|
<StyledButtonLabel>{item.label}</StyledButtonLabel>
|
||||||
<IconChevronDown size={theme.icon.size.md} />
|
<IconChevronDown size={theme.icon.size.md} />
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
}
|
}
|
||||||
dropdownComponents={
|
dropdownComponents={
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{subActions.map((subAction) => (
|
{item.subActions.map((subAction) => (
|
||||||
<MenuItem
|
<MenuItem
|
||||||
key={subAction.label}
|
key={subAction.label}
|
||||||
text={subAction.label}
|
text={subAction.label}
|
||||||
@ -88,9 +80,12 @@ export const ActionBarItem = ({
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<StyledButton accent={accent} onClick={onClick}>
|
<StyledButton
|
||||||
{Icon && <Icon size={theme.icon.size.md} />}
|
accent={item.accent ?? 'default'}
|
||||||
<StyledButtonLabel>{label}</StyledButtonLabel>
|
onClick={() => item.onClick?.()}
|
||||||
|
>
|
||||||
|
{item.Icon && <item.Icon size={theme.icon.size.md} />}
|
||||||
|
<StyledButtonLabel>{item.label}</StyledButtonLabel>
|
||||||
</StyledButton>
|
</StyledButton>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -8,10 +8,10 @@ import { ComponentDecorator } from '~/testing/decorators/ComponentDecorator';
|
|||||||
import { actionBarOpenState } from '../../states/actionBarIsOpenState';
|
import { actionBarOpenState } from '../../states/actionBarIsOpenState';
|
||||||
import { ActionBar } from '../ActionBar';
|
import { ActionBar } from '../ActionBar';
|
||||||
|
|
||||||
const FilledActionBar = (props: { selectedIds: string[] }) => {
|
const FilledActionBar = () => {
|
||||||
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
|
const setActionBarOpenState = useSetRecoilState(actionBarOpenState);
|
||||||
setActionBarOpenState(true);
|
setActionBarOpenState(true);
|
||||||
return <ActionBar selectedIds={props.selectedIds} />;
|
return <ActionBar />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ActionBar> = {
|
const meta: Meta<typeof ActionBar> = {
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
|
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
||||||
import { ActionBarItemAccent } from './ActionBarItemAccent';
|
|
||||||
|
|
||||||
export type ActionBarEntry = {
|
export type ActionBarEntry = {
|
||||||
label: string;
|
label: string;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
accent?: ActionBarItemAccent;
|
accent?: MenuItemAccent;
|
||||||
onClick?: () => void;
|
onClick?: () => void;
|
||||||
subActions?: ActionBarEntry[];
|
subActions?: ActionBarEntry[];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -14,10 +14,6 @@ import { PositionType } from '../types/PositionType';
|
|||||||
|
|
||||||
import { ContextMenuItem } from './ContextMenuItem';
|
import { ContextMenuItem } from './ContextMenuItem';
|
||||||
|
|
||||||
type ContextMenuProps = {
|
|
||||||
selectedIds: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
type StyledContainerProps = {
|
type StyledContainerProps = {
|
||||||
position: PositionType;
|
position: PositionType;
|
||||||
};
|
};
|
||||||
@ -41,7 +37,7 @@ const StyledContainerContextMenu = styled.div<StyledContainerProps>`
|
|||||||
z-index: 2;
|
z-index: 2;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export const ContextMenu = ({ selectedIds }: ContextMenuProps) => {
|
export const ContextMenu = () => {
|
||||||
const contextMenuPosition = useRecoilValue(contextMenuPositionState);
|
const contextMenuPosition = useRecoilValue(contextMenuPositionState);
|
||||||
const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
|
const contextMenuIsOpen = useRecoilValue(contextMenuIsOpenState);
|
||||||
const contextMenuEntries = useRecoilValue(contextMenuEntriesState);
|
const contextMenuEntries = useRecoilValue(contextMenuEntriesState);
|
||||||
@ -57,7 +53,7 @@ export const ContextMenu = ({ selectedIds }: ContextMenuProps) => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectedIds.length === 0 || !contextMenuIsOpen) {
|
if (!contextMenuIsOpen) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,15 +71,9 @@ export const ContextMenu = ({ selectedIds }: ContextMenuProps) => {
|
|||||||
>
|
>
|
||||||
<DropdownMenu data-select-disable width={width}>
|
<DropdownMenu data-select-disable width={width}>
|
||||||
<DropdownMenuItemsContainer>
|
<DropdownMenuItemsContainer>
|
||||||
{contextMenuEntries.map((item) => (
|
{contextMenuEntries.map((item, index) => {
|
||||||
<ContextMenuItem
|
return <ContextMenuItem key={index} item={item} />;
|
||||||
Icon={item.Icon}
|
})}
|
||||||
label={item.label}
|
|
||||||
accent={item.accent}
|
|
||||||
onClick={item.onClick}
|
|
||||||
key={item.label}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</DropdownMenuItemsContainer>
|
</DropdownMenuItemsContainer>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
</StyledContainerContextMenu>
|
</StyledContainerContextMenu>
|
||||||
|
|||||||
@ -1,20 +1,15 @@
|
|||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
import { ContextMenuEntry } from '@/ui/navigation/context-menu/types/ContextMenuEntry';
|
||||||
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
import { MenuItem } from '@/ui/navigation/menu-item/components/MenuItem';
|
||||||
|
|
||||||
import { ContextMenuItemAccent } from '../types/ContextMenuItemAccent';
|
|
||||||
|
|
||||||
type ContextMenuItemProps = {
|
type ContextMenuItemProps = {
|
||||||
Icon: IconComponent;
|
item: ContextMenuEntry;
|
||||||
label: string;
|
|
||||||
accent?: ContextMenuItemAccent;
|
|
||||||
onClick: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ContextMenuItem = ({
|
export const ContextMenuItem = ({ item }: ContextMenuItemProps) => (
|
||||||
label,
|
<MenuItem
|
||||||
Icon,
|
LeftIcon={item.Icon}
|
||||||
accent = 'default',
|
onClick={item.onClick}
|
||||||
onClick,
|
accent={item.accent}
|
||||||
}: ContextMenuItemProps) => (
|
text={item.label}
|
||||||
<MenuItem LeftIcon={Icon} onClick={onClick} accent={accent} text={label} />
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import { contextMenuIsOpenState } from '../../states/contextMenuIsOpenState';
|
|||||||
import { contextMenuPositionState } from '../../states/contextMenuPositionState';
|
import { contextMenuPositionState } from '../../states/contextMenuPositionState';
|
||||||
import { ContextMenu } from '../ContextMenu';
|
import { ContextMenu } from '../ContextMenu';
|
||||||
|
|
||||||
const FilledContextMenu = (props: { selectedIds: string[] }) => {
|
const FilledContextMenu = () => {
|
||||||
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
const setContextMenuPosition = useSetRecoilState(contextMenuPositionState);
|
||||||
setContextMenuPosition({
|
setContextMenuPosition({
|
||||||
x: 100,
|
x: 100,
|
||||||
@ -17,7 +17,7 @@ const FilledContextMenu = (props: { selectedIds: string[] }) => {
|
|||||||
});
|
});
|
||||||
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
const setContextMenuOpenState = useSetRecoilState(contextMenuIsOpenState);
|
||||||
setContextMenuOpenState(true);
|
setContextMenuOpenState(true);
|
||||||
return <ContextMenu selectedIds={props.selectedIds} />;
|
return <ContextMenu />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const meta: Meta<typeof ContextMenu> = {
|
const meta: Meta<typeof ContextMenu> = {
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
import { IconComponent } from '@/ui/display/icon/types/IconComponent';
|
||||||
|
import { MenuItemAccent } from '@/ui/navigation/menu-item/types/MenuItemAccent';
|
||||||
import { ContextMenuItemAccent } from './ContextMenuItemAccent';
|
|
||||||
|
|
||||||
export type ContextMenuEntry = {
|
export type ContextMenuEntry = {
|
||||||
label: string;
|
label: string;
|
||||||
Icon: IconComponent;
|
Icon: IconComponent;
|
||||||
accent?: ContextMenuItemAccent;
|
accent?: MenuItemAccent;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user