From 421ef9251a987dcb695d0fa459294ef8155f78f8 Mon Sep 17 00:00:00 2001 From: lukemojo <143453762+lukemojo@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:16:53 +0000 Subject: [PATCH 1/2] Release/10.4.1 (#1036) * bug/TD-3284 nowrap typography truncation ellipsis color issue * updated request changes by approaching computed style * Upadted TruncatedTooltip.tsx * Updated changes requested * updated comment line for adjacent code * Update stories to show how to correctly use Link with this component * Undo change of sx order * Deleted NoWrapTypography * Replaced NoWrapTypography with TruncatedTooltip * Update LabeSelector.jsx * Replace NoWrapTypography with TruncatedTooltip * updated changes requested * Updated changes requested * Restore NoWrapTypography * Undo change to grid import * TD-3355 - Fixes Clear button on Variant select when deselecting (#1033) * Fixes Clear button on Variant select when deselecting * Add test to test clear button * Bumped the test coverage for this file up slightly * TD-3284 Truncated Tooltip (#1034) * Manually bring in all changes from #1032 * 10.4.1-1 * 10.4.1-2 --------- Co-authored-by: ShushN Co-authored-by: Matthew Corner Co-authored-by: stevenmcsorleyipg --- package.json | 2 +- src/Card/DetailCard/DetailCard.tsx | 47 +++++++------ src/Card/SummaryCard/SummaryCard.tsx | 13 ++-- src/Filter/CheckboxFilter/CheckboxFilter.tsx | 11 +-- src/Filter/LabelFilter/LabelFilter.tsx | 13 ++-- src/LabelSelector/LabelChip/LabelChip.tsx | 17 ++++- src/LabelSelector/LabelSelector.jsx | 21 ++++-- src/LazyLoadImage/LazyLoadImage.stories.tsx | 16 +++-- src/TextField/TextField.tsx | 17 ++--- .../TruncatedTooltip.stories.tsx | 69 +++++-------------- src/TruncatedTooltip/TruncatedTooltip.tsx | 49 +++++++------ src/VehicleSelector/VehicleSelector.test.tsx | 62 ++++++++++++++++- src/VehicleSelector/VehicleSelector.tsx | 5 +- src/index.ts | 4 -- 14 files changed, 209 insertions(+), 137 deletions(-) diff --git a/package.json b/package.json index d16fad90..0782dc30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ipguk/react-ui", - "version": "10.4.0", + "version": "10.4.1-2", "description": "React UI component library for IPG web applications", "author": { "name": "IPG-Automotive-UK" diff --git a/src/Card/DetailCard/DetailCard.tsx b/src/Card/DetailCard/DetailCard.tsx index 4829f631..62fe5c24 100644 --- a/src/Card/DetailCard/DetailCard.tsx +++ b/src/Card/DetailCard/DetailCard.tsx @@ -1,12 +1,12 @@ -import { Box, Card, Stack } from "@mui/material"; +import { Box, Card, Stack, Typography } from "@mui/material"; import { DetailCardHeaderProps, DetailCardProps } from "./DetailCard.types"; import { FileDetails, Infographic } from "../../CardContent"; import React, { Fragment, useEffect, useRef, useState } from "react"; import LabelChipGroup from "../../LabelSelector/LabelChipGroup/LabelChipGroup"; import type { LabelChipGroupProps } from "../../LabelSelector/LabelChipGroup/LabelChipGroup.types"; -import NoWrapTypography from "../../NoWrapTypography/NoWrapTypography"; import { ResizeObserver } from "@juggle/resize-observer"; +import TruncatedTooltip from "../../TruncatedTooltip/TruncatedTooltip"; // TODO: add tests in browser once we are done with the migration to cypress. The old tests live in a txt file in this folder until then. @@ -29,13 +29,13 @@ function DetailCard({ return ( @@ -55,7 +55,7 @@ function DetailCard({ overflow: "auto" }} > - + {content} @@ -133,33 +132,37 @@ function DetailCardHeader({ return ( - - theme.palette.mode === "dark" ? "white" : "black", + ({ + color: "black", fontSize: 20, - fontWeight: 700 - }} + fontWeight: 700, + ...theme.applyStyles("dark", { + color: "white" + }) + })} > {title} - - theme.palette.text.secondary, + + ({ + color: theme.palette.text.secondary, fontSize: 14, fontWeight: 400 - }} + })} > {subtitle} - + - + {labels && labels.length > 0 && } diff --git a/src/Card/SummaryCard/SummaryCard.tsx b/src/Card/SummaryCard/SummaryCard.tsx index 116f393c..b348fce5 100644 --- a/src/Card/SummaryCard/SummaryCard.tsx +++ b/src/Card/SummaryCard/SummaryCard.tsx @@ -7,6 +7,7 @@ import { IconButton, Popover, Stack, + Typography, cardHeaderClasses } from "@mui/material"; import React, { useState } from "react"; @@ -15,8 +16,8 @@ import { Infographic } from "../../CardContent"; import LabelChipGroup from "../../LabelSelector/LabelChipGroup/LabelChipGroup"; import type { LabelChipGroupProps } from "../../LabelSelector/LabelChipGroup/LabelChipGroup.types"; import { MoreVert } from "@mui/icons-material"; -import NoWrapTypography from "../../NoWrapTypography/NoWrapTypography"; import { SummaryCardProps } from "./SummaryCard.types"; +import TruncatedTooltip from "../../TruncatedTooltip/TruncatedTooltip"; // TODO: add tests in browser once we are done with the migration to cypress. The old tests live in a txt file in this folder until then. @@ -99,24 +100,26 @@ function SummaryCard({ } disableTypography title={ - {title} - + } subheader={ - {subtitle} - + } /> } value={option} /> - + {option} - + ); } diff --git a/src/Filter/LabelFilter/LabelFilter.tsx b/src/Filter/LabelFilter/LabelFilter.tsx index 89e33f7b..f72b1058 100644 --- a/src/Filter/LabelFilter/LabelFilter.tsx +++ b/src/Filter/LabelFilter/LabelFilter.tsx @@ -5,13 +5,14 @@ import { AutocompleteRenderGetTagProps, Checkbox, Stack, - TextField + TextField, + Typography } from "@mui/material"; import { Label } from "../../LabelSelector/Label.types"; import LabelChip from "../../LabelSelector/LabelChip/LabelChip"; import { LabelFilterProps } from "./LabelFilter.types"; -import NoWrapTypography from "../../NoWrapTypography"; +import TruncatedTooltip from "../../TruncatedTooltip/TruncatedTooltip"; import { VirtualizedAutocomplete } from "../../Autocomplete/Autocomplete"; import { sortLabelOptions } from "../sortLabelOptions"; @@ -120,10 +121,12 @@ function Option( }} /> - {option.name} - + + {option.name} + + {option.description || "No description"} - + ); diff --git a/src/LabelSelector/LabelChip/LabelChip.tsx b/src/LabelSelector/LabelChip/LabelChip.tsx index 062c67a9..ede25fd7 100644 --- a/src/LabelSelector/LabelChip/LabelChip.tsx +++ b/src/LabelSelector/LabelChip/LabelChip.tsx @@ -1,9 +1,16 @@ -import { Chip, Tooltip, alpha, chipClasses, darken } from "@mui/material"; +import { + Chip, + Tooltip, + Typography, + alpha, + chipClasses, + darken +} from "@mui/material"; import DoneIcon from "@mui/icons-material/Done"; import { LabelChipProps } from "./LabelChip.types"; -import NoWrapTypography from "../../NoWrapTypography"; import React from "react"; +import TruncatedTooltip from "../../TruncatedTooltip/TruncatedTooltip"; // component to display a chip with custom colors export default function LabelChip({ @@ -26,7 +33,11 @@ export default function LabelChip({ className="label-chip" clickable={clickable} icon={selected ? : undefined} - label={{label}} + label={ + + {label} + + } onClick={clickable ? onClick : undefined} sx={{ "&:hover": { diff --git a/src/LabelSelector/LabelSelector.jsx b/src/LabelSelector/LabelSelector.jsx index 42554a05..ac4f8fd3 100644 --- a/src/LabelSelector/LabelSelector.jsx +++ b/src/LabelSelector/LabelSelector.jsx @@ -5,7 +5,8 @@ import { Paper, Stack, TextField, - Tooltip + Tooltip, + Typography } from "@mui/material"; import { Delete, Edit } from "@mui/icons-material"; import React, { useState } from "react"; @@ -14,8 +15,8 @@ import Checkbox from "../Checkbox"; import DeleteLabelDialog from "./DeleteLabelDialog/DeleteLabelDialog"; import EditLabelDialog from "../EditLabelDialog/EditLabelDialog"; import LabelChip from "./LabelChip/LabelChip"; -import NoWrapTypography from "../NoWrapTypography/NoWrapTypography"; import PropTypes from "prop-types"; +import TruncatedTooltip from "../TruncatedTooltip/TruncatedTooltip"; import { VirtualizedAutocomplete } from "../Autocomplete/Autocomplete"; import { createFilterOptions } from "@mui/material/Autocomplete"; @@ -215,11 +216,19 @@ export default function LabelSelector({ color: option.color }} /> - - {option.name} - + + + {option.name} + + {option.description} - + <> {editEnabled && ( diff --git a/src/LazyLoadImage/LazyLoadImage.stories.tsx b/src/LazyLoadImage/LazyLoadImage.stories.tsx index ace39af6..33483071 100644 --- a/src/LazyLoadImage/LazyLoadImage.stories.tsx +++ b/src/LazyLoadImage/LazyLoadImage.stories.tsx @@ -3,8 +3,8 @@ import { Meta, StoryFn } from "@storybook/react"; import LazyLoadImage from "./LazyLoadImage"; import { LazyLoadImageProps } from "./LazyLoadImage.types"; -import NoWrapTypography from "../NoWrapTypography/NoWrapTypography"; import React from "react"; +import TruncatedTooltip from "../TruncatedTooltip/TruncatedTooltip"; /** * Wrapper to lazy load an image @@ -126,7 +126,7 @@ const FlexSizeComponent: StoryFn = args => { transition: "color 0.1s" }, backgroundColor: theme.palette.background.paper, - border: `1px solid`, + border: `1px solid ${theme.palette.divider}`, borderRadius: "6px", cursor: "pointer", height: "238px", @@ -144,15 +144,21 @@ const FlexSizeComponent: StoryFn = args => { }} > - {"Example project code"} - - + + 11 Prototype diff --git a/src/TextField/TextField.tsx b/src/TextField/TextField.tsx index 2590a71d..c89e8462 100644 --- a/src/TextField/TextField.tsx +++ b/src/TextField/TextField.tsx @@ -4,14 +4,15 @@ import React from "react"; import { TextFieldProps } from "./TextField.types"; // masked input -const MaskedTextField = React.forwardRef((props, ref) => ( - -)); -MaskedTextField.displayName = "MaskedTextField"; +const MaskedTextField = React.forwardRef(function MaskedTextField(props, ref) { + return ( + + ); +}); /** * TextField components are used for collecting user provided information as a string. diff --git a/src/TruncatedTooltip/TruncatedTooltip.stories.tsx b/src/TruncatedTooltip/TruncatedTooltip.stories.tsx index 9b9c7c60..85618ef9 100644 --- a/src/TruncatedTooltip/TruncatedTooltip.stories.tsx +++ b/src/TruncatedTooltip/TruncatedTooltip.stories.tsx @@ -1,15 +1,14 @@ -import { Box, Link, Typography } from "@mui/material"; +import { Box, Link as MuiLink, Typography } from "@mui/material"; import { Meta, StoryFn, StoryObj } from "@storybook/react"; import React from "react"; import TruncatedTooltip from "./TruncatedTooltip"; -import { TruncatedTooltipProps } from "./TruncatedTooltip.types"; export default { component: TruncatedTooltip, title: "General/TruncatedTooltip" } satisfies Meta; -const Template: StoryFn = args => { +const Template: StoryFn = args => { return ( = args => { export const Default: StoryObj = { args: { children: "This is a long text that will be truncated", - component: Typography, - href: "This/can/take/an/href/prop" - }, - - render: Template -}; - -export const LinkUsingChildren = { - args: { - children: ( - - This is a long text that will be truncated - - ) + component: Typography }, render: Template }; -export const LinkUsingComponent: StoryObj = { +export const Link: StoryObj = { args: { children: "This is a long text that will be truncated", - component: Link, - href: "This/can/take/an/href/prop" + component: MuiLink, + href: "This/can/take/an/href/prop", + variant: "body1" }, render: Template @@ -55,11 +42,11 @@ export const LinkUsingComponent: StoryObj = { export const Multiline = { args: { - children: `Maybe there's a happy little Evergreen that lives here. - Automatically, all of these beautiful, beautiful things will happen. - We need dark in order to show light. But they're very easily killed. - Clouds are delicate. I really believe that if you practice enough you could paint the 'Mona Lisa' - with a two-inch brush. Look around, look at what we have. + children: `Maybe there's a happy little Evergreen that lives here. + Automatically, all of these beautiful, beautiful things will happen. + We need dark in order to show light. But they're very easily killed. + Clouds are delicate. I really believe that if you practice enough you could paint the 'Mona Lisa' + with a two-inch brush. Look around, look at what we have. Beauty is everywhere, you only have to look to see it.`, component: Typography, multiline: 3 @@ -68,40 +55,20 @@ export const Multiline = { render: Template }; -export const LinkUsingComponentWithoutTruncation: StoryObj< - typeof TruncatedTooltip -> = { +export const AlwaysShowTooltip: StoryObj = { args: { alwaysShowTooltip: true, children: "No truncation", - component: Link, - href: "This/can/take/an/href/prop" + component: Typography }, - render: Template }; -export const LinkUsingComponentAlwaysShowingTooltipTextTruncated: StoryObj< - typeof TruncatedTooltip -> = { +export const TooltipProps: StoryObj = { args: { - alwaysShowTooltip: true, - children: "This is a long text that will be truncated", - component: Link, - href: "This/can/take/an/href/prop" + TooltipProps: { arrow: true }, + children: "This tooltip includes an arrow", + component: Typography }, - - render: Template -}; - -export const ComponentUsesTooltipProps: StoryObj = { - args: { - TooltipProps: { placement: "bottom-start" }, - alwaysShowTooltip: true, - children: "No truncation", - component: Link, - href: "This/can/take/an/href/prop" - }, - render: Template }; diff --git a/src/TruncatedTooltip/TruncatedTooltip.tsx b/src/TruncatedTooltip/TruncatedTooltip.tsx index a330a6fe..ed107c2b 100644 --- a/src/TruncatedTooltip/TruncatedTooltip.tsx +++ b/src/TruncatedTooltip/TruncatedTooltip.tsx @@ -1,31 +1,35 @@ import { Box, Tooltip } from "@mui/material"; -import React, { - Children, - ReactElement, - isValidElement, - useRef, - useState -} from "react"; +import React, { Children, ReactElement, isValidElement, useState } from "react"; import { TruncatedTooltipProps } from "./TruncatedTooltip.types"; +// https://fettblog.eu/typescript-react-generic-forward-refs/#option-3%3A-augment-forwardref +// Note; In React 19 we will no longer need to use (or augment) the forwardRef type. +declare module "react" { + // eslint-disable-next-line no-unused-vars + function forwardRef( + render: (props: P, ref: React.ForwardedRef) => React.ReactNode + ): (props: P & React.RefAttributes) => React.ReactNode; +} + /** * Truncates text and shows a tooltip if the text overflows. * Automatically grabs the tooltip from child. * Renders a specified MUI component or element otherwise wraps string in a span. */ -const TruncatedTooltip = ({ - children, - component, - multiline, - sx, - tooltip, - alwaysShowTooltip = false, - TooltipProps = undefined, - ...rest -}: TruncatedTooltipProps) => { - // Ref to the text element. - const textElementRef = useRef(null); +function TruncatedTooltipInner( + { + children, + component, + multiline, + sx, + tooltip, + alwaysShowTooltip = false, + TooltipProps = undefined, + ...rest + }: TruncatedTooltipProps, + ref: React.ForwardedRef +) { // State to determine if the tooltip should show. const [open, setOpen] = useState(false); @@ -96,7 +100,6 @@ const TruncatedTooltip = ({ > *": { @@ -121,12 +124,16 @@ const TruncatedTooltip = ({ // You cannot spread `sx` directly because `SxProps` (typeof sx) can be an array. ...(Array.isArray(sx) ? sx : [sx]) ]} + ref={ref} {...rest} > {children} ); -}; +} + +// forward ref +const TruncatedTooltip = React.forwardRef(TruncatedTooltipInner); export default TruncatedTooltip; diff --git a/src/VehicleSelector/VehicleSelector.test.tsx b/src/VehicleSelector/VehicleSelector.test.tsx index b830fd7b..3490d398 100644 --- a/src/VehicleSelector/VehicleSelector.test.tsx +++ b/src/VehicleSelector/VehicleSelector.test.tsx @@ -1,6 +1,6 @@ import React, { useState } from "react"; import { Vehicle, VehicleSelectorProps } from "./VehicleSelector.types"; -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import VehicleSelector from "./VehicleSelector"; @@ -235,4 +235,64 @@ describe("VehicleSelector", () => { expect(screen.getByRole("combobox", { name: /variant/i })).toBeDisabled(); expect(screen.getByRole("combobox", { name: /gate/i })).toBeDisabled(); }); + + it("removes clear button when variant is deselected in single selection mode", async () => { + const value = [ + { + _id: "64c8c4cccc8d6f00130b366b", + gate: "Gate 1", + modelYear: "2015", + projectCode: "911", + variant: "NN" + } + ]; + + render( + + ); + + expect(screen.getByRole("combobox", { name: /variant/i })).toHaveValue( + "NN" + ); + + const variantField = screen.getByRole("combobox", { name: /variant/i }); + const clearButton = variantField.parentElement?.querySelector( + '[aria-label="Clear"]' + ); + + expect(clearButton).toBeInTheDocument(); + (clearButton as HTMLElement)?.click(); + + await waitFor(() => + expect( + variantField.parentElement?.querySelector('[aria-label="Clear"]') + ).not.toBeInTheDocument() + ); + + expect(variantField).toHaveValue(""); + }); + + it("filters model years based on selected project", async () => { + render(); + + const projectInput = screen.getByRole("combobox", { + name: /project code/i + }); + fireEvent.mouseDown(projectInput); + fireEvent.click(screen.getByText("911")); + + const modelYearInput = screen.getByRole("combobox", { + name: /model year/i + }); + fireEvent.mouseDown(modelYearInput); + + await waitFor(() => { + expect(screen.getByText("2015")).toBeInTheDocument(); + expect(screen.getByText("2016")).toBeInTheDocument(); + }); + }); }); diff --git a/src/VehicleSelector/VehicleSelector.tsx b/src/VehicleSelector/VehicleSelector.tsx index 1a2dde61..9421c7fe 100644 --- a/src/VehicleSelector/VehicleSelector.tsx +++ b/src/VehicleSelector/VehicleSelector.tsx @@ -223,7 +223,10 @@ function VehicleSelector({ } }} size={size} - value={selectedVariants} + // if array is empty, set value to null to avoid rendering the clear button + value={ + multipleSelection ? selectedVariants : selectedVariants[0] || null + } /> diff --git a/src/index.ts b/src/index.ts index 3c12ce17..849300d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -218,10 +218,6 @@ export { useColorMap, type UserAvatarProps } from "./UserAvatar"; -export { - default as NoWrapTypography, - type NoWrapTypographyProps -} from "./NoWrapTypography"; export { default as ModelButtonImage, type ModelButtonImageProps From b45e5bbbf217b7f90d407405edbf2302fac4b49a Mon Sep 17 00:00:00 2001 From: lukemojo <143453762+lukemojo@users.noreply.github.com> Date: Fri, 31 Jan 2025 16:20:16 +0000 Subject: [PATCH 2/2] 10.4.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0782dc30..0e71d259 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ipguk/react-ui", - "version": "10.4.1-2", + "version": "10.4.1", "description": "React UI component library for IPG web applications", "author": { "name": "IPG-Automotive-UK"