@ledgerhq/lumen-ui-rnative 0.1.31 → 0.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/module/lib/Components/AddressInput/AddressInput.mdx +10 -12
- package/dist/module/lib/Components/AddressInput/AddressInput.stories.js +2 -1
- package/dist/module/lib/Components/AddressInput/AddressInput.stories.js.map +1 -1
- package/dist/module/lib/Components/AmountInput/AmountInput.mdx +3 -3
- package/dist/module/lib/Components/BaseInput/BaseInput.js +60 -32
- package/dist/module/lib/Components/BaseInput/BaseInput.js.map +1 -1
- package/dist/module/lib/Components/DotCount/DotCount.stories.js +9 -18
- package/dist/module/lib/Components/DotCount/DotCount.stories.js.map +1 -1
- package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js +3 -5
- package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js.map +1 -1
- package/dist/module/lib/Components/MediaImage/MediaImage.test.js +7 -3
- package/dist/module/lib/Components/MediaImage/MediaImage.test.js.map +1 -1
- package/dist/module/lib/Components/SearchInput/SearchInput.mdx +4 -10
- package/dist/module/lib/Components/SearchInput/SearchInput.stories.js +2 -1
- package/dist/module/lib/Components/SearchInput/SearchInput.stories.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js +2 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js +32 -7
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js.map +1 -1
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js +26 -0
- package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js.map +1 -1
- package/dist/module/lib/Components/TextInput/TextInput.js +4 -3
- package/dist/module/lib/Components/TextInput/TextInput.js.map +1 -1
- package/dist/module/lib/Components/TextInput/TextInput.mdx +8 -10
- package/dist/module/lib/Components/TextInput/TextInput.stories.js +40 -1
- package/dist/module/lib/Components/TextInput/TextInput.stories.js.map +1 -1
- package/dist/module/lib/Components/TextInput/TextInput.test.js +76 -0
- package/dist/module/lib/Components/TextInput/TextInput.test.js.map +1 -0
- package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts +1 -1
- package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/BaseInput/types.d.ts +9 -2
- package/dist/typescript/src/lib/Components/BaseInput/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts +4 -0
- package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts +4 -3
- package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/Components/AddressInput/AddressInput.mdx +10 -12
- package/src/lib/Components/AddressInput/AddressInput.stories.tsx +2 -1
- package/src/lib/Components/AmountInput/AmountInput.mdx +3 -3
- package/src/lib/Components/BaseInput/BaseInput.tsx +61 -29
- package/src/lib/Components/BaseInput/types.ts +10 -2
- package/src/lib/Components/DotCount/DotCount.stories.tsx +12 -10
- package/src/lib/Components/DotIndicator/DotIndicator.stories.tsx +1 -3
- package/src/lib/Components/MediaImage/MediaImage.test.tsx +7 -3
- package/src/lib/Components/SearchInput/SearchInput.mdx +4 -10
- package/src/lib/Components/SearchInput/SearchInput.stories.tsx +2 -1
- package/src/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
- package/src/lib/Components/SegmentedControl/SegmentedControl.stories.tsx +34 -6
- package/src/lib/Components/SegmentedControl/SegmentedControl.test.tsx +27 -0
- package/src/lib/Components/SegmentedControl/SegmentedControl.tsx +2 -0
- package/src/lib/Components/SegmentedControl/types.ts +4 -0
- package/src/lib/Components/TextInput/TextInput.mdx +8 -10
- package/src/lib/Components/TextInput/TextInput.stories.tsx +41 -1
- package/src/lib/Components/TextInput/TextInput.test.tsx +90 -0
- package/src/lib/Components/TextInput/TextInput.tsx +4 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
2
|
import { useState } from 'react';
|
|
3
|
-
import { Coins, Nft, TransferHorizontal
|
|
3
|
+
import { Coins, Nft, TransferHorizontal } from '../../Symbols';
|
|
4
|
+
import { DotCount } from '../DotCount';
|
|
4
5
|
import { Box } from '../Utility';
|
|
5
6
|
import { SegmentedControl, SegmentedControlButton } from './SegmentedControl';
|
|
6
7
|
|
|
@@ -74,11 +75,8 @@ export const WithIcons: Story = {
|
|
|
74
75
|
<SegmentedControlButton value='nfts' icon={Nft}>
|
|
75
76
|
NFTs
|
|
76
77
|
</SegmentedControlButton>
|
|
77
|
-
<SegmentedControlButton value='
|
|
78
|
-
|
|
79
|
-
</SegmentedControlButton>
|
|
80
|
-
<SegmentedControlButton value='settings' icon={Settings}>
|
|
81
|
-
Settings
|
|
78
|
+
<SegmentedControlButton value='trade' icon={TransferHorizontal}>
|
|
79
|
+
Trade
|
|
82
80
|
</SegmentedControlButton>
|
|
83
81
|
</SegmentedControl>
|
|
84
82
|
);
|
|
@@ -142,3 +140,33 @@ export const Disabled: Story = {
|
|
|
142
140
|
</SegmentedControl>
|
|
143
141
|
),
|
|
144
142
|
};
|
|
143
|
+
|
|
144
|
+
export const WithTrailingContent: Story = {
|
|
145
|
+
args: {} as React.ComponentProps<typeof SegmentedControl>,
|
|
146
|
+
render: (args) => {
|
|
147
|
+
const [state, setState] = useState('tokens');
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<SegmentedControl
|
|
151
|
+
{...args}
|
|
152
|
+
selectedValue={state}
|
|
153
|
+
onSelectedChange={setState}
|
|
154
|
+
accessibilityLabel='Asset section'
|
|
155
|
+
>
|
|
156
|
+
<SegmentedControlButton
|
|
157
|
+
value='tokens'
|
|
158
|
+
trailingContent={<DotCount value={3} />}
|
|
159
|
+
>
|
|
160
|
+
Tokens
|
|
161
|
+
</SegmentedControlButton>
|
|
162
|
+
<SegmentedControlButton
|
|
163
|
+
value='nfts'
|
|
164
|
+
trailingContent={<DotCount value={12} />}
|
|
165
|
+
>
|
|
166
|
+
NFTs
|
|
167
|
+
</SegmentedControlButton>
|
|
168
|
+
<SegmentedControlButton value='trade'>Trade</SegmentedControlButton>
|
|
169
|
+
</SegmentedControl>
|
|
170
|
+
);
|
|
171
|
+
},
|
|
172
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { describe, it, expect, jest } from '@jest/globals';
|
|
2
2
|
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
3
|
import { render, fireEvent } from '@testing-library/react-native';
|
|
4
|
+
import { DotCount } from '../DotCount';
|
|
4
5
|
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
5
6
|
import { SegmentedControl, SegmentedControlButton } from './SegmentedControl';
|
|
6
7
|
|
|
@@ -53,4 +54,30 @@ describe('SegmentedControl', () => {
|
|
|
53
54
|
|
|
54
55
|
expect(onSelectedChange).toHaveBeenCalledWith('receive');
|
|
55
56
|
});
|
|
57
|
+
|
|
58
|
+
it('renders trailingContent inside segment buttons', () => {
|
|
59
|
+
const { getByLabelText } = render(
|
|
60
|
+
<TestWrapper>
|
|
61
|
+
<SegmentedControl
|
|
62
|
+
selectedValue='tokens'
|
|
63
|
+
onSelectedChange={() => {
|
|
64
|
+
/* empty */
|
|
65
|
+
}}
|
|
66
|
+
accessibilityLabel='Asset section'
|
|
67
|
+
>
|
|
68
|
+
<SegmentedControlButton
|
|
69
|
+
value='tokens'
|
|
70
|
+
trailingContent={
|
|
71
|
+
<DotCount value={3} accessibilityLabel='3 tokens' />
|
|
72
|
+
}
|
|
73
|
+
>
|
|
74
|
+
Tokens
|
|
75
|
+
</SegmentedControlButton>
|
|
76
|
+
<SegmentedControlButton value='nfts'>NFTs</SegmentedControlButton>
|
|
77
|
+
</SegmentedControl>
|
|
78
|
+
</TestWrapper>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
expect(getByLabelText('3 tokens')).toBeTruthy();
|
|
82
|
+
});
|
|
56
83
|
});
|
|
@@ -21,6 +21,7 @@ export function SegmentedControlButton({
|
|
|
21
21
|
value,
|
|
22
22
|
children,
|
|
23
23
|
icon: Icon,
|
|
24
|
+
trailingContent,
|
|
24
25
|
onPress,
|
|
25
26
|
...props
|
|
26
27
|
}: SegmentedControlButtonProps) {
|
|
@@ -71,6 +72,7 @@ export function SegmentedControlButton({
|
|
|
71
72
|
>
|
|
72
73
|
{children}
|
|
73
74
|
</Text>
|
|
75
|
+
{trailingContent}
|
|
74
76
|
</Box>
|
|
75
77
|
</Pressable>
|
|
76
78
|
);
|
|
@@ -56,6 +56,10 @@ export type SegmentedControlButtonProps = {
|
|
|
56
56
|
* Optional icon shown to the left of the label (from Symbols).
|
|
57
57
|
*/
|
|
58
58
|
icon?: IconComponent;
|
|
59
|
+
/**
|
|
60
|
+
* Optional content shown to the right of the label (e.g. DotCount badge).
|
|
61
|
+
*/
|
|
62
|
+
trailingContent?: ReactNode;
|
|
59
63
|
/**
|
|
60
64
|
* Optional callback when the button is pressed (in addition to onSelectedChange on the parent).
|
|
61
65
|
*/
|
|
@@ -34,6 +34,10 @@ The label text automatically floats above the input when content is entered, pro
|
|
|
34
34
|
|
|
35
35
|
<Canvas of={TextInputStories.WithContent} />
|
|
36
36
|
|
|
37
|
+
### Label and placeholder
|
|
38
|
+
|
|
39
|
+
<Canvas of={TextInputStories.WithLabelAndPlaceholder} />
|
|
40
|
+
|
|
37
41
|
### Clear Button
|
|
38
42
|
|
|
39
43
|
A clear button (×) appears **automatically** when input has content.
|
|
@@ -54,17 +58,10 @@ Use `onClear` to extend the default clear behavior with custom logic.
|
|
|
54
58
|
|
|
55
59
|
### Error State
|
|
56
60
|
|
|
57
|
-
The input supports error handling through `
|
|
61
|
+
The input supports error handling through `helperText` and `status` (`'error'` \| `'success'`), which show copy below the input with matching border and text styling.
|
|
58
62
|
|
|
59
63
|
<Canvas of={TextInputStories.WithError} />
|
|
60
64
|
|
|
61
|
-
The error message will be automatically:
|
|
62
|
-
|
|
63
|
-
- Connected to the input
|
|
64
|
-
- Displayed with a warning icon
|
|
65
|
-
- Styled in the error color
|
|
66
|
-
- Announced by screen readers
|
|
67
|
-
|
|
68
65
|
### Disabled State
|
|
69
66
|
|
|
70
67
|
The input can be fully disabled using the `disabled` prop, which prevents interaction and applies a muted visual style.
|
|
@@ -221,13 +218,14 @@ function MyComponent() {
|
|
|
221
218
|
label='Username'
|
|
222
219
|
value={value}
|
|
223
220
|
onChangeText={handleChange}
|
|
224
|
-
|
|
221
|
+
helperText={error || undefined}
|
|
222
|
+
status={error ? 'error' : undefined}
|
|
225
223
|
/>
|
|
226
224
|
);
|
|
227
225
|
}
|
|
228
226
|
```
|
|
229
227
|
|
|
230
|
-
> **Note:**
|
|
228
|
+
> **Note:** Helper text is optional. Use `helperText` with `status="error"` to show validation feedback below the input.
|
|
231
229
|
|
|
232
230
|
### With Custom Styling
|
|
233
231
|
|
|
@@ -66,11 +66,51 @@ export const WithContent: Story = {
|
|
|
66
66
|
},
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
+
export const WithLabelAndPlaceholder: Story = {
|
|
70
|
+
render: (args) => <TextInputStory {...args} />,
|
|
71
|
+
args: {
|
|
72
|
+
label: 'Phone',
|
|
73
|
+
placeholder: '+1 (555) 000-0000',
|
|
74
|
+
editable: true,
|
|
75
|
+
hideClearButton: false,
|
|
76
|
+
keyboardType: 'phone-pad',
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
69
80
|
export const WithError: Story = {
|
|
70
81
|
render: (args) => <TextInputStory {...args} initialValue='ab' />,
|
|
71
82
|
args: {
|
|
72
83
|
label: 'Username',
|
|
73
|
-
|
|
84
|
+
helperText: 'Username must be at least 3 characters',
|
|
85
|
+
status: 'error',
|
|
86
|
+
editable: true,
|
|
87
|
+
hideClearButton: false,
|
|
88
|
+
keyboardType: 'default',
|
|
89
|
+
},
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
export const WithSuccess: Story = {
|
|
93
|
+
render: (args) => (
|
|
94
|
+
<TextInputStory
|
|
95
|
+
{...args}
|
|
96
|
+
initialValue='0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb27'
|
|
97
|
+
/>
|
|
98
|
+
),
|
|
99
|
+
args: {
|
|
100
|
+
label: 'Address',
|
|
101
|
+
helperText: 'Address verified',
|
|
102
|
+
status: 'success',
|
|
103
|
+
editable: true,
|
|
104
|
+
hideClearButton: false,
|
|
105
|
+
keyboardType: 'default',
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export const WithNeutralHint: Story = {
|
|
110
|
+
render: (args) => <TextInputStory {...args} />,
|
|
111
|
+
args: {
|
|
112
|
+
label: 'Address',
|
|
113
|
+
helperText: 'Enter your ETH address',
|
|
74
114
|
editable: true,
|
|
75
115
|
hideClearButton: false,
|
|
76
116
|
keyboardType: 'default',
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { describe, expect, it } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import { render, screen } from '@testing-library/react-native';
|
|
4
|
+
import type { ReactElement } from 'react';
|
|
5
|
+
import { CheckmarkCircleFill } from '../../Symbols/Icons/CheckmarkCircleFill';
|
|
6
|
+
import { DeleteCircleFill } from '../../Symbols/Icons/DeleteCircleFill';
|
|
7
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
8
|
+
import { TextInput } from './TextInput';
|
|
9
|
+
|
|
10
|
+
const { colors } = ledgerLiveThemes.dark;
|
|
11
|
+
|
|
12
|
+
const renderWithProvider = (
|
|
13
|
+
component: ReactElement,
|
|
14
|
+
): ReturnType<typeof render> => {
|
|
15
|
+
return render(
|
|
16
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
17
|
+
{component}
|
|
18
|
+
</ThemeProvider>,
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
describe('TextInput', () => {
|
|
23
|
+
describe('Placeholder and label (BaseInput parity with web)', () => {
|
|
24
|
+
it('uses the provided placeholder when label and placeholder are both set', async () => {
|
|
25
|
+
await renderWithProvider(
|
|
26
|
+
<TextInput
|
|
27
|
+
label='Username'
|
|
28
|
+
placeholder='jane.doe'
|
|
29
|
+
value=''
|
|
30
|
+
onChangeText={() => {}}
|
|
31
|
+
/>,
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
expect(screen.getByPlaceholderText('jane.doe')).toBeTruthy();
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
describe('Helper text', () => {
|
|
39
|
+
it('renders neutral helper text without feedback icons', () => {
|
|
40
|
+
renderWithProvider(
|
|
41
|
+
<TextInput label='Address' helperText='Enter your ETH address' />,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
const helperText = screen.getByText('Enter your ETH address');
|
|
45
|
+
|
|
46
|
+
expect(helperText).toBeTruthy();
|
|
47
|
+
expect(helperText.props.style).toEqual(
|
|
48
|
+
expect.objectContaining({ color: colors.text.muted }),
|
|
49
|
+
);
|
|
50
|
+
expect(screen.UNSAFE_queryByType(DeleteCircleFill)).toBeNull();
|
|
51
|
+
expect(screen.UNSAFE_queryByType(CheckmarkCircleFill)).toBeNull();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('renders error helper text with an error icon', () => {
|
|
55
|
+
renderWithProvider(
|
|
56
|
+
<TextInput
|
|
57
|
+
label='Address'
|
|
58
|
+
helperText='Invalid address format'
|
|
59
|
+
status='error'
|
|
60
|
+
/>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
const helperText = screen.getByText('Invalid address format');
|
|
64
|
+
|
|
65
|
+
expect(helperText.props.style).toEqual(
|
|
66
|
+
expect.objectContaining({ color: colors.text.error }),
|
|
67
|
+
);
|
|
68
|
+
expect(screen.UNSAFE_getByType(DeleteCircleFill)).toBeTruthy();
|
|
69
|
+
expect(screen.UNSAFE_queryByType(CheckmarkCircleFill)).toBeNull();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('renders success helper text with a success icon', () => {
|
|
73
|
+
renderWithProvider(
|
|
74
|
+
<TextInput
|
|
75
|
+
label='Address'
|
|
76
|
+
helperText='Address verified'
|
|
77
|
+
status='success'
|
|
78
|
+
/>,
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const helperText = screen.getByText('Address verified');
|
|
82
|
+
|
|
83
|
+
expect(helperText.props.style).toEqual(
|
|
84
|
+
expect.objectContaining({ color: colors.text.success }),
|
|
85
|
+
);
|
|
86
|
+
expect(screen.UNSAFE_getByType(CheckmarkCircleFill)).toBeTruthy();
|
|
87
|
+
expect(screen.UNSAFE_queryByType(DeleteCircleFill)).toBeNull();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
});
|
|
@@ -8,7 +8,7 @@ import { type TextInputProps } from './types';
|
|
|
8
8
|
* - **Automatic clear button** appears when input has content
|
|
9
9
|
* - **Floating label** with smooth animations
|
|
10
10
|
* - **Suffix elements** for icons, buttons, or custom content
|
|
11
|
-
* - **
|
|
11
|
+
* - **Helper text** with optional `status` (`error` | `success`) for border and helper feedback styling
|
|
12
12
|
* - **Container-based spacing** with padding and gap for clean layout
|
|
13
13
|
* - **Flexible styling** via style props
|
|
14
14
|
* - **React Native TextInput** with proper mobile behavior
|
|
@@ -28,12 +28,13 @@ import { type TextInputProps } from './types';
|
|
|
28
28
|
* // Basic input with automatic clear button
|
|
29
29
|
* <TextInput label="Title" value={title} onChangeText={setTitle} />
|
|
30
30
|
*
|
|
31
|
-
* // Input with error
|
|
31
|
+
* // Input with error helper
|
|
32
32
|
* <TextInput
|
|
33
33
|
* label="Email"
|
|
34
34
|
* value={email}
|
|
35
35
|
* onChangeText={setEmail}
|
|
36
|
-
*
|
|
36
|
+
* helperText="Please enter a valid email address"
|
|
37
|
+
* status="error"
|
|
37
38
|
* />
|
|
38
39
|
*
|
|
39
40
|
* // Input with suffix element
|