@openmrs/esm-styleguide 8.0.1-pre.3581 → 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 (60) 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/error-state/error-state.component.d.ts +6 -0
  7. package/dist/error-state/index.d.ts +1 -0
  8. package/dist/internal.d.ts +4 -0
  9. package/dist/left-nav/index.d.ts +23 -0
  10. package/dist/modals/index.d.ts +25 -0
  11. package/dist/notifications/actionable-notification.component.d.ts +20 -0
  12. package/dist/notifications/active-actionable-notifications.component.d.ts +8 -0
  13. package/dist/notifications/active-notifications.component.d.ts +8 -0
  14. package/dist/notifications/index.d.ts +19 -0
  15. package/dist/notifications/notification.component.d.ts +19 -0
  16. package/dist/openmrs-esm-styleguide.css +1 -1
  17. package/dist/openmrs-esm-styleguide.css.map +1 -1
  18. package/dist/openmrs-esm-styleguide.js +2 -2
  19. package/dist/openmrs-esm-styleguide.js.map +1 -1
  20. package/dist/pagination/index.d.ts +1 -0
  21. package/dist/pagination/pagination.component.d.ts +22 -0
  22. package/dist/public.d.ts +20 -18
  23. package/dist/snackbars/active-snackbar.component.d.ts +9 -0
  24. package/dist/snackbars/index.d.ts +13 -0
  25. package/dist/snackbars/snackbar.component.d.ts +22 -0
  26. package/dist/toasts/active-toasts.component.d.ts +9 -0
  27. package/dist/toasts/index.d.ts +13 -0
  28. package/dist/toasts/toast.component.d.ts +19 -0
  29. package/dist/workspaces/action-menu-button/action-menu-button.component.d.ts +11 -0
  30. package/dist/workspaces/container/action-menu.component.d.ts +9 -0
  31. package/dist/workspaces/container/workspace-container.component.d.ts +54 -0
  32. package/dist/workspaces/container/workspace-renderer.component.d.ts +8 -0
  33. package/dist/workspaces/notification/workspace-notification.component.d.ts +6 -0
  34. package/dist/workspaces/public.d.ts +4 -0
  35. package/dist/workspaces/workspace-sidebar-store/useWorkspaceGroupStore.d.ts +11 -0
  36. package/dist/workspaces/workspaces.d.ts +236 -0
  37. package/package.json +12 -12
  38. package/src/cards/card-header.component.tsx +30 -0
  39. package/src/cards/card-header.module.scss +45 -0
  40. package/src/cards/index.ts +1 -0
  41. package/src/declarations.d.ts +14 -2
  42. package/src/empty-card/empty-card-registration.ts +6 -0
  43. package/src/empty-card/empty-card.component.tsx +55 -0
  44. package/src/empty-card/empty-card.module.scss +27 -0
  45. package/src/empty-card/empty-card.test.tsx +58 -0
  46. package/src/empty-card/empty-data-illustration.svg +32 -0
  47. package/src/empty-card/index.ts +1 -0
  48. package/src/error-card/error-card.component.tsx +35 -0
  49. package/src/error-card/error-card.module.scss +21 -0
  50. package/src/error-card/error-card.test.tsx +25 -0
  51. package/src/error-card/index.ts +1 -0
  52. package/src/index.ts +5 -3
  53. package/src/internal.ts +4 -0
  54. package/src/pagination/index.ts +1 -0
  55. package/src/pagination/pagination.component.tsx +77 -0
  56. package/src/pagination/pagination.module.scss +66 -0
  57. package/src/pagination/pagination.test.tsx +72 -0
  58. package/src/public.ts +20 -18
  59. package/src/snackbars/index.tsx +2 -0
  60. package/src/toasts/index.tsx +2 -0
