Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
7161a95
refactor(input): replace errorMessage with helperText and add status …
aammami-ledger Apr 22, 2026
1b50aa0
feat(Input): update feedback API to use helperText and status for Rea…
aammami-ledger Apr 22, 2026
c44cdc6
refactor(inputs): standardize error handling to use helperText and st…
aammami-ledger Apr 23, 2026
988328c
fix typo
aammami-ledger Apr 23, 2026
5b915e2
refactor(TextInput.test): simplify renderWithProvider function and re…
aammami-ledger Apr 27, 2026
99aa601
fix(AmountInput.stories): format error message display for better rea…
aammami-ledger Apr 27, 2026
927d0ee
feat(TextInput): add support for label and placeholder together, enha…
aammami-ledger Apr 27, 2026
62af655
refactor(inputs): standardize error handling by removing aria-invalid…
aammami-ledger Apr 27, 2026
21cd970
fix typo
aammami-ledger Apr 27, 2026
e5d15b4
feat(inputs): auto-set aria-invalid on error status for improved acce…
aammami-ledger Apr 27, 2026
c0281da
refactor(inputs): update documentation to clarify auto-setting of ari…
aammami-ledger Apr 27, 2026
7181b5b
fix docs for better readability
aammami-ledger Apr 27, 2026
a47c80b
feat(TextInput): add status prop for error and success states to enha…
aammami-ledger Apr 28, 2026
f472e28
review comments
aammami-ledger Apr 29, 2026
e97f4c4
fix(BaseInput): apply typography styles to enhance input appearance
aammami-ledger Apr 29, 2026
ac81200
feat(BaseInput): add floated label variant for improved placeholder b…
aammami-ledger Apr 30, 2026
df5c167
refactor(BaseInput): simplify helper text rendering and consolidate s…
aammami-ledger Apr 30, 2026
d053d88
refactor(BaseInput): streamline helper text condition for improved cl…
aammami-ledger Apr 30, 2026
9bc97c3
feat(AddressInput, TextInput): enhance status handling by adding dyna…
aammami-ledger Apr 30, 2026
181469d
Update version plan guidelines to enforce `patch` bump type
aammami-ledger May 5, 2026
92d8742
fix(MediaImage): wrap onError calls in act to ensure proper handling …
aammami-ledger May 6, 2026
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
11 changes: 4 additions & 7 deletions .cursor/commands/open-pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,19 @@ git diff main...HEAD --name-only -- .nx/version-plans/
- Files under `libs/design-core/` → `'@ledgerhq/lumen-design-core'`
- Files under `libs/utils-shared/` → `'@ledgerhq/lumen-utils-shared'`

Pick the bump type based on the change:
- `patch` — bug fixes, small tweaks
- `minor` — new features, new components, new props
- `major` — breaking changes
Always use `patch` as the bump type — never `minor`, never `major` — regardless of the change (feature, fix, breaking change, etc.). See `.cursor/rules/shared/release-plan.mdc`.

Write the file as `.nx/version-plans/version-plan-<timestamp>.md` with this format:
Create **one file per affected package** — never group multiple packages in the same file. If N packages are affected, create N files named `.nx/version-plans/version-plan-<timestamp>-<pkg>.md`, each with a single-package frontmatter:

```markdown
---
'@ledgerhq/lumen-ui-rnative': minor
'@ledgerhq/lumen-ui-rnative': patch
---

feat(Select): add render prop and SelectButtonTrigger
```

The description line should match the PR title / commit message style. If multiple packages are affected, list them all in the frontmatter.
The description line should match the PR title / commit message style.

## Step 2: Create a commit if needed

Expand Down
63 changes: 63 additions & 0 deletions .cursor/rules/shared/release-plan.mdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
---
description: Nx version plans must always use `patch` bump type, regardless of change
globs:
alwaysApply: true
---

# Release plan bump type

When generating an Nx version plan in `.nx/version-plans/`, the bump type
in the frontmatter MUST always be `patch` — never `minor`, never `major` —
regardless of the nature of the change (new feature, new component, new prop,
breaking change, refactor, fix, etc.).

```markdown
---
'@ledgerhq/lumen-ui-rnative': patch
---

feat(Select): add render prop and SelectButtonTrigger
```

- Do not infer `minor` from `feat(...)` commits.
- Do not infer `major` from breaking changes.

## One package per file

Each release plan file MUST list a single package in its frontmatter — never
group multiple packages in the same file. If a change affects N packages,
create N separate `version-plan-<timestamp>-<pkg>.md` files, one per package.

```markdown
---
'@ledgerhq/lumen-ui-react': patch
---

feat(Select): add render prop and SelectButtonTrigger
```

