Skip to content

Commit 80a1878

Browse files
committed
Add offline support to <Show>
1 parent 72750f7 commit 80a1878

5 files changed

Lines changed: 125 additions & 11 deletions

File tree

packages/ra-ui-materialui/src/detail/Show.spec.tsx

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,10 @@ import {
2626
TitleElement,
2727
Themed,
2828
WithRenderProp,
29+
Offline,
2930
} from './Show.stories';
3031
import { Show } from './Show';
32+
import { Alert } from '@mui/material';
3133

3234
describe('<Show />', () => {
3335
beforeEach(async () => {
@@ -226,4 +228,29 @@ describe('<Show />', () => {
226228
await screen.findByText('Foo lorem');
227229
});
228230
});
231+
it('should render the default offline component node when offline', async () => {
232+
const { rerender } = render(<Offline isOnline={false} />);
233+
await screen.findByText('No connectivity. Could not fetch data.');
234+
rerender(<Offline isOnline={true} />);
235+
await screen.findByText('War and Peace');
236+
expect(
237+
screen.queryByText('No connectivity. Could not fetch data.')
238+
).toBeNull();
239+
rerender(<Offline isOnline={false} />);
240+
await screen.findByText('You are offline, the data may be outdated');
241+
});
242+
it('should render the custom offline component node when offline', async () => {
243+
const CustomOffline = () => {
244+
return <Alert severity="warning">You are offline!</Alert>;
245+
};
246+
const { rerender } = render(
247+
<Offline isOnline={false} offline={<CustomOffline />} />
248+
);
249+
await screen.findByText('You are offline!');
250+
rerender(<Offline isOnline={true} offline={<CustomOffline />} />);
251+
await screen.findByText('War and Peace');
252+
expect(screen.queryByText('You are offline!')).toBeNull();
253+
rerender(<Offline isOnline={false} offline={<CustomOffline />} />);
254+
await screen.findByText('You are offline, the data may be outdated');
255+
});
229256
});

packages/ra-ui-materialui/src/detail/Show.stories.tsx

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
import * as React from 'react';
22
import { Admin } from 'react-admin';
3-
import { Resource, useRecordContext, TestMemoryRouter } from 'ra-core';
4-
import { Box, Card, Stack, ThemeOptions } from '@mui/material';
3+
import {
4+
Resource,
5+
useRecordContext,
6+
TestMemoryRouter,
7+
IsOffline,
8+
} from 'ra-core';
9+
import { Alert, Box, Card, Stack, ThemeOptions } from '@mui/material';
10+
import { deepmerge } from '@mui/utils';
11+
import { onlineManager } from '@tanstack/react-query';
512

613
import { TextField } from '../field';
714
import { Labeled } from '../Labeled';
815
import { SimpleShowLayout } from './SimpleShowLayout';
916
import { EditButton } from '../button';
1017
import TopToolbar from '../layout/TopToolbar';
11-
import { Show } from './Show';
12-
import { deepmerge } from '@mui/utils';
18+
import { Show, ShowProps } from './Show';
1319
import { defaultLightTheme } from '../theme';
1420

1521
export default { title: 'ra-ui-materialui/detail/Show' };
@@ -299,3 +305,67 @@ export const WithRenderProp = () => (
299305
</Admin>
300306
</TestMemoryRouter>
301307
);
308+
309+
export const Offline = ({
310+
isOnline = true,
311+
offline,
312+
}: {
313+
isOnline?: boolean;
314+
offline?: React.ReactNode;
315+
}) => {
316+
React.useEffect(() => {
317+
onlineManager.setOnline(isOnline);
318+
}, [isOnline]);
319+
return (
320+
<TestMemoryRouter initialEntries={['/books/1/show']}>
321+
<Admin dataProvider={dataProvider}>
322+
<Resource
323+
name="books"
324+
show={<BookShowOffline offline={offline} />}
325+
/>
326+
</Admin>
327+
</TestMemoryRouter>
328+
);
329+
};
330+
331+
const BookShowOffline = (props: ShowProps) => {
332+
return (
333+
<Show emptyWhileLoading {...props}>
334+
<IsOffline>
335+
<Alert severity="warning">
336+
You are offline, the data may be outdated
337+
</Alert>
338+
</IsOffline>
339+
<SimpleShowLayout>
340+
<TextField source="title" />
341+
<TextField source="author" />
342+
<TextField source="summary" />
343+
<TextField source="year" />
344+
</SimpleShowLayout>
345+
</Show>
346+
);
347+
};
348+
349+
const CustomOffline = () => {
350+
return <Alert severity="warning">You are offline!</Alert>;
351+
};
352+
353+
Offline.args = {
354+
isOnline: true,
355+
offline: false,
356+
};
357+
358+
Offline.argTypes = {
359+
isOnline: {
360+
control: { type: 'boolean' },
361+
},
362+
offline: {
363+
name: 'Offline component',
364+
control: { type: 'radio' },
365+
options: ['default', 'custom'],
366+
mapping: {
367+
default: undefined,
368+
custom: <CustomOffline />,
369+
},
370+
},
371+
};

