Skip to main content

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

Generic Selects

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

PropTypeRequiredDefaultDescription
controlany-React Hook Form control
isDisabledboolean-Disabled state
namestring-Form field name
rulesRegisterOptions--Form validation rules
scheduleTypestring-'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:

  1. Recurring Days: Regular schedule repetition
  2. Single Date: One-time occurrence
  3. 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}>
&lt;Stack&gt;
<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

PropTypeRequiredDefaultDescription
optionsArray-Select options array
onChangefunction-Change handler
onVariantChangefunction-Variant change handler
variantstring-Select variant ('base' or filter variant)
namestring-Select name/label
myValuestring-Current value
queryParamValuestring-Query parameter value
handleClickfunction--Click handler
isClearableboolean-trueShow clear button
isDisabledboolean-falseDisabled 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

PropTypeRequiredDefaultDescription
optionsArray-Select options array
onChangefunction-Change handler
isMultiboolean-falseEnable multi-selection
isSearchableboolean-trueEnable search
isDisabledboolean-falseDisabled state
isLoadingboolean-Loading state
placeholderstring--Placeholder text
defaultValueOption/Array--Default selected values
allowSelectAllboolean-falseEnable "Select All" option
hideSelectedOptionsboolean-falseHide selected options from dropdown
closeMenuOnSelectboolean-trueClose 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

PropTypeRequiredDefaultDescription
controlany-React Hook Form control
isDisabledboolean-Disabled state
namestring-Form field name
rulesRegisterOptions--Form validation rules
labelstring-'Recurring days'Field label
placeholderstring-'Select recurring days'Placeholder text
formHelperTextstring-'Choose the days...'Helper text
isMultiboolean-falseEnable 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

PropTypeRequiredDefaultDescription
controlany-React Hook Form control
isDisabledboolean-Disabled state
namestring-Form field name
rulesRegisterOptions--Form validation rules
labelstring-'How do you want to redeem?'Field label
placeholderstring-'Select redeem type'Placeholder text
formHelperTextstring--Helper text
isMultiboolean-falseEnable 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

  1. Identify Usage: Find all select component usage
  2. Update Imports: Change import statements
  3. Update Props: Modify props to match new API
  4. Test Integration: Verify form integration works
  5. 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',
}}
/>
&lt;FormErrorMessage&gt;
{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',
}}
/>
&lt;FormErrorMessage&gt;{errors.working_days?.message}</FormErrorMessage>
</FormControl>
</Stack>
</form>
);
}