@ledgerhq/lumen-ui-rnative 0.1.33 → 0.1.35

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 (151) hide show
  1. package/dist/module/index.js +1 -0
  2. package/dist/module/index.js.map +1 -1
  3. package/dist/module/lib/Animations/Pulse/Pulse.js +17 -7
  4. package/dist/module/lib/Animations/Pulse/Pulse.js.map +1 -1
  5. package/dist/module/lib/Components/BaseTag/BaseTag.js +122 -0
  6. package/dist/module/lib/Components/BaseTag/BaseTag.js.map +1 -0
  7. package/dist/module/lib/Components/BaseTag/BaseTag.test.js +144 -0
  8. package/dist/module/lib/Components/BaseTag/BaseTag.test.js.map +1 -0
  9. package/dist/module/lib/Components/BaseTag/index.js +5 -0
  10. package/dist/module/lib/Components/BaseTag/index.js.map +1 -0
  11. package/dist/module/lib/Components/BaseTag/types.js +4 -0
  12. package/dist/module/lib/Components/BaseTag/types.js.map +1 -0
  13. package/dist/module/lib/Components/BottomSheet/BottomSheet.js +12 -7
  14. package/dist/module/lib/Components/BottomSheet/BottomSheet.js.map +1 -1
  15. package/dist/module/lib/Components/BottomSheet/BottomSheet.stories.js +220 -1
  16. package/dist/module/lib/Components/BottomSheet/BottomSheet.stories.js.map +1 -1
  17. package/dist/module/lib/Components/BottomSheet/BottomSheet.test.js +73 -0
  18. package/dist/module/lib/Components/BottomSheet/BottomSheet.test.js.map +1 -1
  19. package/dist/module/lib/Components/BottomSheet/BottomSheetHeader.js +1 -1
  20. package/dist/module/lib/Components/BottomSheet/BottomSheetHeader.js.map +1 -1
  21. package/dist/module/lib/Components/BottomSheet/CustomHandle.js +15 -2
  22. package/dist/module/lib/Components/BottomSheet/CustomHandle.js.map +1 -1
  23. package/dist/module/lib/Components/Card/Card.js.map +1 -1
  24. package/dist/module/lib/Components/ListItem/ListItem.js.map +1 -1
  25. package/dist/module/lib/Components/MediaImage/MediaImage.js +10 -2
  26. package/dist/module/lib/Components/MediaImage/MediaImage.js.map +1 -1
  27. package/dist/module/lib/Components/MediaTag/MediaTag.js +39 -0
  28. package/dist/module/lib/Components/MediaTag/MediaTag.js.map +1 -0
  29. package/dist/module/lib/Components/MediaTag/MediaTag.mdx +161 -0
  30. package/dist/module/lib/Components/MediaTag/MediaTag.stories.js +122 -0
  31. package/dist/module/lib/Components/MediaTag/MediaTag.stories.js.map +1 -0
  32. package/dist/module/lib/Components/MediaTag/MediaTag.test.js +30 -0
  33. package/dist/module/lib/Components/MediaTag/MediaTag.test.js.map +1 -0
  34. package/dist/module/lib/Components/MediaTag/index.js +5 -0
  35. package/dist/module/lib/Components/MediaTag/index.js.map +1 -0
  36. package/dist/module/lib/Components/MediaTag/types.js +4 -0
  37. package/dist/module/lib/Components/MediaTag/types.js.map +1 -0
  38. package/dist/module/lib/Components/OptionList/OptionList.js +45 -4
  39. package/dist/module/lib/Components/OptionList/OptionList.js.map +1 -1
  40. package/dist/module/lib/Components/OptionList/OptionList.mdx +19 -0
  41. package/dist/module/lib/Components/OptionList/OptionList.stories.js +254 -1
  42. package/dist/module/lib/Components/OptionList/OptionList.stories.js.map +1 -1
  43. package/dist/module/lib/Components/OptionList/OptionList.test.js +136 -1
  44. package/dist/module/lib/Components/OptionList/OptionList.test.js.map +1 -1
  45. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.js +39 -13
  46. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.js.map +1 -1
  47. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.test.js +117 -2
  48. package/dist/module/lib/Components/OptionList/useOptionList/useOptionListItems.test.js.map +1 -1
  49. package/dist/module/lib/Components/PageIndicator/PageIndicator.test.js.map +1 -1
  50. package/dist/module/lib/Components/Skeleton/Skeleton.js +10 -3
  51. package/dist/module/lib/Components/Skeleton/Skeleton.js.map +1 -1
  52. package/dist/module/lib/Components/TabBar/TabBar.js +7 -6
  53. package/dist/module/lib/Components/TabBar/TabBar.js.map +1 -1
  54. package/dist/module/lib/Components/Tag/Tag.js +10 -95
  55. package/dist/module/lib/Components/Tag/Tag.js.map +1 -1
  56. package/dist/module/lib/Components/Tag/Tag.mdx +1 -79
  57. package/dist/module/lib/Components/Tag/Tag.stories.js +8 -1
  58. package/dist/module/lib/Components/Tag/Tag.stories.js.map +1 -1
  59. package/dist/module/lib/Components/Tag/Tag.test.js +69 -0
  60. package/dist/module/lib/Components/Tag/Tag.test.js.map +1 -0
  61. package/dist/module/lib/Components/index.js +1 -0
  62. package/dist/module/lib/Components/index.js.map +1 -1
  63. package/dist/module/styles/lx/resolveStyle.js.map +1 -1
  64. package/dist/typescript/src/index.d.ts +1 -0
  65. package/dist/typescript/src/index.d.ts.map +1 -1
  66. package/dist/typescript/src/lib/Animations/Pulse/Pulse.d.ts +1 -1
  67. package/dist/typescript/src/lib/Animations/Pulse/Pulse.d.ts.map +1 -1
  68. package/dist/typescript/src/lib/Animations/Pulse/types.d.ts +2 -1
  69. package/dist/typescript/src/lib/Animations/Pulse/types.d.ts.map +1 -1
  70. package/dist/typescript/src/lib/Components/BaseTag/BaseTag.d.ts +3 -0
  71. package/dist/typescript/src/lib/Components/BaseTag/BaseTag.d.ts.map +1 -0
  72. package/dist/typescript/src/lib/Components/BaseTag/index.d.ts +3 -0
  73. package/dist/typescript/src/lib/Components/BaseTag/index.d.ts.map +1 -0
  74. package/dist/typescript/src/lib/Components/BaseTag/types.d.ts +10 -0
  75. package/dist/typescript/src/lib/Components/BaseTag/types.d.ts.map +1 -0
  76. package/dist/typescript/src/lib/Components/BottomSheet/BottomSheet.d.ts +1 -1
  77. package/dist/typescript/src/lib/Components/BottomSheet/BottomSheet.d.ts.map +1 -1
  78. package/dist/typescript/src/lib/Components/BottomSheet/CustomHandle.d.ts +5 -2
  79. package/dist/typescript/src/lib/Components/BottomSheet/CustomHandle.d.ts.map +1 -1
  80. package/dist/typescript/src/lib/Components/BottomSheet/types.d.ts +16 -3
  81. package/dist/typescript/src/lib/Components/BottomSheet/types.d.ts.map +1 -1
  82. package/dist/typescript/src/lib/Components/Card/Card.d.ts.map +1 -1
  83. package/dist/typescript/src/lib/Components/ListItem/ListItem.d.ts +3 -3
  84. package/dist/typescript/src/lib/Components/ListItem/ListItem.d.ts.map +1 -1
  85. package/dist/typescript/src/lib/Components/MediaImage/MediaImage.d.ts.map +1 -1
  86. package/dist/typescript/src/lib/Components/MediaTag/MediaTag.d.ts +26 -0
  87. package/dist/typescript/src/lib/Components/MediaTag/MediaTag.d.ts.map +1 -0
  88. package/dist/typescript/src/lib/Components/MediaTag/index.d.ts +3 -0
  89. package/dist/typescript/src/lib/Components/MediaTag/index.d.ts.map +1 -0
  90. package/dist/typescript/src/lib/Components/MediaTag/types.d.ts +10 -0
  91. package/dist/typescript/src/lib/Components/MediaTag/types.d.ts.map +1 -0
  92. package/dist/typescript/src/lib/Components/OptionList/OptionList.d.ts +3 -2
  93. package/dist/typescript/src/lib/Components/OptionList/OptionList.d.ts.map +1 -1
  94. package/dist/typescript/src/lib/Components/OptionList/types.d.ts +42 -5
  95. package/dist/typescript/src/lib/Components/OptionList/types.d.ts.map +1 -1
  96. package/dist/typescript/src/lib/Components/OptionList/useOptionList/useOptionListItems.d.ts +9 -1
  97. package/dist/typescript/src/lib/Components/OptionList/useOptionList/useOptionListItems.d.ts.map +1 -1
  98. package/dist/typescript/src/lib/Components/Skeleton/Skeleton.d.ts +1 -1
  99. package/dist/typescript/src/lib/Components/Skeleton/Skeleton.d.ts.map +1 -1
  100. package/dist/typescript/src/lib/Components/TabBar/TabBar.d.ts.map +1 -1
  101. package/dist/typescript/src/lib/Components/Tag/Tag.d.ts +1 -1
  102. package/dist/typescript/src/lib/Components/Tag/Tag.d.ts.map +1 -1
  103. package/dist/typescript/src/lib/Components/Tag/types.d.ts +1 -1
  104. package/dist/typescript/src/lib/Components/Tag/types.d.ts.map +1 -1
  105. package/dist/typescript/src/lib/Components/index.d.ts +1 -0
  106. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  107. package/dist/typescript/src/lib/types/index.d.ts +3 -3
  108. package/dist/typescript/src/lib/types/index.d.ts.map +1 -1
  109. package/dist/typescript/src/styles/lx/resolveStyle.d.ts +3 -3
  110. package/dist/typescript/src/styles/lx/resolveStyle.d.ts.map +1 -1
  111. package/package.json +1 -1
  112. package/src/index.ts +1 -0
  113. package/src/lib/Animations/Pulse/Pulse.tsx +34 -29
  114. package/src/lib/Animations/Pulse/types.ts +2 -1
  115. package/src/lib/Components/BaseTag/BaseTag.test.tsx +137 -0
  116. package/src/lib/Components/BaseTag/BaseTag.tsx +152 -0
  117. package/src/lib/Components/BaseTag/index.ts +2 -0
  118. package/src/lib/Components/BaseTag/types.ts +11 -0
  119. package/src/lib/Components/BottomSheet/BottomSheet.stories.tsx +174 -1
  120. package/src/lib/Components/BottomSheet/BottomSheet.test.tsx +59 -0
  121. package/src/lib/Components/BottomSheet/BottomSheet.tsx +19 -7
  122. package/src/lib/Components/BottomSheet/BottomSheetHeader.tsx +1 -1
  123. package/src/lib/Components/BottomSheet/CustomHandle.tsx +26 -5
  124. package/src/lib/Components/BottomSheet/types.ts +24 -3
  125. package/src/lib/Components/Card/Card.tsx +3 -3
  126. package/src/lib/Components/ListItem/ListItem.tsx +3 -3
  127. package/src/lib/Components/MediaImage/MediaImage.tsx +10 -2
  128. package/src/lib/Components/MediaTag/MediaTag.mdx +161 -0
  129. package/src/lib/Components/MediaTag/MediaTag.stories.tsx +112 -0
  130. package/src/lib/Components/MediaTag/MediaTag.test.tsx +27 -0
  131. package/src/lib/Components/MediaTag/MediaTag.tsx +36 -0
  132. package/src/lib/Components/MediaTag/index.ts +2 -0
  133. package/src/lib/Components/MediaTag/types.ts +10 -0
  134. package/src/lib/Components/OptionList/OptionList.mdx +19 -0
  135. package/src/lib/Components/OptionList/OptionList.stories.tsx +254 -0
  136. package/src/lib/Components/OptionList/OptionList.test.tsx +143 -0
  137. package/src/lib/Components/OptionList/OptionList.tsx +49 -3
  138. package/src/lib/Components/OptionList/types.ts +46 -5
  139. package/src/lib/Components/OptionList/useOptionList/useOptionListItems.test.ts +124 -2
  140. package/src/lib/Components/OptionList/useOptionList/useOptionListItems.ts +53 -10
  141. package/src/lib/Components/PageIndicator/PageIndicator.test.tsx +2 -1
  142. package/src/lib/Components/Skeleton/Skeleton.tsx +9 -5
  143. package/src/lib/Components/TabBar/TabBar.tsx +3 -2
  144. package/src/lib/Components/Tag/Tag.mdx +1 -79
  145. package/src/lib/Components/Tag/Tag.stories.tsx +3 -0
  146. package/src/lib/Components/Tag/Tag.test.tsx +51 -0
  147. package/src/lib/Components/Tag/Tag.tsx +12 -119
  148. package/src/lib/Components/Tag/types.ts +2 -1
  149. package/src/lib/Components/index.ts +1 -0
  150. package/src/lib/types/index.ts +3 -3
  151. package/src/styles/lx/resolveStyle.ts +4 -3
