From ee148e8ac158cfe8955aad86e5f235cefec8c039 Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Fri, 7 Jun 2024 14:48:53 +0200 Subject: [PATCH 01/25] Card updated default and hover states --- .../src/pages/HomePage/NewsEventsSection.tsx | 37 ++++++------ .../src/components/Card/Card.tsx | 56 +++++++------------ .../LearningResourceCard.tsx | 5 +- 3 files changed, 42 insertions(+), 56 deletions(-) diff --git a/frontends/mit-open/src/pages/HomePage/NewsEventsSection.tsx b/frontends/mit-open/src/pages/HomePage/NewsEventsSection.tsx index 551fbcb3fa..68e9c530e6 100644 --- a/frontends/mit-open/src/pages/HomePage/NewsEventsSection.tsx +++ b/frontends/mit-open/src/pages/HomePage/NewsEventsSection.tsx @@ -7,7 +7,6 @@ import { Grid, useMuiBreakpointAtLeast, Card, - CardLinkContainer, } from "ol-components" import { useNewsEventsList, @@ -105,7 +104,7 @@ const MobileEvents = styled(Events)` padding: 0 16px; ` -const EventCard = styled(CardLinkContainer)` +const EventCard = styled(Card)` display: flex; align-items: center; gap: 16px; @@ -208,22 +207,24 @@ const NewsEventsSection: React.FC = () => { const EventCards = events!.results?.map((item) => ( - - - {formatDate( - (item as EventFeedItem).event_details?.event_datetime, - "D", - )} - - - {formatDate( - (item as EventFeedItem).event_details?.event_datetime, - "MMM", - )} - - - {item.title} - + + + + {formatDate( + (item as EventFeedItem).event_details?.event_datetime, + "D", + )} + + + {formatDate( + (item as EventFeedItem).event_details?.event_datetime, + "MMM", + )} + + + {item.title} + + )) || [] diff --git a/frontends/ol-components/src/components/Card/Card.tsx b/frontends/ol-components/src/components/Card/Card.tsx index dfba9ce485..d30645cfe9 100644 --- a/frontends/ol-components/src/components/Card/Card.tsx +++ b/frontends/ol-components/src/components/Card/Card.tsx @@ -8,47 +8,35 @@ import React, { import styled from "@emotion/styled" import { theme } from "../ThemeProvider/ThemeProvider" import { pxToRem } from "../ThemeProvider/typography" +import { Link } from "react-router-dom" export type Size = "small" | "medium" -const cardStyles = ` +const getWidthCss = ({ size }: { size?: Size }) => { + let width + if (size === "medium") width = 300 + if (size === "small") width = 192 + return ` + min-width: ${width}px; + max-width: ${width}px; + ` +} + +const Container = styled(Link)` border-radius: 8px; border: 1px solid ${theme.custom.colors.lightGray2}; background: ${theme.custom.colors.white}; - box-shadow: - 0 2px 4px 0 rgb(37 38 43 / 10%), - 0 2px 4px 0 rgb(37 38 43 / 10%); overflow: hidden; -` - -const Container = styled.div<{ size?: Size }>` - ${cardStyles} - ${({ size }) => { - let width - if (size === "medium") width = 300 - if (size === "small") width = 192 - return ` - min-width: ${width}px; - max-width: ${width}px; - ` - }} -` - -const LinkContainer = styled.a` - ${cardStyles} + ${getWidthCss} display: block; :hover { text-decoration: none; - color: ${theme.custom.colors.mitRed}; border-color: ${theme.custom.colors.silverGrayLight}; + box-shadow: + 0 2px 4px 0 rgb(37 38 43 / 10%), + 0 2px 4px 0 rgb(37 38 43 / 10%); cursor: pointer; - - h3, - > p { - color: ${theme.custom.colors.mitRed}; - text-decoration: underline; - } } ` @@ -137,9 +125,7 @@ type Card = FC & { Actions: FC<{ children: ReactNode }> } -const Card: Card = ({ children, className, size, link, href }) => { - const _Container = link ? LinkContainer : Container - +const Card: Card = ({ children, className, size, href }) => { let content, imageProps, info, title, footer, actions Children.forEach(children, (child) => { @@ -154,14 +140,14 @@ const Card: Card = ({ children, className, size, link, href }) => { if (content) { return ( - + {content} ) } return ( - <_Container className={className} href={href} size={size}> + {imageProps && ( {
{footer}
{actions && {actions}} - +
) } @@ -187,4 +173,4 @@ Card.Title = Title Card.Footer = Footer Card.Actions = Actions -export { Card, Container as CardContainer, LinkContainer as CardLinkContainer } +export { Card, Container as CardContainer } diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx index a95d4a9d39..8d23bd8150 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx @@ -129,7 +129,6 @@ const LearningResourceCard: React.FC = ({ resource, className, size = "medium", - onActivate, onAddToLearningPathClick, onAddToUserListClick, }) => { @@ -150,13 +149,13 @@ const LearningResourceCard: React.FC = ({ return null } return ( - + - + <Title resource={resource} size={size} /> </Card.Title> <Card.Actions> {onAddToLearningPathClick && ( From 5ee44aaca6d5d8867374bcdf4801728cc8b55f3c Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:28:45 +0200 Subject: [PATCH 02/25] Base list card components --- .../SearchDisplay/SearchDisplay.tsx | 5 +- .../src/components/Card/Card.tsx | 2 +- .../src/components/Card/ListCard.tsx | 166 ++++++++++++++++++ .../LearningResourceCard.tsx | 33 +--- .../LearningResourceListCard.tsx | 164 +++++++++++++++++ frontends/ol-components/src/index.ts | 1 + 6 files changed, 336 insertions(+), 35 deletions(-) create mode 100644 frontends/ol-components/src/components/Card/ListCard.tsx create mode 100644 frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx diff --git a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx index 8ac3bd1c34..13479553be 100644 --- a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx +++ b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx @@ -12,6 +12,7 @@ import { SimpleSelect, truncateText, css, + LearningResourceListCard } from "ol-components" import TuneIcon from "@mui/icons-material/Tune" @@ -33,7 +34,6 @@ import type { BooleanFacets, FacetManifest, } from "@mitodl/course-search-utils" -import LearningResourceCard from "@/page-components/LearningResourceCard/LearningResourceCard" import _ from "lodash" import { ResourceTypeTabs } from "./ResourceTypeTabs" @@ -365,8 +365,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({ <PlainList itemSpacing={3}> {data.results.map((resource) => ( <li key={resource.id}> - <LearningResourceCard - variant="row-reverse" + <LearningResourceListCard resource={resource} /> </li> diff --git a/frontends/ol-components/src/components/Card/Card.tsx b/frontends/ol-components/src/components/Card/Card.tsx index d30645cfe9..a9050a7ba5 100644 --- a/frontends/ol-components/src/components/Card/Card.tsx +++ b/frontends/ol-components/src/components/Card/Card.tsx @@ -173,4 +173,4 @@ Card.Title = Title Card.Footer = Footer Card.Actions = Actions -export { Card, Container as CardContainer } +export { Card } diff --git a/frontends/ol-components/src/components/Card/ListCard.tsx b/frontends/ol-components/src/components/Card/ListCard.tsx new file mode 100644 index 0000000000..5a6681026a --- /dev/null +++ b/frontends/ol-components/src/components/Card/ListCard.tsx @@ -0,0 +1,166 @@ +import React, { + FC, + ReactNode, + Children, + ImgHTMLAttributes, + isValidElement, +} from "react" +import styled from "@emotion/styled" +import { theme } from "../ThemeProvider/ThemeProvider" +import { pxToRem } from "../ThemeProvider/typography" +import { Link } from "react-router-dom" + +export type Size = "small" | "medium" + + +const Container = styled(Link)` + border-radius: 8px; + border: 1px solid ${theme.custom.colors.lightGray2}; + background: ${theme.custom.colors.white}; + overflow: hidden; + display: block; + + :hover { + text-decoration: none; + border-color: ${theme.custom.colors.silverGrayLight}; + box-shadow: + 0 2px 4px 0 rgb(37 38 43 / 10%), + 0 2px 4px 0 rgb(37 38 43 / 10%); + cursor: pointer; + } +` + +const Content = () => <></> + +const Body = styled.div` + margin: 16px; +` + +const Image = styled.img<{ size?: Size }>` + display: block; + background-size: cover; + background-repeat: no-repeat; + -webkit-background-position: center; + background-position: center; + width: 100%; + height: ${({ size }) => (size === "small" ? 120 : 170)}px; + background-color: ${theme.custom.colors.lightGray1}; +` + +const Info = styled.div<{ size?: Size }>` + ${{ ...theme.typography.subtitle3 }} + color: ${theme.custom.colors.silverGrayDark}; + display: flex; + justify-content: space-between; + margin-bottom: ${({ size }) => (size === "small" ? 4 : 8)}px; +` + +const Title = styled.h3<{ size?: Size }>` + text-overflow: ellipsis; + height: ${({ size }) => theme.typography.pxToRem(size === "small" ? 36 : 60)}; + overflow: hidden; + margin: 0; + + ${({ size }) => + size === "small" + ? { ...theme.typography.subtitle2 } + : { ...theme.typography.subtitle1 }} + @supports (-webkit-line-clamp: ${({ size }) => (size === "small" ? 2 : 3)}) { + white-space: initial; + display: -webkit-box; + -webkit-line-clamp: ${({ size }) => (size === "small" ? 2 : 3)}; + -webkit-box-orient: vertical; + } +` + +const Footer = styled.span` + display: block; + height: ${pxToRem(16)}; + ${{ + ...theme.typography.body3, + color: theme.custom.colors.silverGrayDark, + }} + + span { + color: ${theme.custom.colors.black}; + } +` + +const Bottom = styled.div` + display: flex; + justify-content: space-between; + align-items: flex-end; + margin: 0 16px 16px; + height: 32px; +` + +const Actions = styled.div` + display: flex; + gap: 8px; +` + +type CardProps = { + children: ReactNode[] | ReactNode + className?: string + size?: Size + link?: boolean + href?: string +} +type Card = FC<CardProps> & { + Content: FC<{ children: ReactNode }> + Image: FC<ImgHTMLAttributes<HTMLImageElement> | { size?: Size }> + Info: FC<{ children: ReactNode }> + Title: FC<{ children: ReactNode; size?: Size }> + Footer: FC<{ children: ReactNode }> + Actions: FC<{ children: ReactNode }> +} + +const ListCard: Card = ({ children, className, size, href }) => { + let content, imageProps, info, title, footer, actions + + Children.forEach(children, (child) => { + if (!isValidElement(child)) return + if (child.type === Content) content = child.props.children + else if (child.type === Image) imageProps = child.props + else if (child.type === Info) info = child.props.children + else if (child.type === Title) title = child.props.children + else if (child.type === Footer) footer = child.props.children + else if (child.type === Actions) actions = child.props.children + }) + + if (content) { + return ( + <Container className={className} to={href!} size={size}> + {content} + </Container> + ) + } + + return ( + <Container className={className} to={href!} size={size}> + {imageProps && ( + <Image + size={size} + {...(imageProps as ImgHTMLAttributes<HTMLImageElement>)} + /> + )} + <Body> + {info && <Info size={size}>{info}</Info>} + <Title size={size}>{title} + + +
{footer}
+ {actions && {actions}} +
+
+ ) +} + +ListCard.Content = Content +ListCard.Image = Image +ListCard.Info = Info +ListCard.Title = Title +ListCard.Footer = Footer +ListCard.Actions = Actions + +export { ListCard } diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx index 8d23bd8150..7d041924db 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx @@ -15,15 +15,6 @@ const EllipsisTitle = styled(TruncateText)({ margin: 0, }) -const TitleLink = styled.a` - display: block; - text-align: left; - - &:hover { - cursor: pointer; - } -` - const SkeletonImage = styled(Skeleton)<{ aspect: number }>` padding-bottom: ${({ aspect }) => 100 / aspect}%; ` @@ -44,25 +35,6 @@ const Info = ({ resource }: { resource: LearningResource }) => { ) } -const Title = ({ - resource, - size, - onActivate, -}: { - resource: LearningResource - size?: Size - onActivate?: ResourceIdCallback -}) => { - const lines = size === "small" ? 2 : 3 - return onActivate ? ( - onActivate(resource.id)} role="link"> - {resource.title} - - ) : ( - {resource.title} - ) -} - const Certificate = styled.div` border-radius: 4px; background-color: ${theme.custom.colors.lightGray1}; @@ -119,7 +91,6 @@ interface LearningResourceCardProps { resource?: LearningResource | null className?: string size?: Size - onActivate?: ResourceIdCallback onAddToLearningPathClick?: ResourceIdCallback | null onAddToUserListClick?: ResourceIdCallback | null } @@ -150,12 +121,12 @@ const LearningResourceCard: React.FC = ({ } return ( - + - + <EllipsisTitle lineClamp={size === "small" ? 2 : 3}>{resource.title}</EllipsisTitle> </Card.Title> <Card.Actions> {onAddToLearningPathClick && ( diff --git a/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx b/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx new file mode 100644 index 0000000000..b70851e312 --- /dev/null +++ b/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx @@ -0,0 +1,164 @@ +import React from "react" +import styled from "@emotion/styled" +import Skeleton from "@mui/material/Skeleton" +import { RiMenuAddLine, RiBookmarkLine, RiAwardFill } from "@remixicon/react" +import { LearningResource, ResourceTypeEnum, PlatformEnum } from "api" +import { findBestRun, formatDate, getReadableResourceType } from "ol-utilities" +import { ListCard } from "../Card/ListCard" +import type { Size } from "../Card/ListCard" +import { TruncateText } from "../TruncateText/TruncateText" +import { ActionButton } from "../Button/Button" +import { imgConfigs } from "../../constants/imgConfigs" +import { theme } from "../ThemeProvider/ThemeProvider" + +const EllipsisTitle = styled(TruncateText)({ + margin: 0, +}) + +const SkeletonImage = styled(Skeleton)<{ aspect: number }>` + padding-bottom: ${({ aspect }) => 100 / aspect}%; +` + +type ResourceIdCallback = (resourceId: number) => void + +const Info = ({ resource }: { resource: LearningResource }) => { + return ( + <> + <span>{getReadableResourceType(resource.resource_type)}</span> + {resource.certification && ( + <Certificate> + <RiAwardFill /> + Certificate + </Certificate> + )} + </> + ) +} + +const Certificate = styled.div` + border-radius: 4px; + background-color: ${theme.custom.colors.lightGray1}; + padding: 2px 4px; + color: ${theme.custom.colors.silverGrayDark}; + + ${{ ...theme.typography.subtitle4 }} + svg { + width: 12px; + height: 12px; + } + + display: flex; + align-items: center; + gap: 4px; +` + +const Footer: React.FC<{ resource: LearningResource; size?: Size }> = ({ + resource, + size, +}) => { + const isOcw = + resource.resource_type === ResourceTypeEnum.Course && + resource.platform?.code === PlatformEnum.Ocw + + let startDate = resource.next_start_date + + if (!startDate) { + const bestRun = findBestRun(resource.runs ?? []) + + if (isOcw && bestRun?.semester && bestRun?.year) { + return ( + <> + {size === "medium" ? "As taught in:" : ""}{" "} + <span>{`${bestRun?.semester} ${bestRun?.year}`}</span> + </> + ) + } + startDate = bestRun?.start_date + } + + if (!startDate) return null + + return ( + <> + {size === "medium" ? "Starts:" : ""}{" "} + <span>{formatDate(startDate, "MMMM DD, YYYY")}</span> + </> + ) +} + +interface LearningResourceListCardProps { + isLoading?: boolean + resource?: LearningResource | null + className?: string + size?: Size + onAddToLearningPathClick?: ResourceIdCallback | null + onAddToUserListClick?: ResourceIdCallback | null +} + +const LearningResourceListCard: React.FC<LearningResourceListCardProps> = ({ + isLoading, + resource, + className, + size = "medium", + onAddToLearningPathClick, + onAddToUserListClick, +}) => { + if (isLoading) { + const { width, height } = imgConfigs["column"] + return ( + <ListCard className={className} size={size}> + <ListCard.Content> + <SkeletonImage variant="rectangular" aspect={width / height} /> + <Skeleton height={25} width="65%" sx={{ margin: "23px 16px 0" }} /> + <Skeleton height={25} width="80%" sx={{ margin: "0 16px 35px" }} /> + <Skeleton height={25} width="30%" sx={{ margin: "0 16px 16px" }} /> + </ListCard.Content> + </ListCard> + ) + } + if (!resource) { + return null + } + return ( + <ListCard href={`?resource=${resource.id}`} className={className} size={size}> + <ListCard.Image src={resource.image?.url} alt={resource.image?.alt as string} /> + <ListCard.Info> + <Info resource={resource} /> + </ListCard.Info> + <ListCard.Title> + <EllipsisTitle lineClamp={size === "small" ? 2 : 3}>{resource.title}</EllipsisTitle> + </ListCard.Title> + <ListCard.Actions> + {onAddToLearningPathClick && ( + <ActionButton + variant="secondary" + edge="circular" + color="secondary" + size="small" + aria-label="Add to Learning Path" + onClick={() => onAddToLearningPathClick(resource.id)} + > + <RiMenuAddLine /> + </ActionButton> + )} + {onAddToUserListClick && ( + <ActionButton + variant="secondary" + edge="circular" + color="secondary" + size="small" + aria-label="Add to User List" + onClick={() => onAddToUserListClick(resource.id)} + > + <RiBookmarkLine /> + </ActionButton> + )} + </ListCard.Actions> + <ListCard.Footer> + <Footer resource={resource} size={size} /> + </ListCard.Footer> + </ListCard> + ) +} + +export { LearningResourceListCard } diff --git a/frontends/ol-components/src/index.ts b/frontends/ol-components/src/index.ts index 99449b5c98..e74bee8992 100644 --- a/frontends/ol-components/src/index.ts +++ b/frontends/ol-components/src/index.ts @@ -160,6 +160,7 @@ export * from "./components/Chips/ChipLink" export * from "./components/EmbedlyCard/EmbedlyCard" export * from "./components/FormDialog/FormDialog" export * from "./components/LearningResourceCard/LearningResourceCard" +export * from "./components/LearningResourceListCard/LearningResourceListCard" export * from "./components/LearningResourceExpanded/LearningResourceExpanded" export * from "./components/LoadingSpinner/LoadingSpinner" export * from "./components/Logo/Logo" From 4e121e6dfed407c280e55e86c1490e27318aa528 Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Fri, 7 Jun 2024 22:29:26 +0200 Subject: [PATCH 03/25] List card styles --- docker-compose.yml | 3 + .../SearchDisplay/SearchDisplay.tsx | 6 +- .../src/components/Card/Card.tsx | 1 - .../src/components/Card/ListCard.tsx | 72 +++++++++---------- .../LearningResourceCard.tsx | 9 ++- .../LearningResourceListCard.tsx | 23 ++++-- 6 files changed, 61 insertions(+), 53 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 417f5fafff..fe6073a331 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,6 +22,8 @@ services: - 5432:5432 environment: - POSTGRES_PASSWORD=postgres + volumes: + - pgdata:/var/lib/postgresql redis: profiles: @@ -142,3 +144,4 @@ volumes: opensearch-data1: django_media: yarn-cache: + pgdata: diff --git a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx index 13479553be..1a435b2777 100644 --- a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx +++ b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx @@ -12,7 +12,7 @@ import { SimpleSelect, truncateText, css, - LearningResourceListCard + LearningResourceListCard, } from "ol-components" import TuneIcon from "@mui/icons-material/Tune" @@ -365,9 +365,7 @@ const SearchDisplay: React.FC<SearchDisplayProps> = ({ <PlainList itemSpacing={3}> {data.results.map((resource) => ( <li key={resource.id}> - <LearningResourceListCard - resource={resource} - /> + <LearningResourceListCard resource={resource} /> </li> ))} </PlainList> diff --git a/frontends/ol-components/src/components/Card/Card.tsx b/frontends/ol-components/src/components/Card/Card.tsx index a9050a7ba5..3911f99dab 100644 --- a/frontends/ol-components/src/components/Card/Card.tsx +++ b/frontends/ol-components/src/components/Card/Card.tsx @@ -113,7 +113,6 @@ type CardProps = { children: ReactNode[] | ReactNode className?: string size?: Size - link?: boolean href?: string } type Card = FC<CardProps> & { diff --git a/frontends/ol-components/src/components/Card/ListCard.tsx b/frontends/ol-components/src/components/Card/ListCard.tsx index 5a6681026a..ed01def0fc 100644 --- a/frontends/ol-components/src/components/Card/ListCard.tsx +++ b/frontends/ol-components/src/components/Card/ListCard.tsx @@ -7,18 +7,16 @@ import React, { } from "react" import styled from "@emotion/styled" import { theme } from "../ThemeProvider/ThemeProvider" -import { pxToRem } from "../ThemeProvider/typography" import { Link } from "react-router-dom" export type Size = "small" | "medium" - const Container = styled(Link)` border-radius: 8px; border: 1px solid ${theme.custom.colors.lightGray2}; background: ${theme.custom.colors.white}; overflow: hidden; - display: block; + display: flex; :hover { text-decoration: none; @@ -33,49 +31,51 @@ const Container = styled(Link)` const Content = () => <></> const Body = styled.div` - margin: 16px; + flex-grow: 1; + margin: 24px; + display: flex; + flex-direction: column; + justify-content: space-between; ` -const Image = styled.img<{ size?: Size }>` +const Image = styled.img` display: block; background-size: cover; background-repeat: no-repeat; -webkit-background-position: center; background-position: center; - width: 100%; - height: ${({ size }) => (size === "small" ? 120 : 170)}px; + width: 236px; + height: 122px; + margin: 24px 24px 24px 0; + border-radius: 4px; background-color: ${theme.custom.colors.lightGray1}; ` -const Info = styled.div<{ size?: Size }>` +const Info = styled.div` ${{ ...theme.typography.subtitle3 }} color: ${theme.custom.colors.silverGrayDark}; display: flex; justify-content: space-between; - margin-bottom: ${({ size }) => (size === "small" ? 4 : 8)}px; + margin-bottom: 16px; ` -const Title = styled.h3<{ size?: Size }>` +const Title = styled.h3` + flex-grow: 1; text-overflow: ellipsis; - height: ${({ size }) => theme.typography.pxToRem(size === "small" ? 36 : 60)}; + ${{ ...theme.typography.subtitle1 }} + height: ${theme.typography.pxToRem(40)}; overflow: hidden; margin: 0; - - ${({ size }) => - size === "small" - ? { ...theme.typography.subtitle2 } - : { ...theme.typography.subtitle1 }} - @supports (-webkit-line-clamp: ${({ size }) => (size === "small" ? 2 : 3)}) { + @supports (-webkit-line-clamp: 2) { white-space: initial; display: -webkit-box; - -webkit-line-clamp: ${({ size }) => (size === "small" ? 2 : 3)}; + -webkit-line-clamp: 2; -webkit-box-orient: vertical; } ` const Footer = styled.span` display: block; - height: ${pxToRem(16)}; ${{ ...theme.typography.body3, color: theme.custom.colors.silverGrayDark, @@ -90,7 +90,6 @@ const Bottom = styled.div` display: flex; justify-content: space-between; align-items: flex-end; - margin: 0 16px 16px; height: 32px; ` @@ -102,20 +101,18 @@ const Actions = styled.div` type CardProps = { children: ReactNode[] | ReactNode className?: string - size?: Size - link?: boolean href?: string } type Card = FC<CardProps> & { Content: FC<{ children: ReactNode }> - Image: FC<ImgHTMLAttributes<HTMLImageElement> | { size?: Size }> + Image: FC<ImgHTMLAttributes<HTMLImageElement>> Info: FC<{ children: ReactNode }> - Title: FC<{ children: ReactNode; size?: Size }> + Title: FC<{ children: ReactNode }> Footer: FC<{ children: ReactNode }> Actions: FC<{ children: ReactNode }> } -const ListCard: Card = ({ children, className, size, href }) => { +const ListCard: Card = ({ children, className, href }) => { let content, imageProps, info, title, footer, actions Children.forEach(children, (child) => { @@ -130,28 +127,25 @@ const ListCard: Card = ({ children, className, size, href }) => { if (content) { return ( - <Container className={className} to={href!} size={size}> + <Container className={className} to={href!}> {content} </Container> ) } return ( - <Container className={className} to={href!} size={size}> - {imageProps && ( - <Image - size={size} - {...(imageProps as ImgHTMLAttributes<HTMLImageElement>)} - /> - )} + <Container className={className} to={href!}> <Body> - {info && <Info size={size}>{info}</Info>} - <Title size={size}>{title} + {info} + {title} + +
{footer}
+ {actions && {actions}} +
- -
{footer}
- {actions && {actions}} -
+ {imageProps && ( + )} /> + )} ) } diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx index 7d041924db..c29976b974 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx @@ -121,12 +121,17 @@ const LearningResourceCard: React.FC = ({ } return ( - + - {resource.title} + + {resource.title} + {onAddToLearningPathClick && ( diff --git a/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx b/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx index b70851e312..68fdb3c760 100644 --- a/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceListCard/LearningResourceListCard.tsx @@ -38,13 +38,13 @@ const Info = ({ resource }: { resource: LearningResource }) => { const Certificate = styled.div` border-radius: 4px; background-color: ${theme.custom.colors.lightGray1}; - padding: 2px 4px; + padding: 4px; color: ${theme.custom.colors.silverGrayDark}; - ${{ ...theme.typography.subtitle4 }} + ${{ ...theme.typography.subtitle3 }} svg { - width: 12px; - height: 12px; + width: 16px; + height: 16px; } display: flex; @@ -120,13 +120,22 @@ const LearningResourceListCard: React.FC = ({ return null } return ( - - + + - {resource.title} + + {resource.title} + {onAddToLearningPathClick && ( From e99c822edab94f9a9c1336759c6700f7b75a79ec Mon Sep 17 00:00:00 2001 From: Jon Kafton <939376+jonkafton@users.noreply.github.com> Date: Fri, 7 Jun 2024 23:37:41 +0200 Subject: [PATCH 04/25] Learning format and border separator --- .../ResourceCarousel/ResourceCarousel.tsx | 19 +---- .../SearchDisplay/SearchDisplay.tsx | 7 +- .../LearningResourceCard.tsx | 42 ++++++---- .../LearningResourceListCard.tsx | 84 ++++++++++++------- 4 files changed, 82 insertions(+), 70 deletions(-) diff --git a/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx b/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx index b65b45eb90..961304e403 100644 --- a/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx +++ b/frontends/mit-open/src/page-components/ResourceCarousel/ResourceCarousel.tsx @@ -27,15 +27,7 @@ import { AddToLearningPathDialog, AddToUserListDialog, } from "../Dialogs/AddToListDialog" -import { useOpenLearningResourceDrawer } from "../LearningResourceDrawer/LearningResourceDrawer" -const LearningResourceCardStyled = styled(LearningResourceCard)({ - boxShadow: "none", - ":hover": { - boxShadow: - "0 2px 4px 0 rgb(37 38 43 / 10%), 0 2px 4px 0 rgb(37 38 43 / 10%)", - }, -}) const StyledCarousel = styled(Carousel)({ /** * Our cards have a hover shadow that gets clipped by the carousel container. @@ -241,8 +233,6 @@ const ResourceCarousel: React.FC = ({ } : null - const openLRDrawer = useOpenLearningResourceDrawer() - return ( @@ -270,20 +260,15 @@ const ResourceCarousel: React.FC = ({ {isLoading ? Array.from({ length: 6 }).map((_, index) => ( - + )) : resources.map((resource) => ( - openLRDrawer(resource.id)} /> ))} diff --git a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx index 1a435b2777..1df1fba7cd 100644 --- a/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx +++ b/frontends/mit-open/src/page-components/SearchDisplay/SearchDisplay.tsx @@ -5,7 +5,6 @@ import { MuiCard, CardContent, PlainList, - Skeleton, Container, Typography, Button, @@ -203,10 +202,6 @@ const PaginationContainer = styled.div` justify-content: end; ` -const StyledSkeleton = styled(Skeleton)` - border-radius: 4px; -` - const PAGE_SIZE = 10 const MAX_PAGE = 50 @@ -357,7 +352,7 @@ const SearchDisplay: React.FC = ({ .fill(null) .map((a, index) => (
  • - +
  • ))} diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx index c29976b974..1af93503a7 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceCard.tsx @@ -52,36 +52,44 @@ const Certificate = styled.div` gap: 4px; ` -const Footer: React.FC<{ resource: LearningResource; size?: Size }> = ({ - resource, - size, -}) => { - const isOcw = - resource.resource_type === ResourceTypeEnum.Course && - resource.platform?.code === PlatformEnum.Ocw +const isOcw = (resource: LearningResource) => + resource.resource_type === ResourceTypeEnum.Course && + resource.platform?.code === PlatformEnum.Ocw +const getStartDate = (resource: LearningResource) => { let startDate = resource.next_start_date if (!startDate) { const bestRun = findBestRun(resource.runs ?? []) - if (isOcw && bestRun?.semester && bestRun?.year) { - return ( - <> - {size === "medium" ? "As taught in:" : ""}{" "} - {`${bestRun?.semester} ${bestRun?.year}`} - - ) + if (isOcw(resource) && bestRun?.semester && bestRun?.year) { + return `${bestRun?.semester} ${bestRun?.year}` } startDate = bestRun?.start_date } if (!startDate) return null + return formatDate(startDate, "MMMM DD, YYYY") +} + +const StartDate: React.FC<{ resource: LearningResource; size?: Size }> = ({ + resource, + size, +}) => { + const startDate = getStartDate(resource) + + if (!startDate) return null + + const label = isOcw(resource) + ? size === "medium" + ? "As taught in:" + : "" + : "Starts:" + return ( <> - {size === "medium" ? "Starts:" : ""}{" "} - {formatDate(startDate, "MMMM DD, YYYY")} + {label} {formatDate(startDate, "MMMM DD, YYYY")} ) } @@ -160,7 +168,7 @@ const LearningResourceCard: React.FC = ({ )}
    -