@ledgerhq/lumen-ui-rnative 0.1.11 → 0.1.13

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 (140) 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/MediaBanner/MediaBanner.js +158 -0
  25. package/dist/module/lib/Components/MediaBanner/MediaBanner.js.map +1 -0
  26. package/dist/module/lib/Components/MediaBanner/MediaBanner.mdx +150 -0
  27. package/dist/module/lib/Components/MediaBanner/MediaBanner.stories.js +135 -0
  28. package/dist/module/lib/Components/MediaBanner/MediaBanner.stories.js.map +1 -0
  29. package/dist/module/lib/Components/MediaBanner/MediaBanner.test.js +83 -0
  30. package/dist/module/lib/Components/MediaBanner/MediaBanner.test.js.map +1 -0
  31. package/dist/module/lib/Components/MediaBanner/index.js +5 -0
  32. package/dist/module/lib/Components/MediaBanner/index.js.map +1 -0
  33. package/dist/module/lib/Components/MediaBanner/types.js +4 -0
  34. package/dist/module/lib/Components/MediaBanner/types.js.map +1 -0
  35. package/dist/module/lib/Components/MediaCard/MediaCard.js +183 -0
  36. package/dist/module/lib/Components/MediaCard/MediaCard.js.map +1 -0
  37. package/dist/module/lib/Components/MediaCard/MediaCard.mdx +111 -0
  38. package/dist/module/lib/Components/MediaCard/MediaCard.stories.js +199 -0
  39. package/dist/module/lib/Components/MediaCard/MediaCard.stories.js.map +1 -0
  40. package/dist/module/lib/Components/MediaCard/MediaCard.test.js +140 -0
  41. package/dist/module/lib/Components/MediaCard/MediaCard.test.js.map +1 -0
  42. package/dist/module/lib/Components/MediaCard/index.js +5 -0
  43. package/dist/module/lib/Components/MediaCard/index.js.map +1 -0
  44. package/dist/module/lib/Components/MediaCard/types.js +4 -0
  45. package/dist/module/lib/Components/MediaCard/types.js.map +1 -0
  46. package/dist/module/lib/Components/PageIndicator/PageIndicator.js +2 -2
  47. package/dist/module/lib/Components/SegmentedControl/usePillLayout.js +1 -1
  48. package/dist/module/lib/Components/Stepper/Stepper.js +1 -1
  49. package/dist/module/lib/Components/Switch/BaseSwitch.js +1 -1
  50. package/dist/module/lib/Components/TabBar/TabBar.js +4 -4
  51. package/dist/module/lib/Components/ThemeProvider/ThemeProvider.test.js +22 -20
  52. package/dist/module/lib/Components/ThemeProvider/ThemeProvider.test.js.map +1 -1
  53. package/dist/module/lib/Components/TriggerButton/TriggerButton.js +197 -0
  54. package/dist/module/lib/Components/TriggerButton/TriggerButton.js.map +1 -0
  55. package/dist/module/lib/Components/TriggerButton/TriggerButton.mdx +44 -0
  56. package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js +170 -0
  57. package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js.map +1 -0
  58. package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js +146 -0
  59. package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js.map +1 -0
  60. package/dist/module/lib/Components/TriggerButton/index.js +5 -0
  61. package/dist/module/lib/Components/TriggerButton/index.js.map +1 -0
  62. package/dist/module/lib/Components/TriggerButton/types.js +4 -0
  63. package/dist/module/lib/Components/TriggerButton/types.js.map +1 -0
  64. package/dist/module/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.js.map +1 -1
  65. package/dist/module/lib/Components/index.js +3 -0
  66. package/dist/module/lib/Components/index.js.map +1 -1
  67. package/dist/module/lib/Symbols/Icons/NanoGen5.js +49 -0
  68. package/dist/module/lib/Symbols/Icons/NanoGen5.js.map +1 -0
  69. package/dist/module/lib/Symbols/index.js +1 -0
  70. package/dist/module/lib/Symbols/index.js.map +1 -1
  71. package/dist/typescript/src/lib/Components/Card/Card.d.ts.map +1 -1
  72. package/dist/typescript/src/lib/Components/InteractiveIcon/InteractiveIcon.d.ts +1 -1
  73. package/dist/typescript/src/lib/Components/InteractiveIcon/InteractiveIcon.d.ts.map +1 -1
  74. package/dist/typescript/src/lib/Components/InteractiveIcon/types.d.ts +8 -0
  75. package/dist/typescript/src/lib/Components/InteractiveIcon/types.d.ts.map +1 -1
  76. package/dist/typescript/src/lib/Components/MediaBanner/MediaBanner.d.ts +16 -0
  77. package/dist/typescript/src/lib/Components/MediaBanner/MediaBanner.d.ts.map +1 -0
  78. package/dist/typescript/src/lib/Components/MediaBanner/index.d.ts +3 -0
  79. package/dist/typescript/src/lib/Components/MediaBanner/index.d.ts.map +1 -0
  80. package/dist/typescript/src/lib/Components/MediaBanner/types.d.ts +42 -0
  81. package/dist/typescript/src/lib/Components/MediaBanner/types.d.ts.map +1 -0
  82. package/dist/typescript/src/lib/Components/MediaCard/MediaCard.d.ts +32 -0
  83. package/dist/typescript/src/lib/Components/MediaCard/MediaCard.d.ts.map +1 -0
  84. package/dist/typescript/src/lib/Components/MediaCard/index.d.ts +3 -0
  85. package/dist/typescript/src/lib/Components/MediaCard/index.d.ts.map +1 -0
  86. package/dist/typescript/src/lib/Components/MediaCard/types.d.ts +38 -0
  87. package/dist/typescript/src/lib/Components/MediaCard/types.d.ts.map +1 -0
  88. package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts +26 -0
  89. package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts.map +1 -0
  90. package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts +3 -0
  91. package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts.map +1 -0
  92. package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts +38 -0
  93. package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts.map +1 -0
  94. package/dist/typescript/src/lib/Components/index.d.ts +3 -0
  95. package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
  96. package/dist/typescript/src/lib/Symbols/Icons/NanoGen5.d.ts +35 -0
  97. package/dist/typescript/src/lib/Symbols/Icons/NanoGen5.d.ts.map +1 -0
  98. package/dist/typescript/src/lib/Symbols/index.d.ts +1 -0
  99. package/dist/typescript/src/lib/Symbols/index.d.ts.map +1 -1
  100. package/package.json +3 -3
  101. package/src/i18n/locales/de.json +3 -0
  102. package/src/i18n/locales/en.json +3 -0
  103. package/src/i18n/locales/es.json +3 -0
  104. package/src/i18n/locales/fr.json +3 -0
  105. package/src/i18n/locales/ja.json +3 -0
  106. package/src/i18n/locales/ko.json +3 -0
  107. package/src/i18n/locales/pt.json +3 -0
  108. package/src/i18n/locales/ru.json +3 -0
  109. package/src/i18n/locales/th.json +3 -0
  110. package/src/i18n/locales/tr.json +3 -0
  111. package/src/i18n/locales/zh.json +3 -0
  112. package/src/lib/Components/AmountDisplay/AmountDisplay.tsx +20 -20
  113. package/src/lib/Components/BottomSheet/BottomSheet.stories.tsx +9 -9
  114. package/src/lib/Components/Card/Card.tsx +38 -33
  115. package/src/lib/Components/InteractiveIcon/InteractiveIcon.tsx +26 -4
  116. package/src/lib/Components/InteractiveIcon/types.ts +8 -0
  117. package/src/lib/Components/Link/Link.mdx +1 -0
  118. package/src/lib/Components/MediaBanner/MediaBanner.mdx +150 -0
  119. package/src/lib/Components/MediaBanner/MediaBanner.stories.tsx +143 -0
  120. package/src/lib/Components/MediaBanner/MediaBanner.test.tsx +77 -0
  121. package/src/lib/Components/MediaBanner/MediaBanner.tsx +172 -0
  122. package/src/lib/Components/MediaBanner/index.ts +2 -0
  123. package/src/lib/Components/MediaBanner/types.ts +44 -0
  124. package/src/lib/Components/MediaCard/MediaCard.mdx +111 -0
  125. package/src/lib/Components/MediaCard/MediaCard.stories.tsx +190 -0
  126. package/src/lib/Components/MediaCard/MediaCard.test.tsx +125 -0
  127. package/src/lib/Components/MediaCard/MediaCard.tsx +203 -0
  128. package/src/lib/Components/MediaCard/index.ts +2 -0
  129. package/src/lib/Components/MediaCard/types.ts +39 -0
  130. package/src/lib/Components/ThemeProvider/ThemeProvider.test.tsx +16 -18
  131. package/src/lib/Components/TriggerButton/TriggerButton.mdx +44 -0
  132. package/src/lib/Components/TriggerButton/TriggerButton.stories.tsx +132 -0
  133. package/src/lib/Components/TriggerButton/TriggerButton.test.tsx +157 -0
  134. package/src/lib/Components/TriggerButton/TriggerButton.tsx +228 -0
  135. package/src/lib/Components/TriggerButton/index.ts +2 -0
  136. package/src/lib/Components/TriggerButton/types.ts +38 -0
  137. package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.tsx +1 -1
  138. package/src/lib/Components/index.ts +3 -0
  139. package/src/lib/Symbols/Icons/NanoGen5.tsx +44 -0
  140. package/src/lib/Symbols/index.ts +1 -0
