@ledgerhq/lumen-ui-rnative 0.1.11 → 0.1.12

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 (117) hide show
  1. package/dist/module/i18n/locales/de.json +3 -0
  2. package/dist/module/i18n/locales/en.json +3 -0
  3. package/dist/module/i18n/locales/es.json +3 -0
  4. package/dist/module/i18n/locales/fr.json +3 -0
  5. package/dist/module/i18n/locales/ja.json +3 -0
  6. package/dist/module/i18n/locales/ko.json +3 -0
  7. package/dist/module/i18n/locales/pt.json +3 -0
  8. package/dist/module/i18n/locales/ru.json +3 -0
  9. package/dist/module/i18n/locales/th.json +3 -0
  10. package/dist/module/i18n/locales/tr.json +3 -0
  11. package/dist/module/i18n/locales/zh.json +3 -0
  12. package/dist/module/lib/Animations/Pulse/Pulse.js +1 -1
  13. package/dist/module/lib/Animations/Spin/Spin.js +1 -1
  14. package/dist/module/lib/Components/AmountDisplay/AmountDisplay.js +21 -21
  15. package/dist/module/lib/Components/AmountDisplay/AmountDisplay.js.map +1 -1
  16. package/dist/module/lib/Components/AmountInput/AmountInput.js +3 -3
  17. package/dist/module/lib/Components/BaseInput/BaseInput.js +1 -1
  18. package/dist/module/lib/Components/BottomSheet/BottomSheet.stories.js.map +1 -1
  19. package/dist/module/lib/Components/Card/Card.js +39 -29
  20. package/dist/module/lib/Components/Card/Card.js.map +1 -1
  21. package/dist/module/lib/Components/InteractiveIcon/InteractiveIcon.js +22 -2
  22. package/dist/module/lib/Components/InteractiveIcon/InteractiveIcon.js.map +1 -1
  23. package/dist/module/lib/Components/Link/Link.mdx +1 -0
  24. package/dist/module/lib/Components/MediaCard/MediaCard.js +183 -0
  25. package/dist/module/lib/Components/MediaCard/MediaCard.js.map +1 -0
  26. package/dist/module/lib/Components/MediaCard/MediaCard.mdx +111 -0
  27. package/dist/module/lib/Components/MediaCard/MediaCard.stories.js +199 -0
  28. package/dist/module/lib/Components/MediaCard/MediaCard.stories.js.map +1 -0
  29. package/dist/module/lib/Components/MediaCard/MediaCard.test.js +140 -0
  30. package/dist/module/lib/Components/MediaCard/MediaCard.test.js.map +1 -0
  31. package/dist/module/lib/Components/MediaCard/index.js +5 -0
  32. package/dist/module/lib/Components/MediaCard/index.js.map +1 -0
  33. package/dist/module/lib/Components/MediaCard/types.js +4 -0
  34. package/dist/module/lib/Components/MediaCard/types.js.map +1 -0
  35. package/dist/module/lib/Components/PageIndicator/PageIndicator.js +2 -2
  36. package/dist/module/lib/Components/SegmentedControl/usePillLayout.js +1 -1
  37. package/dist/module/lib/Components/Stepper/Stepper.js +1 -1
  38. package/dist/module/lib/Components/Switch/BaseSwitch.js +1 -1
  39. package/dist/module/lib/Components/TabBar/TabBar.js +4 -4
  40. package/dist/module/lib/Components/ThemeProvider/ThemeProvider.test.js +22 -20
  41. package/dist/module/lib/Components/ThemeProvider/ThemeProvider.test.js.map +1 -1
  42. package/dist/module/lib/Components/TriggerButton/TriggerButton.js +197 -0
  43. package/dist/module/lib/Components/TriggerButton/TriggerButton.js.map +1 -0
  44. package/dist/module/lib/Components/TriggerButton/TriggerButton.mdx +44 -0
  45. package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js +170 -0
  46. package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js.map +1 -0
  47. package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js +146 -0
  48. package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js.map +1 -0
  49. package/dist/module/lib/Components/TriggerButton/index.js +5 -0
  50. package/dist/module/lib/Components/TriggerButton/index.js.map +1 -0
  51. package/dist/module/lib/Components/TriggerButton/types.js +4 -0
  52. package/dist/module/lib/Components/TriggerButton/types.js.map +1 -0
  53. package/dist/module/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.js.map +1 -1
  54. package/dist/module/lib/Components/index.js +2 -0
  55. package/dist/module/lib/Components/index.js.map +1 -1
  56. package/dist/module/lib/Symbols/Icons/NanoGen5.js +49 -0
  57. package/dist/module/lib/Symbols/Icons/NanoGen5.js.map +1 -0
  58. package/dist/module/lib/Symbols/index.js +1 -0
  59. package/dist/module/lib/Symbols/index.js.map +1 -1
  60. package/dist/typescript/src/lib/Components/Card/Card.d.ts.map +1 -1
  61. package/dist/typescript/src/lib/Components/InteractiveIcon/InteractiveIcon.d.ts +1 -1
  62. package/dist/typescript/src/lib/Components/InteractiveIcon/InteractiveIcon.d.ts.map +1 -1
  63. package/dist/typescript/src/lib/Components/InteractiveIcon/types.d.ts +8 -0
  64. package/dist/typescript/src/lib/Components/InteractiveIcon/types.d.ts.map +1 -1
  65. package/dist/typescript/src/lib/Components/MediaCard/MediaCard.d.ts +32 -0
  66. package/dist/typescript/src/lib/Components/MediaCard/MediaCard.d.ts.map +1 -0
  67. package/dist/typescript/src/lib/Components/MediaCard/index.d.ts +3 -0
  68. package/dist/typescript/src/lib/Components/MediaCard/index.d.ts.map +1 -0
  69. package/dist/typescript/src/lib/Components/MediaCard/types.d.ts +38 -0
  70. package/dist/typescript/src/lib/Components/MediaCard/types.d.ts.map +1 -0
  71. package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts +26 -0
  72. package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts.map +1 -0
  73. package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts +3 -0
  74. package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts.map +1 -0
  75. package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts +38 -0
  76. package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts.map +1 -0
  77. package/dist/typescript/src/lib/Components/index.d.ts +2 -0
  78. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  79. package/dist/typescript/src/lib/Symbols/Icons/NanoGen5.d.ts +35 -0
  80. package/dist/typescript/src/lib/Symbols/Icons/NanoGen5.d.ts.map +1 -0
  81. package/dist/typescript/src/lib/Symbols/index.d.ts +1 -0
  82. package/dist/typescript/src/lib/Symbols/index.d.ts.map +1 -1
  83. package/package.json +3 -3
  84. package/src/i18n/locales/de.json +3 -0
  85. package/src/i18n/locales/en.json +3 -0
  86. package/src/i18n/locales/es.json +3 -0
  87. package/src/i18n/locales/fr.json +3 -0
  88. package/src/i18n/locales/ja.json +3 -0
  89. package/src/i18n/locales/ko.json +3 -0
  90. package/src/i18n/locales/pt.json +3 -0
  91. package/src/i18n/locales/ru.json +3 -0
  92. package/src/i18n/locales/th.json +3 -0
  93. package/src/i18n/locales/tr.json +3 -0
  94. package/src/i18n/locales/zh.json +3 -0
  95. package/src/lib/Components/AmountDisplay/AmountDisplay.tsx +20 -20
  96. package/src/lib/Components/BottomSheet/BottomSheet.stories.tsx +9 -9
  97. package/src/lib/Components/Card/Card.tsx +38 -33
  98. package/src/lib/Components/InteractiveIcon/InteractiveIcon.tsx +26 -4
  99. package/src/lib/Components/InteractiveIcon/types.ts +8 -0
  100. package/src/lib/Components/Link/Link.mdx +1 -0
  101. package/src/lib/Components/MediaCard/MediaCard.mdx +111 -0
  102. package/src/lib/Components/MediaCard/MediaCard.stories.tsx +190 -0
  103. package/src/lib/Components/MediaCard/MediaCard.test.tsx +125 -0
  104. package/src/lib/Components/MediaCard/MediaCard.tsx +203 -0
  105. package/src/lib/Components/MediaCard/index.ts +2 -0
  106. package/src/lib/Components/MediaCard/types.ts +39 -0
  107. package/src/lib/Components/ThemeProvider/ThemeProvider.test.tsx +16 -18
  108. package/src/lib/Components/TriggerButton/TriggerButton.mdx +44 -0
  109. package/src/lib/Components/TriggerButton/TriggerButton.stories.tsx +132 -0
  110. package/src/lib/Components/TriggerButton/TriggerButton.test.tsx +157 -0
  111. package/src/lib/Components/TriggerButton/TriggerButton.tsx +228 -0
  112. package/src/lib/Components/TriggerButton/index.ts +2 -0
  113. package/src/lib/Components/TriggerButton/types.ts +38 -0
  114. package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.tsx +1 -1
  115. package/src/lib/Components/index.ts +2 -0
  116. package/src/lib/Symbols/Icons/NanoGen5.tsx +44 -0
  117. package/src/lib/Symbols/index.ts +1 -0
