@ledgerhq/lumen-ui-rnative 0.1.12 → 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.
- package/dist/module/lib/Components/MediaBanner/MediaBanner.js +158 -0
- package/dist/module/lib/Components/MediaBanner/MediaBanner.js.map +1 -0
- package/dist/module/lib/Components/MediaBanner/MediaBanner.mdx +150 -0
- package/dist/module/lib/Components/MediaBanner/MediaBanner.stories.js +135 -0
- package/dist/module/lib/Components/MediaBanner/MediaBanner.stories.js.map +1 -0
- package/dist/module/lib/Components/MediaBanner/MediaBanner.test.js +83 -0
- package/dist/module/lib/Components/MediaBanner/MediaBanner.test.js.map +1 -0
- package/dist/module/lib/Components/MediaBanner/index.js +5 -0
- package/dist/module/lib/Components/MediaBanner/index.js.map +1 -0
- package/dist/module/lib/Components/MediaBanner/types.js +4 -0
- package/dist/module/lib/Components/MediaBanner/types.js.map +1 -0
- package/dist/module/lib/Components/index.js +1 -0
- package/dist/module/lib/Components/index.js.map +1 -1
- package/dist/typescript/src/lib/Components/MediaBanner/MediaBanner.d.ts +16 -0
- package/dist/typescript/src/lib/Components/MediaBanner/MediaBanner.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaBanner/index.d.ts +3 -0
- package/dist/typescript/src/lib/Components/MediaBanner/index.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/MediaBanner/types.d.ts +42 -0
- package/dist/typescript/src/lib/Components/MediaBanner/types.d.ts.map +1 -0
- package/dist/typescript/src/lib/Components/index.d.ts +1 -0
- package/dist/typescript/src/lib/Components/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/lib/Components/MediaBanner/MediaBanner.mdx +150 -0
- package/src/lib/Components/MediaBanner/MediaBanner.stories.tsx +143 -0
- package/src/lib/Components/MediaBanner/MediaBanner.test.tsx +77 -0
- package/src/lib/Components/MediaBanner/MediaBanner.tsx +172 -0
- package/src/lib/Components/MediaBanner/index.ts +2 -0
- package/src/lib/Components/MediaBanner/types.ts +44 -0
- package/src/lib/Components/index.ts +1 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { Image, StyleSheet } from 'react-native';
|
|
5
|
+
import { useCommonTranslation } from "../../../i18n/index.js";
|
|
6
|
+
import { useStyleSheet, useTheme } from "../../../styles/index.js";
|
|
7
|
+
import { Close } from "../../Symbols/index.js";
|
|
8
|
+
import { InteractiveIcon } from "../InteractiveIcon/index.js";
|
|
9
|
+
import { Box, LinearGradient, Pressable, Text } from "../Utility/index.js";
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
/**
|
|
12
|
+
* A promotional banner with a background image, title, description, and an optional close button.
|
|
13
|
+
*
|
|
14
|
+
* @see {@link https://ldls.vercel.app/?path=/docs/communication-mediabanner--docs Storybook}
|
|
15
|
+
*/
|
|
16
|
+
export function MediaBanner({
|
|
17
|
+
lx,
|
|
18
|
+
style,
|
|
19
|
+
imageUrl,
|
|
20
|
+
onClose,
|
|
21
|
+
closeAccessibilityLabel,
|
|
22
|
+
children,
|
|
23
|
+
...props
|
|
24
|
+
}) {
|
|
25
|
+
const {
|
|
26
|
+
t: translate
|
|
27
|
+
} = useCommonTranslation();
|
|
28
|
+
const {
|
|
29
|
+
theme: t
|
|
30
|
+
} = useTheme();
|
|
31
|
+
const [imageLoadError, setImageLoadError] = useState(false);
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setImageLoadError(false);
|
|
34
|
+
}, [imageUrl]);
|
|
35
|
+
const showImage = imageUrl && !imageLoadError;
|
|
36
|
+
const styles = useStyleSheet(t => ({
|
|
37
|
+
container: {
|
|
38
|
+
backgroundColor: t.colors.bg.surface,
|
|
39
|
+
borderRadius: t.borderRadius.md,
|
|
40
|
+
overflow: 'hidden',
|
|
41
|
+
flexDirection: 'row',
|
|
42
|
+
height: t.sizes.s72
|
|
43
|
+
},
|
|
44
|
+
contentWrapper: {
|
|
45
|
+
flex: 1,
|
|
46
|
+
overflow: 'hidden',
|
|
47
|
+
justifyContent: 'center',
|
|
48
|
+
paddingHorizontal: t.spacings.s12,
|
|
49
|
+
paddingVertical: t.spacings.s2
|
|
50
|
+
},
|
|
51
|
+
contentContainer: {
|
|
52
|
+
paddingVertical: t.spacings.s12,
|
|
53
|
+
gap: 4
|
|
54
|
+
},
|
|
55
|
+
closeButton: {
|
|
56
|
+
position: 'absolute',
|
|
57
|
+
top: 8.5,
|
|
58
|
+
right: 8.5
|
|
59
|
+
}
|
|
60
|
+
}), []);
|
|
61
|
+
return /*#__PURE__*/_jsxs(Pressable, {
|
|
62
|
+
lx: lx,
|
|
63
|
+
style: [styles.container, style],
|
|
64
|
+
...props,
|
|
65
|
+
children: [/*#__PURE__*/_jsx(Box, {
|
|
66
|
+
style: styles.contentWrapper,
|
|
67
|
+
children: /*#__PURE__*/_jsx(Box, {
|
|
68
|
+
style: styles.contentContainer,
|
|
69
|
+
children: children
|
|
70
|
+
})
|
|
71
|
+
}), /*#__PURE__*/_jsxs(Box, {
|
|
72
|
+
style: {
|
|
73
|
+
width: 120
|
|
74
|
+
},
|
|
75
|
+
children: [showImage && /*#__PURE__*/_jsx(Image, {
|
|
76
|
+
source: {
|
|
77
|
+
uri: imageUrl
|
|
78
|
+
},
|
|
79
|
+
style: StyleSheet.absoluteFill,
|
|
80
|
+
resizeMode: "cover",
|
|
81
|
+
onError: () => setImageLoadError(true),
|
|
82
|
+
accessible: false
|
|
83
|
+
}), /*#__PURE__*/_jsx(LinearGradient, {
|
|
84
|
+
direction: "to-topright",
|
|
85
|
+
stops: [{
|
|
86
|
+
color: t.colors.bg.black,
|
|
87
|
+
opacity: 0,
|
|
88
|
+
offset: 0.67
|
|
89
|
+
}, {
|
|
90
|
+
color: t.colors.bg.black,
|
|
91
|
+
opacity: 0.8
|
|
92
|
+
}],
|
|
93
|
+
style: StyleSheet.absoluteFill,
|
|
94
|
+
accessible: false,
|
|
95
|
+
pointerEvents: "none"
|
|
96
|
+
})]
|
|
97
|
+
}), onClose && /*#__PURE__*/_jsx(Box, {
|
|
98
|
+
style: styles.closeButton,
|
|
99
|
+
children: /*#__PURE__*/_jsx(InteractiveIcon, {
|
|
100
|
+
testID: "media-banner-close-button",
|
|
101
|
+
iconType: "stroked",
|
|
102
|
+
appearance: "white",
|
|
103
|
+
onPress: onClose,
|
|
104
|
+
accessibilityLabel: closeAccessibilityLabel || translate('components.banner.closeAriaLabel'),
|
|
105
|
+
children: /*#__PURE__*/_jsx(Close, {
|
|
106
|
+
size: 16
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
})]
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The title of the MediaBanner. Clamps at 1 line.
|
|
115
|
+
*/
|
|
116
|
+
export function MediaBannerTitle({
|
|
117
|
+
lx,
|
|
118
|
+
style,
|
|
119
|
+
children,
|
|
120
|
+
...props
|
|
121
|
+
}) {
|
|
122
|
+
const styles = useStyleSheet(t => ({
|
|
123
|
+
title: StyleSheet.flatten([t.typographies.body2SemiBold, {
|
|
124
|
+
color: t.colors.text.base
|
|
125
|
+
}])
|
|
126
|
+
}), []);
|
|
127
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
128
|
+
lx: lx,
|
|
129
|
+
style: [styles.title, style],
|
|
130
|
+
numberOfLines: 1,
|
|
131
|
+
...props,
|
|
132
|
+
children: children
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The description of the MediaBanner. Clamps at 2 lines.
|
|
138
|
+
*/
|
|
139
|
+
export function MediaBannerDescription({
|
|
140
|
+
lx,
|
|
141
|
+
style,
|
|
142
|
+
children,
|
|
143
|
+
...props
|
|
144
|
+
}) {
|
|
145
|
+
const styles = useStyleSheet(t => ({
|
|
146
|
+
description: StyleSheet.flatten([t.typographies.body3, {
|
|
147
|
+
color: t.colors.text.muted
|
|
148
|
+
}])
|
|
149
|
+
}), []);
|
|
150
|
+
return /*#__PURE__*/_jsx(Text, {
|
|
151
|
+
lx: lx,
|
|
152
|
+
style: [styles.description, style],
|
|
153
|
+
numberOfLines: 2,
|
|
154
|
+
...props,
|
|
155
|
+
children: children
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=MediaBanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useEffect","useState","Image","StyleSheet","useCommonTranslation","useStyleSheet","useTheme","Close","InteractiveIcon","Box","LinearGradient","Pressable","Text","jsx","_jsx","jsxs","_jsxs","MediaBanner","lx","style","imageUrl","onClose","closeAccessibilityLabel","children","props","t","translate","theme","imageLoadError","setImageLoadError","showImage","styles","container","backgroundColor","colors","bg","surface","borderRadius","md","overflow","flexDirection","height","sizes","s72","contentWrapper","flex","justifyContent","paddingHorizontal","spacings","s12","paddingVertical","s2","contentContainer","gap","closeButton","position","top","right","width","source","uri","absoluteFill","resizeMode","onError","accessible","direction","stops","color","black","opacity","offset","pointerEvents","testID","iconType","appearance","onPress","accessibilityLabel","size","MediaBannerTitle","title","flatten","typographies","body2SemiBold","text","base","numberOfLines","MediaBannerDescription","description","body3","muted"],"sourceRoot":"../../../../../src","sources":["lib/Components/MediaBanner/MediaBanner.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Meta, Story, Canvas, Controls } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as MediaBannerStories from './MediaBanner.stories';
|
|
3
|
+
import {
|
|
4
|
+
MediaBanner,
|
|
5
|
+
MediaBannerTitle,
|
|
6
|
+
MediaBannerDescription,
|
|
7
|
+
} from './MediaBanner';
|
|
8
|
+
import { Box } from '../Utility';
|
|
9
|
+
import { CustomTabs, Tab } from '../../../../.storybook/components';
|
|
10
|
+
import { DoVsDontRow, DoBlockItem, DontBlockItem } from '../../../../.storybook/components/DoVsDont';
|
|
11
|
+
import CommonRulesDoAndDont from '../../../../.storybook/components/DoVsDont/CommonRulesDoAndDont.mdx';
|
|
12
|
+
|
|
13
|
+
<Meta title='Components/MediaBanner' of={MediaBannerStories} />
|
|
14
|
+
|
|
15
|
+
# 🖼️ MediaBanner
|
|
16
|
+
|
|
17
|
+
<CustomTabs>
|
|
18
|
+
<Tab label="Overview">
|
|
19
|
+
|
|
20
|
+
## Introduction
|
|
21
|
+
|
|
22
|
+
MediaBanner is a promotional banner component that displays a background image alongside a title and description. It is designed for marketing or informational content and supports an optional close action for dismissibility.
|
|
23
|
+
|
|
24
|
+
> View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=11235-5982&m=dev).
|
|
25
|
+
|
|
26
|
+
## Anatomy
|
|
27
|
+
|
|
28
|
+
<Canvas of={MediaBannerStories.Base} />
|
|
29
|
+
|
|
30
|
+
- **MediaBanner**: Root pressable container with surface background, rounded corners, and a fixed height
|
|
31
|
+
- **MediaBannerTitle**: The main label of the banner (clamps at 1 line)
|
|
32
|
+
- **MediaBannerDescription**: Additional context below the title (clamps at 2 lines)
|
|
33
|
+
- **Image**: Background image displayed on the right side with a gradient overlay
|
|
34
|
+
- **Close button**: Optional InteractiveIcon rendered in the top-right corner via the `onClose` prop
|
|
35
|
+
|
|
36
|
+
## Properties
|
|
37
|
+
|
|
38
|
+
### Overview
|
|
39
|
+
|
|
40
|
+
<Canvas of={MediaBannerStories.Base} />
|
|
41
|
+
<Controls of={MediaBannerStories.Base} />
|
|
42
|
+
|
|
43
|
+
### Truncation
|
|
44
|
+
|
|
45
|
+
Title clamps at 1 line and description clamps at 2 lines when content overflows.
|
|
46
|
+
|
|
47
|
+
<Canvas of={MediaBannerStories.Truncation} />
|
|
48
|
+
|
|
49
|
+
### Broken Image
|
|
50
|
+
|
|
51
|
+
When the `imageUrl` fails to load, the broken image is hidden while the gradient overlay and image space are preserved. The banner remains functional and visually consistent.
|
|
52
|
+
|
|
53
|
+
<Canvas of={MediaBannerStories.WithBrokenImage} />
|
|
54
|
+
|
|
55
|
+
### Dismissible
|
|
56
|
+
|
|
57
|
+
When `onClose` is provided, the close button allows the user to dismiss the banner.
|
|
58
|
+
|
|
59
|
+
<Canvas of={MediaBannerStories.WithClose} />
|
|
60
|
+
|
|
61
|
+
## Accessibility
|
|
62
|
+
|
|
63
|
+
- The close button uses `InteractiveIcon` with proper press states
|
|
64
|
+
- The root element is a `Pressable` for interactive use cases
|
|
65
|
+
- Title and description use semantic `Text` elements
|
|
66
|
+
|
|
67
|
+
</Tab>
|
|
68
|
+
<Tab label="Implementation">
|
|
69
|
+
|
|
70
|
+
## Setup
|
|
71
|
+
|
|
72
|
+
Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
|
|
73
|
+
|
|
74
|
+
## Basic Usage
|
|
75
|
+
|
|
76
|
+
MediaBanner uses a composite component pattern with `MediaBannerTitle` and `MediaBannerDescription`:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import {
|
|
80
|
+
MediaBanner,
|
|
81
|
+
MediaBannerTitle,
|
|
82
|
+
MediaBannerDescription,
|
|
83
|
+
} from '@ledgerhq/lumen-ui-rnative';
|
|
84
|
+
|
|
85
|
+
function MyComponent() {
|
|
86
|
+
return (
|
|
87
|
+
<MediaBanner imageUrl='https://example.com/promo.jpg'>
|
|
88
|
+
<MediaBannerTitle>Firmware Update</MediaBannerTitle>
|
|
89
|
+
<MediaBannerDescription>
|
|
90
|
+
Keep your Nano updated!
|
|
91
|
+
</MediaBannerDescription>
|
|
92
|
+
</MediaBanner>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### With Close Button
|
|
98
|
+
|
|
99
|
+
Add an `onClose` callback to make the banner dismissible:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import {
|
|
103
|
+
MediaBanner,
|
|
104
|
+
MediaBannerTitle,
|
|
105
|
+
MediaBannerDescription,
|
|
106
|
+
} from '@ledgerhq/lumen-ui-rnative';
|
|
107
|
+
import { useState } from 'react';
|
|
108
|
+
|
|
109
|
+
function DismissibleBanner() {
|
|
110
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
111
|
+
|
|
112
|
+
if (!isVisible) return null;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<MediaBanner
|
|
116
|
+
imageUrl='https://example.com/promo.jpg'
|
|
117
|
+
onClose={() => setIsVisible(false)}
|
|
118
|
+
>
|
|
119
|
+
<MediaBannerTitle>
|
|
120
|
+
Earn Up to 12% APY With Staking Now!
|
|
121
|
+
</MediaBannerTitle>
|
|
122
|
+
<MediaBannerDescription>
|
|
123
|
+
Put your idle crypto to work. Start staking SOL, ETH, ATOM and more
|
|
124
|
+
directly from Ledger Live
|
|
125
|
+
</MediaBannerDescription>
|
|
126
|
+
</MediaBanner>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Layout Adjustments with lx
|
|
132
|
+
|
|
133
|
+
Use the `lx` prop for layout adjustments like margins or positioning:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<MediaBanner
|
|
137
|
+
imageUrl='https://example.com/promo.jpg'
|
|
138
|
+
lx={{ marginTop: 's16', marginBottom: 's8' }}
|
|
139
|
+
>
|
|
140
|
+
<MediaBannerTitle>With Margin</MediaBannerTitle>
|
|
141
|
+
<MediaBannerDescription>
|
|
142
|
+
This banner has layout adjustments via lx.
|
|
143
|
+
</MediaBannerDescription>
|
|
144
|
+
</MediaBanner>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
<CommonRulesDoAndDont />
|
|
148
|
+
|
|
149
|
+
</Tab>
|
|
150
|
+
</CustomTabs>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Button } from "../Button/index.js";
|
|
5
|
+
import { Box } from "../Utility/index.js";
|
|
6
|
+
import { MediaBanner, MediaBannerDescription, MediaBannerTitle } from "./MediaBanner.js";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
const IMAGE_URL = 'https://images.unsplash.com/photo-1663741954108-d15d514529ef';
|
|
9
|
+
const meta = {
|
|
10
|
+
component: MediaBanner,
|
|
11
|
+
title: 'Communication/MediaBanner',
|
|
12
|
+
subcomponents: {
|
|
13
|
+
MediaBannerTitle,
|
|
14
|
+
MediaBannerDescription
|
|
15
|
+
},
|
|
16
|
+
parameters: {
|
|
17
|
+
docs: {
|
|
18
|
+
source: {
|
|
19
|
+
language: 'tsx',
|
|
20
|
+
format: true,
|
|
21
|
+
type: 'code'
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
argTypes: {
|
|
26
|
+
imageUrl: {
|
|
27
|
+
control: 'text',
|
|
28
|
+
description: 'URL of the background image'
|
|
29
|
+
},
|
|
30
|
+
onClose: {
|
|
31
|
+
control: 'select',
|
|
32
|
+
description: 'Close action callback',
|
|
33
|
+
options: ['With Close', 'None'],
|
|
34
|
+
mapping: {
|
|
35
|
+
'With Close': () => {
|
|
36
|
+
console.log('Close clicked');
|
|
37
|
+
},
|
|
38
|
+
None: undefined
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
export default meta;
|
|
44
|
+
export const Base = {
|
|
45
|
+
args: {
|
|
46
|
+
imageUrl: IMAGE_URL
|
|
47
|
+
},
|
|
48
|
+
render: args => /*#__PURE__*/_jsx(Box, {
|
|
49
|
+
lx: {
|
|
50
|
+
width: 's400'
|
|
51
|
+
},
|
|
52
|
+
children: /*#__PURE__*/_jsxs(MediaBanner, {
|
|
53
|
+
...args,
|
|
54
|
+
children: [/*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
55
|
+
children: "Firmware Update"
|
|
56
|
+
}), /*#__PURE__*/_jsx(MediaBannerDescription, {
|
|
57
|
+
children: "Keep your Nano updated!"
|
|
58
|
+
})]
|
|
59
|
+
})
|
|
60
|
+
}),
|
|
61
|
+
parameters: {
|
|
62
|
+
docs: {
|
|
63
|
+
source: {
|
|
64
|
+
code: `
|
|
65
|
+
<MediaBanner imageUrl="https://images.unsplash.com/photo-1663741954108-d15d514529ef">
|
|
66
|
+
<MediaBannerTitle>Firmware Update</MediaBannerTitle>
|
|
67
|
+
<MediaBannerDescription>
|
|
68
|
+
Keep your Nano updated!
|
|
69
|
+
</MediaBannerDescription>
|
|
70
|
+
</MediaBanner>
|
|
71
|
+
`
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
export const Truncation = {
|
|
77
|
+
render: () => /*#__PURE__*/_jsx(Box, {
|
|
78
|
+
lx: {
|
|
79
|
+
width: 's400'
|
|
80
|
+
},
|
|
81
|
+
children: /*#__PURE__*/_jsxs(MediaBanner, {
|
|
82
|
+
imageUrl: IMAGE_URL,
|
|
83
|
+
onClose: () => console.log('close'),
|
|
84
|
+
children: [/*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
85
|
+
children: "Earn Up to 12% APY With Staking Now And Much More Rewards Awaiting You"
|
|
86
|
+
}), /*#__PURE__*/_jsx(MediaBannerDescription, {
|
|
87
|
+
children: "Put your idle crypto to work. Start staking SOL, ETH, ATOM and more directly from Ledger Live. Maximize your returns with our secure staking solutions."
|
|
88
|
+
})]
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
};
|
|
92
|
+
export const WithBrokenImage = {
|
|
93
|
+
render: () => /*#__PURE__*/_jsx(Box, {
|
|
94
|
+
lx: {
|
|
95
|
+
width: 's400'
|
|
96
|
+
},
|
|
97
|
+
children: /*#__PURE__*/_jsxs(MediaBanner, {
|
|
98
|
+
imageUrl: "https://broken-url.invalid/image.jpg",
|
|
99
|
+
onClose: () => console.log('close'),
|
|
100
|
+
children: [/*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
101
|
+
children: "Sorry!"
|
|
102
|
+
}), /*#__PURE__*/_jsx(MediaBannerDescription, {
|
|
103
|
+
children: "The image failed to load so the banner decided to gracefully hide it."
|
|
104
|
+
})]
|
|
105
|
+
})
|
|
106
|
+
})
|
|
107
|
+
};
|
|
108
|
+
export const WithClose = {
|
|
109
|
+
render: () => {
|
|
110
|
+
const [visible, setVisible] = useState(true);
|
|
111
|
+
if (!visible) {
|
|
112
|
+
return /*#__PURE__*/_jsx(Button, {
|
|
113
|
+
appearance: "transparent",
|
|
114
|
+
size: "sm",
|
|
115
|
+
onPress: () => setVisible(true),
|
|
116
|
+
children: "Show banner"
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return /*#__PURE__*/_jsx(Box, {
|
|
120
|
+
lx: {
|
|
121
|
+
width: 's400'
|
|
122
|
+
},
|
|
123
|
+
children: /*#__PURE__*/_jsxs(MediaBanner, {
|
|
124
|
+
imageUrl: IMAGE_URL,
|
|
125
|
+
onClose: () => setVisible(false),
|
|
126
|
+
children: [/*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
127
|
+
children: "Earn Up to 12% APY With Staking Now!"
|
|
128
|
+
}), /*#__PURE__*/_jsx(MediaBannerDescription, {
|
|
129
|
+
children: "Put your idle crypto to work. Start staking SOL, ETH, ATOM and more directly from Ledger Live"
|
|
130
|
+
})]
|
|
131
|
+
})
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
//# sourceMappingURL=MediaBanner.stories.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["useState","Button","Box","MediaBanner","MediaBannerDescription","MediaBannerTitle","jsx","_jsx","jsxs","_jsxs","IMAGE_URL","meta","component","title","subcomponents","parameters","docs","source","language","format","type","argTypes","imageUrl","control","description","onClose","options","mapping","With Close","console","log","None","undefined","Base","args","render","lx","width","children","code","Truncation","WithBrokenImage","WithClose","visible","setVisible","appearance","size","onPress"],"sourceRoot":"../../../../../src","sources":["lib/Components/MediaBanner/MediaBanner.stories.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, jest } from '@jest/globals';
|
|
4
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
5
|
+
import { fireEvent, render } from '@testing-library/react-native';
|
|
6
|
+
import { ThemeProvider } from "../ThemeProvider/ThemeProvider.js";
|
|
7
|
+
import { MediaBanner, MediaBannerTitle, MediaBannerDescription } from "./MediaBanner.js";
|
|
8
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
9
|
+
const TestWrapper = ({
|
|
10
|
+
children
|
|
11
|
+
}) => /*#__PURE__*/_jsx(ThemeProvider, {
|
|
12
|
+
themes: ledgerLiveThemes,
|
|
13
|
+
colorScheme: "dark",
|
|
14
|
+
locale: "en",
|
|
15
|
+
children: children
|
|
16
|
+
});
|
|
17
|
+
const IMAGE_URL = 'https://example.com/image.jpg';
|
|
18
|
+
describe('MediaBanner', () => {
|
|
19
|
+
it('should render title and description', () => {
|
|
20
|
+
const {
|
|
21
|
+
getByText
|
|
22
|
+
} = render(/*#__PURE__*/_jsx(TestWrapper, {
|
|
23
|
+
children: /*#__PURE__*/_jsxs(MediaBanner, {
|
|
24
|
+
imageUrl: IMAGE_URL,
|
|
25
|
+
children: [/*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
26
|
+
children: "Banner Title"
|
|
27
|
+
}), /*#__PURE__*/_jsx(MediaBannerDescription, {
|
|
28
|
+
children: "Banner description"
|
|
29
|
+
})]
|
|
30
|
+
})
|
|
31
|
+
}));
|
|
32
|
+
getByText('Banner Title');
|
|
33
|
+
getByText('Banner description');
|
|
34
|
+
});
|
|
35
|
+
it('should call onClose when close button is pressed', () => {
|
|
36
|
+
const handleClose = jest.fn();
|
|
37
|
+
const {
|
|
38
|
+
getByTestId
|
|
39
|
+
} = render(/*#__PURE__*/_jsx(TestWrapper, {
|
|
40
|
+
children: /*#__PURE__*/_jsx(MediaBanner, {
|
|
41
|
+
imageUrl: IMAGE_URL,
|
|
42
|
+
onClose: handleClose,
|
|
43
|
+
children: /*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
44
|
+
children: "Title"
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
}));
|
|
48
|
+
const closeButton = getByTestId('media-banner-close-button');
|
|
49
|
+
expect(closeButton).toBeTruthy();
|
|
50
|
+
fireEvent.press(closeButton);
|
|
51
|
+
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
52
|
+
});
|
|
53
|
+
it('should apply surface background color', () => {
|
|
54
|
+
const {
|
|
55
|
+
getByTestId
|
|
56
|
+
} = render(/*#__PURE__*/_jsx(TestWrapper, {
|
|
57
|
+
children: /*#__PURE__*/_jsx(MediaBanner, {
|
|
58
|
+
testID: "media-banner",
|
|
59
|
+
imageUrl: IMAGE_URL,
|
|
60
|
+
children: /*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
61
|
+
children: "Title"
|
|
62
|
+
})
|
|
63
|
+
})
|
|
64
|
+
}));
|
|
65
|
+
const banner = getByTestId('media-banner');
|
|
66
|
+
expect(banner.props.style.backgroundColor).toBe(ledgerLiveThemes.dark.colors.bg.surface);
|
|
67
|
+
});
|
|
68
|
+
it('should render with imageUrl prop', () => {
|
|
69
|
+
const {
|
|
70
|
+
getByTestId
|
|
71
|
+
} = render(/*#__PURE__*/_jsx(TestWrapper, {
|
|
72
|
+
children: /*#__PURE__*/_jsx(MediaBanner, {
|
|
73
|
+
testID: "media-banner",
|
|
74
|
+
imageUrl: IMAGE_URL,
|
|
75
|
+
children: /*#__PURE__*/_jsx(MediaBannerTitle, {
|
|
76
|
+
children: "Title"
|
|
77
|
+
})
|
|
78
|
+
})
|
|
79
|
+
}));
|
|
80
|
+
expect(getByTestId('media-banner')).toBeTruthy();
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
//# sourceMappingURL=MediaBanner.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["describe","it","expect","jest","ledgerLiveThemes","fireEvent","render","ThemeProvider","MediaBanner","MediaBannerTitle","MediaBannerDescription","jsx","_jsx","jsxs","_jsxs","TestWrapper","children","themes","colorScheme","locale","IMAGE_URL","getByText","imageUrl","handleClose","fn","getByTestId","onClose","closeButton","toBeTruthy","press","toHaveBeenCalledTimes","testID","banner","props","style","backgroundColor","toBe","dark","colors","bg","surface"],"sourceRoot":"../../../../../src","sources":["lib/Components/MediaBanner/MediaBanner.test.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../../../src","sources":["lib/Components/MediaBanner/index.ts"],"mappings":";;AAAA,cAAc,kBAAe;AAC7B,cAAc,YAAS","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../../../src","sources":["lib/Components/MediaBanner/types.ts"],"mappings":"","ignoreList":[]}
|
|
@@ -18,6 +18,7 @@ export * from "./IconButton/index.js";
|
|
|
18
18
|
export * from "./InteractiveIcon/index.js";
|
|
19
19
|
export * from "./Link/index.js";
|
|
20
20
|
export * from "./ListItem/index.js";
|
|
21
|
+
export * from "./MediaBanner/index.js";
|
|
21
22
|
export * from "./MediaCard/index.js";
|
|
22
23
|
export * from "./NavBar/index.js";
|
|
23
24
|
export * from "./PageIndicator/index.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":[],"sourceRoot":"../../../../src","sources":["lib/Components/index.ts"],"mappings":";;AAAA,cAAc,yBAAgB;AAC9B,cAAc,0BAAiB;AAC/B,cAAc,wBAAe;AAC7B,cAAc,mBAAU;AACxB,cAAc,mBAAU;AACxB,cAAc,wBAAe;AAC7B,cAAc,mBAAU;AACxB,cAAc,0BAAiB;AAC/B,cAAc,iBAAQ;AACtB,cAAc,uBAAc;AAC5B,cAAc,0BAAiB;AAC/B,cAAc,qBAAY;AAC1B,cAAc,oBAAW;AACzB,cAAc,iBAAQ;AACtB,cAAc,uBAAc;AAC5B,cAAc,4BAAmB;AACjC,cAAc,iBAAQ;AACtB,cAAc,qBAAY;AAC1B,cAAc,sBAAa;AAC3B,cAAc,mBAAU;AACxB,cAAc,0BAAiB;AAC/B,cAAc,wBAAe;AAC7B,cAAc,6BAAoB;AAClC,cAAc,mBAAU;AACxB,cAAc,qBAAY;AAC1B,cAAc,oBAAW;AACzB,cAAc,iBAAQ;AACtB,cAAc,oBAAW;AACzB,cAAc,sBAAa;AAC3B,cAAc,mBAAU;AACxB,cAAc,mBAAU;AACxB,cAAc,gBAAO;AACrB,cAAc,oBAAW;AACzB,cAAc,sBAAa;AAC3B,cAAc,0BAAiB;AAC/B,cAAc,iBAAQ;AACtB,cAAc,uBAAc;AAC5B,cAAc,oBAAW","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":[],"sourceRoot":"../../../../src","sources":["lib/Components/index.ts"],"mappings":";;AAAA,cAAc,yBAAgB;AAC9B,cAAc,0BAAiB;AAC/B,cAAc,wBAAe;AAC7B,cAAc,mBAAU;AACxB,cAAc,mBAAU;AACxB,cAAc,wBAAe;AAC7B,cAAc,mBAAU;AACxB,cAAc,0BAAiB;AAC/B,cAAc,iBAAQ;AACtB,cAAc,uBAAc;AAC5B,cAAc,0BAAiB;AAC/B,cAAc,qBAAY;AAC1B,cAAc,oBAAW;AACzB,cAAc,iBAAQ;AACtB,cAAc,uBAAc;AAC5B,cAAc,4BAAmB;AACjC,cAAc,iBAAQ;AACtB,cAAc,qBAAY;AAC1B,cAAc,wBAAe;AAC7B,cAAc,sBAAa;AAC3B,cAAc,mBAAU;AACxB,cAAc,0BAAiB;AAC/B,cAAc,wBAAe;AAC7B,cAAc,6BAAoB;AAClC,cAAc,mBAAU;AACxB,cAAc,qBAAY;AAC1B,cAAc,oBAAW;AACzB,cAAc,iBAAQ;AACtB,cAAc,oBAAW;AACzB,cAAc,sBAAa;AAC3B,cAAc,mBAAU;AACxB,cAAc,mBAAU;AACxB,cAAc,gBAAO;AACrB,cAAc,oBAAW;AACzB,cAAc,sBAAa;AAC3B,cAAc,0BAAiB;AAC/B,cAAc,iBAAQ;AACtB,cAAc,uBAAc;AAC5B,cAAc,oBAAW","ignoreList":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MediaBannerDescriptionProps, MediaBannerProps, MediaBannerTitleProps } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A promotional banner with a background image, title, description, and an optional close button.
|
|
4
|
+
*
|
|
5
|
+
* @see {@link https://ldls.vercel.app/?path=/docs/communication-mediabanner--docs Storybook}
|
|
6
|
+
*/
|
|
7
|
+
export declare function MediaBanner({ lx, style, imageUrl, onClose, closeAccessibilityLabel, children, ...props }: MediaBannerProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
/**
|
|
9
|
+
* The title of the MediaBanner. Clamps at 1 line.
|
|
10
|
+
*/
|
|
11
|
+
export declare function MediaBannerTitle({ lx, style, children, ...props }: MediaBannerTitleProps): import("react/jsx-runtime").JSX.Element;
|
|
12
|
+
/**
|
|
13
|
+
* The description of the MediaBanner. Clamps at 2 lines.
|
|
14
|
+
*/
|
|
15
|
+
export declare function MediaBannerDescription({ lx, style, children, ...props }: MediaBannerDescriptionProps): import("react/jsx-runtime").JSX.Element;
|
|
16
|
+
//# sourceMappingURL=MediaBanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MediaBanner.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/MediaBanner/MediaBanner.tsx"],"names":[],"mappings":"AAOA,OAAO,EACL,2BAA2B,EAC3B,gBAAgB,EAChB,qBAAqB,EACtB,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,EAC1B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,OAAO,EACP,uBAAuB,EACvB,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,gBAAgB,2CAoFlB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,EAAE,EACF,KAAK,EACL,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,qBAAqB,2CAkBvB;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,EAAE,EACF,KAAK,EACL,QAAQ,EACR,GAAG,KAAK,EACT,EAAE,2BAA2B,2CAuB7B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/MediaBanner/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,SAAS,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyledPressableProps, StyledTextProps } from '../../../styles';
|
|
3
|
+
/**
|
|
4
|
+
* Props for the MediaBanner root component.
|
|
5
|
+
*/
|
|
6
|
+
export type MediaBannerProps = {
|
|
7
|
+
/**
|
|
8
|
+
* URL of the background image displayed on the right side.
|
|
9
|
+
*/
|
|
10
|
+
imageUrl: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional close action callback.
|
|
13
|
+
*/
|
|
14
|
+
onClose?: () => void;
|
|
15
|
+
/**
|
|
16
|
+
* Optional accessibility label for the close button.
|
|
17
|
+
*/
|
|
18
|
+
closeAccessibilityLabel?: string;
|
|
19
|
+
/**
|
|
20
|
+
* The banner content (MediaBannerTitle, MediaBannerDescription).
|
|
21
|
+
*/
|
|
22
|
+
children: ReactNode;
|
|
23
|
+
} & Omit<StyledPressableProps, 'children'>;
|
|
24
|
+
/**
|
|
25
|
+
* Props for the MediaBannerTitle component.
|
|
26
|
+
*/
|
|
27
|
+
export type MediaBannerTitleProps = {
|
|
28
|
+
/**
|
|
29
|
+
* The title text content.
|
|
30
|
+
*/
|
|
31
|
+
children: ReactNode;
|
|
32
|
+
} & Omit<StyledTextProps, 'children'>;
|
|
33
|
+
/**
|
|
34
|
+
* Props for the MediaBannerDescription component.
|
|
35
|
+
*/
|
|
36
|
+
export type MediaBannerDescriptionProps = {
|
|
37
|
+
/**
|
|
38
|
+
* The description text content.
|
|
39
|
+
*/
|
|
40
|
+
children: ReactNode;
|
|
41
|
+
} & Omit<StyledTextProps, 'children'>;
|
|
42
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/lib/Components/MediaBanner/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAClC,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAExE;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAC;IACjB;;OAEG;IACH,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB;;OAEG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,CAAC,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC;AAEtC;;GAEG;AACH,MAAM,MAAM,2BAA2B,GAAG;IACxC;;OAEG;IACH,QAAQ,EAAE,SAAS,CAAC;CACrB,GAAG,IAAI,CAAC,eAAe,EAAE,UAAU,CAAC,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/lib/Components/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,eAAe,CAAC;AAC9B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,iBAAiB,CAAC;AAChC,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,mBAAmB,CAAC;AAClC,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,iBAAiB,CAAC;AAChC,cAAc,eAAe,CAAC;AAC9B,cAAc,oBAAoB,CAAC;AACnC,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,QAAQ,CAAC;AACvB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,UAAU,CAAC;AACzB,cAAc,UAAU,CAAC;AACzB,cAAc,OAAO,CAAC;AACtB,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,QAAQ,CAAC;AACvB,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { Meta, Story, Canvas, Controls } from '@storybook/addon-docs/blocks';
|
|
2
|
+
import * as MediaBannerStories from './MediaBanner.stories';
|
|
3
|
+
import {
|
|
4
|
+
MediaBanner,
|
|
5
|
+
MediaBannerTitle,
|
|
6
|
+
MediaBannerDescription,
|
|
7
|
+
} from './MediaBanner';
|
|
8
|
+
import { Box } from '../Utility';
|
|
9
|
+
import { CustomTabs, Tab } from '../../../../.storybook/components';
|
|
10
|
+
import { DoVsDontRow, DoBlockItem, DontBlockItem } from '../../../../.storybook/components/DoVsDont';
|
|
11
|
+
import CommonRulesDoAndDont from '../../../../.storybook/components/DoVsDont/CommonRulesDoAndDont.mdx';
|
|
12
|
+
|
|
13
|
+
<Meta title='Components/MediaBanner' of={MediaBannerStories} />
|
|
14
|
+
|
|
15
|
+
# 🖼️ MediaBanner
|
|
16
|
+
|
|
17
|
+
<CustomTabs>
|
|
18
|
+
<Tab label="Overview">
|
|
19
|
+
|
|
20
|
+
## Introduction
|
|
21
|
+
|
|
22
|
+
MediaBanner is a promotional banner component that displays a background image alongside a title and description. It is designed for marketing or informational content and supports an optional close action for dismissibility.
|
|
23
|
+
|
|
24
|
+
> View in [Figma](https://www.figma.com/design/JxaLVMTWirCpU0rsbZ30k7/2.-Components-Library?node-id=11235-5982&m=dev).
|
|
25
|
+
|
|
26
|
+
## Anatomy
|
|
27
|
+
|
|
28
|
+
<Canvas of={MediaBannerStories.Base} />
|
|
29
|
+
|
|
30
|
+
- **MediaBanner**: Root pressable container with surface background, rounded corners, and a fixed height
|
|
31
|
+
- **MediaBannerTitle**: The main label of the banner (clamps at 1 line)
|
|
32
|
+
- **MediaBannerDescription**: Additional context below the title (clamps at 2 lines)
|
|
33
|
+
- **Image**: Background image displayed on the right side with a gradient overlay
|
|
34
|
+
- **Close button**: Optional InteractiveIcon rendered in the top-right corner via the `onClose` prop
|
|
35
|
+
|
|
36
|
+
## Properties
|
|
37
|
+
|
|
38
|
+
### Overview
|
|
39
|
+
|
|
40
|
+
<Canvas of={MediaBannerStories.Base} />
|
|
41
|
+
<Controls of={MediaBannerStories.Base} />
|
|
42
|
+
|
|
43
|
+
### Truncation
|
|
44
|
+
|
|
45
|
+
Title clamps at 1 line and description clamps at 2 lines when content overflows.
|
|
46
|
+
|
|
47
|
+
<Canvas of={MediaBannerStories.Truncation} />
|
|
48
|
+
|
|
49
|
+
### Broken Image
|
|
50
|
+
|
|
51
|
+
When the `imageUrl` fails to load, the broken image is hidden while the gradient overlay and image space are preserved. The banner remains functional and visually consistent.
|
|
52
|
+
|
|
53
|
+
<Canvas of={MediaBannerStories.WithBrokenImage} />
|
|
54
|
+
|
|
55
|
+
### Dismissible
|
|
56
|
+
|
|
57
|
+
When `onClose` is provided, the close button allows the user to dismiss the banner.
|
|
58
|
+
|
|
59
|
+
<Canvas of={MediaBannerStories.WithClose} />
|
|
60
|
+
|
|
61
|
+
## Accessibility
|
|
62
|
+
|
|
63
|
+
- The close button uses `InteractiveIcon` with proper press states
|
|
64
|
+
- The root element is a `Pressable` for interactive use cases
|
|
65
|
+
- Title and description use semantic `Text` elements
|
|
66
|
+
|
|
67
|
+
</Tab>
|
|
68
|
+
<Tab label="Implementation">
|
|
69
|
+
|
|
70
|
+
## Setup
|
|
71
|
+
|
|
72
|
+
Install and set up the library with our [Setup Guide →](?path=/docs/getting-started-setup--docs).
|
|
73
|
+
|
|
74
|
+
## Basic Usage
|
|
75
|
+
|
|
76
|
+
MediaBanner uses a composite component pattern with `MediaBannerTitle` and `MediaBannerDescription`:
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
import {
|
|
80
|
+
MediaBanner,
|
|
81
|
+
MediaBannerTitle,
|
|
82
|
+
MediaBannerDescription,
|
|
83
|
+
} from '@ledgerhq/lumen-ui-rnative';
|
|
84
|
+
|
|
85
|
+
function MyComponent() {
|
|
86
|
+
return (
|
|
87
|
+
<MediaBanner imageUrl='https://example.com/promo.jpg'>
|
|
88
|
+
<MediaBannerTitle>Firmware Update</MediaBannerTitle>
|
|
89
|
+
<MediaBannerDescription>
|
|
90
|
+
Keep your Nano updated!
|
|
91
|
+
</MediaBannerDescription>
|
|
92
|
+
</MediaBanner>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### With Close Button
|
|
98
|
+
|
|
99
|
+
Add an `onClose` callback to make the banner dismissible:
|
|
100
|
+
|
|
101
|
+
```tsx
|
|
102
|
+
import {
|
|
103
|
+
MediaBanner,
|
|
104
|
+
MediaBannerTitle,
|
|
105
|
+
MediaBannerDescription,
|
|
106
|
+
} from '@ledgerhq/lumen-ui-rnative';
|
|
107
|
+
import { useState } from 'react';
|
|
108
|
+
|
|
109
|
+
function DismissibleBanner() {
|
|
110
|
+
const [isVisible, setIsVisible] = useState(true);
|
|
111
|
+
|
|
112
|
+
if (!isVisible) return null;
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<MediaBanner
|
|
116
|
+
imageUrl='https://example.com/promo.jpg'
|
|
117
|
+
onClose={() => setIsVisible(false)}
|
|
118
|
+
>
|
|
119
|
+
<MediaBannerTitle>
|
|
120
|
+
Earn Up to 12% APY With Staking Now!
|
|
121
|
+
</MediaBannerTitle>
|
|
122
|
+
<MediaBannerDescription>
|
|
123
|
+
Put your idle crypto to work. Start staking SOL, ETH, ATOM and more
|
|
124
|
+
directly from Ledger Live
|
|
125
|
+
</MediaBannerDescription>
|
|
126
|
+
</MediaBanner>
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### Layout Adjustments with lx
|
|
132
|
+
|
|
133
|
+
Use the `lx` prop for layout adjustments like margins or positioning:
|
|
134
|
+
|
|
135
|
+
```tsx
|
|
136
|
+
<MediaBanner
|
|
137
|
+
imageUrl='https://example.com/promo.jpg'
|
|
138
|
+
lx={{ marginTop: 's16', marginBottom: 's8' }}
|
|
139
|
+
>
|
|
140
|
+
<MediaBannerTitle>With Margin</MediaBannerTitle>
|
|
141
|
+
<MediaBannerDescription>
|
|
142
|
+
This banner has layout adjustments via lx.
|
|
143
|
+
</MediaBannerDescription>
|
|
144
|
+
</MediaBanner>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
<CommonRulesDoAndDont />
|
|
148
|
+
|
|
149
|
+
</Tab>
|
|
150
|
+
</CustomTabs>
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Button } from '../Button';
|
|
4
|
+
import { Box } from '../Utility';
|
|
5
|
+
import {
|
|
6
|
+
MediaBanner,
|
|
7
|
+
MediaBannerDescription,
|
|
8
|
+
MediaBannerTitle,
|
|
9
|
+
} from './MediaBanner';
|
|
10
|
+
|
|
11
|
+
const IMAGE_URL =
|
|
12
|
+
'https://images.unsplash.com/photo-1663741954108-d15d514529ef';
|
|
13
|
+
|
|
14
|
+
const meta: Meta<typeof MediaBanner> = {
|
|
15
|
+
component: MediaBanner,
|
|
16
|
+
title: 'Communication/MediaBanner',
|
|
17
|
+
subcomponents: {
|
|
18
|
+
MediaBannerTitle,
|
|
19
|
+
MediaBannerDescription,
|
|
20
|
+
},
|
|
21
|
+
parameters: {
|
|
22
|
+
docs: {
|
|
23
|
+
source: {
|
|
24
|
+
language: 'tsx',
|
|
25
|
+
format: true,
|
|
26
|
+
type: 'code',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
argTypes: {
|
|
31
|
+
imageUrl: {
|
|
32
|
+
control: 'text',
|
|
33
|
+
description: 'URL of the background image',
|
|
34
|
+
},
|
|
35
|
+
onClose: {
|
|
36
|
+
control: 'select',
|
|
37
|
+
description: 'Close action callback',
|
|
38
|
+
options: ['With Close', 'None'],
|
|
39
|
+
mapping: {
|
|
40
|
+
'With Close': () => {
|
|
41
|
+
console.log('Close clicked');
|
|
42
|
+
},
|
|
43
|
+
None: undefined,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export default meta;
|
|
50
|
+
type Story = StoryObj<typeof MediaBanner>;
|
|
51
|
+
|
|
52
|
+
export const Base: Story = {
|
|
53
|
+
args: {
|
|
54
|
+
imageUrl: IMAGE_URL,
|
|
55
|
+
},
|
|
56
|
+
render: (args) => (
|
|
57
|
+
<Box lx={{ width: 's400' }}>
|
|
58
|
+
<MediaBanner {...args}>
|
|
59
|
+
<MediaBannerTitle>Firmware Update</MediaBannerTitle>
|
|
60
|
+
<MediaBannerDescription>Keep your Nano updated!</MediaBannerDescription>
|
|
61
|
+
</MediaBanner>
|
|
62
|
+
</Box>
|
|
63
|
+
),
|
|
64
|
+
parameters: {
|
|
65
|
+
docs: {
|
|
66
|
+
source: {
|
|
67
|
+
code: `
|
|
68
|
+
<MediaBanner imageUrl="https://images.unsplash.com/photo-1663741954108-d15d514529ef">
|
|
69
|
+
<MediaBannerTitle>Firmware Update</MediaBannerTitle>
|
|
70
|
+
<MediaBannerDescription>
|
|
71
|
+
Keep your Nano updated!
|
|
72
|
+
</MediaBannerDescription>
|
|
73
|
+
</MediaBanner>
|
|
74
|
+
`,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
export const Truncation: Story = {
|
|
81
|
+
render: () => (
|
|
82
|
+
<Box lx={{ width: 's400' }}>
|
|
83
|
+
<MediaBanner imageUrl={IMAGE_URL} onClose={() => console.log('close')}>
|
|
84
|
+
<MediaBannerTitle>
|
|
85
|
+
Earn Up to 12% APY With Staking Now And Much More Rewards Awaiting You
|
|
86
|
+
</MediaBannerTitle>
|
|
87
|
+
<MediaBannerDescription>
|
|
88
|
+
Put your idle crypto to work. Start staking SOL, ETH, ATOM and more
|
|
89
|
+
directly from Ledger Live. Maximize your returns with our secure
|
|
90
|
+
staking solutions.
|
|
91
|
+
</MediaBannerDescription>
|
|
92
|
+
</MediaBanner>
|
|
93
|
+
</Box>
|
|
94
|
+
),
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export const WithBrokenImage: Story = {
|
|
98
|
+
render: () => (
|
|
99
|
+
<Box lx={{ width: 's400' }}>
|
|
100
|
+
<MediaBanner
|
|
101
|
+
imageUrl='https://broken-url.invalid/image.jpg'
|
|
102
|
+
onClose={() => console.log('close')}
|
|
103
|
+
>
|
|
104
|
+
<MediaBannerTitle>Sorry!</MediaBannerTitle>
|
|
105
|
+
<MediaBannerDescription>
|
|
106
|
+
The image failed to load so the banner decided to gracefully hide it.
|
|
107
|
+
</MediaBannerDescription>
|
|
108
|
+
</MediaBanner>
|
|
109
|
+
</Box>
|
|
110
|
+
),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export const WithClose: Story = {
|
|
114
|
+
render: () => {
|
|
115
|
+
const [visible, setVisible] = useState(true);
|
|
116
|
+
|
|
117
|
+
if (!visible) {
|
|
118
|
+
return (
|
|
119
|
+
<Button
|
|
120
|
+
appearance='transparent'
|
|
121
|
+
size='sm'
|
|
122
|
+
onPress={() => setVisible(true)}
|
|
123
|
+
>
|
|
124
|
+
Show banner
|
|
125
|
+
</Button>
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<Box lx={{ width: 's400' }}>
|
|
131
|
+
<MediaBanner imageUrl={IMAGE_URL} onClose={() => setVisible(false)}>
|
|
132
|
+
<MediaBannerTitle>
|
|
133
|
+
Earn Up to 12% APY With Staking Now!
|
|
134
|
+
</MediaBannerTitle>
|
|
135
|
+
<MediaBannerDescription>
|
|
136
|
+
Put your idle crypto to work. Start staking SOL, ETH, ATOM and more
|
|
137
|
+
directly from Ledger Live
|
|
138
|
+
</MediaBannerDescription>
|
|
139
|
+
</MediaBanner>
|
|
140
|
+
</Box>
|
|
141
|
+
);
|
|
142
|
+
},
|
|
143
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
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 {
|
|
7
|
+
MediaBanner,
|
|
8
|
+
MediaBannerTitle,
|
|
9
|
+
MediaBannerDescription,
|
|
10
|
+
} from './MediaBanner';
|
|
11
|
+
|
|
12
|
+
const TestWrapper = ({ children }: { children: ReactNode }) => (
|
|
13
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
14
|
+
{children}
|
|
15
|
+
</ThemeProvider>
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
const IMAGE_URL = 'https://example.com/image.jpg';
|
|
19
|
+
|
|
20
|
+
describe('MediaBanner', () => {
|
|
21
|
+
it('should render title and description', () => {
|
|
22
|
+
const { getByText } = render(
|
|
23
|
+
<TestWrapper>
|
|
24
|
+
<MediaBanner imageUrl={IMAGE_URL}>
|
|
25
|
+
<MediaBannerTitle>Banner Title</MediaBannerTitle>
|
|
26
|
+
<MediaBannerDescription>Banner description</MediaBannerDescription>
|
|
27
|
+
</MediaBanner>
|
|
28
|
+
</TestWrapper>,
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
getByText('Banner Title');
|
|
32
|
+
getByText('Banner description');
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('should call onClose when close button is pressed', () => {
|
|
36
|
+
const handleClose = jest.fn();
|
|
37
|
+
const { getByTestId } = render(
|
|
38
|
+
<TestWrapper>
|
|
39
|
+
<MediaBanner imageUrl={IMAGE_URL} onClose={handleClose}>
|
|
40
|
+
<MediaBannerTitle>Title</MediaBannerTitle>
|
|
41
|
+
</MediaBanner>
|
|
42
|
+
</TestWrapper>,
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const closeButton = getByTestId('media-banner-close-button');
|
|
46
|
+
expect(closeButton).toBeTruthy();
|
|
47
|
+
fireEvent.press(closeButton);
|
|
48
|
+
expect(handleClose).toHaveBeenCalledTimes(1);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('should apply surface background color', () => {
|
|
52
|
+
const { getByTestId } = render(
|
|
53
|
+
<TestWrapper>
|
|
54
|
+
<MediaBanner testID='media-banner' imageUrl={IMAGE_URL}>
|
|
55
|
+
<MediaBannerTitle>Title</MediaBannerTitle>
|
|
56
|
+
</MediaBanner>
|
|
57
|
+
</TestWrapper>,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
const banner = getByTestId('media-banner');
|
|
61
|
+
expect(banner.props.style.backgroundColor).toBe(
|
|
62
|
+
ledgerLiveThemes.dark.colors.bg.surface,
|
|
63
|
+
);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('should render with imageUrl prop', () => {
|
|
67
|
+
const { getByTestId } = render(
|
|
68
|
+
<TestWrapper>
|
|
69
|
+
<MediaBanner testID='media-banner' imageUrl={IMAGE_URL}>
|
|
70
|
+
<MediaBannerTitle>Title</MediaBannerTitle>
|
|
71
|
+
</MediaBanner>
|
|
72
|
+
</TestWrapper>,
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
expect(getByTestId('media-banner')).toBeTruthy();
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Image, StyleSheet } from 'react-native';
|
|
3
|
+
import { useCommonTranslation } from '../../../i18n';
|
|
4
|
+
import { useStyleSheet, useTheme } from '../../../styles';
|
|
5
|
+
import { Close } from '../../Symbols';
|
|
6
|
+
import { InteractiveIcon } from '../InteractiveIcon';
|
|
7
|
+
import { Box, LinearGradient, Pressable, Text } from '../Utility';
|
|
8
|
+
import {
|
|
9
|
+
MediaBannerDescriptionProps,
|
|
10
|
+
MediaBannerProps,
|
|
11
|
+
MediaBannerTitleProps,
|
|
12
|
+
} from './types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* A promotional banner with a background image, title, description, and an optional close button.
|
|
16
|
+
*
|
|
17
|
+
* @see {@link https://ldls.vercel.app/?path=/docs/communication-mediabanner--docs Storybook}
|
|
18
|
+
*/
|
|
19
|
+
export function MediaBanner({
|
|
20
|
+
lx,
|
|
21
|
+
style,
|
|
22
|
+
imageUrl,
|
|
23
|
+
onClose,
|
|
24
|
+
closeAccessibilityLabel,
|
|
25
|
+
children,
|
|
26
|
+
...props
|
|
27
|
+
}: MediaBannerProps) {
|
|
28
|
+
const { t: translate } = useCommonTranslation();
|
|
29
|
+
const { theme: t } = useTheme();
|
|
30
|
+
const [imageLoadError, setImageLoadError] = useState(false);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
setImageLoadError(false);
|
|
34
|
+
}, [imageUrl]);
|
|
35
|
+
|
|
36
|
+
const showImage = imageUrl && !imageLoadError;
|
|
37
|
+
|
|
38
|
+
const styles = useStyleSheet(
|
|
39
|
+
(t) => ({
|
|
40
|
+
container: {
|
|
41
|
+
backgroundColor: t.colors.bg.surface,
|
|
42
|
+
borderRadius: t.borderRadius.md,
|
|
43
|
+
overflow: 'hidden',
|
|
44
|
+
flexDirection: 'row',
|
|
45
|
+
height: t.sizes.s72,
|
|
46
|
+
},
|
|
47
|
+
contentWrapper: {
|
|
48
|
+
flex: 1,
|
|
49
|
+
overflow: 'hidden',
|
|
50
|
+
justifyContent: 'center',
|
|
51
|
+
paddingHorizontal: t.spacings.s12,
|
|
52
|
+
paddingVertical: t.spacings.s2,
|
|
53
|
+
},
|
|
54
|
+
contentContainer: {
|
|
55
|
+
paddingVertical: t.spacings.s12,
|
|
56
|
+
gap: 4,
|
|
57
|
+
},
|
|
58
|
+
closeButton: {
|
|
59
|
+
position: 'absolute',
|
|
60
|
+
top: 8.5,
|
|
61
|
+
right: 8.5,
|
|
62
|
+
},
|
|
63
|
+
}),
|
|
64
|
+
[],
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<Pressable lx={lx} style={[styles.container, style]} {...props}>
|
|
69
|
+
<Box style={styles.contentWrapper}>
|
|
70
|
+
<Box style={styles.contentContainer}>{children}</Box>
|
|
71
|
+
</Box>
|
|
72
|
+
<Box style={{ width: 120 }}>
|
|
73
|
+
{showImage && (
|
|
74
|
+
<Image
|
|
75
|
+
source={{ uri: imageUrl }}
|
|
76
|
+
style={StyleSheet.absoluteFill}
|
|
77
|
+
resizeMode='cover'
|
|
78
|
+
onError={() => setImageLoadError(true)}
|
|
79
|
+
accessible={false}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
<LinearGradient
|
|
83
|
+
direction='to-topright'
|
|
84
|
+
stops={[
|
|
85
|
+
{ color: t.colors.bg.black, opacity: 0, offset: 0.67 },
|
|
86
|
+
{ color: t.colors.bg.black, opacity: 0.8 },
|
|
87
|
+
]}
|
|
88
|
+
style={StyleSheet.absoluteFill}
|
|
89
|
+
accessible={false}
|
|
90
|
+
pointerEvents='none'
|
|
91
|
+
/>
|
|
92
|
+
</Box>
|
|
93
|
+
{onClose && (
|
|
94
|
+
<Box style={styles.closeButton}>
|
|
95
|
+
<InteractiveIcon
|
|
96
|
+
testID='media-banner-close-button'
|
|
97
|
+
iconType='stroked'
|
|
98
|
+
appearance='white'
|
|
99
|
+
onPress={onClose}
|
|
100
|
+
accessibilityLabel={
|
|
101
|
+
closeAccessibilityLabel ||
|
|
102
|
+
translate('components.banner.closeAriaLabel')
|
|
103
|
+
}
|
|
104
|
+
>
|
|
105
|
+
<Close size={16} />
|
|
106
|
+
</InteractiveIcon>
|
|
107
|
+
</Box>
|
|
108
|
+
)}
|
|
109
|
+
</Pressable>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* The title of the MediaBanner. Clamps at 1 line.
|
|
115
|
+
*/
|
|
116
|
+
export function MediaBannerTitle({
|
|
117
|
+
lx,
|
|
118
|
+
style,
|
|
119
|
+
children,
|
|
120
|
+
...props
|
|
121
|
+
}: MediaBannerTitleProps) {
|
|
122
|
+
const styles = useStyleSheet(
|
|
123
|
+
(t) => ({
|
|
124
|
+
title: StyleSheet.flatten([
|
|
125
|
+
t.typographies.body2SemiBold,
|
|
126
|
+
{
|
|
127
|
+
color: t.colors.text.base,
|
|
128
|
+
},
|
|
129
|
+
]),
|
|
130
|
+
}),
|
|
131
|
+
[],
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<Text lx={lx} style={[styles.title, style]} numberOfLines={1} {...props}>
|
|
136
|
+
{children}
|
|
137
|
+
</Text>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* The description of the MediaBanner. Clamps at 2 lines.
|
|
143
|
+
*/
|
|
144
|
+
export function MediaBannerDescription({
|
|
145
|
+
lx,
|
|
146
|
+
style,
|
|
147
|
+
children,
|
|
148
|
+
...props
|
|
149
|
+
}: MediaBannerDescriptionProps) {
|
|
150
|
+
const styles = useStyleSheet(
|
|
151
|
+
(t) => ({
|
|
152
|
+
description: StyleSheet.flatten([
|
|
153
|
+
t.typographies.body3,
|
|
154
|
+
{
|
|
155
|
+
color: t.colors.text.muted,
|
|
156
|
+
},
|
|
157
|
+
]),
|
|
158
|
+
}),
|
|
159
|
+
[],
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<Text
|
|
164
|
+
lx={lx}
|
|
165
|
+
style={[styles.description, style]}
|
|
166
|
+
numberOfLines={2}
|
|
167
|
+
{...props}
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</Text>
|
|
171
|
+
);
|
|
172
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyledPressableProps, StyledTextProps } from '../../../styles';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props for the MediaBanner root component.
|
|
6
|
+
*/
|
|
7
|
+
export type MediaBannerProps = {
|
|
8
|
+
/**
|
|
9
|
+
* URL of the background image displayed on the right side.
|
|
10
|
+
*/
|
|
11
|
+
imageUrl: string;
|
|
12
|
+
/**
|
|
13
|
+
* Optional close action callback.
|
|
14
|
+
*/
|
|
15
|
+
onClose?: () => void;
|
|
16
|
+
/**
|
|
17
|
+
* Optional accessibility label for the close button.
|
|
18
|
+
*/
|
|
19
|
+
closeAccessibilityLabel?: string;
|
|
20
|
+
/**
|
|
21
|
+
* The banner content (MediaBannerTitle, MediaBannerDescription).
|
|
22
|
+
*/
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
} & Omit<StyledPressableProps, 'children'>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Props for the MediaBannerTitle component.
|
|
28
|
+
*/
|
|
29
|
+
export type MediaBannerTitleProps = {
|
|
30
|
+
/**
|
|
31
|
+
* The title text content.
|
|
32
|
+
*/
|
|
33
|
+
children: ReactNode;
|
|
34
|
+
} & Omit<StyledTextProps, 'children'>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Props for the MediaBannerDescription component.
|
|
38
|
+
*/
|
|
39
|
+
export type MediaBannerDescriptionProps = {
|
|
40
|
+
/**
|
|
41
|
+
* The description text content.
|
|
42
|
+
*/
|
|
43
|
+
children: ReactNode;
|
|
44
|
+
} & Omit<StyledTextProps, 'children'>;
|