-
Notifications
You must be signed in to change notification settings - Fork 74
[LG-5532] feat(time-input) display segment values #3379
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[LG-5532] feat(time-input) display segment values #3379
Conversation
…omponents with context integration
…tate handling in TimeInputInputs component
…segment rules and default values
… support 12/24 hour formats
…cale dependency and enhancing formatting logic
… format output with and without seconds
…meInputDisplayContext
…kMode and size, and add console log for debugging in TimeInputInputs
…omponent, covering rendering, value updates, and keyboard interactions
…omponent, covering rendering, value updates, and keyboard interactions
…12HourFormat prop, replacing TimeSegments with TimeSegment for improved clarity and consistency
…ity in time input context
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements display functionality for time input segments in the TimeInput component, enabling users to see and interact with individual time parts (hour, minute, second) in a segmented input format.
Key Changes
- Introduced
TimeInputSegmentcomponent with comprehensive test coverage for keyboard navigation and value formatting - Added
InputBoxintegration to render segmented time inputs with proper validation and formatting rules - Refactored time format detection from
is12hFormattois12HourFormatfor clarity and consistency
Reviewed changes
Copilot reviewed 27 out of 32 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
packages/time-input/src/TimeInputSegment/TimeInputSegment.tsx |
New component rendering individual time segments with validation |
packages/time-input/src/TimeInputSegment/TimeInputSegment.spec.tsx |
Comprehensive test suite for segment keyboard interactions |
packages/time-input/src/TimeInputBox/TimeInputBox.tsx |
Container component integrating InputBox with time-specific configuration |
packages/time-input/src/TimeInputInputs/TimeInputInputs.tsx |
Updated to use TimeInputBox and manage segment state |
packages/time-input/src/constants.ts |
Added segment rules, min/max values, and placeholder definitions |
packages/time-input/src/shared.types.ts |
Defined TimeSegment enum and TimeSegmentsState type |
packages/time-input/src/hooks/useSelectUnit/useSelectUnit.ts |
Hook managing AM/PM select unit synchronization with date value |
packages/time-input/src/TimeFormField/* |
New wrapper components for FormField with time-specific styling |
packages/time-input/src/Context/TimeInputDisplayContext/* |
Renamed is12hFormat to is12HourFormat across context |
packages/date-picker/src/shared/hooks/useDateSegments/useDateSegments.ts |
Added clarifying comments for segment update flow |
Files not reviewed (1)
- pnpm-lock.yaml: Language not supported
packages/time-input/src/TimeInputSegment/TimeInputSegment.spec.tsx
Outdated
Show resolved
Hide resolved
| const onChangeHandler = | ||
| jest.fn<TimeInputSegmentChangeEventHandler>(); | ||
| const { input } = renderSegment({ | ||
| segment: 'minute', |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hardcoded segment value 'minute' should use the dynamic segment variable from the test parameter (line 368) to ensure test correctness for both 'minute' and 'second' segments.
| segment: 'minute', | |
| segment, |
packages/time-input/src/Context/TimeInputDisplayContext/index.ts
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is being replaced in the next PR so it doesn't need to be reviewed
… in TimeInputSegment tests
|
Size Change: +2.08 kB (+0.11%) Total Size: 1.83 MB
ℹ️ View Unchanged
|
| return ( | ||
| <FormFieldInputContainer | ||
| ref={fwdRef} | ||
| role="combobox" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Time input isn't a combobox. I's just a text input
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoopsie
| describe('12 hour format', () => { | ||
| it('should have a min of 1 for the hour segment', () => { | ||
| const { hourInput } = renderTimeInputBox({ | ||
| displayProps: { locale: SupportedLocales.en_US }, | ||
| }); | ||
| expect(hourInput).toHaveAttribute('min', '1'); | ||
| }); | ||
| it('should have a max of 12 for the hour segment', () => { | ||
| const { hourInput } = renderTimeInputBox({ | ||
| displayProps: { locale: SupportedLocales.en_US }, | ||
| }); | ||
| expect(hourInput).toHaveAttribute('max', '12'); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These tests are brittle since we're checking the DOM attributes, not the actual behavior
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can remove these since i'm testing the behavior in TimeInputSegment
packages/time-input/src/constants.ts
Outdated
| return { | ||
| [TimeSegment.Hour]: { | ||
| maxChars: 2, | ||
| minExplicitValue: is12HourFormat ? 1 : 2, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
shouldn't this be:
| minExplicitValue: is12HourFormat ? 1 : 2, | |
| minExplicitValue: is12HourFormat ? 2 : 3, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
whoops, should be. I thought I changed this in this PR, but I did it in the next PR, which adds tests for it.
| test('has 2 options', () => { | ||
| const { getInput, getOptions } = renderTimeInputSelect({ | ||
| unit: 'AM', | ||
| onChange: () => {}, | ||
| }); | ||
|
|
||
| userEvent.click(getInput()); | ||
| expect(getOptions()).toHaveLength(2); | ||
| }); | ||
|
|
||
| test('has AM and PM options', () => { | ||
| const { getInput, getOptionByValue } = renderTimeInputSelect({ | ||
| unit: 'AM', | ||
| onChange: () => {}, | ||
| }); | ||
|
|
||
| userEvent.click(getInput()); | ||
| expect(getOptionByValue('AM')).toBeInTheDocument(); | ||
| expect(getOptionByValue('PM')).toBeInTheDocument(); | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these tests can be combined
…ean up tests by removing unused code
…db/leafygreen-ui into LG-5538/segments-parse-time
…ygreen-ui into LG-5532/segments-display-values
…orrect time input handling
| /** | ||
| * Determines if the input should show a select for the day period (AM/PM) | ||
| */ | ||
| const is12HourFormat = !!hasDayPeriod(providerValue.locale); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| const is12HourFormat = !!hasDayPeriod(providerValue.locale); | |
| const is12HourFormat = hasDayPeriod(providerValue.locale); |
| formatParts?: Array<Intl.DateTimeFormatPart>; | ||
|
|
||
| /** | ||
| * LGIDs for the code snippet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to update comment?
| 'aria-labelledby'?: string; | ||
|
|
||
| /** | ||
| * LGIDs for the code snippet. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to update comment?
| /** | ||
| * LGIDs for the code snippet. | ||
| */ | ||
| lgIds: GetLgIdsReturnType; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you remind me what this pattern is for? It seems odd to pass these down through props and can't remember why it's necessary
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is so that nested child components have access to the dynamic LGIDs.
| @@ -0,0 +1,5 @@ | |||
| import { FormFieldProps } from '@leafygreen-ui/form-field'; | |||
|
|
|||
| export type TimeFormFieldProps = React.ComponentPropsWithoutRef<'div'> & { | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be ComponentPropsWithRef?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If a component is wrapped in a forwardRef, you should use Will update!ComponentPropsWithoutRef because the ref shouldn't be included with the props. React gives you the ref as the second parameter in the forwardRef callback function.
| meta?: { | ||
| key?: (typeof keyMap)[keyof typeof keyMap]; | ||
| [key: string]: any; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this / what is it used for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added it early since I know I will be using it, but I'll move this to shared.types since it will be used in TimeInputBox for the onSegmentChange callback.
| import { TimeSegment, TimeSegmentsState } from '../shared.types'; | ||
|
|
||
| export interface TimeInputBoxProps | ||
| extends React.ComponentPropsWithoutRef<'div'> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ComponentPropsWithRef?
| test.todo( | ||
| 'should call onSegmentChange with the segment name and the value', | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason we can't do this now? imo we should avoid test.todo() because they tend to be left as todos or leave a ticket so it's returned to
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is one large PR that I've broken into 5 separate PRs, this has been added in the 5th PR.
|
|
||
| import { TimeInputBoxProps } from './TimeInputBox.types'; | ||
|
|
||
| export const TimeInputBox = React.forwardRef<HTMLDivElement, TimeInputBoxProps>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we axe the empty *.styles.ts file?
|
|
||
| import { TimeInputSegmentProps } from './TimeInputSegment.types'; | ||
|
|
||
| export const TimeInputSegment = React.forwardRef< |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can we axe the empty *.styles.ts file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I plan on adding some styles to this file in a future PR
…efault context values
…hRef for better ref handling
…ctions to utils for better organization
…ontainer for improved accessibility
| const { ariaLabelProp, ariaLabelledbyProp, is12HourFormat } = | ||
| useTimeInputDisplayContext(); | ||
|
|
||
| return ( | ||
| <FormFieldInputContainer | ||
| ref={fwdRef} | ||
| tabIndex={-1} | ||
| aria-label={ariaLabelProp} | ||
| aria-labelledby={ariaLabelledbyProp} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If i add this to FormField, then aria-label and aria-labelledby always show up on the form field parent container. This may be a bug in FormField because props that should only be included with inputProps are still passed to the main FormField container. I can open a separate PR outside of this integration branch to fix this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| return ( | ||
| <FormFieldInputContainer | ||
| ref={fwdRef} | ||
| tabIndex={-1} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it worth adding a comment about why we need to include this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think I need this, removing
| ? ariaLabelledbyProp | ||
| : undefined | ||
| } | ||
| onClick={onInputClick} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's difficult to provide feedback on this because it's unclear what it's getting used for. Is it possible to add some docs for what this is used for or add it when it gets used? If it's actually for the *InputContainer and not the input element, any reason not to use the native onClick attribute?
packages/time-input/src/constants.ts
Outdated
| /** | ||
| * The default placeholders for each segment | ||
| */ | ||
| export const defaultPlaceholder = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use plural for consistency with surrounding objects
| export const defaultPlaceholder = { | |
| export const defaultPlaceholders = { |
| meta?: { | ||
| key?: (typeof keyMap)[keyof typeof keyMap]; | ||
| [key: string]: any; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious do we actually need this or can we rely on native change events instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need this, but I will remove it from this PR since it isn't being used right now. I was optimizing too early.
| const { is12HourFormat, lgIds } = useTimeInputDisplayContext(); | ||
|
|
||
| return ( | ||
| <InputSegment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll create a ticket for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
…and enhance accessibility attributes in TimeFormField and TimeFormFieldInputContainer
…s for consistency in time input segment
|
Coverage after merging LG-5532/segments-display-values into shaneeza/segment-logic-integration will be
Coverage Report for Changed Files
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /** | ||
| * Dynamically generated LGIDs that will be used for the data-lgid and data-testid attributes in child components | ||
| */ | ||
| interface LgIds { | ||
| lgIds: GetLgIdsReturnType; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taking another look, I see this pattern here and elsewhere. Don't want to blow up scope of these changes, but with this many instances, it seems like a repeat pattern worth abstracting in some way
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll create a ticket for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
172eefe
into
shaneeza/segment-logic-integration


✍️ Proposed changes
🎟 Jira ticket: LG-5532
This PR is the second PR in a chain of PRs
This PR implements display functionality for time input segments, enabling users to view individual time parts (hour, minute, second) in a segmented input format. The ability to update segments is not included in this PR.
✅ Checklist
🧪 How to test changes