@@ -1,5 +1,5 @@
1
1
  import type { BottomSheetVariables } from '@gorhom/bottom-sheet/lib/typescript/types';
2
- import type { Ref } from 'react';
2
+ import type { ComponentRef, Ref } from 'react';
3
3
  import { View } from 'react-native';
4
4
  import { useStyleSheet } from '../../../styles';
5
5
 
@@ -12,13 +12,13 @@ const useStyles = () => {
12
12
  alignItems: 'center',
13
13
  justifyContent: 'center',
14
14
  alignSelf: 'center',
15
- backgroundColor: t.colors.bg.canvasSheet,
15
+ backgroundColor: 'transparent',
16
16
  },
17
17
  handle: {
18
18
  height: t.spacings.s4,
19
19
  width: t.sizes.s36,
20
20
  borderRadius: t.borderRadius.full,
21
- backgroundColor: t.colors.bg.mutedPressed,
21
+ backgroundColor: t.colors.bg.mutedTransparentPressed,
22
22
  },
23
23
  }),
24
24
  [],
@@ -28,12 +28,33 @@ const useStyles = () => {
28
28
  export const CustomHandle = ({
29
29
  ref,
30
30
  ...props
31
- }: BottomSheetVariables & { ref?: Ref<React.ElementRef<typeof View>> }) => {
31
+ }: BottomSheetVariables & { ref?: Ref<ComponentRef<typeof View>> }) => {
32
32
  const styles = useStyles();
33
33
 
34
34
  return (
35
- <View {...props} ref={ref} style={styles.container}>
35
+ <View
36
+ {...props}
37
+ ref={ref}
38
+ style={styles.container}
39
+ testID='bottom-sheet-handle'
40
+ >
36
41
  <View style={styles.handle} />
37
42
  </View>
38
43
  );
39
44
  };
