diff --git a/frontends/mit-open/src/page-components/ResourceCard/ResourceCard.tsx b/frontends/mit-open/src/page-components/ResourceCard/ResourceCard.tsx index ce14cb591f..c439cefbf3 100644 --- a/frontends/mit-open/src/page-components/ResourceCard/ResourceCard.tsx +++ b/frontends/mit-open/src/page-components/ResourceCard/ResourceCard.tsx @@ -1,5 +1,9 @@ import React, { useCallback, useMemo, useState } from "react" -import { LearningResourceCard, LearningResourceListCard } from "ol-components" +import { + LearningResourceCard, + LearningResourceListCard, + LearningResourceListCardCondensed, +} from "ol-components" import * as NiceModal from "@ebay/nice-modal-react" import type { LearningResourceCardProps, @@ -104,7 +108,9 @@ const ResourceCard: React.FC = ({ resource, ...others }) => { type ResourceListCardProps = Omit< LearningResourceListCardProps, "href" | "onAddToLearningPathClick" | "onAddToUserListClick" -> +> & { + condensed?: boolean +} /** * Just like `ol-components/LearningResourceListCard`, but with builtin actions: @@ -115,7 +121,8 @@ type ResourceListCardProps = Omit< */ const ResourceListCard: React.FC = ({ resource, - ...others + condensed, + ...props }) => { const { getDrawerHref, @@ -126,16 +133,20 @@ const ResourceListCard: React.FC = ({ inUserList, inLearningPath, } = useResourceCard(resource) + + const ListCardComponent = condensed + ? LearningResourceListCardCondensed + : LearningResourceListCard return ( <> - diff --git a/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.test.tsx b/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.test.tsx index f4713b2ba7..d57fe4c3a5 100644 --- a/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.test.tsx +++ b/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.test.tsx @@ -30,7 +30,8 @@ jest.mock("ol-components", () => { } }) -const spyLearningResourceCard = jest.mocked(LearningResourceCard) +const spyLearningResourceCardOld = jest.mocked(LearningResourceCard) + const spySortableList = jest.mocked(SortableList) const spySortableItem = jest.mocked(SortableItem) @@ -57,105 +58,107 @@ const getPaginatedRelationships = ( } } -describe.each([ListType.LearningPath, ListType.UserList])( - "ItemsListing", - (listType: string) => { - test("Shows loading message while loading", () => { - const emptyMessage = "Empty list" +describe("ItemsListing", () => { + test.each([ + { listType: ListType.LearningPath }, + { listType: ListType.UserList }, + ])("Shows loading message while loading", ({ listType }) => { + const emptyMessage = "Empty list" + + const { view } = renderWithProviders( + , + ) + screen.getByLabelText("Loading") + view.rerender( + , + ) + }) - const { view } = renderWithProviders( + test.each([ + { listType: ListType.LearningPath, count: 0, hasEmptyMessage: true }, + { + listType: ListType.LearningPath, + count: faker.number.int({ min: 1, max: 5 }), + hasEmptyMessage: false, + }, + { listType: ListType.LearningPath, count: 0, hasEmptyMessage: true }, + { + listType: ListType.UserList, + count: faker.number.int({ min: 1, max: 5 }), + hasEmptyMessage: false, + }, + ])( + "Shows empty message when there are no items", + ({ listType, count, hasEmptyMessage }) => { + setMockResponse.get(urls.userMe.get(), {}) + const emptyMessage = faker.lorem.sentence() + const paginatedRelationships = getPaginatedRelationships( + listType, + count, + faker.number.int(), + ) + renderWithProviders( , ) - screen.getByLabelText("Loading") - view.rerender( - , - ) - }) + const emptyMessageElement = screen.queryByText(emptyMessage) + expect(!!emptyMessageElement).toBe(hasEmptyMessage) + }, + ) - test.each([ - { listType: ListType.LearningPath, count: 0, hasEmptyMessage: true }, - { - listType: ListType.LearningPath, - count: faker.number.int({ min: 1, max: 5 }), - hasEmptyMessage: false, - }, - { listType: ListType.LearningPath, count: 0, hasEmptyMessage: true }, - { - listType: ListType.UserList, - count: faker.number.int({ min: 1, max: 5 }), - hasEmptyMessage: false, - }, - ])( - "Shows empty message when there are no items", - ({ listType, count, hasEmptyMessage }) => { - setMockResponse.get(urls.userMe.get(), {}) - const emptyMessage = faker.lorem.sentence() - const paginatedRelationships = getPaginatedRelationships( - listType, - count, - faker.number.int(), - ) - renderWithProviders( - , - ) - const emptyMessageElement = screen.queryByText(emptyMessage) - expect(!!emptyMessageElement).toBe(hasEmptyMessage) - }, - ) + test.each([ + { listType: ListType.LearningPath, sortable: false, cardProps: {} }, + { + listType: ListType.LearningPath, + sortable: true, + cardProps: { sortable: true }, + }, + { listType: ListType.UserList, sortable: false, cardProps: {} }, + { + listType: ListType.UserList, + sortable: true, + cardProps: { sortable: true }, + }, + ])( + "Shows a list of $listType LearningResourceCards with sortable=$sortable", + ({ listType, sortable, cardProps }) => { + const emptyMessage = faker.lorem.sentence() + const paginatedRelationships = getPaginatedRelationships( + listType, + faker.number.int({ min: 2, max: 4 }), + faker.number.int(), + ) + const items = paginatedRelationships.results as LearningResourceListItem[] + renderWithProviders( + , + { user: {} }, + ) + const titles = items.map((item) => item.resource.title) + const headings = screen.getAllByRole("heading", { + name: (value) => titles.includes(value), + }) + expect(headings.map((h) => h.textContent)).toEqual(titles) - test.each([ - { listType: ListType.LearningPath, sortable: false, cardProps: {} }, - { - listType: ListType.LearningPath, - sortable: true, - cardProps: { sortable: true }, - }, - { listType: ListType.UserList, sortable: false, cardProps: {} }, - { - listType: ListType.UserList, - sortable: true, - cardProps: { sortable: true }, - }, - ])( - "Shows a list of LearningResourceCards with sortable=$sortable", - ({ listType, sortable, cardProps }) => { - const emptyMessage = faker.lorem.sentence() - const paginatedRelationships = getPaginatedRelationships( - listType, - faker.number.int({ min: 2, max: 4 }), - faker.number.int(), - ) - const items = - paginatedRelationships.results as LearningResourceListItem[] - renderWithProviders( - , - { user: {} }, - ) - const titles = items.map((item) => item.resource.title) - const headings = screen.getAllByRole("heading", { - name: (value) => titles.includes(value), - }) - expect(headings.map((h) => h.textContent)).toEqual(titles) + if (sortable) { items.forEach(({ resource }) => { - expectProps(spyLearningResourceCard, { resource, ...cardProps }) + expectProps(spyLearningResourceCardOld, { resource, ...cardProps }) }) - }, - ) - }, -) + } + }, + ) +}) describe.each([ListType.LearningPath, ListType.UserList])( "Sorting ItemListing", diff --git a/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.tsx b/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.tsx index 48adf81d7a..b01d0b96ed 100644 --- a/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.tsx +++ b/frontends/mit-open/src/pages/ListDetailsPage/ItemsListing.tsx @@ -11,6 +11,7 @@ import { styled, PlainList, } from "ol-components" +import { ResourceListCard } from "@/page-components/ResourceCard/ResourceCard" import { useListItemMove } from "api/hooks/learningResources" const EmptyMessage = styled.p({ @@ -38,14 +39,11 @@ const ItemsListingViewOnly: React.FC<{ items: NonNullable }> = ({ items }) => { return ( - + {items.map((item) => { return (
  • - +
  • ) })} diff --git a/frontends/mit-open/src/pages/UserListListingPage/UserListListingPage.tsx b/frontends/mit-open/src/pages/UserListListingPage/UserListListingPage.tsx index ad101d9aca..1b5e85ddc9 100644 --- a/frontends/mit-open/src/pages/UserListListingPage/UserListListingPage.tsx +++ b/frontends/mit-open/src/pages/UserListListingPage/UserListListingPage.tsx @@ -73,24 +73,6 @@ const EditUserListMenu: React.FC = ({ userList }) => { ) } -type ListCardProps = { - list: UserList - onActivate: (userList: UserList) => void - canEdit: boolean -} -const ListCard: React.FC = ({ list, onActivate }) => { - return ( - } - /> - ) -} - type UserListListingComponentProps = { title?: string onActivate: (userList: UserList) => void @@ -132,10 +114,13 @@ const UserListListingComponent: React.FC = ( {listingQuery.data.results?.map((list) => { return (
  • - } />
  • ) diff --git a/frontends/ol-components/src/components/Card/ListCard.tsx b/frontends/ol-components/src/components/Card/ListCard.tsx index 4650a944fb..40ff2a3bdc 100644 --- a/frontends/ol-components/src/components/Card/ListCard.tsx +++ b/frontends/ol-components/src/components/Card/ListCard.tsx @@ -12,7 +12,7 @@ import { Wrapper, containerStyles } from "./Card" import { TruncateText } from "../TruncateText/TruncateText" import { ActionButton, ActionButtonProps } from "../Button/Button" -const LinkContainer = styled(Link)` +export const LinkContainer = styled(Link)` ${containerStyles} display: flex; @@ -26,13 +26,13 @@ const LinkContainer = styled(Link)` } ` -const Container = styled.div` +export const Container = styled.div` ${containerStyles} ` const Content = () => <> -const Body = styled.div` +export const Body = styled.div` flex-grow: 1; overflow: hidden; margin: 24px; @@ -63,7 +63,7 @@ const Image = styled.img` flex-shrink: 0; ` -const Info = styled.div` +export const Info = styled.div` ${{ ...theme.typography.subtitle3 }} margin-bottom: 16px; ${theme.breakpoints.down("md")} { @@ -77,35 +77,28 @@ const Info = styled.div` align-items: center; ` -const Title = styled.h3` +export const Title = styled.h3` flex-grow: 1; color: ${theme.custom.colors.darkGray2}; text-overflow: ellipsis; ${{ ...theme.typography.subtitle1 }} height: ${theme.typography.pxToRem(40)}; ${theme.breakpoints.down("md")} { - ${{ ...theme.typography.subtitle3 }} + ${{ ...theme.typography.subtitle2 }} height: ${theme.typography.pxToRem(32)}; } margin: 0; ` -const Footer = styled.span` +export const Footer = styled.span` display: block; - ${{ - ...theme.typography.body3, - color: theme.custom.colors.silverGrayDark, - }} - - span { - color: ${theme.custom.colors.black}; - } - + ${{ ...theme.typography.body3 }} + color: ${theme.custom.colors.darkGray2}; white-space: nowrap; ` -const Bottom = styled.div` +export const Bottom = styled.div` display: flex; justify-content: space-between; align-items: flex-end; @@ -118,7 +111,7 @@ const Bottom = styled.div` /** * Slot intended to contain ListCardAction buttons. */ -const Actions = styled.div<{ hasImage?: boolean }>` +export const Actions = styled.div<{ hasImage?: boolean }>` display: flex; gap: 8px; position: absolute; @@ -152,7 +145,7 @@ type CardProps = { className?: string href?: string } -type Card = FC & { +export type Card = FC & { Content: FC<{ children: ReactNode }> Image: FC> Info: FC<{ children: ReactNode }> diff --git a/frontends/ol-components/src/components/Card/ListCardCondensed.tsx b/frontends/ol-components/src/components/Card/ListCardCondensed.tsx new file mode 100644 index 0000000000..219fb64e0e --- /dev/null +++ b/frontends/ol-components/src/components/Card/ListCardCondensed.tsx @@ -0,0 +1,113 @@ +import React, { FC, ReactNode, Children, isValidElement } from "react" +import styled from "@emotion/styled" +import { theme } from "../ThemeProvider/ThemeProvider" +import { Wrapper } from "./Card" +import { TruncateText } from "../TruncateText/TruncateText" +import { + ListCard, + Body as BaseBody, + LinkContainer, + Container, + Info as BaseInfo, + Title as BaseTitle, + Footer, + Actions as BaseActions, + Bottom as BaseBottom, +} from "./ListCard" +import type { Card as BaseCard } from "./ListCard" + +const Body = styled(BaseBody)` + margin: 16px; + ${theme.breakpoints.down("md")} { + margin: 16px; + } +` + +const Info = styled(BaseInfo)` + margin-bottom: 4px; +` + +const Title = styled(BaseTitle)` + height: auto; + margin-bottom: 8px; + margin-right: 82px; + ${theme.breakpoints.down("md")} { + height: auto; + ${{ ...theme.typography.subtitle2 }} + } +` + +const Bottom = styled(BaseBottom)` + height: auto; + ${theme.breakpoints.down("md")} { + height: auto; + } +` +export const Actions = styled(BaseActions)` + bottom: 16px; + right: 16px; + gap: 16px; + ${theme.breakpoints.down("md")} { + bottom: 16px; + right: 16px; + gap: 16px; + } +` +const Content = () => <> + +type CardProps = { + children: ReactNode[] | ReactNode + className?: string + href?: string +} + +type Card = FC & Omit + +const ListCardCondensed: Card = ({ children, className, href }) => { + const _Container = href ? LinkContainer : Container + + let content, info, title, footer, actions + + Children.forEach(children, (child) => { + if (!isValidElement(child)) return + if (child.type === Content) content = child.props.children + 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!}> + {content} + + ) + } + + return ( + + <_Container to={href!}> + + {info} + + <TruncateText lineClamp={4}>{title}</TruncateText> + + +
    {footer}
    +
    + + + {actions && {actions}} +
    + ) +} + +ListCardCondensed.Content = Content +ListCardCondensed.Info = Info +ListCardCondensed.Title = Title +ListCardCondensed.Footer = Footer +ListCardCondensed.Actions = Actions +ListCardCondensed.Action = ListCard.Action + +export { ListCardCondensed } diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx index c16a0a09ea..4bce3c552a 100644 --- a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCard.tsx @@ -26,13 +26,14 @@ const IMAGE_SIZES = { desktop: { width: 236, height: 122 }, } -const CardLabel = styled.span` +export const CardLabel = styled.span` + color: ${theme.custom.colors.silverGrayDark}; ${theme.breakpoints.down("sm")} { display: none; } ` -const Certificate = styled.div` +export const Certificate = styled.div` border-radius: 4px; background-color: ${theme.custom.colors.lightGray1}; padding: 4px; @@ -65,7 +66,7 @@ const Certificate = styled.div` align-items: center; ` -const Price = styled.div` +export const Price = styled.div` ${{ ...theme.typography.subtitle2 }} color: ${theme.custom.colors.darkGray2}; ${theme.breakpoints.down("md")} { @@ -73,7 +74,7 @@ const Price = styled.div` } ` -const BorderSeparator = styled.div` +export const BorderSeparator = styled.div` div { display: inline; } @@ -102,7 +103,7 @@ type Prices = { certificate: null | number } -const getPrices = (resource: LearningResource) => { +export const getPrices = (resource: LearningResource) => { const prices: Prices = { course: null, certificate: null, @@ -198,7 +199,7 @@ const getDisplayPrecision = (price: number) => { return price.toFixed(2) } -const getDisplayPrice = (price: number | number[] | null) => { +export const getDisplayPrice = (price: number | number[] | null) => { if (price === null) { return null } @@ -221,7 +222,6 @@ const getDisplayPrice = (price: number | number[] | null) => { */ const Info = ({ resource }: { resource: LearningResource }) => { const prices = getPrices(resource) - getDisplayPrice(+Infinity) return ( <> {getReadableResourceType(resource.resource_type)} @@ -237,7 +237,7 @@ const Info = ({ resource }: { resource: LearningResource }) => { ) } -const Count = ({ resource }: { resource: LearningResource }) => { +export const Count = ({ resource }: { resource: LearningResource }) => { if (resource.resource_type !== ResourceTypeEnum.LearningPath) { return null } @@ -270,7 +270,9 @@ const getStartDate = (resource: LearningResource) => { return formatDate(startDate, "MMMM DD, YYYY") } -const StartDate: React.FC<{ resource: LearningResource }> = ({ resource }) => { +export const StartDate: React.FC<{ resource: LearningResource }> = ({ + resource, +}) => { const startDate = getStartDate(resource) if (!startDate) return null @@ -284,7 +286,7 @@ const StartDate: React.FC<{ resource: LearningResource }> = ({ resource }) => { ) } -const Format = ({ resource }: { resource: LearningResource }) => { +export const Format = ({ resource }: { resource: LearningResource }) => { const format = resource.learning_format?.[0]?.name if (!format) return null return ( @@ -356,18 +358,26 @@ interface LearningResourceListCardProps { inLearningPath?: boolean } -const FILLED_PROPS = { variant: "primary" } as const -const UNFILLED_PROPS = { color: "secondary", variant: "secondary" } as const -const CardActionButton: React.FC< - Pick & { - filled?: boolean - isMobile?: boolean - } -> = ({ filled, isMobile, ...props }) => { +type CardActionButtonProps = Pick< + ActionButtonProps, + "aria-label" | "onClick" | "children" +> & { + filled?: boolean + isMobile?: boolean +} + +export const CardActionButton: React.FC = ({ + filled, + isMobile, + ...props +}) => { + const FILLED_PROPS = { variant: "primary" } as const + const UNFILLED_PROPS = { color: "secondary", variant: "secondary" } as const + return ( @@ -435,10 +445,12 @@ const LearningResourceListCard: React.FC = ({ {editMenu} + export{" "} + export{" "} diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCardCondensed.stories.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCardCondensed.stories.tsx new file mode 100644 index 0000000000..4ec396c2d6 --- /dev/null +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCardCondensed.stories.tsx @@ -0,0 +1,153 @@ +import React from "react" +import type { Meta, StoryObj } from "@storybook/react" +import { LearningResourceListCardCondensed } from "./LearningResourceListCardCondensed" +import { ResourceTypeEnum } from "api" +import { factories } from "api/test-utils" +import { withRouter } from "storybook-addon-react-router-v6" + +const _makeResource = factories.learningResources.resource + +const makeResource: typeof _makeResource = (overrides) => { + const resource = _makeResource(overrides) + resource.image!.url = + "https://ocw.mit.edu/courses/res-hso-001-mit-haystack-observatory-k12-stem-lesson-plans/mitres_hso_001.jpg" + return resource +} + +const meta: Meta = { + title: "smoot-design/Cards/LearningResourceListCardCondensed", + argTypes: { + resource: { + options: ["Loading", ...Object.values(ResourceTypeEnum)], + mapping: { + Loading: undefined, + [ResourceTypeEnum.Course]: makeResource({ + resource_type: ResourceTypeEnum.Course, + }), + [ResourceTypeEnum.Program]: makeResource({ + resource_type: ResourceTypeEnum.Program, + }), + [ResourceTypeEnum.Video]: makeResource({ + resource_type: ResourceTypeEnum.Video, + url: "https://www.youtube.com/watch?v=-E9hf5RShzQ", + }), + [ResourceTypeEnum.VideoPlaylist]: makeResource({ + resource_type: ResourceTypeEnum.VideoPlaylist, + }), + [ResourceTypeEnum.Podcast]: makeResource({ + resource_type: ResourceTypeEnum.Podcast, + }), + [ResourceTypeEnum.PodcastEpisode]: makeResource({ + resource_type: ResourceTypeEnum.PodcastEpisode, + }), + [ResourceTypeEnum.LearningPath]: makeResource({ + resource_type: ResourceTypeEnum.LearningPath, + }), + }, + }, + onAddToLearningPathClick: { + action: "click-add-to-learning-path", + }, + onAddToUserListClick: { + action: "click-add-to-user-list", + }, + }, + render: ({ + resource, + isLoading, + onAddToLearningPathClick, + onAddToUserListClick, + }) => ( + + ), + decorators: [withRouter], +} + +export default meta + +type Story = StoryObj + +export const PaidCourse: Story = { + args: { + resource: makeResource({ + resource_type: ResourceTypeEnum.Course, + runs: [factories.learningResources.run()], + free: false, + certification: true, + prices: ["999"], + }), + }, +} + +export const FreeCourse: Story = { + args: { + resource: makeResource({ + resource_type: ResourceTypeEnum.Course, + runs: [factories.learningResources.run()], + free: true, + certification: true, + prices: ["0", "400"], + }), + }, +} + +export const LearningPath: Story = { + args: { + resource: makeResource({ resource_type: ResourceTypeEnum.LearningPath }), + }, +} + +export const Program: Story = { + args: { + resource: makeResource({ resource_type: ResourceTypeEnum.Program }), + }, +} + +export const Podcast: Story = { + args: { + resource: makeResource({ + resource_type: ResourceTypeEnum.Podcast, + free: true, + }), + }, +} + +export const PodcastEpisode: Story = { + args: { + resource: makeResource({ + resource_type: ResourceTypeEnum.PodcastEpisode, + free: true, + }), + }, +} + +export const Video: Story = { + args: { + resource: makeResource({ + resource_type: ResourceTypeEnum.Video, + url: "https://www.youtube.com/watch?v=4A9bGL-_ilA", + free: true, + }), + }, +} + +export const VideoPlaylist: Story = { + args: { + resource: makeResource({ + resource_type: ResourceTypeEnum.VideoPlaylist, + free: true, + }), + }, +} + +export const Loading: Story = { + args: { + isLoading: true, + }, +} diff --git a/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCardCondensed.tsx b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCardCondensed.tsx new file mode 100644 index 0000000000..efb37027cd --- /dev/null +++ b/frontends/ol-components/src/components/LearningResourceCard/LearningResourceListCardCondensed.tsx @@ -0,0 +1,157 @@ +import React from "react" +import styled from "@emotion/styled" +import Skeleton from "@mui/material/Skeleton" +import { + RiMenuAddLine, + RiBookmarkLine, + RiAwardFill, + RiBookmarkFill, +} from "@remixicon/react" +import { LearningResource } from "api" +import { getReadableResourceType } from "ol-utilities" +import { ListCardCondensed } from "../Card/ListCardCondensed" +import { useMuiBreakpointAtLeast } from "../../hooks/useBreakpoint" +import { + Certificate, + Price, + BorderSeparator, + getPrices, + getDisplayPrice, + Count, + StartDate, + Format, +} from "./LearningResourceListCard" +import type { LearningResourceListCardProps } from "./LearningResourceListCard" +import { ActionButton, ActionButtonProps } from "../Button/Button" + +const ResourceType = styled.span` + align-self: flex-start; +` + +/* The only variation on the LearningResourceListCard + * Info is the ResourceType flex alignment + */ +const Info = ({ resource }: { resource: LearningResource }) => { + const prices = getPrices(resource) + return ( + <> + + {getReadableResourceType(resource.resource_type)} + + {resource.certification && ( + + + Certificate{prices?.certificate ? ":" : ""}{" "} + {getDisplayPrice(prices?.certificate)} + + )} + {getDisplayPrice(prices?.course)} + + ) +} + +const Loading = styled.div<{ mobile?: boolean }>` + padding: 16px; +` + +const LoadingView = ({ isMobile }: { isMobile: boolean }) => { + return ( + + + + + + ) +} + +type CardActionButtonProps = Pick< + ActionButtonProps, + "aria-label" | "onClick" | "children" +> & { + filled?: boolean +} + +export const CardActionButton: React.FC = ({ + filled, + ...props +}) => { + const FILLED_PROPS = { variant: "primary" } as const + const UNFILLED_PROPS = { color: "secondary", variant: "secondary" } as const + + return ( + + ) +} + +const LearningResourceListCardCondensed: React.FC< + LearningResourceListCardProps +> = ({ + isLoading, + resource, + className, + href, + onAddToLearningPathClick, + onAddToUserListClick, + editMenu, + inLearningPath, + inUserList, +}) => { + const isMobile = !useMuiBreakpointAtLeast("md") + + if (isLoading) { + return ( + + + + + + ) + } + if (!resource) { + return null + } + return ( + + + + + {resource.title} + + {onAddToLearningPathClick && ( + onAddToLearningPathClick(event, resource.id)} + > + + + )} + {onAddToUserListClick && ( + onAddToUserListClick(event, resource.id)} + > + {inUserList ? : } + + )} + {editMenu} + + + + + + + + + + ) +} + +export { LearningResourceListCardCondensed } +export type { LearningResourceListCardProps as LearningResourceListCardCondensedProps } diff --git a/frontends/ol-components/src/index.ts b/frontends/ol-components/src/index.ts index 9913ff4a29..fb0ffb53af 100644 --- a/frontends/ol-components/src/index.ts +++ b/frontends/ol-components/src/index.ts @@ -28,7 +28,7 @@ export { ActionButtonLink, ButtonLink, } from "./components/Button/Button" -export { ListCardActionButton } from "./components/Card/ListCard" +export { ListCard, ListCardActionButton } from "./components/Card/ListCard" export type { ButtonProps, @@ -167,7 +167,9 @@ export * from "./components/Chips/ChipLink" export * from "./components/EmbedlyCard/EmbedlyCard" export * from "./components/FormDialog/FormDialog" export * from "./components/LearningResourceCard/LearningResourceCard" -export * from "./components/LearningResourceCard/LearningResourceListCard" +export { LearningResourceListCard } from "./components/LearningResourceCard/LearningResourceListCard" +export type { LearningResourceListCardProps } from "./components/LearningResourceCard/LearningResourceListCard" +export * from "./components/LearningResourceCard/LearningResourceListCardCondensed" export * from "./components/LearningResourceExpanded/LearningResourceExpanded" export * from "./components/LoadingSpinner/LoadingSpinner" export * from "./components/Logo/Logo"