diff --git a/apps/website/screens/components/avatar/code/AvatarCodePage.tsx b/apps/website/screens/components/avatar/code/AvatarCodePage.tsx index 1d55b83a54..1c6722f879 100644 --- a/apps/website/screens/components/avatar/code/AvatarCodePage.tsx +++ b/apps/website/screens/components/avatar/code/AvatarCodePage.tsx @@ -7,6 +7,7 @@ import basicUsage from "./examples/basicUsage"; import clickable from "./examples/clickable"; import tooltip from "./examples/tooltip"; import status from "./examples/status"; +import StatusBadge from "@/common/StatusBadge"; const statusTypeString = `{ mode: 'default' | 'info' | @@ -100,6 +101,19 @@ const sections = [ Text to be displayed as label next to the avatar. - + + + + + ref + + + + {"React.Ref"} + + Ref to the avatar element. + - + secondaryText diff --git a/apps/website/screens/components/badge/code/BadgeCodePage.tsx b/apps/website/screens/components/badge/code/BadgeCodePage.tsx index 0c8b3be9b8..b75be17c6c 100644 --- a/apps/website/screens/components/badge/code/BadgeCodePage.tsx +++ b/apps/website/screens/components/badge/code/BadgeCodePage.tsx @@ -6,6 +6,7 @@ import Example from "@/common/example/Example"; import contextual from "./examples/contextual"; import notification from "./examples/notification"; import icons from "./examples/icons"; +import StatusBadge from "@/common/StatusBadge"; const sections = [ { @@ -79,6 +80,19 @@ const sections = [ 99 + + + + + ref + + + + {"React.Ref"} + + Ref to the badge element. + - + size diff --git a/apps/website/screens/components/button/code/ButtonCodePage.tsx b/apps/website/screens/components/button/code/ButtonCodePage.tsx index 0e0217718c..a90dd39834 100644 --- a/apps/website/screens/components/button/code/ButtonCodePage.tsx +++ b/apps/website/screens/components/button/code/ButtonCodePage.tsx @@ -7,6 +7,7 @@ import semantic from "./examples/semantic"; import icons from "./examples/icons"; import sizes from "./examples/sizes"; import Code, { TableCode } from "@/common/Code"; +import StatusBadge from "@/common/StatusBadge"; const sections = [ { @@ -94,6 +95,19 @@ const sections = [ This function will be called when the user clicks the button. - + + + + + ref + + + + {"React.Ref"} + + Ref to the button element. + - + semantic diff --git a/apps/website/screens/components/card/code/CardCodePage.tsx b/apps/website/screens/components/card/code/CardCodePage.tsx index e1977c8bee..0fd8191a36 100644 --- a/apps/website/screens/components/card/code/CardCodePage.tsx +++ b/apps/website/screens/components/card/code/CardCodePage.tsx @@ -245,6 +245,22 @@ const sections = [ - + + + + + ref + + + + {"React.Ref"} + + + Ref to the card element. The type is determined by whether the card is rendered as a div or an anchor + element based on the props such as href. + + - + diff --git a/apps/website/screens/components/chip/code/ChipCodePage.tsx b/apps/website/screens/components/chip/code/ChipCodePage.tsx index 6681764bce..7d2ea1e4c0 100644 --- a/apps/website/screens/components/chip/code/ChipCodePage.tsx +++ b/apps/website/screens/components/chip/code/ChipCodePage.tsx @@ -128,6 +128,19 @@ const sections = [ - + + + + + ref + + + + {"React.Ref"} + + Ref to the chip element. + - + diff --git a/packages/lib/src/avatar/Avatar.tsx b/packages/lib/src/avatar/Avatar.tsx index 01d3dffe66..c66929c2d4 100644 --- a/packages/lib/src/avatar/Avatar.tsx +++ b/packages/lib/src/avatar/Avatar.tsx @@ -1,5 +1,5 @@ -import { memo, useCallback, useMemo, useState } from "react"; -import AvatarPropsType from "./types"; +import { forwardRef, useCallback, useMemo, useState } from "react"; +import AvatarPropsType, { ContentType, LabelWrapperType } from "./types"; import { getBorderWidth, getFontSize, getInitials, getModeColor } from "./utils"; import DxcTypography from "../typography/Typography"; import DxcImage from "../image/Image"; @@ -63,107 +63,145 @@ const StatusContainer = styled.div<{ background-color: ${({ status }) => getModeColor(status!.mode)}; `; -const DxcAvatar = memo( - ({ - color = "neutral", - disabled = false, - icon = "person", - imageSrc, - label, - linkHref, - onClick, - primaryText, - secondaryText, - shape = "circle", - size = "medium", - status, - tabIndex = 0, - title, - }: AvatarPropsType) => { +const content = ({ + hasAction, + onClick, + linkHref, + disabled, + imageSrc, + error, + handleError, + label, + title, + size, + initials, + icon, + color, + status, +}: ContentType) => ( + <> + + {(!!onClick || !!linkHref || disabled) && + {status && } + +); + +const LabelWrapper = ({ condition, children, primaryText, secondaryText }: LabelWrapperType) => + condition ? ( + + {children} + + {primaryText && ( + + {primaryText} + + )} + {secondaryText && ( + + {secondaryText} + + )} + + + ) : ( + <>{children} + ); + +const DxcAvatar = forwardRef( + ( + { + color = "neutral", + disabled = false, + icon = "person", + imageSrc, + label, + linkHref, + onClick, + primaryText, + secondaryText, + shape = "circle", + size = "medium", + status, + tabIndex = 0, + title, + }: AvatarPropsType, + ref + ) => { const [error, setError] = useState(false); const initials = useMemo(() => getInitials(label), [label]); const handleError = useCallback(() => setError(true), []); const hasAction = !disabled && (!!onClick || !!linkHref); - const content = ( - <> - - {(!!onClick || !!linkHref || disabled) && - {status && } - - ); - - const LabelWrapper = ({ condition, children }: { condition: boolean; children: React.ReactNode }) => - condition ? ( - - {children} - - {primaryText && ( - - {primaryText} - - )} - {secondaryText && ( - - {secondaryText} - - )} - - - ) : ( - <>{children} - ); - return ( - + void; + linkHref?: string; + disabled?: boolean; + imageSrc?: string; + error: boolean; + handleError: () => void; + label?: string; + title?: string; + size: Size; + initials: string; + icon?: string | SVG; + color: Color; + status?: Status; +}; + export default Props; diff --git a/packages/lib/src/badge/Badge.tsx b/packages/lib/src/badge/Badge.tsx index cdcb0e8926..85856a2c53 100644 --- a/packages/lib/src/badge/Badge.tsx +++ b/packages/lib/src/badge/Badge.tsx @@ -2,6 +2,7 @@ import styled from "@emotion/styled"; import BadgePropsType from "./types"; import DxcIcon from "../icon/Icon"; import { Tooltip } from "../tooltip/Tooltip"; +import { forwardRef } from "react"; const contextualColorMap = { primary: { @@ -136,33 +137,31 @@ const Label = styled.span<{ size: BadgePropsType["size"] }>` line-height: normal; `; -const DxcBadge = ({ - label, - title, - mode = "contextual", - color = "neutral", - icon, - notificationLimit = 99, - size = "medium", -}: BadgePropsType): JSX.Element => ( - - - {mode === "contextual" && icon && ( - {typeof icon === "string" ? : icon} - )} - {label && ( - - )} - - +const DxcBadge = forwardRef( + ( + { label, title, mode = "contextual", color = "neutral", icon, notificationLimit = 99, size = "medium" }, + ref + ): JSX.Element => ( + + + {mode === "contextual" && icon && ( + {typeof icon === "string" ? : icon} + )} + {label && ( + + )} + + + ) ); export default DxcBadge; diff --git a/packages/lib/src/button/Button.tsx b/packages/lib/src/button/Button.tsx index 9c3f86bd80..962dac87d7 100644 --- a/packages/lib/src/button/Button.tsx +++ b/packages/lib/src/button/Button.tsx @@ -1,4 +1,5 @@ import styled from "@emotion/styled"; +import { forwardRef } from "react"; import { spaces } from "../common/variables"; import ButtonPropsType, { Mode, Semantic, Size } from "./types"; import DxcIcon from "../icon/Icon"; @@ -66,38 +67,48 @@ const IconContainer = styled.div<{ } `; -const DxcButton = ({ - disabled, - icon, - iconPosition = "before", - label, - margin, - mode = "primary", - onClick, - semantic = "default", - size = { height: "large", width: "fitContent" }, - tabIndex = 0, - title, - type = "button", -}: ButtonPropsType): JSX.Element => ( - - - +const DxcButton = forwardRef( + ( + { + disabled, + icon, + iconPosition = "before", + label, + margin, + mode = "primary", + onClick, + semantic = "default", + size = { height: "large", width: "fitContent" }, + tabIndex = 0, + title, + type = "button", + }, + ref + ): JSX.Element => { + return ( + + + + ); + } ); export default DxcButton; diff --git a/packages/lib/src/card/Card.tsx b/packages/lib/src/card/Card.tsx index 8591392ccc..2c7fb92bfe 100644 --- a/packages/lib/src/card/Card.tsx +++ b/packages/lib/src/card/Card.tsx @@ -106,7 +106,7 @@ const DxcCard = forwardRef( size = { width: "fitContent", height: "fitContent" }, tabIndex = 0, }: CardPropsType, - ref: Ref + ref: Ref ) => { const isInteractive = !!(onClick || onSelectionChange) || selectable; const [internalSelected, setInternalSelected] = useState( @@ -170,7 +170,7 @@ const DxcCard = forwardRef( onClick={(event) => { handleEvent(event, onClick); }} - ref={ref} + ref={ref as Ref} onKeyDown={(event) => { handleEvent(event, onClick); }} @@ -201,6 +201,7 @@ const DxcCard = forwardRef( handleEvent(event, onClick, onSelectionChange, internalSelected, setInternalSelected, selected, selectable); }} aria-checked={selectable ? internalSelected : undefined} + ref={ref as Ref} > {image && ( diff --git a/packages/lib/src/chip/Chip.tsx b/packages/lib/src/chip/Chip.tsx index dc758ab540..e5dd0adce3 100644 --- a/packages/lib/src/chip/Chip.tsx +++ b/packages/lib/src/chip/Chip.tsx @@ -3,7 +3,7 @@ import DxcIcon from "../icon/Icon"; import ChipPropsType, { ChipAvatarType } from "./types"; import DxcActionIcon from "../action-icon/ActionIcon"; import DxcAvatar from "../avatar/Avatar"; -import { isValidElement, ReactNode, useState } from "react"; +import { forwardRef, isValidElement, ReactNode, useState } from "react"; import { SVG } from "../common/utils"; import { getChipStyles } from "./utils"; @@ -87,60 +87,55 @@ const renderPrefix = (prefix: string | SVG | ChipAvatarType | undefined, disable return isValidElement(prefix) ? {prefix} : undefined; }; -const DxcChip = ({ - disabled = false, - label, - mode = "selectable", - onClick, - prefix, - selected, - tabIndex = 0, -}: ChipPropsType) => { - const [innerSelected, setInnerSelected] = useState(false); - - if (mode === "selectable" && isAvatarType(prefix) && !label) { - return null; - } +const DxcChip = forwardRef( + ({ disabled = false, label, mode = "selectable", onClick, prefix, selected, tabIndex = 0 }, ref) => { + const [innerSelected, setInnerSelected] = useState(false); - const handleSelectableClick = () => { - if (selected == null) { - setInnerSelected((prev) => !prev); + if (mode === "selectable" && isAvatarType(prefix) && !label) { + return null; } - onClick?.(); - }; - - const isSelected = selected ?? innerSelected; - - return ( - - - {prefix && renderPrefix(prefix, disabled)} - {label && {label}} - - - {mode === "dismissible" && ( - - )} - - ); -}; + + const handleSelectableClick = () => { + if (selected == null) { + setInnerSelected((prev) => !prev); + } + onClick?.(); + }; + + const isSelected = selected ?? innerSelected; + + return ( + + + {prefix && renderPrefix(prefix, disabled)} + {label && {label}} + + + {mode === "dismissible" && ( + + )} + + ); + } +); export default DxcChip;