@openmrs/esm-styleguide 8.0.1-pre.3585 → 8.0.1-pre.3592

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.turbo/turbo-build.log +4 -4
  2. package/dist/empty-card/empty-card.component.d.ts +13 -0
  3. package/dist/empty-card/index.d.ts +1 -0
  4. package/dist/error-card/error-card.component.d.ts +11 -0
  5. package/dist/error-card/index.d.ts +1 -0
  6. package/dist/internal.d.ts +4 -0
  7. package/dist/openmrs-esm-styleguide.css +1 -1
  8. package/dist/openmrs-esm-styleguide.css.map +1 -1
  9. package/dist/openmrs-esm-styleguide.js +2 -2
  10. package/dist/openmrs-esm-styleguide.js.map +1 -1
  11. package/dist/pagination/index.d.ts +1 -0
  12. package/dist/pagination/pagination.component.d.ts +22 -0
  13. package/dist/public.d.ts +20 -18
  14. package/dist/snackbars/index.d.ts +1 -0
  15. package/dist/toasts/index.d.ts +1 -0
  16. package/package.json +12 -12
  17. package/src/cards/card-header.component.tsx +30 -0
  18. package/src/cards/card-header.module.scss +45 -0
  19. package/src/cards/index.ts +1 -0
  20. package/src/declarations.d.ts +14 -2
  21. package/src/empty-card/empty-card-registration.ts +6 -0
  22. package/src/empty-card/empty-card.component.tsx +55 -0
  23. package/src/empty-card/empty-card.module.scss +27 -0
  24. package/src/empty-card/empty-card.test.tsx +58 -0
  25. package/src/empty-card/empty-data-illustration.svg +32 -0
  26. package/src/empty-card/index.ts +1 -0
  27. package/src/error-card/error-card.component.tsx +35 -0
  28. package/src/error-card/error-card.module.scss +21 -0
  29. package/src/error-card/error-card.test.tsx +25 -0
  30. package/src/error-card/index.ts +1 -0
  31. package/src/index.ts +5 -3
  32. package/src/internal.ts +4 -0
  33. package/src/pagination/index.ts +1 -0
  34. package/src/pagination/pagination.component.tsx +77 -0
  35. package/src/pagination/pagination.module.scss +66 -0
  36. package/src/pagination/pagination.test.tsx +72 -0
  37. package/src/public.ts +20 -18
  38. package/src/snackbars/index.tsx +2 -0
  39. package/src/toasts/index.tsx +2 -0
  40. package/dist/brand.d.ts +0 -1
  41. package/dist/breakpoints/index.d.ts +0 -11
  42. package/dist/config-schema.d.ts +0 -23
  43. package/dist/custom-overflow-menu/custom-overflow-menu.component.d.ts +0 -8
  44. package/dist/custom-overflow-menu/index.d.ts +0 -1
  45. package/dist/datepicker/DatePickerIcon.d.ts +0 -6
  46. package/dist/datepicker/DatePickerInput.d.ts +0 -11
  47. package/dist/datepicker/MonthYear.d.ts +0 -10
  48. package/dist/datepicker/locale-context.d.ts +0 -2
  49. package/dist/datepicker/utils.d.ts +0 -15
  50. package/dist/icons/icon-registration.d.ts +0 -1
  51. package/dist/icons/icons.d.ts +0 -329
  52. package/dist/icons/index.d.ts +0 -1
  53. package/dist/index.d.ts +0 -1
  54. package/dist/logo/index.d.ts +0 -1
  55. package/dist/pictograms/pictogram-registration.d.ts +0 -1
  56. package/dist/pictograms/pictograms.d.ts +0 -64
  57. package/dist/svg-utils.d.ts +0 -1
  58. package/dist/utils.d.ts +0 -6
  59. package/dist/workspaces2/workspace2-close-prompt.modal.d.ts +0 -11