@@ -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
+ };
@@ -0,0 +1,66 @@
1
+ @use '@carbon/layout';
2
+ @use '@carbon/type';
3
+ @use '../vars' as *;
4
+
5
+ .bodyShort01 {
6
+ @include type.type-style('body-compact-01');
7
+ }
8
+
9
+ .desktop,
10
+ .tablet {
11
+ @include type.type-style('body-compact-01');
12
+ display: flex;
13
+ justify-content: space-between;
14
+ color: $text-02;
15
+ background-color: $ui-02;
16
+ padding-left: layout.$spacing-05;
17
+ align-items: center;
18
+ border-top: 1px solid $ui-03;
19
+ }
20
+
21
+ .desktop :global(.cds--pagination) {
22
+ min-height: 0;
23
+ height: layout.$spacing-07;
24
+ width: auto;
25
+ border: none;
26
+
27
+ & :global(.cds--select-input),
28
+ :global(.cds--btn),
29
+ :global(.cds--pagination__right) {
30
+ min-height: 0;
31
+ height: layout.$spacing-07;
32
+ }
33
+ }
34
+
35
+ .tablet :global(.cds--pagination) {
36
+ min-height: 0;
37
+ height: layout.$spacing-09;
38
+ width: auto;
39
+ border: none;
40
+
41
+ & :global(.cds--select-input),
42
+ :global(.cds--btn),
43
+ :global(.cds--pagination__right) {
44
+ min-height: 0;
45
+ height: layout.$spacing-09;
46
+ }
47
+ }
48
+
49
+ .configurableLink {
50
+ text-decoration: none;
51
+ @extend .bodyShort01;
52
+ padding: 0 layout.$spacing-03;
53
+ }
54
+
55
+ .pagination {
56
+ @include type.type-style('body-compact-01');
57
+ background-color: $ui-02;
58
+ color: $text-02;
59
+ display: flex;
60
+ }
61
+
62
+ div.pagination {
63
+ & > :global(.cds--pagination__left) {
64
+ display: none;
65
+ }
66
+ }
@@ -0,0 +1,72 @@
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 { Pagination } from './pagination.component';
6
+
7
+ describe('Pagination', () => {
8
+ const defaultProps = {
9
+ currentItems: 10,
10
+ totalItems: 100,
11
+ pageNumber: 1,
12
+ pageSize: 10,
13
+ onPageNumberChange: vi.fn(),
14
+ };
15
+
16
+ it('renders nothing when totalItems is 0', () => {
17
+ const { container } = render(<Pagination {...defaultProps} totalItems={0} />);
18
+ expect(container).toBeEmptyDOMElement();
19
+ });
20
+
21
+ it('renders pagination when totalItems is greater than 0', () => {
22
+ render(<Pagination {...defaultProps} />);
23
+ expect(screen.getByText(/10 \/ 100 items/i)).toBeInTheDocument();
24
+ });
25
+
26
+ it('displays correct items count for the first page', () => {
27
+ render(<Pagination {...defaultProps} pageNumber={1} currentItems={10} />);
28
+ expect(screen.getByText(/10 \/ 100 items/i)).toBeInTheDocument();
29
+ });
30
+
31
+ it('displays correct items count for a middle page', () => {
32
+ render(<Pagination {...defaultProps} pageNumber={5} currentItems={10} />);
33
+ expect(screen.getByText(/50 \/ 100 items/i)).toBeInTheDocument();
34
+ });
35
+
36
+ it('displays correct items count for the last page with fewer items', () => {
37
+ render(<Pagination {...defaultProps} totalItems={95} pageNumber={10} currentItems={5} />);
38
+ expect(screen.getByText(/95 \/ 95 items/i)).toBeInTheDocument();
39
+ });
40
+
41
+ it('renders ConfigurableLink when dashboardLinkUrl is provided', () => {
42
+ render(<Pagination {...defaultProps} dashboardLinkUrl="/dashboard" />);
43
+ expect(screen.getByRole('link', { name: /see all/i })).toBeInTheDocument();
44
+ expect(screen.getByRole('link', { name: /see all/i })).toHaveAttribute('href', '/dashboard');
45
+ });
46
+
47
+ it('uses custom label when dashboardLinkLabel is provided', () => {
48
+ render(<Pagination {...defaultProps} dashboardLinkUrl="/dashboard" dashboardLinkLabel="View all items" />);
49
+ expect(screen.getByRole('link', { name: /view all items/i })).toBeInTheDocument();
50
+ });
51
+
52
+ it('does not render ConfigurableLink when dashboardLinkUrl is not provided', () => {
53
+ render(<Pagination {...defaultProps} />);
54
+ expect(screen.queryByRole('link')).not.toBeInTheDocument();
55
+ });
56
+
57
+ it('calls onPageNumberChange when page changes', async () => {
58
+ const user = userEvent.setup();
59
+ const onPageNumberChange = vi.fn();
60
+ render(<Pagination {...defaultProps} onPageNumberChange={onPageNumberChange} />);
61
+
62
+ const nextButton = screen.getByRole('button', { name: /next/i });
63
+ await user.click(nextButton);
64
+
65
+ expect(onPageNumberChange).toHaveBeenCalled();
66
+ });
67
+
68
+ it('handles case when pageSize is greater than totalItems', () => {
69
+ render(<Pagination {...defaultProps} pageSize={50} totalItems={30} currentItems={30} />);
70
+ expect(screen.getByText(/30 \/ 30 items/i)).toBeInTheDocument();
71
+ });
72
+ });
package/src/public.ts CHANGED
@@ -1,29 +1,31 @@
1
+ export { type StyleguideConfigObject } from './config-schema';
2
+ export * from './cards';
3
+ export * from './custom-overflow-menu';
4
+ export * from './dashboard-extension';
5
+ export * from './datepicker';
6
+ export * from './diagnosis-tags';
7
+ export * from './empty-card';
8
+ export * from './error-state';
9
+ export * from './error-card';
10
+ export * from './icons/icons';
11
+ export * from './left-nav';
12
+ export * from './location-picker';
13
+ export { showModal } from './modals';
1
14
  export { showNotification, showActionableNotification } from './notifications';
