Skip to content

Commit e65cf57

Browse files
authored
Merge pull request #78 from troberts-28/release/v2.6.0
Release v2.6.0
2 parents 7c63c61 + 57dccde commit e65cf57

13 files changed

Lines changed: 460 additions & 35 deletions

File tree

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ node_modules
44
.yarn
55
examples/example-bare/android
66
examples/example-bare/ios
7+
*.md

README.md

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Works with Expo and bare React Native apps ✅
1313

1414
Includes iOS-style haptic and audio feedback 🍏
1515

16+
- [React Native Timer Picker ⏰🕰️⏳](#react-native-timer-picker-️)
1617
- [Demos 📱](#demos-)
1718
- [Installation 🚀](#installation-)
1819
- [Peer Dependencies 👶](#peer-dependencies-)
@@ -289,6 +290,9 @@ return (
289290
setIsVisible={setShowPicker}
290291
styles={{
291292
theme: "light",
293+
pickerColumnWidth: {
294+
hours: 90,
295+
},
292296
}}
293297
use12HourPicker
294298
visible={showPicker}
@@ -426,6 +430,8 @@ return (
426430
setIsVisible={setShowPicker}
427431
styles={{
428432
theme: "dark",
433+
pickerLabelGap: 10,
434+
text: { fontSize: 18 },
429435
}}
430436
visible={showPicker}
431437
/>
@@ -464,14 +470,13 @@ return (
464470
pickerItem: {
465471
fontSize: 34,
466472
},
467-
pickerLabelContainer: {
468-
marginTop: -4,
469-
right: 0,
470-
left: undefined,
471-
},
472473
pickerLabel: {
473474
fontSize: 32,
474475
},
476+
pickerLabelContainer: {
477+
marginTop: -4,
478+
},
479+
pickerLabelGap: 23,
475480
pickerContainer: {
476481
paddingHorizontal: 50,
477482
},
@@ -502,7 +507,7 @@ return (
502507
secondLabel="sec"
503508
styles={{
504509
theme: "light",
505-
labelOffsetPercentage: 0,
510+
pickerLabelGap: 8,
506511
pickerItem: {
507512
fontSize: 34,
508513
},
@@ -582,14 +587,18 @@ return (
582587

583588
#### Custom Styles 👗
584589

585-
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:
590+
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:
591+
592+
| Style Prop | Description | Type |
593+
| :---------------------------------------: | :------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------: |
594+
| theme | Theme of the component | "light" \| "dark" |
595+
| backgroundColor | Main background color | string |
596+
| text | Base text style | TextStyle |
597+
| 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`\* |
598+
| 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`\* |
599+
| labelOffsetPercentage **(DEPRECATED)** | Percentage offset for horizontal label positioning relative to the picker (use `pickerLabelGap` instead) | number |
586600

587-
| Style Prop | Description | Type |
588-
| :-------------------: | :----------------------------------------------------------------------- | :---------------: |
589-
| theme | Theme of the component | "light" \| "dark" |
590-
| backgroundColor | Main background color | string |
591-
| text | Base text style | TextStyle |
592-
| labelOffsetPercentage | Percentage offset for horizonal label positioning relative to the picker | number |
601+
**\*`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.
593602

594603
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.
595604

examples/example-bare/App.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,9 @@ export default function App() {
173173
pickerFeedback={pickerFeedback}
174174
setIsVisible={setShowPickerExample2}
175175
styles={{
176+
pickerColumnWidth: {
177+
hours: 90,
178+
},
176179
theme: "light",
177180
}}
178181
use12HourPicker
@@ -217,6 +220,8 @@ export default function App() {
217220
pickerFeedback={pickerFeedback}
218221
setIsVisible={setShowPickerExample3}
219222
styles={{
223+
pickerLabelGap: 10,
224+
text: { fontSize: 18 },
220225
theme: "dark",
221226
}}
222227
visible={showPickerExample3}
@@ -253,10 +258,9 @@ export default function App() {
253258
fontSize: 32,
254259
},
255260
pickerLabelContainer: {
256-
left: undefined,
257261
marginTop: -4,
258-
right: 0,
259262
},
263+
pickerLabelGap: 23,
260264
theme: "dark",
261265
}}
262266
/>
@@ -275,7 +279,6 @@ export default function App() {
275279
pickerFeedback={pickerFeedback}
276280
secondLabel="sec"
277281
styles={{
278-
labelOffsetPercentage: 0,
279282
pickerContainer: {
280283
paddingHorizontal: 50,
281284
},
@@ -285,6 +288,7 @@ export default function App() {
285288
pickerLabel: {
286289
fontSize: 26,
287290
},
291+
pickerLabelGap: 8,
288292
theme: "light",
289293
}}
290294
/>

examples/example-expo/App.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,6 @@ export default function App() {
119119
</TouchableOpacity>
120120
<TimerPickerModal
121121
closeOnOverlayPress
122-
hideCancelButton
123122
LinearGradient={LinearGradient}
124123
modalProps={{ overlayOpacity: 0.2 }}
125124
modalTitle="Set Alarm"
@@ -166,7 +165,12 @@ export default function App() {
166165
}}
167166
pickerFeedback={pickerFeedback}
168167
setIsVisible={setShowPickerExample2}
169-
styles={{ theme: "light" }}
168+
styles={{
169+
theme: "light",
170+
pickerColumnWidth: {
171+
hours: 90,
172+
},
173+
}}
170174
use12HourPicker
171175
visible={showPickerExample2}
172176
/>
@@ -206,7 +210,11 @@ export default function App() {
206210
}}
207211
pickerFeedback={pickerFeedback}
208212
setIsVisible={setShowPickerExample3}
209-
styles={{ theme: "dark" }}
213+
styles={{
214+
pickerLabelGap: 10,
215+
text: { fontWeight: "bold" },
216+
theme: "dark",
217+
}}
210218
visible={showPickerExample3}
211219
/>
212220
</View>
@@ -241,10 +249,9 @@ export default function App() {
241249
fontSize: 32,
242250
},
243251
pickerLabelContainer: {
244-
left: undefined,
245252
marginTop: -4,
246-
right: 0,
247253
},
254+
pickerLabelGap: 23,
248255
theme: "dark",
249256
}}
250257
/>
@@ -263,7 +270,6 @@ export default function App() {
263270
pickerFeedback={pickerFeedback}
264271
secondLabel="sec"
265272
styles={{
266-
labelOffsetPercentage: 0,
267273
pickerContainer: {
268274
paddingHorizontal: 50,
269275
},
@@ -273,6 +279,7 @@ export default function App() {
273279
pickerLabel: {
274280
fontSize: 26,
275281
},
282+
pickerLabelGap: 8,
276283
theme: "light",
277284
}}
278285
/>
@@ -350,6 +357,7 @@ export default function App() {
350357
horizontal
351358
onMomentumScrollEnd={onMomentumScrollEnd}
352359
pagingEnabled
360+
showsHorizontalScrollIndicator={false}
353361
>
354362
{renderExample1}
355363
{renderExample2}

package.json

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"url": "https://github.com/troberts-28"
77
},
88
"license": "MIT",
9-
"version": "2.5.0",
9+
"version": "2.6.0",
1010
"main": "dist/commonjs/index.js",
1111
"module": "dist/module/index.js",
1212
"types": "dist/typescript/index.d.ts",
@@ -30,7 +30,7 @@
3030
"lint:fix": "eslint src/ examples/ --fix",
3131
"format": "prettier --check ./src ./examples",
3232
"format:fix": "prettier --write ./src ./examples",
33-
"prepare": "yarn build"
33+
"prepare": "simple-git-hooks"
3434
},
3535
"homepage": "https://github.com/troberts-28/react-native-timer-picker",
3636
"bugs": {
@@ -108,12 +108,14 @@
108108
"eslint-plugin-react": "^7.37.5",
109109
"eslint-plugin-react-hooks": "^5.2.0",
110110
"jest": "^29.0.0",
111+
"lint-staged": "^16.2.7",
111112
"metro-react-native-babel-preset": "^0.71.1",
112113
"prettier": "2.8.8",
113114
"react": "18.2.0",
114115
"react-native": "0.72.0",
115116
"react-native-builder-bob": "^0.18.3",
116117
"react-test-renderer": "18.2.0",
118+
"simple-git-hooks": "^2.13.1",
117119
"typescript": "~5.8.0",
118120
"typescript-eslint": "^8.33.0"
119121
},
@@ -126,6 +128,18 @@
126128
"typescript"
127129
]
128130
},
131+
"simple-git-hooks": {
132+
"pre-commit": "npx lint-staged"
133+
},
134+
"lint-staged": {
135+
"*.{ts,tsx,js,jsx}": [
136+
"eslint --fix",
137+
"prettier --write"
138+
],
139+
"*.{json}": [
140+
"prettier --write"
141+
]
142+
},
129143
"eslintIgnore": [
130144
"node_modules/",
131145
"dist/"

src/components/DurationScroll/DurationScroll.tsx

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,10 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
4545
onDurationChange,
4646
padNumbersWithZero = false,
4747
padWithNItems,
48+
pickerColumnWidth,
4849
pickerFeedback,
4950
pickerGradientOverlayProps,
51+
pickerLabelGap,
5052
pmLabel,
5153
repeatNumbersNTimes = 3,
5254
repeatNumbersNTimesNotExplicitlySet,
@@ -55,6 +57,24 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
5557
testID,
5658
} = props;
5759

60+
const labelPositionStyle = useMemo(() => {
61+
// When the style already has an explicit `left` (from legacy percentage system or
62+
// user override), don't apply pixel-based positioning.
63+
if (styles.pickerLabelContainer.left != null) {
64+
return undefined;
65+
}
66+
67+
const gap = pickerLabelGap ?? 6;
68+
const fontSize = styles.pickerItem.fontSize ?? 25;
69+
const maxDigitCount = Math.max(2, String(maximumValue).length);
70+
const halfNumberWidth = (maxDigitCount * fontSize * 0.55) / 2;
71+
72+
return {
73+
left: "50%" as const,
74+
marginLeft: halfNumberWidth + gap,
75+
};
76+
}, [maximumValue, pickerLabelGap, styles.pickerItem.fontSize, styles.pickerLabelContainer.left]);
77+
5878
const numberOfItems = useMemo(() => {
5979
// guard against negative maximum values
6080
if (maximumValue < 0) {
@@ -210,6 +230,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
210230
amLabel={amLabel}
211231
is12HourPicker={is12HourPicker}
212232
item={item}
233+
pickerAmPmPositionStyle={labelPositionStyle}
213234
pmLabel={pmLabel}
214235
selectedValue={selectedValue}
215236
styles={styles}
@@ -221,6 +242,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
221242
allowFontScaling,
222243
amLabel,
223244
is12HourPicker,
245+
labelPositionStyle,
224246
pmLabel,
225247
selectedValue,
226248
styles,
@@ -488,7 +510,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
488510
viewabilityConfigCallbackPairs={viewabilityConfigCallbackPairs}
489511
windowSize={numberOfItemsToShow}
490512
/>
491-
<View pointerEvents="none" style={styles.pickerLabelContainer}>
513+
<View pointerEvents="none" style={[styles.pickerLabelContainer, labelPositionStyle]}>
492514
{typeof label === "string" ? (
493515
<Text allowFontScaling={allowFontScaling} style={styles.pickerLabel}>
494516
{label}
@@ -508,6 +530,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
508530
initialScrollIndex,
509531
isDisabled,
510532
label,
533+
labelPositionStyle,
511534
numberOfItemsToShow,
512535
numbersForFlatList,
513536
onMomentumScrollEnd,
@@ -571,6 +594,7 @@ const DurationScroll = forwardRef<DurationScrollRef, DurationScrollProps>((props
571594
pointerEvents={isDisabled ? "none" : undefined}
572595
style={[
573596
styles.durationScrollFlatListContainer,
597+
pickerColumnWidth != null && { flex: 0, width: pickerColumnWidth },
574598
{
575599
height: styles.pickerItemContainer.height * numberOfItemsToShow,
576600
},

src/components/DurationScroll/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,10 @@ export interface DurationScrollProps {
2828
onDurationChange: (duration: number) => void;
2929
padNumbersWithZero?: boolean;
3030
padWithNItems: number;
31+
pickerColumnWidth?: number;
3132
pickerFeedback?: () => void | Promise<void>;
3233
pickerGradientOverlayProps?: Partial<LinearGradientProps>;
34+
pickerLabelGap?: number;
3335
pmLabel?: string;
3436
repeatNumbersNTimes?: number;
3537
repeatNumbersNTimesNotExplicitlySet: boolean;

src/components/PickerItem/PickerItem.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ interface PickerItemProps {
1111
amLabel?: string;
1212
is12HourPicker?: boolean;
1313
item: string;
14+
pickerAmPmPositionStyle?: { left: "50%"; marginLeft: number };
1415
pmLabel?: string;
1516
selectedValue?: number;
1617
styles: ReturnType<typeof generateStyles>;
@@ -24,6 +25,7 @@ const PickerItem = React.memo<PickerItemProps>(
2425
amLabel,
2526
is12HourPicker,
2627
item,
28+
pickerAmPmPositionStyle,
2729
pmLabel,
2830
selectedValue,
2931
styles,
@@ -57,7 +59,7 @@ const PickerItem = React.memo<PickerItemProps>(
5759
{stringItem}
5860
</Text>
5961
{is12HourPicker && (
60-
<View style={styles.pickerAmPmContainer}>
62+
<View style={[styles.pickerAmPmContainer, pickerAmPmPositionStyle]}>
6163
<Text allowFontScaling={allowFontScaling} style={styles.pickerAmPmLabel}>
6264
{isAm ? amLabel : pmLabel}
6365
</Text>

0 commit comments

Comments
 (0)