@@ -9,13 +9,16 @@ import { Pressable } from '../Utility';
9
9
  import { HIT_SLOP_MAP, InteractiveIconProps } from './types';
10
10
 
11
11
  type IconType = InteractiveIconProps['iconType'];
12
+ type Appearance = NonNullable<InteractiveIconProps['appearance']>;
12
13
 
13
14
  const useStyles = ({
14
15
  iconType,
16
+ appearance,
15
17
  pressed,
16
18
  disabled,
17
19
  }: {
18
20
  iconType: IconType;
21
+ appearance: Appearance;
19
22
  pressed: boolean;
20
23
  disabled: boolean;
21
24
  }) => {
@@ -25,6 +28,21 @@ const useStyles = ({
25
28
  filled: { backgroundColor: t.colors.bg.base },
26
29
  stroked: { backgroundColor: t.colors.bg.baseTransparent },
27
30
  };
31
+ const appearanceColors = {
32
+ base: {
33
+ default: t.colors.text.base,
34
+ pressed: t.colors.text.basePressed,
35
+ },
36
+ muted: {
37
+ default: t.colors.text.muted,
38
+ pressed: t.colors.text.mutedPressed,
39
+ },
40
+ white: {
41
+ default: t.colors.text.white,
42
+ pressed: t.colors.text.whitePressed,
43
+ },
44
+ };
45
+ const colorSet = appearanceColors[appearance];
28
46
 
29
47
  return {
30
48
  container: StyleSheet.flatten([
@@ -39,12 +57,12 @@ const useStyles = ({
39
57
  color: disabled
40
58
  ? t.colors.text.disabled
41
59
  : pressed
42
- ? t.colors.text.mutedPressed
43
- : t.colors.text.muted,
60
+ ? colorSet.pressed
61
+ : colorSet.default,
44
62
  },
45
63
  };
46
64
  },
47
- [iconType, pressed, disabled],
65
+ [iconType, appearance, pressed, disabled],
48
66
  );
49
67
  };
50
68
 
@@ -83,6 +101,7 @@ export const InteractiveIcon = ({
83
101
  disabled: disabledProp = false,
84
102
  hitSlop: hitSlopProp,
85
103
  hitSlopType = 'comfortable',
104
+ appearance = 'muted',
86
105
  style,
87
106
  lx,
88
107
  ...props
@@ -113,6 +132,7 @@ export const InteractiveIcon = ({
113
132
  {({ pressed }) => (
114
133
  <InteractiveIconContent
115
134
  iconType={iconType}
135
+ appearance={appearance}
116
136
  pressed={pressed}
117
137
  disabled={disabled}
118
138
  >
@@ -125,15 +145,17 @@ export const InteractiveIcon = ({
125
145
 
126
146
  const InteractiveIconContent = ({
127
147
  iconType,
148
+ appearance,
128
149
  pressed,
129
150
  disabled,
130
151
  children,
131
152
  }: PropsWithChildren<{
132
153
  iconType: IconType;
154
+ appearance: Appearance;
133
155
  pressed: boolean;
134
156
  disabled: boolean;
135
157
  }>) => {
136
- const styles = useStyles({ iconType, pressed, disabled });
158
+ const styles = useStyles({ iconType, appearance, pressed, disabled });
137
159
 
138
160
  return (
139
161
  <View style={styles.container}>
@@ -52,6 +52,14 @@ export type InteractiveIconProps = {
52
52
  * Choose 'filled' for icons with solid backgrounds or 'stroked' for outlined icons.
53
53
  */
54
54
  iconType: 'filled' | 'stroked';
55
+ /**
56
+ * The color appearance of the icon.
57
+ * - `base`: Default high-contrast color.
58
+ * - `muted`: Subdued color for secondary actions.
59
+ * - `white`: White color for use on dark backgrounds.
60
+ * @default 'muted'
61
+ */
62
+ appearance?: 'base' | 'muted' | 'white';
55
63
  /**
56
64
  * Preset for the touchable area. Ignored if `hitSlop` is passed explicitly.
57
65
  * Automatically applies insets based on the child's icon size.
@@ -1,6 +1,7 @@
1
1
  import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
2
  import * as LinkStories from './Link.stories';
3
3
  import { Link } from './Link';
4
+ import { Box } from '../Utility';
4
5
  import { CustomTabs, Tab } from '../../../../.storybook/components';
5
6
  import { DoVsDontRow, DoBlockItem, DontBlockItem } from '../../../../.storybook/components/DoVsDont';
6
7
  import CommonRulesDoAndDont from '../../../../.storybook/components/DoVsDont/CommonRulesDoAndDont.mdx';
@@ -0,0 +1,111 @@
1
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
2
+ import * as MediaCardStories from './MediaCard.stories';
3
+ import { CustomTabs, Tab } from '../../../../.storybook/components';
4
+ import CommonRulesDoAndDont from '../../../../.storybook/components/DoVsDont/CommonRulesDoAndDont.mdx';
5
+
6
+ <Meta title='Communication/MediaCard' of={MediaCardStories} />
7
+
8
+ # MediaCard
9
+
10
+ <CustomTabs>
11
+ <Tab label="Overview">
12
+
13
+ ## Introduction
14
+
15
+ MediaCard is a promotional card component that displays a full-bleed background image with gradient overlays for text readability. It uses a simplified compound pattern with `MediaCardTitle` for the text and free-form children for leading content (e.g. tags, icons).
16
+
17
+ > View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7?node-id=15160-2853).
18
+
19
+ ## Anatomy
20
+
21
+ <Canvas of={MediaCardStories.Base} />
22
+
23
+ - **MediaCard**: Root pressable container with background image and gradient overlays
24
+ - **MediaCardTitle**: Styled title text (clamps at 3 lines)
25
+ - **Leading content**: Any element rendered before `MediaCardTitle` (tags, badges, icons) — no wrapper needed
26
+ - **Close button**: Rendered via the `onClose` prop (positioned absolute top-right) — only visible when `onClose` is provided
27
+
28
+ ## Properties
29
+
30
+ <Canvas of={MediaCardStories.Base} />
31
+ <Controls of={MediaCardStories.Base} />
32
+
33
+ ### Layout
34
+
35
+ The card fills its parent width by default.
36
+
37
+ <Canvas of={MediaCardStories.LayoutShowcase} />
38
+
39
+ ### Compositions
40
+
41
+ Leading content is optional — just place any element before `MediaCardTitle` inside `MediaCard`.
42
+
43
+ <Canvas of={MediaCardStories.CompositionShowcase} />
44
+
45
+ ## Accessibility
46
+
47
+ - The root element uses `accessibilityRole='button'` when `onPress` is provided
48
+ - The close button includes an `accessibilityLabel` (auto-translated via i18n) — only rendered when `onClose` is provided
49
+ - The background image is not accessible (`accessible={false}`) since it is decorative
50
+ - Components forward refs and spread props for accessibility support
51
+
52
+ </Tab>
53
+ <Tab label="Implementation">
54
+
55
+ ## Setup
56
+
57
+ Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
58
+
59
+ ## Basic Usage
60
+
61
+ Both `onPress` and `onClose` are optional. When `onClose` is omitted, the close button is hidden. When `onPress` is omitted, the card is non-interactive.
62
+
63
+ ```tsx
64
+ import { MediaCard, MediaCardTitle, Tag } from '@ledgerhq/lumen-ui-rnative';
65
+
66
+ function MyComponent() {
67
+ return (
68
+ <MediaCard
69
+ imageUrl='https://example.com/promo.jpg'
70
+ onPress={() => console.log('pressed')}
71
+ onClose={() => console.log('closed')}
72
+ >
73
+ <Tag label='New' size='md' />
74
+ <MediaCardTitle>Card title</MediaCardTitle>
75
+ </MediaCard>
76
+ );
77
+ }
78
+ ```
79
+
80
+ ### Without Close Button
81
+
82
+ Omit `onClose` to hide the close button — useful for non-dismissible promotions:
83
+
84
+ ```tsx
85
+ <MediaCard
86
+ imageUrl='https://example.com/promo.jpg'
87
+ onPress={() => console.log('pressed')}
88
+ >
89
+ <Tag label='New' size='md' />
90
+ <MediaCardTitle>Card title</MediaCardTitle>
91
+ </MediaCard>
92
+ ```
93
+
94
+ ### Layout Adjustments with lx
95
+
96
+ Use the `lx` prop for layout adjustments like margins or positioning:
97
+
98
+ ```tsx
99
+ <MediaCard
100
+ imageUrl='https://example.com/promo.jpg'
101
+ onPress={() => console.log('pressed')}
102
+ lx={{ marginTop: 's16', marginBottom: 's8' }}
103
+ >
104
+ <MediaCardTitle>With margin</MediaCardTitle>
105
+ </MediaCard>
106
+ ```
107
+
108
+ <CommonRulesDoAndDont />
109
+
110
+ </Tab>
111
+ </CustomTabs>
@@ -0,0 +1,190 @@
1
+ import { CryptoIcon } from '@ledgerhq/crypto-icons';
2
+ import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
3
+ import { useState } from 'react';
4
+ import { Button } from '../Button';
5
+ import { Tag } from '../Tag';
6
+ import { Box, Text } from '../Utility';
7
+ import { MediaCard, MediaCardTitle } from './MediaCard';
8
+
9
+ const meta = {
10
+ component: MediaCard,
11
+ subcomponents: { MediaCardTitle },
12
+ title: 'Communication/MediaCard',
13
+ parameters: {
14
+ layout: 'centered',
15
+ backgrounds: { default: 'light' },
16
+ docs: {
17
+ source: {
18
+ language: 'tsx',
19
+ format: true,
20
+ type: 'code',
21
+ },
22
+ },
23
+ },
24
+ } satisfies Meta<typeof MediaCard>;
25
+
26
+ export default meta;
27
+ type Story = StoryObj<typeof MediaCard>;
28
+
29
+ const EXAMPLE_SRC =
30
+ 'https://ledger-wp-website-s3-prd.ledger.com/uploads/2026/03/hero_visual-1.webp';
31
+
32
+ const baseArgs = {
33
+ imageUrl: EXAMPLE_SRC,
34
+ onPress: () => ({}),
35
+ onClose: () => ({}),
36
+ };
37
+
38
+ export const Base: Story = {
39
+ args: baseArgs,
40
+ render: (args) => (
41
+ <Box lx={{ width: 's320' }}>
42
+ <MediaCard {...args}>
43
+ <Tag label='Label' size='md' />
44
+ <MediaCardTitle>
45
+ Black Friday sale.
46
+ <Text typography='heading3SemiBold' lx={{ color: 'active' }}>
47
+ {' '}
48
+ 3 days with no fees{' '}
49
+ </Text>
50
+ on your transactions.
51
+ </MediaCardTitle>
52
+ </MediaCard>
53
+ </Box>
54
+ ),
55
+ parameters: {
56
+ docs: {
57
+ source: {
58
+ code: `
59
+ <MediaCard imageUrl="/promo.jpg" onPress={() => {}} onClose={() => {}}>
60
+ <Tag label='Label' size='md' />
61
+ <MediaCardTitle>
62
+ Black Friday sale.
63
+ <Text typography='heading3SemiBold' lx={{ color: 'active' }}>
64
+ {' '}
65
+ 3 days with no fees{' '}
66
+ </Text>
67
+ on your transactions.
68
+ </MediaCardTitle>
69
+ </MediaCard>`,
70
+ },
71
+ },
72
+ },
73
+ };
74
+
75
+ export const LayoutShowcase: Story = {
76
+ render: () => (
77
+ <Box
78
+ lx={{
79
+ flexDirection: 'column',
80
+ width: 's320',
81
+ gap: 's16',
82
+ padding: 's8',
83
+ }}
84
+ >
85
+ <MediaCard {...baseArgs}>
86
+ <Tag label='Label' size='md' />
87
+ <MediaCardTitle>Full width card</MediaCardTitle>
88
+ </MediaCard>
89
+ </Box>
90
+ ),
91
+ parameters: {
92
+ docs: {
93
+ source: {
94
+ code: `
95
+ <MediaCard imageUrl="/promo.jpg" onPress={() => {}} onClose={() => {}}>
96
+ <Tag label="Label" size="md" />
97
+ <MediaCardTitle>Full width card</MediaCardTitle>
98
+ </MediaCard>`,
99
+ },
100
+ },
101
+ },
102
+ };
103
+
104
+ export const CompositionShowcase: Story = {
105
+ render: () => (
106
+ <Box
107
+ lx={{
108
+ flexDirection: 'column',
109
+ width: 's320',
110
+ gap: 's16',
111
+ padding: 's8',
112
+ }}
113
+ >
114
+ <MediaCard {...baseArgs}>
115
+ <MediaCardTitle>Title only</MediaCardTitle>
116
+ </MediaCard>
117
+
118
+ <MediaCard {...baseArgs}>
119
+ <Tag label='Promo' size='md' />
120
+ <MediaCardTitle>With tag and title</MediaCardTitle>
121
+ </MediaCard>
122
+
123
+ <MediaCard {...baseArgs}>
124
+ <CryptoIcon ledgerId='bitcoin' ticker='BTC' size='32px' />
125
+ <MediaCardTitle>With crypto icon</MediaCardTitle>
126
+ </MediaCard>
127
+ </Box>
128
+ ),
129
+ parameters: {
130
+ docs: {
131
+ source: {
132
+ code: `
133
+ {/* Title only */}
134
+ <MediaCard imageUrl="/promo.jpg" onPress={() => {}} onClose={() => {}}>
135
+ <MediaCardTitle>Title only</MediaCardTitle>
136
+ </MediaCard>
137
+
138
+ {/* With tag */}
139
+ <MediaCard imageUrl="/promo.jpg" onPress={() => {}} onClose={() => {}}>
140
+ <Tag label="Promo" size="md" />
141
+ <MediaCardTitle>With tag and title</MediaCardTitle>
142
+ </MediaCard>
143
+
144
+ {/* With crypto icon */}
145
+ <MediaCard imageUrl="/promo.jpg" onPress={() => {}} onClose={() => {}}>
146
+ <CryptoIcon ledgerId="bitcoin" ticker="BTC" size="32px" />
147
+ <MediaCardTitle>With crypto icon</MediaCardTitle>
148
+ </MediaCard>`,
149
+ },
150
+ },
151
+ },
152
+ };
153
+
154
+ export const WithClose: Story = {
155
+ render: () => {
156
+ const [visible, setVisible] = useState(true);
157
+
158
+ if (!visible) {
159
+ return (
160
+ <Button
161
+ appearance='transparent'
162
+ size='sm'
163
+ onPress={() => setVisible(true)}
164
+ >
165
+ Show card again
166
+ </Button>
167
+ );
168
+ }
169
+
170
+ return (
171
+ <Box lx={{ width: 's320' }}>
172
+ <MediaCard
173
+ imageUrl={EXAMPLE_SRC}
174
+ onPress={() => ({})}
175
+ onClose={() => setVisible(false)}
176
+ >
177
+ <Tag label='Label' size='md' />
178
+ <MediaCardTitle>
179
+ Black Friday sale.
180
+ <Text typography='heading3SemiBold' lx={{ color: 'active' }}>
181
+ {' '}
182
+ 3 days with no fees{' '}
183
+ </Text>
184
+ on your transactions.
185
+ </MediaCardTitle>
186
+ </MediaCard>
187
+ </Box>
188
+ );
189
+ },
190
+ };
@@ -0,0 +1,125 @@
1
+ import { describe, it, expect, jest } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
3
+ import { fireEvent, render } from '@testing-library/react-native';
4
+ import type { ReactNode } from 'react';
5
+ import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
6
+ import { MediaCard, MediaCardTitle } from './MediaCard';
7
+
8
+ const TestWrapper = ({ children }: { children: ReactNode }) => (
9
+ <ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
10
+ {children}
11
+ </ThemeProvider>
12
+ );
13
+
14
+ const makeProps = () => ({
15
+ imageUrl: 'https://example.com/image.jpg',
16
+ onPress: jest.fn(),
17
+ onClose: jest.fn(),
18
+ });
19
+
20
+ describe('MediaCard', () => {
21
+ it('should render title', () => {
22
+ const { getByText } = render(
23
+ <TestWrapper>
24
+ <MediaCard {...makeProps()}>
25
+ <MediaCardTitle>Title</MediaCardTitle>
26
+ </MediaCard>
27
+ </TestWrapper>,
28
+ );
29
+
30
+ getByText('Title');
31
+ });
32
+
33
+ it('should render leading content and title', () => {
34
+ const { getByText } = render(
35
+ <TestWrapper>
36
+ <MediaCard {...makeProps()}>
37
+ <MediaCardTitle>Tag</MediaCardTitle>
38
+ <MediaCardTitle>Title</MediaCardTitle>
39
+ </MediaCard>
40
+ </TestWrapper>,
41
+ );
42
+
43
+ getByText('Tag');
44
+ getByText('Title');
45
+ });
46
+
47
+ it('should fire onPress on press', () => {
48
+ const props = makeProps();
49
+ const { getByTestId } = render(
50
+ <TestWrapper>
51
+ <MediaCard {...props} testID='media-card'>
52
+ <MediaCardTitle>Title</MediaCardTitle>
53
+ </MediaCard>
54
+ </TestWrapper>,
55
+ );
56
+
57
+ fireEvent.press(getByTestId('media-card'));
58
+ expect(props.onPress).toHaveBeenCalledTimes(1);
59
+ });
60
+
61
+ it('should render image when imageUrl is provided', () => {
62
+ const { getByTestId } = render(
63
+ <TestWrapper>
64
+ <MediaCard {...makeProps()}>
65
+ <MediaCardTitle>Title</MediaCardTitle>
66
+ </MediaCard>
67
+ </TestWrapper>,
68
+ );
69
+
70
+ expect(getByTestId('media-card-image')).toBeTruthy();
71
+ });
72
+
73
+ it('should hide image on error', () => {
74
+ const { getByTestId, queryByTestId } = render(
75
+ <TestWrapper>
76
+ <MediaCard {...makeProps()}>
77
+ <MediaCardTitle>Title</MediaCardTitle>
78
+ </MediaCard>
79
+ </TestWrapper>,
80
+ );
81
+
82
+ const img = getByTestId('media-card-image');
83
+ fireEvent(img, 'onError');
84
+
85
+ expect(queryByTestId('media-card-image')).toBeNull();
86
+ });
87
+
88
+ it('should fire onClose on close button press', () => {
89
+ const props = makeProps();
90
+ const { getByTestId } = render(
91
+ <TestWrapper>
92
+ <MediaCard {...props}>
93
+ <MediaCardTitle>Title</MediaCardTitle>
94
+ </MediaCard>
95
+ </TestWrapper>,
96
+ );
97
+
98
+ fireEvent.press(getByTestId('media-card-close-button'));
99
+ expect(props.onClose).toHaveBeenCalledTimes(1);
100
+ });
101
+
102
+ it('should not render close button when onClose is not provided', () => {
103
+ const { queryByTestId } = render(
104
+ <TestWrapper>
105
+ <MediaCard imageUrl='https://example.com/image.jpg'>
106
+ <MediaCardTitle>Title</MediaCardTitle>
107
+ </MediaCard>
108
+ </TestWrapper>,
109
+ );
110
+
111
+ expect(queryByTestId('media-card-close-button')).toBeNull();
112
+ });
113
+
114
+ it('should not have button role when onPress is not provided', () => {
115
+ const { getByTestId } = render(
116
+ <TestWrapper>
117
+ <MediaCard imageUrl='https://example.com/image.jpg' testID='media-card'>
118
+ <MediaCardTitle>Title</MediaCardTitle>
119
+ </MediaCard>
120
+ </TestWrapper>,
121
+ );
122
+
123
+ expect(getByTestId('media-card').props.accessibilityRole).toBeUndefined();
124
+ });
125
+ });