From 327025f17b9dd4a2fa2eba954bec68b5b8dcbd57 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 25 Feb 2026 20:29:35 +0000 Subject: [PATCH 1/5] =?UTF-8?q?=F0=9F=92=84=20Improve=20layout=20styling?= =?UTF-8?q?=20options=20and=20fix=20modal=20width=20issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/example-bare/App.tsx | 5 +- examples/example-expo/App.tsx | 14 ++++-- .../DurationScroll/DurationScroll.tsx | 26 ++++++++++- src/components/DurationScroll/types.ts | 2 + src/components/PickerItem/PickerItem.tsx | 4 +- src/components/TimerPicker/TimerPicker.tsx | 46 ++++++++++++++++++- src/components/TimerPicker/styles.ts | 25 ++++++++-- .../TimerPickerModal/TimerPickerModal.tsx | 35 ++++++++++++++ src/components/TimerPickerModal/styles.ts | 9 +++- 9 files changed, 149 insertions(+), 17 deletions(-) diff --git a/examples/example-bare/App.tsx b/examples/example-bare/App.tsx index f0d72c3..680c25d 100644 --- a/examples/example-bare/App.tsx +++ b/examples/example-bare/App.tsx @@ -253,10 +253,9 @@ export default function App() { fontSize: 32, }, pickerLabelContainer: { - left: undefined, marginTop: -4, - right: 0, }, + pickerLabelGap: 23, theme: "dark", }} /> @@ -275,7 +274,6 @@ export default function App() { pickerFeedback={pickerFeedback} secondLabel="sec" styles={{ - labelOffsetPercentage: 0, pickerContainer: { paddingHorizontal: 50, }, @@ -285,6 +283,7 @@ export default function App() { pickerLabel: { fontSize: 26, }, + pickerLabelGap: 8, theme: "light", }} /> diff --git a/examples/example-expo/App.tsx b/examples/example-expo/App.tsx index 7a72534..2864df4 100644 --- a/examples/example-expo/App.tsx +++ b/examples/example-expo/App.tsx @@ -119,7 +119,6 @@ export default function App() { @@ -241,10 +245,9 @@ export default function App() { fontSize: 32, }, pickerLabelContainer: { - left: undefined, marginTop: -4, - right: 0, }, + pickerLabelGap: 23, theme: "dark", }} /> @@ -263,7 +266,6 @@ export default function App() { pickerFeedback={pickerFeedback} secondLabel="sec" styles={{ - labelOffsetPercentage: 0, pickerContainer: { paddingHorizontal: 50, }, @@ -273,6 +275,7 @@ export default function App() { pickerLabel: { fontSize: 26, }, + pickerLabelGap: 8, theme: "light", }} /> @@ -350,6 +353,7 @@ export default function App() { horizontal onMomentumScrollEnd={onMomentumScrollEnd} pagingEnabled + showsHorizontalScrollIndicator={false} > {renderExample1} {renderExample2} diff --git a/src/components/DurationScroll/DurationScroll.tsx b/src/components/DurationScroll/DurationScroll.tsx index 1867bc0..ca2da04 100644 --- a/src/components/DurationScroll/DurationScroll.tsx +++ b/src/components/DurationScroll/DurationScroll.tsx @@ -45,8 +45,10 @@ const DurationScroll = forwardRef((props onDurationChange, padNumbersWithZero = false, padWithNItems, + pickerColumnWidth, pickerFeedback, pickerGradientOverlayProps, + pickerLabelGap, pmLabel, repeatNumbersNTimes = 3, repeatNumbersNTimesNotExplicitlySet, @@ -55,6 +57,24 @@ const DurationScroll = forwardRef((props testID, } = props; + const labelPositionStyle = useMemo(() => { + // When the style already has an explicit `left` (from legacy percentage system or + // user override), don't apply pixel-based positioning. + if (styles.pickerLabelContainer.left != null) { + return undefined; + } + + const gap = pickerLabelGap ?? 6; + const fontSize = styles.pickerItem.fontSize ?? 25; + const maxDigitCount = Math.max(2, String(maximumValue).length); + const halfNumberWidth = (maxDigitCount * fontSize * 0.55) / 2; + + return { + left: "50%" as const, + marginLeft: halfNumberWidth + gap, + }; + }, [maximumValue, pickerLabelGap, styles.pickerItem.fontSize, styles.pickerLabelContainer.left]); + const numberOfItems = useMemo(() => { // guard against negative maximum values if (maximumValue < 0) { @@ -210,6 +230,7 @@ const DurationScroll = forwardRef((props amLabel={amLabel} is12HourPicker={is12HourPicker} item={item} + pickerAmPmPositionStyle={labelPositionStyle} pmLabel={pmLabel} selectedValue={selectedValue} styles={styles} @@ -221,6 +242,7 @@ const DurationScroll = forwardRef((props allowFontScaling, amLabel, is12HourPicker, + labelPositionStyle, pmLabel, selectedValue, styles, @@ -488,7 +510,7 @@ const DurationScroll = forwardRef((props viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs} windowSize={numberOfItemsToShow} /> - + {typeof label === "string" ? ( {label} @@ -508,6 +530,7 @@ const DurationScroll = forwardRef((props initialScrollIndex, isDisabled, label, + labelPositionStyle, numberOfItemsToShow, numbersForFlatList, onMomentumScrollEnd, @@ -571,6 +594,7 @@ const DurationScroll = forwardRef((props pointerEvents={isDisabled ? "none" : undefined} style={[ styles.durationScrollFlatListContainer, + pickerColumnWidth != null && { flex: 0, width: pickerColumnWidth }, { height: styles.pickerItemContainer.height * numberOfItemsToShow, }, diff --git a/src/components/DurationScroll/types.ts b/src/components/DurationScroll/types.ts index d4e7f2a..0bd2030 100644 --- a/src/components/DurationScroll/types.ts +++ b/src/components/DurationScroll/types.ts @@ -28,8 +28,10 @@ export interface DurationScrollProps { onDurationChange: (duration: number) => void; padNumbersWithZero?: boolean; padWithNItems: number; + pickerColumnWidth?: number; pickerFeedback?: () => void | Promise; pickerGradientOverlayProps?: Partial; + pickerLabelGap?: number; pmLabel?: string; repeatNumbersNTimes?: number; repeatNumbersNTimesNotExplicitlySet: boolean; diff --git a/src/components/PickerItem/PickerItem.tsx b/src/components/PickerItem/PickerItem.tsx index 73aae7b..0d764ca 100644 --- a/src/components/PickerItem/PickerItem.tsx +++ b/src/components/PickerItem/PickerItem.tsx @@ -11,6 +11,7 @@ interface PickerItemProps { amLabel?: string; is12HourPicker?: boolean; item: string; + pickerAmPmPositionStyle?: { left: "50%"; marginLeft: number }; pmLabel?: string; selectedValue?: number; styles: ReturnType; @@ -24,6 +25,7 @@ const PickerItem = React.memo( amLabel, is12HourPicker, item, + pickerAmPmPositionStyle, pmLabel, selectedValue, styles, @@ -57,7 +59,7 @@ const PickerItem = React.memo( {stringItem} {is12HourPicker && ( - + {isAm ? amLabel : pmLabel} diff --git a/src/components/TimerPicker/TimerPicker.tsx b/src/components/TimerPicker/TimerPicker.tsx index 4e95c6e..43af37c 100644 --- a/src/components/TimerPicker/TimerPicker.tsx +++ b/src/components/TimerPicker/TimerPicker.tsx @@ -13,8 +13,24 @@ import { getSafeInitialValue } from "../../utils/getSafeInitialValue"; import DurationScroll from "../DurationScroll"; import type { DurationScrollRef } from "../DurationScroll"; import { generateStyles } from "./styles"; +import type { PerColumnValue, PickerColumn } from "./styles"; import type { TimerPickerProps, TimerPickerRef } from "./types"; +const resolvePerColumn = ( + value: PerColumnValue | undefined, + column: PickerColumn +): number | undefined => { + if (value == null) { + return undefined; + } + + if (typeof value === "number") { + return value; + } + + return value[column]; +}; + const TimerPicker = forwardRef((props, ref) => { const { aggressivelyGetLatestDuration = false, @@ -80,7 +96,24 @@ const TimerPicker = forwardRef((props, ref) => 'The "clickSoundAsset" prop is deprecated and will be removed in a future version. Please use the "pickerFeedback" prop instead.' ); } - }, [otherProps.Audio, otherProps.Haptics, otherProps.clickSoundAsset]); + if (customStyles?.labelOffsetPercentage != null) { + if (customStyles?.pickerLabelGap != null) { + console.warn( + "labelOffsetPercentage is ignored when pickerLabelGap is set. Please remove labelOffsetPercentage." + ); + } else { + console.warn( + 'The "labelOffsetPercentage" style prop is deprecated and will be removed in a future version. Please use the "pickerLabelGap" style prop instead.' + ); + } + } + }, [ + otherProps.Audio, + otherProps.Haptics, + otherProps.clickSoundAsset, + customStyles?.labelOffsetPercentage, + customStyles?.pickerLabelGap, + ]); const safePadWithNItems = useMemo(() => { if (padWithNItems < 0 || isNaN(padWithNItems)) { @@ -107,6 +140,9 @@ const TimerPicker = forwardRef((props, ref) => [initialValue?.days, initialValue?.hours, initialValue?.minutes, initialValue?.seconds] ); + const pickerLabelGap = customStyles?.pickerLabelGap; + const pickerColumnWidth = customStyles?.pickerColumnWidth; + const styles = useMemo( () => generateStyles(customStyles), @@ -187,6 +223,8 @@ const TimerPicker = forwardRef((props, ref) => onDurationChange={setSelectedDays} padNumbersWithZero={padDaysWithZero} padWithNItems={safePadWithNItems} + pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "days")} + pickerLabelGap={resolvePerColumn(pickerLabelGap, "days")} repeatNumbersNTimes={repeatDayNumbersNTimes} repeatNumbersNTimesNotExplicitlySet={props?.repeatDayNumbersNTimes === undefined} selectedValue={selectedDays} @@ -213,6 +251,8 @@ const TimerPicker = forwardRef((props, ref) => onDurationChange={setSelectedHours} padNumbersWithZero={padHoursWithZero} padWithNItems={safePadWithNItems} + pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "hours")} + pickerLabelGap={resolvePerColumn(pickerLabelGap, "hours")} pmLabel={pmLabel} repeatNumbersNTimes={repeatHourNumbersNTimes} repeatNumbersNTimesNotExplicitlySet={props?.repeatHourNumbersNTimes === undefined} @@ -238,6 +278,8 @@ const TimerPicker = forwardRef((props, ref) => onDurationChange={setSelectedMinutes} padNumbersWithZero={padMinutesWithZero} padWithNItems={safePadWithNItems} + pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "minutes")} + pickerLabelGap={resolvePerColumn(pickerLabelGap, "minutes")} repeatNumbersNTimes={repeatMinuteNumbersNTimes} repeatNumbersNTimesNotExplicitlySet={props?.repeatMinuteNumbersNTimes === undefined} selectedValue={selectedMinutes} @@ -262,6 +304,8 @@ const TimerPicker = forwardRef((props, ref) => onDurationChange={setSelectedSeconds} padNumbersWithZero={padSecondsWithZero} padWithNItems={safePadWithNItems} + pickerColumnWidth={resolvePerColumn(pickerColumnWidth, "seconds")} + pickerLabelGap={resolvePerColumn(pickerLabelGap, "seconds")} repeatNumbersNTimes={repeatSecondNumbersNTimes} repeatNumbersNTimesNotExplicitlySet={props?.repeatSecondNumbersNTimes === undefined} selectedValue={selectedSeconds} diff --git a/src/components/TimerPicker/styles.ts b/src/components/TimerPicker/styles.ts index ba1c7a4..cf4da5f 100644 --- a/src/components/TimerPicker/styles.ts +++ b/src/components/TimerPicker/styles.ts @@ -1,6 +1,10 @@ import { StyleSheet } from "react-native"; import type { TextStyle, ViewStyle } from "react-native"; +export type PickerColumn = "days" | "hours" | "minutes" | "seconds"; + +export type PerColumnValue = number | Partial>; + export interface CustomTimerPickerStyles { backgroundColor?: string; disabledPickerContainer?: ViewStyle; @@ -8,15 +12,18 @@ export interface CustomTimerPickerStyles { durationScrollFlatList?: ViewStyle; durationScrollFlatListContainer?: ViewStyle; durationScrollFlatListContentContainer?: ViewStyle; + /** @deprecated Use pickerLabelGap instead. Will be removed in a future version. */ labelOffsetPercentage?: number; pickerAmPmContainer?: ViewStyle; pickerAmPmLabel?: TextStyle; + pickerColumnWidth?: PerColumnValue; pickerContainer?: ViewStyle & { backgroundColor?: string }; pickerGradientOverlay?: ViewStyle; pickerItem?: TextStyle; pickerItemContainer?: ViewStyle & { height?: number }; pickerLabel?: TextStyle; pickerLabelContainer?: ViewStyle; + pickerLabelGap?: PerColumnValue; selectedPickerItem?: TextStyle; text?: TextStyle; theme?: "light" | "dark"; @@ -49,9 +56,13 @@ export const generateStyles = (customStyles: CustomTimerPickerStyles | undefined const pickerLabelVerticalOffset = pickerItemFontSize - pickerLabelFontSize - 1; const pickerAmPmVerticalOffset = pickerItemFontSize - pickerAmPmFontSize - 1; - // The label is absolutely positioned, so we need to offset it for it to appear - // in the correct position. We offset it to the left of the container so that - // the width of the label doesn't impact its position. + // Determine whether to use the legacy percentage-based label positioning. + // The new pixel-based system (pickerLabelGap) is the default. + // The old system is only used when labelOffsetPercentage is explicitly set + // without pickerLabelGap. + const useLegacyLabelPosition = + customStyles?.labelOffsetPercentage != null && customStyles?.pickerLabelGap == null; + const extraLabelOffsetPercentage = customStyles?.labelOffsetPercentage ?? 8; const baseLeftOffsetPercentage = 70; const labelOffsetPercentage = baseLeftOffsetPercentage + extraLabelOffsetPercentage; @@ -85,10 +96,12 @@ export const generateStyles = (customStyles: CustomTimerPickerStyles | undefined pickerAmPmContainer: { bottom: 0, justifyContent: "center", - left: `${labelOffsetPercentage}%`, marginTop: pickerAmPmVerticalOffset, position: "absolute", top: 0, + // Only apply percentage-based left when using legacy positioning. + // The new pixel-based positioning is applied per-column in PickerItem. + ...(useLegacyLabelPosition ? { left: `${labelOffsetPercentage}%` } : undefined), ...customStyles?.pickerLabelContainer, ...customStyles?.pickerAmPmContainer, }, @@ -137,10 +150,12 @@ export const generateStyles = (customStyles: CustomTimerPickerStyles | undefined pickerLabelContainer: { bottom: 0, justifyContent: "center", - left: `${labelOffsetPercentage}%`, marginTop: pickerLabelVerticalOffset, position: "absolute", top: 0, + // Only apply percentage-based left when using legacy positioning. + // The new pixel-based positioning is applied per-column in DurationScroll. + ...(useLegacyLabelPosition ? { left: `${labelOffsetPercentage}%` } : undefined), ...customStyles?.pickerLabelContainer, }, selectedPickerItem: { diff --git a/src/components/TimerPickerModal/TimerPickerModal.tsx b/src/components/TimerPickerModal/TimerPickerModal.tsx index 31aa195..93b7e9a 100644 --- a/src/components/TimerPickerModal/TimerPickerModal.tsx +++ b/src/components/TimerPickerModal/TimerPickerModal.tsx @@ -13,9 +13,27 @@ import { getSafeInitialValue } from "../../utils/getSafeInitialValue"; import Modal from "../Modal"; import TimerPicker from "../TimerPicker"; import type { TimerPickerRef } from "../TimerPicker"; +import type { PerColumnValue, PickerColumn } from "../TimerPicker/styles"; import { generateStyles } from "./styles"; import type { TimerPickerModalRef, TimerPickerModalProps } from "./types"; +const DEFAULT_COLUMN_WIDTH = 58; + +const resolveColumnWidth = ( + pickerColumnWidth: PerColumnValue | undefined, + column: PickerColumn +): number => { + if (pickerColumnWidth == null) { + return DEFAULT_COLUMN_WIDTH; + } + + if (typeof pickerColumnWidth === "number") { + return pickerColumnWidth; + } + + return pickerColumnWidth[column] ?? DEFAULT_COLUMN_WIDTH; +}; + const TimerPickerModal = forwardRef((props, ref) => { const { buttonContainerProps, @@ -26,6 +44,10 @@ const TimerPickerModal = forwardRef( containerProps, contentContainerProps, hideCancelButton = false, + hideDays = true, + hideHours = false, + hideMinutes = false, + hideSeconds = false, initialValue, modalProps, modalTitle, @@ -39,8 +61,17 @@ const TimerPickerModal = forwardRef( ...otherProps } = props; + const pickerColumnWidth = customStyles?.pickerColumnWidth; + + const totalColumnWidth = + (!hideDays ? resolveColumnWidth(pickerColumnWidth, "days") : 0) + + (!hideHours ? resolveColumnWidth(pickerColumnWidth, "hours") : 0) + + (!hideMinutes ? resolveColumnWidth(pickerColumnWidth, "minutes") : 0) + + (!hideSeconds ? resolveColumnWidth(pickerColumnWidth, "seconds") : 0); + const styles = generateStyles(customStyles, { hasModalTitle: Boolean(modalTitle), + totalColumnWidth, }); const timerPickerRef = useRef(null); @@ -147,6 +178,10 @@ const TimerPickerModal = forwardRef( ) : null} { const { @@ -35,6 +36,11 @@ export const generateStyles = ( ...customTimerPickerStyles } = customStyles ?? {}; + const totalColumnWidth = variables.totalColumnWidth; + const modalPadding = (customContentContainerStyle?.paddingHorizontal as number) ?? 20; + const labelOverhang = 20; + const computedWidth = Math.max(116, totalColumnWidth + modalPadding * 2 + labelOverhang); + return StyleSheet.create({ button: { borderRadius: 10, @@ -83,6 +89,7 @@ export const generateStyles = ( justifyContent: "center", overflow: "hidden", paddingHorizontal: 20, + width: computedWidth, ...customContentContainerStyle, }, modalTitle: { From 5fb7c234f4fa32861d30e462a1da41990bf20efe Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 25 Feb 2026 20:30:37 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9D=20Update=20docs=20to=20reflect?= =?UTF-8?q?=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 600c87f..18970b1 100644 --- a/README.md +++ b/README.md @@ -464,14 +464,13 @@ return ( pickerItem: { fontSize: 34, }, - pickerLabelContainer: { - marginTop: -4, - right: 0, - left: undefined, - }, pickerLabel: { fontSize: 32, }, + pickerLabelContainer: { + marginTop: -4, + }, + pickerLabelGap: 23, pickerContainer: { paddingHorizontal: 50, }, @@ -502,7 +501,7 @@ return ( secondLabel="sec" styles={{ theme: "light", - labelOffsetPercentage: 0, + pickerLabelGap: 8, pickerItem: { fontSize: 34, }, @@ -582,14 +581,18 @@ return ( #### Custom Styles 👗 -The component should look good straight out of the box, but you can use these styles to make it fit in with your App's theme: +The component should look good straight out of the box, but you can use these easy styles to make it fit in with your App's theme: + +| Style Prop | Description | Type | +| :---------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------: | +| theme | Theme of the component | "light" \| "dark" | +| backgroundColor | Main background color | string | +| text | Base text style | TextStyle | +| pickerLabelGap | Pixel gap between the label and the picker number column. Can be a single number or a per-column object (e.g. `{ hours: 10, minutes: 8 }`). Default: `6` | `PerColumnValue`\* | +| pickerColumnWidth | Width of individual picker columns in pixels. Can be a single number or a per-column object. Overrides default flex-based sizing when set | `PerColumnValue`\* | +| labelOffsetPercentage **(DEPRECATED)** | Percentage offset for horizontal label positioning relative to the picker (use `pickerLabelGap` instead) | number | -| Style Prop | Description | Type | -| :-------------------: | :----------------------------------------------------------------------- | :---------------: | -| theme | Theme of the component | "light" \| "dark" | -| backgroundColor | Main background color | string | -| text | Base text style | TextStyle | -| labelOffsetPercentage | Percentage offset for horizonal label positioning relative to the picker | number | +**\*`PerColumnValue` type:** `number | { days?: number, hours?: number, minutes?: number, seconds?: number }` — pass a single number for all columns, or an object to set values per column. Omitted columns use the default. For deeper style customization, you can supply the following custom styles to adjust the component in any way. These are applied on top of the default styling so take a look at those [styles](src/components/TimerPicker/styles.ts) if something isn't adjusting in the way you'd expect. From d02544f547de93dcb95f296c78d4eed7405d4af1 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 25 Feb 2026 20:30:55 +0000 Subject: [PATCH 3/5] =?UTF-8?q?=F0=9F=A7=91=E2=80=8D=F0=9F=92=BB=20Add=20p?= =?UTF-8?q?re-commit=20hook=20that=20lints/formats?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .prettierignore | 1 + package.json | 16 ++- yarn.lock | 259 +++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 274 insertions(+), 2 deletions(-) diff --git a/.prettierignore b/.prettierignore index 9cfabee..80f1432 100644 --- a/.prettierignore +++ b/.prettierignore @@ -4,3 +4,4 @@ node_modules .yarn examples/example-bare/android examples/example-bare/ios +*.md diff --git a/package.json b/package.json index 90fa1ce..82c991c 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "lint:fix": "eslint src/ examples/ --fix", "format": "prettier --check ./src ./examples", "format:fix": "prettier --write ./src ./examples", - "prepare": "yarn build" + "prepare": "simple-git-hooks" }, "homepage": "https://github.com/troberts-28/react-native-timer-picker", "bugs": { @@ -108,12 +108,14 @@ "eslint-plugin-react": "^7.37.5", "eslint-plugin-react-hooks": "^5.2.0", "jest": "^29.0.0", + "lint-staged": "^16.2.7", "metro-react-native-babel-preset": "^0.71.1", "prettier": "2.8.8", "react": "18.2.0", "react-native": "0.72.0", "react-native-builder-bob": "^0.18.3", "react-test-renderer": "18.2.0", + "simple-git-hooks": "^2.13.1", "typescript": "~5.8.0", "typescript-eslint": "^8.33.0" }, @@ -126,6 +128,18 @@ "typescript" ] }, + "simple-git-hooks": { + "pre-commit": "npx lint-staged" + }, + "lint-staged": { + "*.{ts,tsx,js,jsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{json}": [ + "prettier --write" + ] + }, "eslintIgnore": [ "node_modules/", "dist/" diff --git a/yarn.lock b/yarn.lock index 7564f86..eda073a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4450,6 +4450,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^7.0.0": + version: 7.3.0 + resolution: "ansi-escapes@npm:7.3.0" + dependencies: + environment: "npm:^1.0.0" + checksum: 10c0/068961d99f0ef28b661a4a9f84a5d645df93ccf3b9b93816cc7d46bbe1913321d4cdf156bb842a4e1e4583b7375c631fa963efb43001c4eb7ff9ab8f78fc0679 + languageName: node + linkType: hard + "ansi-fragments@npm:^0.2.1": version: 0.2.1 resolution: "ansi-fragments@npm:0.2.1" @@ -4514,6 +4523,13 @@ __metadata: languageName: node linkType: hard +"ansi-styles@npm:^6.2.1": + version: 6.2.3 + resolution: "ansi-styles@npm:6.2.3" + checksum: 10c0/23b8a4ce14e18fb854693b95351e286b771d23d8844057ed2e7d083cd3e708376c3323707ec6a24365f7d7eda3ca00327fe04092e29e551499ec4c8b7bfac868 + languageName: node + linkType: hard + "any-promise@npm:^1.0.0": version: 1.3.0 resolution: "any-promise@npm:1.3.0" @@ -5385,6 +5401,15 @@ __metadata: languageName: node linkType: hard +"cli-cursor@npm:^5.0.0": + version: 5.0.0 + resolution: "cli-cursor@npm:5.0.0" + dependencies: + restore-cursor: "npm:^5.0.0" + checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220 + languageName: node + linkType: hard + "cli-spinners@npm:^2.0.0, cli-spinners@npm:^2.5.0": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" @@ -5392,6 +5417,16 @@ __metadata: languageName: node linkType: hard +"cli-truncate@npm:^5.0.0": + version: 5.1.1 + resolution: "cli-truncate@npm:5.1.1" + dependencies: + slice-ansi: "npm:^7.1.0" + string-width: "npm:^8.0.0" + checksum: 10c0/3842920829a62f3e041ce39199050c42706c3c9c756a4efc8b86d464e102d1fa031d8b1b9b2e3bb36e1017c763558275472d031bdc884c1eff22a2f20e4f6b0a + languageName: node + linkType: hard + "cliui@npm:^6.0.0": version: 6.0.0 resolution: "cliui@npm:6.0.0" @@ -5485,6 +5520,13 @@ __metadata: languageName: node linkType: hard +"colorette@npm:^2.0.20": + version: 2.0.20 + resolution: "colorette@npm:2.0.20" + checksum: 10c0/e94116ff33b0ff56f3b83b9ace895e5bf87c2a7a47b3401b8c3f3226e050d5ef76cf4072fb3325f9dc24d1698f9b730baf4e05eeaf861d74a1883073f4c98a40 + languageName: node + linkType: hard + "command-exists@npm:^1.2.8": version: 1.2.9 resolution: "command-exists@npm:1.2.9" @@ -5499,6 +5541,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^14.0.2": + version: 14.0.3 + resolution: "commander@npm:14.0.3" + checksum: 10c0/755652564bbf56ff2ff083313912b326450d3f8d8c85f4b71416539c9a05c3c67dbd206821ca72635bf6b160e2afdefcb458e86b317827d5cb333b69ce7f1a24 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -6021,6 +6070,13 @@ __metadata: languageName: node linkType: hard +"emoji-regex@npm:^10.3.0": + version: 10.6.0 + resolution: "emoji-regex@npm:10.6.0" + checksum: 10c0/1e4aa097bb007301c3b4b1913879ae27327fdc48e93eeefefe3b87e495eb33c5af155300be951b4349ff6ac084f4403dc9eff970acba7c1c572d89396a9a32d7 + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -6099,6 +6155,13 @@ __metadata: languageName: node linkType: hard +"environment@npm:^1.0.0": + version: 1.1.0 + resolution: "environment@npm:1.1.0" + checksum: 10c0/fb26434b0b581ab397039e51ff3c92b34924a98b2039dcb47e41b7bca577b9dbf134a8eadb364415c74464b682e2d3afe1a4c0eb9873dc44ea814c5d3103331d + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -6640,6 +6703,13 @@ __metadata: languageName: node linkType: hard +"eventemitter3@npm:^5.0.1": + version: 5.0.4 + resolution: "eventemitter3@npm:5.0.4" + checksum: 10c0/575b8cac8d709e1473da46f8f15ef311b57ff7609445a7c71af5cd42598583eee6f098fa7a593e30f27e94b8865642baa0689e8fa97c016f742abdb3b1bf6d9a + languageName: node + linkType: hard + "example-bare@workspace:examples/example-bare": version: 0.0.0-use.local resolution: "example-bare@workspace:examples/example-bare" @@ -7272,6 +7342,13 @@ __metadata: languageName: node linkType: hard +"get-east-asian-width@npm:^1.0.0, get-east-asian-width@npm:^1.3.1, get-east-asian-width@npm:^1.5.0": + version: 1.5.0 + resolution: "get-east-asian-width@npm:1.5.0" + checksum: 10c0/bff8bbc8d81790b9477f7aa55b1806b9f082a8dc1359fff7bd8b96939622c86b729685afc2bfeb22def1fc6ef1e5228e4d87dd4e6da60bc43a5edfb03c4ee167 + languageName: node + linkType: hard + "get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0": version: 1.3.0 resolution: "get-intrinsic@npm:1.3.0" @@ -8004,6 +8081,15 @@ __metadata: languageName: node linkType: hard +"is-fullwidth-code-point@npm:^5.0.0": + version: 5.1.0 + resolution: "is-fullwidth-code-point@npm:5.1.0" + dependencies: + get-east-asian-width: "npm:^1.3.1" + checksum: 10c0/c1172c2e417fb73470c56c431851681591f6a17233603a9e6f94b7ba870b2e8a5266506490573b607fb1081318589372034aa436aec07b465c2029c0bc7f07a4 + languageName: node + linkType: hard + "is-generator-fn@npm:^2.0.0": version: 2.1.0 resolution: "is-generator-fn@npm:2.1.0" @@ -9269,6 +9355,37 @@ __metadata: languageName: node linkType: hard +"lint-staged@npm:^16.2.7": + version: 16.2.7 + resolution: "lint-staged@npm:16.2.7" + dependencies: + commander: "npm:^14.0.2" + listr2: "npm:^9.0.5" + micromatch: "npm:^4.0.8" + nano-spawn: "npm:^2.0.0" + pidtree: "npm:^0.6.0" + string-argv: "npm:^0.3.2" + yaml: "npm:^2.8.1" + bin: + lint-staged: bin/lint-staged.js + checksum: 10c0/9a677c21a8112d823ae5bc565ba2c9e7b803786f2a021c46827a55fe44ed59def96edb24fc99c06a2545cdbbf366022ad82addcb3bf60c712f3b98ef92069717 + languageName: node + linkType: hard + +"listr2@npm:^9.0.5": + version: 9.0.5 + resolution: "listr2@npm:9.0.5" + dependencies: + cli-truncate: "npm:^5.0.0" + colorette: "npm:^2.0.20" + eventemitter3: "npm:^5.0.1" + log-update: "npm:^6.1.0" + rfdc: "npm:^1.4.1" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/46448d1ba0addc9d71aeafd05bb8e86ded9641ccad930ac302c2bd2ad71580375604743e18586fcb8f11906edf98e8e17fca75ba0759947bf275d381f68e311d + languageName: node + linkType: hard + "locate-path@npm:^3.0.0": version: 3.0.0 resolution: "locate-path@npm:3.0.0" @@ -9344,6 +9461,19 @@ __metadata: languageName: node linkType: hard +"log-update@npm:^6.1.0": + version: 6.1.0 + resolution: "log-update@npm:6.1.0" + dependencies: + ansi-escapes: "npm:^7.0.0" + cli-cursor: "npm:^5.0.0" + slice-ansi: "npm:^7.1.0" + strip-ansi: "npm:^7.1.0" + wrap-ansi: "npm:^9.0.0" + checksum: 10c0/4b350c0a83d7753fea34dcac6cd797d1dc9603291565de009baa4aa91c0447eab0d3815a05c8ec9ac04fdfffb43c82adcdb03ec1fceafd8518e1a8c1cff4ff89 + languageName: node + linkType: hard + "logkitty@npm:^0.7.1": version: 0.7.1 resolution: "logkitty@npm:0.7.1" @@ -10360,6 +10490,13 @@ __metadata: languageName: node linkType: hard +"mimic-function@npm:^5.0.0": + version: 5.0.1 + resolution: "mimic-function@npm:5.0.1" + checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d + languageName: node + linkType: hard + "min-indent@npm:^1.0.0": version: 1.0.1 resolution: "min-indent@npm:1.0.1" @@ -10558,6 +10695,13 @@ __metadata: languageName: node linkType: hard +"nano-spawn@npm:^2.0.0": + version: 2.0.0 + resolution: "nano-spawn@npm:2.0.0" + checksum: 10c0/d00f9b5739f86e28cb732ffd774793e110810cded246b8393c75c4f22674af47f98ee37b19f022ada2d8c9425f800e841caa0662fbff4c0930a10e39339fb366 + languageName: node + linkType: hard + "nanoid@npm:^3.3.7": version: 3.3.11 resolution: "nanoid@npm:3.3.11" @@ -10902,6 +11046,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^7.0.0": + version: 7.0.0 + resolution: "onetime@npm:7.0.0" + dependencies: + mimic-function: "npm:^5.0.0" + checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221 + languageName: node + linkType: hard + "open@npm:^6.2.0": version: 6.4.0 resolution: "open@npm:6.4.0" @@ -11207,6 +11360,15 @@ __metadata: languageName: node linkType: hard +"pidtree@npm:^0.6.0": + version: 0.6.0 + resolution: "pidtree@npm:0.6.0" + bin: + pidtree: bin/pidtree.js + checksum: 10c0/0829ec4e9209e230f74ebf4265f5ccc9ebfb488334b525cb13f86ff801dca44b362c41252cd43ae4d7653a10a5c6ab3be39d2c79064d6895e0d78dc50a5ed6e9 + languageName: node + linkType: hard + "pify@npm:^4.0.1": version: 4.0.1 resolution: "pify@npm:4.0.1" @@ -11653,12 +11815,14 @@ __metadata: eslint-plugin-react: "npm:^7.37.5" eslint-plugin-react-hooks: "npm:^5.2.0" jest: "npm:^29.0.0" + lint-staged: "npm:^16.2.7" metro-react-native-babel-preset: "npm:^0.71.1" prettier: "npm:2.8.8" react: "npm:18.2.0" react-native: "npm:0.72.0" react-native-builder-bob: "npm:^0.18.3" react-test-renderer: "npm:18.2.0" + simple-git-hooks: "npm:^2.13.1" typescript: "npm:~5.8.0" typescript-eslint: "npm:^8.33.0" peerDependencies: @@ -12194,6 +12358,16 @@ __metadata: languageName: node linkType: hard +"restore-cursor@npm:^5.0.0": + version: 5.1.0 + resolution: "restore-cursor@npm:5.1.0" + dependencies: + onetime: "npm:^7.0.0" + signal-exit: "npm:^4.1.0" + checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60 + languageName: node + linkType: hard + "retry@npm:^0.12.0": version: 0.12.0 resolution: "retry@npm:0.12.0" @@ -12208,6 +12382,13 @@ __metadata: languageName: node linkType: hard +"rfdc@npm:^1.4.1": + version: 1.4.1 + resolution: "rfdc@npm:1.4.1" + checksum: 10c0/4614e4292356cafade0b6031527eea9bc90f2372a22c012313be1dcc69a3b90c7338158b414539be863fa95bfcb2ddcd0587be696841af4e6679d85e62c060c7 + languageName: node + linkType: hard + "rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" @@ -12575,13 +12756,22 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^4.0.1": +"signal-exit@npm:^4.0.1, signal-exit@npm:^4.1.0": version: 4.1.0 resolution: "signal-exit@npm:4.1.0" checksum: 10c0/41602dce540e46d599edba9d9860193398d135f7ff72cab629db5171516cfae628d21e7bfccde1bbfdf11c48726bc2a6d1a8fb8701125852fbfda7cf19c6aa83 languageName: node linkType: hard +"simple-git-hooks@npm:^2.13.1": + version: 2.13.1 + resolution: "simple-git-hooks@npm:2.13.1" + bin: + simple-git-hooks: cli.js + checksum: 10c0/09631ce2cce5c6cf91f5a990bbefce88f95589ce112a601e2745b8e10ea951668c13f89b3fab2044506646380dcc91b618bb400d35142873a1990c4528b456bb + languageName: node + linkType: hard + "simple-plist@npm:^1.1.0": version: 1.4.0 resolution: "simple-plist@npm:1.4.0" @@ -12618,6 +12808,16 @@ __metadata: languageName: node linkType: hard +"slice-ansi@npm:^7.1.0": + version: 7.1.2 + resolution: "slice-ansi@npm:7.1.2" + dependencies: + ansi-styles: "npm:^6.2.1" + is-fullwidth-code-point: "npm:^5.0.0" + checksum: 10c0/36742f2eb0c03e2e81a38ed14d13a64f7b732fe38c3faf96cce0599788a345011e840db35f1430ca606ea3f8db2abeb92a8d25c2753a819e3babaa10c2e289a2 + languageName: node + linkType: hard + "slugify@npm:^1.3.4, slugify@npm:^1.6.6": version: 1.6.6 resolution: "slugify@npm:1.6.6" @@ -12770,6 +12970,13 @@ __metadata: languageName: node linkType: hard +"string-argv@npm:^0.3.2": + version: 0.3.2 + resolution: "string-argv@npm:0.3.2" + checksum: 10c0/75c02a83759ad1722e040b86823909d9a2fc75d15dd71ec4b537c3560746e33b5f5a07f7332d1e3f88319909f82190843aa2f0a0d8c8d591ec08e93d5b8dec82 + languageName: node + linkType: hard + "string-length@npm:^4.0.1": version: 4.0.2 resolution: "string-length@npm:4.0.2" @@ -12809,6 +13016,27 @@ __metadata: languageName: node linkType: hard +"string-width@npm:^7.0.0": + version: 7.2.0 + resolution: "string-width@npm:7.2.0" + dependencies: + emoji-regex: "npm:^10.3.0" + get-east-asian-width: "npm:^1.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9 + languageName: node + linkType: hard + +"string-width@npm:^8.0.0": + version: 8.2.0 + resolution: "string-width@npm:8.2.0" + dependencies: + get-east-asian-width: "npm:^1.5.0" + strip-ansi: "npm:^7.1.2" + checksum: 10c0/d8915428b43519b0f494da6590dbe4491857d8a12e40250e50fc01fbb616ffd8400a436bbe25712255ee129511fe0414c49d3b6b9627e2bc3a33dcec1d2eda02 + languageName: node + linkType: hard + "string.prototype.matchall@npm:^4.0.12": version: 4.0.12 resolution: "string.prototype.matchall@npm:4.0.12" @@ -12923,6 +13151,15 @@ __metadata: languageName: node linkType: hard +"strip-ansi@npm:^7.1.0, strip-ansi@npm:^7.1.2": + version: 7.1.2 + resolution: "strip-ansi@npm:7.1.2" + dependencies: + ansi-regex: "npm:^6.0.1" + checksum: 10c0/0d6d7a023de33368fd042aab0bf48f4f4077abdfd60e5393e73c7c411e85e1b3a83507c11af2e656188511475776215df9ca589b4da2295c9455cc399ce1858b + languageName: node + linkType: hard + "strip-bom@npm:^4.0.0": version: 4.0.0 resolution: "strip-bom@npm:4.0.0" @@ -13808,6 +14045,17 @@ __metadata: languageName: node linkType: hard +"wrap-ansi@npm:^9.0.0": + version: 9.0.2 + resolution: "wrap-ansi@npm:9.0.2" + dependencies: + ansi-styles: "npm:^6.2.1" + string-width: "npm:^7.0.0" + strip-ansi: "npm:^7.1.0" + checksum: 10c0/3305839b9a0d6fb930cb63a52f34d3936013d8b0682ff3ec133c9826512620f213800ffa19ea22904876d5b7e9a3c1f40682f03597d986a4ca881fa7b033688c + languageName: node + linkType: hard + "wrappy@npm:1": version: 1.0.2 resolution: "wrappy@npm:1.0.2" @@ -13976,6 +14224,15 @@ __metadata: languageName: node linkType: hard +"yaml@npm:^2.8.1": + version: 2.8.2 + resolution: "yaml@npm:2.8.2" + bin: + yaml: bin.mjs + checksum: 10c0/703e4dc1e34b324aa66876d63618dcacb9ed49f7e7fe9b70f1e703645be8d640f68ab84f12b86df8ac960bac37acf5513e115de7c970940617ce0343c8c9cd96 + languageName: node + linkType: hard + "yargs-parser@npm:^18.1.2": version: 18.1.3 resolution: "yargs-parser@npm:18.1.3" From cc34507d41675366daca2a18361896bb3e7e9e21 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 25 Feb 2026 20:32:10 +0000 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=94=96=20bump=20version?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 82c991c..7ace772 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,7 @@ "url": "https://github.com/troberts-28" }, "license": "MIT", - "version": "2.5.0", + "version": "2.6.0", "main": "dist/commonjs/index.js", "module": "dist/module/index.js", "types": "dist/typescript/index.d.ts", From 57dccdeebd614fe0c08067a916995bb5c284cb3e Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 25 Feb 2026 20:46:09 +0000 Subject: [PATCH 5/5] =?UTF-8?q?=F0=9F=93=9D=20Make=20use=20of=20new=20styl?= =?UTF-8?q?es=20in=20examples?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 ++++++ examples/example-bare/App.tsx | 5 +++++ examples/example-expo/App.tsx | 6 +++++- src/components/TimerPickerModal/TimerPickerModal.tsx | 4 +--- src/components/TimerPickerModal/styles.ts | 7 ++++++- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 18970b1..4c27912 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Works with Expo and bare React Native apps ✅ Includes iOS-style haptic and audio feedback 🍏 +- [React Native Timer Picker ⏰🕰️⏳](#react-native-timer-picker-️) - [Demos 📱](#demos-) - [Installation 🚀](#installation-) - [Peer Dependencies 👶](#peer-dependencies-) @@ -289,6 +290,9 @@ return ( setIsVisible={setShowPicker} styles={{ theme: "light", + pickerColumnWidth: { + hours: 90, + }, }} use12HourPicker visible={showPicker} @@ -426,6 +430,8 @@ return ( setIsVisible={setShowPicker} styles={{ theme: "dark", + pickerLabelGap: 10, + text: { fontSize: 18 }, }} visible={showPicker} /> diff --git a/examples/example-bare/App.tsx b/examples/example-bare/App.tsx index 680c25d..8502d40 100644 --- a/examples/example-bare/App.tsx +++ b/examples/example-bare/App.tsx @@ -173,6 +173,9 @@ export default function App() { pickerFeedback={pickerFeedback} setIsVisible={setShowPickerExample2} styles={{ + pickerColumnWidth: { + hours: 90, + }, theme: "light", }} use12HourPicker @@ -217,6 +220,8 @@ export default function App() { pickerFeedback={pickerFeedback} setIsVisible={setShowPickerExample3} styles={{ + pickerLabelGap: 10, + text: { fontSize: 18 }, theme: "dark", }} visible={showPickerExample3} diff --git a/examples/example-expo/App.tsx b/examples/example-expo/App.tsx index 2864df4..4542332 100644 --- a/examples/example-expo/App.tsx +++ b/examples/example-expo/App.tsx @@ -210,7 +210,11 @@ export default function App() { }} pickerFeedback={pickerFeedback} setIsVisible={setShowPickerExample3} - styles={{ theme: "dark" }} + styles={{ + pickerLabelGap: 10, + text: { fontWeight: "bold" }, + theme: "dark", + }} visible={showPickerExample3} /> diff --git a/src/components/TimerPickerModal/TimerPickerModal.tsx b/src/components/TimerPickerModal/TimerPickerModal.tsx index 93b7e9a..022568e 100644 --- a/src/components/TimerPickerModal/TimerPickerModal.tsx +++ b/src/components/TimerPickerModal/TimerPickerModal.tsx @@ -14,11 +14,9 @@ import Modal from "../Modal"; import TimerPicker from "../TimerPicker"; import type { TimerPickerRef } from "../TimerPicker"; import type { PerColumnValue, PickerColumn } from "../TimerPicker/styles"; -import { generateStyles } from "./styles"; +import { DEFAULT_COLUMN_WIDTH, generateStyles } from "./styles"; import type { TimerPickerModalRef, TimerPickerModalProps } from "./types"; -const DEFAULT_COLUMN_WIDTH = 58; - const resolveColumnWidth = ( pickerColumnWidth: PerColumnValue | undefined, column: PickerColumn diff --git a/src/components/TimerPickerModal/styles.ts b/src/components/TimerPickerModal/styles.ts index 3dab02a..45208b6 100644 --- a/src/components/TimerPickerModal/styles.ts +++ b/src/components/TimerPickerModal/styles.ts @@ -3,6 +3,8 @@ import type { DimensionValue, TextStyle, ViewStyle } from "react-native"; import type { CustomTimerPickerStyles } from "../TimerPicker/styles"; +export const DEFAULT_COLUMN_WIDTH = 70; + export interface CustomTimerPickerModalStyles extends CustomTimerPickerStyles { button?: TextStyle; buttonContainer?: ViewStyle; @@ -39,7 +41,10 @@ export const generateStyles = ( const totalColumnWidth = variables.totalColumnWidth; const modalPadding = (customContentContainerStyle?.paddingHorizontal as number) ?? 20; const labelOverhang = 20; - const computedWidth = Math.max(116, totalColumnWidth + modalPadding * 2 + labelOverhang); + const computedWidth = Math.max( + DEFAULT_COLUMN_WIDTH * 2, + totalColumnWidth + modalPadding * 2 + labelOverhang + ); return StyleSheet.create({ button: {