fix(striker-ui): revise header override and item checkbox in List

main
Tsu-ba-me 2 years ago
parent c32471658d
commit 3abd21df72
  1. 200
      striker-ui/components/List.tsx

@ -14,77 +14,71 @@ import {
SxProps, SxProps,
Theme, Theme,
} from '@mui/material'; } from '@mui/material';
import { FC, ReactNode, useMemo } from 'react'; import { FC, ReactNode, useCallback, useMemo, useState } from 'react';
import { v4 as uuidv4 } from 'uuid'; import { v4 as uuidv4 } from 'uuid';
import { BLUE, DIVIDER, GREY, RED } from '../lib/consts/DEFAULT_THEME'; import { BLUE, GREY, RED } from '../lib/consts/DEFAULT_THEME';
import Checkbox, { CheckboxProps } from './Checkbox'; import Checkbox, { CheckboxProps } from './Checkbox';
import Divider from './Divider';
import FlexBox, { FlexBoxProps } from './FlexBox'; import FlexBox, { FlexBoxProps } from './FlexBox';
import IconButton, { IconButtonProps } from './IconButton'; import IconButton, { IconButtonProps } from './IconButton';
import { BodyText } from './Text'; import { BodyText } from './Text';
type ListOptionalProps<T = unknown> = { type OnCheckboxChange = Exclude<CheckboxProps['onChange'], undefined>;
header?: ReactNode;
isAllowAddItem?: boolean; type ListOptionalPropsWithDefaults<T = unknown> = {
isAllowCheckItem?: boolean; allowCheckAll?: boolean;
isAllowDelete?: boolean; allowEdit?: boolean;
isAllowEdit?: boolean; edit?: boolean;
isAllowEditItem?: boolean; initialCheckAll?: boolean;
isEdit?: boolean; insertHeader?: boolean;
isScroll?: boolean;
listEmpty?: ReactNode;
listItemKeyPrefix?: string; listItemKeyPrefix?: string;
listItemProps?: MUIListItemProps; listItemProps?: MUIListItemProps;
listProps?: MUIListProps; listProps?: MUIListProps;
renderListItem?: (key: string, value: T) => ReactNode;
scroll?: boolean;
};
type ListOptionalPropsWithoutDefaults<T = unknown> = {
allowAddItem?: boolean;
allowCheckItem?: boolean;
allowDelete?: boolean;
allowEditItem?: boolean;
header?: ReactNode;
listEmpty?: ReactNode;
onAdd?: IconButtonProps['onClick']; onAdd?: IconButtonProps['onClick'];
onDelete?: IconButtonProps['onClick']; onDelete?: IconButtonProps['onClick'];
onEdit?: IconButtonProps['onClick']; onEdit?: IconButtonProps['onClick'];
onItemCheckboxChange?: CheckboxProps['onChange']; onAllCheckboxChange?: CheckboxProps['onChange'];
renderListItem?: (key: string, value: T) => ReactNode; onItemCheckboxChange?: (
key: string,
...onChangeParams: Parameters<OnCheckboxChange>
) => ReturnType<OnCheckboxChange>;
renderListItemCheckboxState?: (key: string, value: T) => boolean;
}; };
type ListOptionalProps<T = unknown> = ListOptionalPropsWithDefaults<T> &
ListOptionalPropsWithoutDefaults<T>;
type ListProps<T = unknown> = FlexBoxProps & type ListProps<T = unknown> = FlexBoxProps &
ListOptionalProps<T> & { ListOptionalProps<T> & {
listItems: Record<string, T>; listItems: Record<string, T>;
}; };
const LIST_DEFAULT_PROPS: Required< const HEADER_SPACING = '.3em';
Omit< const LIST_DEFAULT_PROPS: Required<ListOptionalPropsWithDefaults> &
ListOptionalProps, ListOptionalPropsWithoutDefaults = {
| 'header'
| 'isAllowAddItem'
| 'isAllowCheckItem'
| 'isAllowDelete'
| 'isAllowEditItem'
| 'listEmpty'
| 'onAdd'
| 'onDelete'
| 'onEdit'
| 'onItemCheckboxChange'
>
> &
Pick<
ListOptionalProps,
| 'header'
| 'isAllowAddItem'
| 'isAllowCheckItem'
| 'isAllowDelete'
| 'isAllowEditItem'
| 'listEmpty'
| 'onAdd'
| 'onDelete'
| 'onEdit'
| 'onItemCheckboxChange'
> = {
header: undefined, header: undefined,
isAllowAddItem: undefined, allowAddItem: undefined,
isAllowCheckItem: undefined, allowCheckAll: false,
isAllowDelete: undefined, allowCheckItem: undefined,
isAllowEdit: false, allowDelete: undefined,
isAllowEditItem: undefined, allowEdit: false,
isEdit: false, allowEditItem: undefined,
isScroll: false, edit: false,
initialCheckAll: false,
insertHeader: true,
listEmpty: undefined, listEmpty: undefined,
listItemKeyPrefix: uuidv4(), listItemKeyPrefix: uuidv4(),
listItemProps: {}, listItemProps: {},
@ -92,15 +86,23 @@ const LIST_DEFAULT_PROPS: Required<
onAdd: undefined, onAdd: undefined,
onDelete: undefined, onDelete: undefined,
onEdit: undefined, onEdit: undefined,
onAllCheckboxChange: undefined,
onItemCheckboxChange: undefined, onItemCheckboxChange: undefined,
renderListItem: (key) => <BodyText>{key}</BodyText>, renderListItem: (key) => <BodyText>{key}</BodyText>,
renderListItemCheckboxState: undefined,
scroll: false,
}; };
const LIST_ICON_MIN_WIDTH = '56px';
const CHECK_ALL_MIN_WIDTH = `calc(${LIST_ICON_MIN_WIDTH} - ${HEADER_SPACING})`;
const List = <T,>({ const List = <T,>({
header, header,
isAllowEdit = LIST_DEFAULT_PROPS.isAllowEdit, allowCheckAll: isAllowCheckAll = LIST_DEFAULT_PROPS.allowCheckAll,
isEdit = LIST_DEFAULT_PROPS.isEdit, allowEdit: isAllowEdit = LIST_DEFAULT_PROPS.allowEdit,
isScroll = LIST_DEFAULT_PROPS.isScroll, edit: isEdit = LIST_DEFAULT_PROPS.edit,
initialCheckAll = LIST_DEFAULT_PROPS.initialCheckAll,
insertHeader: isInsertHeader = LIST_DEFAULT_PROPS.insertHeader,
listEmpty = LIST_DEFAULT_PROPS.listEmpty, listEmpty = LIST_DEFAULT_PROPS.listEmpty,
listItemKeyPrefix = LIST_DEFAULT_PROPS.listItemKeyPrefix, listItemKeyPrefix = LIST_DEFAULT_PROPS.listItemKeyPrefix,
listItemProps: { listItemProps: {
@ -112,16 +114,21 @@ const List = <T,>({
onAdd, onAdd,
onDelete, onDelete,
onEdit, onEdit,
onAllCheckboxChange,
onItemCheckboxChange, onItemCheckboxChange,
renderListItem = LIST_DEFAULT_PROPS.renderListItem, renderListItem = LIST_DEFAULT_PROPS.renderListItem,
renderListItemCheckboxState,
scroll: isScroll = LIST_DEFAULT_PROPS.scroll,
// Input props that depend on other input props. // Input props that depend on other input props.
isAllowAddItem = isAllowEdit, allowAddItem: isAllowAddItem = isAllowEdit,
isAllowCheckItem = isAllowEdit, allowCheckItem: isAllowCheckItem = isAllowEdit,
isAllowDelete = isAllowEdit, allowDelete: isAllowDelete = isAllowEdit,
isAllowEditItem = isAllowEdit, allowEditItem: isAllowEditItem = isAllowEdit,
...rootProps ...rootProps
}: ListProps<T>): ReturnType<FC<ListProps<T>>> => { }: ListProps<T>): ReturnType<FC<ListProps<T>>> => {
const [isCheckAll, setIsCheckAll] = useState<boolean>(initialCheckAll);
const addItemButton = useMemo( const addItemButton = useMemo(
() => () =>
isAllowAddItem ? ( isAllowAddItem ? (
@ -160,19 +167,49 @@ const List = <T,>({
return undefined; return undefined;
}, [isAllowEditItem, isEdit, onEdit]); }, [isAllowEditItem, isEdit, onEdit]);
const checkAllElement = useMemo(() => {
let element;
if (isEdit && isAllowCheckItem) {
element = isAllowCheckAll ? (
<MUIBox sx={{ minWidth: CHECK_ALL_MIN_WIDTH }}>
<Checkbox
checked={isCheckAll}
edge="start"
onChange={(...args) => {
const [, isChecked] = args;
onAllCheckboxChange?.call(null, ...args);
setIsCheckAll(isChecked);
}}
/>
</MUIBox>
) : (
<Divider sx={{ minWidth: CHECK_ALL_MIN_WIDTH }} />
);
}
return element;
}, [
isAllowCheckAll,
isAllowCheckItem,
isCheckAll,
isEdit,
onAllCheckboxChange,
]);
const headerElement = useMemo( const headerElement = useMemo(
() => () =>
typeof header === 'string' ? ( isInsertHeader ? (
<FlexBox row spacing=".3em" sx={{ height: '2.4em' }}> <FlexBox row spacing={HEADER_SPACING} sx={{ height: '2.4em' }}>
{checkAllElement}
{typeof header === 'string' ? (
<>
<BodyText>{header}</BodyText> <BodyText>{header}</BodyText>
<MUIBox <Divider sx={{ flexGrow: 1 }} />
sx={{ </>
borderTopColor: DIVIDER, ) : (
borderTopStyle: 'solid', header
borderTopWidth: '1px', )}
flexGrow: 1,
}}
/>
{deleteItemButton} {deleteItemButton}
{editItemButton} {editItemButton}
{addItemButton} {addItemButton}
@ -180,7 +217,14 @@ const List = <T,>({
) : ( ) : (
header header
), ),
[addItemButton, deleteItemButton, editItemButton, header], [
addItemButton,
checkAllElement,
deleteItemButton,
editItemButton,
header,
isInsertHeader,
],
); );
const listEmptyElement = useMemo( const listEmptyElement = useMemo(
() => () =>
@ -191,15 +235,23 @@ const List = <T,>({
), ),
[listEmpty], [listEmpty],
); );
const listItemCheckbox = useMemo(
() => const listItemCheckbox = useCallback(
(key: string, checked?: boolean) =>
isEdit && isAllowCheckItem ? ( isEdit && isAllowCheckItem ? (
<MUIListItemIcon> <MUIListItemIcon sx={{ minWidth: LIST_ICON_MIN_WIDTH }}>
<Checkbox edge="start" onChange={onItemCheckboxChange} /> <Checkbox
checked={checked}
edge="start"
onChange={(...args) =>
onItemCheckboxChange?.call(null, key, ...args)
}
/>
</MUIListItemIcon> </MUIListItemIcon>
) : undefined, ) : undefined,
[isAllowCheckItem, isEdit, onItemCheckboxChange], [isAllowCheckItem, isEdit, onItemCheckboxChange],
); );
const listItemElements = useMemo(() => { const listItemElements = useMemo(() => {
const entries = Object.entries(listItems); const entries = Object.entries(listItems);
@ -210,7 +262,10 @@ const List = <T,>({
key={`${listItemKeyPrefix}-${key}`} key={`${listItemKeyPrefix}-${key}`}
sx={{ paddingLeft: 0, paddingRight: 0, ...listItemSx }} sx={{ paddingLeft: 0, paddingRight: 0, ...listItemSx }}
> >
{listItemCheckbox} {listItemCheckbox(
key,
renderListItemCheckboxState?.call(null, key, value),
)}
{renderListItem(key, value)} {renderListItem(key, value)}
</MUIListItem> </MUIListItem>
)) ))
@ -222,6 +277,7 @@ const List = <T,>({
listItems, listItems,
listItemSx, listItemSx,
renderListItem, renderListItem,
renderListItemCheckboxState,
restListItemProps, restListItemProps,
]); ]);
const listScrollSx: SxProps<Theme> | undefined = useMemo( const listScrollSx: SxProps<Theme> | undefined = useMemo(

Loading…
Cancel
Save