Skip to content

Feature: add "days" support #55

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions README.md

Large diffs are not rendered by default.

52 changes: 50 additions & 2 deletions src/components/TimerPicker/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,12 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
aggressivelyGetLatestDuration = false,
allowFontScaling = false,
amLabel = "am",
dayInterval = 1,
dayLabel,
dayLimit,
daysPickerIsDisabled = false,
disableInfiniteScroll = false,
hideDays = true,
hideHours = false,
hideMinutes = false,
hideSeconds = false,
Expand All @@ -31,6 +36,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
hourLimit,
hoursPickerIsDisabled = false,
initialValue,
maximumDays = 30,
maximumHours = 23,
maximumMinutes = 59,
maximumSeconds = 59,
Expand All @@ -39,12 +45,14 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
minuteLimit,
minutesPickerIsDisabled = false,
onDurationChange,
padDaysWithZero = false,
padHoursWithZero = false,
padMinutesWithZero = true,
padSecondsWithZero = true,
padWithNItems = 1,
pickerContainerProps,
pmLabel = "pm",
repeatDayNumbersNTimes = 3,
repeatHourNumbersNTimes = 8,
repeatMinuteNumbersNTimes = 3,
repeatSecondNumbersNTimes = 3,
Expand Down Expand Up @@ -74,11 +82,17 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
const safeInitialValue = useMemo(
() =>
getSafeInitialValue({
days: initialValue?.days,
hours: initialValue?.hours,
minutes: initialValue?.minutes,
seconds: initialValue?.seconds,
}),
[initialValue?.hours, initialValue?.minutes, initialValue?.seconds]
[
initialValue?.days,
initialValue?.hours,
initialValue?.minutes,
initialValue?.seconds,
]
);

const styles = useMemo(
Expand All @@ -87,6 +101,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
[customStyles]
);