2
- export { type NotificationDescriptor, type InlineNotificationType } from './notifications/notification.component';
3
15
  export {
4
16
  type ActionableNotificationDescriptor,
5
17
  type ActionableNotificationType,
6
18
  } from './notifications/actionable-notification.component';
7
- export { showToast } from './toasts';
8
- export { showModal } from './modals';
9
- export * from './workspaces/public';
10
- export { type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toasts/toast.component';
11
- export { showSnackbar } from './snackbars';
12
- export { type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbars/snackbar.component';
13
- export * from './left-nav';
14
- export * from './dashboard-extension';
15
- export * from './error-state';
16
- export * from './datepicker';
17
- export * from './responsive-wrapper';
19
+ export { type NotificationDescriptor, type InlineNotificationType } from './notifications/notification.component';
20
+ export * from './page-header';
21
+ export * from './pagination';
18
22
  export * from './patient-banner';
19
23
  export * from './patient-photo';
20
- export * from './custom-overflow-menu';
21
- export * from './icons/icons';
22
- export * from './page-header';
23
24
  export * from './pictograms/pictograms';
24
- export { type StyleguideConfigObject } from './config-schema';
25
- export * from './location-picker';
26
- export * from './diagnosis-tags';
25
+ export * from './responsive-wrapper';
26
+ export { showSnackbar, type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbars';
27
+ export { showToast, type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toasts';
28
+ export * from './workspaces/public';
27
29
  export {
28
30
  launchWorkspace2,
29
31
  launchWorkspaceGroup2,
@@ -6,6 +6,8 @@ import { Subject } from 'rxjs';
6
6
  import type { SnackbarDescriptor, SnackbarMeta } from './snackbar.component';
7
7
  import ActiveSnackbars from './active-snackbar.component';
8
8
 
9
+ export { type SnackbarDescriptor, type SnackbarType, type SnackbarMeta } from './snackbar.component';
10
+
9
11
  const snackbarsSubject = new Subject<SnackbarMeta>();
10
12
  let snackbarId = 0;
11
13
 
@@ -6,6 +6,8 @@ import type { ToastDescriptor, ToastNotificationMeta } from './toast.component';
6
6
  import ActiveToasts from './active-toasts.component';
7
7
  import isEmpty from 'lodash-es/isEmpty';
8
8
 
9
+ export { type ToastDescriptor, type ToastType, type ToastNotificationMeta } from './toast.component';
10
+
9
11
  const toastsSubject = new Subject<ToastNotificationMeta>();
10
12
  let toastId = 0;
11
13