Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
09d117b
feat(input-box): enhance InputBox and InputSegment components with ne…
shaneeza Nov 6, 2025
35d975b
feat(input-box): add '@leafygreen-ui/a11y' as a dependency in pnpm-lo…
shaneeza Nov 6, 2025
2f81c18
fix(input-box): fix lint errors
shaneeza Nov 6, 2025
86fbca9
feat(input-box): set default size for InputBox in stories and refacto…
shaneeza Nov 6, 2025
7e6e4b4
feat(input-box): implement InputBoxContext and InputBoxProvider with …
shaneeza Nov 7, 2025
1c69f5d
remove segement files
shaneeza Nov 7, 2025
46746a5
Merge branch LG-5504/input-box-context of github.com:mongodb/leafygre…
shaneeza Nov 7, 2025
691bde9
feat(input-box): implement InputSegment component with styles, tests,…
shaneeza Nov 7, 2025
b2984f3
feat(input-box): add @leafygreen-ui/a11y dependency and update InputS…
shaneeza Nov 7, 2025
6be4fdf
Merge branch LG-5504/input-box-segment of github.com:mongodb/leafygre…
shaneeza Nov 7, 2025
b0d7bba
refactor(input-box): update createExplicitSegmentValidator tests to u…
shaneeza Nov 7, 2025
3986897
test(input-box): refactor InputBoxContext tests for improved readabil…
shaneeza Nov 7, 2025
2eda96e
refactor(input-box): update InputBoxContext types to extend SharedInp…
shaneeza Nov 10, 2025
fff0557
fix(input-box): correct comment formatting in testutils.mocks.ts for …
shaneeza Nov 10, 2025
959c5a1
feat(input-box): add InputSegment component for modular input handling
shaneeza Nov 10, 2025
e97d393
feat(input-box): add placeholder for InputSegment types
shaneeza Nov 10, 2025
0ab86c9
Merge branch 'LG-5504/input-box-context' of github.com:mongodb/leafyg…
shaneeza Nov 10, 2025
ad1f017
refactor(input-box): move InputSegmentChangeEventHandler import to sh…
shaneeza Nov 10, 2025
81a943c
refactor(input-box): rename min and max props to minSegmentValue and …
shaneeza Nov 10, 2025
8cfadbe
refactor(input-box): simplify placeholder logic in InputSegment stori…
shaneeza Nov 10, 2025
68fc653
refactor(input-box): update InputSegment styles to use dynamic theme …
shaneeza Nov 10, 2025
a04d5ec
feat(input-box): extend InputSegmentProps to include hours, minutes, …
shaneeza Nov 10, 2025
0101c32
refactor(input-box): rename onChange and onBlur props in InputSegment…
shaneeza Nov 10, 2025
662f2dd
refactor(input-box): rename shouldSkipValidation prop to shouldValida…
shaneeza Nov 10, 2025
967b33b
refactor(input-box): reorganize imports in testutils for better clari…
shaneeza Nov 11, 2025
a589e94
refactor(input-box): remove deprecated InputSegment types and update …
shaneeza Nov 11, 2025
4a03f0b
Merge branch 'LG-5504/input-box-context' of github.com:mongodb/leafyg…
shaneeza Nov 11, 2025
e8a3705
refactor(input-box): update InputSegmentChangeEventHandler import to …
shaneeza Nov 11, 2025
4cf138e
refactor(input-box): enhance InputSegment types and documentation, ad…
shaneeza Nov 11, 2025
a7062e2
refactor(input-box): streamline InputSegment exports by removing unus…
shaneeza Nov 11, 2025
dd132ea
test(input-box): add accessibility test for InputSegment to ensure no…
shaneeza Nov 11, 2025
0e9b9bd
refactor(input-box): update InputSegment to remove size prop and enha…
shaneeza Nov 12, 2025
5e73301
refactor(input-box): enhance InputSegment types by adding onChange an…
shaneeza Nov 12, 2025
0baa5dc
refactor(input-box): pull the latest from input-segment
shaneeza Nov 12, 2025
6db5451
refactor(input-box): update InputSegment types to extend from 'div' a…
shaneeza Nov 12, 2025
2d76c2c
Merge branch 'LG-5504/input-box-segment' of github.com:mongodb/leafyg…
shaneeza Nov 12, 2025
d4ec60d
refactor(input-box): simplify SharedInputBoxTypes by removing redunda…
shaneeza Nov 12, 2025
bf2eeda
refactor(input-box): remove InputBoxContext and related tests to stre…
shaneeza Nov 12, 2025
5c05dc1
refactor(input-box): remove InputBoxContext and related types to stre…
shaneeza Nov 12, 2025
904fb8c
refactor(input-box): simplify InputSegment types by removing Value ge…
shaneeza Nov 12, 2025
81e00e6
Merge branch 'LG-5504/input-box-segment' of github.com:mongodb/leafyg…
shaneeza Nov 12, 2025
3792f8b
refactor(input-box): update InputSegment and InputBox types to includ…
shaneeza Nov 12, 2025
7b1db76
refactor(input-box): update InputSegment types to include value prop …
shaneeza Nov 12, 2025
348faa3
Merge branch 'LG-5504/input-box-segment' of github.com:mongodb/leafyg…
shaneeza Nov 12, 2025
20da919
refactor(input-box): remove unused Size import from InputBox.spec.tsx…
shaneeza Nov 12, 2025
6942348
refactor(input-box): enhance InputBox and InputSegment documentation,…
shaneeza Nov 12, 2025
3fe8f0b
Merge branch 'shaneeza/time-picker-integration' of github.com:mongodb…
shaneeza Nov 13, 2025
2dc0134
testing
shaneeza Nov 13, 2025
b4dd84d
refactor(input-box): remove unused dependencies and update InputSegme…
shaneeza Nov 13, 2025
f2cfaa3
update lock file
shaneeza Nov 13, 2025
c269b96
testing
shaneeza Nov 13, 2025
73ea273
testing build
shaneeza Nov 13, 2025
a55bf24
testing build
shaneeza Nov 13, 2025
717daa7
Merge branch 'LG-5504/input-box-segment' of github.com:mongodb/leafyg…
shaneeza Nov 13, 2025
67d8f9f
test(input-segment): add test for resetting value with complete zeros…
shaneeza Nov 13, 2025
d7c1fc2
refactor(input-box): update separator literal styles to use new token…
shaneeza Nov 13, 2025
f52ed19
fix(input-segment): add missing line to check for number input handling
shaneeza Nov 13, 2025
94f2900
Merge branch shaneeza/time-picker-integration of github.com:mongodb/l…
shaneeza Nov 14, 2025
68e5f2c
refactor(input-segment): update comments and variable name for clarit…
shaneeza Nov 14, 2025
3896c9c
refactor(input-box): update comment to reflect correct component resp…
shaneeza Nov 14, 2025
410813e
refactor(input-segment): utilize isSingleDigit utility for digit inpu…
shaneeza Nov 14, 2025
ed31fdc
refactor(input-box): enhance documentation for InputBox component to …
shaneeza Nov 14, 2025
adaa3b6
refactor(input-box): integrate size prop into InputBox and InputSegme…
shaneeza Nov 14, 2025
a106f71
refactor(input-box): migrate Size import to shared.types for consiste…
shaneeza Nov 14, 2025
df546c1
refactor(input-box): enhance InputBox and InputSegment tests with seg…
shaneeza Nov 14, 2025
ea4d8b8
feat(input-box): add comprehensive mocks for date and time segments i…
shaneeza Nov 17, 2025
4d1030b
feat(input-box): integrate lodash for utility functions and enhance I…
shaneeza Nov 17, 2025
f342d2f
refactor(input-box): remove unused props from InputBox stories and te…
shaneeza Nov 17, 2025
f7f28eb
fix(input-box): ensure segments prop is required and handle error log…
shaneeza Nov 18, 2025
349b655
refactor(input-box): rename isSingleDigit to isSingleDigitKey and upd…
shaneeza Nov 19, 2025
bdabf2a
refactor(input-box): update README and InputSegment to use charsCount…
shaneeza Nov 19, 2025
c666bb7
refactor(input-box): update useSegmentRefs to accept segmentRefs and …
shaneeza Nov 19, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 129 additions & 3 deletions packages/input-box/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,130 @@
# Internal Input Box
# Input Box

