mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-09 20:29:36 +02:00
refactor list internal state to target rerenders on change
This commit is contained in:
@@ -9,7 +9,11 @@ import styles from './item-card.module.css';
|
|||||||
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
import { ItemCardControls } from '/@/renderer/components/item-card/item-card-controls';
|
||||||
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
||||||
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
import { getTitlePath } from '/@/renderer/components/item-list/helpers/get-title-path';
|
||||||
import { ItemListStateActions } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import {
|
||||||
|
ItemListStateActions,
|
||||||
|
useItemDraggingState,
|
||||||
|
useItemSelectionState,
|
||||||
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ItemControls } from '/@/renderer/components/item-list/types';
|
import { ItemControls } from '/@/renderer/components/item-list/types';
|
||||||
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
import { useDragDrop } from '/@/renderer/hooks/use-drag-drop';
|
||||||
import { AppRoute } from '/@/renderer/router/routes';
|
import { AppRoute } from '/@/renderer/router/routes';
|
||||||
@@ -141,10 +145,11 @@ const CompactItemCard = ({
|
|||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
const [showControls, setShowControls] = useState(false);
|
||||||
const isSelected =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.isSelected(internalState.extractRowId(data) || '')
|
? internalState.extractRowId(data)
|
||||||
: false;
|
: undefined;
|
||||||
|
const isSelected = useItemSelectionState(internalState, itemRowId || undefined);
|
||||||
|
|
||||||
const handleClick = useDoubleClick({
|
const handleClick = useDoubleClick({
|
||||||
onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
@@ -345,10 +350,11 @@ const DefaultItemCard = ({
|
|||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
const [showControls, setShowControls] = useState(false);
|
||||||
const isSelected =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.isSelected(internalState.extractRowId(data) || '')
|
? internalState.extractRowId(data)
|
||||||
: false;
|
: undefined;
|
||||||
|
const isSelected = useItemSelectionState(internalState, itemRowId || undefined);
|
||||||
|
|
||||||
const handleClick = useDoubleClick({
|
const handleClick = useDoubleClick({
|
||||||
onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
@@ -549,10 +555,11 @@ const PosterItemCard = ({
|
|||||||
withControls,
|
withControls,
|
||||||
}: ItemCardDerivativeProps) => {
|
}: ItemCardDerivativeProps) => {
|
||||||
const [showControls, setShowControls] = useState(false);
|
const [showControls, setShowControls] = useState(false);
|
||||||
const isSelected =
|
const itemRowId =
|
||||||
data && internalState && typeof data === 'object' && 'id' in data
|
data && internalState && typeof data === 'object' && 'id' in data
|
||||||
? internalState.isSelected(internalState.extractRowId(data) || '')
|
? internalState.extractRowId(data)
|
||||||
: false;
|
: undefined;
|
||||||
|
const isSelected = useItemSelectionState(internalState, itemRowId || undefined);
|
||||||
|
|
||||||
const { isDragging: isDraggingLocal, ref } = useDragDrop<HTMLDivElement>({
|
const { isDragging: isDraggingLocal, ref } = useDragDrop<HTMLDivElement>({
|
||||||
drag: {
|
drag: {
|
||||||
@@ -597,7 +604,9 @@ const PosterItemCard = ({
|
|||||||
isEnabled: !!enableDrag && !!data,
|
isEnabled: !!enableDrag && !!data,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDragging = data && internalState ? internalState.isDragging(data.id) : isDraggingLocal;
|
const itemId = data && internalState ? data.id : undefined;
|
||||||
|
const isDraggingState = useItemDraggingState(internalState, itemId);
|
||||||
|
const isDragging = isDraggingState || isDraggingLocal;
|
||||||
|
|
||||||
const handleClick = useDoubleClick({
|
const handleClick = useDoubleClick({
|
||||||
onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
onDoubleClick: (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import styles from './expanded-list-item.module.css';
|
|||||||
import {
|
import {
|
||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItem,
|
ItemListStateItem,
|
||||||
|
useItemListStateSubscription,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
|
import { ExpandedAlbumListItem } from '/@/renderer/features/albums/components/expanded-album-list-item';
|
||||||
import { Spinner } from '/@/shared/components/spinner/spinner';
|
import { Spinner } from '/@/shared/components/spinner/spinner';
|
||||||
@@ -16,7 +17,9 @@ interface ExpandedListItemProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemProps) => {
|
export const ExpandedListItem = ({ internalState, itemType }: ExpandedListItemProps) => {
|
||||||
const expandedItems = internalState.getExpanded();
|
const expandedItems = useItemListStateSubscription(internalState, () =>
|
||||||
|
internalState ? internalState.getExpandedItemsCached() : [],
|
||||||
|
);
|
||||||
const currentItem = expandedItems[0];
|
const currentItem = expandedItems[0];
|
||||||
|
|
||||||
if (!currentItem) {
|
if (!currentItem) {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useMemo, useReducer } from 'react';
|
import { useCallback, useMemo, useRef, useSyncExternalStore } from 'react';
|
||||||
|
|
||||||
import { itemListSelectors } from '/@/renderer/components/item-list/helpers/item-list-reducer-utils';
|
import { itemListSelectors } from '/@/renderer/components/item-list/helpers/item-list-reducer-utils';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
@@ -84,6 +84,7 @@ export interface ItemListStateActions {
|
|||||||
getDraggingIds: () => string[];
|
getDraggingIds: () => string[];
|
||||||
getExpanded: () => unknown[];
|
getExpanded: () => unknown[];
|
||||||
getExpandedIds: () => string[];
|
getExpandedIds: () => string[];
|
||||||
|
getExpandedItemsCached: () => unknown[];
|
||||||
getSelected: () => unknown[];
|
getSelected: () => unknown[];
|
||||||
getSelectedIds: () => string[];
|
getSelectedIds: () => string[];
|
||||||
getVersion: () => number;
|
getVersion: () => number;
|
||||||
@@ -281,11 +282,122 @@ export const initialItemListState: ItemListState = {
|
|||||||
version: 0,
|
version: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* External store for item list state that doesn't cause React rerenders
|
||||||
|
* Components can subscribe to specific state slices using useSyncExternalStore
|
||||||
|
*/
|
||||||
|
class ItemListStateStore {
|
||||||
|
// Cache for derived values to prevent unnecessary rerenders
|
||||||
|
private expandedItemsCache: null | unknown[] = null;
|
||||||
|
private expandedItemsCacheVersion: number = -1;
|
||||||
|
private listeners = new Set<() => void>();
|
||||||
|
private state: ItemListState = { ...initialItemListState };
|
||||||
|
|
||||||
|
dispatch(action: ItemListAction): void {
|
||||||
|
this.state = itemListReducer(this.state, action);
|
||||||
|
// Invalidate caches when state changes
|
||||||
|
this.expandedItemsCache = null;
|
||||||
|
// Notify all subscribers
|
||||||
|
this.listeners.forEach((listener) => listener());
|
||||||
|
}
|
||||||
|
|
||||||
|
getExpandedItems(): unknown[] {
|
||||||
|
// Return cached array if state version hasn't changed
|
||||||
|
if (
|
||||||
|
this.expandedItemsCache !== null &&
|
||||||
|
this.expandedItemsCacheVersion === this.state.version
|
||||||
|
) {
|
||||||
|
return this.expandedItemsCache;
|
||||||
|
}
|
||||||
|
// Create new array and cache it
|
||||||
|
this.expandedItemsCache = Array.from(this.state.expandedItems.values());
|
||||||
|
this.expandedItemsCacheVersion = this.state.version;
|
||||||
|
return this.expandedItemsCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
getState(): ItemListState {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
subscribe(listener: () => void): () => void {
|
||||||
|
this.listeners.add(listener);
|
||||||
|
return () => {
|
||||||
|
this.listeners.delete(listener);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to subscribe to specific state changes in the item list state
|
||||||
|
* Use this in components that need to rerender when state changes
|
||||||
|
*/
|
||||||
|
export const useItemListStateSubscription = <T>(
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
selector: (state: ItemListState | null) => T,
|
||||||
|
): T => {
|
||||||
|
const store = internalState ? ((internalState as any).__store as ItemListStateStore) : null;
|
||||||
|
|
||||||
|
return useSyncExternalStore(
|
||||||
|
store?.subscribe.bind(store) || (() => () => {}), // Return no-op unsubscribe if no store
|
||||||
|
() => selector(store?.getState() || null),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to subscribe to selection state for a specific item
|
||||||
|
* Use this in components that need to rerender when a specific item's selection changes
|
||||||
|
*/
|
||||||
|
export const useItemSelectionState = (
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
rowId: string | undefined,
|
||||||
|
): boolean => {
|
||||||
|
return useItemListStateSubscription(internalState, (state) =>
|
||||||
|
state && rowId ? state.selected.has(rowId) : false,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to subscribe to expansion state for a specific item
|
||||||
|
* Use this in components that need to rerender when a specific item's expansion changes
|
||||||
|
*/
|
||||||
|
export const useItemExpansionState = (
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
rowId: string | undefined,
|
||||||
|
): boolean => {
|
||||||
|
return useItemListStateSubscription(internalState, (state) =>
|
||||||
|
state && rowId ? state.expanded.has(rowId) : false,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hook to subscribe to dragging state for a specific item
|
||||||
|
* Use this in components that need to rerender when a specific item's dragging state changes
|
||||||
|
*/
|
||||||
|
export const useItemDraggingState = (
|
||||||
|
internalState: ItemListStateActions | undefined,
|
||||||
|
rowId: string | undefined,
|
||||||
|
): boolean => {
|
||||||
|
return useItemListStateSubscription(internalState, (state) =>
|
||||||
|
state && rowId ? state.dragging.has(rowId) : false,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const useItemListState = (
|
export const useItemListState = (
|
||||||
getDataFn?: () => unknown[],
|
getDataFn?: () => unknown[],
|
||||||
extractRowId?: (item: unknown) => string | undefined,
|
extractRowId?: (item: unknown) => string | undefined,
|
||||||
): ItemListStateActions => {
|
): ItemListStateActions => {
|
||||||
const [state, dispatch] = useReducer(itemListReducer, initialItemListState);
|
// Create store instance (stable across rerenders)
|
||||||
|
const storeRef = useRef<ItemListStateStore | null>(null);
|
||||||
|
if (!storeRef.current) {
|
||||||
|
storeRef.current = new ItemListStateStore();
|
||||||
|
}
|
||||||
|
const store = storeRef.current;
|
||||||
|
|
||||||
|
// DON'T subscribe here - this prevents rerenders when state changes
|
||||||
|
// Components that need to react should use useItemListStateSubscription
|
||||||
|
|
||||||
|
// Get current state (this doesn't cause rerenders, it's just reading from the store)
|
||||||
|
const getCurrentState = useCallback(() => store.getState(), [store]);
|
||||||
|
|
||||||
const extractRowIdFn = useCallback(
|
const extractRowIdFn = useCallback(
|
||||||
(item: unknown) => {
|
(item: unknown) => {
|
||||||
@@ -303,138 +415,156 @@ export const useItemListState = (
|
|||||||
|
|
||||||
const setExpanded = useCallback(
|
const setExpanded = useCallback(
|
||||||
(items: ItemListStateItemWithRequiredProperties[]) => {
|
(items: ItemListStateItemWithRequiredProperties[]) => {
|
||||||
dispatch({
|
store.dispatch({
|
||||||
extractRowId: extractRowIdFn,
|
extractRowId: extractRowIdFn,
|
||||||
payload: items,
|
payload: items,
|
||||||
type: 'SET_EXPANDED',
|
type: 'SET_EXPANDED',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[extractRowIdFn],
|
[store, extractRowIdFn],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setDragging = useCallback(
|
const setDragging = useCallback(
|
||||||
(items: ItemListStateItemWithRequiredProperties[]) => {
|
(items: ItemListStateItemWithRequiredProperties[]) => {
|
||||||
dispatch({
|
store.dispatch({
|
||||||
extractRowId: extractRowIdFn,
|
extractRowId: extractRowIdFn,
|
||||||
payload: items,
|
payload: items,
|
||||||
type: 'SET_DRAGGING',
|
type: 'SET_DRAGGING',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[extractRowIdFn],
|
[store, extractRowIdFn],
|
||||||
);
|
);
|
||||||
|
|
||||||
const setSelected = useCallback(
|
const setSelected = useCallback(
|
||||||
(items: ItemListStateItemWithRequiredProperties[]) => {
|
(items: ItemListStateItemWithRequiredProperties[]) => {
|
||||||
dispatch({
|
store.dispatch({
|
||||||
extractRowId: extractRowIdFn,
|
extractRowId: extractRowIdFn,
|
||||||
payload: items,
|
payload: items,
|
||||||
type: 'SET_SELECTED',
|
type: 'SET_SELECTED',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[extractRowIdFn],
|
[store, extractRowIdFn],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleExpanded = useCallback(
|
const toggleExpanded = useCallback(
|
||||||
(item: ItemListStateItemWithRequiredProperties) => {
|
(item: ItemListStateItemWithRequiredProperties) => {
|
||||||
dispatch({
|
store.dispatch({
|
||||||
extractRowId: extractRowIdFn,
|
extractRowId: extractRowIdFn,
|
||||||
payload: item,
|
payload: item,
|
||||||
type: 'TOGGLE_EXPANDED',
|
type: 'TOGGLE_EXPANDED',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[extractRowIdFn],
|
[store, extractRowIdFn],
|
||||||
);
|
);
|
||||||
|
|
||||||
const toggleSelected = useCallback(
|
const toggleSelected = useCallback(
|
||||||
(item: ItemListStateItemWithRequiredProperties) => {
|
(item: ItemListStateItemWithRequiredProperties) => {
|
||||||
dispatch({
|
store.dispatch({
|
||||||
extractRowId: extractRowIdFn,
|
extractRowId: extractRowIdFn,
|
||||||
payload: item,
|
payload: item,
|
||||||
type: 'TOGGLE_SELECTED',
|
type: 'TOGGLE_SELECTED',
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
[extractRowIdFn],
|
[store, extractRowIdFn],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// These methods read from the store without subscribing, so they don't cause rerenders
|
||||||
const isExpanded = useCallback(
|
const isExpanded = useCallback(
|
||||||
(rowId: string) => {
|
(rowId: string) => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.isExpanded(state, rowId);
|
return itemListSelectors.isExpanded(state, rowId);
|
||||||
},
|
},
|
||||||
[state],
|
[getCurrentState],
|
||||||
);
|
);
|
||||||
|
|
||||||
const isSelected = useCallback(
|
const isSelected = useCallback(
|
||||||
(rowId: string) => {
|
(rowId: string) => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.isSelected(state, rowId);
|
return itemListSelectors.isSelected(state, rowId);
|
||||||
},
|
},
|
||||||
[state],
|
[getCurrentState],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getExpanded = useCallback(() => {
|
const getExpanded = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.getExpanded(state);
|
return itemListSelectors.getExpanded(state);
|
||||||
}, [state]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
|
const getExpandedItemsCached = useCallback(() => {
|
||||||
|
return store.getExpandedItems();
|
||||||
|
}, [store]);
|
||||||
|
|
||||||
const getDragging = useCallback(() => {
|
const getDragging = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.getDragging(state);
|
return itemListSelectors.getDragging(state);
|
||||||
}, [state]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const getSelected = useCallback(() => {
|
const getSelected = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
const selectedItems = itemListSelectors.getSelected(state);
|
const selectedItems = itemListSelectors.getSelected(state);
|
||||||
const data = getDataFn ? getDataFn() : [];
|
const data = getDataFn ? getDataFn() : [];
|
||||||
return sortByDataOrder(selectedItems, data, extractRowIdFn, false);
|
return sortByDataOrder(selectedItems, data, extractRowIdFn, false);
|
||||||
}, [state, getDataFn, extractRowIdFn]);
|
}, [getCurrentState, getDataFn, extractRowIdFn]);
|
||||||
|
|
||||||
const getDraggingIds = useCallback(() => {
|
const getDraggingIds = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return Array.from(state.dragging);
|
return Array.from(state.dragging);
|
||||||
}, [state.dragging]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const getExpandedIds = useCallback(() => {
|
const getExpandedIds = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return Array.from(state.expanded);
|
return Array.from(state.expanded);
|
||||||
}, [state.expanded]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const getSelectedIds = useCallback(() => {
|
const getSelectedIds = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
const selectedIds = Array.from(state.selected);
|
const selectedIds = Array.from(state.selected);
|
||||||
const data = getDataFn ? getDataFn() : [];
|
const data = getDataFn ? getDataFn() : [];
|
||||||
return sortByDataOrder(selectedIds, data, extractRowIdFn, true);
|
return sortByDataOrder(selectedIds, data, extractRowIdFn, true);
|
||||||
}, [state.selected, getDataFn, extractRowIdFn]);
|
}, [getCurrentState, getDataFn, extractRowIdFn]);
|
||||||
|
|
||||||
const clearExpanded = useCallback(() => {
|
const clearExpanded = useCallback(() => {
|
||||||
dispatch({ type: 'CLEAR_EXPANDED' });
|
store.dispatch({ type: 'CLEAR_EXPANDED' });
|
||||||
}, []);
|
}, [store]);
|
||||||
|
|
||||||
const clearDragging = useCallback(() => {
|
const clearDragging = useCallback(() => {
|
||||||
dispatch({ type: 'CLEAR_DRAGGING' });
|
store.dispatch({ type: 'CLEAR_DRAGGING' });
|
||||||
}, []);
|
}, [store]);
|
||||||
|
|
||||||
const clearSelected = useCallback(() => {
|
const clearSelected = useCallback(() => {
|
||||||
dispatch({ type: 'CLEAR_SELECTED' });
|
store.dispatch({ type: 'CLEAR_SELECTED' });
|
||||||
}, []);
|
}, [store]);
|
||||||
|
|
||||||
const clearAll = useCallback(() => {
|
const clearAll = useCallback(() => {
|
||||||
dispatch({ type: 'CLEAR_ALL' });
|
store.dispatch({ type: 'CLEAR_ALL' });
|
||||||
}, []);
|
}, [store]);
|
||||||
|
|
||||||
const getVersion = useCallback(() => {
|
const getVersion = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.getVersion(state);
|
return itemListSelectors.getVersion(state);
|
||||||
}, [state]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const hasExpanded = useCallback(() => {
|
const hasExpanded = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.hasAnyExpanded(state);
|
return itemListSelectors.hasAnyExpanded(state);
|
||||||
}, [state]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const hasDragging = useCallback(() => {
|
const hasDragging = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.hasAnyDragging(state);
|
return itemListSelectors.hasAnyDragging(state);
|
||||||
}, [state]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const hasSelected = useCallback(() => {
|
const hasSelected = useCallback(() => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.hasAnySelected(state);
|
return itemListSelectors.hasAnySelected(state);
|
||||||
}, [state]);
|
}, [getCurrentState]);
|
||||||
|
|
||||||
const isDragging = useCallback(
|
const isDragging = useCallback(
|
||||||
(rowId: string) => {
|
(rowId: string) => {
|
||||||
|
const state = getCurrentState();
|
||||||
return itemListSelectors.isDragging(state, rowId);
|
return itemListSelectors.isDragging(state, rowId);
|
||||||
},
|
},
|
||||||
[state],
|
[getCurrentState],
|
||||||
);
|
);
|
||||||
|
|
||||||
const getData = useCallback(() => {
|
const getData = useCallback(() => {
|
||||||
@@ -457,8 +587,12 @@ export const useItemListState = (
|
|||||||
[getDataFn, extractRowId],
|
[getDataFn, extractRowId],
|
||||||
);
|
);
|
||||||
|
|
||||||
return useMemo(
|
// Expose the store so components can subscribe if needed
|
||||||
() => ({
|
// Store it in the actions object for access
|
||||||
|
const actions = useMemo(() => {
|
||||||
|
const actionsObj = {
|
||||||
|
__getState: getCurrentState,
|
||||||
|
__store: store,
|
||||||
clearAll,
|
clearAll,
|
||||||
clearDragging,
|
clearDragging,
|
||||||
clearExpanded,
|
clearExpanded,
|
||||||
@@ -470,6 +604,7 @@ export const useItemListState = (
|
|||||||
getDraggingIds,
|
getDraggingIds,
|
||||||
getExpanded,
|
getExpanded,
|
||||||
getExpandedIds,
|
getExpandedIds,
|
||||||
|
getExpandedItemsCached,
|
||||||
getSelected,
|
getSelected,
|
||||||
getSelectedIds,
|
getSelectedIds,
|
||||||
getVersion,
|
getVersion,
|
||||||
@@ -484,33 +619,41 @@ export const useItemListState = (
|
|||||||
setSelected,
|
setSelected,
|
||||||
toggleExpanded,
|
toggleExpanded,
|
||||||
toggleSelected,
|
toggleSelected,
|
||||||
}),
|
} as ItemListStateActions & {
|
||||||
[
|
__getState: () => ItemListState;
|
||||||
clearAll,
|
__store: ItemListStateStore;
|
||||||
clearDragging,
|
};
|
||||||
clearExpanded,
|
return actionsObj;
|
||||||
clearSelected,
|
}, [
|
||||||
extractRowIdFn,
|
clearAll,
|
||||||
findItemIndex,
|
clearDragging,
|
||||||
getData,
|
clearExpanded,
|
||||||
getDragging,
|
clearSelected,
|
||||||
getDraggingIds,
|
extractRowIdFn,
|
||||||
getExpanded,
|
findItemIndex,
|
||||||
getExpandedIds,
|
getData,
|
||||||
getSelected,
|
getDragging,
|
||||||
getSelectedIds,
|
getDraggingIds,
|
||||||
getVersion,
|
getExpanded,
|
||||||
hasDragging,
|
getExpandedIds,
|
||||||
hasExpanded,
|
getExpandedItemsCached,
|
||||||
hasSelected,
|
getSelected,
|
||||||
isDragging,
|
getSelectedIds,
|
||||||
isExpanded,
|
getVersion,
|
||||||
isSelected,
|
hasDragging,
|
||||||
setDragging,
|
hasExpanded,
|
||||||
setExpanded,
|
hasSelected,
|
||||||
setSelected,
|
isDragging,
|
||||||
toggleExpanded,
|
isExpanded,
|
||||||
toggleSelected,
|
isSelected,
|
||||||
],
|
setDragging,
|
||||||
);
|
setExpanded,
|
||||||
|
setSelected,
|
||||||
|
toggleExpanded,
|
||||||
|
toggleSelected,
|
||||||
|
store,
|
||||||
|
getCurrentState,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return actions;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ import styles from './item-detail-list.module.css';
|
|||||||
|
|
||||||
import { ItemDetail } from '/@/renderer/components/item-detail/item-detail';
|
import { ItemDetail } from '/@/renderer/components/item-detail/item-detail';
|
||||||
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
import { ExpandedListItem } from '/@/renderer/components/item-list/expanded-list-item';
|
||||||
import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import {
|
||||||
|
useItemListState,
|
||||||
|
useItemListStateSubscription,
|
||||||
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { useElementSize } from '/@/shared/hooks/use-element-size';
|
import { useElementSize } from '/@/shared/hooks/use-element-size';
|
||||||
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
import { useMergedRef } from '/@/shared/hooks/use-merged-ref';
|
||||||
import { LibraryItem } from '/@/shared/types/domain-types';
|
import { LibraryItem } from '/@/shared/types/domain-types';
|
||||||
@@ -118,15 +121,17 @@ export const ItemDetailList = ({
|
|||||||
}
|
}
|
||||||
}, [itemDetailRef, initialize]);
|
}, [itemDetailRef, initialize]);
|
||||||
|
|
||||||
const hasExpanded = internalState.hasExpanded();
|
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
|
||||||
|
state ? state.expanded.size > 0 : false,
|
||||||
|
);
|
||||||
|
|
||||||
const handleExpand = useCallback(
|
const handleExpand = useCallback(
|
||||||
(_e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => {
|
(_e: MouseEvent<HTMLDivElement>, item: unknown, itemType: LibraryItem) => {
|
||||||
if (item && typeof item === 'object' && 'id' in item && 'serverId' in item) {
|
if (item && typeof item === 'object' && 'id' in item && 'serverId' in item) {
|
||||||
internalState.toggleExpanded({
|
internalState.toggleExpanded({
|
||||||
|
_itemType: itemType,
|
||||||
_serverId: item.serverId as string,
|
_serverId: item.serverId as string,
|
||||||
id: item.id as string,
|
id: item.id as string,
|
||||||
itemType: itemType,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import {
|
|||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItemWithRequiredProperties,
|
ItemListStateItemWithRequiredProperties,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
|
useItemListStateSubscription,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
import { ItemControls, ItemListHandle } from '/@/renderer/components/item-list/types';
|
||||||
import { animationProps } from '/@/shared/components/animations/animation-props';
|
import { animationProps } from '/@/shared/components/animations/animation-props';
|
||||||
@@ -320,8 +321,6 @@ const BaseItemGridList = ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const hasExpanded = internalState.hasExpanded();
|
|
||||||
|
|
||||||
const tableMetaRef = useRef<null | {
|
const tableMetaRef = useRef<null | {
|
||||||
columnCount: number;
|
columnCount: number;
|
||||||
itemHeight: number;
|
itemHeight: number;
|
||||||
@@ -691,13 +690,7 @@ const BaseItemGridList = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</AutoSizer>
|
</AutoSizer>
|
||||||
<AnimatePresence>
|
<ExpandedContainer internalState={internalState} itemType={itemType} />
|
||||||
{hasExpanded && (
|
|
||||||
<ExpandedListContainer>
|
|
||||||
<ExpandedListItem internalState={internalState} itemType={itemType} />
|
|
||||||
</ExpandedListContainer>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -754,3 +747,25 @@ const ListComponent = memo((props: ListChildComponentProps<GridItemProps>) => {
|
|||||||
export const ItemGridList = memo(BaseItemGridList);
|
export const ItemGridList = memo(BaseItemGridList);
|
||||||
|
|
||||||
ItemGridList.displayName = 'ItemGridList';
|
ItemGridList.displayName = 'ItemGridList';
|
||||||
|
|
||||||
|
const ExpandedContainer = ({
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
}: {
|
||||||
|
internalState: ItemListStateActions;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}) => {
|
||||||
|
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
|
||||||
|
state ? state.expanded.size > 0 : false,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{hasExpanded && (
|
||||||
|
<ExpandedListContainer>
|
||||||
|
<ExpandedListItem internalState={internalState} itemType={itemType} />
|
||||||
|
</ExpandedListContainer>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ import styles from './item-table-list-column.module.css';
|
|||||||
|
|
||||||
import i18n from '/@/i18n/i18n';
|
import i18n from '/@/i18n/i18n';
|
||||||
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
import { getDraggedItems } from '/@/renderer/components/item-list/helpers/get-dragged-items';
|
||||||
|
import {
|
||||||
|
useItemDraggingState,
|
||||||
|
useItemSelectionState,
|
||||||
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { ActionsColumn } from '/@/renderer/components/item-list/item-table-list/columns/actions-column';
|
import { ActionsColumn } from '/@/renderer/components/item-list/item-table-list/columns/actions-column';
|
||||||
import { AlbumArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-artists-column';
|
import { AlbumArtistsColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-artists-column';
|
||||||
import { AlbumColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-column';
|
import { AlbumColumn } from '/@/renderer/components/item-list/item-table-list/columns/album-column';
|
||||||
@@ -294,10 +298,16 @@ export const ItemTableListColumn = (props: ItemTableListColumn) => {
|
|||||||
isEnabled: shouldEnableDrag,
|
isEnabled: shouldEnableDrag,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isDragging =
|
const itemRowId =
|
||||||
item && typeof item === 'object' && 'id' in item && props.internalState
|
item && typeof item === 'object' && 'id' in item && props.internalState
|
||||||
? props.internalState.isDragging((item as any).id)
|
? props.internalState.extractRowId(item)
|
||||||
: isDraggingLocal;
|
: undefined;
|
||||||
|
const isDraggingState = useItemDraggingState(
|
||||||
|
props.internalState,
|
||||||
|
itemRowId ||
|
||||||
|
(item && typeof item === 'object' && 'id' in item ? (item as any).id : undefined),
|
||||||
|
);
|
||||||
|
const isDragging = props.internalState ? isDraggingState : isDraggingLocal;
|
||||||
|
|
||||||
const controls = props.controls;
|
const controls = props.controls;
|
||||||
|
|
||||||
@@ -452,10 +462,11 @@ export const TableColumnTextContainer = (
|
|||||||
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
|
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
|
||||||
const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex;
|
const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex;
|
||||||
const item = isDataRow ? props.data[props.rowIndex] : null;
|
const item = isDataRow ? props.data[props.rowIndex] : null;
|
||||||
const isSelected =
|
const itemRowId =
|
||||||
item && typeof item === 'object' && 'id' in item
|
item && typeof item === 'object' && 'id' in item
|
||||||
? props.internalState.isSelected(props.internalState.extractRowId(item) || '')
|
? props.internalState.extractRowId(item)
|
||||||
: false;
|
: undefined;
|
||||||
|
const isSelected = useItemSelectionState(props.internalState, itemRowId || undefined);
|
||||||
|
|
||||||
const isDragging = props.isDragging ?? false;
|
const isDragging = props.isDragging ?? false;
|
||||||
const mergedRef = useMergedRef(containerRef, props.dragRef ?? null);
|
const mergedRef = useMergedRef(containerRef, props.dragRef ?? null);
|
||||||
@@ -664,10 +675,11 @@ export const TableColumnContainer = (
|
|||||||
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
|
const isDataRow = props.enableHeader ? props.rowIndex > 0 : true;
|
||||||
const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex;
|
const dataIndex = props.enableHeader ? props.rowIndex - 1 : props.rowIndex;
|
||||||
const item = isDataRow ? props.data[props.rowIndex] : null;
|
const item = isDataRow ? props.data[props.rowIndex] : null;
|
||||||
const isSelected =
|
const itemRowId =
|
||||||
item && typeof item === 'object' && 'id' in item
|
item && typeof item === 'object' && 'id' in item
|
||||||
? props.internalState.isSelected(props.internalState.extractRowId(item) || '')
|
? props.internalState.extractRowId(item)
|
||||||
: false;
|
: undefined;
|
||||||
|
const isSelected = useItemSelectionState(props.internalState, itemRowId || undefined);
|
||||||
|
|
||||||
const isDragging = props.isDragging ?? false;
|
const isDragging = props.isDragging ?? false;
|
||||||
const mergedRef = useMergedRef(containerRef, props.dragRef ?? null);
|
const mergedRef = useMergedRef(containerRef, props.dragRef ?? null);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import {
|
|||||||
ItemListStateActions,
|
ItemListStateActions,
|
||||||
ItemListStateItemWithRequiredProperties,
|
ItemListStateItemWithRequiredProperties,
|
||||||
useItemListState,
|
useItemListState,
|
||||||
|
useItemListStateSubscription,
|
||||||
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
|
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
|
||||||
import { useStickyTableGroupRows } from '/@/renderer/components/item-list/item-table-list/hooks/use-sticky-table-group-rows';
|
import { useStickyTableGroupRows } from '/@/renderer/components/item-list/item-table-list/hooks/use-sticky-table-group-rows';
|
||||||
@@ -1583,8 +1584,6 @@ const BaseItemTableList = ({
|
|||||||
|
|
||||||
const internalState = useItemListState(getDataFn, extractRowId);
|
const internalState = useItemListState(getDataFn, extractRowId);
|
||||||
|
|
||||||
const hasExpanded = internalState.hasExpanded();
|
|
||||||
|
|
||||||
// Helper function to get ItemListStateItemWithRequiredProperties (rowId is separate, not part of item)
|
// Helper function to get ItemListStateItemWithRequiredProperties (rowId is separate, not part of item)
|
||||||
const getStateItem = useCallback(
|
const getStateItem = useCallback(
|
||||||
(item: any): ItemListStateItemWithRequiredProperties | null => {
|
(item: any): ItemListStateItemWithRequiredProperties | null => {
|
||||||
@@ -2169,17 +2168,33 @@ const BaseItemTableList = ({
|
|||||||
totalColumnCount={totalColumnCount}
|
totalColumnCount={totalColumnCount}
|
||||||
totalRowCount={totalRowCount}
|
totalRowCount={totalRowCount}
|
||||||
/>
|
/>
|
||||||
<AnimatePresence initial={false}>
|
<ExpandedContainer internalState={internalState} itemType={itemType} />
|
||||||
{hasExpanded && (
|
|
||||||
<ExpandedListContainer>
|
|
||||||
<ExpandedListItem internalState={internalState} itemType={itemType} />
|
|
||||||
</ExpandedListContainer>
|
|
||||||
)}
|
|
||||||
</AnimatePresence>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ItemTableList = memo(BaseItemTableList);
|
export const ItemTableList = memo(BaseItemTableList);
|
||||||
|
|
||||||
|
const ExpandedContainer = ({
|
||||||
|
internalState,
|
||||||
|
itemType,
|
||||||
|
}: {
|
||||||
|
internalState: ItemListStateActions;
|
||||||
|
itemType: LibraryItem;
|
||||||
|
}) => {
|
||||||
|
const hasExpanded = useItemListStateSubscription(internalState, (state) =>
|
||||||
|
state ? state.expanded.size > 0 : false,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
{hasExpanded && (
|
||||||
|
<ExpandedListContainer>
|
||||||
|
<ExpandedListItem internalState={internalState} itemType={itemType} />
|
||||||
|
</ExpandedListContainer>
|
||||||
|
)}
|
||||||
|
</AnimatePresence>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
ItemTableList.displayName = 'ItemTableList';
|
ItemTableList.displayName = 'ItemTableList';
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { useId, useMemo } from 'react';
|
import { memo, useId, useMemo } from 'react';
|
||||||
|
|
||||||
import styles from './simple-item-table.module.css';
|
import styles from './simple-item-table.module.css';
|
||||||
|
|
||||||
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
|
import { createExtractRowId } from '/@/renderer/components/item-list/helpers/extract-row-id';
|
||||||
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
import { useDefaultItemListControls } from '/@/renderer/components/item-list/helpers/item-list-controls';
|
||||||
import { useItemListState } from '/@/renderer/components/item-list/helpers/item-list-state';
|
import {
|
||||||
|
ItemListStateActions,
|
||||||
|
useItemListState,
|
||||||
|
useItemSelectionState,
|
||||||
|
} from '/@/renderer/components/item-list/helpers/item-list-state';
|
||||||
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
|
import { parseTableColumns } from '/@/renderer/components/item-list/helpers/parse-table-columns';
|
||||||
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
import { TableItemProps } from '/@/renderer/components/item-list/item-table-list/item-table-list';
|
||||||
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
import { ItemTableListColumn } from '/@/renderer/components/item-list/item-table-list/item-table-list-column';
|
||||||
@@ -176,68 +180,115 @@ export const SimpleItemTable = ({
|
|||||||
</Table.Thead>
|
</Table.Thead>
|
||||||
)}
|
)}
|
||||||
<Table.Tbody>
|
<Table.Tbody>
|
||||||
{data.map((item, rowIndex) => {
|
{data.map((item, rowIndex) => (
|
||||||
const adjustedRowIndex = enableHeader ? rowIndex + 1 : rowIndex;
|
<SimpleItemTableRow
|
||||||
const isSelected =
|
adjustedRowIndex={enableHeader ? rowIndex + 1 : rowIndex}
|
||||||
item && typeof item === 'object' && 'id' in item
|
enableAlternateRowColors={enableAlternateRowColors}
|
||||||
? internalState.isSelected(internalState.extractRowId(item) || '')
|
enableHeader={enableHeader}
|
||||||
: false;
|
enableHorizontalBorders={enableHorizontalBorders}
|
||||||
|
enableRowHoverHighlight={enableRowHoverHighlight}
|
||||||
const isLastRow = rowIndex === data.length - 1;
|
enableVerticalBorders={enableVerticalBorders}
|
||||||
|
internalState={internalState}
|
||||||
return (
|
isLastRow={rowIndex === data.length - 1}
|
||||||
<Table.Tr
|
item={item}
|
||||||
className={clsx({
|
key={internalState.extractRowId(item) || rowIndex}
|
||||||
[styles.alternateRowEven]:
|
parsedColumns={parsedColumns}
|
||||||
enableAlternateRowColors && rowIndex % 2 === 0,
|
rowIndex={rowIndex}
|
||||||
[styles.alternateRowOdd]:
|
tableId={tableId}
|
||||||
enableAlternateRowColors && rowIndex % 2 === 1,
|
tableItemProps={tableItemProps}
|
||||||
[styles.rowHover]: enableRowHoverHighlight,
|
/>
|
||||||
[styles.rowSelected]: isSelected,
|
))}
|
||||||
[styles.withHorizontalBorder]:
|
|
||||||
enableHorizontalBorders && enableHeader && !isLastRow,
|
|
||||||
})}
|
|
||||||
data-row-index={`${tableId}-${adjustedRowIndex}`}
|
|
||||||
key={internalState.extractRowId(item) || rowIndex}
|
|
||||||
>
|
|
||||||
{parsedColumns.map((column, columnIndex) => {
|
|
||||||
const isLastColumn = columnIndex === parsedColumns.length - 1;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table.Td
|
|
||||||
className={clsx({
|
|
||||||
[styles.withVerticalBorder]:
|
|
||||||
enableVerticalBorders && !isLastColumn,
|
|
||||||
})}
|
|
||||||
key={column.id}
|
|
||||||
style={{
|
|
||||||
textAlign:
|
|
||||||
column.align === 'start'
|
|
||||||
? 'left'
|
|
||||||
: column.align === 'end'
|
|
||||||
? 'right'
|
|
||||||
: 'center',
|
|
||||||
width: column.width,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ItemTableListColumn
|
|
||||||
{...tableItemProps}
|
|
||||||
ariaAttributes={{
|
|
||||||
'aria-colindex': columnIndex + 1,
|
|
||||||
role: 'gridcell',
|
|
||||||
}}
|
|
||||||
columnIndex={columnIndex}
|
|
||||||
rowIndex={adjustedRowIndex}
|
|
||||||
style={{ width: column.width }}
|
|
||||||
/>
|
|
||||||
</Table.Td>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Table.Tr>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</Table.Tbody>
|
</Table.Tbody>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
interface SimpleItemTableRowProps {
|
||||||
|
adjustedRowIndex: number;
|
||||||
|
enableAlternateRowColors: boolean;
|
||||||
|
enableHeader: boolean;
|
||||||
|
enableHorizontalBorders: boolean;
|
||||||
|
enableRowHoverHighlight: boolean;
|
||||||
|
enableVerticalBorders: boolean;
|
||||||
|
internalState: ItemListStateActions;
|
||||||
|
isLastRow: boolean;
|
||||||
|
item: unknown;
|
||||||
|
parsedColumns: ReturnType<typeof parseTableColumns>;
|
||||||
|
rowIndex: number;
|
||||||
|
tableId: string;
|
||||||
|
tableItemProps: TableItemProps;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SimpleItemTableRow = memo(
|
||||||
|
({
|
||||||
|
adjustedRowIndex,
|
||||||
|
enableAlternateRowColors,
|
||||||
|
enableHeader,
|
||||||
|
enableHorizontalBorders,
|
||||||
|
enableRowHoverHighlight,
|
||||||
|
enableVerticalBorders,
|
||||||
|
internalState,
|
||||||
|
isLastRow,
|
||||||
|
item,
|
||||||
|
parsedColumns,
|
||||||
|
rowIndex,
|
||||||
|
tableId,
|
||||||
|
tableItemProps,
|
||||||
|
}: SimpleItemTableRowProps) => {
|
||||||
|
const itemRowId =
|
||||||
|
item && typeof item === 'object' && 'id' in item
|
||||||
|
? internalState.extractRowId(item)
|
||||||
|
: undefined;
|
||||||
|
const isSelected = useItemSelectionState(internalState, itemRowId || undefined);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table.Tr
|
||||||
|
className={clsx({
|
||||||
|
[styles.alternateRowEven]: enableAlternateRowColors && rowIndex % 2 === 0,
|
||||||
|
[styles.alternateRowOdd]: enableAlternateRowColors && rowIndex % 2 === 1,
|
||||||
|
[styles.rowHover]: enableRowHoverHighlight,
|
||||||
|
[styles.rowSelected]: isSelected,
|
||||||
|
[styles.withHorizontalBorder]:
|
||||||
|
enableHorizontalBorders && enableHeader && !isLastRow,
|
||||||
|
})}
|
||||||
|
data-row-index={`${tableId}-${adjustedRowIndex}`}
|
||||||
|
>
|
||||||
|
{parsedColumns.map((column, columnIndex) => {
|
||||||
|
const isLastColumn = columnIndex === parsedColumns.length - 1;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table.Td
|
||||||
|
className={clsx({
|
||||||
|
[styles.withVerticalBorder]: enableVerticalBorders && !isLastColumn,
|
||||||
|
})}
|
||||||
|
key={column.id}
|
||||||
|
style={{
|
||||||
|
textAlign:
|
||||||
|
column.align === 'start'
|
||||||
|
? 'left'
|
||||||
|
: column.align === 'end'
|
||||||
|
? 'right'
|
||||||
|
: 'center',
|
||||||
|
width: column.width,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ItemTableListColumn
|
||||||
|
{...tableItemProps}
|
||||||
|
ariaAttributes={{
|
||||||
|
'aria-colindex': columnIndex + 1,
|
||||||
|
role: 'gridcell',
|
||||||
|
}}
|
||||||
|
columnIndex={columnIndex}
|
||||||
|
rowIndex={adjustedRowIndex}
|
||||||
|
style={{ width: column.width }}
|
||||||
|
/>
|
||||||
|
</Table.Td>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Table.Tr>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
SimpleItemTableRow.displayName = 'SimpleItemTableRow';
|
||||||
|
|||||||
Reference in New Issue
Block a user