45
+
46
+ export const HiddenHandle = ({
47
+ ref,
48
+ ...props
49
+ }: BottomSheetVariables & { ref?: Ref<ComponentRef<typeof View>> }) => {
50
+ const styles = useStyles();
51
+
52
+ return (
53
+ <View
54
+ {...props}
55
+ ref={ref}
56
+ style={styles.container}
57
+ testID='bottom-sheet-handle-hidden'
58
+ />
59
+ );
60
+ };
@@ -5,16 +5,23 @@ import type {
5
5
  BottomSheetSectionList as GorhomBottomSheetSectionList,
6
6
  BottomSheetScrollView as GorhomBottomSheetScrollView,
7
7
  BottomSheetVirtualizedList as GorhomBottomSheetVirtualizedList,
8
+ BottomSheetBackgroundProps,
8
9
  } from '@gorhom/bottom-sheet';
9
-
10
10
  import type { Density } from '@ledgerhq/lumen-utils-shared';
11
- import type { PropsWithChildren, ReactNode, Ref } from 'react';
11
+ import type {
12
+ ComponentRef,
13
+ FC,
14
+ PropsWithChildren,
15
+ ReactNode,
16
+ Ref,
17
+ } from 'react';
18
+
12
19
  import type { StyledViewProps } from '../../../styles';