```markdown
---
'@ledgerhq/lumen-ui-rnative': patch
---

feat(Select): add render prop and SelectButtonTrigger
```

Do NOT do this:

```markdown
---
'@ledgerhq/lumen-ui-react': patch
'@ledgerhq/lumen-ui-rnative': patch
---
```

This applies to all packages, including but not limited to:

- `@ledgerhq/lumen-ui-react`
- `@ledgerhq/lumen-ui-rnative`
- `@ledgerhq/lumen-ui-react-visualization`
- `@ledgerhq/lumen-ui-rnative-visualization`
- `@ledgerhq/lumen-design-core`
- `@ledgerhq/lumen-utils-shared`
58 changes: 58 additions & 0 deletions .nx/version-plans/version-plan-1776837601000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
'@ledgerhq/lumen-ui-react': patch
Comment thread
aammami-ledger marked this conversation as resolved.
---
Comment thread
aammami-ledger marked this conversation as resolved.

feat(Input): replace `errorMessage` with `helperText` and `status` across react inputs

## Breaking change
Comment thread
aammami-ledger marked this conversation as resolved.

The input feedback API changed across React input components.

- Removed: `errorMessage`
- Added: `helperText`
- Added: `status` with `error | success`
Comment thread
aammami-ledger marked this conversation as resolved.

This affects `TextInput`, `SearchInput`, `AddressInput`, `BaseInput`, and `SelectSearch`.

## Migration guide

### Error feedback

```diff
- <TextInput errorMessage="Please enter a valid email address" />
+ <TextInput
+ helperText="Please enter a valid email address"
+ status="error"
+ />
```

### Error feedback — `aria-invalid` is now auto-set

`aria-invalid={true}` is automatically set on the input when `status="error"`. You no longer need to pass it explicitly.

```diff
- <SearchInput
- aria-invalid={!isValid}
- errorMessage="Search failed. Please try again."
- />
+ <SearchInput
+ helperText="Search failed. Please try again."
+ status="error"
+ />
```

## New addition:

### Neutral helper copy

```diff
- <AddressInput errorMessage="Enter a valid address" />
+ <AddressInput helperText="Enter a valid address" />
```

### Success feedback

```diff
- <TextInput helperText="Address verified" />
+ <TextInput helperText="Address verified" status="success" />
```
53 changes: 53 additions & 0 deletions .nx/version-plans/version-plan-1776837602000.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
---
'@ledgerhq/lumen-ui-rnative': patch
---

feat(Input): replace `errorMessage` with `helperText` and `status` across native inputs

## Breaking change

The input feedback API changed across React Native input components.

- Removed: `errorMessage`
- Added: `helperText`
- Added: `status` with `error | success`

This affects `TextInput`, `SearchInput`, `AddressInput`, and `BaseInput`.

## Migration guide

### Error feedback

```diff
- <TextInput errorMessage="Username must be at least 3 characters" />
+ <TextInput
+ helperText="Username must be at least 3 characters"
+ status="error"
+ />
```

### Search validation feedback

```diff
- <SearchInput errorMessage="Search term is invalid" />
+ <SearchInput
+ helperText="Search term is invalid"
+ status="error"
+ />
```

## New addition:

### Neutral helper copy

```diff
- <AddressInput errorMessage="Enter address or ENS" />
+ <AddressInput helperText="Enter address or ENS" />
```

### Success feedback

