Skip to content

Commit 46f2d9a

Browse files
(fix) O3-5831: Fix patient banner building blocks at narrow widths
1 parent f886a3b commit 46f2d9a

7 files changed

Lines changed: 107 additions & 25 deletions

packages/framework/esm-styleguide/src/patient-banner/contact-details/patient-banner-contact-details.module.scss

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
color: $text-02;
99
width: 100%;
1010
border-top: 1px solid $ui-03;
11+
container-type: inline-size;
12+
container-name: contact-details;
1113
}
1214

1315
.heading {
@@ -22,6 +24,10 @@
2224
&:first-child {
2325
border-bottom: 1px solid $ui-03;
2426
}
27+
28+
@container contact-details (max-width: 40rem) {
29+
flex-direction: column;
30+
}
2531
}
2632

2733
.row > .col {
@@ -31,6 +37,13 @@
3137
&:nth-of-type(2n + 1) {
3238
border-right: 1px solid $ui-03;
3339
}
40+
41+
@container contact-details (max-width: 40rem) {
42+
&:nth-of-type(2n + 1) {
43+
border-right: none;
44+
border-bottom: 1px solid $ui-03;
45+
}
46+
}
3447
}
3548

3649
.row li {
@@ -39,7 +52,7 @@
3952

4053
.relationship {
4154
display: flex;
42-
flex-flow: row nowrap;
55+
flex-flow: row wrap;
4356
}
4457

4558
.relationship div {

packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.component.tsx

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
/** @module @category UI */
2+
import classNames from 'classnames';
23
import React from 'react';
34
import { FormLabel, Tag } from '@carbon/react';
45
import { useConfig, usePrimaryIdentifierCode } from '@openmrs/esm-react-utils';
@@ -14,6 +15,8 @@ interface IdentifiersProps {
1415
interface PatientBannerPatientIdentifiersProps {
1516
identifiers: fhir.Identifier[] | undefined;
1617
showIdentifierLabel: boolean;
18+
showLeadingSeparator?: boolean;
19+
showAllIdentifiers?: boolean;
1720
}
1821

1922
function PrimaryIdentifier({ showIdentifierLabel, type, value }: IdentifiersProps) {
@@ -39,6 +42,8 @@ function SecondaryIdentifier({ showIdentifierLabel, type, value }: IdentifiersPr
3942
export function PatientBannerPatientIdentifiers({
4043
identifiers,
4144
showIdentifierLabel,
45+
showLeadingSeparator = false,
46+
showAllIdentifiers = true,
4247
}: PatientBannerPatientIdentifiersProps) {
4348
const { excludePatientIdentifierCodeTypes } = useConfig<StyleguideConfigObject>();
4449
const { primaryIdentifierCode } = usePrimaryIdentifierCode();
@@ -49,20 +54,31 @@ export function PatientBannerPatientIdentifiers({
4954
return code && !excludePatientIdentifierCodeTypes?.uuids.includes(code);
5055
}) ?? [];
5156

57+
const primaryIdentifiers = filteredIdentifiers.filter(
58+
(identifier) => identifier.type?.coding?.[0]?.code === primaryIdentifierCode,
59+
);
60+
61+
let visibleIdentifiers = filteredIdentifiers;
62+
if (!showAllIdentifiers) {
63+
visibleIdentifiers = primaryIdentifiers.length > 0 ? primaryIdentifiers : filteredIdentifiers.slice(0, 1);
64+
}
65+
5266
return (
5367
<>
54-
{filteredIdentifiers?.length
55-
? filteredIdentifiers.map(({ value, type }, index) => (
56-
<React.Fragment key={value}>
57-
<span className={styles.identifier}>
58-
{type?.coding?.[0]?.code === primaryIdentifierCode ? (
59-
<PrimaryIdentifier showIdentifierLabel={showIdentifierLabel} type={type} value={value} />
60-
) : (
61-
<SecondaryIdentifier showIdentifierLabel={showIdentifierLabel} type={type} value={value} />
62-
)}
63-
</span>
64-
{index < filteredIdentifiers.length - 1 && <span className={styles.separator}>&middot;</span>}
65-
</React.Fragment>
68+
{visibleIdentifiers?.length
69+
? visibleIdentifiers.map(({ value, type }, index) => (
70+
<span
71+
key={value}
72+
className={classNames(styles.identifier, {
73+
[styles.withSeparator]: index > 0 || showLeadingSeparator,
74+
})}
75+
>
76+
{type?.coding?.[0]?.code === primaryIdentifierCode ? (
77+
<PrimaryIdentifier showIdentifierLabel={showIdentifierLabel} type={type} value={value} />
78+
) : (
79+
<SecondaryIdentifier showIdentifierLabel={showIdentifierLabel} type={type} value={value} />
80+
)}
81+
</span>
6682
))
6783
: ''}
6884
</>

packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-identifiers.test.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,30 @@ describe('PatientBannerPatientIdentifiers', () => {
7373
expect(screen.getByText(/openmrs id/i)).toBeInTheDocument();
7474
expect(screen.queryByText(/national id/i)).not.toBeInTheDocument();
7575
});
76+
77+
it('falls back to showing the first identifier when showAllIdentifiers is false but the primary identifier code is unresolved', () => {
78+
mockUsePrimaryIdentifierCode.mockReturnValue({
79+
primaryIdentifierCode: undefined,
80+
isLoading: false,
81+
error: undefined,
82+
});
83+
84+
render(
85+
<PatientBannerPatientIdentifiers identifiers={mockIdentifiers} showIdentifierLabel showAllIdentifiers={false} />,
86+
);
87+
88+
expect(screen.getByText(/openmrs id/i)).toBeInTheDocument();
89+
expect(screen.getByText(/100gej/i)).toBeInTheDocument();
90+
expect(screen.queryByText(/national id/i)).not.toBeInTheDocument();
91+
});
92+
93+
it('shows only the primary identifier when showAllIdentifiers is false and the primary identifier code resolves', () => {
94+
render(
95+
<PatientBannerPatientIdentifiers identifiers={mockIdentifiers} showIdentifierLabel showAllIdentifiers={false} />,
96+
);
97+
98+
expect(screen.getByText(/openmrs id/i)).toBeInTheDocument();
99+
expect(screen.getByText(/100gej/i)).toBeInTheDocument();
100+
expect(screen.queryByText(/national id/i)).not.toBeInTheDocument();
101+
});
76102
});

packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.component.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ interface PatientBannerPatientInfoProps {
1717
* affect how / if they should be rendered
1818
*/
1919
renderedFrom?: string;
20+
showAllIdentifiers?: boolean;
2021
}
2122

2223
type Gender = 'female' | 'male' | 'other' | 'unknown';
@@ -58,7 +59,11 @@ const getGender = (gender: string) => {
5859
};
5960
};
6061

61-
export function PatientBannerPatientInfo({ patient, renderedFrom }: PatientBannerPatientInfoProps) {
62+
export function PatientBannerPatientInfo({
63+
patient,
64+
renderedFrom,
65+
showAllIdentifiers = true,
66+
}: Readonly<PatientBannerPatientInfoProps>) {
6267
const name = getPatientName(patient);
6368
const genderInfo = patient?.gender && getGender(patient.gender);
6469

@@ -87,12 +92,15 @@ export function PatientBannerPatientInfo({ patient, renderedFrom }: PatientBanne
8792
{patient.birthDate && (
8893
<>
8994
<span>{age(patient.birthDate)}</span>
90-
<span className={styles.separator}>&middot;</span>
91-
<span>{formatPartialDate(patient.birthDate, { time: false })}</span>
92-
<span className={styles.separator}>&middot;</span>
95+
<span className={styles.withSeparator}>{formatPartialDate(patient.birthDate, { time: false })}</span>
9396
</>
9497
)}
95-
<PatientBannerPatientIdentifiers identifiers={patient.identifier} showIdentifierLabel />
98+
<PatientBannerPatientIdentifiers
99+
identifiers={patient.identifier}
100+
showIdentifierLabel
101+
showLeadingSeparator={Boolean(patient.birthDate)}
102+
showAllIdentifiers={showAllIdentifiers}
103+
/>
96104
<ExtensionSlot className={styles.extensionSlot} name="patient-banner-bottom-slot" state={extensionState} />
97105
</div>
98106
</div>

packages/framework/esm-styleguide/src/patient-banner/patient-info/patient-banner-patient-info.module.scss

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@
1313
background-color: $ui-01;
1414
}
1515

16+
.withSeparator {
17+
&::before {
18+
content: '\00B7';
19+
margin: 0 layout.$spacing-02;
20+
}
21+
}
22+
1623
.patientBanner {
1724
display: flex;
1825
}
@@ -22,6 +29,7 @@
2229
color: colors.$gray-100;
2330
font-weight: 600;
2431
margin-right: layout.$spacing-02;
32+
overflow-wrap: anywhere;
2533
}
2634

2735
.patientAvatar {
@@ -43,6 +51,7 @@
4351
flex-direction: column;
4452
padding: layout.$spacing-05 layout.$spacing-03 layout.$spacing-05 0;
4553
width: 100%;
54+
min-width: 0;
4655
}
4756

4857
.demographics {

packages/framework/esm-styleguide/src/patient-photo/patient-photo.component.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,10 @@ export interface PatientPhotoProps {
1313
alt?: string;
1414
}
1515

16-
function getInitials(name: string, maxInitials = 3): string {
17-
return name
18-
.split(/\s+/)
19-
.filter(Boolean)
20-
.slice(0, maxInitials)
21-
.map((part) => part[0])
22-
.join('');
16+
function getInitials(name: string, maxInitials = 2): string {
17+
const parts = name.split(/\s+/).filter(Boolean);
18+
const selected = parts.length > maxInitials ? [parts[0], parts.at(-1)] : parts;
19+
return selected.map((part) => part![0]).join('');
2320
}
2421

2522
/**

packages/framework/esm-styleguide/src/patient-photo/patient-photo.test.tsx

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,17 @@ describe('PatientPhoto', () => {
184184
// Restore the original Image constructor
185185
window.Image = originalImage;
186186
});
187+
188+
it('caps initials at two characters, using first and last name parts, for multi-part names', () => {
189+
mockUsePatientPhoto.mockReturnValue({
190+
isLoading: false,
191+
data: null,
192+
error: undefined,
193+
});
194+
195+
render(<PatientPhoto patientUuid={patientUuid} patientName="Wanjiru Achieng Nakato" />);
196+
197+
const avatar = screen.getByTitle('Wanjiru Achieng Nakato');
198+
expect(avatar).toHaveTextContent('WN');
199+
});
187200
});

0 commit comments

Comments
 (0)