13
20
  export type BottomSheetProps = PropsWithChildren & {
14
21
  /**
15
22
  * Ref to the bottom sheet component.
16
23
  */
17
- ref?: Ref<React.ElementRef<typeof GorhomBottomSheetModal>>;
24
+ ref?: Ref<ComponentRef<typeof GorhomBottomSheetModal>>;
18
25
  /**
19
26
  * Used to locate this view in end-to-end tests.
20
27
  */
@@ -131,8 +138,22 @@ export type BottomSheetProps = PropsWithChildren & {
131
138
  * @default true
132
139
  */
133
140
  enableBlurKeyboardOnGesture?: boolean;
141
+ /**
142
+ * Custom background component rendered behind the sheet content and handle.
143
+ * Use to render gradients or other effects that should span the full sheet area,
144
+ * including the handle strip. When provided, the default sheet background is removed.
145
+ * @default undefined
146
+ */
147
+ backgroundComponent?: FC<BottomSheetBackgroundProps> | null;
148
+ /**
149
+ * If true, the drag handle (grabber) at the top of the sheet is hidden.
150
+ * @default false
151
+ */
152
+ hideHandle?: boolean;
134
153
  };
135
154
 
155
+ export type { BottomSheetBackgroundProps };
156
+
136
157
  export type BottomSheetHeaderProps = {
137
158
  /**
138
159
  * The density of the header.
@@ -3,7 +3,7 @@ import {
3
3
  DisabledProvider,
4
4
  isTextChildren,
5
5
  } from '@ledgerhq/lumen-utils-shared';
6
- import type { ReactNode, Ref } from 'react';
6
+ import type { ComponentRef, ReactNode, Ref } from 'react';
7
7
  import { useCallback, useEffect, useMemo } from 'react';
8
8
  import type { LayoutChangeEvent, ViewStyle } from 'react-native';
9
9
  import { StyleSheet, View } from 'react-native';
@@ -486,7 +486,7 @@ export const CardContentTitle = ({
486
486
  if (isTextChildren(children)) {
487
487
  return (
488
488
  <Text
489
- ref={ref as Ref<React.ElementRef<typeof Text>>}
489
+ ref={ref as Ref<ComponentRef<typeof Text>>}
490
490
  lx={lx}
491
491
  style={StyleSheet.flatten([styles.asText, style])}
492
492
  numberOfLines={1}
@@ -553,7 +553,7 @@ export const CardContentDescription = ({
553
553
  if (isTextChildren(children)) {
554
554
  return (
555
555
  <Text
556
- ref={ref as Ref<React.ElementRef<typeof Text>>}
556
+ ref={ref as Ref<ComponentRef<typeof Text>>}
557
557
  lx={lx}
558
558
  style={StyleSheet.flatten([styles.asText, style])}
559
559
  numberOfLines={1}
@@ -4,7 +4,7 @@ import {
4
4
  DisabledProvider,
5
5
  useDisabledContext,
6
6
  } from '@ledgerhq/lumen-utils-shared';
7
- import type { ElementRef, ReactNode, Ref } from 'react';
7
+ import type { ComponentRef, ReactNode, Ref } from 'react';
8
8
  import type { ViewStyle } from 'react-native';
9
9
  import { StyleSheet, View } from 'react-native';
10
10
  import { useStyleSheet } from '../../../styles';
@@ -286,7 +286,7 @@ export const ListItemTitle = ({
286
286
  style,
287
287
  ref,
288
288
  ...props
289
- }: ListItemTitleProps & { ref?: Ref<ElementRef<typeof Text>> }) => {
289
+ }: ListItemTitleProps & { ref?: Ref<ComponentRef<typeof Text>> }) => {
290
290
  const disabled = useDisabledContext({
291
291
  consumerName: 'ListItemTitle',
292
292
  contextRequired: true,
@@ -336,7 +336,7 @@ export const ListItemDescription = ({
336
336
  style,
337
337
  ref,
338
338
  ...props
339
- }: ListItemDescriptionProps & { ref?: Ref<ElementRef<typeof Text>> }) => {
339
+ }: ListItemDescriptionProps & { ref?: Ref<ComponentRef<typeof Text>> }) => {
340
340
  const disabled = useDisabledContext({
341
341
  consumerName: 'ListItemDescription',
342
342
  contextRequired: true,
@@ -2,6 +2,7 @@ import { useEffect, useState } from 'react';
2
2
  import { Image, StyleSheet } from 'react-native';
3
3
  import { useStyleSheet } from '../../../styles';
4
4
  import type { LumenStyleSheetTheme } from '../../../styles';
5
+ import { RuntimeConstants } from '../../utils';
5
6
  import { Skeleton } from '../Skeleton';
6
7
  import { Box, Text } from '../Utility';
7
8
  import type { MediaImageProps, MediaImageSize, MediaImageShape } from './types';
@@ -65,6 +66,10 @@ const useStyles = ({
65
66
  width: '100%',
66
67
  height: '100%',
67
68
  },
69
+ skeleton: {
70
+ width: '100%',
71
+ height: '100%',
72
+ },
68
73
  };
69
74
  },
70
75
  [size, shape],
@@ -116,10 +121,13 @@ export const MediaImage = ({
116
121
  accessibilityLabel={alt}
117
122
  {...props}
118
123
  >
119
- {loading && <Skeleton style={StyleSheet.absoluteFillObject} />}
124
+ {loading && <Skeleton style={styles.skeleton} />}
120
125
  {!loading && shouldFallback && fallback && (
121
126
  <Text
122
- style={{ fontSize: fontSizeMap[size] }}
127
+ style={{
128
+ fontSize: fontSizeMap[size],
129
+ ...(RuntimeConstants.isIOS && { lineHeight: 0 }),
130
+ }}
123
131
  lx={{ color: 'base' }}
124
132
  accessible={false}
125
133
  >
@@ -0,0 +1,161 @@
1
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as MediaTagStories from './MediaTag.stories';
3
+ import { CustomTabs, Tab } from '../../../../.storybook/components';
4
+ import { DoVsDontRow, DoBlockItem, DontBlockItem } from '../../../../.storybook/components/DoVsDont';
5
+ import CommonRulesDoAndDont from '../../../../.storybook/components/DoVsDont/CommonRulesDoAndDont.mdx';
6
+ import { Box } from '../Utility/Box';
7
+
8
+ <Meta title='Components/MediaTag' of={MediaTagStories} />
9
+
10
+ # MediaTag
11
+
12
+ <CustomTabs>
13
+ <Tab label="Overview">
14
+
15
+ ## Introduction
16
+
17
+ MediaTag is a compact label used to categorize, classify, or highlight information alongside a required media element such as an image or crypto icon. Like [Tag](/docs/communication-tag--docs), it supports multiple appearances and sizes, but is designed for cases where the icon is richer than a Symbol.
18
+
19
+ > View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=16793-36383&m=dev).
20
+
21
+ ## Anatomy
22
+
23
+ <Canvas of={MediaTagStories.Base} />
24
+
25
+ - **Label:** The text content of the tag.
26
+ - **Leading content:** A required media element (image, crypto icon, etc.) displayed before the label. Passed via the `leadingContent` prop.
27
+
28
+ ## Properties
29
+
30
+ <Canvas of={MediaTagStories.Base} />
31
+ <Controls of={MediaTagStories.Base} />
32
+
33
+ ### Appearance
34
+
35
+ <Canvas of={MediaTagStories.AppearanceShowcase} />
36
+
37
+ ---
38
+
39
+ ### Size
40
+
41
+ MediaTags come in two different sizes:
42
+
43
+ - **md** (default) — the `leadingContent` media should be rendered at **16px**.
44
+ - **sm** — the `leadingContent` media should be rendered at **12px**.
45
+
46
+ The MediaTag does not resize its `leadingContent`; consumers are responsible for passing a media element sized to match the tag's `size`.
47
+
48
+ <Canvas of={MediaTagStories.SizeShowcase} />
49
+
50
+ ### Truncation
51
+
52
+ When a MediaTag's label exceeds the available space, the text is automatically truncated with an ellipsis.
53
+
54
+ <Canvas of={MediaTagStories.TruncateShowcase} />
55
+
56
+ ### Accessibility
57
+
58
+ To be implemented:
59
+
60
+ - **Color contrast**
61
+ - **Text zoom**
62
+ - **Touch targets**
63
+ - **Screen reader support**
64
+ - **Semantic labeling**
65
+
66
+ </Tab>
67
+ <Tab label="Implementation">
68
+
69
+ ## Setup
70
+
71
+ Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
72
+
73
+ ### Basic Usage
74
+
75
+ Match the media element size to the MediaTag `size`: **16px** for `md`, **12px** for `sm`.
76
+
77
+ ```tsx
78
+ import { MediaTag, MediaImage } from '@ledgerhq/lumen-ui-rnative';
79
+
80
+ function MyComponent() {
81
+ return (
82
+ <>
83
+ {/* md → image size 16 */}
84
+ <MediaTag
85
+ size="md"
86
+ appearance="accent"
87
+ label="Ethereum"
88
+ leadingContent={
89
+ <MediaImage
90
+ src="https://crypto-icons.ledger.com/ETH.png"
91
+ alt="Ethereum"
92
+ size={16}
93
+ shape="square"
94
+ />
95
+ }
96
+ />
97
+
98
+ {/* sm → image size 12 */}
99
+ <MediaTag
100
+ size="sm"
101
+ appearance="accent"
102
+ label="Ethereum"
103
+ leadingContent={
104
+ <MediaImage
105
+ src="https://crypto-icons.ledger.com/ETH.png"
106
+ alt="Ethereum"
107
+ size={12}
108
+ shape="square"
109
+ />
110
+ }
111
+ />
112
+ </>
113
+ );
114
+ }
115
+ ```
116
+
117
+ ### Custom Styling
118
+
119
+ While the component comes with predefined styles, you can extend them using the `lx` prop for layout adjustments:
120
+
121
+ ```tsx
122
+ <MediaTag
123
+ size="md"
124
+ appearance="accent"
125
+ label="Custom MediaTag"
126
+ leadingContent={myIcon}
127
+ lx={{ marginTop: 's4' }}
128
+ />
129
+ ```
130
+
131
+ ### With Crypto Icons
132
+
133
+ The MediaTag accepts any `ReactNode` as its `leadingContent`, including crypto icons. Use **16px** for `md` and **12px** for `sm`:
134
+
135
+ ```tsx
136
+ import { MediaTag } from '@ledgerhq/lumen-ui-rnative';
137
+ import CryptoIcon from '@ledgerhq/crypto-icons/native';
138
+
139
+ function MyComponent() {
140
+ return (
141
+ <>
142
+ {/* md → leadingContent size 16 */}
143
+ <MediaTag
144
+ size="md"
145
+ label="Bitcoin"
146
+ leadingContent={<CryptoIcon ledgerId="bitcoin" ticker="BTC" size={16} />}
147
+ />
148
+
149
+ {/* sm → leadingContent size 12 */}
150
+ <MediaTag
151
+ size="sm"
152
+ label="Bitcoin"
153
+ leadingContent={<CryptoIcon ledgerId="bitcoin" ticker="BTC" size={12} />}
154
+ />
155
+ </>
156
+ );
157
+ }
158
+ ```
159
+
160
+ </Tab>
161
+ </CustomTabs>
@@ -0,0 +1,112 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
2
+ import { MediaImage } from '../MediaImage';
3
+ import { Box } from '../Utility/Box';
4
+ import { MediaTag } from './MediaTag';
5
+
6
+ const ETH_ICON = (
7
+ <MediaImage
8
+ src='https://crypto-icons.ledger.com/ETH.png'
9
+ alt='Ethereum'
10
+ size={16}
11
+ shape='square'
12
+ />
13
+ );
14
+
15
+ const meta: Meta<typeof MediaTag> = {
16
+ component: MediaTag,
17
+ title: 'Communication/MediaTag',
18
+ argTypes: {
19
+ appearance: {
20
+ control: 'select',
21
+ options: [
22
+ 'base',
23
+ 'gray',
24
+ 'accent',
25
+ 'accent-subtle',
26
+ 'success',
27
+ 'error',
28
+ 'warning',
29
+ 'white',
30
+ ],
31
+ },
32
+ size: {
33
+ control: 'radio',
34
+ options: ['sm', 'md'],
35
+ },
36
+ label: {
37
+ control: 'text',
38
+ },
39
+ },
40
+ };
41
+
42
+ export default meta;
43
+
44
+ type Story = StoryObj<typeof MediaTag>;
45
+
46
+ export const Base: Story = {
47
+ args: {
48
+ label: 'Ethereum',
49
+ appearance: 'accent',
50
+ size: 'md',
51
+ },
52
+ render: (args) => <MediaTag {...args} leadingContent={ETH_ICON} />,
53
+ };
54
+
55
+ export const AppearanceShowcase: Story = {
56
+ render: () => (
57
+ <Box lx={{ flexDirection: 'row', flexWrap: 'wrap', gap: 's4' }}>
58
+ <MediaTag appearance='base' label='Base' leadingContent={ETH_ICON} />
59
+ <MediaTag appearance='gray' label='Gray' leadingContent={ETH_ICON} />
60
+ <MediaTag appearance='accent' label='Accent' leadingContent={ETH_ICON} />
61
+ <MediaTag
62
+ appearance='accent-subtle'
63
+ label='Accent subtle'
64
+ leadingContent={ETH_ICON}
65
+ />
66
+ <MediaTag
67
+ appearance='success'
68
+ label='Success'
69
+ leadingContent={ETH_ICON}
70
+ />
71
+ <MediaTag appearance='error' label='Error' leadingContent={ETH_ICON} />
72
+ <MediaTag
73
+ appearance='warning'
74
+ label='Warning'
75
+ leadingContent={ETH_ICON}
76
+ />
77
+ <MediaTag appearance='white' label='White' leadingContent={ETH_ICON} />
78
+ <MediaTag label='Disabled' leadingContent={ETH_ICON} disabled />
79
+ </Box>
80
+ ),
81
+ };
82
+
83
+ export const SizeShowcase: Story = {
84
+ render: () => (
85
+ <Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's4' }}>
86
+ <MediaTag size='md' label='Medium' leadingContent={ETH_ICON} />
87
+ <MediaTag
88
+ size='sm'
89
+ label='Small'
90
+ leadingContent={
91
+ <MediaImage
92
+ src='https://crypto-icons.ledger.com/ETH.png'
93
+ alt='Ethereum'
94
+ size={12}
95
+ shape='square'
96
+ />
97
+ }
98
+ />
99
+ </Box>
100
+ ),
101
+ };
102
+
103
+ export const TruncateShowcase: Story = {
104
+ render: () => (
105
+ <Box lx={{ width: 's176' }}>
106
+ <MediaTag
107
+ label='Very long text that truncates'
108
+ leadingContent={ETH_ICON}
109
+ />
110
+ </Box>
111
+ ),
112
+ };
@@ -0,0 +1,27 @@
1
+ import { describe, it, expect } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { render, screen } from '@testing-library/react-native';
4
+ import { Text } from 'react-native';
5
+ import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
6
+ import { MediaTag } from './MediaTag';
7
+
8
+ const renderWithProvider = (component: React.ReactElement) =>
9
+ render(
10
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
11
+ {component}
12
+ </ThemeProvider>,
13
+ );
14
+
15
+ describe('MediaTag Component', () => {
16
+ it('should render label and the provided ReactNode icon as-is', () => {
17
+ renderWithProvider(
18
+ <MediaTag
19
+ label='Bitcoin'
20
+ leadingContent={<Text testID='media-icon'>bitcoin-icon</Text>}
21
+ />,
22
+ );
23
+ expect(screen.getByText('Bitcoin')).toBeTruthy();
24
+ expect(screen.getByTestId('media-icon')).toBeTruthy();
25
+ expect(screen.getByText('bitcoin-icon')).toBeTruthy();
26
+ });
27
+ });
@@ -0,0 +1,36 @@
1
+ import { BaseTag } from '../BaseTag';
2
+ import type { MediaTagProps } from './types';
3
+
4
+ /**
5
+ * A compact label used to categorize, classify, or highlight information with a required media element (image, crypto icon, etc.).
6
+ *
7
+ * The appearance determines the color scheme used.
8
+ *
9
+ * @see {@link https://ldls.vercel.app/?path=/docs/communication-mediatag-overview--docs Storybook}
10
+ * @see {@link https://ldls.vercel.app/?path=/docs/communication-mediatag-implementation--docs#dos-and-donts Guidelines}
11
+ *
12
+ * @warning The `lx` prop should only be used for layout adjustments like margins or positioning.
13
+ * Do not use it to modify the tag's core appearance (colors, padding, etc). Use the `appearance` prop instead.
14
+ *
15
+ * @example
16
+ * import { MediaTag, MediaImage } from '@ledgerhq/lumen-ui-rnative';
17
+ *
18
+ * // MediaTag with image
19
+ * <MediaTag
20
+ * label='Ethereum'
21
+ * leadingContent={<MediaImage src='https://crypto-icons.ledger.com/ETH.png' alt='Ethereum' size={16} shape='square' />}
22
+ * />
23
+ *
24
+ * // Small MediaTag
25
+ * <MediaTag label='Bitcoin' size='sm' leadingContent={myIcon} />
26
+ */
27
+ export const MediaTag = ({ leadingContent, ...props }: MediaTagProps) => {
28
+ return (
29
+ <BaseTag
30
+ {...props}
31
+ variant='media'
32
+ consumerName='MediaTag'
33
+ renderIcon={() => leadingContent}
34
+ />
35
+ );
36
+ };
@@ -0,0 +1,2 @@
1
+ export * from './types';
2
+ export * from './MediaTag';
@@ -0,0 +1,10 @@
1
+ import type { ReactNode } from 'react';
2
+ import type { TagProps } from '../Tag';
3
+
4
+ export type MediaTagProps = Omit<TagProps, 'icon'> & {
5
+ /**
6
+ * The media element rendered before the label (e.g. an image or a crypto icon).
7
+ * Should be sized to match the tag's `size`: 16px for `md`, 12px for `sm`.
8
+ */
9
+ leadingContent: ReactNode;
10
+ };
@@ -36,6 +36,7 @@ It handles **selection state**, **automatic grouping** (via a `group` field on i
36
36
  - **OptionListItemContentRow**: Horizontal row for placing elements side-by-side (e.g. title + tag)
37
37
  - **OptionListItemText**: Styled title text
38
38
  - **OptionListItemDescription**: Styled description text
39
+ - **OptionListSearch**: Search input that filters items by label (default) or via a custom `filter` function
39
40
 
40
41
  ## Properties
41
42
 
@@ -63,6 +64,24 @@ Combining grouping with rich item content:
63
64
 
64
65
  <Canvas of={OptionListStories.GroupedWithContentRow} />
65
66
 
67
+ ### Search
68
+
69
+ Drop `OptionListSearch` inside the list to enable case-insensitive filtering on item labels. Pair with `OptionListEmptyState` to handle no-match scenarios. Pass a custom `filter` to match against meta (or anything else), or `filter={null}` to disable filtering. Use `filteredItems` + `onSearchValueChange` for async/server-side search.
70
+
71
+ <Canvas of={OptionListStories.WithSearch} />
72
+
73
+ Search works within grouped items too. Empty groups are hidden automatically:
74
+
75
+ <Canvas of={OptionListStories.WithSearchAndGroups} />
76
+
77
+ Pass a custom `filter` to match against fields beyond the label. Here, the query also matches each item's ticker via `meta`:
78
+
79
+ <Canvas of={OptionListStories.WithCustomSearchFilter} />
80
+
81
+ Drive the search input from the outside with `searchValue` + `onSearchValueChange`. This is useful for syncing search state with other UI or kicking off async fetches:
82
+
83
+ <Canvas of={OptionListStories.WithControlledSearch} />
84
+
66
85
  ### Trigger showcase
67
86
 
68
87
  OptionList can be opened from any trigger. `MediaButton` supports multiple appearances (`gray`, `transparent`, `no-background`), optional icons (`flat` / `rounded`), and disabled state: