@ledgerhq/lumen-ui-rnative 0.1.47 → 0.1.48

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 (77) hide show
  1. package/dist/module/lib/Components/AmountInput/AmountInput.js +3 -1
  2. package/dist/module/lib/Components/AmountInput/AmountInput.js.map +1 -1
  3. package/dist/module/lib/Components/AmountInput/AmountInput.stories.js +15 -0
  4. package/dist/module/lib/Components/AmountInput/AmountInput.stories.js.map +1 -1
  5. package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.js +1 -1
  6. package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.js.map +1 -1
  7. package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.js +20 -1
  8. package/dist/module/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.js.map +1 -1
  9. package/dist/module/lib/Components/OptionList/OptionList.js +16 -1
  10. package/dist/module/lib/Components/OptionList/OptionList.js.map +1 -1
  11. package/dist/module/lib/Components/OptionList/OptionList.mdx +45 -0
  12. package/dist/module/lib/Components/OptionList/OptionList.stories.js +61 -1
  13. package/dist/module/lib/Components/OptionList/OptionList.stories.js.map +1 -1
  14. package/dist/module/lib/Components/OptionList/OptionList.test.js +43 -1
  15. package/dist/module/lib/Components/OptionList/OptionList.test.js.map +1 -1
  16. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.js.map +1 -1
  17. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js +7 -1
  18. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.js.map +1 -1
  19. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.mdx +28 -0
  20. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js +24 -1
  21. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.stories.js.map +1 -1
  22. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js +28 -1
  23. package/dist/module/lib/Components/SegmentedControl/SegmentedControl.test.js.map +1 -1
  24. package/dist/module/lib/Components/TabBar/TabBar.js +7 -4
  25. package/dist/module/lib/Components/TabBar/TabBar.js.map +1 -1
  26. package/dist/module/lib/Components/TabBar/TabBar.mdx +29 -0
  27. package/dist/module/lib/Components/TabBar/TabBar.stories.js +35 -1
  28. package/dist/module/lib/Components/TabBar/TabBar.stories.js.map +1 -1
  29. package/dist/module/lib/Components/TabBar/TabBar.test.js +28 -1
  30. package/dist/module/lib/Components/TabBar/TabBar.test.js.map +1 -1
  31. package/dist/module/lib/Components/TabBar/index.js +2 -1
  32. package/dist/module/lib/Components/TabBar/index.js.map +1 -1
  33. package/dist/typescript/src/lib/Components/AmountInput/AmountInput.d.ts +1 -1
  34. package/dist/typescript/src/lib/Components/AmountInput/AmountInput.d.ts.map +1 -1
  35. package/dist/typescript/src/lib/Components/AmountInput/types.d.ts +8 -0
  36. package/dist/typescript/src/lib/Components/AmountInput/types.d.ts.map +1 -1
  37. package/dist/typescript/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.d.ts +1 -0
  38. package/dist/typescript/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.d.ts.map +1 -1
  39. package/dist/typescript/src/lib/Components/OptionList/OptionList.d.ts +18 -4
  40. package/dist/typescript/src/lib/Components/OptionList/OptionList.d.ts.map +1 -1
  41. package/dist/typescript/src/lib/Components/OptionList/types.d.ts +16 -15
  42. package/dist/typescript/src/lib/Components/OptionList/types.d.ts.map +1 -1
  43. package/dist/typescript/src/lib/Components/OptionList/useOptionList/useOptionListItems.d.ts +9 -9
  44. package/dist/typescript/src/lib/Components/OptionList/useOptionList/useOptionListItems.d.ts.map +1 -1
  45. package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts +8 -3
  46. package/dist/typescript/src/lib/Components/SegmentedControl/SegmentedControl.d.ts.map +1 -1
  47. package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts +6 -5
  48. package/dist/typescript/src/lib/Components/SegmentedControl/types.d.ts.map +1 -1
  49. package/dist/typescript/src/lib/Components/TabBar/TabBar.d.ts +7 -2
  50. package/dist/typescript/src/lib/Components/TabBar/TabBar.d.ts.map +1 -1
  51. package/dist/typescript/src/lib/Components/TabBar/index.d.ts +2 -2
  52. package/dist/typescript/src/lib/Components/TabBar/index.d.ts.map +1 -1
  53. package/dist/typescript/src/lib/Components/TabBar/types.d.ts +17 -16
  54. package/dist/typescript/src/lib/Components/TabBar/types.d.ts.map +1 -1
  55. package/package.json +3 -3
  56. package/src/lib/Components/AmountInput/AmountInput.stories.tsx +14 -0
  57. package/src/lib/Components/AmountInput/AmountInput.tsx +2 -0
  58. package/src/lib/Components/AmountInput/types.ts +8 -0
  59. package/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.test.ts +20 -0
  60. package/src/lib/Components/AmountInput/useAmountInputFormatting/useAmountInputFormatting.ts +2 -0
  61. package/src/lib/Components/OptionList/OptionList.mdx +45 -0
  62. package/src/lib/Components/OptionList/OptionList.stories.tsx +63 -0
  63. package/src/lib/Components/OptionList/OptionList.test.tsx +40 -0
  64. package/src/lib/Components/OptionList/OptionList.tsx +57 -10
  65. package/src/lib/Components/OptionList/types.ts +34 -15
  66. package/src/lib/Components/OptionList/useOptionList/useOptionListItems.ts +28 -13
  67. package/src/lib/Components/SegmentedControl/SegmentedControl.mdx +28 -0
  68. package/src/lib/Components/SegmentedControl/SegmentedControl.stories.tsx +34 -1
  69. package/src/lib/Components/SegmentedControl/SegmentedControl.test.tsx +32 -1
  70. package/src/lib/Components/SegmentedControl/SegmentedControl.tsx +22 -5
  71. package/src/lib/Components/SegmentedControl/types.ts +11 -5
  72. package/src/lib/Components/TabBar/TabBar.mdx +29 -0
  73. package/src/lib/Components/TabBar/TabBar.stories.tsx +34 -1
  74. package/src/lib/Components/TabBar/TabBar.test.tsx +23 -1
  75. package/src/lib/Components/TabBar/TabBar.tsx +17 -9
  76. package/src/lib/Components/TabBar/index.ts +2 -2
  77. package/src/lib/Components/TabBar/types.ts +19 -17
