Skip to content

Commit

Permalink
Merge branch 'master' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
adids1221 committed Mar 13, 2024
2 parents fc0abd1 + 9415434 commit 2c74549
Show file tree
Hide file tree
Showing 9 changed files with 193 additions and 33 deletions.
5 changes: 4 additions & 1 deletion demo/src/screens/componentScreens/FloatingButtonScreen.tsx
Expand Up @@ -13,7 +13,8 @@ export default class FloatingButtonScreen extends Component<{}, State> {
state = {
showButton: true,
showSecondary: true,
showVertical: true
showVertical: true,
fullWidth: false
};

notNow = () => {
Expand All @@ -34,6 +35,7 @@ export default class FloatingButtonScreen extends Component<{}, State> {
Trigger Floating Button
</Text>
{renderBooleanOption.call(this, 'Show Floating Button', 'showButton')}
{renderBooleanOption.call(this, 'Full Width Button', 'fullWidth')}
{renderBooleanOption.call(this, 'Show Secondary Button', 'showSecondary')}
{renderBooleanOption.call(this, 'Button Layout Vertical', 'showVertical')}

Expand Down Expand Up @@ -64,6 +66,7 @@ export default class FloatingButtonScreen extends Component<{}, State> {

<FloatingButton
visible={this.state.showButton}
fullWidth={this.state.fullWidth}
button={{
label: 'Approve',
onPress: this.close
Expand Down
2 changes: 1 addition & 1 deletion docuilib/docusaurus.config.js
Expand Up @@ -17,7 +17,7 @@ const darkCodeTheme = require('prism-react-renderer/themes/dracula');
trailingSlash: false,
customFields: {
docsMainEntry: 'getting-started/setup',
expoSnackLink: 'https://snack.expo.io/@ethanshar/rnuilib_snack?platform=ios&supportedPlatforms=ios,android',
expoSnackLink: 'https://snack.expo.io/@ethanshar/rnuilib_snack',
stars: '4.7'
},
plugins: ['docusaurus-plugin-sass'],
Expand Down
Expand Up @@ -129,7 +129,9 @@ - (void)_keyboardDidShowNotification:(NSNotification*)notification
{
_keyboardState = KeyboardStateShown;

[self invalidateIntrinsicContentSize];
if (_keyboardHeight > 0) { //prevent triggering observeValueForKeyPath if an external keyboard is in use
[self invalidateIntrinsicContentSize];
}
}

- (void)_keyboardWillHideNotification:(NSNotification*)notification
Expand Down
2 changes: 1 addition & 1 deletion lib/package.json
@@ -1,6 +1,6 @@
{
"name": "uilib-native",
"version": "4.1.2",
"version": "4.1.3",
"homepage": "https://github.com/wix/react-native-ui-lib",
"description": "uilib native components (separated from js components)",
"main": "components/index.js",
Expand Down
154 changes: 154 additions & 0 deletions src/components/floatingButton/__tests__/index.spec.tsx
@@ -0,0 +1,154 @@
import React from 'react';
import {ViewStyle} from 'react-native';
import {render} from '@testing-library/react-native';
import {Spacings} from '../../../style';
import FloatingButton, {FloatingButtonLayouts} from '../index';
import {ButtonDriver} from '../../button/Button.driver.new';
import {useComponentDriver, ComponentProps} from '../../../testkit/new/Component.driver';

const TEST_ID = 'floating_button';
const button = {
label: 'First'
};
const secondaryButton = {
label: 'Second'
};

const TestCase = (props) => {
return <FloatingButton {...props} testID={TEST_ID}/>;
};
export const FloatingButtonDriver = (props: ComponentProps) => {
const driver = useComponentDriver(props);
const getStyle = () => driver.getProps().style as ViewStyle;
return {...driver, getStyle};
};

describe('FloatingButton', () => {
describe('visible', () => {
it('should render a button according to visibility', async () => {
const props = {};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});
expect(await buttonDriver.exists()).not.toBeTruthy();

renderTree.rerender(<TestCase visible/>);
expect(await buttonDriver.exists()).toBeTruthy();
});
});

describe('buttons', () => {
it('should render a button', async () => {
const props = {visible: true};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});
expect(await buttonDriver.exists()).toBeTruthy();
});

it('should not render a secondary button', async () => {
const props = {visible: true};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.secondaryButton`});
expect(await buttonDriver.exists()).not.toBeTruthy();
});

it('should render a button with a label', async () => {
const props = {visible: true, button};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});
expect(await buttonDriver.getLabel().getText()).toEqual(button.label);
});

it('should render secondary button with label', async () => {
const props = {visible: true, secondaryButton};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.secondaryButton`});
expect(await buttonDriver.getLabel().getText()).toEqual(secondaryButton.label);
});
});

describe('bottomMargin', () => {
it('should have default bottom margin', () => {
const props = {visible: true};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});

expect(buttonDriver.getProps()?.style?.marginBottom).toBe(Spacings.s8);
});

it('should have default bottom margin for both buttons', () => {
const props = {visible: true, secondaryButton};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});
const buttonDriver2 = ButtonDriver({renderTree, testID: `${TEST_ID}.secondaryButton`});

expect(buttonDriver.getProps()?.style?.marginBottom).toBe(Spacings.s4);
expect(buttonDriver2.getProps()?.style?.marginBottom).toBe(Spacings.s7);
});

it('should have bottom margin that match bottomMargin prop', () => {
const props = {visible: true, bottomMargin: 10};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});

expect(buttonDriver.getProps()?.style?.marginBottom).toBe(10);
});

