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>
);
}