-
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
Changes from 32 commits
bf86e07
612b474
a578ecb
804cd88
1db42af
206e145
bb3916d
df69d22
2f5707f
9715407
dad7b62
f559593
3e6f6be
1ef4c97
87023fb
580c90f
2495aa1
4cad730
1afe57e
cb84996
6e265a7
2547fdc
10d4039
e46a73c
94ed98f
7e42f34
2d2fbd5
d9b99ec
f07a1b8
ed87ee7
e18c444
a361ba7
aa4e7a2
d38ee94
d57c109
aff3376
9e343ff
346cb74
31dba0b
388339e
cea6a1d
8abc708
4458f6f
5455489
237c4ee
617ebb7
a736467
c9751da
7ccb116
2b4acae
574a23a
96b9ebd
e032c8b
a04fca7
379f871
4a62b19
e88a937
a714dc8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,10 @@ import React, { | |
| useState, | ||
| } from 'react'; | ||
| import defaults from 'lodash/defaults'; | ||
| import defaultTo from 'lodash/defaultTo'; | ||
|
|
||
| import { hasDayPeriod } from '../../utils'; | ||
| import { getFormatParts } from '../../utils/getFormatParts/getFormatParts'; | ||
|
|
||
| import { | ||
| TimeInputDisplayContextProps, | ||
|
|
@@ -37,7 +41,27 @@ export const TimeInputDisplayProvider = ({ | |
| ...defaults(rest, defaultTimeInputDisplayContext), | ||
| }; | ||
|
|
||
| // TODO: min, max helpers | ||
| /** | ||
| * Determines if the input should show a select for the day period (AM/PM) | ||
| */ | ||
| const is12HourFormat = !!hasDayPeriod(providerValue.locale); | ||
|
|
||
| /** | ||
| * Only used to track the presentation format of the segments, not the value itself | ||
| */ | ||
| const formatParts = getFormatParts({ | ||
| showSeconds: providerValue.showSeconds, | ||
| }); | ||
|
|
||
| /** | ||
| * Gets the time zone from the provider value or the browser's default | ||
| */ | ||
| const timeZone = defaultTo( | ||
| providerValue.timeZone, | ||
| Intl.DateTimeFormat().resolvedOptions().timeZone, | ||
| ); | ||
|
|
||
| // TODO: min, max helpers will be in a future PR | ||
|
||
|
|
||
| return ( | ||
| <TimeInputDisplayContext.Provider | ||
|
|
@@ -48,6 +72,9 @@ export const TimeInputDisplayProvider = ({ | |
| ariaLabelledbyProp, | ||
| isDirty, | ||
| setIsDirty, | ||
| is12HourFormat, | ||
| formatParts, | ||
| timeZone, | ||
| }} | ||
| > | ||
| {children} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import { AriaLabelPropsWithLabel } from '@leafygreen-ui/a11y'; | |
| import { DarkModeProps } from '@leafygreen-ui/lib'; | ||
|
|
||
| import { DisplayTimeInputProps } from '../../TimeInput/TimeInput.types'; | ||
| import { GetLgIdsReturnType } from '../../utils/getLgIds'; | ||
|
|
||
| type AriaLabelKeys = keyof AriaLabelPropsWithLabel; | ||
| type AriaLabelKeysWithoutLabel = Exclude<AriaLabelKeys, 'label'>; | ||
|
|
@@ -38,6 +39,24 @@ export type TimeInputDisplayContextProps = Omit< | |
| * Setter for whether the input has been interacted with | ||
| */ | ||
| setIsDirty: React.Dispatch<React.SetStateAction<boolean>>; | ||
|
|
||
| /** | ||
| * Whether the time input is in 12-hour format. Helps determine if the AM/PM select should be shown. | ||
| * | ||
| * @default false | ||
| */ | ||
| is12HourFormat: boolean; | ||
|
|
||
| /** | ||
| * An array of {@link Intl.DateTimeFormatPart}, | ||
| * used to determine the order of segments in the input | ||
| */ | ||
| formatParts?: Array<Intl.DateTimeFormatPart>; | ||
|
|
||
| /** | ||
| * LGIDs for the code snippet. | ||
|
||
| */ | ||
| lgIds: GetLgIdsReturnType; | ||
|
||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -62,4 +81,9 @@ export type TimeInputDisplayProviderProps = Omit< | |
| * The aria-labelledby prop | ||
| */ | ||
| 'aria-labelledby'?: string; | ||
|
|
||
| /** | ||
| * LGIDs for the code snippet. | ||
|
||
| */ | ||
| lgIds?: GetLgIdsReturnType; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| import React from 'react'; | ||
|
|
||
| import { FormField } from '@leafygreen-ui/form-field'; | ||
|
|
||
| import { useTimeInputDisplayContext } from '../../Context/TimeInputDisplayContext'; | ||
|
|
||
| import { TimeFormFieldProps } from './TimeFormField.types'; | ||
|
|
||
| /** | ||
| * A wrapper around `FormField` that sets the relevant | ||
| * attributes, and styling | ||
| */ | ||
| export const TimeFormField = React.forwardRef< | ||
| HTMLDivElement, | ||
| TimeFormFieldProps | ||
| >(({ children, ...rest }: TimeFormFieldProps, fwdRef) => { | ||
| const { | ||
| label, | ||
| description, | ||
| // stateNotification: { state, message: errorMessage }, | ||
|
||
| disabled, | ||
| size, | ||
| } = useTimeInputDisplayContext(); | ||
|
|
||
| return ( | ||
| <FormField | ||
| label={label} | ||
| description={description} | ||
| disabled={disabled} | ||
| // state={state} | ||
| // errorMessage={errorMessage} | ||
| size={size} | ||
| ref={fwdRef} | ||
| {...rest} | ||
| > | ||
| {children} | ||
| </FormField> | ||
| ); | ||
| }); | ||
|
|
||
| TimeFormField.displayName = 'TimeFormField'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { FormFieldProps } from '@leafygreen-ui/form-field'; | ||
|
|
||
| export type TimeFormFieldProps = React.ComponentPropsWithoutRef<'div'> & { | ||
|
||
| children: FormFieldProps['children']; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import { css, cx } from '@leafygreen-ui/emotion'; | ||
|
|
||
| const selectStyles = css` | ||
| border-top-right-radius: 0; | ||
| border-bottom-right-radius: 0; | ||
| `; | ||
|
|
||
| const baseStyles = css` | ||
| &:hover, | ||
| &:focus-within { | ||
| z-index: 1; | ||
| } | ||
| `; | ||
|
|
||
| export const getContainerStyles = ({ | ||
| is12HourFormat, | ||
| }: { | ||
| is12HourFormat: boolean; | ||
| }) => | ||
| cx(baseStyles, { | ||
| [selectStyles]: is12HourFormat, | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. might be worth adding a comment on why we're removing border radius for 12hr format
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. since this is a pattern that exists in
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that is a good idea, but I would want to spend some more time thinking of a good pattern to support removing border-radius on the left and right sides of the input. Since I'm on a tight deadline, I can create a ticket to look into this. |
||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import React from 'react'; | ||
|
|
||
| import { FormFieldInputContainer } from '@leafygreen-ui/form-field'; | ||
|
|
||
| import { useTimeInputDisplayContext } from '../../Context/TimeInputDisplayContext'; | ||
|
|
||
| import { getContainerStyles } from './TimeFormFieldInputContainer.styles'; | ||
| import { TimeFormFieldInputContainerProps } from './TimeFormFieldInputContainer.types'; | ||
|
|
||
| /** | ||
| * A wrapper around `FormField` that sets the relevant | ||
| * attributes, and styling | ||
| */ | ||
| export const TimeFormFieldInputContainer = React.forwardRef< | ||
| HTMLDivElement, | ||
| TimeFormFieldInputContainerProps | ||
| >(({ children, onInputClick }: TimeFormFieldInputContainerProps, fwdRef) => { | ||
| const { label, ariaLabelProp, ariaLabelledbyProp, is12HourFormat } = | ||
| useTimeInputDisplayContext(); | ||
|
|
||
| return ( | ||
| <FormFieldInputContainer | ||
| ref={fwdRef} | ||
| role="combobox" | ||
|
||
| tabIndex={-1} | ||
|
||
| aria-label={!label && ariaLabelProp ? ariaLabelProp : undefined} | ||
| aria-labelledby={ | ||
| !label && !ariaLabelProp && ariaLabelledbyProp | ||
| ? ariaLabelledbyProp | ||
| : undefined | ||
| } | ||
|
||
| onClick={onInputClick} | ||
|
||
| className={getContainerStyles({ is12HourFormat })} | ||
| > | ||
| {children} | ||
| </FormFieldInputContainer> | ||
| ); | ||
| }); | ||
|
|
||
| TimeFormFieldInputContainer.displayName = 'TimeFormFieldInputContainer'; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { FormFieldProps } from '@leafygreen-ui/form-field'; | ||
|
|
||
| export type TimeFormFieldInputContainerProps = | ||
| React.ComponentPropsWithoutRef<'div'> & { | ||
|
||
| children: FormFieldProps['children']; | ||
| onInputClick?: React.MouseEventHandler<HTMLDivElement>; | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { TimeFormField } from './TimeFormField/TimeFormField'; | ||
| export { TimeFormFieldInputContainer } from './TimeFormFieldInputContainer/TimeFormFieldInputContainer'; | ||
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,19 +1,63 @@ | ||
| import React from 'react'; | ||
| import { type StoryMetaType } from '@lg-tools/storybook-utils'; | ||
| import React, { useState } from 'react'; | ||
| import { | ||
| storybookArgTypes, | ||
| type StoryMetaType, | ||
| } from '@lg-tools/storybook-utils'; | ||
| import { StoryFn } from '@storybook/react'; | ||
|
|
||
| import { DateType, SupportedLocales } from '@leafygreen-ui/date-utils'; | ||
|
|
||
| import { Size } from './TimeInput/TimeInput.types'; | ||
| import { TimeInput } from '.'; | ||
|
|
||
| const meta: StoryMetaType<typeof TimeInput> = { | ||
| title: 'Components/Inputs/TimeInput', | ||
| component: TimeInput, | ||
| parameters: { | ||
| default: 'LiveExample', | ||
| controls: { | ||
| exclude: [ | ||
| 'handleValidation', | ||
| 'initialValue', | ||
| 'onChange', | ||
| 'onDateChange', | ||
| 'onSegmentChange', | ||
| 'value', | ||
| 'onTimeChange', | ||
| 'data-lgid', | ||
| 'data-testid', | ||
| ], | ||
| }, | ||
| }, | ||
| args: { | ||
| showSeconds: true, | ||
| locale: SupportedLocales.ISO_8601, | ||
| timeZone: 'UTC', | ||
| label: 'Time Input', | ||
| darkMode: false, | ||
| size: Size.Default, | ||
| }, | ||
| argTypes: { | ||
| locale: { control: 'select', options: Object.values(SupportedLocales) }, | ||
| timeZone: { | ||
| control: 'select', | ||
| options: [undefined, 'UTC', 'America/New_York', 'Europe/London'], | ||
| }, | ||
| darkMode: storybookArgTypes.darkMode, | ||
| size: { control: 'select', options: Object.values(Size) }, | ||
| }, | ||
| }; | ||
|
|
||
| export default meta; | ||
|
|
||
| const Template: StoryFn<typeof TimeInput> = props => <TimeInput {...props} />; | ||
| const Template: StoryFn<typeof TimeInput> = props => { | ||
| const [value, setValue] = useState<DateType | undefined>( | ||
| new Date('1990-02-20T14:30:50Z'), | ||
| ); | ||
|
|
||
| return ( | ||
| <TimeInput {...props} value={value} onTimeChange={time => setValue(time)} /> | ||
| ); | ||
| }; | ||
|
|
||
| export const LiveExample = Template.bind({}); |
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.