@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.
- package/dist/module/i18n/locales/de.json +3 -0
- package/dist/module/i18n/locales/en.json +3 -0
- package/dist/module/i18n/locales/es.json +3 -0
- package/dist/module/i18n/locales/fr.json +3 -0
- package/dist/module/i18n/locales/ja.json +3 -0
- package/dist/module/i18n/locales/ko.json +3 -0
- package/dist/module/i18n/locales/pt.json +3 -0
- package/dist/module/i18n/locales/ru.json +3 -0
- package/dist/module/i18n/locales/th.json +3 -0
- package/dist/module/i18n/locales/tr.json +3 -0
- package/dist/module/i18n/locales/zh.json +3 -0
- package/dist/module/lib/Animations/Pulse/Pulse.js +1 -1
- package/dist/module/lib/Animations/Spin/Spin.js +1 -1
- package/dist/module/lib/Components/AmountDisplay/AmountDisplay.js +21 -21
- package/dist/module/lib/Components/AmountDisplay/AmountDisplay.js.map +1 -1
- package/dist/module/lib/Components/AmountInput/AmountInput.js +3 -3
- package/dist/module/lib/Components/BaseInput/BaseInput.js +1 -1
- package/dist/module/lib/Components/BottomSheet/BottomSheet.stories.js.map +1 -1
- package/dist/module/lib/Components/Card/Card.js +39 -29
- package/dist/module/lib/Components/Card/Card.js.map +1 -1
- package/dist/module/lib/Components/InteractiveIcon/InteractiveIcon.js +22 -2
- package/dist/module/lib/Components/InteractiveIcon/InteractiveIcon.js.map +1 -1
- package/dist/module/lib/Components/Link/Link.mdx +1 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.js +183 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.js.map +1 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.mdx +111 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.stories.js +199 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.stories.js.map +1 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.test.js +140 -0
- package/dist/module/lib/Components/MediaCard/MediaCard.test.js.map +1 -0
- package/dist/module/lib/Components/MediaCard/index.js +5 -0
- package/dist/module/lib/Components/MediaCard/index.js.map +1 -0
- package/dist/module/lib/Components/MediaCard/types.js +4 -0
- package/dist/module/lib/Components/MediaCard/types.js.map +1 -0
- package/dist/module/lib/Components/PageIndicator/PageIndicator.js +2 -2
- package/dist/module/lib/Components/SegmentedControl/usePillLayout.js +1 -1
- package/dist/module/lib/Components/Stepper/Stepper.js +1 -1
- package/dist/module/lib/Components/Switch/BaseSwitch.js +1 -1
- package/dist/module/lib/Components/TabBar/TabBar.js +4 -4
- package/dist/module/lib/Components/ThemeProvider/ThemeProvider.test.js +22 -20
- package/dist/module/lib/Components/ThemeProvider/ThemeProvider.test.js.map +1 -1
- package/dist/module/lib/Components/TriggerButton/TriggerButton.js +197 -0
- package/dist/module/lib/Components/TriggerButton/TriggerButton.js.map +1 -0
- package/dist/module/lib/Components/TriggerButton/TriggerButton.mdx +44 -0
- package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js +170 -0
- package/dist/module/lib/Components/TriggerButton/TriggerButton.stories.js.map +1 -0
- package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js +146 -0
- package/dist/module/lib/Components/TriggerButton/TriggerButton.test.js.map +1 -0
- package/dist/module/lib/Components/TriggerButton/index.js +5 -0
- package/dist/module/lib/Components/TriggerButton/index.js.map +1 -0
- package/dist/module/lib/Components/TriggerButton/types.js +4 -0
- package/dist/module/lib/Components/TriggerButton/types.js.map +1 -0
- package/dist/module/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.js.map +1 -1
- package/dist/module/lib/Components/index.js +2 -0
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/module/lib/Symbols/Icons/NanoGen5.js +49 -0
- package/dist/module/lib/Symbols/Icons/NanoGen5.js.map +1 -0
- package/dist/module/lib/Symbols/index.js +1 -0
- package/dist/module/lib/Symbols/index.js.map +1 -1
- package/dist/typescript/src/lib/Components/Card/Card.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/InteractiveIcon/InteractiveIcon.d.ts +1 -1
- package/dist/typescript/src/lib/Components/InteractiveIcon/InteractiveIcon.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/InteractiveIcon/types.d.ts +8 -0
- package/dist/typescript/src/lib/Components/InteractiveIcon/types.d.ts.map +1 -1
- package/dist/typescript/src/lib/Components/MediaCard/MediaCard.d.ts +32 -0
- package/dist/typescript/src/lib/Components/MediaCard/MediaCard.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaCard/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/MediaCard/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaCard/types.d.ts +38 -0
- package/dist/typescript/src/lib/Components/MediaCard/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts +26 -0
- package/dist/typescript/src/lib/Components/TriggerButton/TriggerButton.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/TriggerButton/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts +38 -0
- package/dist/typescript/src/lib/Components/TriggerButton/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/index.d.ts +2 -0
- package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
- package/dist/typescript/src/lib/Symbols/Icons/NanoGen5.d.ts +35 -0
- package/dist/typescript/src/lib/Symbols/Icons/NanoGen5.d.ts.map +1 -0
- package/dist/typescript/src/lib/Symbols/index.d.ts +1 -0
- package/dist/typescript/src/lib/Symbols/index.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/i18n/locales/de.json +3 -0
- package/src/i18n/locales/en.json +3 -0
- package/src/i18n/locales/es.json +3 -0
- package/src/i18n/locales/fr.json +3 -0
- package/src/i18n/locales/ja.json +3 -0
- package/src/i18n/locales/ko.json +3 -0
- package/src/i18n/locales/pt.json +3 -0
- package/src/i18n/locales/ru.json +3 -0
- package/src/i18n/locales/th.json +3 -0
- package/src/i18n/locales/tr.json +3 -0
- package/src/i18n/locales/zh.json +3 -0
- package/src/lib/Components/AmountDisplay/AmountDisplay.tsx +20 -20
- package/src/lib/Components/BottomSheet/BottomSheet.stories.tsx +9 -9
- package/src/lib/Components/Card/Card.tsx +38 -33
- package/src/lib/Components/InteractiveIcon/InteractiveIcon.tsx +26 -4
- package/src/lib/Components/InteractiveIcon/types.ts +8 -0
- package/src/lib/Components/Link/Link.mdx +1 -0
- package/src/lib/Components/MediaCard/MediaCard.mdx +111 -0
- package/src/lib/Components/MediaCard/MediaCard.stories.tsx +190 -0
- package/src/lib/Components/MediaCard/MediaCard.test.tsx +125 -0
- package/src/lib/Components/MediaCard/MediaCard.tsx +203 -0
- package/src/lib/Components/MediaCard/index.ts +2 -0
- package/src/lib/Components/MediaCard/types.ts +39 -0
- package/src/lib/Components/ThemeProvider/ThemeProvider.test.tsx +16 -18
- package/src/lib/Components/TriggerButton/TriggerButton.mdx +44 -0
- package/src/lib/Components/TriggerButton/TriggerButton.stories.tsx +132 -0
- package/src/lib/Components/TriggerButton/TriggerButton.test.tsx +157 -0
- package/src/lib/Components/TriggerButton/TriggerButton.tsx +228 -0
- package/src/lib/Components/TriggerButton/index.ts +2 -0
- package/src/lib/Components/TriggerButton/types.ts +38 -0
- package/src/lib/Components/Utility/Gradient/RadialGradient/RadialGradient.stories.tsx +1 -1
- package/src/lib/Components/index.ts +2 -0
- package/src/lib/Symbols/Icons/NanoGen5.tsx +44 -0
- 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
|
-
?
|
|
43
|
-
:
|
|
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
|
+
});
|