Skip to content

Commit

Permalink
feat: extend PickerBase component functionality (#11851)
Browse files Browse the repository at this point in the history
## **Description**

This PR enhances the PickerBase component to provide more flexibility
and control to developers, particularly in relation to the dropdown icon
styling and positioning. These changes are necessary to accommodate the
[Header
Update](https://www.figma.com/design/aMYisczaJyEsYl1TYdcPUL/Portfolio-View?m=auto&node-id=5019-59596&t=PAdxL1bg2Mk08dSk-1)
design requirements.

### Key Changes:

1. **Dropdown Icon Size Control**: Developers can now specify the size
of the dropdown icon, with a fallback to ensure consistent behavior.

2. **Dropdown Icon Spacing**: Added the ability to control the spacing
between the dropdown icon and other elements within the component.

3. **Backward Compatibility**: These changes are designed to be
non-breaking, maintaining compatibility with existing implementations.

### Benefits:

- Increased flexibility for custom designs
- Better alignment with the new Header Update requirements
- Improved developer control over component styling

### Impact:

## **Related issues**

Related:
[#11763](#11763)

## **Manual testing steps**

1. View any instance of `PickerBase` being used. For example the
`NetworkPicker` in header in the home screen

## **Screenshots/Recordings**

| Before  | After  |
|:---:|:---:|
|<img width="459" alt="original"
src="https://github.com/user-attachments/assets/bfee7ac9-0fd2-43e3-876a-7f2dafc5901e">|<img
width="458" alt="updated"
src="https://github.com/user-attachments/assets/278cf902-34a5-4aca-a848-02cba745aa9a">|

### **Before**

NA

### **After**

NA

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [x] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [x] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.
  • Loading branch information
vinnyhoward authored Oct 24, 2024
1 parent a5e5110 commit 6b2cf82
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 21 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* eslint-disable react/display-name */
/* eslint-disable react-native/no-inline-styles */
// External dependencies.
import React from 'react';
import { View, Text } from 'react-native';

// Internal dependencies.
import PickerBase from './PickerBase';
import { IconSize } from '../../Icons/Icon';

const PickerBaseMeta = {
title: 'Component Library / Pickers',
component: PickerBase,
argTypes: {
children: {
control: { type: 'text' },
defaultValue: 'Select an option',
},
iconSize: {
options: Object.values(IconSize),
control: { type: 'select' },
defaultValue: IconSize.Md,
},
},
};

export default PickerBaseMeta;

export const Default = {
render: ({
children,
iconSize,
}: {
children: string;
iconSize: IconSize;
}) => (
<View style={{ alignItems: 'flex-start' }}>
<PickerBase onPress={() => null} iconSize={iconSize}>
<Text>{children}</Text>
</PickerBase>
</View>
),
};

export const WithCustomStyles = {
render: () => (
<View style={{ alignItems: 'flex-start' }}>
<PickerBase
onPress={() => null}
style={{ width: 200 }}
dropdownIconStyle={{ marginLeft: 20 }}
>
<Text>Custom Styled Picker</Text>
</PickerBase>
</View>
),
};
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ const styleSheet = (params: {
}) => {
const { vars, theme } = params;
const { colors } = theme;
const { style } = vars;
const { style, dropdownIconStyle } = vars;

return StyleSheet.create({
base: Object.assign(
{
Expand All @@ -35,9 +36,12 @@ const styleSheet = (params: {
} as ViewStyle,
style,
) as ViewStyle,
dropdownIcon: {
marginLeft: 16,
},
dropdownIcon: Object.assign(
{
marginLeft: 16,
} as ViewStyle,
dropdownIconStyle,
),
});
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,78 @@
// Third party dependencies.
import React from 'react';
import { View } from 'react-native';
import { render } from '@testing-library/react-native';
import { Text } from 'react-native';
import { render, fireEvent } from '@testing-library/react-native';

// Internal dependencies.
import PickerBase from './PickerBase';
import { IconName, IconSize } from '../../Icons/Icon';

describe('PickerBase', () => {
it('should render correctly', () => {
const { toJSON } = render(
<PickerBase onPress={jest.fn}>
<View />
<PickerBase onPress={jest.fn()}>
<Text>Test Content</Text>
</PickerBase>,
);
expect(toJSON()).toMatchSnapshot();
});

it('should call onPress when pressed', () => {
const onPressMock = jest.fn();
const { getByText } = render(
<PickerBase onPress={onPressMock}>
<Text>Test Content</Text>
</PickerBase>,
);

fireEvent.press(getByText('Test Content'));
expect(onPressMock).toHaveBeenCalledTimes(1);
});

it('should render children correctly', () => {
const { getByText } = render(
<PickerBase onPress={jest.fn()}>
<Text>Child Component</Text>
</PickerBase>,
);

expect(getByText('Child Component')).toBeTruthy();
});

it('should render dropdown icon', () => {
const { UNSAFE_getByProps } = render(
<PickerBase onPress={jest.fn()}>
<Text>Test Content</Text>
</PickerBase>,
);

const icon = UNSAFE_getByProps({ name: IconName.ArrowDown });
expect(icon).toBeTruthy();
});

it('should apply custom icon size', () => {
const { UNSAFE_getByProps } = render(
<PickerBase onPress={jest.fn()} iconSize={IconSize.Lg}>
<Text>Test Content</Text>
</PickerBase>,
);

const icon = UNSAFE_getByProps({
name: IconName.ArrowDown,
size: IconSize.Lg,
});
expect(icon).toBeTruthy();
});

it('should apply custom dropdown icon style', () => {
const customStyle = { marginLeft: 20 };
const { UNSAFE_getByProps } = render(
<PickerBase onPress={jest.fn()} dropdownIconStyle={customStyle}>
<Text>Test Content</Text>
</PickerBase>,
);

const icon = UNSAFE_getByProps({ name: IconName.ArrowDown });
expect(icon.props.style).toEqual(expect.objectContaining(customStyle));
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,18 @@ import styleSheet from './PickerBase.styles';
const PickerBase: React.ForwardRefRenderFunction<
TouchableOpacity,
PickerBaseProps
> = ({ style, children, ...props }, ref) => {
const { styles, theme } = useStyles(styleSheet, { style });
> = (
{ iconSize = IconSize.Md, style, dropdownIconStyle, children, ...props },
ref,
) => {
const { styles, theme } = useStyles(styleSheet, { style, dropdownIconStyle });
const { colors } = theme;

return (
<TouchableOpacity style={styles.base} {...props} ref={ref}>
{children}
<Icon
size={IconSize.Md}
size={iconSize}
color={colors.icon.default}
name={IconName.ArrowDown}
style={styles.dropdownIcon}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// Third party dependencies.
import { TouchableOpacityProps } from 'react-native';
import { TouchableOpacityProps, ViewStyle } from 'react-native';
import { IconSize } from '../../Icons/Icon';

/**
* PickerBase component props.
Expand All @@ -13,9 +14,20 @@ export interface PickerBaseProps extends TouchableOpacityProps {
* Content to wrap in PickerBase.
*/
children: React.ReactNode;
/**
* Icon size.
*/
iconSize?: IconSize;
/**
* Dropdown icon styles.
*/
dropdownIconStyle?: ViewStyle;
}

/**
* Style sheet input parameters.
*/
export type PickerBaseStyleSheetVars = Pick<PickerBaseProps, 'style'>;
export type PickerBaseStyleSheetVars = Pick<
PickerBaseProps,
'style' | 'dropdownIconStyle'
>;
56 changes: 50 additions & 6 deletions app/component-library/components/Pickers/PickerBase/README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# PickerBase

PickerBase is a **wrapper** component used for providing a dropdown icon next to wrapped content.
PickerBase is a **wrapper** component used for providing a dropdown icon next to wrapped content. It's designed to be a flexible base for various picker-style components.

## Props

This component extends `TouchableOpacityProps` from React Native's [TouchableOpacityProps](https://reactnative.dev/docs/touchableOpacity) opacity.
This component extends `TouchableOpacityProps` from React Native's [TouchableOpacity](https://reactnative.dev/docs/touchableopacity).

### `onPress`

Expand All @@ -22,11 +22,55 @@ Content to wrap in PickerBase.
| :-------------------------------------------------- | :------------------------------------------------------ |
| ReactNode | Yes |

### `iconSize`

Size of the dropdown icon.

| <span style="color:gray;font-size:14px">TYPE</span> | <span style="color:gray;font-size:14px">REQUIRED</span> | <span style="color:gray;font-size:14px">DEFAULT</span> |
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| IconSize | No | IconSize.Md |

### `dropdownIconStyle`

Custom styles for the dropdown icon.

| <span style="color:gray;font-size:14px">TYPE</span> | <span style="color:gray;font-size:14px">REQUIRED</span> |
| :-------------------------------------------------- | :------------------------------------------------------ |
| ViewStyle | No |

### `style`

Custom styles for the main container.

| <span style="color:gray;font-size:14px">TYPE</span> | <span style="color:gray;font-size:14px">REQUIRED</span> |
| :-------------------------------------------------- | :------------------------------------------------------ |
| ViewStyle | No |

## Usage

```javascript
// Replace import with relative path.
import React from 'react';
import { Text } from 'react-native';
import PickerBase from 'app/component-library/components/Pickers/PickerBase';
import { IconSize } from 'app/component-library/components/Icons/Icon';

const ExampleComponent = () => (
<PickerBase
onPress={() => console.log('Picker pressed')}
iconSize={IconSize.Lg}
dropdownIconStyle={{ marginLeft: 20 }}
style={{ backgroundColor: 'lightgray' }}
>
<Text>Select an option</Text>
</PickerBase>
);

<PickerBase onPress={ONPRESS_CALLBACK}>
<SampleContent />
</PickerBase>;
export default ExampleComponent;
```

## Notes

- The component uses a `TouchableOpacity` as its base, providing press feedback.
- It automatically includes a dropdown icon (ArrowDown) to the right of the content.
- The component is designed to be flexible and can be customized using the `style` and `dropdownIconStyle` props.
- The dropdown icon color is determined by the theme's `colors.icon.default`.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

exports[`PickerBase should render correctly 1`] = `
<TouchableOpacity
onPress={[Function]}
onPress={[MockFunction]}
style={
{
"alignItems": "center",
Expand All @@ -15,7 +15,9 @@ exports[`PickerBase should render correctly 1`] = `
}
}
>
<View />
<Text>
Test Content
</Text>
<SvgMock
color="#141618"
height={20}
Expand Down

0 comments on commit 6b2cf82

Please sign in to comment.