@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.
Files changed (59) hide show
  1. package/dist/module/lib/Components/AddressInput/AddressInput.mdx +10 -12
  2. package/dist/module/lib/Components/AddressInput/AddressInput.stories.js +2 -1
  3. package/dist/module/lib/Components/AddressInput/AddressInput.stories.js.map +1 -1
  4. package/dist/module/lib/Components/AmountInput/AmountInput.mdx +3 -3
  5. package/dist/module/lib/Components/BaseInput/BaseInput.js +60 -32
  6. package/dist/module/lib/Components/BaseInput/BaseInput.js.map +1 -1
  7. package/dist/module/lib/Components/DotCount/DotCount.stories.js +9 -18
  8. package/dist/module/lib/Components/DotCount/DotCount.stories.js.map +1 -1
  9. package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js +3 -5
  10. package/dist/module/lib/Components/DotIndicator/DotIndicator.stories.js.map +1 -1
  11. package/dist/module/lib/Components/MediaImage/MediaImage.test.js +7 -3
  12. package/dist/module/lib/Components/MediaImage/MediaImage.test.js.map +1 -1
  13. package/dist/module/lib/Components/SearchInput/SearchInput.mdx +4 -10
  14. package/dist/module/lib/Components/SearchInput/SearchInput.stories.js +2 -1
  15. package/dist/module/lib/Components/SearchInput/SearchInput.stories.js.map +1 -1
  16. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js +2 -1
  17. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js.map +1 -1
  18. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
  19. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js +32 -7
  20. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js.map +1 -1
  21. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js +26 -0
  22. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js.map +1 -1
  23. package/dist/module/lib/Components/TextInput/TextInput.js +4 -3
  24. package/dist/module/lib/Components/TextInput/TextInput.js.map +1 -1
  25. package/dist/module/lib/Components/TextInput/TextInput.mdx +8 -10
  26. package/dist/module/lib/Components/TextInput/TextInput.stories.js +40 -1
  27. package/dist/module/lib/Components/TextInput/TextInput.stories.js.map +1 -1
  28. package/dist/module/lib/Components/TextInput/TextInput.test.js +76 -0
  29. package/dist/module/lib/Components/TextInput/TextInput.test.js.map +1 -0
  30. package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts +1 -1
  31. package/dist/typescript/src/lib/Components/BaseInput/BaseInput.d.ts.map +1 -1
  32. package/dist/typescript/src/lib/Components/BaseInput/types.d.ts +9 -2
  33. package/dist/typescript/src/lib/Components/BaseInput/types.d.ts.map +1 -1
  34. package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts +1 -1
  35. package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  36. package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts +4 -0
  37. package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts.map +1 -1
  38. package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts +4 -3
  39. package/dist/typescript/src/lib/Components/TextInput/TextInput.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/lib/Components/AddressInput/AddressInput.mdx +10 -12
  42. package/src/lib/Components/AddressInput/AddressInput.stories.tsx +2 -1
  43. package/src/lib/Components/AmountInput/AmountInput.mdx +3 -3
  44. package/src/lib/Components/BaseInput/BaseInput.tsx +61 -29
  45. package/src/lib/Components/BaseInput/types.ts +10 -2
  46. package/src/lib/Components/DotCount/DotCount.stories.tsx +12 -10
  47. package/src/lib/Components/DotIndicator/DotIndicator.stories.tsx +1 -3
  48. package/src/lib/Components/MediaImage/MediaImage.test.tsx +7 -3
  49. package/src/lib/Components/SearchInput/SearchInput.mdx +4 -10
  50. package/src/lib/Components/SearchInput/SearchInput.stories.tsx +2 -1
  51. package/src/lib/Components/SegmentedControl/SegmentedControl.mdx +34 -4
  52. package/src/lib/Components/SegmentedControl/SegmentedControl.stories.tsx +34 -6
  53. package/src/lib/Components/SegmentedControl/SegmentedControl.test.tsx +27 -0
  54. package/src/lib/Components/SegmentedControl/SegmentedControl.tsx +2 -0
  55. package/src/lib/Components/SegmentedControl/types.ts +4 -0
  56. package/src/lib/Components/TextInput/TextInput.mdx +8 -10
  57. package/src/lib/Components/TextInput/TextInput.stories.tsx +41 -1
  58. package/src/lib/Components/TextInput/TextInput.test.tsx +90 -0
  59. 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, Settings } from '../../Symbols';
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='activity' icon={TransferHorizontal}>
78
- Activity
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 `errorMessage` which displays an error message below the input with error styling including a red border and text color.
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
- errorMessage={error}
221
+ helperText={error || undefined}
222
+ status={error ? 'error' : undefined}
225
223
  />
226
224
  );
227
225
  }
228
226
  ```
229
227
 
230
- > **Note:** Error messages are optional. Use `errorMessage` to display an error message below the input.
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
- errorMessage: 'Username must be at least 3 characters',
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
- * - **Error state styling** with errorMessage support
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 state
31
+ * // Input with error helper
32
32
  * <TextInput
33
33
  * label="Email"
34
34
  * value={email}
35
35
  * onChangeText={setEmail}
36
- * errorMessage="Please enter a valid email address"
36
+ * helperText="Please enter a valid email address"
37
+ * status="error"
37
38
  * />
38
39
  *
39
40
  * // Input with suffix element