it('should have bottom margin for secondary button that match bottomMarginProp', () => {
const props = {visible: true, secondaryButton, bottomMargin: 10};
const renderTree = render(<TestCase {...props}/>);
const buttonDriver = ButtonDriver({renderTree, testID: `${TEST_ID}.button`});
const buttonDriver2 = ButtonDriver({renderTree, testID: `${TEST_ID}.secondaryButton`});

expect(buttonDriver.getProps()?.style?.marginBottom).toBe(Spacings.s4);
expect(buttonDriver2.getProps()?.style?.marginBottom).toBe(10);
});
});

describe('buttonLayout', () => {
it('should style vertical layout (default)', () => {
const props = {visible: true, secondaryButton};
const renderTree = render(<TestCase {...props}/>);
const driver = FloatingButtonDriver({renderTree, testID: TEST_ID});

expect(driver.getStyle()?.flexDirection).toBe(undefined);
expect(driver.getStyle()?.alignItems).toBe('center');
expect(driver.getStyle()?.justifyContent).toBe('center');
expect(driver.getStyle()?.paddingHorizontal).toBe(undefined);
});

it('should style horizontal layout', () => {
const props = {visible: true, secondaryButton, buttonLayout: FloatingButtonLayouts.HORIZONTAL};
const renderTree = render(<TestCase {...props}/>);
const driver = FloatingButtonDriver({renderTree, testID: TEST_ID});

expect(driver.getStyle()?.flexDirection).toBe('row');
expect(driver.getStyle()?.alignItems).toBe('center');
expect(driver.getStyle()?.justifyContent).toBe('center');
expect(driver.getStyle()?.paddingHorizontal).toBe(undefined);
});
});

describe('fullWidth', () => {
it('should style vertical layout (default) when fullWidth is true', () => {
const props = {visible: true, secondaryButton, fullWidth: true};
const renderTree = render(<TestCase {...props}/>);
const driver = FloatingButtonDriver({renderTree, testID: TEST_ID});

expect(driver.getStyle()?.flexDirection).toBe(undefined);
expect(driver.getStyle()?.alignItems).toBe(undefined);
expect(driver.getStyle()?.justifyContent).toBe(undefined);
expect(driver.getStyle()?.paddingHorizontal).toBe(16);
});

it('should style horizontal layout when fullWidth is true', () => {
const props = {visible: true, secondaryButton, buttonLayout: FloatingButtonLayouts.HORIZONTAL, fullWidth: true};
const renderTree = render(<TestCase {...props}/>);
const driver = FloatingButtonDriver({renderTree, testID: TEST_ID});

expect(driver.getStyle()?.flexDirection).toBe('row');
expect(driver.getStyle()?.alignItems).toBe('center');
expect(driver.getStyle()?.justifyContent).toBe('center');
expect(driver.getStyle()?.paddingHorizontal).toBe(undefined);
});
});
});
2 changes: 2 additions & 0 deletions src/components/floatingButton/floatingButton.api.json
Expand Up @@ -16,6 +16,8 @@
"type": "number",
"description": "The bottom margin of the button, or secondary button if passed"
},
{"name": "fullWidth", "type": "boolean", "description": "Whether the buttons get the container's full with", "note": "Relevant to vertical layout only"},
{"name": "buttonLayout", "type": "FloatingButtonLayouts", "description": "Button layout direction: vertical or horizontal"},
{"name": "duration", "type": "number", "description": "The duration of the button's animations (show/hide)"},
{"name": "withoutAnimation", "type": "boolean", "description": "Whether to show/hide the button without animation"},
{"name": "hideBackgroundOverlay", "type": "boolean", "description": "Whether to show background overlay"},
Expand Down
47 changes: 21 additions & 26 deletions src/components/floatingButton/index.tsx
Expand Up @@ -3,8 +3,8 @@ import {StyleSheet, Animated} from 'react-native';
import {Constants, asBaseComponent} from '../../commons/new';
import {Colors, Spacings} from '../../style';
import View from '../view';
import Button, {ButtonProps} from '../button';
import Image from '../image';
import Button, {ButtonProps} from '../button';

