react95-native-rabbl 0.1.1
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/LICENSE +21 -0
- package/README.md +24 -0
- package/package.json +154 -0
- package/src/assets/fonts/src/ms-sans-serif/MS Sans Serif.ttf +0 -0
- package/src/assets/fonts/src/ms-sans-serif/license.txt +4 -0
- package/src/assets/fonts/src/ms-sans-serif/readme.txt +26 -0
- package/src/assets/fonts/src/ms-sans-serif-bold/MS Sans Serif Bold.ttf +0 -0
- package/src/assets/fonts/src/ms-sans-serif-bold/license.txt +4 -0
- package/src/assets/fonts/src/ms-sans-serif-bold/readme.txt +26 -0
- package/src/components/AppBar/AppBar.spec.tsx +140 -0
- package/src/components/AppBar/AppBar.tsx +43 -0
- package/src/components/AppBar/AppBarBackAction.tsx +20 -0
- package/src/components/AppBar/AppBarContent.tsx +84 -0
- package/src/components/AppBar/index.ts +1 -0
- package/src/components/Button/Button.spec.tsx +59 -0
- package/src/components/Button/Button.tsx +236 -0
- package/src/components/Button/index.ts +1 -0
- package/src/components/Card/Card.spec.tsx +54 -0
- package/src/components/Card/Card.tsx +88 -0
- package/src/components/Card/CardContent.tsx +23 -0
- package/src/components/Card/index.ts +1 -0
- package/src/components/Checkbox/Checkbox.tsx +10 -0
- package/src/components/Checkbox/index.ts +1 -0
- package/src/components/ColorButton/ColorButton.tsx +69 -0
- package/src/components/ColorButton/index.ts +1 -0
- package/src/components/ColorPicker/ColorPicker.tsx +109 -0
- package/src/components/ColorPicker/index.ts +1 -0
- package/src/components/Desktop/Desktop.spec.tsx +32 -0
- package/src/components/Desktop/Desktop.tsx +132 -0
- package/src/components/Desktop/index.tsx +1 -0
- package/src/components/Divider/Divider.spec.tsx +47 -0
- package/src/components/Divider/Divider.tsx +52 -0
- package/src/components/Divider/index.tsx +1 -0
- package/src/components/FAB/FAB.tsx +288 -0
- package/src/components/FAB/FABGroup.tsx +385 -0
- package/src/components/FAB/index.ts +1 -0
- package/src/components/Fieldset/Fieldset.spec.tsx +48 -0
- package/src/components/Fieldset/Fieldset.tsx +107 -0
- package/src/components/Fieldset/index.ts +1 -0
- package/src/components/Hourglass/Hourglass.spec.tsx +24 -0
- package/src/components/Hourglass/Hourglass.tsx +43 -0
- package/src/components/Hourglass/base64hourglass.ts +3 -0
- package/src/components/Hourglass/index.ts +1 -0
- package/src/components/Icons/ArrowIcon.tsx +85 -0
- package/src/components/Icons/CheckmarkIcon.tsx +55 -0
- package/src/components/Icons/ChevronIcon.tsx +93 -0
- package/src/components/Icons/CloseIcon.tsx +48 -0
- package/src/components/Icons/index.ts +4 -0
- package/src/components/Label/Label.tsx +77 -0
- package/src/components/Label/index.ts +1 -0
- package/src/components/List/List.tsx +3 -0
- package/src/components/List/ListAccordion.tsx +154 -0
- package/src/components/List/ListItem.tsx +74 -0
- package/src/components/List/ListSection.tsx +51 -0
- package/src/components/List/index.ts +3 -0
- package/src/components/Menu/Menu.tsx +100 -0
- package/src/components/Menu/MenuItem.tsx +100 -0
- package/src/components/Menu/index.ts +1 -0
- package/src/components/NumberInput/NumberInput.spec.tsx +119 -0
- package/src/components/NumberInput/NumberInput.tsx +144 -0
- package/src/components/NumberInput/index.ts +1 -0
- package/src/components/Panel/Panel.spec.tsx +29 -0
- package/src/components/Panel/Panel.tsx +75 -0
- package/src/components/Panel/index.ts +1 -0
- package/src/components/Portal/Portal.tsx +52 -0
- package/src/components/Portal/PortalConsumer.tsx +48 -0
- package/src/components/Portal/PortalHost.tsx +150 -0
- package/src/components/Portal/PortalManager.tsx +57 -0
- package/src/components/Portal/index.ts +1 -0
- package/src/components/Progress/Progress.tsx +125 -0
- package/src/components/Progress/index.ts +1 -0
- package/src/components/Radio/Radio.tsx +14 -0
- package/src/components/Radio/index.ts +1 -0
- package/src/components/ScrollPanel/ScrollPanel.tsx +72 -0
- package/src/components/ScrollPanel/index.ts +1 -0
- package/src/components/ScrollView/ScrollView.tsx +284 -0
- package/src/components/ScrollView/index.ts +1 -0
- package/src/components/Select/Select.tsx +229 -0
- package/src/components/Select/SelectBase.tsx +119 -0
- package/src/components/Select/SelectBox.tsx +66 -0
- package/src/components/Select/index.ts +2 -0
- package/src/components/Slider/Slider.tsx +301 -0
- package/src/components/Slider/index.ts +1 -0
- package/src/components/Snackbar/Snackbar.tsx +260 -0
- package/src/components/Snackbar/SnackbarContent.tsx +23 -0
- package/src/components/Snackbar/index.ts +1 -0
- package/src/components/SwitchBase/SwitchBase.tsx +193 -0
- package/src/components/SwitchBase/index.ts +1 -0
- package/src/components/Tabs/Tabs.tsx +208 -0
- package/src/components/Tabs/index.ts +1 -0
- package/src/components/TextInput/TextInput.tsx +82 -0
- package/src/components/TextInput/index.ts +1 -0
- package/src/components/Toolbar/Toolbar.tsx +113 -0
- package/src/components/Toolbar/index.ts +1 -0
- package/src/components/Typography/Anchor.tsx +38 -0
- package/src/components/Typography/Text.spec.tsx +30 -0
- package/src/components/Typography/Text.tsx +55 -0
- package/src/components/Typography/Title.tsx +58 -0
- package/src/components/Typography/index.ts +3 -0
- package/src/components/Window/Window.tsx +132 -0
- package/src/components/Window/index.ts +1 -0
- package/src/core/Provider.tsx +52 -0
- package/src/core/theming.tsx +8 -0
- package/src/hooks/useAsyncReference.ts +22 -0
- package/src/hooks/useControlledOrUncontrolled.ts +23 -0
- package/src/index.ts +38 -0
- package/src/styles/shadow.tsx +36 -0
- package/src/styles/styleElements.tsx +105 -0
- package/src/styles/styles.ts +129 -0
- package/src/styles/themes/aiee.ts +36 -0
- package/src/styles/themes/ash.ts +35 -0
- package/src/styles/themes/azureOrange.ts +33 -0
- package/src/styles/themes/bee.ts +33 -0
- package/src/styles/themes/blackAndWhite.ts +33 -0
- package/src/styles/themes/blue.ts +36 -0
- package/src/styles/themes/brick.ts +33 -0
- package/src/styles/themes/candy.ts +33 -0
- package/src/styles/themes/cherry.ts +36 -0
- package/src/styles/themes/coldGray.ts +34 -0
- package/src/styles/themes/counterStrike.ts +33 -0
- package/src/styles/themes/darkTeal.ts +36 -0
- package/src/styles/themes/eggplant.ts +33 -0
- package/src/styles/themes/fxDev.ts +36 -0
- package/src/styles/themes/highContrast.ts +33 -0
- package/src/styles/themes/hotChocolate.ts +36 -0
- package/src/styles/themes/index.ts +103 -0
- package/src/styles/themes/lilac.ts +33 -0
- package/src/styles/themes/lilacRoseDark.ts +34 -0
- package/src/styles/themes/maple.ts +33 -0
- package/src/styles/themes/marine.ts +33 -0
- package/src/styles/themes/matrix.ts +33 -0
- package/src/styles/themes/millenium.ts +33 -0
- package/src/styles/themes/modernDark.ts +33 -0
- package/src/styles/themes/molecule.ts +33 -0
- package/src/styles/themes/monochrome.ts +0 -0
- package/src/styles/themes/ninjaTurtles.ts +33 -0
- package/src/styles/themes/olive.ts +33 -0
- package/src/styles/themes/original.ts +33 -0
- package/src/styles/themes/pamelaAnderson.ts +33 -0
- package/src/styles/themes/plum.ts +33 -0
- package/src/styles/themes/polarized.ts +36 -0
- package/src/styles/themes/powerShell.ts +36 -0
- package/src/styles/themes/rainyDay.ts +33 -0
- package/src/styles/themes/raspberry.ts +36 -0
- package/src/styles/themes/redWine.ts +36 -0
- package/src/styles/themes/rose.ts +33 -0
- package/src/styles/themes/seawater.ts +36 -0
- package/src/styles/themes/slate.ts +33 -0
- package/src/styles/themes/solarizedDark.ts +36 -0
- package/src/styles/themes/solarizedLight.ts +36 -0
- package/src/styles/themes/spruce.ts +33 -0
- package/src/styles/themes/stormClouds.ts +36 -0
- package/src/styles/themes/theSixtiesUSA.ts +33 -0
- package/src/styles/themes/tokyoDark.ts +33 -0
- package/src/styles/themes/tooSexy.ts +33 -0
- package/src/styles/themes/travel.ts +33 -0
- package/src/styles/themes/vaporTeal.ts +33 -0
- package/src/styles/themes/vermillion.ts +33 -0
- package/src/styles/themes/violetDark.ts +33 -0
- package/src/styles/themes/water.ts +33 -0
- package/src/styles/themes/wmii.ts +36 -0
- package/src/types.tsx +55 -0
- package/src/utils/index.ts +57 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, StyleSheet, ViewStyle, View } from 'react-native';
|
|
3
|
+
import type { LayoutChangeEvent } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import type { Theme, Orientation } from '../../types';
|
|
6
|
+
import { withTheme } from '../../core/theming';
|
|
7
|
+
|
|
8
|
+
import { Panel } from '../..';
|
|
9
|
+
|
|
10
|
+
import MenuItem from './MenuItem';
|
|
11
|
+
|
|
12
|
+
type Props = React.ComponentPropsWithRef<typeof View> & {
|
|
13
|
+
anchor?: React.ReactNode;
|
|
14
|
+
children: React.ReactNode;
|
|
15
|
+
open?: boolean;
|
|
16
|
+
orientation?: Orientation;
|
|
17
|
+
style?: StyleProp<ViewStyle>;
|
|
18
|
+
theme: Theme;
|
|
19
|
+
horizontalAlignment?: 'left' | 'right';
|
|
20
|
+
verticalAlignment?: 'above' | 'below';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const Menu = ({
|
|
24
|
+
anchor,
|
|
25
|
+
children,
|
|
26
|
+
open = false,
|
|
27
|
+
orientation = 'vertical',
|
|
28
|
+
style = {},
|
|
29
|
+
theme,
|
|
30
|
+
horizontalAlignment: horizontalAlign = 'left',
|
|
31
|
+
verticalAlignment: verticalAlign = 'below',
|
|
32
|
+
...rest
|
|
33
|
+
}: Props) => {
|
|
34
|
+
const [menuSize, setMenuSize] = React.useState({ width: 0, height: 0 });
|
|
35
|
+
|
|
36
|
+
const handleMenuLayout = (e: LayoutChangeEvent) => {
|
|
37
|
+
const { width, height } = e.nativeEvent.layout;
|
|
38
|
+
setMenuSize({ width, height });
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const menuPosition: StyleProp<ViewStyle> = {};
|
|
42
|
+
if (verticalAlign === 'below') {
|
|
43
|
+
menuPosition.top = '100%';
|
|
44
|
+
} else {
|
|
45
|
+
menuPosition.top = -menuSize.height;
|
|
46
|
+
}
|
|
47
|
+
if (horizontalAlign === 'left') {
|
|
48
|
+
menuPosition.left = 0;
|
|
49
|
+
} else {
|
|
50
|
+
menuPosition.right = menuSize.width;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<View style={styles.wrapper}>
|
|
55
|
+
{anchor}
|
|
56
|
+
|
|
57
|
+
{open && (
|
|
58
|
+
<View style={[styles.menuWrapper, menuPosition]}>
|
|
59
|
+
<Panel
|
|
60
|
+
{...rest}
|
|
61
|
+
theme={theme}
|
|
62
|
+
variant='raised'
|
|
63
|
+
elevation={2}
|
|
64
|
+
onLayout={handleMenuLayout}
|
|
65
|
+
style={[
|
|
66
|
+
styles.menu,
|
|
67
|
+
{
|
|
68
|
+
display: 'flex',
|
|
69
|
+
flexDirection: orientation === 'vertical' ? 'column' : 'row',
|
|
70
|
+
},
|
|
71
|
+
style,
|
|
72
|
+
]}
|
|
73
|
+
>
|
|
74
|
+
{children}
|
|
75
|
+
</Panel>
|
|
76
|
+
</View>
|
|
77
|
+
)}
|
|
78
|
+
</View>
|
|
79
|
+
);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const styles = StyleSheet.create({
|
|
83
|
+
wrapper: {
|
|
84
|
+
position: 'relative',
|
|
85
|
+
},
|
|
86
|
+
menuWrapper: {
|
|
87
|
+
position: 'absolute',
|
|
88
|
+
},
|
|
89
|
+
menu: {
|
|
90
|
+
width: 'auto',
|
|
91
|
+
position: 'absolute',
|
|
92
|
+
height: 'auto',
|
|
93
|
+
flexGrow: 0,
|
|
94
|
+
padding: 6,
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
Menu.Item = MenuItem;
|
|
99
|
+
|
|
100
|
+
export default withTheme(Menu);
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
StyleSheet,
|
|
4
|
+
View,
|
|
5
|
+
TouchableHighlight,
|
|
6
|
+
StyleProp,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
|
|
10
|
+
import type { Theme, Sizes } from '../../types';
|
|
11
|
+
import { withTheme } from '../../core/theming';
|
|
12
|
+
import { blockSizes, builtTextStyles } from '../../styles/styles';
|
|
13
|
+
|
|
14
|
+
import { Text } from '../..';
|
|
15
|
+
|
|
16
|
+
// TODO: add icon prop
|
|
17
|
+
|
|
18
|
+
type Props = {
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
onPress: () => void;
|
|
21
|
+
primary?: boolean;
|
|
22
|
+
size?: Sizes;
|
|
23
|
+
style?: StyleProp<ViewStyle>;
|
|
24
|
+
theme: Theme;
|
|
25
|
+
title: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export const Item = ({
|
|
29
|
+
disabled,
|
|
30
|
+
onPress,
|
|
31
|
+
primary = false,
|
|
32
|
+
size = 'md',
|
|
33
|
+
style,
|
|
34
|
+
theme,
|
|
35
|
+
title,
|
|
36
|
+
...rest
|
|
37
|
+
}: Props) => {
|
|
38
|
+
const [isPressed, setIsPressed] = useState(false);
|
|
39
|
+
|
|
40
|
+
const textStyles = builtTextStyles(theme);
|
|
41
|
+
return (
|
|
42
|
+
<View
|
|
43
|
+
{...rest}
|
|
44
|
+
style={[
|
|
45
|
+
styles.item,
|
|
46
|
+
{ height: blockSizes[size] },
|
|
47
|
+
{
|
|
48
|
+
backgroundColor: isPressed ? theme.hoverBackground : theme.material,
|
|
49
|
+
},
|
|
50
|
+
style,
|
|
51
|
+
]}
|
|
52
|
+
>
|
|
53
|
+
<TouchableHighlight
|
|
54
|
+
style={[styles.button]}
|
|
55
|
+
onPress={onPress}
|
|
56
|
+
disabled={disabled}
|
|
57
|
+
onHideUnderlay={() => setIsPressed(false)}
|
|
58
|
+
onShowUnderlay={() => setIsPressed(true)}
|
|
59
|
+
underlayColor='none'
|
|
60
|
+
// TODO: which accessibilityRole put in here?
|
|
61
|
+
accessibilityRole='menuitem'
|
|
62
|
+
accessibilityState={{ disabled }}
|
|
63
|
+
>
|
|
64
|
+
<View pointerEvents='none' style={[styles.content]}>
|
|
65
|
+
<Text
|
|
66
|
+
theme={theme}
|
|
67
|
+
bold={primary}
|
|
68
|
+
style={[
|
|
69
|
+
disabled ? textStyles.disabled : textStyles.default,
|
|
70
|
+
!disabled && {
|
|
71
|
+
color: isPressed
|
|
72
|
+
? theme.materialTextInvert
|
|
73
|
+
: theme.materialText,
|
|
74
|
+
},
|
|
75
|
+
]}
|
|
76
|
+
>
|
|
77
|
+
{title}
|
|
78
|
+
</Text>
|
|
79
|
+
</View>
|
|
80
|
+
</TouchableHighlight>
|
|
81
|
+
</View>
|
|
82
|
+
);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const styles = StyleSheet.create({
|
|
86
|
+
item: {
|
|
87
|
+
position: 'relative',
|
|
88
|
+
},
|
|
89
|
+
button: {
|
|
90
|
+
flex: 1,
|
|
91
|
+
justifyContent: 'center',
|
|
92
|
+
alignItems: 'center',
|
|
93
|
+
paddingHorizontal: 8,
|
|
94
|
+
},
|
|
95
|
+
content: {
|
|
96
|
+
alignSelf: 'flex-start',
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export default withTheme(Item);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Menu';
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import { NumberInput } from '../..';
|
|
4
|
+
|
|
5
|
+
describe('<NumberInput />', () => {
|
|
6
|
+
it('should call onChange on increment press', () => {
|
|
7
|
+
const handleChange = jest.fn();
|
|
8
|
+
|
|
9
|
+
const { getByTestId } = render(
|
|
10
|
+
<NumberInput onChange={handleChange} defaultValue={2} />,
|
|
11
|
+
);
|
|
12
|
+
const spinButton = getByTestId('increment');
|
|
13
|
+
fireEvent(spinButton, 'press');
|
|
14
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
15
|
+
expect(handleChange).toHaveBeenCalledWith(3);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should call onChange on decrement press', () => {
|
|
19
|
+
const handleChange = jest.fn();
|
|
20
|
+
|
|
21
|
+
const { getByTestId } = render(
|
|
22
|
+
<NumberInput onChange={handleChange} defaultValue={2} />,
|
|
23
|
+
);
|
|
24
|
+
const spinButton = getByTestId('decrement');
|
|
25
|
+
fireEvent(spinButton, 'press');
|
|
26
|
+
expect(handleChange).toHaveBeenCalledTimes(1);
|
|
27
|
+
expect(handleChange).toHaveBeenCalledWith(1);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('should reach max value', () => {
|
|
31
|
+
const { getByTestId } = render(
|
|
32
|
+
<NumberInput defaultValue={90} min={0} max={100} step={10} />,
|
|
33
|
+
);
|
|
34
|
+
const input = getByTestId('input');
|
|
35
|
+
const incrementButton = getByTestId('increment');
|
|
36
|
+
fireEvent(incrementButton, 'press');
|
|
37
|
+
|
|
38
|
+
expect(input.props.value).toBe('100');
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('should reach min value', () => {
|
|
42
|
+
const { getByTestId } = render(
|
|
43
|
+
<NumberInput defaultValue={10} min={0} max={100} step={10} />,
|
|
44
|
+
);
|
|
45
|
+
const input = getByTestId('input');
|
|
46
|
+
const decrementButton = getByTestId('decrement');
|
|
47
|
+
fireEvent(decrementButton, 'press');
|
|
48
|
+
|
|
49
|
+
expect(input.props.value).toBe('0');
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('prop: step', () => {
|
|
53
|
+
it('should be 1 by default', () => {
|
|
54
|
+
const { getByTestId } = render(<NumberInput defaultValue={0} />);
|
|
55
|
+
const input = getByTestId('input');
|
|
56
|
+
|
|
57
|
+
const incrementButton = getByTestId('increment');
|
|
58
|
+
fireEvent(incrementButton, 'press');
|
|
59
|
+
|
|
60
|
+
expect(input.props.value).toBe('1');
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should change value by specified step', () => {
|
|
64
|
+
const { getByTestId } = render(
|
|
65
|
+
<NumberInput defaultValue={10} step={3} />,
|
|
66
|
+
);
|
|
67
|
+
const input = getByTestId('input');
|
|
68
|
+
|
|
69
|
+
const decrementButton = getByTestId('decrement');
|
|
70
|
+
fireEvent(decrementButton, 'press');
|
|
71
|
+
|
|
72
|
+
expect(input.props.value).toBe('7');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it('should handle decimal step', () => {
|
|
76
|
+
const { getByTestId } = render(
|
|
77
|
+
<NumberInput defaultValue={10} step={0.3} />,
|
|
78
|
+
);
|
|
79
|
+
const input = getByTestId('input');
|
|
80
|
+
|
|
81
|
+
const decrementButton = getByTestId('decrement');
|
|
82
|
+
fireEvent(decrementButton, 'press');
|
|
83
|
+
|
|
84
|
+
expect(input.props.value).toBe('9.7');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
describe('prop: disabled', () => {
|
|
89
|
+
// it('should render disabled', () => {
|
|
90
|
+
// const { getByTestId } = render(
|
|
91
|
+
// <NumberInput defaultValue={10} disabled />,
|
|
92
|
+
// );
|
|
93
|
+
// const input = getByTestId('input');
|
|
94
|
+
// const incrementButton = getByTestId('increment');
|
|
95
|
+
// const decrementButton = getByTestId('decrement');
|
|
96
|
+
// expect(input.props.editable).toBe(false);
|
|
97
|
+
// expect(incrementButton.props.disabled).toBe(true);
|
|
98
|
+
// expect(decrementButton.props.disabled).toBe(true);
|
|
99
|
+
// });
|
|
100
|
+
// it('should not react to button clicks', () => {
|
|
101
|
+
// const { getByTestId } = render(
|
|
102
|
+
// <NumberInput defaultValue={10} disabled />,
|
|
103
|
+
// );
|
|
104
|
+
// const input = getByTestId('input');
|
|
105
|
+
// const incrementButton = getByTestId('increment');
|
|
106
|
+
// const decrementButton = getByTestId('decrement');
|
|
107
|
+
// fireEvent(incrementButton, 'press');
|
|
108
|
+
// expect(input.props.value).toBe('10');
|
|
109
|
+
// fireEvent(decrementButton, 'press');
|
|
110
|
+
// expect(input.props.value).toBe('10');
|
|
111
|
+
// });
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('prop: width', () => {
|
|
115
|
+
it('should render component of specified width', () => {});
|
|
116
|
+
|
|
117
|
+
it('should handle %', () => {});
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { View, StyleProp, StyleSheet, ViewStyle } from 'react-native';
|
|
3
|
+
import useControlledOrUncontrolled from '../../hooks/useControlledOrUncontrolled';
|
|
4
|
+
|
|
5
|
+
import type { Theme, DimensionValue } from '../../types';
|
|
6
|
+
import { withTheme } from '../../core/theming';
|
|
7
|
+
|
|
8
|
+
import { blockSizes } from '../../styles/styles';
|
|
9
|
+
import { clamp } from '../../utils';
|
|
10
|
+
|
|
11
|
+
import { TextInput, Button, ArrowIcon } from '../..';
|
|
12
|
+
|
|
13
|
+
type Props = {
|
|
14
|
+
defaultValue?: number;
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
inputWidth?: DimensionValue;
|
|
17
|
+
max?: number | null;
|
|
18
|
+
min?: number | null;
|
|
19
|
+
onChange?: (value: number) => void;
|
|
20
|
+
step?: number;
|
|
21
|
+
style?: StyleProp<ViewStyle>;
|
|
22
|
+
theme: Theme;
|
|
23
|
+
value?: number;
|
|
24
|
+
variant?: 'default' | 'flat';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// TODO: allow to center input text horizontally
|
|
28
|
+
// TODO: how are uncontrolled inputs handled in RN?
|
|
29
|
+
const NumberInput = ({
|
|
30
|
+
defaultValue,
|
|
31
|
+
disabled,
|
|
32
|
+
inputWidth,
|
|
33
|
+
max = null,
|
|
34
|
+
min = null,
|
|
35
|
+
onChange,
|
|
36
|
+
step = 1,
|
|
37
|
+
style = {},
|
|
38
|
+
theme,
|
|
39
|
+
value,
|
|
40
|
+
variant = 'default',
|
|
41
|
+
...rest
|
|
42
|
+
}: Props) => {
|
|
43
|
+
const [valueDerived, setValueState] = useControlledOrUncontrolled({
|
|
44
|
+
value,
|
|
45
|
+
defaultValue,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const handleClick = (val: number) => {
|
|
49
|
+
const stateValue = parseFloat(valueDerived);
|
|
50
|
+
const newValue = clamp(
|
|
51
|
+
+parseFloat((stateValue + val).toString()).toFixed(2),
|
|
52
|
+
min,
|
|
53
|
+
max,
|
|
54
|
+
).toString();
|
|
55
|
+
|
|
56
|
+
setValueState(parseFloat(newValue));
|
|
57
|
+
|
|
58
|
+
if (onChange) {
|
|
59
|
+
onChange(parseFloat(newValue));
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const valueDerivedNumber = parseFloat(valueDerived);
|
|
64
|
+
|
|
65
|
+
const isDecrementDisabled = disabled || valueDerivedNumber === min;
|
|
66
|
+
const isIncrementDisabled = disabled || valueDerivedNumber === max;
|
|
67
|
+
const isFlat = variant === 'flat';
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<View
|
|
71
|
+
style={[styles.wrapper, style]}
|
|
72
|
+
accessibilityState={{ disabled }}
|
|
73
|
+
// TODO: are these accessibility traits correct?
|
|
74
|
+
accessibilityRole='adjustable'
|
|
75
|
+
accessibilityValue={{
|
|
76
|
+
min: min === null ? undefined : min,
|
|
77
|
+
max: max === null ? undefined : max,
|
|
78
|
+
now: valueDerived,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
<Button
|
|
82
|
+
theme={theme}
|
|
83
|
+
disabled={isDecrementDisabled}
|
|
84
|
+
onPress={() => handleClick(-step)}
|
|
85
|
+
variant={isFlat ? 'flat' : 'raised'}
|
|
86
|
+
style={styles.button}
|
|
87
|
+
testID='decrement'
|
|
88
|
+
>
|
|
89
|
+
<ArrowIcon
|
|
90
|
+
theme={theme}
|
|
91
|
+
segments={4}
|
|
92
|
+
disabled={isDecrementDisabled}
|
|
93
|
+
direction='left'
|
|
94
|
+
/>
|
|
95
|
+
</Button>
|
|
96
|
+
<TextInput
|
|
97
|
+
theme={theme}
|
|
98
|
+
variant={variant}
|
|
99
|
+
disabled={disabled}
|
|
100
|
+
value={valueDerived.toString()}
|
|
101
|
+
style={[styles.input, { width: inputWidth || 'auto' }]}
|
|
102
|
+
editable={false}
|
|
103
|
+
// eslint-disable-next-line react/jsx-props-no-spreading
|
|
104
|
+
testID='input'
|
|
105
|
+
{...rest}
|
|
106
|
+
/>
|
|
107
|
+
<Button
|
|
108
|
+
theme={theme}
|
|
109
|
+
disabled={isIncrementDisabled}
|
|
110
|
+
onPress={() => handleClick(step)}
|
|
111
|
+
variant={isFlat ? 'flat' : 'raised'}
|
|
112
|
+
style={styles.button}
|
|
113
|
+
testID='increment'
|
|
114
|
+
>
|
|
115
|
+
<ArrowIcon
|
|
116
|
+
theme={theme}
|
|
117
|
+
segments={4}
|
|
118
|
+
disabled={isIncrementDisabled}
|
|
119
|
+
direction='right'
|
|
120
|
+
/>
|
|
121
|
+
</Button>
|
|
122
|
+
</View>
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const styles = StyleSheet.create({
|
|
127
|
+
wrapper: {
|
|
128
|
+
display: 'flex',
|
|
129
|
+
flexDirection: 'row',
|
|
130
|
+
justifyContent: 'center',
|
|
131
|
+
},
|
|
132
|
+
input: {
|
|
133
|
+
marginHorizontal: 2,
|
|
134
|
+
minWidth: blockSizes.md + 2,
|
|
135
|
+
},
|
|
136
|
+
button: {
|
|
137
|
+
width: blockSizes.md,
|
|
138
|
+
},
|
|
139
|
+
buttonText: {
|
|
140
|
+
fontSize: 24,
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
export default withTheme(NumberInput);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './NumberInput';
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render } from '@testing-library/react-native';
|
|
3
|
+
|
|
4
|
+
import { testId } from './Panel';
|
|
5
|
+
import { Panel, Text } from '../..';
|
|
6
|
+
|
|
7
|
+
describe('<Panel />', () => {
|
|
8
|
+
it('should render children', () => {
|
|
9
|
+
const { getByTestId } = render(
|
|
10
|
+
<Panel>
|
|
11
|
+
<Text>Banana dance</Text>
|
|
12
|
+
</Panel>,
|
|
13
|
+
);
|
|
14
|
+
|
|
15
|
+
expect(getByTestId(testId)).toHaveTextContent('Banana dance');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('should render custom styles', () => {
|
|
19
|
+
const style = { backgroundColor: 'teal' };
|
|
20
|
+
|
|
21
|
+
const { getByTestId } = render(
|
|
22
|
+
<Panel style={style}>
|
|
23
|
+
<Text>Panel</Text>
|
|
24
|
+
</Panel>,
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
expect(getByTestId(testId)).toHaveStyle(style);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { StyleProp, StyleSheet, Animated, View, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
import { withTheme } from '../../core/theming';
|
|
5
|
+
import { Border } from '../../styles/styleElements';
|
|
6
|
+
import shadow from '../../styles/shadow';
|
|
7
|
+
import type { Theme } from '../../types';
|
|
8
|
+
|
|
9
|
+
export const testId = 'panel';
|
|
10
|
+
|
|
11
|
+
// TODO: common interface with styleElements/Border ?
|
|
12
|
+
type Props = React.ComponentPropsWithRef<typeof View> & {
|
|
13
|
+
background?: 'material' | 'canvas' | 'materialDark';
|
|
14
|
+
children?: React.ReactNode;
|
|
15
|
+
elevation?: number;
|
|
16
|
+
invert?: boolean;
|
|
17
|
+
radius?: number;
|
|
18
|
+
style?: StyleProp<ViewStyle>;
|
|
19
|
+
theme: Theme;
|
|
20
|
+
variant?: 'default' | 'well' | 'raised' | 'clear' | 'cutout';
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const Panel = ({
|
|
24
|
+
background = 'material',
|
|
25
|
+
children,
|
|
26
|
+
elevation = 0,
|
|
27
|
+
invert = false,
|
|
28
|
+
radius = 0,
|
|
29
|
+
style = {},
|
|
30
|
+
theme,
|
|
31
|
+
variant = 'default',
|
|
32
|
+
...rest
|
|
33
|
+
}: Props) => {
|
|
34
|
+
const getBackgroundColor = () => {
|
|
35
|
+
return theme[background];
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
// TODO: fix this TS error
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
41
|
+
// @ts-ignore
|
|
42
|
+
<Animated.View
|
|
43
|
+
{...rest}
|
|
44
|
+
style={[
|
|
45
|
+
styles.container,
|
|
46
|
+
{
|
|
47
|
+
padding: variant === 'well' ? 2 : 4,
|
|
48
|
+
backgroundColor: getBackgroundColor(),
|
|
49
|
+
borderRadius: radius,
|
|
50
|
+
},
|
|
51
|
+
shadow(elevation),
|
|
52
|
+
style,
|
|
53
|
+
]}
|
|
54
|
+
testID={testId}
|
|
55
|
+
>
|
|
56
|
+
{variant !== 'clear' && (
|
|
57
|
+
<Border
|
|
58
|
+
theme={theme}
|
|
59
|
+
variant={variant}
|
|
60
|
+
radius={radius}
|
|
61
|
+
invert={invert}
|
|
62
|
+
/>
|
|
63
|
+
)}
|
|
64
|
+
{children}
|
|
65
|
+
</Animated.View>
|
|
66
|
+
);
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const styles = StyleSheet.create({
|
|
70
|
+
container: {
|
|
71
|
+
position: 'relative',
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
export default withTheme(Panel);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { default } from './Panel';
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import PortalConsumer from './PortalConsumer';
|
|
3
|
+
import PortalHost, { PortalContext, PortalMethods } from './PortalHost';
|
|
4
|
+
|
|
5
|
+
import type { Theme } from '../../types';
|
|
6
|
+
import { ThemeProvider, withTheme } from '../../core/theming';
|
|
7
|
+
|
|
8
|
+
type Props = {
|
|
9
|
+
/**
|
|
10
|
+
* Content of the `Portal`.
|
|
11
|
+
*/
|
|
12
|
+
children: React.ReactNode;
|
|
13
|
+
/**
|
|
14
|
+
* @optional
|
|
15
|
+
*/
|
|
16
|
+
theme: Theme;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Portal allows to render a component at a different place in the parent tree.
|
|
21
|
+
* You can use it to render content which should appear above other elements, similar to `Modal`.
|
|
22
|
+
* It requires a [`Portal.Host`](portal-host.html) component to be rendered somewhere in the parent tree.
|
|
23
|
+
*
|
|
24
|
+
* ## Usage
|
|
25
|
+
* ```js
|
|
26
|
+
* import * as React from 'react';
|
|
27
|
+
* import { Portal, Text } from 'react-native-paper';
|
|
28
|
+
*
|
|
29
|
+
* const MyComponent = () => (
|
|
30
|
+
* <Portal>
|
|
31
|
+
* <Text>This is rendered at a different place</Text>
|
|
32
|
+
* </Portal>
|
|
33
|
+
* );
|
|
34
|
+
*
|
|
35
|
+
* export default MyComponent;
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
const Portal = ({ children, theme }: Props) => {
|
|
39
|
+
return (
|
|
40
|
+
<PortalContext.Consumer>
|
|
41
|
+
{manager => (
|
|
42
|
+
<PortalConsumer manager={manager as PortalMethods}>
|
|
43
|
+
<ThemeProvider theme={theme}>{children}</ThemeProvider>
|
|
44
|
+
</PortalConsumer>
|
|
45
|
+
)}
|
|
46
|
+
</PortalContext.Consumer>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
Portal.Host = PortalHost;
|
|
51
|
+
|
|
52
|
+
export default withTheme(Portal);
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/* eslint-disable react/destructuring-assignment */
|
|
2
|
+
import * as React from 'react';
|
|
3
|
+
import type { PortalMethods } from './PortalHost';
|
|
4
|
+
|
|
5
|
+
type Props = {
|
|
6
|
+
manager: PortalMethods;
|
|
7
|
+
children: React.ReactNode;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export default class PortalConsumer extends React.Component<Props> {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
private key: any;
|
|
13
|
+
|
|
14
|
+
async componentDidMount() {
|
|
15
|
+
this.checkManager();
|
|
16
|
+
|
|
17
|
+
// Delay updating to prevent React from going to infinite loop
|
|
18
|
+
await Promise.resolve();
|
|
19
|
+
|
|
20
|
+
this.key = this.props.manager.mount(this.props.children);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
componentDidUpdate() {
|
|
24
|
+
this.checkManager();
|
|
25
|
+
|
|
26
|
+
this.props.manager.update(this.key, this.props.children);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
componentWillUnmount() {
|
|
30
|
+
this.checkManager();
|
|
31
|
+
|
|
32
|
+
this.props.manager.unmount(this.key);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
private checkManager() {
|
|
36
|
+
if (!this.props.manager) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
'Looks like you forgot to wrap your root component with `Provider` component from `react-native-paper`.\n\n' +
|
|
39
|
+
"Please read our getting-started guide and make sure you've followed all the required steps.\n\n" +
|
|
40
|
+
'https://callstack.github.io/react-native-paper/getting-started.html',
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
render() {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|