@@ -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
+ });
@@ -0,0 +1,203 @@
1
+ import { useState } from 'react';
2
+ import { Image, StyleSheet, View } from 'react-native';
3
+ import { useCommonTranslation } from '../../../i18n';
4
+ import { useStyleSheet } from '../../../styles';
5
+ import { Close } from '../../Symbols';
6
+ import { InteractiveIcon } from '../InteractiveIcon';
7
+ import { LinearGradient, Pressable, Text } from '../Utility';
8
+ import { MediaCardProps, MediaCardTitleProps } from './types';
9
+
10
+ const CARD_HEIGHT = 164;
11
+
12
+ const useStyles = () =>
13
+ useStyleSheet(
14
+ (t) => ({
15
+ root: {
16
+ position: 'relative',
17
+ width: t.sizes.full,
18
+ height: CARD_HEIGHT,
19
+ borderRadius: t.borderRadius.md,
20
+ overflow: 'hidden',
21
+ flexDirection: 'column',
22
+ alignItems: 'flex-start',
23
+ justifyContent: 'flex-end',
24
+ backgroundColor: t.colors.bg.muted,
25
+ },
26
+ content: {
27
+ flexDirection: 'column',
28
+ alignItems: 'flex-start',
29
+ gap: t.spacings.s8,
30
+ width: t.sizes.full,
31
+ minWidth: 0,
32
+ padding: t.spacings.s12,
33
+ },
34
+ title: {
35
+ ...t.typographies.heading3SemiBold,
36
+ color: t.colors.text.white,
37
+ },
38
+ closeButton: {
39
+ position: 'absolute',
40
+ top: t.spacings.s12,
41
+ right: t.spacings.s12,
42
+ },
43
+ gradientOverlays: {
44
+ color: t.colors.text.black,
45
+ },
46
+ pressedOverlay: {
47
+ backgroundColor: t.colors.bg.mutedTransparentPressed,
48
+ },
49
+ }),
50
+ [],
51
+ );
52
+
53
+ /**
54
+ * Title text for the card, styled with heading typography and white color.
55
+ */
56
+ export const MediaCardTitle = ({
57
+ children,
58
+ lx = {},
59
+ style,
60
+ ref,
61
+ ...props
62
+ }: MediaCardTitleProps) => {
63
+ const styles = useStyles();
64
+
65
+ return (
66
+ <Text
67
+ ref={ref}
68
+ lx={lx}
69
+ style={StyleSheet.flatten([styles.title, style])}
70
+ numberOfLines={3}
71
+ ellipsizeMode='tail'
72
+ {...props}
73
+ >
74
+ {children}
75
+ </Text>
76
+ );
77
+ };
78
+
79
+ MediaCardTitle.displayName = 'MediaCardTitle';
80
+
81
+ const GradientOverlays = () => {
82
+ const styles = useStyles();
83
+
84
+ return (
85
+ <>
86
+ <LinearGradient
87
+ direction='to-top'
88
+ stops={[
89
+ { color: styles.gradientOverlays.color, opacity: 0.8, offset: 0 },
90
+ { color: styles.gradientOverlays.color, opacity: 0, offset: 0.75 },
91
+ ]}
92
+ style={StyleSheet.absoluteFill}
93
+ pointerEvents='none'
94
+ accessible={false}
95
+ />
96
+
97
+ <LinearGradient
98
+ direction={45}
99
+ stops={[
100
+ { color: styles.gradientOverlays.color, opacity: 0, offset: 0.6 },
101
+ { color: styles.gradientOverlays.color, opacity: 0.8 },
102
+ ]}
103
+ style={StyleSheet.absoluteFill}
104
+ pointerEvents='none'
105
+ accessible={false}
106
+ />
107
+ </>
108
+ );
109
+ };
110
+
111
+ /**
112
+ * A media card component for displaying a full-bleed background image with
113
+ * composable content and a close button, using gradient overlays to ensure
114
+ * readability.
115
+ *
116
+ * @example
117
+ * import { MediaCard, MediaCardTitle } from '@ledgerhq/lumen-ui-rnative';
118
+ * import { Tag } from '@ledgerhq/lumen-ui-rnative';
119
+ *
120
+ * <MediaCard imageUrl="/image.jpg" onPress={() => {}} onClose={() => {}}>
121
+ * <Tag label="New" size="md" />
122
+ * <MediaCardTitle>Card title</MediaCardTitle>
123
+ * </MediaCard>
124
+ *
125
+ * // Without close button
126
+ * <MediaCard imageUrl="/image.jpg" onPress={() => {}}>
127
+ * <MediaCardTitle>Card title</MediaCardTitle>
128
+ * </MediaCard>
129
+ */
130
+ export const MediaCard = ({
131
+ ref,
132
+ children,
133
+ imageUrl,
134
+ onPress,
135
+ onClose,
136
+ closeAccessibilityLabel,
137
+ lx = {},
138
+ style,
139
+ ...pressableProps
140
+ }: MediaCardProps) => {
141
+ const { t } = useCommonTranslation();
142
+ const [imageLoadError, setImageLoadError] = useState(false);
143
+ const showImage = imageUrl && !imageLoadError;
144
+
145
+ const styles = useStyles();
146
+
147
+ return (
148
+ <Pressable
149
+ ref={ref}
150
+ lx={lx}
151
+ style={StyleSheet.flatten([styles.root, style])}
152
+ accessibilityRole={onPress ? 'button' : undefined}
153
+ onPress={onPress}
154
+ {...pressableProps}
155
+ >
156
+ {({ pressed }) => (
157
+ <>
158
+ {showImage && (
159
+ <Image
160
+ source={{ uri: imageUrl }}
161
+ style={[
162
+ StyleSheet.absoluteFill,
163
+ imageLoadError && { opacity: 0 },
164
+ ]}
165
+ accessible={false}
166
+ onError={() => setImageLoadError(true)}
167
+ testID='media-card-image'
168
+ />
169
+ )}
170
+
171
+ <GradientOverlays />
172
+
173
+ <View style={styles.content}>{children}</View>
174
+
175
+ {onClose && (
176
+ <InteractiveIcon
177
+ iconType='stroked'
178
+ appearance='white'
179
+ style={styles.closeButton}
180
+ onPress={onClose}
181
+ accessibilityLabel={
182
+ closeAccessibilityLabel || t('common.closeAriaLabel')
183
+ }
184
+ testID='media-card-close-button'
185
+ >
186
+ <Close size={20} />
187
+ </InteractiveIcon>
188
+ )}
189
+
190
+ {pressed && (
191
+ <View
192
+ style={[StyleSheet.absoluteFill, styles.pressedOverlay]}
193
+ pointerEvents='none'
194
+ accessible={false}
195
+ />
196
+ )}
197
+ </>
198
+ )}
199
+ </Pressable>
200
+ );
201
+ };
202
+
203
+ MediaCard.displayName = 'MediaCard';
@@ -0,0 +1,2 @@
1
+ export { MediaCard, MediaCardTitle } from './MediaCard';
2
+ export * from './types';
@@ -0,0 +1,39 @@
1
+ import type { ReactNode } from 'react';
2
+ import { StyledPressableProps, StyledTextProps } from '../../../styles';
3
+
4
+ /**
5
+ * Props for the MediaCard root component
6
+ */
7
+ export type MediaCardProps = {
8
+ /**
9
+ * The source URL for the background image.
10
+ */
11
+ imageUrl: string;
12
+ /**
13
+ * Callback fired when the card is pressed.
14
+ */
15
+ onPress?: () => void;
16
+ /**
17
+ * Callback fired when the close button is pressed.
18
+ */
19
+ onClose?: () => void;
20
+ /**
21
+ * Optional accessibility label for the close button.
22
+ */
23
+ closeAccessibilityLabel?: string;
24
+ /**
25
+ * The card content — typically a `MediaCardTitle` and optional
26
+ * leading content such as tags or icons.
27
+ */
28
+ children: ReactNode;
29
+ } & Omit<StyledPressableProps, 'children' | 'onPress'>;
30
+
31
+ /**
32
+ * Props for the MediaCardTitle component
33
+ */
34
+ export type MediaCardTitleProps = {
35
+ /**
36
+ * The title text or custom content.
37
+ */
38
+ children: ReactNode;
39
+ } & Omit<StyledTextProps, 'children'>;
@@ -1,30 +1,28 @@
1
1
  import { describe, expect, it } from '@jest/globals';
