@boostdev/design-system-react-native 0.2.5 → 0.3.0
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/CHANGELOG.md +87 -0
- package/dist/index.cjs +176 -165
- package/dist/index.d.cts +12 -2
- package/dist/index.d.ts +12 -2
- package/dist/index.js +176 -165
- package/package.json +1 -1
- package/src/components/interaction/Dialog/Dialog.mdx +3 -2
- package/src/components/interaction/Dialog/Dialog.spec.tsx +42 -11
- package/src/components/interaction/Dialog/Dialog.tsx +19 -12
- package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +3 -1
- package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +73 -0
- package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +79 -73
- package/src/components/interaction/Popover/Popover.mdx +3 -1
- package/src/components/interaction/Popover/Popover.spec.tsx +43 -3
- package/src/components/interaction/Popover/Popover.tsx +23 -13
- package/src/components/interaction/SideSheet/SideSheet.mdx +4 -3
- package/src/components/interaction/SideSheet/SideSheet.spec.tsx +8 -9
- package/src/components/interaction/SideSheet/SideSheet.tsx +9 -3
- package/src/components/ui/Alert/Alert.spec.tsx +32 -1
- package/src/components/ui/Alert/Alert.stories.tsx +45 -0
- package/src/components/ui/Alert/Alert.tsx +11 -2
- package/src/components/ui/Collapsible/Collapsible.spec.tsx +11 -0
- package/src/components/ui/Collapsible/Collapsible.tsx +13 -8
- package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +32 -1
- package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +30 -0
- package/src/components/ui/NotificationBanner/NotificationBanner.tsx +16 -3
|
@@ -58,7 +58,12 @@ const styles = StyleSheet.create({
|
|
|
58
58
|
lineHeight: lineHeight.body,
|
|
59
59
|
},
|
|
60
60
|
chevron: {
|
|
61
|
-
|
|
61
|
+
// Border-box chevron (no glyph font dependency): a small square with two
|
|
62
|
+
// adjacent borders, rotated to form a crisp "›" / "⌄" disclosure indicator.
|
|
63
|
+
width: 8,
|
|
64
|
+
height: 8,
|
|
65
|
+
borderRightWidth: 2,
|
|
66
|
+
borderBottomWidth: 2,
|
|
62
67
|
marginStart: spacing.xs,
|
|
63
68
|
},
|
|
64
69
|
content: {
|
|
@@ -103,9 +108,10 @@ export function Collapsible({
|
|
|
103
108
|
onToggle?.(next);
|
|
104
109
|
}, [isOpen, isControlled, onToggle, rotation]);
|
|
105
110
|
|
|
106
|
-
|
|
111
|
+
// Chevron points right when collapsed (-45deg) and down when expanded (45deg).
|
|
112
|
+
const rotate = rotation.interpolate({
|
|
107
113
|
inputRange: [0, 1],
|
|
108
|
-
outputRange: ['
|
|
114
|
+
outputRange: ['-45deg', '45deg'],
|
|
109
115
|
});
|
|
110
116
|
|
|
111
117
|
return (
|
|
@@ -121,13 +127,12 @@ export function Collapsible({
|
|
|
121
127
|
style={({ pressed }) => [styles.header, pressed && styles.pressed]}
|
|
122
128
|
>
|
|
123
129
|
<Text style={[styles.summaryText, { color: colors.colorOnBg }]}>{summary}</Text>
|
|
124
|
-
<Animated.
|
|
125
|
-
|
|
130
|
+
<Animated.View
|
|
131
|
+
testID="bds-collapsible-chevron"
|
|
132
|
+
style={[styles.chevron, { borderColor: colors.colorOnBgSubtle, transform: [{ rotate }] }]}
|
|
126
133
|
importantForAccessibility="no"
|
|
127
134
|
accessibilityElementsHidden={true}
|
|
128
|
-
|
|
129
|
-
▸
|
|
130
|
-
</Animated.Text>
|
|
135
|
+
/>
|
|
131
136
|
</Pressable>
|
|
132
137
|
{isOpen && (
|
|
133
138
|
<View style={styles.content}>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { render, screen, fireEvent } from '@testing-library/react';
|
|
2
|
-
import { Text } from 'react-native';
|
|
2
|
+
import { Text, View, Pressable } from 'react-native';
|
|
3
3
|
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
4
|
import { NotificationBanner } from './NotificationBanner';
|
|
5
5
|
|
|
@@ -13,6 +13,37 @@ describe('NotificationBanner (native)', () => {
|
|
|
13
13
|
expect(screen.getByText('Update available')).toBeInTheDocument();
|
|
14
14
|
});
|
|
15
15
|
|
|
16
|
+
it('renders non-text children such as a view with an action', () => {
|
|
17
|
+
const onPress = vi.fn();
|
|
18
|
+
renderWithTheme(
|
|
19
|
+
<NotificationBanner variant="warning">
|
|
20
|
+
<View>
|
|
21
|
+
<Text>Session expiring</Text>
|
|
22
|
+
<Pressable accessibilityRole="button" onPress={onPress}>
|
|
23
|
+
<Text>Extend</Text>
|
|
24
|
+
</Pressable>
|
|
25
|
+
</View>
|
|
26
|
+
</NotificationBanner>,
|
|
27
|
+
);
|
|
28
|
+
expect(screen.getByText('Session expiring')).toBeInTheDocument();
|
|
29
|
+
fireEvent.click(screen.getByRole('button', { name: 'Extend' }));
|
|
30
|
+
expect(onPress).toHaveBeenCalledTimes(1);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('passes the resolved text color to a function child', () => {
|
|
34
|
+
let receivedColor = '';
|
|
35
|
+
renderWithTheme(
|
|
36
|
+
<NotificationBanner variant="info">
|
|
37
|
+
{({ color }) => {
|
|
38
|
+
receivedColor = color;
|
|
39
|
+
return <Text style={{ color }}>Themed content</Text>;
|
|
40
|
+
}}
|
|
41
|
+
</NotificationBanner>,
|
|
42
|
+
);
|
|
43
|
+
expect(screen.getByText('Themed content')).toBeInTheDocument();
|
|
44
|
+
expect(receivedColor).not.toBe('');
|
|
45
|
+
});
|
|
46
|
+
|
|
16
47
|
it('has alert role for error variant', () => {
|
|
17
48
|
renderWithTheme(
|
|
18
49
|
<NotificationBanner variant="error">Something went wrong</NotificationBanner>,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
+
import { View, Text, Pressable } from 'react-native';
|
|
2
3
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
4
|
import { ThemeProvider } from '../../../native/ThemeContext';
|
|
4
5
|
import { NotificationBanner } from './NotificationBanner';
|
|
@@ -35,3 +36,32 @@ export const AllVariants: Story = {
|
|
|
35
36
|
</div>
|
|
36
37
|
),
|
|
37
38
|
};
|
|
39
|
+
|
|
40
|
+
// A string child keeps the default body styling. A non-text child renders as-is.
|
|
41
|
+
export const WithRichContent: Story = {
|
|
42
|
+
args: { children: '', variant: 'warning' },
|
|
43
|
+
render: (args) => (
|
|
44
|
+
<NotificationBanner {...args}>
|
|
45
|
+
<View style={{ gap: 2 }}>
|
|
46
|
+
<Text style={{ fontSize: 13, fontWeight: '600' }}>Session expiring</Text>
|
|
47
|
+
<Text style={{ fontSize: 13 }}>You will be signed out in 2 minutes.</Text>
|
|
48
|
+
</View>
|
|
49
|
+
</NotificationBanner>
|
|
50
|
+
),
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Pass a function child to receive the variant's resolved text color, so custom
|
|
54
|
+
// content matches the banner's theme automatically.
|
|
55
|
+
export const WithThemedContent: Story = {
|
|
56
|
+
args: { children: '', variant: 'error' },
|
|
57
|
+
render: (args) => (
|
|
58
|
+
<NotificationBanner {...args}>
|
|
59
|
+
{({ color }) => (
|
|
60
|
+
<View style={{ gap: 2 }}>
|
|
61
|
+
<Text style={{ color, fontSize: 13, fontWeight: '600' }}>Sync failed</Text>
|
|
62
|
+
<Text style={{ color, fontSize: 13 }}>Check your connection and try again.</Text>
|
|
63
|
+
</View>
|
|
64
|
+
)}
|
|
65
|
+
</NotificationBanner>
|
|
66
|
+
),
|
|
67
|
+
};
|
|
@@ -5,9 +5,13 @@ import { useTheme } from '../../../native/ThemeContext';
|
|
|
5
5
|
|
|
6
6
|
type BannerVariant = 'info' | 'success' | 'warning' | 'error';
|
|
7
7
|
|
|
8
|
+
/** Function form of children: receives the variant's resolved text color so
|
|
9
|
+
* custom content can match the banner's theme. */
|
|
10
|
+
type BannerContentRenderProp = (props: { color: string }) => ReactNode;
|
|
11
|
+
|
|
8
12
|
interface NotificationBannerProps {
|
|
9
13
|
variant?: BannerVariant;
|
|
10
|
-
children: ReactNode;
|
|
14
|
+
children: ReactNode | BannerContentRenderProp;
|
|
11
15
|
action?: ReactNode;
|
|
12
16
|
onDismiss?: () => void;
|
|
13
17
|
style?: StyleProp<ViewStyle>;
|
|
@@ -21,8 +25,10 @@ const styles = StyleSheet.create({
|
|
|
21
25
|
paddingHorizontal: spacing.m,
|
|
22
26
|
gap: spacing.xs,
|
|
23
27
|
},
|
|
24
|
-
|
|
28
|
+
body: {
|
|
25
29
|
flex: 1,
|
|
30
|
+
},
|
|
31
|
+
content: {
|
|
26
32
|
fontSize: font.size.bodyS,
|
|
27
33
|
fontFamily: font.family.body,
|
|
28
34
|
lineHeight: lineHeight.bodyS,
|
|
@@ -51,6 +57,7 @@ export function NotificationBanner({
|
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
const { bg, text, border: borderColor } = variantColors[variant];
|
|
60
|
+
const resolvedChildren = typeof children === 'function' ? children({ color: text }) : children;
|
|
54
61
|
|
|
55
62
|
return (
|
|
56
63
|
<View
|
|
@@ -58,7 +65,13 @@ export function NotificationBanner({
|
|
|
58
65
|
accessibilityLiveRegion={isUrgent ? 'assertive' : 'polite'}
|
|
59
66
|
style={[styles.banner, { backgroundColor: bg, borderBottomWidth: 3, borderBottomColor: borderColor }, style]}
|
|
60
67
|
>
|
|
61
|
-
<
|
|
68
|
+
<View style={styles.body}>
|
|
69
|
+
{typeof resolvedChildren === 'string' || typeof resolvedChildren === 'number' ? (
|
|
70
|
+
<Text style={[styles.content, { color: text }]}>{resolvedChildren}</Text>
|
|
71
|
+
) : (
|
|
72
|
+
resolvedChildren
|
|
73
|
+
)}
|
|
74
|
+
</View>
|
|
62
75
|
{action}
|
|
63
76
|
{onDismiss && (
|
|
64
77
|
<Pressable
|