packages/ra-ui-materialui/src/detail/Show.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ export const Show = <RecordType extends RaRecord = any>(
9090
queryOptions={queryOptions}
9191
resource={resource}
9292
loading={loading}
93+
// Disable offline support from ShowBase as it is handled by ShowView to keep the ShowView container
94+
offline={false}
9395
>
9496
<ShowView {...rest} />
9597
</ShowBase>

packages/ra-ui-materialui/src/detail/ShowView.tsx

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import type { ReactElement, ElementType, ReactNode } from 'react';
2+
import type { ElementType, ReactNode } from 'react';
33
import {
44
Card,
55
type ComponentsOverrides,
@@ -16,8 +16,10 @@ import {
1616
import { ShowActions } from './ShowActions';
1717
import { Title } from '../layout';
1818
import { ShowProps } from './Show';
19+
import { Offline } from '../Offline';
1920

2021
const defaultActions = <ShowActions />;
22+
const defaultOffline = <Offline />;
2123

2224
export const ShowView = (props: ShowViewProps) => {
2325
const {
@@ -28,20 +30,32 @@ export const ShowView = (props: ShowViewProps) => {
2830
className,
2931
component: Content = Card,
3032
emptyWhileLoading = false,
33+
offline = defaultOffline,
3134
title,
3235
...rest
3336
} = props;
3437

3538
const showContext = useShowContext();
36-
const { resource, defaultTitle, record } = showContext;
39+
const { resource, defaultTitle, isPaused, record } = showContext;
3740
const { hasEdit } = useResourceDefinition();
3841

3942
const finalActions =
4043
typeof actions === 'undefined' && hasEdit ? defaultActions : actions;
4144

45+
if (!record && offline !== false && isPaused) {
46+
return (
47+
<Root className={clsx('show-page', className)} {...rest}>
48+
<div className={clsx(ShowClasses.main, ShowClasses.noActions)}>
49+
<Content className={ShowClasses.card}>{offline}</Content>
50+
{aside}
51+
</div>
52+
</Root>
53+
);
54+
}
4255
if (!record && emptyWhileLoading) {
4356
return null;
4457
}
58+
4559
return (
4660
<Root className={clsx('show-page', className)} {...rest}>
4761
{title !== false && (
@@ -68,11 +82,12 @@ export const ShowView = (props: ShowViewProps) => {
6882

6983
export interface ShowViewProps
7084
extends Omit<React.HTMLAttributes<HTMLDivElement>, 'id' | 'title'> {
71-
actions?: ReactElement | false;
72-
aside?: ReactElement;
85+
actions?: ReactNode | false;
86+
aside?: ReactNode;
7387
component?: ElementType;
7488
emptyWhileLoading?: boolean;
75-
title?: string | ReactElement | false;
89+
offline?: ReactNode;
90+
title?: ReactNode;
7691
sx?: SxProps<Theme>;
7792
render?: (showContext: ShowControllerResult) => ReactNode;
7893
}

packages/ra-ui-materialui/src/layout/Title.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from 'react';
2-
import { useEffect, useState, ReactElement } from 'react';
2+
import { useEffect, useState } from 'react';
33
import { createPortal } from 'react-dom';
44
import { RaRecord, TitleComponent, warning } from 'ra-core';
55

@@ -50,6 +50,6 @@ export interface TitleProps {
5050
className?: string;
5151
defaultTitle?: TitleComponent;
5252
record?: Partial<RaRecord>;
53-
title?: string | ReactElement;
53+
title?: React.ReactNode;
5454
preferenceKey?: string | false;
5555
}

0 commit comments

Comments
 (0)