2
+ import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
2
3
  import { render, screen } from '@testing-library/react-native';
3
- import { Text } from 'react-native';
4
+ import { ChevronBigLeft, Wallet } from '../../Symbols';
5
+ import { Button } from '../Button';
6
+ import { Spot } from '../Spot';
7
+ import { Tile } from '../Tile/Tile';
8
+ import { Box, Text } from '../Utility';
4
9
  import { ThemeProvider } from './ThemeProvider';
5
10
 
6
- const themes: any = {
7
- light: {
8
- typographies: {
9
- xs: {},
10
- },
11
- },
12
- dark: {
13
- typographies: {
14
- xs: {},
15
- },
16
- },
17
- };
18
-
19
11
  describe('ThemeProvider', () => {
20
12
  it('renders children correctly', () => {
21
13
  render(
22
- <ThemeProvider themes={themes}>
23
- <Text testID='child'>Hello World</Text>
14
+ <ThemeProvider themes={ledgerLiveThemes}>
15
+ <Button testID='child'>Hello World</Button>
16
+ <Tile>Tile</Tile>
17
+ <Spot appearance='icon' icon={Wallet} />
18
+ <Box>
19
+ <Text>Text</Text>
20
+ </Box>
21
+ <ChevronBigLeft />
24
22
  </ThemeProvider>,
25
23
  );
26
24
 
27
- expect(screen.getByTestId('child'));
28
- expect(screen.getByText('Hello World'));
25
+ expect(screen.getByTestId('child')).toBeTruthy();
26
+ expect(screen.getByText('Hello World')).toBeTruthy();
29
27
  });
30
28
  });
