Skip to content

Commit 41d284d

Browse files
authored
show more button for v2 drawer dates (#1809)
* break out learning resource run comparison into a utility function * add show more functionality for start dates * remove separator at the end before show more / show less button * don't wrap dates * don't wrap show more button * fix test * fix cta image, use aspect-ratio and relative position * put aspect on img element instead of containing div so it works in chrome and safari * fix tests * remove start date comparison as it's not supposed to work that way * don't show dates info item at all if there are differning runs * show less link should be on its own line * remove unnecessary line break * optimize the code a bit * better name * fix column widths in differing runs table * refactor logic surrounding run dates * filter out null start dates * adjust padding above "Show less"
1 parent 2942fac commit 41d284d

File tree

7 files changed

+365
-100
lines changed

7 files changed

+365
-100
lines changed

frontends/ol-components/src/components/LearningResourceCard/testUtils.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ const resources = {
4242
}
4343

4444
const sameDataRun = factories.learningResources.run({
45+
delivery: [
46+
{
47+
code: DeliveryEnum.Online,
48+
name: DeliveryEnumDescriptions.online,
49+
},
50+
],
4551
resource_prices: [
4652
{ amount: "0", currency: "USD" },
4753
{ amount: "100", currency: "USD" },
@@ -161,6 +167,7 @@ const courses = {
161167
multipleRuns: {
162168
sameData: makeResource({
163169
resource_type: ResourceTypeEnum.Course,
170+
free: true,
164171
runs: [
165172
factories.learningResources.run({
166173
delivery: sameDataRun.delivery,

frontends/ol-components/src/components/LearningResourceExpanded/DifferingRunsTable.tsx

Lines changed: 44 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from "react"
22
import styled from "@emotion/styled"
33
import { theme } from "../ThemeProvider/ThemeProvider"
4-
import { LearningResource, LearningResourcePrice } from "api"
4+
import { LearningResource } from "api"
55
import {
6+
allRunsAreIdentical,
67
formatRunDate,
78
getDisplayPrice,
89
getRunPrices,
@@ -33,7 +34,6 @@ const DifferingRunHeader = styled.div({
3334
display: "flex",
3435
alignSelf: "stretch",
3536
alignItems: "center",
36-
flex: "1 0 0",
3737
gap: "16px",
3838
padding: "12px",
3939
color: theme.custom.colors.darkGray2,
@@ -43,17 +43,46 @@ const DifferingRunHeader = styled.div({
4343

4444
const DifferingRunData = styled.div({
4545
display: "flex",
46-
flexShrink: 0,
47-
flex: "1 0 0",
4846
color: theme.custom.colors.darkGray2,
4947
...theme.typography.body3,
5048
})
5149

5250
const DifferingRunLabel = styled.strong({
5351
display: "flex",
54-
flex: "1 0 0",
5552
})
5653

54+
const dateColumnStyle = {
55+
width: "130px",
56+
[theme.breakpoints.down("sm")]: {
57+
width: "auto",
58+
flex: "2 0 0",
59+
},
60+
}
61+
62+
const priceColumnStyle = {
63+
width: "110px",
64+
[theme.breakpoints.down("sm")]: {
65+
width: "auto",
66+
flex: "1 0 0",
67+
},
68+
}
69+
70+
const formatStyle = {
71+
flex: "1 0 0",
72+
}
73+
74+
const DateLabel = styled(DifferingRunLabel)(dateColumnStyle)
75+
76+
const PriceLabel = styled(DifferingRunLabel)(priceColumnStyle)
77+
78+
const FormatLabel = styled(DifferingRunLabel)(formatStyle)
79+
80+
const DateData = styled(DifferingRunData)(dateColumnStyle)
81+
82+
const PriceData = styled(DifferingRunData)(priceColumnStyle)
83+
84+
const FormatData = styled(DifferingRunData)(formatStyle)
85+
5786
const DifferingRunLocation = styled(DifferingRunData)({
5887
flex: "1 0 100%",
5988
flexDirection: "column",
@@ -63,62 +92,27 @@ const DifferingRunLocation = styled(DifferingRunData)({
6392
const DifferingRunsTable: React.FC<{ resource: LearningResource }> = ({
6493
resource,
6594
}) => {
66-
if (!resource.runs) {
67-
return null
68-
}
69-
if (resource.runs.length === 1) {
70-
return null
71-
}
7295
const asTaughtIn = resource ? showStartAnytime(resource) : false
73-
const prices: LearningResourcePrice[] = []
74-
const deliveryMethods = []
75-
const locations = []
76-
for (const run of resource.runs) {
77-
if (run.resource_prices) {
78-
run.resource_prices.forEach((price) => {
79-
if (price.amount !== "0") {
80-
prices.push(price)
81-
}
82-
})
83-
}
84-
if (run.delivery) {
85-
deliveryMethods.push(run.delivery)
86-
}
87-
if (run.location) {
88-
locations.push(run.location)
89-
}
90-
}
91-
const distinctPrices = [...new Set(prices.map((p) => p.amount).flat())]
92-
const distinctDeliveryMethods = [
93-
...new Set(deliveryMethods.flat().map((dm) => dm?.code)),
94-
]
95-
const distinctLocations = [...new Set(locations.flat().map((l) => l))]
96-
if (
97-
distinctPrices.length > 1 ||
98-
distinctDeliveryMethods.length > 1 ||
99-
distinctLocations.length > 1
100-
) {
96+
if (!allRunsAreIdentical(resource)) {
10197
return (
10298
<DifferingRuns data-testid="differing-runs-table">
10399
<DifferingRunHeader>
104-
<DifferingRunLabel>Date</DifferingRunLabel>
105-
<DifferingRunLabel>Price</DifferingRunLabel>
106-
<DifferingRunLabel>Format</DifferingRunLabel>
100+
<DateLabel>Date</DateLabel>
101+
<PriceLabel>Price</PriceLabel>
102+
<FormatLabel>Format</FormatLabel>
107103
</DifferingRunHeader>
108-
{resource.runs.map((run, index) => (
104+
{resource.runs?.map((run, index) => (
109105
<DifferingRun key={index}>
110-
<DifferingRunData>
111-
{formatRunDate(run, asTaughtIn)}
112-
</DifferingRunData>
106+
<DateData>{formatRunDate(run, asTaughtIn)}</DateData>
113107
{run.resource_prices && (
114-
<DifferingRunData>
108+
<PriceData>
115109
<span>{getDisplayPrice(getRunPrices(run)["course"])}</span>
116-
</DifferingRunData>
110+
</PriceData>
117111
)}
118112
{run.delivery && (
119-
<DifferingRunData>
113+
<FormatData>
120114
<span>{run.delivery?.map((dm) => dm?.name).join(", ")}</span>
121-
</DifferingRunData>
115+
</FormatData>
122116
)}
123117
{run.delivery.filter((d) => d.code === "in_person").length > 0 &&
124118
run.location && (

frontends/ol-components/src/components/LearningResourceExpanded/InfoSectionV2.test.tsx

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import InfoSectionV2 from "./InfoSectionV2"
55
import { ThemeProvider } from "../ThemeProvider/ThemeProvider"
66
import { formatRunDate } from "ol-utilities"
77
import invariant from "tiny-invariant"
8+
import user from "@testing-library/user-event"
89

910
// This is a pipe followed by a zero-width space
1011
const SEPARATOR = "|​"
@@ -134,25 +135,49 @@ describe("Learning resource info section start date", () => {
134135
within(section).getByText(runDate)
135136
})
136137

137-
test("Multiple Runs", () => {
138-
const course = courses.free.multipleRuns
139-
const expectedDateText = course.runs
138+
test("Multiple run dates", () => {
139+
const course = courses.multipleRuns.sameData
140+
const expectedDateText = `${course.runs
140141
?.sort((a, b) => {
141142
if (a?.start_date && b?.start_date) {
142143
return Date.parse(a.start_date) - Date.parse(b.start_date)
143144
}
144145
return 0
145146
})
146147
.map((run) => formatRunDate(run, false))
147-
.join(SEPARATOR)
148+
.slice(0, 2)
149+
.join(SEPARATOR)}Show more`
148150
invariant(expectedDateText)
149151
render(<InfoSectionV2 resource={course} />, {
150152
wrapper: ThemeProvider,
151153
})
152154

153155
const section = screen.getByTestId("drawer-info-items")
154-
within(section).getByText((_content, node) => {
156+
within(section).getAllByText((_content, node) => {
155157
return node?.textContent === expectedDateText || false
156158
})
157159
})
160+
161+
test("If data is different, dates are not shown", () => {
162+
const course = courses.multipleRuns.differentData
163+
render(<InfoSectionV2 resource={course} />, {
164+
wrapper: ThemeProvider,
165+
})
166+
const section = screen.getByTestId("drawer-info-items")
167+
expect(within(section).queryByText("Start Date:")).toBeNull()
168+
})
169+
170+
test("Clicking the show more button should show more dates", async () => {
171+
const course = courses.multipleRuns.sameData
172+
const totalRuns = course.runs?.length ? course.runs.length : 0
173+
render(<InfoSectionV2 resource={course} />, {
174+
wrapper: ThemeProvider,
175+
})
176+
177+
const runDates = screen.getByTestId("drawer-run-dates")
178+
expect(runDates.children.length).toBe(3)
179+
const showMoreLink = within(runDates).getByText("Show more")
180+
await user.click(showMoreLink)
181+
expect(runDates.children.length).toBe(totalRuns + 1)
182+
})
158183
})

frontends/ol-components/src/components/LearningResourceExpanded/InfoSectionV2.tsx

Lines changed: 96 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from "react"
1+
import React, { useState } from "react"
22
import styled from "@emotion/styled"
33
import ISO6391 from "iso-639-1"
44
import {
@@ -16,13 +16,15 @@ import {
1616
} from "@remixicon/react"
1717
import { LearningResource, ResourceTypeEnum } from "api"
1818
import {
19+
allRunsAreIdentical,
1920
formatDurationClockTime,
2021
formatRunDate,
2122
getLearningResourcePrices,
2223
showStartAnytime,
2324
} from "ol-utilities"
2425
import { theme } from "../ThemeProvider/ThemeProvider"
2526
import DifferingRunsTable from "./DifferingRunsTable"
27+
import { Link } from "../Link/Link"
2628

2729
const SeparatorContainer = styled.span({
2830
padding: "0 8px",
@@ -87,6 +89,19 @@ const InfoValue = styled.div({
8789
...theme.typography.body3,
8890
})
8991

92+
const NoWrap = styled.span({
93+
whiteSpace: "nowrap",
94+
})
95+
96+
const ShowMoreLink = styled(Link)({
97+
paddingLeft: "12px",
98+
})
99+
100+
const ShowLessLink = styled(Link)({
101+
display: "flex",
102+
paddingTop: "4px",
103+
})
104+
90105
const PriceDisplay = styled.div({
91106
display: "flex",
92107
alignItems: "center",
@@ -145,6 +160,77 @@ const InfoItemValue: React.FC<InfoItemValueProps> = ({
145160
)
146161
}
147162

163+
const RunDates: React.FC<{ resource: LearningResource }> = ({ resource }) => {
164+
const [showingMore, setShowingMore] = useState(false)
165+
const asTaughtIn = showStartAnytime(resource)
166+
const sortedDates = resource.runs
167+
?.sort((a, b) => {
168+
if (a?.start_date && b?.start_date) {
169+
return Date.parse(a.start_date) - Date.parse(b.start_date)
170+
}
171+
return 0
172+
})
173+
.map((run) => formatRunDate(run, asTaughtIn))
174+
const totalDates = sortedDates?.length || 0
175+
const showMore = totalDates > 2
176+
if (showMore) {
177+
const ShowHideLink = showingMore ? ShowLessLink : ShowMoreLink
178+
const showMoreLink = (
179+
<NoWrap>
180+
<ShowHideLink
181+
color="red"
182+
size="small"
183+
onClick={() => setShowingMore(!showingMore)}
184+
>
185+
{showingMore ? "Show less" : "Show more"}
186+
</ShowHideLink>
187+
</NoWrap>
188+
)
189+
return (
190+
<span data-testid="drawer-run-dates">
191+
{sortedDates?.slice(0, 2).map((runDate, index) => {
192+
return (
193+
<NoWrap key={`run-${index}`}>
194+
<InfoItemValue
195+
label={runDate}
196+
index={index}
197+
total={showingMore ? 3 : 2}
198+
/>
199+
</NoWrap>
200+
)
201+
})}
202+
{!showingMore && showMoreLink}
203+
{showingMore &&
204+
sortedDates?.slice(2).map((runDate, index) => {
205+
return (
206+
<NoWrap key={`run-${index + 2}`}>
207+
<InfoItemValue
208+
label={runDate}
209+
index={index}
210+
total={sortedDates.length - 2}
211+
/>
212+
</NoWrap>
213+
)
214+
})}
215+
{showingMore && showMoreLink}
216+
</span>
217+
)
218+
} else {
219+
const runDates = sortedDates?.map((runDate, index) => {
220+
return (
221+
<NoWrap key={`run-${index}`}>
222+
<InfoItemValue
223+
label={runDate}
224+
index={index}
225+
total={sortedDates.length}
226+
/>
227+
</NoWrap>
228+
)
229+
})
230+
return <span data-testid="drawer-run-dates">{runDates}</span>
231+
}
232+
}
233+
148234
const INFO_ITEMS: InfoItemConfig = [
149235
{
150236
label: (resource: LearningResource) => {
@@ -154,33 +240,10 @@ const INFO_ITEMS: InfoItemConfig = [
154240
},
155241
Icon: RiCalendarLine,
156242
selector: (resource: LearningResource) => {
157-
const asTaughtIn = resource ? showStartAnytime(resource) : false
158-
if (
159-
[ResourceTypeEnum.Course, ResourceTypeEnum.Program].includes(
160-
resource.resource_type as "course" | "program",
161-
)
162-
) {
163-
const sortedDates =
164-
resource.runs
165-
?.sort((a, b) => {
166-
if (a?.start_date && b?.start_date) {
167-
return Date.parse(a.start_date) - Date.parse(b.start_date)
168-
}
169-
return 0
170-
})
171-
.map((run) => formatRunDate(run, asTaughtIn)) ?? []
172-
const runDates =
173-
sortedDates.map((runDate, index) => {
174-
return (
175-
<InfoItemValue
176-
key={`run-${index}`}
177-
label={runDate}
178-
index={index}
179-
total={sortedDates.length}
180-
/>
181-
)
182-
}) ?? []
183-
return runDates
243+
const totalDatesWithRuns =
244+
resource.runs?.filter((run) => run.start_date !== null).length || 0
245+
if (allRunsAreIdentical(resource) && totalDatesWithRuns > 0) {
246+
return <RunDates resource={resource} />
184247
} else return null
185248
},
186249
},
@@ -396,9 +459,11 @@ const InfoSectionV2 = ({ resource }: { resource?: LearningResource }) => {
396459
<>
397460
<DifferingRunsTable resource={resource} />
398461
<InfoItems data-testid="drawer-info-items">
399-
{infoItems.map((props, index) => (
400-
<InfoItem key={index} {...props} />
401-
))}
462+
{infoItems
463+
.filter((props) => props.value !== null)
464+
.map((props, index) => (
465+
<InfoItem key={index} {...props} />
466+
))}
402467
</InfoItems>
403468
</>
404469
)

0 commit comments

Comments
 (0)