export enum FloatingButtonLayouts {
VERTICAL = 'Vertical',
Expand All @@ -27,6 +27,14 @@ export interface FloatingButtonProps {
* The bottom margin of the button, or secondary button if passed
*/
bottomMargin?: number;
/**
* Whether the buttons get the container's full with (vertical layout only)
*/
fullWidth?: boolean;
/**
* Button layout direction: vertical or horizontal
*/
buttonLayout?: FloatingButtonLayouts | `${FloatingButtonLayouts}`;
/**
* The duration of the button's animations (show/hide)
*/
Expand All @@ -46,10 +54,6 @@ export interface FloatingButtonProps {
* <TestID>.secondaryButton - the floatingButton secondaryButton
*/
testID?: string;
/**
* Button layout direction: vertical or horizontal
*/
buttonLayout?: FloatingButtonLayouts | `${FloatingButtonLayouts}`;
}

const gradientImage = () => require('./gradient.png');
Expand Down Expand Up @@ -156,35 +160,26 @@ class FloatingButton extends PureComponent<FloatingButtonProps> {
const {secondaryButton, bottomMargin, testID, buttonLayout} = this.props;

const bgColor = secondaryButton?.backgroundColor || Colors.$backgroundDefault;

if (buttonLayout === FloatingButtonLayouts.HORIZONTAL) {
return (
<Button
outline
flex
size={Button.sizes.large}
testID={`${testID}.secondaryButton`}
{...secondaryButton}
style={[styles.shadow, styles.secondaryMargin, {backgroundColor: bgColor}]}
enableShadow={false}
/>
);
}

const isHorizontal = buttonLayout === FloatingButtonLayouts.HORIZONTAL;
const buttonStyle = isHorizontal ?
[styles.shadow, styles.secondaryMargin, {backgroundColor: bgColor}] : {marginBottom: bottomMargin || Spacings.s7};

return (
<Button
link
outline={isHorizontal}
flex={isHorizontal}
link={!isHorizontal}
size={Button.sizes.large}
testID={`${testID}.secondaryButton`}
{...secondaryButton}
style={{marginBottom: bottomMargin || Spacings.s7}}
style={buttonStyle}
enableShadow={false}
/>
);
}

render() {
const {withoutAnimation, visible, testID} = this.props;
const {withoutAnimation, visible, fullWidth, testID} = this.props;
// NOTE: keep this.firstLoad as true as long as the visibility changed to true
this.firstLoad && !visible ? (this.firstLoad = true) : (this.firstLoad = false);

Expand All @@ -198,8 +193,9 @@ class FloatingButton extends PureComponent<FloatingButtonProps> {

return (
<View
row={!!this.isSecondaryHorizontal}
center={!!this.isSecondaryHorizontal}
row={this.isSecondaryHorizontal}
center={this.isSecondaryHorizontal || !fullWidth}
paddingH-16={!this.isSecondaryHorizontal && fullWidth}
pointerEvents="box-none"
animated
style={[styles.container, this.getAnimatedStyle()]}
Expand All @@ -218,7 +214,6 @@ const styles = StyleSheet.create({
container: {
...StyleSheet.absoluteFillObject,
top: undefined,
alignItems: 'center',
zIndex: Constants.isAndroid ? 99 : undefined
},
image: {
Expand Down
2 changes: 1 addition & 1 deletion src/components/picker/index.tsx
Expand Up @@ -273,7 +273,7 @@ const Picker = React.forwardRef((props: PickerProps, ref) => {
);
} else if (fieldType === PickerFieldTypes.settings) {
return (
<View flex row spread>
<View flexG row spread>
<Text text70 style={labelStyle}>
{others.label}
</Text>
Expand Down
8 changes: 6 additions & 2 deletions src/uilib-test-renderer/helper.ts
@@ -1,15 +1,19 @@
import {StyleSheet} from 'react-native';

interface Props {
style: any;
style?: any;
}

interface Component {
props: Props;
}

const findStyle = <T>(key: string, component: Component): T => {
return StyleSheet.flatten(component.props.style)[key];
const style = component?.props?.style;
if (style) {
return StyleSheet.flatten(style)[key];
}
return style;
};

export {findStyle};

0 comments on commit 2c74549

Please sign in to comment.