@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
|
@@ -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,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 {
|
|
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={
|
|
23
|
-
<
|
|
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>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { CryptoIcon } from '@ledgerhq/crypto-icons';
|
|
2
|
+
import type { Meta, StoryObj } from '@storybook/react-native-web-vite';
|
|
3
|
+
import { Settings, Star } from '../../Symbols';
|
|
4
|
+
import { Box } from '../Utility';
|
|
5
|
+
import { TriggerButton } from './TriggerButton';
|
|
6
|
+
|
|
7
|
+
const meta: Meta<typeof TriggerButton> = {
|
|
8
|
+
component: TriggerButton,
|
|
9
|
+
title: 'Action/TriggerButton',
|
|
10
|
+
parameters: {
|
|
11
|
+
layout: 'centered',
|
|
12
|
+
docs: {
|
|
13
|
+
source: {
|
|
14
|
+
language: 'tsx',
|
|
15
|
+
format: true,
|
|
16
|
+
type: 'code',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default meta;
|
|
23
|
+
type Story = StoryObj<typeof TriggerButton>;
|
|
24
|
+
|
|
25
|
+
export const Base: Story = {
|
|
26
|
+
args: {
|
|
27
|
+
children: 'All accounts',
|
|
28
|
+
appearance: 'gray',
|
|
29
|
+
},
|
|
30
|
+
render: (args) => <TriggerButton {...args} />,
|
|
31
|
+
parameters: {
|
|
32
|
+
docs: {
|
|
33
|
+
source: {
|
|
34
|
+
code: `
|
|
35
|
+
<TriggerButton appearance="gray">
|
|
36
|
+
All accounts
|
|
37
|
+
</TriggerButton>
|
|
38
|
+
`,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export const SizeShowcase: Story = {
|
|
45
|
+
render: () => (
|
|
46
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's16' }}>
|
|
47
|
+
<TriggerButton size='sm' icon={<Star size={20} />} iconType='flat'>
|
|
48
|
+
Small
|
|
49
|
+
</TriggerButton>
|
|
50
|
+
<TriggerButton size='md' icon={<Star size={20} />} iconType='flat'>
|
|
51
|
+
Medium
|
|
52
|
+
</TriggerButton>
|
|
53
|
+
</Box>
|
|
54
|
+
),
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export const IconTypeShowcase: Story = {
|
|
58
|
+
render: () => (
|
|
59
|
+
<Box lx={{ flexDirection: 'column', gap: 's16' }}>
|
|
60
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's16' }}>
|
|
61
|
+
<TriggerButton
|
|
62
|
+
icon={<Settings size={20} />}
|
|
63
|
+
iconType='flat'
|
|
64
|
+
appearance='gray'
|
|
65
|
+
>
|
|
66
|
+
Flat icon (md)
|
|
67
|
+
</TriggerButton>
|
|
68
|
+
<TriggerButton
|
|
69
|
+
icon={<CryptoIcon ledgerId='bitcoin' ticker='BTC' size='32px' />}
|
|
70
|
+
iconType='rounded'
|
|
71
|
+
appearance='gray'
|
|
72
|
+
>
|
|
73
|
+
Rounded icon (md)
|
|
74
|
+
</TriggerButton>
|
|
75
|
+
<TriggerButton appearance='gray'>No icon (md)</TriggerButton>
|
|
76
|
+
</Box>
|
|
77
|
+
<Box lx={{ flexDirection: 'row', alignItems: 'center', gap: 's16' }}>
|
|
78
|
+
<TriggerButton
|
|
79
|
+
icon={<Settings size={20} />}
|
|
80
|
+
iconType='flat'
|
|
81
|
+
appearance='gray'
|
|
82
|
+
size='sm'
|
|
83
|
+
>
|
|
84
|
+
Flat icon (sm)
|
|
85
|
+
</TriggerButton>
|
|
86
|
+
<TriggerButton
|
|
87
|
+
icon={<CryptoIcon ledgerId='bitcoin' ticker='BTC' size='24px' />}
|
|
88
|
+
iconType='rounded'
|
|
89
|
+
appearance='gray'
|
|
90
|
+
size='sm'
|
|
91
|
+
>
|
|
92
|
+
Rounded icon (sm)
|
|
93
|
+
</TriggerButton>
|
|
94
|
+
<TriggerButton appearance='gray' size='sm'>
|
|
95
|
+
No icon (sm)
|
|
96
|
+
</TriggerButton>
|
|
97
|
+
</Box>
|
|
98
|
+
</Box>
|
|
99
|
+
),
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const AppearanceShowcase: Story = {
|
|
103
|
+
render: () => {
|
|
104
|
+
const appearances = ['gray', 'transparent', 'no-background'] as const;
|
|
105
|
+
return (
|
|
106
|
+
<Box lx={{ flexDirection: 'column', gap: 's16', padding: 's16' }}>
|
|
107
|
+
{appearances.map((appearance) => (
|
|
108
|
+
<Box
|
|
109
|
+
key={appearance}
|
|
110
|
+
lx={{ flexDirection: 'row', alignItems: 'center', gap: 's16' }}
|
|
111
|
+
>
|
|
112
|
+
<TriggerButton appearance={appearance}>{appearance}</TriggerButton>
|
|
113
|
+
<TriggerButton
|
|
114
|
+
appearance={appearance}
|
|
115
|
+
icon={<Settings size={20} />}
|
|
116
|
+
iconType='flat'
|
|
117
|
+
>
|
|
118
|
+
{appearance}
|
|
119
|
+
</TriggerButton>
|
|
120
|
+
<TriggerButton
|
|
121
|
+
appearance={appearance}
|
|
122
|
+
icon={<CryptoIcon ledgerId='bitcoin' ticker='BTC' size='32px' />}
|
|
123
|
+
iconType='rounded'
|
|
124
|
+
>
|
|
125
|
+
{appearance}
|
|
126
|
+
</TriggerButton>
|
|
127
|
+
</Box>
|
|
128
|
+
))}
|
|
129
|
+
</Box>
|
|
130
|
+
);
|
|
131
|
+
},
|
|
132
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { describe, it, expect, jest } from '@jest/globals';
|
|
2
|
+
import { ledgerLiveThemes } from '@ledgerhq/lumen-design-core';
|
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/react-native';
|
|
4
|
+
import React, { createRef } from 'react';
|
|
5
|
+
import { View, ViewStyle } from 'react-native';
|
|
6
|
+
|
|
7
|
+
import { Settings } from '../../Symbols';
|
|
8
|
+
import { ThemeProvider } from '../ThemeProvider/ThemeProvider';
|
|
9
|
+
import { TriggerButton } from './TriggerButton';
|
|
10
|
+
|
|
11
|
+
const renderWithProvider = (component: React.ReactElement) => {
|
|
12
|
+
return render(
|
|
13
|
+
<ThemeProvider themes={ledgerLiveThemes} colorScheme='dark' locale='en'>
|
|
14
|
+
{component}
|
|
15
|
+
</ThemeProvider>,
|
|
16
|
+
);
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('TriggerButton', () => {
|
|
20
|
+
describe('Rendering', () => {
|
|
21
|
+
it('should render with label text and correct accessibility role', () => {
|
|
22
|
+
renderWithProvider(
|
|
23
|
+
<TriggerButton testID='trigger'>All accounts</TriggerButton>,
|
|
24
|
+
);
|
|
25
|
+
const trigger = screen.getByTestId('trigger');
|
|
26
|
+
expect(trigger).toBeTruthy();
|
|
27
|
+
expect(screen.getByText('All accounts')).toBeTruthy();
|
|
28
|
+
expect(trigger.props.accessibilityRole).toBe('button');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('should always render a chevron icon', () => {
|
|
32
|
+
renderWithProvider(<TriggerButton testID='trigger'>Label</TriggerButton>);
|
|
33
|
+
expect(screen.getByTestId('button-trigger-chevron')).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it.each(['gray', 'transparent', 'no-background'] as const)(
|
|
37
|
+
'should render without errors for appearance "%s"',
|
|
38
|
+
(appearance) => {
|
|
39
|
+
renderWithProvider(
|
|
40
|
+
<TriggerButton testID='trigger' appearance={appearance}>
|
|
41
|
+
Label
|
|
42
|
+
</TriggerButton>,
|
|
43
|
+
);
|
|
44
|
+
expect(screen.getByTestId('trigger')).toBeTruthy();
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
it.each(['sm', 'md'] as const)(
|
|
49
|
+
'should render without errors for size "%s"',
|
|
50
|
+
(size) => {
|
|
51
|
+
renderWithProvider(
|
|
52
|
+
<TriggerButton testID='trigger' size={size}>
|
|
53
|
+
Label
|
|
54
|
+
</TriggerButton>,
|
|
55
|
+
);
|
|
56
|
+
expect(screen.getByTestId('trigger')).toBeTruthy();
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('Icons', () => {
|
|
62
|
+
it('should render with a flat interface icon', () => {
|
|
63
|
+
renderWithProvider(
|
|
64
|
+
<TriggerButton
|
|
65
|
+
testID='trigger'
|
|
66
|
+
icon={<Settings size={20} testID='icon' />}
|
|
67
|
+
iconType='flat'
|
|
68
|
+
>
|
|
69
|
+
Network
|
|
70
|
+
</TriggerButton>,
|
|
71
|
+
);
|
|
72
|
+
expect(screen.getByTestId('icon')).toBeTruthy();
|
|
73
|
+
expect(screen.getByText('Network')).toBeTruthy();
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should render with a rounded icon', () => {
|
|
77
|
+
renderWithProvider(
|
|
78
|
+
<TriggerButton
|
|
79
|
+
testID='trigger'
|
|
80
|
+
icon={<View testID='crypto-icon' />}
|
|
81
|
+
iconType='rounded'
|
|
82
|
+
>
|
|
83
|
+
Bitcoin
|
|
84
|
+
</TriggerButton>,
|
|
85
|
+
);
|
|
86
|
+
expect(screen.getByTestId('crypto-icon')).toBeTruthy();
|
|
87
|
+
expect(screen.getByText('Bitcoin')).toBeTruthy();
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe('States', () => {
|
|
92
|
+
it('should be disabled when disabled prop is true', () => {
|
|
93
|
+
renderWithProvider(
|
|
94
|
+
<TriggerButton testID='trigger' disabled>
|
|
95
|
+
Label
|
|
96
|
+
</TriggerButton>,
|
|
97
|
+
);
|
|
98
|
+
const trigger = screen.getByTestId('trigger');
|
|
99
|
+
expect(trigger.props.accessibilityState.disabled).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('Interactions', () => {
|
|
104
|
+
it('should call onPress when pressed', () => {
|
|
105
|
+
const handlePress = jest.fn();
|
|
106
|
+
renderWithProvider(
|
|
107
|
+
<TriggerButton testID='trigger' onPress={handlePress}>
|
|
108
|
+
Press me
|
|
109
|
+
</TriggerButton>,
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
fireEvent.press(screen.getByTestId('trigger'));
|
|
113
|
+
expect(handlePress).toHaveBeenCalledTimes(1);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should not call onPress when disabled', () => {
|
|
117
|
+
const handlePress = jest.fn();
|
|
118
|
+
renderWithProvider(
|
|
119
|
+
<TriggerButton testID='trigger' onPress={handlePress} disabled>
|
|
120
|
+
Disabled
|
|
121
|
+
</TriggerButton>,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
fireEvent.press(screen.getByTestId('trigger'));
|
|
125
|
+
expect(handlePress).not.toHaveBeenCalled();
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
describe('Ref Forwarding', () => {
|
|
130
|
+
it('should forward ref', () => {
|
|
131
|
+
const ref = createRef<View>();
|
|
132
|
+
renderWithProvider(
|
|
133
|
+
<TriggerButton ref={ref} testID='trigger'>
|
|
134
|
+
Label
|
|
135
|
+
</TriggerButton>,
|
|
136
|
+
);
|
|
137
|
+
expect(ref.current).toBeTruthy();
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe('Styling', () => {
|
|
142
|
+
it('should apply custom style and lx props', () => {
|
|
143
|
+
const customStyle: ViewStyle = { marginTop: 16 };
|
|
144
|
+
renderWithProvider(
|
|
145
|
+
<TriggerButton
|
|
146
|
+
testID='trigger'
|
|
147
|
+
style={customStyle}
|
|
148
|
+
lx={{ padding: 's8' }}
|
|
149
|
+
>
|
|
150
|
+
Styled
|
|
151
|
+
</TriggerButton>,
|
|
152
|
+
);
|
|
153
|
+
const trigger = screen.getByTestId('trigger');
|
|
154
|
+
expect(trigger.props.style).toBeDefined();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|