Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JUIP-148 Crear componente Button #35

Merged
merged 18 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .ondevice/storybook.requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ try {
const getStories = () => {
return {
'./storybook/stories/Avatar/Avatar.stories.js': require('../storybook/stories/Avatar/Avatar.stories.js'),
'./storybook/stories/BaseButton/BaseButton.stories.js': require('../storybook/stories/BaseButton/BaseButton.stories.js'),
'./storybook/stories/Button/Button.stories.js': require('../storybook/stories/Button/Button.stories.js'),
'./storybook/stories/Carousel/Carousel.stories.js': require('../storybook/stories/Carousel/Carousel.stories.js'),
'./storybook/stories/CheckBox/CheckBox.stories.js': require('../storybook/stories/CheckBox/CheckBox.stories.js'),
'./storybook/stories/DesignStystem/Colors.stories.js': require('../storybook/stories/DesignStystem/Colors.stories.js'),
Expand Down
2 changes: 1 addition & 1 deletion env.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"LOAD_STORYBOOK": true, "WEB_MODE": false}
{"LOAD_STORYBOOK": false, "WEB_MODE": false}
70 changes: 11 additions & 59 deletions src/components/BaseButton/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,15 @@
import React from 'react';
import {create} from 'react-test-renderer';
import {Text, View} from 'react-native';
import {palette} from '../../theme/palette';
import {View, Text} from 'react-native';
import BaseButton from './';

const validData = {
title: 'test title',
icon: 'plus_circle',
iconRight: true,
disabled: true,
borderRadius: 15,
pressedColor: palette.success.main,
style: {backgroundColor: palette.black.main},
iconStyle: {backgroundColor: palette.black.main},
textStyle: {color: palette.black.main},
children: (
<View>
<Text>Button</Text>
</View>
),
};

describe('BaseButton Component', () => {
Expand All @@ -24,56 +20,12 @@ describe('BaseButton Component', () => {
});
});

describe('render correctly when has minimum props needed, example', () => {
it('title', () => {
const {toJSON} = create(<BaseButton title={validData.title} />);
expect(toJSON()).toBeTruthy();
});

it('icon', () => {
const {toJSON} = create(<BaseButton icon={validData.icon} />);
expect(toJSON()).toBeTruthy();
});

it('children', () => {
const {toJSON} = create(
<BaseButton>
<View>
<Text>Valid Children</Text>
</View>
</BaseButton>
);
expect(toJSON()).toBeTruthy();
});
});

describe('Icon', () => {
it('is renders right icon when iconRight prop is true', () => {
const {toJSON} = create(
<BaseButton title={validData.title} icon={validData.icon} iconRight={validData.iconRight} />
);
expect(toJSON()).toBeTruthy();
});

it('is renders left icon when iconRight prop is not passed', () => {
const {toJSON} = create(<BaseButton title={validData.title} icon={validData.icon} />);
expect(toJSON()).toBeTruthy();
});
});

describe('pressedColor', () => {
it('it changes when pressedColor props is an string color format', () => {
const {toJSON} = create(
<BaseButton title={validData.title} pressedColor={validData.pressedColor} />
describe('render correctly when has minimum props needed', () => {
it('when hasnt minimum props needed', () => {
const {root} = create(
<BaseButton borderRadius={validData.borderRadius}>{validData.children}</BaseButton>
);
expect(toJSON()).toBeTruthy();
});
});

describe('is disabled', () => {
it('when disabled props is true', () => {
const {toJSON} = create(<BaseButton title={validData.title} disabled={validData.disabled} />);
expect(toJSON()).toBeTruthy();
expect(root).toBeTruthy();
});
});
});
90 changes: 18 additions & 72 deletions src/components/BaseButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,44 @@
import React, {FC} from 'react';
import {Pressable, PressableProps, ViewStyle, StyleSheet, TextStyle} from 'react-native';
import {moderateScale, horizontalScale, scaledForDevice} from '../../scale';
import {palette} from '../../theme/palette';
import Text from '../Text';
import Icon from '../Icon';
import React, {FC, ReactNode} from 'react';
import {Pressable, PressableProps, StyleSheet} from 'react-native';
import {moderateScale, scaledForDevice} from '../../scale';

interface PressableStyleProp {
pressed: boolean;
}

interface BaseButtonProps extends PressableProps {
title?: string | null;
icon?: string;
iconRight?: boolean;
disabled?: boolean;
export interface BaseButtonProps extends PressableProps {
borderRadius?: number;
pressedColor?: string;
style?: ViewStyle;
iconStyle?: ViewStyle;
textStyle?: TextStyle;
children?: React.ReactNode;
children?: ReactNode | null;
pressedStyle?: ReactNode;
style?: any;
}

const BaseButton: FC<BaseButtonProps> = ({
title = null,
icon = null,
iconRight = false,
disabled = false,
borderRadius = 0,
pressedColor,
style,
iconStyle,
textStyle,
children = null,
style,
pressedStyle,
...props
}) => {
if (!title && !icon && !children) {
if (!children) {
return null;
}

const bgColor = !disabled ? palette.primary.main : palette.grey[200];
const iconPaddingLeft = iconRight && title ? 8 : 0;
const iconPaddingRight = !iconRight && title ? 8 : 0;

const validatePaddingVertical = scaledForDevice(10, moderateScale);
const validatePaddingHorizontal = scaledForDevice(16, horizontalScale);
const validateFontSize = scaledForDevice(14, moderateScale);
const validateBorderRadius = scaledForDevice(borderRadius, moderateScale);
const validatePaddingRightIcon = scaledForDevice(iconPaddingRight, horizontalScale);
const validatePaddingLeftIcon = scaledForDevice(iconPaddingLeft, horizontalScale);

const styles = StyleSheet.create({
container: {
display: 'flex',
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
paddingHorizontal: validatePaddingHorizontal,
paddingVertical: validatePaddingVertical,
borderRadius: validateBorderRadius,
backgroundColor: bgColor,
},
icon: {
color: palette.base.white,
paddingRight: validatePaddingRightIcon,
paddingLeft: validatePaddingLeftIcon,
},
title: {
fontSize: validateFontSize,
fontWeight: '500',
textAlign: 'center',
color: palette.base.white,
},
});

const noChildren = () => (
<>
{icon && !iconRight && <Icon name={icon} style={[styles.icon, iconStyle]} size={24} />}
{title && <Text style={[styles.title, textStyle]}>{title}</Text>}
{icon && iconRight && <Icon name={icon} style={[styles.icon, iconStyle]} size={24} />}
</>
);

/* istanbul ignore next */
const PressableStyle = ({pressed}: PressableStyleProp) => {
const backgroundColor = pressedColor ?? palette.primary.dark;
const pressedBgColor = pressed ? [{backgroundColor}] : [];

return [styles.container, style, ...pressedBgColor];
};

return (
<Pressable style={PressableStyle} disabled={disabled} {...props}>
{children ?? noChildren}
<Pressable
style={({pressed}) => [
styles.container,
style,
pressed && /* istanbul ignore next */ pressedStyle,
]}
{...props}>
{children}
</Pressable>
);
};
Expand Down
43 changes: 43 additions & 0 deletions src/components/Button/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from 'react';
import {create} from 'react-test-renderer';
import {Text} from 'react-native';
import Icon from '../Icon';
import Loading from '../Loading';
import Button from './';

const validData = {
isLoading: true,
value: 'Button Test',
icon: 'box',
};

jest.spyOn(React, 'useEffect').mockImplementation((f) => f());
jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper');

describe('Button component', () => {
describe('it renders correctly', () => {
it('when it has valie as minimum prop', () => {
const {root} = create(<Button value={validData.value} />);
const TitleComp = root.findByType(Text);
const {children} = TitleComp.props;

expect(children).toEqual(validData.value);
});

it('when it has icon as minimum prop', () => {
const {root} = create(<Button icon={validData.icon} />);
const IconComp = root.findByType(Icon);
const {name} = IconComp.props;

expect(name).toEqual(validData.icon);
});

it('when isLoading is true, show an loading spinner', () => {
const {root} = create(<Button icon={validData.icon} isLoading={validData.isLoading} />);
const LoadingComp = root.findByType(Loading);
const {isLoading} = LoadingComp.props;

expect(isLoading).toEqual(validData.isLoading);
});
});
});
113 changes: 113 additions & 0 deletions src/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React, {FC} from 'react';
import {ViewStyle, StyleSheet, TextStyle, View} from 'react-native';
import BaseButton, {BaseButtonProps} from '../BaseButton';
import getButtonStyles from './utils/getButtonStyles';
import Loading from '../Loading';
import Text from '../Text';
import Icon from '../Icon';

export const types = {
main: 'main',
secondary: 'secondary',
};

export const variant = {
contained: 'contained',
outlined: 'outlined',
text: 'text',
};

export const color = {
primary: 'primary',
black: 'black',
success: 'success',
error: 'error',
warning: 'warning',
alert: 'alert',
};

export const iconPosition = {
top: 'top',
bottom: 'bottom',
left: 'left',
right: 'right',
};

export type buttonType = typeof types;
export type keyType = keyof buttonType;

export type buttonVariant = typeof variant;
export type keyVariant = keyof buttonVariant;

export type buttonColor = typeof color;
export type keyColor = keyof buttonColor;

export type buttonIconPosition = typeof iconPosition;
export type keyIconPosition = keyof buttonIconPosition;

interface ButtonProps extends BaseButtonProps {
type?: keyType;
variant?: keyVariant;
color?: keyColor;
isLoading?: boolean;
value?: string | null;
icon?: string;
iconPosition?: keyIconPosition;
disabled?: boolean;
style?: ViewStyle;
pressedStyle?: ViewStyle;
iconStyle?: TextStyle;
textStyle?: TextStyle;
}

const Button: FC<ButtonProps> = ({
type = 'main',
variant = 'contained',
color = 'primary',
iconPosition = 'left',
isLoading = false,
value = 'Button',
icon = null,
disabled = false,
style,
iconStyle,
textStyle,
...props
}) => {
const validDisabled = disabled || isLoading;
const hasIconAndText = !!icon && !!value;
const borderRadius = variant === 'text' ? 6 : 50;

const buttonStyles = getButtonStyles({
type,
variant,
color,
iconPosition,
isLoading,
isDisabled: disabled,
hasIconAndText,
});
const styles = StyleSheet.create(buttonStyles);

const LoadingCompontent = <Loading isLoading={isLoading} color={styles.loadingColor} size={24} />;

const WrapperComponent = (
<View style={styles.direction}>
{icon && <Icon name={icon} style={[styles.icon, iconStyle]} size={24} />}
{value && <Text style={[styles.text, textStyle]}>{value}</Text>}
</View>
);

return (
<BaseButton
style={[styles.container, style]}
pressedStyle={!validDisabled && styles.pressed}
borderRadius={borderRadius}
disabled={validDisabled}
{...props}>
{isLoading ? LoadingCompontent : WrapperComponent}
</BaseButton>
);
};

export default Button;
Loading
Loading