@@ -0,0 +1 @@
1
+ export * from './pagination.component';
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import { type PaginationProps as CarbonPaginationProps } from '@carbon/react';
3
+ export interface PaginationProps {
4
+ /** The count of current items displayed */
5
+ currentItems: number;
6
+ /** The count of total items displayed */
7
+ totalItems: number;
8
+ /** The current page number */
9
+ pageNumber: number;
10
+ /** The size of each page */
11
+ pageSize: number;
12
+ /** A callback to be called when the page changes */
13
+ onPageNumberChange?: CarbonPaginationProps['onChange'];
14
+ /** An optional URL the user should be directed to if they click on the link to see all results */
15
+ dashboardLinkUrl?: string;
16
+ /** Optional text to display instead of the default "See all" */
17
+ dashboardLinkLabel?: string;
18
+ }
19
+ /**
20
+ * Re-usable pagination bar
21
+ */
22
+ export declare const Pagination: React.FC<PaginationProps>;
package/dist/public.d.ts CHANGED
@@ -1,24 +1,26 @@
1
- export { showNotification, showActionableNotification } from './notifications';
2
- export { type NotificationDescriptor, type InlineNotificationType } from './notifications/notification.component';
3
- export { type ActionableNotificationDescriptor, type ActionableNotificationType, } from './notifications/actionable-notification.component';
4
- export { showToast } from './toasts';
5
- export { showModal } from './modals';
6
- export * from './workspaces/public';
7
- export { type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toasts/toast.component';
8
- export { showSnackbar } from './snackbars';
9
- export { type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbars/snackbar.component';
10
- export * from './left-nav';
1
+ export { type StyleguideConfigObject } from './config-schema';
2
+ export * from './cards';
3
+ export * from './custom-overflow-menu';
11
4
  export * from './dashboard-extension';
12
- export * from './error-state';
13
5
  export * from './datepicker';
14
- export * from './responsive-wrapper';
15
- export * from './patient-banner';
16
- export * from './patient-photo';
17
- export * from './custom-overflow-menu';
6
+ export * from './diagnosis-tags';
7
+ export * from './empty-card';
8
+ export * from './error-state';
9
+ export * from './error-card';
18
10
  export * from './icons/icons';
11
+ export * from './left-nav';
12
+ export * from './location-picker';
13
+ export { showModal } from './modals';
14
+ export { showNotification, showActionableNotification } from './notifications';
15
+ export { type ActionableNotificationDescriptor, type ActionableNotificationType, } from './notifications/actionable-notification.component';
16
+ export { type NotificationDescriptor, type InlineNotificationType } from './notifications/notification.component';
19
17
  export * from './page-header';
18
+ export * from './pagination';
19
+ export * from './patient-banner';
20
+ export * from './patient-photo';
20
21
  export * from './pictograms/pictograms';
21
- export { type StyleguideConfigObject } from './config-schema';
22
- export * from './location-picker';
23
- export * from './diagnosis-tags';
22
+ export * from './responsive-wrapper';
23
+ export { showSnackbar, type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbars';
24
+ export { showToast, type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toasts';
25
+ export * from './workspaces/public';
24
26
  export { launchWorkspace2, launchWorkspaceGroup2, closeWorkspaceGroup2, getRegisteredWorkspace2Names, useWorkspace2Context, ActionMenuButton2, Workspace2, type Workspace2Definition, type Workspace2DefinitionProps, } from './workspaces2';
@@ -1,4 +1,5 @@
1
1
  import type { SnackbarDescriptor } from './snackbar.component';
2
+ export { type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbar.component';
2
3
  /**
3
4
  * Starts a rendering host for snack bar notifications. Should only be used by the app shell.
4
5
  * Under normal conditions there is no need to use this function.
@@ -1,4 +1,5 @@
1
1
  import type { ToastDescriptor } from './toast.component';
2
+ export { type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toast.component';
2
3
  /**
3
4
  * Starts a rendering host for toast notifications. Should only be used by the app shell.
4
5
  * Under normal conditions there is no need to use this function.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openmrs/esm-styleguide",
3
- "version": "8.0.1-pre.3585",
3
+ "version": "8.0.1-pre.3592",
4
4
  "license": "MPL-2.0",
5
5
  "description": "The styleguide for OpenMRS SPA",
6
6
  "main": "dist/openmrs-esm-styleguide.js",
@@ -98,17 +98,17 @@
98
98
  "swr": "2.x"
99
99
  },
100
100
  "devDependencies": {
101
- "@openmrs/esm-api": "8.0.1-pre.3585",
102
- "@openmrs/esm-config": "8.0.1-pre.3585",
103
- "@openmrs/esm-emr-api": "8.0.1-pre.3585",
104
- "@openmrs/esm-error-handling": "8.0.1-pre.3585",
105
- "@openmrs/esm-extensions": "8.0.1-pre.3585",
106
- "@openmrs/esm-globals": "8.0.1-pre.3585",
107
- "@openmrs/esm-navigation": "8.0.1-pre.3585",
108
- "@openmrs/esm-react-utils": "8.0.1-pre.3585",
109
- "@openmrs/esm-state": "8.0.1-pre.3585",
110
- "@openmrs/esm-translations": "8.0.1-pre.3585",
111
- "@openmrs/esm-utils": "8.0.1-pre.3585",
101
+ "@openmrs/esm-api": "8.0.1-pre.3592",
102
+ "@openmrs/esm-config": "8.0.1-pre.3592",
103
+ "@openmrs/esm-emr-api": "8.0.1-pre.3592",
104
+ "@openmrs/esm-error-handling": "8.0.1-pre.3592",
105
+ "@openmrs/esm-extensions": "8.0.1-pre.3592",
106
+ "@openmrs/esm-globals": "8.0.1-pre.3592",
107
+ "@openmrs/esm-navigation": "8.0.1-pre.3592",
108
+ "@openmrs/esm-react-utils": "8.0.1-pre.3592",
109
+ "@openmrs/esm-state": "8.0.1-pre.3592",
110
+ "@openmrs/esm-translations": "8.0.1-pre.3592",
111
+ "@openmrs/esm-utils": "8.0.1-pre.3592",
112
112
  "@rspack/cli": "^1.3.11",
113
113
  "@rspack/core": "^1.3.11",
114
114
  "@types/geopattern": "^1.2.9",
@@ -0,0 +1,30 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import { useLayoutType } from '@openmrs/esm-react-utils';
4
+ import styles from './card-header.module.scss';
5
+
6
+ export interface CardHeaderProps {
7
+ /** The title for this card. This must be a pre-translated string. */
8
+ title: string;
9
+ /** The contents of the card header to render if any. */
10
+ children?: React.ReactNode;
11
+ }
12
+
13
+ /**
14
+ * Re-usable header component for O3-style cards, like those found on the patient chart
15
+ */
16
+ export function CardHeader({ title, children }: CardHeaderProps) {
17
+ const isTablet = useLayoutType() === 'tablet';
18
+
19
+ return (
20
+ <div
21
+ className={classNames({
22
+ [styles.tabletHeader]: isTablet,
23
+ [styles.desktopHeader]: !isTablet,
24
+ })}
25
+ >
26
+ <h4>{title}</h4>
27
+ {children}
28
+ </div>
29
+ );
30
+ }
@@ -0,0 +1,45 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '../vars' as *;
4
+
5
+ .desktopHeader,
6
+ .tabletHeader {
7
+ display: flex;
8
+ justify-content: space-between;
9
+ align-items: center;
10
+ padding: layout.$spacing-04 0 layout.$spacing-04 layout.$spacing-05;
11
+ background-color: $ui-background;
12
+
13
+ h4:after {
14
+ content: '';
15
+ display: block;
16
+ width: layout.$spacing-07;
17
+ padding-top: 0.188rem;
18
+ border-bottom: 0.375rem solid var(--brand-03);
19
+ }
20
+ }
21
+
22
+ .desktopHeader {
23
+ height: layout.$spacing-09;
24
+ h4 {
25
+ @include type.type-style('heading-compact-02');
26
+ color: $text-02;
27
+ }
28
+ }
29
+
30
+ .tabletHeader {
31
+ height: 4.5rem;
32
+ h4 {
33
+ @include type.type-style('heading-03');
34
+ color: $text-02;
35
+ }
36
+ }
37
+
38
+ // Overriding styles for RTL support
39
+ html[dir='rtl'] {
40
+ .desktopHeader,
41
+ .tabletHeader {
42
+ text-align: right;
43
+ padding: layout.$spacing-04 layout.$spacing-05 layout.$spacing-04 0;
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export { CardHeader, type CardHeaderProps } from './card-header.component';
@@ -1,5 +1,17 @@
1
- declare module '*.css';
2
- declare module '*.scss';
1
+ declare module '*.css' {
2
+ interface Styles {
3
+ [key: string]: string;
4
+ }
5
+ const content: Styles;
6
+ export default content;
7
+ }
8
+ declare module '*.scss' {
9
+ interface Styles {
10
+ [key: string]: string;
11
+ }
12
+ const content: Styles;
13
+ export default content;
14
+ }
3
15
  declare module '*.svg' {
4
16
  const content: string;
5
17
  export default content;
@@ -0,0 +1,6 @@
1
+ import emptyStateIllustration from './empty-data-illustration.svg';
2
+ import { addSvg } from '../svg-utils';
3
+
4
+ export function setupEmptyCard() {
5
+ addSvg('omrs-empty-state-illustration', emptyStateIllustration);
6
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import { Button, Layer, Tile } from '@carbon/react';
4
+ import { useLayoutType } from '@openmrs/esm-react-utils';
5
+ import { getCoreTranslation } from '@openmrs/esm-translations';
6
+ import { CardHeader } from '../cards';
7
+ import styles from './empty-card.module.scss';
8
+
9
+ export interface EmptyCardProps {
10
+ /** The name of the type of item that would be displayed here if not empty. This must be a pre-translated string. */
11
+ displayText: string;
12
+ /** The title to use for this empty component. This must be a pre-translated string. */
13
+ headerTitle: string;
14
+ /** A callback to invoke when the user tries to record a new item. */
15
+ launchForm?(): void;
16
+ }
17
+
18
+ const EmptyDataIllustration = ({ width = '64', height = '64' }) => {
19
+ return (
20
+ <svg width={width} height={height} viewBox="0 0 64 64">
21
+ <use href={`#omrs-empty-data-illustration`} />
22
+ </svg>
23
+ );
24
+ };
25
+
26
+ /**
27
+ * Re-usable card for displaying an empty state
28
+ */
29
+ export const EmptyCard: React.FC<EmptyCardProps> = (props) => {
30
+ const isTablet = useLayoutType() === 'tablet';
31
+ const launchForm = props.launchForm;
32
+
33
+ return (
34
+ <Layer className={styles.layer}>
35
+ <Tile className={styles.tile}>
36
+ <CardHeader title={props.headerTitle} />
37
+ <EmptyDataIllustration />
38
+ <p className={styles.content}>
39
+ {getCoreTranslation('emptyStateText', 'There are no {{displayText}} to display', {
40
+ displayText: props.displayText,
41
+ })}
42
+ </p>
43
+ <p className={styles.action}>
44
+ {launchForm && (
45
+ <Button onClick={() => launchForm()} kind="ghost" size={isTablet ? 'lg' : 'sm'}>
46
+ {getCoreTranslation('recordNewEntry', 'Record {{ displayText }}', {
47
+ displayText: props.displayText,
48
+ })}
49
+ </Button>
50
+ )}
51
+ </p>
52
+ </Tile>
53
+ </Layer>
54
+ );
55
+ };
@@ -0,0 +1,27 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '../vars' as *;
4
+
5
+ .action {
6
+ margin-bottom: layout.$spacing-03;
7
+ }
8
+
9
+ .content {
10
+ @include type.type-style('heading-compact-01');
11
+ color: $text-02;
12
+ margin-top: layout.$spacing-05;
13
+ margin-bottom: layout.$spacing-03;
14
+ }
15
+
16
+ .heading:after {
17
+ content: '';
18
+ display: block;
19
+ width: layout.$spacing-07;
20
+ padding-top: 0.188rem;
21
+ border-bottom: 0.375rem solid var(--brand-03);
22
+ }
23
+
24
+ .tile {
25
+ text-align: center;
26
+ border: 1px solid $ui-03;
27
+ }
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { describe, expect, it, vi } from 'vitest';
3
+ import { render, screen } from '@testing-library/react';
4
+ import userEvent from '@testing-library/user-event';
5
+ import { EmptyCard } from './empty-card.component';
6
+
7
+ describe('EmptyCard', () => {
8
+ const defaultProps = {
9
+ displayText: 'medications',
10
+ headerTitle: 'Medications',
11
+ };
12
+
13
+ it('renders the header title', () => {
14
+ render(<EmptyCard {...defaultProps} />);
15
+ expect(screen.getByRole('heading', { name: /medications/i })).toBeInTheDocument();
16
+ });
17
+
18
+ it('renders the empty state text with the display text', () => {
19
+ render(<EmptyCard {...defaultProps} />);
20
+ expect(screen.getByText(/there are no medications to display/i)).toBeInTheDocument();
21
+ });
22
+
23
+ it('does not render the action button when launchForm is not provided', () => {
24
+ render(<EmptyCard {...defaultProps} />);
25
+ expect(screen.queryByRole('button')).not.toBeInTheDocument();
26
+ });
27
+
28
+ it('renders the action button when launchForm is provided', () => {
29
+ const launchForm = vi.fn();
30
+ render(<EmptyCard {...defaultProps} launchForm={launchForm} />);
31
+ expect(screen.getByRole('button', { name: /record medications/i })).toBeInTheDocument();
32
+ });
33
+
34
+ it('calls launchForm when the action button is clicked', async () => {
35
+ const user = userEvent.setup();
36
+ const launchForm = vi.fn();
37
+ render(<EmptyCard {...defaultProps} launchForm={launchForm} />);
38
+
39
+ const button = screen.getByRole('button', { name: /record medications/i });
40
+ await user.click(button);
41
+
42
+ expect(launchForm).toHaveBeenCalledTimes(1);
43
+ });
44
+
45
+ it('renders with different display text', () => {
46
+ render(<EmptyCard displayText="allergies" headerTitle="Allergies" />);
47
+ expect(screen.getByRole('heading', { name: /allergies/i })).toBeInTheDocument();
48
+ expect(screen.getByText(/there are no allergies to display/i)).toBeInTheDocument();
49
+ });
50
+
51
+ it('renders the empty data illustration', () => {
52
+ render(<EmptyCard {...defaultProps} />);
53
+ // eslint-disable-next-line testing-library/no-node-access
54
+ const svg = document.querySelector('svg');
55
+ expect(svg).toBeInTheDocument();
56
+ expect(svg).toHaveAttribute('viewBox', '0 0 64 64');
57
+ });
58
+ });
@@ -0,0 +1,32 @@
1
+ <svg viewBox="0 0 64 64">
2
+ <g fill="none" fillRule="evenodd">
3
+ <path
4
+ d="M38.133 13.186H21.947c-.768.001-1.39.623-1.39 1.391V50.55l-.186.057-3.97 1.216a.743.743 0 01-.927-.493L3.664 12.751a.742.742 0 01.492-.926l6.118-1.874 17.738-5.43 6.119-1.873a.741.741 0 01.926.492L38.076 13l.057.186z"
5
+ fill="#F4F4F4"
6
+ />
7
+ <path
8
+ d="M41.664 13L38.026 1.117A1.576 1.576 0 0036.056.07l-8.601 2.633-17.737 5.43-8.603 2.634a1.578 1.578 0 00-1.046 1.97l12.436 40.616a1.58 1.58 0 001.969 1.046l5.897-1.805.185-.057v-.194l-.185.057-5.952 1.822a1.393 1.393 0 01-1.737-.923L.247 12.682a1.39 1.39 0 01.923-1.738L9.772 8.31 27.51 2.881 36.112.247a1.393 1.393 0 011.737.923L41.47 13l.057.186h.193l-.057-.185z"
9
+ fill="#8D8D8D"
10
+ />
11
+ <path
12
+ d="M11.378 11.855a.836.836 0 01-.798-.59L9.385 7.361a.835.835 0 01.554-1.042l16.318-4.996a.836.836 0 011.042.554l1.195 3.902a.836.836 0 01-.554 1.043l-16.318 4.995a.831.831 0 01-.244.037z"
13
+ fill="#C6C6C6"
14
+ />
15
+ <circle fill="#C6C6C6" cx="17.636" cy="2.314" r="1.855" />
16
+ <circle fill="#FFF" fillRule="nonzero" cx="17.636" cy="2.314" r="1.175" />
17
+ <path
18
+ d="M55.893 53.995H24.544a.79.79 0 01-.788-.789V15.644a.79.79 0 01.788-.788h31.349a.79.79 0 01.788.788v37.562a.79.79 0 01-.788.789z"
19
+ fill="#F4F4F4"
20
+ />
21
+ <path
22
+ d="M41.47 13H21.948a1.579 1.579 0 00-1.576 1.577V52.4l.185-.057V14.577c.001-.768.623-1.39 1.391-1.39h19.581L41.471 13zm17.02 0H21.947a1.579 1.579 0 00-1.576 1.577v42.478c0 .87.706 1.576 1.576 1.577H58.49a1.579 1.579 0 001.576-1.577V14.577a1.579 1.579 0 00-1.576-1.576zm1.39 44.055c0 .768-.622 1.39-1.39 1.392H21.947c-.768-.001-1.39-.624-1.39-1.392V14.577c0-.768.622-1.39 1.39-1.39H58.49c.768 0 1.39.622 1.39 1.39v42.478z"
23
+ fill="#8D8D8D"
24
+ />
25
+ <path
26
+ d="M48.751 17.082H31.686a.836.836 0 01-.835-.835v-4.081c0-.46.374-.834.835-.835H48.75c.461 0 .834.374.835.835v4.08c0 .462-.374.835-.835.836z"
27
+ fill="#C6C6C6"
28
+ />
29
+ <circle fill="#C6C6C6" cx="40.218" cy="9.755" r="1.855" />
30
+ <circle fill="#FFF" fillRule="nonzero" cx="40.218" cy="9.755" r="1.13" />
31
+ </g>
32
+ </svg>
@@ -0,0 +1 @@
1
+ export { EmptyCard, type EmptyCardProps } from './empty-card.component';
@@ -0,0 +1,35 @@
1
+ import React from 'react';
2
+ import { Layer, Tile } from '@carbon/react';
3
+ import { getCoreTranslation } from '@openmrs/esm-translations';
4
+ import { CardHeader } from '../cards';
5
+ import styles from './error-card.module.scss';
6
+
7
+ export interface ErrorCardProps {
8
+ /** This is the error that triggered rendering the card. It is expected to be the error returned from `openmrsFetch()` */
9
+ error: any;
10
+ /** The title for this card. This must be a pre-translated string. */
11
+ headerTitle: string;
12
+ }
13
+
14
+ /**
15
+ * Re-usable card for displaying a fetch error
16
+ */
17
+ export const ErrorCard: React.FC<ErrorCardProps> = ({ error, headerTitle }) => {
18
+ return (
19
+ <Layer>
20
+ <Tile className={styles.tile}>
21
+ <CardHeader title={headerTitle} />
22
+ <p className={styles.errorMessage}>
23
+ {getCoreTranslation('error', 'Error')} {`${error?.response?.status}: `}
24
+ {error?.response?.statusText}
25
+ </p>
26
+ <p className={styles.errorCopy}>
27
+ {getCoreTranslation(
28
+ 'errorCopy',
29
+ 'Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above.',
30
+ )}
31
+ </p>
32
+ </Tile>
33
+ </Layer>
34
+ );
35
+ };
@@ -0,0 +1,21 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '@openmrs/esm-styleguide/src/vars' as *;
4
+
5
+ .errorMessage {
6
+ @include type.type-style('heading-compact-02');
7
+
8
+ margin-top: 2.25rem;
9
+ margin-bottom: layout.$spacing-03;
10
+ }
11
+
12
+ .errorCopy {
13
+ margin-bottom: layout.$spacing-03;
14
+ @include type.type-style('body-01');
15
+ color: $text-02;
16
+ }
17
+
18
+ .tile {
19
+ text-align: center;
20
+ border: 1px solid $ui-03;
21
+ }
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ import { describe, expect, it } from 'vitest';
3
+ import '@testing-library/jest-dom/vitest';
4
+ import { render, screen } from '@testing-library/react';
5
+ import { ErrorCard } from './error-card.component';
6
+
7
+ describe('ErrorCard', () => {
8
+ it('renders an error state widget card', () => {
9
+ const testError = {
10
+ response: {
11
+ status: 500,
12
+ statusText: 'Internal Server Error',
13
+ },
14
+ };
15
+ render(<ErrorCard headerTitle="appointments" error={testError} />);
16
+
17
+ expect(screen.getByRole('heading', { name: /appointments/i })).toBeInTheDocument();
18
+ expect(screen.getByText(/Error 500: Internal Server Error/i)).toBeInTheDocument();
19
+ expect(
20
+ screen.getByText(
21
+ /Sorry, there was a problem displaying this information. You can try to reload this page, or contact the site administrator and quote the error code above./i,
22
+ ),
23
+ ).toBeInTheDocument();
24
+ });
25
+ });
@@ -0,0 +1 @@
1
+ export { ErrorCard, type ErrorCardProps } from './error-card.component';
package/src/index.ts CHANGED
@@ -1,10 +1,11 @@
1
1
  import { defineConfigSchema } from '@openmrs/esm-config';
2
- import { setupLogo } from './logo';
3
- import { setupIcons } from './icons/icon-registration';
2
+ import { registerModal } from '@openmrs/esm-extensions';
4
3
  import { setupBranding } from './brand';
5
4
  import { esmStyleGuideSchema } from './config-schema';
5
+ import { setupEmptyCard } from './empty-card/empty-card-registration';
6
+ import { setupIcons } from './icons/icon-registration';
7
+ import { setupLogo } from './logo';
6
8
  import { setupPictograms } from './pictograms/pictogram-registration';
7
- import { registerModal } from '@openmrs/esm-extensions';
8
9
  import { getSyncLifecycle } from '@openmrs/esm-react-utils';
9
10
  import Workspace2ClosePromptModal from './workspaces2/workspace2-close-prompt.modal';
10
11
 
@@ -13,6 +14,7 @@ setupBranding();
13
14
  setupLogo();
14
15
  setupIcons();
15
16
  setupPictograms();
17
+ setupEmptyCard();
16
18
 
17
19
  registerModal({
18
20
  name: 'workspace2-close-prompt',
package/src/internal.ts CHANGED
@@ -1,9 +1,12 @@
1
1
  export * from './breakpoints';
2
+ export * from './cards';
2
3
  export * from './config-schema';
3
4
  export * from './custom-overflow-menu';
4
5
  export * from './dashboard-extension';
5
6
  export * from './datepicker';
6
7
  export * from './diagnosis-tags';
8
+ export * from './empty-card';
9
+ export * from './error-card';
7
10
  export * from './error-state';
8
11
  export * from './icons/icons';
9
12
  export * from './left-nav';
@@ -13,6 +16,7 @@ export * from './notifications';
13
16
  export * from './notifications/actionable-notification.component';
14
17
  export * from './notifications/notification.component';
15
18
  export * from './page-header';
19
+ export * from './pagination';
16
20
  export * from './patient-banner';
17
21
  export * from './patient-photo';
18
22
  export * from './pictograms/pictograms';
@@ -0,0 +1 @@
1
+ export * from './pagination.component';
@@ -0,0 +1,77 @@
1
+ import React from 'react';
2
+ import classNames from 'classnames';
3
+ import { Pagination as CarbonPagination, type PaginationProps as CarbonPaginationProps } from '@carbon/react';
4
+ import { ConfigurableLink, useLayoutType, usePaginationInfo } from '@openmrs/esm-react-utils';
5
+ import { getCoreTranslation } from '@openmrs/esm-translations';
6
+ import styles from './pagination.module.scss';
7
+
8
+ export interface PaginationProps {
9
+ /** The count of current items displayed */
10
+ currentItems: number;
11
+ /** The count of total items displayed */
12
+ totalItems: number;
13
+ /** The current page number */
14
+ pageNumber: number;
15
+ /** The size of each page */
16
+ pageSize: number;
17
+ /** A callback to be called when the page changes */
18
+ onPageNumberChange?: CarbonPaginationProps['onChange'];
19
+ /** An optional URL the user should be directed to if they click on the link to see all results */
20
+ dashboardLinkUrl?: string;
21
+ /** Optional text to display instead of the default "See all" */
22
+ dashboardLinkLabel?: string;
23
+ }
24
+
25
+ /**
26
+ * Re-usable pagination bar
27
+ */
28
+ export const Pagination: React.FC<PaginationProps> = ({
29
+ totalItems,
30
+ pageSize,
31
+ onPageNumberChange,
32
+ pageNumber,
33
+ dashboardLinkUrl,
34
+ currentItems,
35
+ dashboardLinkLabel: urlLabel,
36
+ }) => {
37
+ const { pageSizes, pageItemsCount } = usePaginationInfo(pageSize, totalItems, pageNumber, currentItems);
38
+ const isTablet = useLayoutType() === 'tablet';
39
+ const itemsDisplayed = getCoreTranslation('paginationItemsCount', '{{pageItemsCount}} / {{totalItems}} items', {
40
+ totalItems,
41
+ pageItemsCount,
42
+ });
43
+
44
+ return (
45
+ <>
46
+ {totalItems > 0 && (
47
+ <div
48
+ className={classNames({
49
+ [styles.tablet]: isTablet,
50
+ [styles.desktop]: !isTablet,
51
+ })}
52
+ >
53
+ <div>
54
+ {itemsDisplayed}
55
+ {dashboardLinkUrl && (
56
+ <ConfigurableLink to={dashboardLinkUrl} className={styles.configurableLink}>
57
+ {urlLabel ?? getCoreTranslation('seeAll', 'See all')}
58
+ </ConfigurableLink>
59
+ )}
60
+ </div>
61
+ <CarbonPagination
62
+ className={styles.pagination}
63
+ page={pageNumber}
64
+ pageSize={pageSize}
65
+ pageSizes={pageSizes}
66
+ totalItems={totalItems}
67
+ onChange={onPageNumberChange}
68
+ pageRangeText={(_, total) =>
69
+ getCoreTranslation('paginationOfPages', 'of {{count}} pages', { count: total })
70
+ }
71
+ size={isTablet ? 'lg' : 'sm'}
72
+ />
73
+ </div>
74
+ )}
75
+ </>
76
+ );
77
+ };