Skip to content

Commit

Permalink
feat: Created SelectOption (#7969)
Browse files Browse the repository at this point in the history
## **Description**
- Added SelectOption

## **Related issues**

## **Manual testing steps**

1. Change pointer to storybook
2. Navigate to SelectOption

## **Screenshots/Recordings**

https://github.com/MetaMask/metamask-mobile/assets/14355083/dd0c9166-1f59-4e64-8c01-f533ad01165f

## **Pre-merge author checklist**

- [x] I’ve followed [MetaMask Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've clearly explained what problem this PR is solving and how it
is solved.
- [x] I've linked related issues
- [x] I've included manual testing steps
- [x] I've included screenshots/recordings if applicable
- [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.
- [x] I’ve properly set the pull request status:
  - [x] In case it's not yet "ready for review", I've set it to "draft".
- [x] In case it's "ready for review", I've changed it from "draft" to
"non-draft".

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] 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.

---------

Co-authored-by: George Marshall <george.marshall@consensys.net>
  • Loading branch information
brianacnguyen and georgewrmarshall committed Dec 19, 2023
1 parent 968016f commit 2c0cdb6
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 0 deletions.
1 change: 1 addition & 0 deletions .storybook/storybook.requires.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ const getStories = () => {
"./app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx": require("../app/component-library/components/Pickers/PickerAccount/PickerAccount.stories.tsx"),
"./app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx": require("../app/component-library/components/Pickers/PickerNetwork/PickerNetwork.stories.tsx"),
"./app/component-library/components/RadioButton/RadioButton.stories.tsx": require("../app/component-library/components/RadioButton/RadioButton.stories.tsx"),
"./app/component-library/components/Select/SelectOption/SelectOption.stories.tsx": require("../app/component-library/components/Select/SelectOption/SelectOption.stories.tsx"),
"./app/component-library/components/Select/SelectButton/SelectButton.stories.tsx": require("../app/component-library/components/Select/SelectButton/SelectButton.stories.tsx"),
"./app/component-library/components/Select/SelectValue/SelectValue.stories.tsx": require("../app/component-library/components/Select/SelectValue/SelectValue.stories.tsx"),
"./app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx": require("../app/component-library/components/Sheet/SheetBottom/SheetBottom.stories.tsx"),
Expand Down
144 changes: 144 additions & 0 deletions app/component-library/components/Select/SelectOption/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# SelectOption

`SelectOption` is a selectable option used in the SelectMenu and is part of the Select component

## Props

This component extends [SelectValueProps](../SelectValue/SelectValue.types.ts) and [ListItemSelectProps](../../List/ListItemSelect/ListItemSelect.types.ts) from the [SelectValue](../SelectValue/SelectValue.tsx) and [ListItemSelect](../../List/ListItemSelect/ListItemSelect.tsx) components.

### `iconEl`

Optional prop to replace the start Icon Element

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

### `iconProps`

Optional prop for the start Icon Element

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

### `label`

Optional prop for the label of SelectOption

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

### `description`

Optional description below the label

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

### `isSelected`

Optional prop to determine if the item is selected

| <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> |
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| boolean | No | false |

### `isDisabled`

Optional prop to determine if the item is disabled

| <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> |
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| boolean | No | false |

## Custom Props

### `startAccessory`

Optional content to be displayed before the info section

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

### `children`

Optional content to be displayed in the info section

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

### `endAccessory`

Optional content to be displayed after the info section

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

### `gap`

Optional prop to configure the gap between items inside the ListItem

| <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> |
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| number or string | No | 16 |

### `verticalAlignment`

Optional prop to configure the vertical alignment between items inside the ListItem

| <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> |
| :-------------------------------------------------- | :------------------------------------------------------ | :----------------------------------------------------- |
| VerticalAlignment | No | VerticalAlignment.Top |

## Usage

```javascript
// Common use case
<SelectOption
iconProps={SAMPLE_ICON_PROPS}
label={SAMPLE_LABEL_TEXT}
description={SAMPLE_DESCRIPTION_TEXT}
isSelected={false}
isDisabled={false}
/>

// Custom starting icon
<SelectOption
label={SAMPLE_LABEL_TEXT}
description={SAMPLE_DESCRIPTION_TEXT}
iconEl={SAMPLE_ICON_EL}
isSelected={false}
isDisabled={false}
/>

// Custom starting accessory
<SelectOption
label={SAMPLE_LABEL_TEXT}
description={SAMPLE_DESCRIPTION_TEXT}
startAccessory={SAMPLE_STARTACCESSORY_EL}
isSelected={false}
isDisabled={false}
/>

// Custom ending accessory
<SelectOption
label={SAMPLE_LABEL_TEXT}
description={SAMPLE_DESCRIPTION_TEXT}
endAccessory={SAMPLE_ENDACCESSORY_EL}
isSelected={false}
isDisabled={false}
/>

// Custom info section
<SelectOption
isSelected={false}
isDisabled={false}>
{SAMPLE_CHILDREN}
</SelectOption>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable import/prefer-default-export */
// External dependencies.
import { SAMPLE_AVATAR_PROPS } from '../../Avatars/Avatar/Avatar.constants';

// Internal dependencies.
import { SelectOptionProps } from './SelectOption.types';

// Sample consts
export const SAMPLE_SELECTOPTION_PROPS: SelectOptionProps = {
iconProps: SAMPLE_AVATAR_PROPS,
label: 'Sample SelectOption label',
description: 'Sample SelectOption description',
isSelected: false,
isDisabled: false,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable react/display-name */
// Third party dependencies.
import React from 'react';

// Internal dependencies.
import { default as SelectOptionComponent } from './SelectOption';
import { SAMPLE_SELECTOPTION_PROPS } from './SelectOption.constants';

const SelectOptionStoryMeta = {
title: 'Component Library / Select',
component: SelectOptionComponent,
argTypes: {
label: {
control: { type: 'text' },
},
description: {
control: { type: 'text' },
},
isSelected: {
control: { type: 'boolean' },
},
isDisabled: {
control: { type: 'boolean' },
},
},
};

export default SelectOptionStoryMeta;

export const SelectOption = {
args: {
label: SAMPLE_SELECTOPTION_PROPS.label,
description: SAMPLE_SELECTOPTION_PROPS.description,
isSelected: SAMPLE_SELECTOPTION_PROPS.isSelected,
isDisabled: SAMPLE_SELECTOPTION_PROPS.isDisabled,
},
render: (args: any) => (
<SelectOptionComponent
{...args}
iconProps={SAMPLE_SELECTOPTION_PROPS.iconProps}
/>
),
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Third party dependencies.
import { StyleSheet, ViewStyle } from 'react-native';

// Internal dependencies.
import { SelectOptionStyleSheetVars } from './SelectOption.types';

/**
* Style sheet function for SelectOption component.
*
* @param params Style sheet params.
* @param params.theme App theme from ThemeContext.
* @param params.vars Inputs that the style sheet depends on.
* @returns StyleSheet object.
*/
const styleSheet = (params: { vars: SelectOptionStyleSheetVars }) => {
const { vars } = params;
const { style } = vars;
return StyleSheet.create({
base: Object.assign({} as ViewStyle, style) as ViewStyle,
});
};

export default styleSheet;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Third party dependencies.
import React from 'react';
import { render } from '@testing-library/react-native';

// Internal dependencies.
import SelectOption from './SelectOption';
import { SAMPLE_SELECTOPTION_PROPS } from './SelectOption.constants';

describe('SelectOption', () => {
it('should render snapshot correctly', () => {
const wrapper = render(<SelectOption {...SAMPLE_SELECTOPTION_PROPS} />);
expect(wrapper).toMatchSnapshot();
});

it('should not render the selected view if isSelected is false', () => {
const { queryByRole } = render(
<SelectOption {...SAMPLE_SELECTOPTION_PROPS} />,
);
expect(queryByRole('checkbox')).toBeNull();
});

it('should render the selected view if isSelected is true', () => {
const { queryByRole } = render(
<SelectOption {...SAMPLE_SELECTOPTION_PROPS} isSelected />,
);
expect(queryByRole('checkbox')).not.toBeNull();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* eslint-disable react/prop-types */

// Third party dependencies.
import React from 'react';

// External dependencies.
import { useStyles } from '../../../hooks';
import ListItemSelect from '../../List/ListItemSelect/ListItemSelect';
import SelectValue from '../SelectValue/SelectValue';

// Internal dependencies.
import styleSheet from './SelectOption.styles';
import { SelectOptionProps } from './SelectOption.types';

const SelectOption: React.FC<SelectOptionProps> = ({
style,
isSelected,
isDisabled,
gap = 12,
verticalAlignment,
...props
}) => {
const { styles } = useStyles(styleSheet, {
style,
});

return (
<ListItemSelect
style={styles.base}
gap={gap}
verticalAlignment={verticalAlignment}
isSelected={isSelected}
isDisabled={isDisabled}
accessibilityRole="menuitem"
>
<SelectValue {...props} />
</ListItemSelect>
);
};

export default SelectOption;
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// External dependencies.
import { ListItemSelectProps } from '../../List/ListItemSelect/ListItemSelect.types';
import { SelectValueProps } from '../SelectValue/SelectValue.types';

/**
* SelectOption component props.
*/
export interface SelectOptionProps
extends ListItemSelectProps,
Omit<SelectValueProps, 'style'> {}

/**
* Style sheet input parameters.
*/
export type SelectOptionStyleSheetVars = Pick<SelectOptionProps, 'style'>;
Loading

0 comments on commit 2c0cdb6

Please sign in to comment.