From c8ad87d102318581cd2d76b145f56b5fdbf8db42 Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Tue, 3 Jan 2023 15:02:38 +0100 Subject: [PATCH 1/2] Add ability to pass a tab count in `` and `` --- docs/TabbedForm.md | 3 +- docs/TabbedShowLayout.md | 1 + examples/demo/src/orders/OrderList.tsx | 37 ++++------------ examples/demo/src/products/ProductEdit.tsx | 39 ++++++----------- examples/simple/src/posts/PostEdit.tsx | 12 +++++- examples/simple/src/posts/PostShow.tsx | 12 +++++- packages/ra-ui-materialui/src/detail/Tab.tsx | 43 ++++++++++++------- .../src/detail/TabbedShowLayout.stories.tsx | 23 ++++++++++ .../src/field/ReferenceManyCount.tsx | 1 + .../ra-ui-materialui/src/form/FormTab.tsx | 6 ++- .../src/form/FormTabHeader.tsx | 19 ++++++-- .../src/form/TabbedForm.stories.tsx | 15 +++++++ 12 files changed, 133 insertions(+), 78 deletions(-) diff --git a/docs/TabbedForm.md b/docs/TabbedForm.md index 2e199a124e5..fc0ab6947eb 100644 --- a/docs/TabbedForm.md +++ b/docs/TabbedForm.md @@ -471,10 +471,11 @@ export const TagEdit = () => ( ## `` -`` expect `` elements as children. `` elements accept four props: +`` expect `` elements as children. `` elements accept five props: - `label`: the label of the tab - `path`: the path of the tab in the URL (ignored when `syncWithLocation={false}`) +- `count`: the number of items in the tab (dislayed close to the label) - `sx`: custom styles to apply to the tab - `children`: the content of the tab (usually a list of inputs) diff --git a/docs/TabbedShowLayout.md b/docs/TabbedShowLayout.md index 0922456cfe0..1b8b0a1410b 100644 --- a/docs/TabbedShowLayout.md +++ b/docs/TabbedShowLayout.md @@ -76,6 +76,7 @@ It accepts the following props: - `label`: The string displayed for each tab - `icon`: The icon to show before the label (optional). Must be a component. - `path`: The string used for custom urls (optional) +- `count`: the number of items in the tab (dislayed close to the label) ```jsx // in src/posts.js diff --git a/examples/demo/src/orders/OrderList.tsx b/examples/demo/src/orders/OrderList.tsx index 1c8aced907a..4e918164889 100644 --- a/examples/demo/src/orders/OrderList.tsx +++ b/examples/demo/src/orders/OrderList.tsx @@ -3,6 +3,7 @@ import { Fragment, useCallback } from 'react'; import { AutocompleteInput, BooleanField, + Count, DatagridConfigurable, DateField, DateInput, @@ -72,37 +73,12 @@ const tabs = [ { id: 'cancelled', name: 'cancelled' }, ]; -const useGetTotals = (filterValues: any) => { - const { total: totalOrdered } = useGetList('commands', { - pagination: { perPage: 1, page: 1 }, - sort: { field: 'id', order: 'ASC' }, - filter: { ...filterValues, status: 'ordered' }, - }); - const { total: totalDelivered } = useGetList('commands', { - pagination: { perPage: 1, page: 1 }, - sort: { field: 'id', order: 'ASC' }, - filter: { ...filterValues, status: 'delivered' }, - }); - const { total: totalCancelled } = useGetList('commands', { - pagination: { perPage: 1, page: 1 }, - sort: { field: 'id', order: 'ASC' }, - filter: { ...filterValues, status: 'cancelled' }, - }); - - return { - ordered: totalOrdered, - delivered: totalDelivered, - cancelled: totalCancelled, - }; -}; - const TabbedDatagrid = () => { const listContext = useListContext(); const { filterValues, setFilters, displayedFilters } = listContext; const isXSmall = useMediaQuery(theme => theme.breakpoints.down('sm') ); - const totals = useGetTotals(filterValues) as any; const handleChange = useCallback( (event: React.ChangeEvent<{}>, value: any) => { @@ -129,9 +105,14 @@ const TabbedDatagrid = () => { + {choice.name} ( + + ) + } value={choice.id} /> diff --git a/examples/demo/src/products/ProductEdit.tsx b/examples/demo/src/products/ProductEdit.tsx index fa161df6790..c35da562fa7 100644 --- a/examples/demo/src/products/ProductEdit.tsx +++ b/examples/demo/src/products/ProductEdit.tsx @@ -6,13 +6,12 @@ import { EditButton, Pagination, ReferenceManyField, + ReferenceManyCount, required, TabbedForm, TextField, TextInput, useRecordContext, - useGetManyReference, - useTranslate, } from 'react-admin'; import { RichTextInput } from 'ra-input-rich-text'; @@ -52,7 +51,17 @@ const ProductEdit = () => ( > - + + } + path="reviews" + > ( - + ); const req = [required()]; -const ReviewsFormTab = (props: any) => { - const record = useRecordContext(); - const { isLoading, total } = useGetManyReference( - 'reviews', - { - target: 'product_id', - id: record.id, - pagination: { page: 1, perPage: 25 }, - sort: { field: 'id', order: 'DESC' }, - }, - { - enabled: !!record, - } - ); - const translate = useTranslate(); - let label = translate('resources.products.tabs.reviews'); - if (!isLoading) { - label += ` (${total})`; - } - return ; -}; - export default ProductEdit; diff --git a/examples/simple/src/posts/PostEdit.tsx b/examples/simple/src/posts/PostEdit.tsx index 3f920215c3f..16af0bffb17 100644 --- a/examples/simple/src/posts/PostEdit.tsx +++ b/examples/simple/src/posts/PostEdit.tsx @@ -18,6 +18,7 @@ import { ImageInput, NumberInput, ReferenceManyField, + ReferenceManyCount, ReferenceInput, SelectInput, SimpleFormIterator, @@ -239,7 +240,16 @@ const PostEdit = () => { - + + } + > { - + + } + > ( - - ); + const renderHeader = () => { + let tabLabel = + typeof label === 'string' ? translate(label, { _: label }) : label; + if (count) { + tabLabel = ( + + {tabLabel} ({count}) + + ); + } + + return ( + + ); + }; const renderContent = () => ( @@ -115,10 +124,11 @@ export const Tab = ({ }; Tab.propTypes = { + children: PropTypes.node, className: PropTypes.string, contentClassName: PropTypes.string, - children: PropTypes.node, context: PropTypes.oneOf(['header', 'content']), + count: PropTypes.node, icon: PropTypes.element, label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]) .isRequired, @@ -131,6 +141,7 @@ export interface TabProps extends Omit { children: ReactNode; contentClassName?: string; context?: 'header' | 'content'; + count?: ReactNode; className?: string; divider?: ReactNode; icon?: ReactElement; diff --git a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx index 518f54eaddf..bcef7257a6c 100644 --- a/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx +++ b/packages/ra-ui-materialui/src/detail/TabbedShowLayout.stories.tsx @@ -42,6 +42,29 @@ export const Basic = () => ( ); +export const Count = () => ( + + + + + + + + + + + + + + + + + + + + +); + const BookTitle = () => { const record = useRecordContext(); return record ? {record.title} : null; diff --git a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx index 4eb16c91114..f05a2c78198 100644 --- a/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx +++ b/packages/ra-ui-materialui/src/field/ReferenceManyCount.tsx @@ -76,6 +76,7 @@ export const ReferenceManyCount = (props: ReferenceManyCountProps) => { })}`, }} variant="body2" + component="span" onClick={e => e.stopPropagation()} {...rest} > diff --git a/packages/ra-ui-materialui/src/form/FormTab.tsx b/packages/ra-ui-materialui/src/form/FormTab.tsx index 68d0c811a18..5c9e2c7f80d 100644 --- a/packages/ra-ui-materialui/src/form/FormTab.tsx +++ b/packages/ra-ui-materialui/src/form/FormTab.tsx @@ -8,9 +8,10 @@ import { FormTabHeader } from './FormTabHeader'; export const FormTab = (props: FormTabProps) => { const { + children, className, contentClassName, - children, + count, hidden, icon, intent, @@ -26,6 +27,7 @@ export const FormTab = (props: FormTabProps) => { const renderHeader = () => ( + {tabLabel} ({count}) + + ); + } return ( { className?: string; + count?: ReactNode; hidden?: boolean; icon?: ReactElement; intent?: 'header' | 'content'; @@ -68,6 +78,7 @@ interface FormTabHeaderProps extends Omit { FormTabHeader.propTypes = { className: PropTypes.string, contentClassName: PropTypes.string, + count: PropTypes.node, children: PropTypes.node, intent: PropTypes.oneOf(['header', 'content']), hidden: PropTypes.bool, diff --git a/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx b/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx index 389b3fac798..95085cb09c9 100644 --- a/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx +++ b/packages/ra-ui-materialui/src/form/TabbedForm.stories.tsx @@ -89,3 +89,18 @@ export const NoToolbar = () => ( ); + +export const Count = () => ( + + + + + + + + + + + + +); From fe32f3c9c103017c9af9512baca026fc3e8a083a Mon Sep 17 00:00:00 2001 From: fzaninotto Date: Wed, 4 Jan 2023 18:13:55 +0100 Subject: [PATCH 2/2] Review --- examples/demo/src/orders/OrderList.tsx | 16 +++++++++------- packages/ra-ui-materialui/src/detail/Tab.tsx | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/examples/demo/src/orders/OrderList.tsx b/examples/demo/src/orders/OrderList.tsx index 4e918164889..3ead7ce59e8 100644 --- a/examples/demo/src/orders/OrderList.tsx +++ b/examples/demo/src/orders/OrderList.tsx @@ -7,20 +7,19 @@ import { DatagridConfigurable, DateField, DateInput, + ExportButton, + FilterButton, List, NullableBooleanInput, NumberField, - ReferenceInput, ReferenceField, + ReferenceInput, SearchInput, + SelectColumnsButton, TextField, TextInput, - useGetList, - useListContext, TopToolbar, - SelectColumnsButton, - FilterButton, - ExportButton, + useListContext, } from 'react-admin'; import { useMediaQuery, Divider, Tabs, Tab, Theme } from '@mui/material'; @@ -108,7 +107,10 @@ const TabbedDatagrid = () => { {choice.name} ( ) diff --git a/packages/ra-ui-materialui/src/detail/Tab.tsx b/packages/ra-ui-materialui/src/detail/Tab.tsx index 70dc01c3968..b767af21f6c 100644 --- a/packages/ra-ui-materialui/src/detail/Tab.tsx +++ b/packages/ra-ui-materialui/src/detail/Tab.tsx @@ -79,7 +79,7 @@ export const Tab = ({ const renderHeader = () => { let tabLabel = typeof label === 'string' ? translate(label, { _: label }) : label; - if (count) { + if (count !== undefined) { tabLabel = ( {tabLabel} ({count})