const [selectedDays, setSelectedDays] = useState(safeInitialValue.days);
const [selectedHours, setSelectedHours] = useState(
safeInitialValue.hours
);
Expand All @@ -99,30 +114,36 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(

useEffect(() => {
onDurationChange?.({
days: selectedDays,
hours: selectedHours,
minutes: selectedMinutes,
seconds: selectedSeconds,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [selectedHours, selectedMinutes, selectedSeconds]);
}, [selectedDays, selectedHours, selectedMinutes, selectedSeconds]);

const daysDurationScrollRef = useRef<DurationScrollRef>(null);
const hoursDurationScrollRef = useRef<DurationScrollRef>(null);
const minutesDurationScrollRef = useRef<DurationScrollRef>(null);
const secondsDurationScrollRef = useRef<DurationScrollRef>(null);

useImperativeHandle(ref, () => ({
reset: (options) => {
setSelectedDays(safeInitialValue.days);
setSelectedHours(safeInitialValue.hours);
setSelectedMinutes(safeInitialValue.minutes);
setSelectedSeconds(safeInitialValue.seconds);
daysDurationScrollRef.current?.reset(options);
hoursDurationScrollRef.current?.reset(options);
minutesDurationScrollRef.current?.reset(options);
secondsDurationScrollRef.current?.reset(options);
},
setValue: (value, options) => {
setSelectedDays(value.days);
setSelectedHours(value.hours);
setSelectedMinutes(value.minutes);
setSelectedSeconds(value.seconds);
daysDurationScrollRef.current?.setValue(value.days, options);
hoursDurationScrollRef.current?.setValue(value.hours, options);
minutesDurationScrollRef.current?.setValue(
value.minutes,
Expand All @@ -134,6 +155,7 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
);
},
latestDuration: {
days: daysDurationScrollRef.current?.latestDuration,
hours: hoursDurationScrollRef.current?.latestDuration,
minutes: minutesDurationScrollRef.current?.latestDuration,
seconds: secondsDurationScrollRef.current?.latestDuration,
Expand All @@ -145,6 +167,32 @@ const TimerPicker = forwardRef<TimerPickerRef, TimerPickerProps>(
{...pickerContainerProps}
style={styles.pickerContainer}
testID="timer-picker">
{!hideDays ? (
<DurationScroll
ref={daysDurationScrollRef}
aggressivelyGetLatestDuration={
aggressivelyGetLatestDuration
}
allowFontScaling={allowFontScaling}
disableInfiniteScroll={disableInfiniteScroll}
initialValue={safeInitialValue.days}
interval={dayInterval}
isDisabled={daysPickerIsDisabled}
label={dayLabel ?? "d"}
limit={dayLimit}
maximumValue={maximumDays}
onDurationChange={setSelectedDays}
padNumbersWithZero={padDaysWithZero}
padWithNItems={safePadWithNItems}
repeatNumbersNTimes={repeatDayNumbersNTimes}
repeatNumbersNTimesNotExplicitlySet={
props?.repeatDayNumbersNTimes === undefined
}
styles={styles}
testID="duration-scroll-day"
{...otherProps}
/>
) : null}
{!hideHours ? (
<DurationScroll
ref={hoursDurationScrollRef}
Expand Down
2 changes: 1 addition & 1 deletion src/components/TimerPicker/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const LIGHT_MODE_BACKGROUND_COLOR = "#F1F1F1";
const LIGHT_MODE_TEXT_COLOR = "#1B1B1B";

export const generateStyles = (
customStyles: CustomTimerPickerStyles | undefined,
customStyles: CustomTimerPickerStyles | undefined
) =>
StyleSheet.create({
pickerContainer: {
Expand Down
12 changes: 12 additions & 0 deletions src/components/TimerPicker/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@ import type { CustomTimerPickerStyles } from "./styles";

export interface TimerPickerRef {
latestDuration: {
days: MutableRefObject<number> | undefined;
hours: MutableRefObject<number> | undefined;
minutes: MutableRefObject<number> | undefined;
seconds: MutableRefObject<number> | undefined;
};
reset: (options?: { animated?: boolean }) => void;
setValue: (
value: {
days: number;
hours: number;
minutes: number;
seconds: number;
Expand All @@ -42,7 +44,12 @@ export interface TimerPickerProps {
allowFontScaling?: boolean;
amLabel?: string;
clickSoundAsset?: SoundAssetType;
dayInterval?: number;
dayLabel?: string | React.ReactElement;
dayLimit?: LimitType;
daysPickerIsDisabled?: boolean;
disableInfiniteScroll?: boolean;
hideDays?: boolean;
hideHours?: boolean;
hideMinutes?: boolean;
hideSeconds?: boolean;
Expand All @@ -51,10 +58,12 @@ export interface TimerPickerProps {
hourLimit?: LimitType;
hoursPickerIsDisabled?: boolean;
initialValue?: {
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
};
maximumDays?: number;
maximumHours?: number;
maximumMinutes?: number;
maximumSeconds?: number;
Expand All @@ -63,10 +72,12 @@ export interface TimerPickerProps {
minuteLimit?: LimitType;
minutesPickerIsDisabled?: boolean;
onDurationChange?: (duration: {
days: number;
hours: number;
minutes: number;
seconds: number;
}) => void;
padDaysWithZero?: boolean;
padHoursWithZero?: boolean;
padMinutesWithZero?: boolean;
padSecondsWithZero?: boolean;
Expand All @@ -75,6 +86,7 @@ export interface TimerPickerProps {
pickerFeedback?: () => void | Promise<void>;
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
pmLabel?: string;
repeatDayNumbersNTimes?: number;
repeatHourNumbersNTimes?: number;
repeatMinuteNumbersNTimes?: number;
repeatSecondNumbersNTimes?: number;
Expand Down
12 changes: 11 additions & 1 deletion src/components/TimerPickerModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const TimerPickerModal = forwardRef<TimerPickerModalRef, TimerPickerModalProps>(
const timerPickerRef = useRef<TimerPickerRef>(null);

const safeInitialValue = getSafeInitialValue({
days: initialValue?.days,
hours: initialValue?.hours,
minutes: initialValue?.minutes,
seconds: initialValue?.seconds,
Expand All @@ -69,13 +70,15 @@ const TimerPickerModal = forwardRef<TimerPickerModalRef, TimerPickerModalProps>(
reset();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
safeInitialValue.days,
safeInitialValue.hours,
safeInitialValue.minutes,
safeInitialValue.seconds,
]);

const hideModalHandler = () => {
setSelectedDuration({
days: confirmedDuration.days,
hours: confirmedDuration.hours,
minutes: confirmedDuration.minutes,
seconds: confirmedDuration.seconds,
Expand All @@ -87,6 +90,7 @@ const TimerPickerModal = forwardRef<TimerPickerModalRef, TimerPickerModalProps>(
const latestDuration = timerPickerRef.current?.latestDuration;

const newDuration = {
days: latestDuration?.days?.current ?? selectedDuration.days,
hours: latestDuration?.hours?.current ?? selectedDuration.hours,
minutes:
latestDuration?.minutes?.current ??
Expand All @@ -107,7 +111,12 @@ const TimerPickerModal = forwardRef<TimerPickerModalRef, TimerPickerModalProps>(

// wrapped in useCallback to avoid unnecessary re-renders of TimerPicker
const durationChangeHandler = useCallback(
(duration: { hours: number; minutes: number; seconds: number }) => {
(duration: {
days: number;
hours: number;
minutes: number;
seconds: number;
}) => {
setSelectedDuration(duration);
onDurationChange?.(duration);
},
Expand All @@ -122,6 +131,7 @@ const TimerPickerModal = forwardRef<TimerPickerModalRef, TimerPickerModalProps>(
timerPickerRef.current?.setValue(value, options);
},
latestDuration: {
days: timerPickerRef.current?.latestDuration?.days,
hours: timerPickerRef.current?.latestDuration?.hours,
minutes: timerPickerRef.current?.latestDuration?.minutes,
seconds: timerPickerRef.current?.latestDuration?.seconds,
Expand Down
4 changes: 4 additions & 0 deletions src/components/TimerPickerModal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import type { CustomTimerPickerModalStyles } from "./styles";

export interface TimerPickerModalRef {
latestDuration: {
days: MutableRefObject<number> | undefined;
hours: MutableRefObject<number> | undefined;
minutes: MutableRefObject<number> | undefined;
seconds: MutableRefObject<number> | undefined;
};
reset: (options?: { animated?: boolean }) => void;
setValue: (
value: {
days: number;
hours: number;
minutes: number;
seconds: number;
Expand All @@ -38,10 +40,12 @@ export interface TimerPickerModalProps extends TimerPickerProps {
modalTitleProps?: React.ComponentProps<typeof Text>;
onCancel?: () => void;
onConfirm: ({
days,
hours,
minutes,
seconds,
}: {
days: number;
hours: number;
minutes: number;
seconds: number;
Expand Down
8 changes: 5 additions & 3 deletions src/tests/TimerPicker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ describe("TimerPicker", () => {
expect(component).toBeDefined();
});

it("hides minutes and seconds when respective hide props are provided", () => {
it("hides days, minutes and seconds when respective hide props are provided", () => {
const { queryByTestId } = render(
<TimerPicker hideMinutes hideSeconds />
<TimerPicker hideDays hideMinutes hideSeconds />
);
const dayPicker = queryByTestId("duration-scroll-day");
const minutePicker = queryByTestId("duration-scroll-minute");
const secondPicker = queryByTestId("duration-scroll-second");
expect(dayPicker).toBeNull();
expect(minutePicker).toBeNull();
expect(secondPicker).toBeNull();
});
Expand All @@ -36,6 +38,6 @@ describe("TimerPicker", () => {
<TimerPicker FlatList={CustomFlatList} />
);
const customFlatList = queryAllByTestId("custom-flat-list");
expect(customFlatList).toHaveLength(3);
expect(customFlatList).toHaveLength(4);
});
});
5 changes: 5 additions & 0 deletions src/utils/getSafeInitialValue.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
export const getSafeInitialValue = (
initialValue:
| {
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
}
| undefined
) => ({
days:
typeof initialValue?.days === "number" && !isNaN(initialValue?.days)
? initialValue.days
: 0,
hours:
typeof initialValue?.hours === "number" && !isNaN(initialValue?.hours)
? initialValue.hours
Expand Down