From 448f315fadfc21c0629c6417962ca59c2603821b Mon Sep 17 00:00:00 2001 From: Vitor George Date: Thu, 9 Jan 2025 11:50:49 +0000 Subject: [PATCH 1/5] refactor(card): deprecate linkProperties on the Card Component - Updated type definitions to ensure `to` and `onClick` cannot be present simultaneously - Added JSDoc comments for better documentation and clarity - Included a warning for deprecated props `linkTo` and `linkProperties` - Handled non-interactive card case where neither `to` nor `onClick` is provided --- app/scripts/components/common/card/index.tsx | 229 ++++++++++++------- 1 file changed, 142 insertions(+), 87 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index b5d29de64..1eb1619af 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -1,4 +1,4 @@ -import React, { lazy, MouseEventHandler } from 'react'; +import React, { MouseEventHandler } from 'react'; import styled, { css } from 'styled-components'; import format from 'date-fns/format'; import { CollecticonExpandTopRight } from '@devseed-ui/collecticons'; @@ -9,7 +9,6 @@ import { themeVal, listReset } from '@devseed-ui/theme-provider'; -const SmartLink = lazy(() => import('../smart-link')); import { CardBody, @@ -25,6 +24,7 @@ import HorizontalInfoCard, { import { variableBaseType, variableGlsp } from '$styles/variable-utils'; import { ElementInteractive } from '$components/common/element-interactive'; import { Figure } from '$components/common/figure'; +import { useVedaUI } from '$context/veda-ui-provider'; import { LinkProperties } from '$types/veda'; type CardType = 'classic' | 'cover' | 'featured' | 'horizontal-info'; @@ -36,6 +36,48 @@ interface CardItemProps { cardType?: CardType; } +interface BaseCardComponentProps { + title: JSX.Element | string; + linkLabel?: string; + className?: string; + cardType?: CardType; + description?: JSX.Element | string; + date?: Date; + overline?: JSX.Element; + imgSrc?: string; + imgAlt?: string; + parentTo?: string; + tagLabels?: string[]; + footerContent?: JSX.Element; + hideExternalLinkBadge?: boolean; + onCardClickCapture?: MouseEventHandler; +} + +interface LinkCardComponentProps extends BaseCardComponentProps { + to: string; + onClick?: never; +} + +interface ClickCardComponentProps extends BaseCardComponentProps { + to?: never; + onClick: MouseEventHandler; +} + +interface NonInteractiveCardComponentProps extends BaseCardComponentProps { + to?: never; + onClick?: never; +} + +export type CardComponentProps = + | LinkCardComponentProps + | ClickCardComponentProps + | NonInteractiveCardComponentProps; + +export interface DeprecatedCardComponentProps { + linkProperties?: LinkProperties & { linkTo?: string }; + linkTo?: string; +} + /** @NOTE: CardList & CardFooter have been moved over to /common/card/styles and has modified styles These styles are used in GHG instance, so we leave these for now. We should move these styles to GHG instances @@ -233,48 +275,34 @@ export function ExternalLinkFlag() { ); } -export interface LinkWithPathProperties extends LinkProperties { - linkTo: string; -} - -export interface CardComponentBaseProps { - title: JSX.Element | string; - linkLabel?: string; - className?: string; - cardType?: CardType; - description?: JSX.Element | string; - date?: Date; - overline?: JSX.Element; - imgSrc?: string; - imgAlt?: string; - parentTo?: string; - tagLabels?: string[]; - footerContent?: JSX.Element; - hideExternalLinkBadge?: boolean; - onCardClickCapture?: MouseEventHandler; - onClick?: MouseEventHandler; -} - -// @TODO: Created because GHG uses the card component directly and passes in "linkTo" prop. Consolidate these props when the instance adapts the new syntax -// Specifically: https://github.com/US-GHG-Center/veda-config-ghg/blob/develop/custom-pages/news-and-events/component.tsx#L108 -export interface CardComponentPropsDeprecated extends CardComponentBaseProps { - linkTo: string; -} - -export interface CardComponentProps extends CardComponentBaseProps { - linkProperties?: LinkWithPathProperties; -} - -type CardComponentPropsType = CardComponentProps | CardComponentPropsDeprecated; - -// Type guard to check if props has linkProperties -function hasLinkProperties( - props: CardComponentPropsType -): props is CardComponentProps { - return !!(props as CardComponentProps).linkProperties; -} - -function CardComponent(props: CardComponentPropsType) { +/** + * CardComponent + * + * This component renders a card with various styles and content based on the provided props. + * It can behave as a link if the `to` prop is provided, using the `Link` component from the Veda UI provider. + * The `onClick` and `to` props are mutually exclusive. + * + * @param {string | JSX.Element} title - The title of the card. + * @param {string} [linkLabel] - The label for the link. + * @param {string} [className] - Additional class names for the card. + * @param {CardType} [cardType] - The type of the card, which determines its style. + * @param {string | JSX.Element} [description] - The description of the card. + * @param {Date} [date] - The date associated with the card. + * @param {JSX.Element} [overline] - The overline content for the card. + * @param {string} [imgSrc] - The source URL for the card image. + * @param {string} [imgAlt] - The alt text for the card image. + * @param {string} [parentTo] - The URL for the parent link. + * @param {string[]} [tagLabels] - The labels for the tags. + * @param {JSX.Element} [footerContent] - The content for the card footer. + * @param {boolean} [hideExternalLinkBadge] - Whether to hide the external link badge. + * @param {MouseEventHandler} [onCardClickCapture] - The click capture handler for the card. + * @param {MouseEventHandler} [onClick] - The click handler for the card. Mutually exclusive with `to`. + * @param {string} [to] - The URL to link to. If provided, the card behaves as a link. Mutually exclusive with `onClick`. + * @returns {JSX.Element} The rendered CardComponent. + */ +function CardComponent( + props: CardComponentProps & DeprecatedCardComponentProps +) { const { className, title, @@ -292,47 +320,30 @@ function CardComponent(props: CardComponentPropsType) { onCardClickCapture, onClick } = props; - // @TODO: This process is not necessary once all the instances adapt the linkProperties syntax - // Consolidate them to use LinkProperties only - let linkProperties: LinkWithPathProperties | undefined; - - if (hasLinkProperties(props)) { - // Handle new props with linkProperties - const { linkProperties: linkPropertiesProps } = props; - linkProperties = linkPropertiesProps; - } else { - // @NOTE: This currently just exists for GHG which uses the Card component - const { linkTo } = props; - linkProperties = linkTo - ? { - linkTo, - pathAttributeKeyName: 'to', - LinkElement: SmartLink - } - : undefined; + + const { Link } = useVedaUI(); + + // For backwards compatibility with deprecated props + const to = props.to || props.linkTo || props.linkProperties?.linkTo; + + if (props.linkProperties || props.linkTo) { + // eslint-disable-next-line no-console + console.warn( + 'linkProperties and linkTo are deprecated in Card component. Please use the "to" prop instead.' + ); + + if (onClick) { + // eslint-disable-next-line no-console + console.warn( + 'onClick and linkProperties/linkTo are mutually exclusive. Please use only one of them.' + ); + } } - const isExternalLink = linkProperties - ? /^https?:\/\//.test(linkProperties.linkTo) - : false; + const isExternalLink = to ? /^https?:\/\//.test(to) : false; - return ( - + const CardContent = ( + <> {cardType !== 'horizontal-info' && ( <> @@ -346,11 +357,7 @@ function CardComponent(props: CardComponentPropsType) { tagLabels && parentTo && tagLabels.map((label) => ( - + {label} ))} @@ -390,6 +397,54 @@ function CardComponent(props: CardComponentPropsType) { tagLabels={tagLabels} /> )} + + ); + + // Link Card + if (to) { + return ( + + {CardContent} + + ); + } + + // Click Card + if (onClick) { + return ( + + {CardContent} + + ); + } + + // Non-interactive Card + return ( + + {CardContent} ); } From aa8045516186fd8b910ab2439ade934728e2ffc2 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 13 Jan 2025 12:35:56 +0000 Subject: [PATCH 2/5] Render on external links instead of Link component --- app/scripts/components/common/card/index.tsx | 3 ++- app/scripts/context/veda-ui-provider.tsx | 10 ++++++++++ app/scripts/utils/utils.ts | 12 ++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index 1eb1619af..f848c2158 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -21,6 +21,7 @@ import { import HorizontalInfoCard, { HorizontalCardStyles } from './horizontal-info-card'; +import * as utils from '$utils/utils'; import { variableBaseType, variableGlsp } from '$styles/variable-utils'; import { ElementInteractive } from '$components/common/element-interactive'; import { Figure } from '$components/common/figure'; @@ -340,7 +341,7 @@ function CardComponent( } } - const isExternalLink = to ? /^https?:\/\//.test(to) : false; + const isExternalLink = to ? utils.isExternalLink(to) : false; const CardContent = ( <> diff --git a/app/scripts/context/veda-ui-provider.tsx b/app/scripts/context/veda-ui-provider.tsx index c978d221b..edf4b7772 100644 --- a/app/scripts/context/veda-ui-provider.tsx +++ b/app/scripts/context/veda-ui-provider.tsx @@ -1,5 +1,6 @@ import React, { createContext, ReactNode, useContext } from 'react'; import { DATASETS_PATH, STORIES_PATH } from '$utils/routes'; +import { isExternalLink } from '$utils/url'; interface EnvironmentConfig { envMapboxToken: string; @@ -91,6 +92,15 @@ export function VedaUIProvider({ config, children }: VedaUIProviderProps) { const Link: React.FC = ({ to, children, ...props }) => { const { LinkComponent, linkProps } = navigation; + + if (isExternalLink(to)) { + return ( + + {children} + + ); + } + return ( { + return /^https?:\/\//.test(url); +}; From 58a26cae963323b164c236f727f5bba9e8834121 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Mon, 13 Jan 2025 12:45:49 +0000 Subject: [PATCH 3/5] Refactor card variants --- app/scripts/components/common/card/index.tsx | 52 +++++++------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index f848c2158..c0889d1f1 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -309,7 +309,7 @@ function CardComponent( title, cardType, description, - linkLabel, + linkLabel = 'View more', date, overline, imgSrc, @@ -401,53 +401,35 @@ function CardComponent( ); - // Link Card + const baseProps = { + as: CardItem, + cardType, + className, + linkLabel, + onCardClickCapture, + children: CardContent + }; + + // Link variant if (to) { return ( - {CardContent} - + /> ); } - // Click Card + // Clickable variant if (onClick) { - return ( - - {CardContent} - - ); + return ; } - // Non-interactive Card - return ( - - {CardContent} - - ); + // Non-interactive variant + return ; } export const Card = styled(CardComponent)` From 3c14e7e641dcb42150d374c8e50b5f7fac4cb9a4 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 14 Jan 2025 09:22:42 +0000 Subject: [PATCH 4/5] Open external links in a new tab --- app/scripts/context/veda-ui-provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/context/veda-ui-provider.tsx b/app/scripts/context/veda-ui-provider.tsx index edf4b7772..2bd9dd6df 100644 --- a/app/scripts/context/veda-ui-provider.tsx +++ b/app/scripts/context/veda-ui-provider.tsx @@ -95,7 +95,7 @@ export function VedaUIProvider({ config, children }: VedaUIProviderProps) { if (isExternalLink(to)) { return ( - + {children} ); From f1a065d1dd18e91534e489a17e475e49becfd154 Mon Sep 17 00:00:00 2001 From: Vitor George Date: Tue, 14 Jan 2025 10:02:54 +0000 Subject: [PATCH 5/5] Remove NonInteractiveCardComponentProps and use BaseCardComponentProps --- app/scripts/components/common/card/index.tsx | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/app/scripts/components/common/card/index.tsx b/app/scripts/components/common/card/index.tsx index c0889d1f1..f84c54ecd 100644 --- a/app/scripts/components/common/card/index.tsx +++ b/app/scripts/components/common/card/index.tsx @@ -64,15 +64,10 @@ interface ClickCardComponentProps extends BaseCardComponentProps { onClick: MouseEventHandler; } -interface NonInteractiveCardComponentProps extends BaseCardComponentProps { - to?: never; - onClick?: never; -} - export type CardComponentProps = | LinkCardComponentProps | ClickCardComponentProps - | NonInteractiveCardComponentProps; + | BaseCardComponentProps; export interface DeprecatedCardComponentProps { linkProperties?: LinkProperties & { linkTo?: string }; @@ -318,14 +313,14 @@ function CardComponent( parentTo, footerContent, hideExternalLinkBadge, - onCardClickCapture, - onClick + onCardClickCapture } = props; const { Link } = useVedaUI(); // For backwards compatibility with deprecated props - const to = props.to || props.linkTo || props.linkProperties?.linkTo; + const to = + ('to' in props && props.to) || props.linkTo || props.linkProperties?.linkTo; if (props.linkProperties || props.linkTo) { // eslint-disable-next-line no-console @@ -333,7 +328,7 @@ function CardComponent( 'linkProperties and linkTo are deprecated in Card component. Please use the "to" prop instead.' ); - if (onClick) { + if ('onClick' in props && props.onClick) { // eslint-disable-next-line no-console console.warn( 'onClick and linkProperties/linkTo are mutually exclusive. Please use only one of them.' @@ -424,8 +419,8 @@ function CardComponent( } // Clickable variant - if (onClick) { - return ; + if ('onClick' in props && props.onClick) { + return ; } // Non-interactive variant