@@ -0,0 +1,44 @@
1
+ import { CustomTabs, Tab } from '../../../../.storybook/components';
2
+ import { Meta, Canvas, Controls } from '@storybook/addon-docs/blocks';
3
+ import * as TriggerButtonStories from './TriggerButton.stories';
4
+
5
+ <Meta title='Action/TriggerButton' of={TriggerButtonStories} />
6
+
7
+ <CustomTabs>
8
+ <Tab label="Overview ">
9
+
10
+ # TriggerButton
11
+
12
+ ## Introduction
13
+
14
+ A specialized trigger button designed exclusively for select and dropdown patterns. It displays a label with an optional leading icon and a permanent trailing chevron indicator.
15
+
16
+ > View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=6389-45680&m=dev).
17
+
18
+ > **Important**: This component should only be used as a trigger inside a Select or dropdown. For standalone actions, use [Button](/docs/action-button--docs) or [IconButton](/docs/action-iconbutton--docs) instead.
19
+
20
+ ## Properties
21
+
22
+ <Canvas of={TriggerButtonStories.Base} />
23
+ <Controls of={TriggerButtonStories.Base} />
24
+
25
+ ## Appearance
26
+
27
+ Three appearances are available: `gray` (default), `transparent`, and `no-background`.
28
+
29
+ <Canvas of={TriggerButtonStories.AppearanceShowcase} />
30
+
31
+ ## Sizes
32
+
33
+ <Canvas of={TriggerButtonStories.SizeShowcase} />
34
+
35
+ ## Icon Types
36
+
37
+ The `iconType` prop controls the padding scheme based on the leading icon's shape:
38
+
39
+ - **`flat`**: Standard padding for interface icons (line icons without background).
40
+ - **`rounded`**: Tighter left padding for circular icons with their own background (e.g., crypto icons).
41
+
42
+ <Canvas of={TriggerButtonStories.IconTypeShowcase} />
43
+ </Tab>
44
+ </CustomTabs>