@spaced-out/ui-design-system
Version:
Sense UI components library
230 lines (208 loc) • 5.72 kB
Flow
// @flow strict
import * as React from 'react';
import classify from '../../utils/classify';
import {UnstyledButton} from '../Button';
import css from './WeekdayPicker.module.css';
const DEFAULT_WEEKDAYS = [
{key: 'sun', label: 'S'},
{key: 'mon', label: 'M'},
{key: 'tue', label: 'T'},
{key: 'wed', label: 'W'},
{key: 'thu', label: 'T'},
{key: 'fri', label: 'F'},
{key: 'sat', label: 'S'},
];
const WEEKEND_KEYS = ['sat', 'sun'];
export const WEEKDAY_PICKER_SIZE = Object.freeze({
small: 'small',
medium: 'medium',
});
type ClassNames = $ReadOnly<{
wrapper?: string,
button?: string,
label?: string,
helperText?: string,
}>;
type WeekdayPickerSize = $Values<typeof WEEKDAY_PICKER_SIZE>;
type Weekday = {
key: string,
label: string,
secondaryLabel?: string,
};
export type WeekdayPickerProps = {
size?: WeekdayPickerSize,
selectedWeekDays?: Array<Weekday>,
hiddenWeekDays?: Array<string>,
disabledWeekDays?: Array<string>,
customDayLabels?: Array<Weekday>,
onChange?: (
selectedDays: Array<Weekday>,
?SyntheticEvent<HTMLElement>,
) => mixed,
readOnly?: boolean,
classNames?: ClassNames,
showWeekends?: boolean,
ariaLabel?: string,
label?: React.Node,
helperText?: React.Node,
disableMultiSelect?: boolean,
};
type DayButtonProps = {
day: Weekday,
isSelected: boolean,
isDisabled: boolean,
onClick?: ?(SyntheticEvent<HTMLElement>) => mixed,
className?: string,
size: WeekdayPickerSize,
readOnly?: boolean,
};
const DayButton = ({
day,
isSelected,
isDisabled,
onClick,
className,
size,
readOnly,
}: DayButtonProps) => (
<UnstyledButton
key={day.key}
className={classify(
css.weekdayButton,
{
[css.weekdayButtonMedium]: size === WEEKDAY_PICKER_SIZE.medium,
[css.weekdayButtonSmall]: size === WEEKDAY_PICKER_SIZE.small,
[css.weekdayButtonSelected]: isSelected,
[css.weekdayButtonDisabled]: isDisabled,
},
className,
)}
onClick={onClick}
disabled={isDisabled}
aria-label={`Select ${day.label}`}
tabIndex={isDisabled || readOnly ? -1 : 0}
role="checkbox"
>
{day.secondaryLabel && (
<div
className={classify(css.secondaryLabel, {
[css.secondaryLabelSelected]: isSelected,
[css.secondaryLabelDisabled]: isDisabled,
})}
>
{day.secondaryLabel}
</div>
)}
{day.label}
</UnstyledButton>
);
export const WeekdayPicker: React$AbstractComponent<
WeekdayPickerProps,
HTMLDivElement,
> = React.forwardRef<WeekdayPickerProps, HTMLDivElement>(
(
{
size = 'medium',
selectedWeekDays = [],
disabledWeekDays = [],
hiddenWeekDays = [],
customDayLabels = DEFAULT_WEEKDAYS,
onChange,
readOnly = false,
classNames = {},
showWeekends = true,
ariaLabel = 'Select weekdays',
label,
helperText,
disableMultiSelect = false,
}: WeekdayPickerProps,
ref,
) => {
const [selectedDays, setSelectedDays] =
React.useState<Array<Weekday>>(selectedWeekDays);
const handleDayToggle = (
day: Weekday,
event: SyntheticEvent<HTMLElement>,
) => {
if (readOnly || disabledWeekDays.includes(day.key)) {
return;
}
let updatedSelectedDays = selectedDays;
if (disableMultiSelect) {
if (selectedDays.some((selected) => selected.key === day.key)) {
updatedSelectedDays = [];
} else {
updatedSelectedDays = [day];
}
} else {
const isSelected = selectedDays.some(
(selected) => selected.key === day.key,
);
if (isSelected) {
updatedSelectedDays = selectedDays.filter(
(selected) => selected.key !== day.key,
);
} else {
updatedSelectedDays = [...selectedDays, day];
}
}
setSelectedDays(updatedSelectedDays);
onChange?.(updatedSelectedDays, event);
};
return (
<div
data-testid="Weekday-Picker"
ref={ref}
className={classify(css.weekdayPickerContainer, classNames.wrapper, {
[css.weekdayReadOnly]: readOnly,
})}
aria-label={ariaLabel}
role="group"
>
{!!label && (
<div className={classify(css.weekdayLabel, classNames.label)}>
{label}
</div>
)}
<div className={css.weekdayPickerDays}>
{customDayLabels.map((day) => {
const isWeekend = WEEKEND_KEYS.includes(day.key);
const isHidden = hiddenWeekDays.includes(day.key);
if ((!showWeekends && isWeekend) || isHidden) {
return null;
}
const isSelected = selectedDays.some(
(selected) => selected.key === day.key,
);
const isDisabled = disabledWeekDays.includes(day.key);
return (
<DayButton
key={day.key}
day={day}
isSelected={isSelected}
isDisabled={isDisabled}
onClick={(event) => {
if (isDisabled) {
event.preventDefault();
} else {
handleDayToggle(day, event);
}
}}
className={classNames.button}
size={size}
readOnly={readOnly}
/>
);
})}
</div>
{!!helperText && (
<div
className={classify(css.weekdayHelperText, classNames.helperText)}
>
{helperText}
</div>
)}
</div>
);
},
);