Skip to content

Commit

Permalink
fix: polymorphic types for Typography and TimeAgo (#3576)
Browse files Browse the repository at this point in the history
* fix(Typography): polymorphic component
* fix(TimeAgo): polymorphic component
* test: typography updated
  • Loading branch information
MEsteves22 committed Aug 8, 2023
1 parent 40d68f3 commit 16fe93a
Show file tree
Hide file tree
Showing 6 changed files with 267 additions and 147 deletions.
118 changes: 78 additions & 40 deletions packages/core/src/components/AppSwitcher/Action/Action.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from "react";
import { useCallback, useState } from "react";

import { theme } from "@hitachivantara/uikit-styles";
import { Info } from "@hitachivantara/uikit-react-icons";
Expand Down Expand Up @@ -112,18 +112,63 @@ export const HvAppSwitcherAction = ({
/**
* Handles the onClick event and triggers the appropriate callback if it exists.
*/
const handleOnClick = (event: React.MouseEvent) => {
if (disabled) {
event.preventDefault();
return;
}

onClickCallback?.(event, { ...application, isSelected });
};
const handleOnClick = useCallback(
(event: React.MouseEvent) => {
if (disabled) {
event.preventDefault();
return;
}

onClickCallback?.(event, { ...application, isSelected });
},
[application, disabled, isSelected, onClickCallback]
);

const isLink = url != null;
const descriptionElementId = useUniqueId(id, "hvAction-description");

const renderApplication = useCallback(
(children: React.ReactNode) => {
const typographyProps = {
className: classes.typography,
onClick: handleOnClick,
style: { borderColor: color },
"aria-label": name,
...(description && { "aria-describedby": descriptionElementId }),
};

if (isLink) {
return (
<HvTypography
component="a"
href={url}
target={target || "_top"}
{...typographyProps}
>
{children}
</HvTypography>
);
}

return (
<HvTypography component="button" {...typographyProps}>
{children}
</HvTypography>
);
},
[
classes.typography,
color,
description,
descriptionElementId,
handleOnClick,
isLink,
name,
target,
url,
]
);

return (
<HvListItem
id={id}
Expand All @@ -139,37 +184,30 @@ export const HvAppSwitcherAction = ({
>
{/* As HvTooltip don't have the id prop, is not possible to use the aria-labelledby to reference it.
In substitution is used the aria-label with the "title" value */}
<HvTypography
component={isLink ? "a" : "button"}
href={isLink ? url : undefined}
target={isLink ? target || "_top" : undefined}
className={classes.typography}
onClick={handleOnClick}
style={{ borderColor: color }}
aria-label={name}
{...(description && { "aria-describedby": descriptionElementId })}
>
<div className={classes.icon}>{renderApplicationIcon()}</div>

<TitleWithTooltip title={name} className={classes.title} />

{description && (
<HvTooltip
disableFocusListener
disableTouchListener
title={<HvTypography>{description}</HvTypography>}
>
<div>
<Info
className={classes.iconInfo}
role="img"
aria-label={description}
id={descriptionElementId}
/>
</div>
</HvTooltip>
)}
</HvTypography>
{renderApplication(
<>
<div className={classes.icon}>{renderApplicationIcon()}</div>

<TitleWithTooltip title={name} className={classes.title} />

{description && (
<HvTooltip
disableFocusListener
disableTouchListener
title={<HvTypography>{description}</HvTypography>}
>
<div>
<Info
className={classes.iconInfo}
role="img"
aria-label={description}
id={descriptionElementId}
/>
</div>
</HvTooltip>
)}
</>
)}
</HvListItem>
);
};
6 changes: 5 additions & 1 deletion packages/core/src/components/Button/Button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -293,7 +293,11 @@ Semantic.args = {
onClick: clickAction,
};

const CustomLink = ({ to, children, ...others }) => (
interface CustomLinkProps extends HvButtonProps<"a"> {
to: string;
}

const CustomLink = ({ to, children, ...others }: CustomLinkProps) => (
<a href={to} {...others}>
{children}
</a>
Expand Down
138 changes: 71 additions & 67 deletions packages/core/src/components/TimeAgo/TimeAgo.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { forwardRef } from "react";

import isEmpty from "lodash/isEmpty";
import { useDefaultProps } from "@core/hooks/useDefaultProps";

import { useDefaultProps } from "@core/hooks/useDefaultProps";
import { ExtractNames } from "@core/utils/classes";
import { HvTypography } from "@core/components/Typography";
import { HvBaseProps } from "@core/types/generic";
import { PolymorphicComponentRef, PolymorphicRef } from "@core/types/generic";

import { staticClasses, useClasses } from "./TimeAgo.styles";
import useTimeAgo from "./useTimeAgo";
Expand All @@ -12,75 +14,77 @@ export { staticClasses as timeAgoClasses };

export type HvTimeAgoClasses = ExtractNames<typeof useClasses>;

export interface HvTimeAgoProps extends HvBaseProps<HTMLElement, "children"> {
/**
* The timestamp to format, in seconds or milliseconds.
* Defaults to `emptyElement` if value is null or 0
*/
timestamp?: number;
/**
* The locale to be used. Should be on of the dayjs supported locales and explicitly imported
* @see https://day.js.org/docs/en/i18n/i18n
*/
locale?: string;
/**
* The component used for the root node. Either a string to use a HTML element or a component.
* Defaults to `HvTypography`.
*/
component?: React.ElementType<React.HTMLAttributes<HTMLElement>>;
/**
* The element to render when the timestamp is null or 0
* Defaults to `—` (Em Dash)
*/
emptyElement?: React.ReactNode;
/**
* Disables periodic date refreshes
*/
disableRefresh?: boolean;
/**
* Whether to show seconds in the rendered time
*/
showSeconds?: boolean;
/**
* Whether the component should render just the string
* Consider using `useTimeAgo` instead
*/
justText?: boolean;
/** A Jss Object used to override or extend the styles applied to the component. */
classes?: HvTimeAgoClasses;
}
export type HvTimeAgoProps<C extends React.ElementType = "p"> =
PolymorphicComponentRef<
C,
{
/**
* The timestamp to format, in seconds or milliseconds.
* Defaults to `emptyElement` if value is null or 0
*/
timestamp?: number;
/**
* The locale to be used. Should be on of the dayjs supported locales and explicitly imported
* @see https://day.js.org/docs/en/i18n/i18n
*/
locale?: string;
/**
* The element to render when the timestamp is null or 0
* Defaults to `—` (Em Dash)
*/
emptyElement?: React.ReactNode;
/** Disables periodic date refreshes */
disableRefresh?: boolean;
/** Whether to show seconds in the rendered time */
showSeconds?: boolean;
/**
* Whether the component should render just the string
* Consider using `useTimeAgo` instead
*/
justText?: boolean;
/** A Jss Object used to override or extend the styles applied to the component. */
classes?: HvTimeAgoClasses;
}
>;

/**
* The HvTimeAgo component implements the Design System relative time format guidelines.
*/
export const HvTimeAgo = (props: HvTimeAgoProps) => {
const {
classes: classesProp,
className,
timestamp,
locale: localeProp = "en",
component: Component = HvTypography,
emptyElement = "—",
disableRefresh = false,
showSeconds = false,
justText = false,
...others
} = useDefaultProps("HvTimeAgo", props);
export const HvTimeAgo: <C extends React.ElementType = "p">(
props: HvTimeAgoProps<C>
) => React.ReactElement | null = forwardRef(
<C extends React.ElementType = "p">(
props: HvTimeAgoProps<C>,
ref: PolymorphicRef<C>
) => {
const {
classes: classesProp,
className,
timestamp,
locale: localeProp = "en",
component: Component = HvTypography,
emptyElement = "—",
disableRefresh = false,
showSeconds = false,
justText = false,
...others
} = useDefaultProps("HvTimeAgo", props);

const { classes, cx } = useClasses(classesProp);
const locale = isEmpty(localeProp) ? "en" : localeProp;
const timeAgo = useTimeAgo(timestamp, {
locale,
disableRefresh,
showSeconds,
});
const { classes, cx } = useClasses(classesProp);
const locale = isEmpty(localeProp) ? "en" : localeProp;
const timeAgo = useTimeAgo(timestamp, {
locale,
disableRefresh,
showSeconds,
});

// eslint-disable-next-line react/jsx-no-useless-fragment
if (justText && timestamp) return <>{timeAgo}</>;
// eslint-disable-next-line react/jsx-no-useless-fragment
if (justText && timestamp) return <>{timeAgo}</>;

return (
<Component className={cx(classes.root, className)} {...others}>
{!timestamp ? emptyElement : timeAgo}
</Component>
);
};
return (
<Component ref={ref} className={cx(classes.root, className)} {...others}>
{!timestamp ? emptyElement : timeAgo}
</Component>
);
}
);
30 changes: 30 additions & 0 deletions packages/core/src/components/Typography/Typography.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,33 @@ export const Variants = () => {
</HvBox>
);
};

interface CustomLinkProps extends HvTypographyProps<"a"> {
to: string;
}

const CustomLink = ({ to, children, ...others }: CustomLinkProps) => (
<a href={to} {...others}>
{children}
</a>
);

export const CustomRootComponent = () => {
return (
<HvBox sx={{ display: "flex", gap: 20, padding: 20 }}>
<HvTypography>Typography</HvTypography>
<HvTypography
component="a"
href="https://lumada-design.github.io/uikit/master"
>
Link
</HvTypography>
<HvTypography
component={CustomLink}
to="https://lumada-design.github.io/uikit/master"
>
Custom link
</HvTypography>
</HvBox>
);
};

0 comments on commit 16fe93a

Please sign in to comment.