@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.
Files changed (26) hide show
  1. package/CHANGELOG.md +87 -0
  2. package/dist/index.cjs +176 -165
  3. package/dist/index.d.cts +12 -2
  4. package/dist/index.d.ts +12 -2
  5. package/dist/index.js +176 -165
  6. package/package.json +1 -1
  7. package/src/components/interaction/Dialog/Dialog.mdx +3 -2
  8. package/src/components/interaction/Dialog/Dialog.spec.tsx +42 -11
  9. package/src/components/interaction/Dialog/Dialog.tsx +19 -12
  10. package/src/components/interaction/DropdownMenu/DropdownMenu.mdx +3 -1
  11. package/src/components/interaction/DropdownMenu/DropdownMenu.spec.tsx +73 -0
  12. package/src/components/interaction/DropdownMenu/DropdownMenu.tsx +79 -73
  13. package/src/components/interaction/Popover/Popover.mdx +3 -1
  14. package/src/components/interaction/Popover/Popover.spec.tsx +43 -3
  15. package/src/components/interaction/Popover/Popover.tsx +23 -13
  16. package/src/components/interaction/SideSheet/SideSheet.mdx +4 -3
  17. package/src/components/interaction/SideSheet/SideSheet.spec.tsx +8 -9
  18. package/src/components/interaction/SideSheet/SideSheet.tsx +9 -3
  19. package/src/components/ui/Alert/Alert.spec.tsx +32 -1
  20. package/src/components/ui/Alert/Alert.stories.tsx +45 -0
  21. package/src/components/ui/Alert/Alert.tsx +11 -2
  22. package/src/components/ui/Collapsible/Collapsible.spec.tsx +11 -0
  23. package/src/components/ui/Collapsible/Collapsible.tsx +13 -8
  24. package/src/components/ui/NotificationBanner/NotificationBanner.spec.tsx +32 -1
  25. package/src/components/ui/NotificationBanner/NotificationBanner.stories.tsx +30 -0
  26. 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
- fontSize: font.size.body,
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
- const rotateZ = rotation.interpolate({
111
+ // Chevron points right when collapsed (-45deg) and down when expanded (45deg).
112
+ const rotate = rotation.interpolate({
107
113
  inputRange: [0, 1],
108
- outputRange: ['0deg', '90deg'],
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.Text
125
- style={[styles.chevron, { color: colors.colorOnBgSubtle, transform: [{ rotateZ }] }]}
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
- content: {
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
- <Text style={[styles.content, { color: text }]}>{children}</Text>
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