From 241d744b5460f36262cb25fe3500d03a207466e5 Mon Sep 17 00:00:00 2001 From: nuria1110 Date: Fri, 17 Jan 2025 16:40:48 +0000 Subject: [PATCH 1/2] feat(numeral-date, time): add support for labelAlign on non-inline labels Adds support for `labelAlign` prop on non-line labels in fieldset components NumeralDate and Time. Also adds `fieldLabelsAlign` prop to enable consumers to change the field labels to align left or right. --- .../fieldset/fieldset.component.tsx | 14 ++++- src/__internal__/fieldset/fieldset.style.ts | 18 +++++- src/__internal__/fieldset/fieldset.test.tsx | 2 +- .../checkbox-group.component.tsx | 2 +- src/components/form/form.stories.tsx | 1 + .../numeral-date-test.stories.tsx | 50 ++++++++++++++-- .../numeral-date/numeral-date.component.tsx | 14 +++-- .../numeral-date/numeral-date.style.ts | 10 ++-- .../radio-button-group.component.tsx | 2 +- .../textbox/textbox-test.stories.tsx | 12 ++-- src/components/time/time-test.stories.tsx | 60 +++++++++++++++++++ src/components/time/time.component.tsx | 14 ++++- src/components/time/time.stories.tsx | 13 ++-- src/components/time/time.style.ts | 2 +- 14 files changed, 178 insertions(+), 36 deletions(-) create mode 100644 src/components/time/time-test.stories.tsx diff --git a/src/__internal__/fieldset/fieldset.component.tsx b/src/__internal__/fieldset/fieldset.component.tsx index 5ea58e43c0..87ba195cc3 100644 --- a/src/__internal__/fieldset/fieldset.component.tsx +++ b/src/__internal__/fieldset/fieldset.component.tsx @@ -6,6 +6,7 @@ import { StyledLegend, StyledLegendContent, StyledIconWrapper, + StyledLegendProps, } from "./fieldset.style"; import ValidationIcon from "../validations/validation-icon.component"; import NewValidationContext from "../../components/carbon-provider/__internal__/new-validation.context"; @@ -67,7 +68,7 @@ const Fieldset = ({ children, inline = false, legendWidth, - legendAlign = "right", + legendAlign, legendSpacing = 2, error, warning, @@ -127,6 +128,15 @@ const Fieldset = ({ return null; }; + let legendAlignment: StyledLegendProps["align"]; + if (inline && !legendAlign) { + legendAlignment = "right"; + } else if (!legendAlign) { + legendAlignment = "left"; + } else { + legendAlignment = legendAlign; + } + return ( ` `} `; -type StyledLegendProps = { +export type StyledLegendProps = { inline?: boolean; width?: number; align?: "left" | "right"; @@ -78,14 +78,26 @@ const StyledLegend = styled.legend` padding: 0; font-weight: var(--fontWeights500); color: var(--colorsUtilityYin090); - ${({ inline, width, align, rightPadding }) => + + ${({ align, inline }) => + align && + css` + text-align: ${align}; + justify-content: ${align === "right" ? "flex-end" : "flex-start"}; + + ${!inline && + css` + width: -moz-available; + `} + `}; + + ${({ inline, width, rightPadding }) => inline && css` float: left; box-sizing: border-box; margin: 0; ${width && `width: ${width}%`}; - justify-content: ${align === "right" ? "flex-end" : "flex-start"}; padding-right: ${rightPadding === 1 ? "var(--spacing100)" : "var(--spacing200)"}; diff --git a/src/__internal__/fieldset/fieldset.test.tsx b/src/__internal__/fieldset/fieldset.test.tsx index a9c5f2cb53..18f41f84a7 100644 --- a/src/__internal__/fieldset/fieldset.test.tsx +++ b/src/__internal__/fieldset/fieldset.test.tsx @@ -123,7 +123,7 @@ test("renders legend with provided `legendWidth` when `inline` is true", () => { }); // coverage -test("renders with expected styles when `inline` is true and `align` is 'left'", () => { +test("renders with expected styles when `inline` is true and `legendAlign` is 'left'", () => { render(
diff --git a/src/components/checkbox/checkbox-group/checkbox-group.component.tsx b/src/components/checkbox/checkbox-group/checkbox-group.component.tsx index 9f9a72eae3..a6f32c237a 100644 --- a/src/components/checkbox/checkbox-group/checkbox-group.component.tsx +++ b/src/components/checkbox/checkbox-group/checkbox-group.component.tsx @@ -56,7 +56,7 @@ export const CheckboxGroup = (props: CheckboxGroupProps) => { isOptional, legendInline, legendWidth, - legendAlign, + legendAlign = "left", legendSpacing, legendHelp, tooltipPosition, diff --git a/src/components/form/form.stories.tsx b/src/components/form/form.stories.tsx index bbb264178b..ec1a4ea430 100644 --- a/src/components/form/form.stories.tsx +++ b/src/components/form/form.stories.tsx @@ -664,6 +664,7 @@ export const FormAlignmentExample: Story = () => { legendInline legendWidth={10} legendSpacing={2} + legendAlign="right" > { NewDesignValidations.storyName = "new design validations"; -export const Required = () => { - return ; -}; - -Required.storyName = "required"; - export const TooltipPosition = () => { return ( <> @@ -263,3 +257,47 @@ export const InForm = () => { }; InForm.storyName = "in form"; + +export const LabelAlign = ({ ...args }) => { + return ( + + + + + + + + ); +}; + +LabelAlign.storyName = "label align"; +LabelAlign.args = { + dateFormat: ["dd", "mm", "yyyy"], +}; +LabelAlign.parameters = { + chromatic: { disableSnapshot: false }, + themeProvider: { chromatic: { theme: "sage" } }, +}; diff --git a/src/components/numeral-date/numeral-date.component.tsx b/src/components/numeral-date/numeral-date.component.tsx index ad4fdda939..91ced85b5d 100644 --- a/src/components/numeral-date/numeral-date.component.tsx +++ b/src/components/numeral-date/numeral-date.component.tsx @@ -102,8 +102,10 @@ export interface NumeralDateProps name?: string; /** Label */ label?: string; - /** [Legacy] Label alignment. Works only when labelInline is true */ + /** Label alignment */ labelAlign?: "left" | "right"; + /** Field labels alignment */ + fieldLabelsAlign?: "left" | "right"; /** * Text applied to label help tooltip, will be rendered as * hint text when `validationRedesignOptIn` is true. @@ -242,6 +244,7 @@ export const NumeralDate = ({ labelInline, labelWidth, labelAlign, + fieldLabelsAlign, labelHelp, labelSpacing, fieldHelp, @@ -470,9 +473,9 @@ export const NumeralDate = ({ } > ({ data-role={dataRole} id={uniqueId} legend={label} + legendMargin={{ mb: 0 }} isRequired={required} isOptional={isOptional} isDisabled={disabled} @@ -528,7 +532,7 @@ export const NumeralDate = ({ aria-describedby={ariaDescribedBy} {...filterStyledSystemMarginProps(rest)} > - + {renderInputs()} {fieldHelp && {fieldHelp}} @@ -544,6 +548,8 @@ export const NumeralDate = ({ data-role={dataRole} id={uniqueId} legend={label} + legendMargin={{ mb: 0 }} + legendAlign={labelAlign} isRequired={required} isOptional={isOptional} isDisabled={disabled} @@ -555,7 +561,7 @@ export const NumeralDate = ({ {labelHelp} )} - + {(internalError || internalWarning) && ( <> { inline = false, legendInline = false, legendWidth, - legendAlign, + legendAlign = "left", legendSpacing, labelSpacing = 1, adaptiveLegendBreakpoint, diff --git a/src/components/textbox/textbox-test.stories.tsx b/src/components/textbox/textbox-test.stories.tsx index cce63dd0b6..8147944bc1 100644 --- a/src/components/textbox/textbox-test.stories.tsx +++ b/src/components/textbox/textbox-test.stories.tsx @@ -71,13 +71,13 @@ export const commonTextboxArgTypes = (isNewValidation?: boolean) => ({ type: "select", }, }, - ...(!isNewValidation && { - labelAlign: { - options: ["left", "right"], - control: { - type: "select", - }, + labelAlign: { + options: ["left", "right"], + control: { + type: "select", }, + }, + ...(!isNewValidation && { labelWidth: { control: { type: "range", diff --git a/src/components/time/time-test.stories.tsx b/src/components/time/time-test.stories.tsx new file mode 100644 index 0000000000..c15bc8b7d8 --- /dev/null +++ b/src/components/time/time-test.stories.tsx @@ -0,0 +1,60 @@ +import React, { useState } from "react"; +import { Meta, StoryObj } from "@storybook/react"; + +import { TimeInputEvent, TimeValue } from "./time.component"; +import { Time } from "."; +import Box from "../box"; + +const meta: Meta = { + component: Time, + title: "Time/Test", + parameters: { + controls: { + exclude: ["value", "onChange", "onBlur"], + }, + }, +}; + +export default meta; +type Story = StoryObj; + +export const LabelAlign: Story = ({ ...args }) => { + const [value, setValue] = useState({ + hours: "", + minutes: "", + period: "AM", + }); + + const handleChange = (ev: TimeInputEvent) => { + setValue(ev.target.value); + }; + + return ( + + + ); +}; +LabelAlign.storyName = "Label Align"; diff --git a/src/components/time/time.component.tsx b/src/components/time/time.component.tsx index 630e89ff0a..100f4be15c 100644 --- a/src/components/time/time.component.tsx +++ b/src/components/time/time.component.tsx @@ -54,6 +54,10 @@ export interface TimeProps MarginProps { /** Label text for the component */ label?: string; + /** Label alignment */ + labelAlign?: "left" | "right"; + /** Field labels alignment */ + fieldLabelsAlign?: "left" | "right"; /** Sets the size of the inputs */ size?: Sizes; /** Additional hint text rendered above the input elements */ @@ -101,6 +105,8 @@ const Time = React.forwardRef( ( { label, + labelAlign, + fieldLabelsAlign, size = "medium", inputHint, hoursInputProps = {}, @@ -251,7 +257,8 @@ const Time = React.forwardRef(
( {inputHint} )} - + ( aria-label={hrsAriaLabel} htmlFor={internalHrsId.current} disabled={disabled} + align={fieldLabelsAlign} > {hrsLabel} @@ -317,6 +325,7 @@ const Time = React.forwardRef( aria-label={minsAriaLabel} htmlFor={internalMinsId.current} disabled={disabled} + align={fieldLabelsAlign} > {minsLabel} @@ -341,6 +350,7 @@ const Time = React.forwardRef( display="flex" flexDirection="column" justifyContent="flex-end" + width="max-content" > = { title: "Time", component: Time, + parameters: { + controls: { + exclude: ["value", "onChange", "onBlur"], + }, + }, argTypes: { ...styledSystemProps, }, @@ -28,7 +33,7 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = () => { +export const Default: Story = ({ ...args }) => { const [value, setValue] = useState({ hours: "", minutes: "", @@ -40,7 +45,7 @@ export const Default: Story = () => { return ( - ); }; @@ -50,7 +55,7 @@ Default.parameters = { themeProvider: { chromatic: { theme: "sage" } }, }; -export const AmPmToggle: Story = () => { +export const AmPmToggle: Story = ({ ...args }) => { const [value, setValue] = useState({ hours: "", minutes: "", @@ -63,7 +68,7 @@ export const AmPmToggle: Story = () => { return ( - ); }; diff --git a/src/components/time/time.style.ts b/src/components/time/time.style.ts index eeb73e0f8f..6e61d46c7a 100644 --- a/src/components/time/time.style.ts +++ b/src/components/time/time.style.ts @@ -16,7 +16,7 @@ export const StyledHintText = styled.div<{ } margin-top: var(--spacing000); - margin-bottom: var(--spacing150); + margin-bottom: var(--spacing100); color: ${({ isDisabled }) => isDisabled ? "var(--colorsUtilityYin030)" : "var(--colorsUtilityYin055)"}; font-size: 14px; From b13e5dc2d8fbbb08728f3f2fafe1aba2d1f2c866 Mon Sep 17 00:00:00 2001 From: nuria1110 Date: Thu, 30 Jan 2025 13:42:47 +0000 Subject: [PATCH 2/2] fix(numeral-date): ensure inlineLabel works as expected in Safari Changes styling to achieve inline label as Safari does not support display: flex in fieldset elements. --- .../numeral-date-test.stories.tsx | 20 +++++++++++++ .../numeral-date/numeral-date.component.tsx | 3 +- .../numeral-date/numeral-date.pw.tsx | 30 +++++++++---------- .../numeral-date/numeral-date.style.ts | 25 ++++++++++++---- .../numeral-date/numeral-date.test.tsx | 6 ++-- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/components/numeral-date/numeral-date-test.stories.tsx b/src/components/numeral-date/numeral-date-test.stories.tsx index cad9af788e..ee059fb6d4 100644 --- a/src/components/numeral-date/numeral-date-test.stories.tsx +++ b/src/components/numeral-date/numeral-date-test.stories.tsx @@ -301,3 +301,23 @@ LabelAlign.parameters = { chromatic: { disableSnapshot: false }, themeProvider: { chromatic: { theme: "sage" } }, }; + +export const InlineLabelsSizes = ({ ...args }) => { + return ( + + + + + + ); +}; + +InlineLabelsSizes.storyName = "inline labels sizes"; +InlineLabelsSizes.args = { + dateFormat: ["dd", "mm", "yyyy"], + labelInline: true, +}; +InlineLabelsSizes.parameters = { + chromatic: { disableSnapshot: false }, + themeProvider: { chromatic: { theme: "sage" } }, +}; diff --git a/src/components/numeral-date/numeral-date.component.tsx b/src/components/numeral-date/numeral-date.component.tsx index 91ced85b5d..c0ff2d5103 100644 --- a/src/components/numeral-date/numeral-date.component.tsx +++ b/src/components/numeral-date/numeral-date.component.tsx @@ -524,6 +524,7 @@ export const NumeralDate = ({ warning={validationOnLabel && internalWarning} info={validationOnLabel && info} inline={inline} + size={size} labelHelp={labelHelp} legendAlign={labelAlign} legendWidth={labelWidth} @@ -532,7 +533,7 @@ export const NumeralDate = ({ aria-describedby={ariaDescribedBy} {...filterStyledSystemMarginProps(rest)} > - + {renderInputs()} {fieldHelp && {fieldHelp}} diff --git a/src/components/numeral-date/numeral-date.pw.tsx b/src/components/numeral-date/numeral-date.pw.tsx index 121d28509d..d572c23ec2 100644 --- a/src/components/numeral-date/numeral-date.pw.tsx +++ b/src/components/numeral-date/numeral-date.pw.tsx @@ -102,9 +102,6 @@ test.describe("NumeralDate component", () => { }) => { await mount(); - const fieldset = page.locator("fieldset"); - await expect(fieldset).toHaveCSS("display", "flex"); - const legend = getDataElementByValue(page, "legend"); await expect(legend).toHaveCSS("float", "left"); }); @@ -568,22 +565,25 @@ test.describe("NumeralDate component", () => { ( [ - ["flex", 399], - ["flex", 400], - ["block", 401], - ] as [string, NumeralDateProps["adaptiveLabelBreakpoint"]][] - ).forEach(([displayValue, breakpoint]) => { - test(`should render NumeralDate with ${displayValue} label alignment when the adaptiveLabelBreakpoint prop is ${breakpoint} with a set viewport of 400`, async ({ + [399, false], + [400, true], + [401, true], + ] as [number, boolean][] + ).forEach(([viewport, isInline]) => { + test(`should render NumeralDate with labelInline when the adaptiveLabelBreakpoint prop is 400 with a viewport of ${viewport}`, async ({ mount, page, }) => { - await page.setViewportSize({ width: 400, height: 300 }); - await mount( - , - ); + await page.setViewportSize({ width: viewport, height: 300 }); + await mount(); + + const legend = getDataElementByValue(page, "legend"); - const fieldset = page.locator("fieldset"); - await expect(fieldset).toHaveCSS("display", displayValue); + if (isInline) { + await expect(legend).toHaveCSS("float", "left"); + } else { + await expect(legend).not.toHaveCSS("float", "left"); + } }); }); diff --git a/src/components/numeral-date/numeral-date.style.ts b/src/components/numeral-date/numeral-date.style.ts index cd343d8616..7577bba6fd 100644 --- a/src/components/numeral-date/numeral-date.style.ts +++ b/src/components/numeral-date/numeral-date.style.ts @@ -2,7 +2,8 @@ import styled, { css } from "styled-components"; import StyledIconSpan from "../../__internal__/input-icon-toggle/input-icon-toggle.style"; import StyledInputPresentation from "../../__internal__/input/input-presentation.style"; import StyledInput from "../../__internal__/input/input.style"; -import Fieldset from "../../__internal__/fieldset"; +import Fieldset, { FieldsetProps } from "../../__internal__/fieldset"; +import { StyledLegend } from "../../__internal__/fieldset/fieldset.style"; interface StyledDateFieldProps { isYearInput?: boolean; @@ -55,12 +56,26 @@ export const StyledDateField = styled.div` `} `; -export const StyledFieldset = styled(Fieldset)` - ${({ inline }) => css` +interface StyledFieldsetProps extends FieldsetProps { + inline?: boolean; + size?: "small" | "medium" | "large"; +} + +// We need to match height of the legend to the input container when it is inline to center it vertically, +// as Safari does not support display: flex on fieldset elements. +const sizeHeight = { + small: "57px", + medium: "65px", + large: "73px", +}; + +export const StyledFieldset = styled(Fieldset)` + ${({ inline, size }) => css` ${inline && css` - display: flex; - align-items: center; + ${StyledLegend} { + height: ${size && sizeHeight[size]}; + } `} ${!inline && diff --git a/src/components/numeral-date/numeral-date.test.tsx b/src/components/numeral-date/numeral-date.test.tsx index edd3e3f4e5..4c31e54b8e 100644 --- a/src/components/numeral-date/numeral-date.test.tsx +++ b/src/components/numeral-date/numeral-date.test.tsx @@ -767,9 +767,9 @@ test("should render with `labelInline` when `adaptiveLabelBreakpoint` set and sc , ); - expect(screen.getByRole("group")).toHaveStyle({ - display: "flex", - alignItems: "center", + expect(screen.getByTestId("legend")).toHaveStyle({ + float: "left", + height: "65px", }); });