An internal component intended to be used by any date or time component.
I.e. `DatePicker`, `TimeInput` etc.
![npm (scoped)](https://img.shields.io/npm/v/@leafygreen-ui/input-box.svg)

## Installation

### PNPM

```shell
pnpm add @leafygreen-ui/input-box
```

### Yarn

```shell
yarn add @leafygreen-ui/input-box
```

### NPM

```shell
npm install @leafygreen-ui/input-box
```

## Example

```tsx
import { InputBox, InputSegment } from '@leafygreen-ui/input-box';
import { Size } from '@leafygreen-ui/tokens';

// 1. Create a custom segment component
const MySegment = ({ segment, ...props }) => (
<InputSegment
segment={segment}
min={minValues[segment]}
max={maxValues[segment]}
{...props}
/>
);

// 2. Use InputBox with your segments
<InputBox
segments={{ day: '01', month: '02', year: '2025' }}
setSegment={(segment, value) => console.log(segment, value)}
segmentEnum={{ Day: 'day', Month: 'month', Year: 'year' }}
segmentComponent={MySegment}
formatParts={[
{ type: 'month', value: '02' },
{ type: 'literal', value: '/' },
{ type: 'day', value: '01' },
{ type: 'literal', value: '/' },
{ type: 'year', value: '2025' }
]}
charsPerSegment={{ day: 2, month: 2, year: 4 }}
segmentRefs={{ day: dayRef, month: monthRef, year: yearRef }}
segmentRules={{
day: { maxChars: 2, minExplicitValue: 1 },
month: { maxChars: 2, minExplicitValue: 4 },
year: { maxChars: 4, minExplicitValue: 1970 }
}}
disabled={false}
size={Size.Default}
/>
```

Refer to `DateInputBox` in the `@leafygreen-ui/date-picker` package for an implementation example.

## Overview

An internal component intended to be used by any date or time component, such as `DatePicker`, `TimeInput`, etc.

This package provides two main components that work together to create segmented input experiences.

### InputBox

A generic controlled input box component that renders an input with multiple segments separated by literals.

**Key Features:**

- **Auto-format**: Automatically pads segment values with leading zeros (based on `charsPerSegment`) when they become explicit/unambiguous. A value is explicit when it either: (1) reaches the maximum character length, or (2) meets or exceeds the `minExplicitValue` threshold (e.g., typing "5" for day → "05", but typing "2" stays "2" since it could be 20-29). Also formats on blur.
- **Auto-focus**: Automatically advances focus to the next segment when the current segment is complete
- **Keyboard navigation**: Handles left/right arrow key navigation between segments
- **Segment management**: Renders segments and separators based on `formatParts` (from `Intl.DateTimeFormat`)

The component handles high-level interactions like moving between segments, while delegating segment-specific logic to the `InputSegment` component. Internally, it uses `InputBoxContext` to share state and handlers across all segments.

#### Props

| Prop | Type | Description | Default |
| ------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `segments` | `Record<Segment, string>` | An object containing the values of the segments.<br/><br/>Example: `{ day: '01', month: '02', year: '2025' }` | |
| `setSegment` | `(segment: Segment, value: string) => void` | A function that sets the value of a segment.<br/><br/>Example: `(segment: 'day', value: '15') => void` | |
| `segmentEnum` | `Record<string, Segment>` | An enumerable object that maps the segment names to their values.<br/><br/>Example: `{ Day: 'day', Month: 'month', Year: 'year' }` | |
| `segmentComponent` | `React.ComponentType<InputSegmentComponentProps<Segment>>` | React component to render each segment (must accept `InputSegmentComponentProps`).<br/><br/>Example: `DateInputSegment` | |
| `formatParts` | `Array<Intl.DateTimeFormatPart>` | Array of `Intl.DateTimeFormatPart` defining segment order and separators.<br/><br/>Example:<br/>`[{ type: 'month', value: '02' },`<br/>`{ type: 'literal', value: '/' }, ...]` | |
| `charsPerSegment` | `Record<Segment, number>` | Record of maximum characters per segment.<br/><br/>Example: `{ day: 2, month: 2, year: 4 }` | |
| `segmentRefs` | `Record<Segment, ReturnType<DynamicRefGetter<HTMLInputElement>>>` | Record mapping segment names to their input refs.<br/><br/>Example: `{ day: dayRef, month: monthRef, year: yearRef }` | |
| `segmentRules` | `Record<Segment, ExplicitSegmentRule>` | Record of validation rules per segment with `maxChars` and `minExplicitValue`.<br/><br/>Example:<br/>`{ day: { maxChars: 2, minExplicitValue: 1 },`<br/>`month: { maxChars: 2, minExplicitValue: 4 }, ... }` | |
| `disabled` | `boolean` | Whether the input is disabled | |
| `size` | `Size` | Size of the input.<br/><br/>Example: `Size.Default`, `Size.Small`, or `Size.XSmall` | |
| `onSegmentChange` | `InputSegmentChangeEventHandler<Segment, string>` | Optional callback fired when any segment changes | |
| `labelledBy` | `string` | ID of the labelling element for accessibility.<br/><br/>Example: `'date-input-label'` | |

\+ other HTML `div` element props

### InputSegment

A controlled input segment component that renders a single input field within an `InputBox`.

**Key Features:**

- **Up/down arrow key navigation**: Increment/decrement segment values using arrow keys
- **Value validation**: Validates input against configurable min/max ranges
- **Auto-formatting**: Formats values with leading zeros based on character length
- **Rollover support**: Optionally rolls over values (e.g., 31 → 1 for days, or stops at boundaries)
- **Keyboard interaction**: Handles backspace and space keys to clear values
- **onChange/onBlur events**: Fires custom change events with segment metadata

#### Props

| Prop | Type | Description | Default |
| ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------- | ------- |
| `segment` | `string` | The segment identifier.<br/><br/>Example: `'day'`, `'month'`, or `'year'` | |
| `min` | `number` | Minimum valid value for the segment.<br/><br/>Example: `1` for day, `1` for month, `1900` for year | |
| `max` | `number` | Maximum valid value for the segment.<br/><br/>Example: `31` for day, `12` for month, `2100` for year | |
| `step` | `number` | Increment/decrement step for arrow keys | `1` |
| `shouldWrap` | `boolean` | Whether values should wrap around at min/max boundaries.<br/><br/>Example: `true` to wrap 31 → 1 for days | |
| `shouldSkipValidation` | `boolean` | Skips validation for segments that allow extended ranges | |

\+ native HTML `input` element props
1 change: 1 addition & 0 deletions packages/input-box/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"access": "public"
},
"dependencies": {
"@leafygreen-ui/a11y": "workspace:^",
"@leafygreen-ui/emotion": "workspace:^",
"@leafygreen-ui/lib": "workspace:^",
"@leafygreen-ui/hooks": "workspace:^",
Expand Down
63 changes: 63 additions & 0 deletions packages/input-box/src/InputBox.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';

Check failure on line 1 in packages/input-box/src/InputBox.stories.tsx

View workflow job for this annotation

GitHub Actions / Check lints

Run autofix to sort these imports!
import {
storybookExcludedControlParams,
StoryMetaType,
} from '@lg-tools/storybook-utils';
import { StoryFn } from '@storybook/react';

import { css } from '@leafygreen-ui/emotion';
import { palette } from '@leafygreen-ui/palette';

import { SegmentObjMock } from './testutils/testutils.mocks';
import { InputBox, InputBoxProps } from './InputBox';
import { InputBoxWithState } from './testutils';
import { Size } from '@leafygreen-ui/tokens';

const meta: StoryMetaType<typeof InputBox> = {
title: 'Components/Inputs/InputBox',
component: InputBox,
decorators: [
StoryFn => (
<div
className={css`
border: 1px solid ${palette.gray.base};
`}
>
<StoryFn />
</div>
),
],
parameters: {
default: 'LiveExample',
controls: {
exclude: [
...storybookExcludedControlParams,
'segments',
'segmentObj',
'segmentRefs',
'setSegment',
'charsPerSegment',
'formatParts',
'segmentRules',
'labelledBy',
'onSegmentChange',
'renderSegment',
'segmentComponent',
'segmentEnum',
],
},
},
argTypes: {
size: {
control: 'select',
options: Object.values(Size),
},
},
};
export default meta;

export const LiveExample: StoryFn<typeof InputBox> = props => {
return (
<InputBoxWithState {...(props as Partial<InputBoxProps<SegmentObjMock>>)} />
);
};
Comment on lines +94 to +98
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar to InputSegment, should we disable this snapshot and include a generated snapshot with some different combos?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I won't block on it, but I think new stories pair well with their related component code similar to when adding specs with their related code

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I missed this comment, but I added it!

Loading
Loading