Specialized Select Components
Specialized select components provide domain-specific dropdown functionality with custom options, styling, and behavior. These components are tailored for specific use cases like time attendance, frequency selection, redemption types, and multi-selection scenarios.
Overview
This document covers specialized select components that provide focused functionality for specific business domains and use cases within the WorkPayCore Frontend application.
Components Overview
Time Attendance Selects
- TAScheduleFrequencySelect - Schedule frequency selection with descriptions
- WeekDaySelect - Day of the week selection
Generic Selects
- CustomSelect - Highly customizable dropdown with variants
- MultiSelect - Advanced multi-selection with custom styling
- RedeemTypeSelect - Redemption type selection
Payroll Selects
- DailyReportSelect - Daily report type selection
- ReportTimeSelect - Report time period selection
- WpOvertimeFactorsSelect - Overtime factor selection
- AttendanceReportTypeSelect - Attendance report type selection
Form Selects
- FormShiftsSelect - Shift selection in forms
- WpProjectsFormMultiSelect - Project multi-selection
- ClockingTypeMultiSelect - Clocking type multi-selection
- WalletTransferCategorySelect - Wallet transfer category selection
TAScheduleFrequencySelect
Time attendance schedule frequency selector with detailed descriptions for each frequency option.
Component Location
import TAScheduleFrequencySelect from 'components/Selects/TAScheduleFrequencySelect';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| control | any | ✓ | - | React Hook Form control |
| isDisabled | boolean | ✓ | - | Disabled state |
| name | string | ✓ | - | Form field name |
| rules | RegisterOptions | - | - | Form validation rules |
| scheduleType | string | - | 'shift' | Schedule type ('shift', 'off_day', 'rest_day') |
TypeScript Interface
interface TAScheduleFrequencySelectProps {
control: any;
isDisabled: boolean;
name: string;
rules?: RegisterOptions;
formHelperText?: string;
scheduleType: 'shift' | 'off_day' | 'rest_day';
}
Frequency Options
The component provides three frequency options:
- Recurring Days: Regular schedule repetition
- Single Date: One-time occurrence
- Date Range: Continuous period with start/end dates
Usage Examples
Basic Shift Schedule
import TAScheduleFrequencySelect from 'components/Selects/TAScheduleFrequencySelect';
import { useForm } from 'react-hook-form';
function ShiftScheduleForm() {
const { control } = useForm();
return (
<TAScheduleFrequencySelect
control={control}
name='schedule_frequency'
isDisabled={false}
scheduleType='shift'
/>
);
}
Off Day Schedule
import TAScheduleFrequencySelect from 'components/Selects/TAScheduleFrequencySelect';
import { useForm } from 'react-hook-form';
function OffDayScheduleForm() {
const { control } = useForm();
return (
<TAScheduleFrequencySelect
control={control}
name='off_day_frequency'
isDisabled={false}
scheduleType='off_day'
rules={{ required: 'Off day frequency is required' }}
/>
);
}
Rest Day Schedule
import TAScheduleFrequencySelect from 'components/Selects/TAScheduleFrequencySelect';
import { useForm } from 'react-hook-form';
function RestDayScheduleForm() {
const { control } = useForm();
return (
<TAScheduleFrequencySelect
control={control}
name='rest_day_frequency'
isDisabled={false}
scheduleType='rest_day'
rules={{ required: 'Rest day frequency is required' }}
/>
);
}
Dynamic Labels
The component automatically adjusts labels based on scheduleType:
- Shift: "Shift assignment frequency"
- Off Day: "Off day assignment frequency"
- Rest Day: "Rest day assignment frequency"
Custom Option Component
The component includes a custom option renderer that displays both label and description:
export const Option = props => {
return (
<components.Option {...props}>
<Stack>
<Text
textStyle='body-regular'
color={props.isSelected ? 'white' : 'charcoal'}
>
{props?.data?.label}
</Text>
<Text
textStyle='body-small'
color={props.isSelected ? 'white' : 'slate'}
>
{props?.data?.description}
</Text>
</Stack>
</components.Option>
);
};
CustomSelect
Highly customizable dropdown component with multiple variants and styling options.
Component Location
import CustomSelectDropdown from 'components/Selects/CustomSelect';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| options | Array | ✓ | - | Select options array |
| onChange | function | ✓ | - | Change handler |
| onVariantChange | function | ✓ | - | Variant change handler |
| variant | string | ✓ | - | Select variant ('base' or filter variant) |
| name | string | ✓ | - | Select name/label |
| myValue | string | ✓ | - | Current value |
| queryParamValue | string | ✓ | - | Query parameter value |
| handleClick | function | - | - | Click handler |
| isClearable | boolean | - | true | Show clear button |
| isDisabled | boolean | - | false | Disabled state |
TypeScript Interface
interface CustomSelectDropdownProps {
options: { label: string; value: string | number }[];
onChange: (e: OptionType) => void;
onVariantChange: (e: OptionType) => void;
variant: string;
name: string;
myValue: string;
queryParamValue: string;
handleClick?: () => void;
isClearable?: boolean;
isDisabled?: boolean;
}
Variants
Base Variant
Full-width dropdown with simple styling:
import CustomSelectDropdown from 'components/Selects/CustomSelect';
function BaseSelectExample() {
const options = [
{ label: 'Option 1', value: 'option1' },
{ label: 'Option 2', value: 'option2' },
{ label: 'Option 3', value: 'option3' },
];
return (
<CustomSelectDropdown
options={options}
onChange={value => console.log('Selected:', value)}
onVariantChange={value => console.log('Variant changed:', value)}
variant='base'
name='Base Selection'
myValue='option1'
queryParamValue='option1'
/>
);
}
Filter Variant
Compact filter-style dropdown:
import CustomSelectDropdown from 'components/Selects/CustomSelect';
function FilterSelectExample() {
const statusOptions = [
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
{ label: 'Pending', value: 'pending' },
];
return (
<CustomSelectDropdown
options={statusOptions}
onChange={value => console.log('Filter changed:', value)}
onVariantChange={value => console.log('Variant changed:', value)}
variant='filter'
name='Status'
myValue='active'
queryParamValue='active'
isClearable={true}
handleClick={() => console.log('Clear clicked')}
/>
);
}
Features
- Popover Integration: Uses Chakra UI Popover for dropdown
- Custom Styling: Tailored styles for different variants
- Clearable Options: Optional clear functionality
- Search Support: Built-in search functionality
- Responsive Design: Adapts to different screen sizes
MultiSelect
Advanced multi-selection component with custom styling and "Select All" functionality.
Component Location
import MultiSelect from 'components/Selects/MultiSelect';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| options | Array | ✓ | - | Select options array |
| onChange | function | ✓ | - | Change handler |
| isMulti | boolean | - | false | Enable multi-selection |
| isSearchable | boolean | - | true | Enable search |
| isDisabled | boolean | - | false | Disabled state |
| isLoading | boolean | ✓ | - | Loading state |
| placeholder | string | - | - | Placeholder text |
| defaultValue | Option/Array | - | - | Default selected values |
| allowSelectAll | boolean | - | false | Enable "Select All" option |
| hideSelectedOptions | boolean | - | false | Hide selected options from dropdown |
| closeMenuOnSelect | boolean | - | true | Close menu after selection |
TypeScript Interface
interface Option {
value: string | number;
label: string | number;
}
interface MultiSelectProps {
closeMenuOnSelect?: boolean;
isSearchable?: boolean;
isDisabled?: boolean;
placeholder?: string;
options: Option[];
isMulti?: boolean;
onChange: (selected: Option[] | Option | null) => void;
defaultValue?: Option | Option[];
hideSelectedOptions?: boolean;
allowSelectAll?: boolean;
allOption?: Option;
components?: typeof components;
optionalStyles?: object;
isLoading: boolean;
styles?: object;
isInvalid?: boolean;
}
Usage Examples
Basic Multi-Select
import MultiSelect from 'components/Selects/MultiSelect';
function BasicMultiSelect() {
const options = [
{ value: 'user1', label: 'John Doe' },
{ value: 'user2', label: 'Jane Smith' },
{ value: 'user3', label: 'Bob Johnson' },
];
return (
<MultiSelect
options={options}
onChange={selected => console.log('Selected:', selected)}
isMulti={true}
isLoading={false}
placeholder='Select users...'
/>
);
}
Select All Functionality
import MultiSelect from 'components/Selects/MultiSelect';
function SelectAllExample() {
const departmentOptions = [
{ value: 'hr', label: 'Human Resources' },
{ value: 'it', label: 'Information Technology' },
{ value: 'finance', label: 'Finance' },
{ value: 'marketing', label: 'Marketing' },
];
return (
<MultiSelect
options={departmentOptions}
onChange={selected => console.log('Selected departments:', selected)}
isMulti={true}
isLoading={false}
allowSelectAll={true}
placeholder='Select departments...'
closeMenuOnSelect={false}
/>
);
}
Country Flag Multi-Select
import { CountryFlagMultiSelect } from 'components/Selects/MultiSelect';
function CountryFlagExample() {
const countryOptions = [
{ value: 'KE', label: 'Kenya', flag: '🇰🇪' },
{ value: 'NG', label: 'Nigeria', flag: '🇳🇬' },
{ value: 'UG', label: 'Uganda', flag: '🇺🇬' },
];
return (
<CountryFlagMultiSelect
options={countryOptions}
onChange={selected => console.log('Selected countries:', selected)}
isMulti={true}
isLoading={false}
placeholder='Select countries...'
/>
);
}
Features
- Custom Multi-Value Display: Shows selected count when multiple items are selected
- Search Functionality: Built-in search with custom styling
- Select All Option: Optional "Select All" functionality
- Custom Styling: Tailored appearance with brand colors
- Loading States: Built-in loading indicator support
- Error Handling: Custom "No Options" message with avatar
WeekDaySelect
Day of the week selection component for recurring schedule setup.
Component Location
import WeekDaySelect from 'components/Selects/WeekDaySelect';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| control | any | ✓ | - | React Hook Form control |
| isDisabled | boolean | ✓ | - | Disabled state |
| name | string | ✓ | - | Form field name |
| rules | RegisterOptions | - | - | Form validation rules |
| label | string | - | 'Recurring days' | Field label |
| placeholder | string | - | 'Select recurring days' | Placeholder text |
| formHelperText | string | - | 'Choose the days...' | Helper text |
| isMulti | boolean | - | false | Enable multi-selection |
TypeScript Interface
interface WeekDaySelectProps {
control: any;
isDisabled: boolean;
name: string;
rules?: RegisterOptions;
label?: string;
placeholder?: string;
formHelperText?: string;
isMulti?: boolean;
}
Week Day Options
The component provides all seven days of the week:
export const WEEK_DAY_OPTIONS = [
{ value: 'Monday', label: 'Monday' },
{ value: 'Tuesday', label: 'Tuesday' },
{ value: 'Wednesday', label: 'Wednesday' },
{ value: 'Thursday', label: 'Thursday' },
{ value: 'Friday', label: 'Friday' },
{ value: 'Saturday', label: 'Saturday' },
{ value: 'Sunday', label: 'Sunday' },
];
Usage Examples
Single Day Selection
import WeekDaySelect from 'components/Selects/WeekDaySelect';
import { useForm } from 'react-hook-form';
function SingleDayForm() {
const { control } = useForm();
return (
<WeekDaySelect
control={control}
name='rest_day'
isDisabled={false}
label='Rest Day'
placeholder='Select rest day'
formHelperText='Choose your weekly rest day'
isMulti={false}
/>
);
}
Multiple Days Selection
import WeekDaySelect from 'components/Selects/WeekDaySelect';
import { useForm } from 'react-hook-form';
function MultipleDaysForm() {
const { control } = useForm();
return (
<WeekDaySelect
control={control}
name='working_days'
isDisabled={false}
label='Working Days'
placeholder='Select working days'
formHelperText='Choose your working days'
isMulti={true}
rules={{ required: 'At least one working day is required' }}
/>
);
}
Shift Schedule Days
import WeekDaySelect from 'components/Selects/WeekDaySelect';
import { useForm } from 'react-hook-form';
function ShiftScheduleDays() {
const { control } = useForm();
return (
<WeekDaySelect
control={control}
name='shift_days'
isDisabled={false}
isMulti={true}
rules={{
required: 'Select at least one day for the shift',
validate: value =>
value.length > 0 || 'At least one day must be selected',
}}
/>
);
}
RedeemTypeSelect
Redemption type selection component with descriptions for each redemption option.
Component Location
import RedeemTypeSelect from 'components/Selects/RedeemTypeSelect';
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| control | any | ✓ | - | React Hook Form control |
| isDisabled | boolean | ✓ | - | Disabled state |
| name | string | ✓ | - | Form field name |
| rules | RegisterOptions | - | - | Form validation rules |
| label | string | - | 'How do you want to redeem?' | Field label |
| placeholder | string | - | 'Select redeem type' | Placeholder text |
| formHelperText | string | - | - | Helper text |
| isMulti | boolean | - | false | Enable multi-selection |
TypeScript Interface
interface RedeemTypeSelectProps {
control: any;
isDisabled: boolean;
name: string;
rules?: RegisterOptions;
label?: string;
placeholder?: string;
formHelperText?: string;
isMulti?: boolean;
}
Usage Examples
Basic Redemption Selection
import RedeemTypeSelect from 'components/Selects/RedeemTypeSelect';
import { useForm } from 'react-hook-form';
function OvertimeRedemptionForm() {
const { control } = useForm();
return (
<RedeemTypeSelect
control={control}
name='redeem_type'
isDisabled={false}
rules={{ required: 'Please select a redemption type' }}
/>
);
}
Custom Labels
import RedeemTypeSelect from 'components/Selects/RedeemTypeSelect';
import { useForm } from 'react-hook-form';
function CustomRedemptionForm() {
const { control } = useForm();
return (
<RedeemTypeSelect
control={control}
name='leave_redeem_type'
isDisabled={false}
label='Leave Redemption Type'
placeholder='Choose redemption method'
formHelperText='Select how you want to redeem your accumulated leave'
rules={{ required: 'Redemption type is required' }}
/>
);
}
Features
- Custom Option Rendering: Displays both label and description
- Form Integration: Built-in React Hook Form support
- Validation Support: Comprehensive validation rules
- Responsive Design: Adapts to different screen sizes
Integration Patterns
Form Integration
All specialized select components integrate seamlessly with React Hook Form:
import { useForm } from 'react-hook-form';
import {
TAScheduleFrequencySelect,
WeekDaySelect,
RedeemTypeSelect,
MultiSelect,
} from 'components/Selects';
function ComprehensiveForm() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm();
const onSubmit = data => {
console.log('Form data:', data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={4}>
<TAScheduleFrequencySelect
control={control}
name='schedule_frequency'
isDisabled={false}
scheduleType='shift'
rules={{ required: 'Schedule frequency is required' }}
/>
<WeekDaySelect
control={control}
name='working_days'
isDisabled={false}
isMulti={true}
rules={{ required: 'Working days are required' }}
/>
<RedeemTypeSelect
control={control}
name='redeem_type'
isDisabled={false}
rules={{ required: 'Redemption type is required' }}
/>
<Button type='submit'>Submit</Button>
</Stack>
</form>
);
}
Filter Integration
Custom selects work well in filtering scenarios:
import CustomSelectDropdown from 'components/Selects/CustomSelect';
function FilterBar() {
const statusOptions = [
{ label: 'All', value: 'all' },
{ label: 'Active', value: 'active' },
{ label: 'Inactive', value: 'inactive' },
];
const departmentOptions = [
{ label: 'All Departments', value: 'all' },
{ label: 'HR', value: 'hr' },
{ label: 'IT', value: 'it' },
{ label: 'Finance', value: 'finance' },
];
return (
<HStack spacing={4}>
<CustomSelectDropdown
options={statusOptions}
onChange={value => console.log('Status filter:', value)}
onVariantChange={value => console.log('Status changed:', value)}
variant='filter'
name='Status'
myValue='all'
queryParamValue='all'
/>
<CustomSelectDropdown
options={departmentOptions}
onChange={value => console.log('Department filter:', value)}
onVariantChange={value => console.log('Department changed:', value)}
variant='filter'
name='Department'
myValue='all'
queryParamValue='all'
/>
</HStack>
);
}
Styling
Common Styling Patterns
All specialized select components follow consistent styling patterns:
Colors
- Focus border:
green - Selected text:
white - Unselected text:
charcoal - Helper text:
slate - Error states:
red
Typography
- Label:
body-regular - Options:
body-regular - Descriptions:
body-small - Helper text:
body-small
Spacing
- Consistent padding and margins
- Proper option spacing
- Responsive design considerations
Custom Styling
Components support custom styling through props:
// CustomSelect with custom styling
<CustomSelectDropdown
// ... other props
optionalStyles={{
control: (provided) => ({
...provided,
borderColor: 'purple.500',
'&:hover': {
borderColor: 'purple.600',
},
}),
}}
/>
// MultiSelect with custom styling
<MultiSelect
// ... other props
styles={{
control: (provided) => ({
...provided,
minHeight: '50px',
}),
}}
/>
Accessibility
Keyboard Navigation
- Tab Navigation: All components support proper tab order
- Arrow Keys: Navigate through options with arrow keys
- Enter/Space: Select options with Enter or Space keys
- Escape: Close dropdowns with Escape key
Screen Reader Support
- ARIA Labels: Proper labeling for all interactive elements
- Role Attributes: Correct roles for dropdown and option elements
- Status Announcements: Selection changes are announced
- Error Messages: Form errors are properly announced
Visual Accessibility
- High Contrast: Sufficient color contrast ratios
- Focus Indicators: Clear focus indicators for all interactive elements
- Text Scaling: Components work with text scaling
- Reduced Motion: Respects prefers-reduced-motion settings
Performance Considerations
Optimization Techniques
- Memoization: Components use React.memo where appropriate
- Virtualization: Long option lists use virtualization
- Debouncing: Search inputs are debounced
- Lazy Loading: Options loaded on demand when possible
Best Practices
- Option Limits: Implement pagination for large datasets
- Search Optimization: Implement server-side search for large datasets
- Caching: Cache frequently used options
- Loading States: Provide proper loading indicators
Testing Strategies
Unit Testing
import { render, screen, fireEvent } from '@testing-library/react';
import { useForm } from 'react-hook-form';
import WeekDaySelect from 'components/Selects/WeekDaySelect';
function TestWrapper() {
const { control } = useForm();
return (
<WeekDaySelect
control={control}
name='test_days'
isDisabled={false}
isMulti={true}
/>
);
}
describe('WeekDaySelect', () => {
it('renders all week days', () => {
render(<TestWrapper />);
fireEvent.click(screen.getByRole('button'));
expect(screen.getByText('Monday')).toBeInTheDocument();
expect(screen.getByText('Tuesday')).toBeInTheDocument();
expect(screen.getByText('Wednesday')).toBeInTheDocument();
expect(screen.getByText('Thursday')).toBeInTheDocument();
expect(screen.getByText('Friday')).toBeInTheDocument();
expect(screen.getByText('Saturday')).toBeInTheDocument();
expect(screen.getByText('Sunday')).toBeInTheDocument();
});
});
Integration Testing
- Form Integration: Test with React Hook Form
- Validation Testing: Test form validation rules
- State Management: Test with global state management
- API Integration: Test with real API calls
E2E Testing
- User Workflows: Test complete user interactions
- Accessibility Testing: Test with screen readers
- Performance Testing: Test with large datasets
- Cross-browser Testing: Test across different browsers
Migration Notes
From Legacy Select Components
Update Basic Selects
// Old approach
<Select options={options} onChange={handleChange} />
// New approach
<CustomSelectDropdown
options={options}
onChange={handleChange}
onVariantChange={handleVariantChange}
variant="base"
name="Selection"
myValue={value}
queryParamValue={queryValue}
/>
Update Multi-Selects
// Old approach
<Select isMulti options={options} onChange={handleChange} />
// New approach
<MultiSelect
options={options}
onChange={handleChange}
isMulti={true}
isLoading={false}
allowSelectAll={true}
/>
Breaking Changes
- Props Structure: Some props have been renamed or restructured
- Styling: Custom styling approach has changed
- Event Handlers: Event handler signatures may be different
Upgrade Paths
- Identify Usage: Find all select component usage
- Update Imports: Change import statements
- Update Props: Modify props to match new API
- Test Integration: Verify form integration works
- Update Styling: Migrate custom styles
Best Practices
Component Selection
- Use TAScheduleFrequencySelect for time attendance scheduling
- Use WeekDaySelect for day-of-week selections
- Use MultiSelect for multiple item selection
- Use CustomSelect for custom filter scenarios
- Use RedeemTypeSelect for redemption workflows
Form Integration
- Always use with React Hook Form for consistency
- Implement proper validation rules for data integrity
- Provide meaningful error messages for user guidance
- Use appropriate field names for accessibility
Performance
- Implement search for large datasets (>100 options)
- Use virtualization for very large lists (>1000 options)
- Debounce search inputs to reduce API calls
- Cache frequently used options to improve performance
User Experience
- Provide clear labels and descriptions for all options
- Use appropriate placeholders to guide users
- Implement loading states for async operations
- Provide feedback for user actions (selection, clearing, etc.)
Common Patterns
Conditional Selects
function ConditionalSelectExample() {
const [scheduleType, setScheduleType] = useState('shift');
return (
<Stack spacing={4}>
<Select
value={scheduleType}
onChange={e => setScheduleType(e.target.value)}
>
<option value='shift'>Shift</option>
<option value='off_day'>Off Day</option>
<option value='rest_day'>Rest Day</option>
</Select>
<TAScheduleFrequencySelect
control={control}
name='frequency'
isDisabled={false}
scheduleType={scheduleType}
/>
</Stack>
);
}
Dependent Selects
function DependentSelectExample() {
const [department, setDepartment] = useState(null);
const [employeeOptions, setEmployeeOptions] = useState([]);
useEffect(() => {
if (department) {
fetchEmployeesByDepartment(department.value).then(setEmployeeOptions);
}
}, [department]);
return (
<Stack spacing={4}>
<CustomSelectDropdown
options={departmentOptions}
onChange={setDepartment}
variant='base'
name='Department'
myValue={department?.label || ''}
queryParamValue={department?.value || ''}
/>
<MultiSelect
options={employeeOptions}
onChange={setSelectedEmployees}
isMulti={true}
isLoading={!department}
isDisabled={!department}
placeholder='Select employees...'
/>
</Stack>
);
}
Form Validation
function ValidatedSelectForm() {
const {
control,
handleSubmit,
formState: { errors },
} = useForm();
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Stack spacing={4}>
<FormControl isInvalid={!!errors.schedule_frequency}>
<TAScheduleFrequencySelect
control={control}
name='schedule_frequency'
isDisabled={false}
scheduleType='shift'
rules={{
required: 'Schedule frequency is required',
validate: value =>
value !== 'invalid' || 'Invalid frequency selected',
}}
/>
<FormErrorMessage>
{errors.schedule_frequency?.message}
</FormErrorMessage>
</FormControl>
<FormControl isInvalid={!!errors.working_days}>
<WeekDaySelect
control={control}
name='working_days'
isDisabled={false}
isMulti={true}
rules={{
required: 'At least one working day is required',
validate: value =>
value.length >= 1 || 'Select at least one working day',
}}
/>
<FormErrorMessage>{errors.working_days?.message}</FormErrorMessage>
</FormControl>
</Stack>
</form>
);
}