Skip to content

Commit eb4fa67

Browse files
committed
feat: Add tooltips to library sync icon
1 parent 0969264 commit eb4fa67

7 files changed

Lines changed: 92 additions & 40 deletions

File tree

src/course-outline/section-card/SectionCard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ const section = {
7575
versionDeclined: null,
7676
errorMessage: null,
7777
downstreamCustomized: [] as string[],
78+
upstreamName: 'Upstream',
7879
},
7980
} satisfies Partial<XBlock> as XBlock;
8081

src/course-outline/subsection-card/SubsectionCard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const subsection: XBlock = {
7979
versionDeclined: null,
8080
errorMessage: null,
8181
downstreamCustomized: [] as string[],
82+
upstreamName: 'Upstream',
8283
},
8384
} satisfies Partial<XBlock> as XBlock;
8485

src/course-outline/unit-card/UnitCard.test.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ const unit = {
6969
versionDeclined: null,
7070
errorMessage: null,
7171
downstreamCustomized: [] as string[],
72+
upstreamName: 'Upstream',
7273
},
7374
} satisfies Partial<XBlock> as XBlock;
7475

src/data/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export interface UpstreamChildrenInfo {
5757
export interface UpstreamInfo {
5858
readyToSync: boolean,
5959
upstreamRef: string,
60+
upstreamName: string,
6061
versionSynced: number,
6162
versionAvailable: number | null,
6263
versionDeclined: number | null,

src/generic/upstream-info-icon/UpstreamInfoIcon.test.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ describe('<UpstreamInfoIcon>', () => {
2323
errorMessage: null,
2424
readyToSync: false,
2525
downstreamCustomized: [],
26+
upstreamName: 'Upstream',
2627
});
2728
expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
2829
expect(screen.queryByTitle('The referenced library or library object is not available.')).not.toBeInTheDocument();
@@ -34,6 +35,7 @@ describe('<UpstreamInfoIcon>', () => {
3435
errorMessage: 'upstream error',
3536
readyToSync: false,
3637
downstreamCustomized: [],
38+
upstreamName: 'Upstream',
3739
});
3840
expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
3941
expect(screen.getByTitle('The referenced library or library object is not available.')).toBeInTheDocument();
@@ -45,6 +47,7 @@ describe('<UpstreamInfoIcon>', () => {
4547
errorMessage: null,
4648
readyToSync: true,
4749
downstreamCustomized: [],
50+
upstreamName: 'Upstream',
4851
});
4952

5053
const icon = screen.getByTitle('This item is linked to a library item.');
@@ -61,6 +64,7 @@ describe('<UpstreamInfoIcon>', () => {
6164
errorMessage: null,
6265
readyToSync: false,
6366
downstreamCustomized: ['data'],
67+
upstreamName: 'Upstream',
6468
});
6569

6670
expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
@@ -73,6 +77,7 @@ describe('<UpstreamInfoIcon>', () => {
7377
errorMessage: null,
7478
readyToSync: true,
7579
downstreamCustomized: ['data'],
80+
upstreamName: 'Upstream',
7681
});
7782

7883
expect(screen.getByTitle('This item is linked to a library item.')).toBeInTheDocument();
@@ -92,6 +97,7 @@ describe('<UpstreamInfoIcon>', () => {
9297
errorMessage: null,
9398
readyToSync: false,
9499
downstreamCustomized: [],
100+
upstreamName: 'Upstream',
95101
});
96102
const container = screen.getByTestId('redux-provider');
97103
expect(container).toBeEmptyDOMElement();

src/generic/upstream-info-icon/index.tsx

Lines changed: 72 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,21 @@
11
/* eslint-disable react/prop-types */
22
import { useIntl } from '@edx/frontend-platform/i18n';
3-
import { Button, Icon } from '@openedx/paragon';
3+
import {
4+
Button, Icon, OverlayTrigger, Tooltip,
5+
} from '@openedx/paragon';
46
import {
57
CallSplit, LinkOff, Newsstand, Sync,
68
} from '@openedx/paragon/icons';
79

10+
import { BoldText } from '@src/utils';
11+
import { ReactNode } from 'react';
812
import messages from './messages';
913

1014
export interface UpstreamInfoIconProps {
1115
upstreamInfo?: {
1216
errorMessage?: string | null;
1317
upstreamRef?: string | null;
18+
upstreamName: string;
1419
readyToSync: boolean;
1520
downstreamCustomized: string[];
1621
};
@@ -23,54 +28,81 @@ const UpstreamInfoIconContent = ({
2328
}: UpstreamInfoIconProps) => {
2429
const intl = useIntl();
2530

31+
if (!upstreamInfo) {
32+
return null;
33+
}
34+
2635
let hasTwoIcons = false;
27-
const hasCourseOverrides = (upstreamInfo?.downstreamCustomized.length || 0) > 0;
28-
if (upstreamInfo?.errorMessage || upstreamInfo?.readyToSync || hasCourseOverrides) {
36+
let secondIconProps = {};
37+
let tooltipMessage: string | ReactNode = intl.formatMessage(
38+
messages.upstreamLinkTooltip,
39+
{
40+
upstreamName: upstreamInfo.upstreamName,
41+
b: BoldText,
42+
},
43+
);
44+
45+
if (upstreamInfo.errorMessage) {
46+
hasTwoIcons = true;
47+
tooltipMessage = intl.formatMessage(messages.upstreamLinkError);
48+
secondIconProps = {
49+
title: intl.formatMessage(messages.upstreamLinkError),
50+
ariaLabel: intl.formatMessage(messages.upstreamLinkError),
51+
src: LinkOff,
52+
};
53+
} else if (upstreamInfo.readyToSync) {
2954
hasTwoIcons = true;
55+
tooltipMessage = intl.formatMessage(
56+
messages.upstreamLinkReadyToSyncTooltip,
57+
{
58+
upstreamName: upstreamInfo.upstreamName,
59+
b: BoldText,
60+
},
61+
);
62+
secondIconProps = {
63+
title: intl.formatMessage(messages.upstreamLinkReadyToSyncAriaLabel),
64+
ariaLabel: intl.formatMessage(messages.upstreamLinkReadyToSyncAriaLabel),
65+
src: Sync,
66+
};
67+
} else if ((upstreamInfo.downstreamCustomized.length || 0) > 0) {
68+
hasTwoIcons = true;
69+
tooltipMessage = intl.formatMessage(messages.upstreamLinkOverridesAriaLabel);
70+
secondIconProps = {
71+
title: intl.formatMessage(messages.upstreamLinkOverridesAriaLabel),
72+
ariaLabel: intl.formatMessage(messages.upstreamLinkOverridesAriaLabel),
73+
src: CallSplit,
74+
};
3075
}
3176

32-
const getSecondIconProps = () => {
33-
if (upstreamInfo?.errorMessage) {
34-
return {
35-
title: intl.formatMessage(messages.upstreamLinkError),
36-
ariaLabel: intl.formatMessage(messages.upstreamLinkError),
37-
src: LinkOff,
38-
};
39-
} if (upstreamInfo?.readyToSync) {
40-
return {
41-
title: intl.formatMessage(messages.upstreamLinkReadyToSyncAriaLabel),
42-
ariaLabel: intl.formatMessage(messages.upstreamLinkReadyToSyncAriaLabel),
43-
src: Sync,
44-
};
45-
} if (hasCourseOverrides) {
46-
return {
47-
title: intl.formatMessage(messages.upstreamLinkOverridesAriaLabel),
48-
ariaLabel: intl.formatMessage(messages.upstreamLinkOverridesAriaLabel),
49-
src: CallSplit,
50-
};
51-
}
52-
return {};
53-
};
54-
5577
return (
56-
<div
57-
className={
58-
`upstream-info-icon size-${hasTwoIcons ? 'two' : 'one'}-${size} box-shadow-centered-1 d-flex justify-content-center`
59-
}
78+
<OverlayTrigger
79+
key={`upstream-icon-${upstreamInfo.upstreamRef}`}
80+
placement="top"
81+
overlay={(
82+
<Tooltip id={`upstream-icon-tooltip-${upstreamInfo.upstreamRef}`}>
83+
{tooltipMessage}
84+
</Tooltip>
85+
)}
6086
>
61-
<Icon
62-
title={intl.formatMessage(messages.upstreamLinkOk)}
63-
aria-label={intl.formatMessage(messages.upstreamLinkOk)}
64-
src={Newsstand}
65-
size={size}
66-
/>
67-
{hasTwoIcons && (
87+
<div
88+
className={
89+
`upstream-info-icon size-${hasTwoIcons ? 'two' : 'one'}-${size} box-shadow-centered-1 d-flex justify-content-center`
90+
}
91+
>
6892
<Icon
93+
title={intl.formatMessage(messages.upstreamLinkOk)}
94+
aria-label={intl.formatMessage(messages.upstreamLinkOk)}
95+
src={Newsstand}
6996
size={size}
70-
{...getSecondIconProps()}
7197
/>
72-
)}
73-
</div>
98+
{hasTwoIcons && (
99+
<Icon
100+
size={size}
101+
{...secondIconProps}
102+
/>
103+
)}
104+
</div>
105+
</OverlayTrigger>
74106
);
75107
};
76108

src/generic/upstream-info-icon/messages.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@ const messages = defineMessages({
1616
id: 'upstream-icon.ready-to-sync.aria-label',
1717
description: 'Hint and aria-label for the upstream icon when the link is ready to sync.',
1818
},
19+
upstreamLinkReadyToSyncTooltip: {
20+
defaultMessage: 'The linked <b>{upstreamName}</b> has updates available.',
21+
id: 'upstream-icon.ready-to-sync.tooltip',
22+
description: 'Tooltip text for the upstream icon when the link is ready to sync.',
23+
},
1924
upstreamLinkOverridesAriaLabel: {
2025
defaultMessage: 'This library reference has course overrides applied.',
2126
id: 'upstream-icon.course-overrides.aria-label',
2227
description: 'Hint and aria-label for the upstream icon when the link has course overrides.',
2328
},
29+
upstreamLinkTooltip: {
30+
defaultMessage: 'This is referenced via <b>{upstreamName}</b>',
31+
id: 'upstream-icon.ok.tooltip',
32+
description: 'Tooltip text for the upstream icon when the link is valid.',
33+
},
2434
});
2535

2636
export default messages;

0 commit comments

Comments
 (0)