@@ -1,4 +1,5 @@
1
1
  import { useDisabledContext } from '@ledgerhq/lumen-utils-shared';
2
+ import type { ReactElement } from 'react';
2
3
  import type { LayoutChangeEvent } from 'react-native';
3
4
  import Animated from 'react-native-reanimated';
4
5
  import { useStyleSheet } from '../../../styles';
@@ -11,20 +12,23 @@ import {
11
12
  import type {
12
13
  SegmentedControlButtonProps,
13
14
  SegmentedControlProps,
15
+ SegmentedControlValue,
14
16
  } from './types';
15
17
  import {
16
18
  usePillLayout,
17
19
  useSegmentedControlSelectedIndex,
18
20
  } from './usePillLayout';
19
21
 
20
- export function SegmentedControlButton({
22
+ export function SegmentedControlButton<
23
+ T extends SegmentedControlValue = SegmentedControlValue,
24
+ >({
21
25
  value,
22
26
  children,
23
27
  icon: Icon,
24
28
  trailingContent,
25
29
  onPress,
26
30
  ...props
27
- }: SegmentedControlButtonProps) {
31
+ }: SegmentedControlButtonProps<T>) {
28
32
  const {
29
33
  selectedValue,
30
34
  onSelectedChange,
@@ -126,7 +130,9 @@ function useButtonStyles({
126
130
  return { ...styles, typography, textColor };
127
131
  }
128
132
 
129
- export function SegmentedControl({
133
+ export function SegmentedControl<
134
+ T extends SegmentedControlValue = SegmentedControlValue,
135
+ >({
130
136
  selectedValue,
131
137
  onSelectedChange,
132
138
  accessibilityLabel,
@@ -135,7 +141,7 @@ export function SegmentedControl({
135
141
  appearance = 'background',
136
142
  tabLayout = 'fixed',
137
143
  ...props
138
- }: SegmentedControlProps) {
144
+ }: SegmentedControlProps<T>) {
139
145
  const disabled = useDisabledContext({
140
146
  consumerName: 'SegmentedControl',
141
147
  mergeWith: { disabled: disabledProp },
@@ -160,7 +166,7 @@ export function SegmentedControl({
160
166
  <SegmentedControlContextProvider
161
167
  value={{
162
168
  selectedValue,
163
- onSelectedChange,
169
+ onSelectedChange: onSelectedChange as (value: string) => void,
164
170
  disabled,
165
171
  tabLayout,
166
172
  registerButtonLayout,
@@ -220,3 +226,14 @@ function useRootStyles({
220
226
  [disabled, appearance, tabLayout],
221
227
  );
222
228
  }
229
+
230
+ export function createSegmentedControl<
231
+ T extends SegmentedControlValue = never,
232
+ >(): {
233
+ SegmentedControl: (props: SegmentedControlProps<T>) => ReactElement;
234
+ SegmentedControlButton: (
235
+ props: SegmentedControlButtonProps<T>,
236
+ ) => ReactElement;
237
+ } {
238
+ return { SegmentedControl, SegmentedControlButton };
239
+ }
@@ -3,15 +3,19 @@ import type { LumenTextStyle, StyledPressableProps } from '../../../styles';
3
3
  import type { IconSize } from '../Icon';
4
4
  import type { BoxProps } from '../Utility';
5
5
 
6
- export type SegmentedControlProps = {
6
+ export type SegmentedControlValue = string;
7
+
8
+ export type SegmentedControlProps<
9
+ T extends SegmentedControlValue = SegmentedControlValue,
10
+ > = {
7
11
  /**
8
12
  * The value of the currently selected segment (drives the sliding pill).
9
13
  */
10
- selectedValue: string;
14
+ selectedValue: T;
11
15
  /**
12
16
  * Callback when the selected segment value changes.
13
17
  */
14
- onSelectedChange: (value: string) => void;
18
+ onSelectedChange: (value: T) => void;
15
19
  /**
16
20
  * When true, the entire control is disabled (no interaction, selected uses muted styling).
17
21
  */
@@ -43,11 +47,13 @@ type IconComponent = ComponentType<{
43
47
  color?: LumenTextStyle['color'];
44
48
  }>;
45
49
 
46
- export type SegmentedControlButtonProps = {
50
+ export type SegmentedControlButtonProps<
51
+ T extends SegmentedControlValue = SegmentedControlValue,
52
+ > = {
47
53
  /**
48
54
  * Value for this segment (must be unique among siblings).
49
55
  */
50
- value: string;
56
+ value: T;
51
57
  /**
52
58
  * Button label (e.g. "Preview", "Raw").
53
59
  */
@@ -69,6 +69,8 @@ Install and set up the library with our [Setup Guide →](?path=/docs/getting-st
69
69
 
70
70
  ### Basic Usage
71
71
 
72
+ > **Recommended:** prefer the [type-safe variant](#type-safe-variant) below. It shares one literal union across `active` and every `TabBarItem.value`.
73
+
72
74
  ```tsx
73
75
  import { useState } from 'react';
74
76
  import { TabBar, TabBarItem } from '@ledgerhq/lumen-ui-rnative';
@@ -86,6 +88,33 @@ function MyComponent() {
86
88
  }
87
89
  ```
88
90
 
91
+ ### Type-safe variant
92
+
93
+ `TabBar` and `TabBarItem` communicate via context, so TypeScript cannot connect the root's `active` to each item's `value` on its own. A typo compiles, and the wrong tab (or none) is highlighted.
94
+
95
+ `createTabBar<T>()` ties both components to one literal union. Call it once at module scope; the returned pair are typed against your `T`.
96
+
97
+ ```tsx
98
+ import { createTabBar } from '@ledgerhq/lumen-ui-rnative';
99
+ import { Home, HomeFill, Settings } from '@ledgerhq/lumen-ui-rnative/symbols';
100
+
101
+ type Route = 'home' | 'swap' | 'settings';
102
+
103
+ const { TabBar, TabBarItem } = createTabBar<Route>();
104
+
105
+ function Example() {
106
+ const [active, setActive] = React.useState<Route>('home');
107
+
108
+ return (
109
+ <TabBar active={active} onTabPress={setActive}>
110
+ <TabBarItem value='home' label='Home' icon={Home} activeIcon={HomeFill} />
111
+ <TabBarItem value='swap' label='Swap' icon={Settings} />
112
+ <TabBarItem value='settings' label='Settings' icon={Settings} />
113
+ </TabBar>
114
+ );
115
+ }
116
+ ```
117
+
89
118
  ### With Active Icons
90
119
 
91
120
  Provide different icons for active and inactive states:
@@ -15,7 +15,7 @@ import {
15
15
  CreditCard,
16
16
  CreditCardFill,
17
17
  } from '../../Symbols';
18
- import { TabBar, TabBarItem } from './TabBar';
18
+ import { createTabBar, TabBar, TabBarItem } from './TabBar';
19
19
 
20
20
  const meta = {
21
21
  id: 'rnative-tabbar',
@@ -119,3 +119,36 @@ export const MissingLabel: Story = {
119
119
  );
120
120
  },
121
121
  };
122
+
123
+ type Route = 'home' | 'swap' | 'card' | 'help';
124
+ const Nav = createTabBar<Route>();
125
+
126
+ export const TypesafeFactory: Story = {
127
+ args: {} as React.ComponentProps<typeof TabBar>,
128
+ render: () => {
129
+ const [active, setActive] = useState<Route>('home');
130
+ return (
131
+ <Nav.TabBar active={active} onTabPress={setActive} lx={{ width: 's320' }}>
132
+ <Nav.TabBarItem
133
+ value='home'
134
+ label='Home'
135
+ icon={Home}
136
+ activeIcon={HomeFill}
137
+ />
138
+ <Nav.TabBarItem value='swap' label='Swap' icon={CoinPercent} />
139
+ <Nav.TabBarItem
140
+ value='card'
141
+ label='Card'
142
+ icon={CreditCard}
143
+ activeIcon={CreditCardFill}
144
+ />
145
+ <Nav.TabBarItem
146
+ value='help'
147
+ label='Help'
148
+ icon={LifeRing}
149
+ activeIcon={LifeRingFill}
150
+ />
151
+ </Nav.TabBar>
152
+ );
153
+ },
154
+ };
@@ -3,7 +3,7 @@ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
3
  import { render, fireEvent } from '@testing-library/react-native';
4
4
  import { HomeFill, Settings, BasketPutIn } from '../../Symbols';
5
5
  import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
6
- import { TabBar, TabBarItem } from './TabBar';
6
+ import { TabBar, TabBarItem, createTabBar } from './TabBar';
7
7
 
8
8
  const renderWithProvider = (component: React.ReactElement) => {
9
9
  return render(
@@ -54,4 +54,26 @@ describe('TabBar', () => {
54
54
  expect(getByText('Shop')).toBeTruthy();
55
55
  expect(getByText('Settings')).toBeTruthy();
56
56
  });
57
+
58
+ describe('createTabBar', () => {
59
+ it('returns typed components that render and respond to presses', () => {
60
+ const { TabBar: TypedTabBar, TabBarItem: TypedItem } = createTabBar<
61
+ 'home' | 'settings'
62
+ >();
63
+ const onTabPress = jest.fn();
64
+
65
+ const { getByText } = renderWithProvider(
66
+ <TypedTabBar active='home' onTabPress={onTabPress}>
67
+ <TypedItem value='home' label='Home' icon={HomeFill} />
68
+ <TypedItem value='settings' label='Settings' icon={Settings} />
69
+ </TypedTabBar>,
70
+ );
71
+
72
+ expect(getByText('Home')).toBeTruthy();
73
+
74
+ fireEvent.press(getByText('Settings'));
75
+
76
+ expect(onTabPress).toHaveBeenCalledWith('settings');
77
+ });
78
+ });
57
79
  });
@@ -1,5 +1,5 @@
1
1
  import { BlurView } from '@sbaiahmed1/react-native-blur';
2
- import type { ReactNode } from 'react';
2
+ import type { ReactElement, ReactNode } from 'react';
3
3
  import { Children, isValidElement, useEffect, useMemo, useRef } from 'react';
4
4
  import type { LayoutChangeEvent } from 'react-native';
5
5
  import { StyleSheet, Text, View } from 'react-native';
@@ -18,7 +18,7 @@ import { Placeholder } from '../../Symbols';
18
18
  import { RuntimeConstants } from '../../utils';
19
19
  import { Box, Pressable } from '../Utility';
20
20
  import { TabBarContextProvider, useTabBarContext } from './TabBarContext';
21
- import type { TabBarItemProps, TabBarProps } from './types';
21
+ import type { TabBarItemProps, TabBarProps, TabBarValue } from './types';
22
22
 
23
23
  export const TAB_BAR_HEIGHT = 60;
24
24
  const PILL_INSET = 4;
@@ -241,12 +241,12 @@ export function TabBarItem({
241
241
  * <TabBarItem value="settings" label="Settings" icon={Settings} />
242
242
  * </TabBar>
243
243
  */
244
- export function TabBar({
244
+ export function TabBar<T extends TabBarValue = TabBarValue>({
245
245
  active,
246
246
  onTabPress,
247
247
  children,
248
248
  ...props
249
- }: TabBarProps) {
249
+ }: TabBarProps<T>) {
250
250
  const styles = useStyles();
251
251
  const { theme, colorScheme } = useTheme();
252
252
 
@@ -255,12 +255,13 @@ export function TabBar({
255
255
  children,
256
256
  });
257
257
 
258
- function handleTabPress(value: string) {
259
- onTabPress?.(value);
260
- }
261
-
262
258
  return (
263
- <TabBarContextProvider value={{ active, onTabPress: handleTabPress }}>
259
+ <TabBarContextProvider
260
+ value={{
261
+ active,
262
+ onTabPress: (value) => onTabPress(value as T),
263
+ }}
264
+ >
264
265
  <Box
265
266
  style={styles.container}
266
267
  onLayout={onLayout}
@@ -348,3 +349,10 @@ const useStyles = () =>
348
349
  }),
349
350
  [],
350
351
  );
352
+
353
+ export function createTabBar<T extends TabBarValue = never>(): {
354
+ TabBar: (props: TabBarProps<T>) => ReactElement;
355
+ TabBarItem: (props: TabBarItemProps<T>) => ReactElement;
356
+ } {
357
+ return { TabBar, TabBarItem };
358
+ }
@@ -1,2 +1,2 @@
1
- export { TabBar, TabBarItem, TAB_BAR_HEIGHT } from './TabBar';
2
- export type { TabBarProps, TabBarItemProps } from './types';
1
+ export * from './TabBar';
2
+ export * from './types';
@@ -7,11 +7,28 @@ type IconComponent = ComponentType<{
7
7
  size?: IconSize;
8
8
  }>;
9
9
 
10
- export type TabBarItemProps = {
10
+ export type TabBarValue = string;
11
+
12
+ export type TabBarProps<T extends TabBarValue = TabBarValue> = {
13
+ /**
14
+ * The value of the currently active tab.
15
+ */
16
+ active: T;
17
+ /**
18
+ * The callback function called when a tab is pressed.
19
+ */
20
+ onTabPress: (active: T) => void;
21
+ /**
22
+ * The tab items to display.
23
+ */
24
+ children: ReactNode;
25
+ } & Omit<BoxProps, 'children'>;
26
+
27
+ export type TabBarItemProps<T extends TabBarValue = TabBarValue> = {
11
28
  /**
12
29
  * The unique identifier for the tab item.
13
30
  */
14
- value: string;
31
+ value: T;
15
32
  /**
16
33
  * The display label for the tab item.
17
34
  * If not provided, the icon will be centered.
@@ -27,18 +44,3 @@ export type TabBarItemProps = {
27
44
  */
28
45
  activeIcon?: IconComponent;
29
46
  } & Omit<StyledViewProps, 'children'>;
30
-
31
- export type TabBarProps = {
32
- /**
33
- * The value of the currently active tab.
34
- */
35
- active: string;
36
- /**
37
- * The callback function called when a tab is pressed.
38
- */
39
- onTabPress: (active: string) => void;
40
- /**
41
- * The tab items to display.
42
- */
43
- children: ReactNode;
44
- } & Omit<BoxProps, 'children'>;