```diff
- <TextInput helperText="Address verified" />
+ <TextInput helperText="Address verified" status="success" />
```
16 changes: 15 additions & 1 deletion apps/app-sandbox-rnative/src/app/blocks/TextInputs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export function TextInputs() {
])
}
/>
<TextInput label='Email' placeholder='name@example.com' />
<TextInput
label='Password'
secureTextEntry={!showPassword}
Expand All @@ -50,11 +51,24 @@ export function TextInputs() {
label='Team'
value={team}
onChangeText={setTeam}
errorMessage={
helperText={
!isTeamValid && team !== undefined
? 'Team must match "lumen"!'
: undefined
}
status={!isTeamValid && team !== undefined ? 'error' : undefined}
/>

<TextInput
label='Team'
value={team}
status={isTeamValid && team !== undefined ? 'success' : undefined}
onChangeText={setTeam}
helperText={
isTeamValid && team !== undefined
? 'Team matches "lumen"!'
: undefined
}
/>
<TextInput
label='A very long label that should really be truncated at different breakpoints'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,11 @@ figma.connect(
disabled: figma.enum('state', {
disabled: true,
}),
errorMessage: figma.nestedProps('.status', {
status: figma.enum('state', {
error: 'error',
success: 'success',
}),
helperText: figma.nestedProps('.status', {
label: figma.string('label'),
}),
},
Expand All @@ -28,7 +32,8 @@ figma.connect(
value={props.value}
placeholder={props.placeholder}
onQrCodeClick={() => openQrScanner}
errorMessage={props.errorMessage.label}
helperText={props.helperText.label}
status={props.status}
/>
),
},
Expand Down
24 changes: 12 additions & 12 deletions libs/ui-react/src/lib/Components/AddressInput/AddressInput.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ AddressInput is designed specifically where users need to enter wallet addresses
- **Customizable prefix label** - defaults to "To:" but can be overridden via prefix prop
- **Conditional QR code scanner** - Appears only when onQrCodeClick handler is provided, using consistent InteractiveIcon styling
- **Smart clear button** - Appears automatically when content is entered
- **Error handling** - Supports error states and error messages
- **Error handling** - Supports validation feedback via `helperText` and `status`

<Canvas of={AddressInputStories.Default} />

Expand Down Expand Up @@ -178,8 +178,8 @@ React.useEffect(() => {
<AddressInput
value={address}
onChange={(e) => setAddress(e.target.value)}
errorMessage={error}
aria-invalid={!!error}
helperText={error || undefined}
status={error ? 'error' : undefined}
/>;
```

Expand Down Expand Up @@ -256,8 +256,8 @@ const handleQrScan = () => {
placeholder='Enter address or ENS'
value={recipientAddress}
onChange={(e) => setRecipientAddress(e.target.value)}
errorMessage={addressError}
aria-invalid={!!addressError}
helperText={addressError || undefined}
status={addressError ? 'error' : undefined}
className='max-w-md'
/>
{/* Other transaction fields */}
Expand Down Expand Up @@ -448,8 +448,8 @@ The component includes comprehensive test coverage for:

<DoVsDontRow>
<DoBlockItem
title='Use aria-invalid for errors'
description='Use aria-invalid to control the error state styling'
title='Use status for errors'
description='status drives visual styling and automatically sets aria-invalid on the input for accessibility'
>

{/* prettier-ignore */}
Expand All @@ -458,15 +458,15 @@ The component includes comprehensive test coverage for:
placeholder='Enter address or ENS'
value={address}
onChange={handleChange}
aria-invalid={hasError}
errorMessage={hasError ? 'Invalid address' : undefined}
helperText={hasError ? 'Invalid address' : undefined}
status={hasError ? 'error' : undefined}
/>
```

</DoBlockItem>
<DontBlockItem
title="Don't omit aria-invalid"
description='Always provide aria-invalid when showing error messages for proper accessibility'
title="Don't rely on aria-invalid for styling"
description='aria-invalid is for screen readers only — it no longer drives the error ring on the container'
>

{/* prettier-ignore */}
Expand All @@ -475,7 +475,7 @@ The component includes comprehensive test coverage for:
placeholder='Enter address or ENS'
value={address}
onChange={handleChange}
errorMessage={hasError ? 'Invalid address' : undefined}
aria-invalid={hasError}
/>
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ export const Error: Story = {
args: {
placeholder: 'Enter address or ENS',
defaultValue: 'invalid-address-format',
errorMessage: 'Invalid address format',
'aria-invalid': true,
helperText: 'Invalid address format',
status: 'error',
className: 'max-w-md',
onQrCodeClick: () => console.log('QR code clicked!'),
},
Expand All @@ -125,8 +125,8 @@ export const Error: Story = {
code: `<AddressInput
placeholder="Enter address or ENS"
defaultValue="invalid-address-format"
errorMessage="Invalid address format"
aria-invalid={true}
helperText="Invalid address format"
status="error"
className="max-w-md"
/>`,
},
Expand Down Expand Up @@ -178,8 +178,8 @@ export const Controlled: Story = {
setError(''); // Clear error state
console.log('Address cleared');
}}
errorMessage={error}
aria-invalid={!!error}
helperText={error || undefined}
status={error ? 'error' : undefined}
className='max-w-md'
/>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,17 +184,17 @@ describe('AddressInput', () => {
expect(handleClear).toHaveBeenCalled();
});

it('displays error message when provided', () => {
it('displays helper text when provided with error status', () => {
render(
<AddressInput
placeholder='Enter address or ENS'
errorMessage='Invalid address format'
aria-invalid={true}
helperText='Invalid address format'
status='error'
/>,
);

const errorMessage = screen.getByText('Invalid address format');
expect(errorMessage).toBeInTheDocument();
const helperTextEl = screen.getByText('Invalid address format');
expect(helperTextEl).toBeInTheDocument();

// The role="alert" is on the error container, not the text span
const errorContainer = screen.getByRole('alert');
Expand Down
Loading
Loading