@atom_design/menu
v1.0.0
Published
A React Native dropdown menu component with search, radio, checkbox, and chips support. Part of the Atom Design System.
Readme
@atom_design/menu
A React Native dropdown menu component with search, radio, checkbox, and chips support. Part of the Atom Design System.
Features
- 🔍 Searchable - Built-in search functionality
- 🎯 Radio Select - Single selection with radio buttons
- ☑️ Checkbox - Multi-select with "Select All" option
- 🏷️ Chips - Visual chip tags for selections
- 🔢 Input Mode - Two input fields with footer actions
- 🎨 Customizable - Style all elements with custom props
- ♿ Accessible - Full screen reader support
- 💪 TypeScript - Full type definitions included
📦 Installation
npm install @atom_design/menu
# or
yarn add @atom_design/menuPeer Dependencies
npm install prop-types react-native-vector-icons🚀 Basic Usage
import React, { useState } from 'react';
import { View } from 'react-native';
import Menu from '@atom_design/menu';
const App = () => {
const [selected, setSelected] = useState('');
const options = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Cherry', value: 'cherry' },
];
return (
<View style={{ flex: 1, padding: 20 }}>
<Menu
label="Select Fruit"
type="searchRadio"
options={options}
onSelect={setSelected}
/>
</View>
);
};
export default App;🧩 Menu Types
Search + Radio (Single Select)
<Menu
label="Country"
type="searchRadio"
options={countries}
onSelect={(value) => setCountry(value)}
/>Search + Checkbox (Multi-Select)
<Menu
label="Skills"
type="searchCheckbox"
options={skills}
showCheckAll={true}
checkAllLabel="Select All Skills"
onSelect={(values) => setSelectedSkills(values)}
/>Search + Chips Checkbox
<Menu
label="Tags"
type="searchChipsCheckbox"
options={tagOptions}
chips={selectedChips}
onSelect={(values) => setSelectedValues(values)}
onAddChip={(chip) => setChips([...chips, chip])}
onRemoveChip={(chip) => setChips(chips.filter(c => c !== chip))}
/>Input with Footer
<Menu
label="Price Range"
type="inputFooter"
inputLabels={['Min Price', 'Max Price']}
inputPlaceholders={['₹0', '₹10,000']}
applyButtonText="Apply Filter"
cancelButtonText="Reset"
onApply={(data) => console.log(data.input1, data.input2)}
onCancel={() => console.log('Cancelled')}
/>📋 Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| label | string | - | Label above trigger |
| triggerText | string | - | Trigger button text |
| type | string | 'searchRadio' | Menu type |
| options | Option[] | [] | Array of options |
| chips | string[] | [] | Chips for chips mode |
| value | string \| array | - | Controlled value |
| onSelect | function | - | Selection callback |
| onAddChip | function | - | Add chip callback |
| onRemoveChip | function | - | Remove chip callback |
| onApply | function | - | Apply callback (inputFooter) |
| onCancel | function | - | Cancel callback |
| placeholder | string | 'Search...' | Search placeholder |
| disabled | boolean | false | Disable menu |
| searchable | boolean | true | Show search bar |
| showCheckAll | boolean | true | Show "Select All" |
| checkAllLabel | string | 'Select All' | "Select All" text |
| applyButtonText | string | 'Apply' | Apply button text |
| cancelButtonText | string | 'Cancel' | Cancel button text |
| inputLabels | [string, string] | ['Min Value', 'Max Value'] | Input labels |
| inputPlaceholders | [string, string] | ['Enter min', 'Enter max'] | Input placeholders |
| containerStyle | ViewStyle | - | Container style |
| triggerStyle | ViewStyle | - | Trigger style |
| menuStyle | ViewStyle | - | Menu popup style |
| testID | string | - | Test ID |
📁 Option Structure
interface MenuOption {
label: string; // Display text
value: string | number; // Value returned on selection
}🧪 Test Screen Example
import React, { useState } from 'react';
import { SafeAreaView, ScrollView, Text, StyleSheet, View } from 'react-native';
import Menu from '@atom_design/menu';
const options = [
{ label: 'Apple', value: 'apple' },
{ label: 'Banana', value: 'banana' },
{ label: 'Cherry', value: 'cherry' },
{ label: 'Orange', value: 'orange' },
{ label: 'Mango', value: 'mango' },
];
const MenuTestScreen = () => {
const [radioValue, setRadioValue] = useState('');
const [checkboxValues, setCheckboxValues] = useState([]);
const [chips, setChips] = useState(['Apple', 'Banana']);
const [chipsValues, setChipsValues] = useState(['apple', 'banana']);
const [inputResult, setInputResult] = useState(null);
return (
<SafeAreaView style={styles.container}>
<ScrollView contentContainerStyle={styles.content}>
<Text style={styles.header}>@atom_design/menu</Text>
{/* Radio Select */}
<Menu
label="Single Select (Radio)"
triggerText={radioValue || 'Select a fruit'}
type="searchRadio"
options={options}
onSelect={setRadioValue}
/>
{/* Checkbox Multi-Select */}
<View style={styles.spacing} />
<Menu
label="Multi Select (Checkbox)"
triggerText={
checkboxValues.length > 0
? `${checkboxValues.length} selected`
: 'Select fruits'
}
type="searchCheckbox"
options={options}
showCheckAll={true}
onSelect={setCheckboxValues}
/>
{/* Chips Checkbox */}
<View style={styles.spacing} />
<Menu
label="Chips Select"
triggerText="Select with chips"
type="searchChipsCheckbox"
options={options}
chips={chips}
value={chipsValues}
onSelect={setChipsValues}
onAddChip={(chip) => {
if (!chips.includes(chip)) {
setChips([...chips, chip]);
}
}}
onRemoveChip={(chip) => setChips(chips.filter(c => c !== chip))}
/>
{/* Input Footer */}
<View style={styles.spacing} />
<Menu
label="Quantity Range"
triggerText={
inputResult
? `${inputResult.input1} - ${inputResult.input2}`
: 'Set range'
}
type="inputFooter"
inputLabels={['Minimum', 'Maximum']}
inputPlaceholders={['0', '100']}
applyButtonText="Apply"
cancelButtonText="Cancel"
onApply={setInputResult}
onCancel={() => setInputResult(null)}
/>
{/* Disabled */}
<View style={styles.spacing} />
<Menu
label="Disabled Menu"
triggerText="Cannot open"
type="searchRadio"
options={options}
disabled
/>
{/* Results */}
<View style={styles.results}>
<Text style={styles.resultsTitle}>Current Values:</Text>
<Text>Radio: {radioValue || 'None'}</Text>
<Text>Checkbox: {checkboxValues.join(', ') || 'None'}</Text>
<Text>Chips: {chips.join(', ') || 'None'}</Text>
<Text>
Range: {inputResult ? `${inputResult.input1} - ${inputResult.input2}` : 'None'}
</Text>
</View>
</ScrollView>
</SafeAreaView>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f5f5f5',
},
content: {
padding: 20,
},
header: {
fontSize: 24,
fontWeight: 'bold',
textAlign: 'center',
marginBottom: 24,
color: '#333',
},
spacing: {
height: 16,
},
results: {
marginTop: 32,
padding: 16,
backgroundColor: '#fff',
borderRadius: 8,
borderWidth: 1,
borderColor: '#eee',
},
resultsTitle: {
fontSize: 16,
fontWeight: '600',
marginBottom: 12,
color: '#333',
},
});
export default MenuTestScreen;📝 TypeScript
Full TypeScript support included:
import Menu, { MenuOption, MenuProps } from '@atom_design/menu';
const options: MenuOption[] = [
{ label: 'Option 1', value: 'opt1' },
{ label: 'Option 2', value: 'opt2' },
];
<Menu
type="searchCheckbox"
options={options}
onSelect={(values) => console.log(values as string[])}
/>♿ Accessibility
The component includes full accessibility support:
- Proper
accessibilityRoleon all interactive elements accessibilityStatefor checked/expanded states- Descriptive labels for screen readers
- Keyboard navigation support
👤 Author
Atom Design Team
📄 License
MIT © Atom Design
