react-native-ruler-date-picker 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (220) hide show
  1. package/README.md +186 -60
  2. package/lib/commonjs/LensTick.js +83 -0
  3. package/lib/commonjs/LensTick.js.map +1 -0
  4. package/lib/commonjs/Ruler.js +38 -0
  5. package/lib/commonjs/Ruler.js.map +1 -0
  6. package/lib/commonjs/RulerContext.js +17 -0
  7. package/lib/commonjs/RulerContext.js.map +1 -0
  8. package/lib/commonjs/RulerDatePicker.js +38 -351
  9. package/lib/commonjs/RulerDatePicker.js.map +1 -1
  10. package/lib/commonjs/RulerDateRangePicker.js +56 -0
  11. package/lib/commonjs/RulerDateRangePicker.js.map +1 -0
  12. package/lib/commonjs/RulerHeader.js +106 -0
  13. package/lib/commonjs/RulerHeader.js.map +1 -0
  14. package/lib/commonjs/RulerIndicator.js +67 -0
  15. package/lib/commonjs/RulerIndicator.js.map +1 -0
  16. package/lib/commonjs/RulerLensTicks.js +55 -0
  17. package/lib/commonjs/RulerLensTicks.js.map +1 -0
  18. package/lib/commonjs/RulerLensTrack.js +52 -0
  19. package/lib/commonjs/RulerLensTrack.js.map +1 -0
  20. package/lib/commonjs/RulerRange.js +39 -0
  21. package/lib/commonjs/RulerRange.js.map +1 -0
  22. package/lib/commonjs/RulerRangeContext.js +17 -0
  23. package/lib/commonjs/RulerRangeContext.js.map +1 -0
  24. package/lib/commonjs/RulerRangeHeader.js +154 -0
  25. package/lib/commonjs/RulerRangeHeader.js.map +1 -0
  26. package/lib/commonjs/RulerRangeHighlight.js +50 -0
  27. package/lib/commonjs/RulerRangeHighlight.js.map +1 -0
  28. package/lib/commonjs/RulerRangeIndicator.js +71 -0
  29. package/lib/commonjs/RulerRangeIndicator.js.map +1 -0
  30. package/lib/commonjs/RulerRangeRoot.js +80 -0
  31. package/lib/commonjs/RulerRangeRoot.js.map +1 -0
  32. package/lib/commonjs/RulerRangeTicks.js +57 -0
  33. package/lib/commonjs/RulerRangeTicks.js.map +1 -0
  34. package/lib/commonjs/RulerRangeTrack.js +46 -0
  35. package/lib/commonjs/RulerRangeTrack.js.map +1 -0
  36. package/lib/commonjs/RulerResetButton.js +46 -0
  37. package/lib/commonjs/RulerResetButton.js.map +1 -0
  38. package/lib/commonjs/RulerRoot.js +87 -0
  39. package/lib/commonjs/RulerRoot.js.map +1 -0
  40. package/lib/commonjs/RulerTicks.js +51 -0
  41. package/lib/commonjs/RulerTicks.js.map +1 -0
  42. package/lib/commonjs/RulerTrack.js +46 -0
  43. package/lib/commonjs/RulerTrack.js.map +1 -0
  44. package/lib/commonjs/Tick.js +41 -36
  45. package/lib/commonjs/Tick.js.map +1 -1
  46. package/lib/commonjs/defaults.js +20 -19
  47. package/lib/commonjs/defaults.js.map +1 -1
  48. package/lib/commonjs/index.js +287 -1
  49. package/lib/commonjs/index.js.map +1 -1
  50. package/lib/commonjs/presets.js +108 -0
  51. package/lib/commonjs/presets.js.map +1 -0
  52. package/lib/commonjs/tickResolver.js +101 -0
  53. package/lib/commonjs/tickResolver.js.map +1 -0
  54. package/lib/commonjs/useHaptics.js +1 -1
  55. package/lib/commonjs/useHaptics.web.js +43 -0
  56. package/lib/commonjs/useHaptics.web.js.map +1 -0
  57. package/lib/commonjs/useRulerDatePicker.js +264 -0
  58. package/lib/commonjs/useRulerDatePicker.js.map +1 -0
  59. package/lib/commonjs/useRulerDateRangePicker.js +313 -0
  60. package/lib/commonjs/useRulerDateRangePicker.js.map +1 -0
  61. package/lib/commonjs/utils.js +61 -47
  62. package/lib/commonjs/utils.js.map +1 -1
  63. package/lib/module/LensTick.js +77 -0
  64. package/lib/module/LensTick.js.map +1 -0
  65. package/lib/module/Ruler.js +35 -0
  66. package/lib/module/Ruler.js.map +1 -0
  67. package/lib/module/RulerContext.js +12 -0
  68. package/lib/module/RulerContext.js.map +1 -0
  69. package/lib/module/RulerDatePicker.js +38 -350
  70. package/lib/module/RulerDatePicker.js.map +1 -1
  71. package/lib/module/RulerDateRangePicker.js +52 -0
  72. package/lib/module/RulerDateRangePicker.js.map +1 -0
  73. package/lib/module/RulerHeader.js +101 -0
  74. package/lib/module/RulerHeader.js.map +1 -0
  75. package/lib/module/RulerIndicator.js +62 -0
  76. package/lib/module/RulerIndicator.js.map +1 -0
  77. package/lib/module/RulerLensTicks.js +50 -0
  78. package/lib/module/RulerLensTicks.js.map +1 -0
  79. package/lib/module/RulerLensTrack.js +47 -0
  80. package/lib/module/RulerLensTrack.js.map +1 -0
  81. package/lib/module/RulerRange.js +36 -0
  82. package/lib/module/RulerRange.js.map +1 -0
  83. package/lib/module/RulerRangeContext.js +12 -0
  84. package/lib/module/RulerRangeContext.js.map +1 -0
  85. package/lib/module/RulerRangeHeader.js +149 -0
  86. package/lib/module/RulerRangeHeader.js.map +1 -0
  87. package/lib/module/RulerRangeHighlight.js +45 -0
  88. package/lib/module/RulerRangeHighlight.js.map +1 -0
  89. package/lib/module/RulerRangeIndicator.js +66 -0
  90. package/lib/module/RulerRangeIndicator.js.map +1 -0
  91. package/lib/module/RulerRangeRoot.js +75 -0
  92. package/lib/module/RulerRangeRoot.js.map +1 -0
  93. package/lib/module/RulerRangeTicks.js +51 -0
  94. package/lib/module/RulerRangeTicks.js.map +1 -0
  95. package/lib/module/RulerRangeTrack.js +41 -0
  96. package/lib/module/RulerRangeTrack.js.map +1 -0
  97. package/lib/module/RulerResetButton.js +41 -0
  98. package/lib/module/RulerResetButton.js.map +1 -0
  99. package/lib/module/RulerRoot.js +82 -0
  100. package/lib/module/RulerRoot.js.map +1 -0
  101. package/lib/module/RulerTicks.js +45 -0
  102. package/lib/module/RulerTicks.js.map +1 -0
  103. package/lib/module/RulerTrack.js +41 -0
  104. package/lib/module/RulerTrack.js.map +1 -0
  105. package/lib/module/Tick.js +41 -36
  106. package/lib/module/Tick.js.map +1 -1
  107. package/lib/module/defaults.js +19 -18
  108. package/lib/module/defaults.js.map +1 -1
  109. package/lib/module/index.js +46 -1
  110. package/lib/module/index.js.map +1 -1
  111. package/lib/module/presets.js +103 -0
  112. package/lib/module/presets.js.map +1 -0
  113. package/lib/module/tickResolver.js +97 -0
  114. package/lib/module/tickResolver.js.map +1 -0
  115. package/lib/module/useHaptics.js +1 -1
  116. package/lib/module/useHaptics.js.map +1 -1
  117. package/lib/module/useHaptics.web.js +39 -0
  118. package/lib/module/useHaptics.web.js.map +1 -0
  119. package/lib/module/useRulerDatePicker.js +260 -0
  120. package/lib/module/useRulerDatePicker.js.map +1 -0
  121. package/lib/module/useRulerDateRangePicker.js +310 -0
  122. package/lib/module/useRulerDateRangePicker.js.map +1 -0
  123. package/lib/module/utils.js +59 -45
  124. package/lib/module/utils.js.map +1 -1
  125. package/lib/typescript/LensTick.d.ts +20 -0
  126. package/lib/typescript/LensTick.d.ts.map +1 -0
  127. package/lib/typescript/Ruler.d.ts +31 -0
  128. package/lib/typescript/Ruler.d.ts.map +1 -0
  129. package/lib/typescript/RulerContext.d.ts +11 -0
  130. package/lib/typescript/RulerContext.d.ts.map +1 -0
  131. package/lib/typescript/RulerDatePicker.d.ts +10 -7
  132. package/lib/typescript/RulerDatePicker.d.ts.map +1 -1
  133. package/lib/typescript/RulerDateRangePicker.d.ts +26 -0
  134. package/lib/typescript/RulerDateRangePicker.d.ts.map +1 -0
  135. package/lib/typescript/RulerHeader.d.ts +14 -0
  136. package/lib/typescript/RulerHeader.d.ts.map +1 -0
  137. package/lib/typescript/RulerIndicator.d.ts +17 -0
  138. package/lib/typescript/RulerIndicator.d.ts.map +1 -0
  139. package/lib/typescript/RulerLensTicks.d.ts +12 -0
  140. package/lib/typescript/RulerLensTicks.d.ts.map +1 -0
  141. package/lib/typescript/RulerLensTrack.d.ts +13 -0
  142. package/lib/typescript/RulerLensTrack.d.ts.map +1 -0
  143. package/lib/typescript/RulerRange.d.ts +32 -0
  144. package/lib/typescript/RulerRange.d.ts.map +1 -0
  145. package/lib/typescript/RulerRangeContext.d.ts +11 -0
  146. package/lib/typescript/RulerRangeContext.d.ts.map +1 -0
  147. package/lib/typescript/RulerRangeHeader.d.ts +12 -0
  148. package/lib/typescript/RulerRangeHeader.d.ts.map +1 -0
  149. package/lib/typescript/RulerRangeHighlight.d.ts +8 -0
  150. package/lib/typescript/RulerRangeHighlight.d.ts.map +1 -0
  151. package/lib/typescript/RulerRangeIndicator.d.ts +17 -0
  152. package/lib/typescript/RulerRangeIndicator.d.ts.map +1 -0
  153. package/lib/typescript/RulerRangeRoot.d.ts +7 -0
  154. package/lib/typescript/RulerRangeRoot.d.ts.map +1 -0
  155. package/lib/typescript/RulerRangeTicks.d.ts +14 -0
  156. package/lib/typescript/RulerRangeTicks.d.ts.map +1 -0
  157. package/lib/typescript/RulerRangeTrack.d.ts +8 -0
  158. package/lib/typescript/RulerRangeTrack.d.ts.map +1 -0
  159. package/lib/typescript/RulerResetButton.d.ts +10 -0
  160. package/lib/typescript/RulerResetButton.d.ts.map +1 -0
  161. package/lib/typescript/RulerRoot.d.ts +7 -0
  162. package/lib/typescript/RulerRoot.d.ts.map +1 -0
  163. package/lib/typescript/RulerTicks.d.ts +9 -0
  164. package/lib/typescript/RulerTicks.d.ts.map +1 -0
  165. package/lib/typescript/RulerTrack.d.ts +8 -0
  166. package/lib/typescript/RulerTrack.d.ts.map +1 -0
  167. package/lib/typescript/Tick.d.ts +3 -16
  168. package/lib/typescript/Tick.d.ts.map +1 -1
  169. package/lib/typescript/defaults.d.ts +6 -9
  170. package/lib/typescript/defaults.d.ts.map +1 -1
  171. package/lib/typescript/index.d.ts +37 -1
  172. package/lib/typescript/index.d.ts.map +1 -1
  173. package/lib/typescript/presets.d.ts +27 -0
  174. package/lib/typescript/presets.d.ts.map +1 -0
  175. package/lib/typescript/tickResolver.d.ts +10 -0
  176. package/lib/typescript/tickResolver.d.ts.map +1 -0
  177. package/lib/typescript/types.d.ts +302 -85
  178. package/lib/typescript/types.d.ts.map +1 -1
  179. package/lib/typescript/useHaptics.web.d.ts +17 -0
  180. package/lib/typescript/useHaptics.web.d.ts.map +1 -0
  181. package/lib/typescript/useRulerDatePicker.d.ts +26 -0
  182. package/lib/typescript/useRulerDatePicker.d.ts.map +1 -0
  183. package/lib/typescript/useRulerDateRangePicker.d.ts +40 -0
  184. package/lib/typescript/useRulerDateRangePicker.d.ts.map +1 -0
  185. package/lib/typescript/utils.d.ts +14 -12
  186. package/lib/typescript/utils.d.ts.map +1 -1
  187. package/package.json +25 -7
  188. package/src/LensTick.tsx +90 -0
  189. package/src/Ruler.ts +32 -0
  190. package/src/RulerContext.tsx +20 -0
  191. package/src/RulerDatePicker.tsx +41 -439
  192. package/src/RulerDateRangePicker.tsx +53 -0
  193. package/src/RulerHeader.tsx +125 -0
  194. package/src/RulerIndicator.tsx +84 -0
  195. package/src/RulerLensTicks.tsx +50 -0
  196. package/src/RulerLensTrack.tsx +45 -0
  197. package/src/RulerRange.ts +33 -0
  198. package/src/RulerRangeContext.tsx +20 -0
  199. package/src/RulerRangeHeader.tsx +180 -0
  200. package/src/RulerRangeHighlight.tsx +47 -0
  201. package/src/RulerRangeIndicator.tsx +85 -0
  202. package/src/RulerRangeRoot.tsx +87 -0
  203. package/src/RulerRangeTicks.tsx +68 -0
  204. package/src/RulerRangeTrack.tsx +39 -0
  205. package/src/RulerResetButton.tsx +59 -0
  206. package/src/RulerRoot.tsx +93 -0
  207. package/src/RulerTicks.tsx +57 -0
  208. package/src/RulerTrack.tsx +39 -0
  209. package/src/Tick.tsx +60 -49
  210. package/src/defaults.ts +21 -19
  211. package/src/index.ts +83 -1
  212. package/src/presets.ts +108 -0
  213. package/src/tickResolver.ts +123 -0
  214. package/src/types.ts +354 -102
  215. package/src/useHaptics.web.ts +55 -0
  216. package/src/useRulerDatePicker.ts +373 -0
  217. package/src/useRulerDateRangePicker.ts +468 -0
  218. package/src/utils.ts +67 -68
  219. package/lib/commonjs/package.json +0 -1
  220. package/lib/module/package.json +0 -1
@@ -0,0 +1,84 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { useRulerContext } from './RulerContext';
4
+ import { DEFAULTS } from './defaults';
5
+
6
+ export interface RulerIndicatorProps {
7
+ children?: React.ReactNode;
8
+ width?: number;
9
+ height?: number;
10
+ color?: string;
11
+ /**
12
+ * Offset [x, y] in pixels of the center needle pointer.
13
+ * Default: [0, 0] (anchored to bottom).
14
+ */
15
+ offset?: [number, number];
16
+ style?: StyleProp<ViewStyle>;
17
+ render?: () => React.ReactNode;
18
+ }
19
+
20
+ export function RulerIndicator({
21
+ children,
22
+ width: customWidth,
23
+ height: customHeight,
24
+ color: customColor,
25
+ offset: customOffset,
26
+ style,
27
+ render,
28
+ }: RulerIndicatorProps = {}) {
29
+ const { indicatorConfig, theme } = useRulerContext();
30
+
31
+ const customRenderer = render ?? indicatorConfig?.render;
32
+ const offset = customOffset ?? indicatorConfig?.offset ?? [0, 0];
33
+ const [offsetX = 0, offsetY = 0] = offset;
34
+ const indicatorStyle = style ?? indicatorConfig?.style;
35
+
36
+ if (children || customRenderer) {
37
+ return (
38
+ <View
39
+ pointerEvents="none"
40
+ style={[
41
+ styles.indicator,
42
+ {
43
+ bottom: offsetY,
44
+ transform: [{ translateX: offsetX }],
45
+ },
46
+ indicatorStyle,
47
+ ]}
48
+ >
49
+ {children ?? customRenderer?.()}
50
+ </View>
51
+ );
52
+ }
53
+
54
+ const width = customWidth ?? indicatorConfig?.width ?? DEFAULTS.indicatorWidth;
55
+ const height = customHeight ?? indicatorConfig?.height ?? DEFAULTS.indicatorHeight;
56
+ const color = customColor ?? indicatorConfig?.color ?? theme.indicatorColor;
57
+
58
+ return (
59
+ <View
60
+ pointerEvents="none"
61
+ style={[
62
+ styles.indicator,
63
+ {
64
+ bottom: offsetY,
65
+ transform: [{ translateX: offsetX }],
66
+ width,
67
+ height,
68
+ backgroundColor: color,
69
+ borderRadius: width / 2,
70
+ },
71
+ indicatorStyle,
72
+ ]}
73
+ />
74
+ );
75
+ }
76
+
77
+ const styles = StyleSheet.create({
78
+ indicator: {
79
+ position: 'absolute',
80
+ zIndex: 10,
81
+ alignItems: 'center',
82
+ justifyContent: 'center',
83
+ },
84
+ });
@@ -0,0 +1,50 @@
1
+ import React from 'react';
2
+ import { StyleSheet, View, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { useRulerContext } from './RulerContext';
4
+ import { LensTick } from './LensTick';
5
+ import { DEFAULT_LENS_CONFIG } from './defaults';
6
+ import type { LensConfig } from './types';
7
+
8
+ export interface RulerLensTicksProps {
9
+ style?: StyleProp<ViewStyle>;
10
+ config?: LensConfig;
11
+ }
12
+
13
+ /**
14
+ * Ticks container rendering ticks with dynamic non-linear fisheye lens spacing.
15
+ */
16
+ export function RulerLensTicks({ style, config: propConfig }: RulerLensTicksProps = {}) {
17
+ const { translateX, visibleTicks, tickSpacing, rulerHeight, lensConfig: contextConfig } =
18
+ useRulerContext();
19
+
20
+ const config = propConfig
21
+ ? { ...DEFAULT_LENS_CONFIG, ...propConfig }
22
+ : contextConfig ?? DEFAULT_LENS_CONFIG;
23
+
24
+ return (
25
+ <View pointerEvents="none" style={[styles.ticksContainer, style]}>
26
+ {visibleTicks.map((tick) => (
27
+ <LensTick
28
+ key={tick.offset}
29
+ tick={tick}
30
+ tickSpacing={tickSpacing}
31
+ rulerHeight={rulerHeight}
32
+ translateX={translateX}
33
+ magnification={config.magnification}
34
+ radius={config.radius}
35
+ scaleTicks={config.scaleTicks}
36
+ />
37
+ ))}
38
+ </View>
39
+ );
40
+ }
41
+
42
+ const styles = StyleSheet.create({
43
+ ticksContainer: {
44
+ position: 'absolute',
45
+ height: '100%',
46
+ width: '100%',
47
+ left: 0,
48
+ top: 0,
49
+ },
50
+ });
@@ -0,0 +1,45 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { GestureDetector } from 'react-native-gesture-handler';
4
+ import { useRulerContext } from './RulerContext';
5
+ import { RulerIndicator } from './RulerIndicator';
6
+ import { RulerLensTicks } from './RulerLensTicks';
7
+ import type { LensConfig } from './types';
8
+
9
+ export interface RulerLensTrackProps {
10
+ children?: React.ReactNode;
11
+ style?: StyleProp<ViewStyle>;
12
+ config?: LensConfig;
13
+ }
14
+
15
+ /**
16
+ * Horizontal ruler track with pan gesture detection and non-linear fisheye lens distortion.
17
+ */
18
+ export function RulerLensTrack({ children, style, config }: RulerLensTrackProps = {}) {
19
+ const { panGesture, rulerHeight, width } = useRulerContext();
20
+
21
+ return (
22
+ <GestureDetector gesture={panGesture}>
23
+ <View style={[styles.rulerContainer, { width, height: rulerHeight }, style]}>
24
+ {children ? (
25
+ children
26
+ ) : (
27
+ <>
28
+ <RulerIndicator />
29
+ <RulerLensTicks config={config} />
30
+ </>
31
+ )}
32
+ </View>
33
+ </GestureDetector>
34
+ );
35
+ }
36
+
37
+ const styles = StyleSheet.create({
38
+ rulerContainer: {
39
+ position: 'relative',
40
+ flexDirection: 'row',
41
+ alignItems: 'flex-end',
42
+ justifyContent: 'center',
43
+ overflow: 'hidden',
44
+ },
45
+ });
@@ -0,0 +1,33 @@
1
+ import { RulerRangeRoot } from './RulerRangeRoot';
2
+ import { RulerRangeTrack } from './RulerRangeTrack';
3
+ import { RulerRangeHeader } from './RulerRangeHeader';
4
+ import { RulerRangeIndicator } from './RulerRangeIndicator';
5
+ import { RulerRangeHighlight } from './RulerRangeHighlight';
6
+ import { RulerRangeTicks } from './RulerRangeTicks';
7
+ import { Tick } from './Tick';
8
+
9
+ /**
10
+ * Compound Anatomy API for Date Range Ruler Picker.
11
+ *
12
+ * @experimental This compound API is experimental and subject to breaking changes.
13
+ *
14
+ * @example
15
+ * ```tsx
16
+ * <RulerRange.Root value={range} onChange={setRange}>
17
+ * <RulerRange.Header />
18
+ * <RulerRange.Track>
19
+ * <RulerRange.Indicator />
20
+ * <RulerRange.Ticks />
21
+ * </RulerRange.Track>
22
+ * </RulerRange.Root>
23
+ * ```
24
+ */
25
+ export const RulerRange = {
26
+ Root: RulerRangeRoot,
27
+ Track: RulerRangeTrack,
28
+ Indicator: RulerRangeIndicator,
29
+ Header: RulerRangeHeader,
30
+ Highlight: RulerRangeHighlight,
31
+ Ticks: RulerRangeTicks,
32
+ Tick: Tick,
33
+ };
@@ -0,0 +1,20 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { UseRulerDateRangePickerReturn } from './useRulerDateRangePicker';
3
+ import type { RangeIndicatorConfig, RangeHeaderConfig, RangeHighlightConfig } from './types';
4
+
5
+ export interface RulerRangeContextValue extends UseRulerDateRangePickerReturn {
6
+ indicatorConfig?: RangeIndicatorConfig;
7
+ headerConfig?: RangeHeaderConfig;
8
+ highlightConfig?: RangeHighlightConfig;
9
+ locale?: string;
10
+ }
11
+
12
+ export const RulerRangeContext = createContext<RulerRangeContextValue | null>(null);
13
+
14
+ export function useRulerRangeContext(): RulerRangeContextValue {
15
+ const context = useContext(RulerRangeContext);
16
+ if (!context) {
17
+ throw new Error('RulerRange compound subcomponents must be rendered within a <RulerRange.Root> provider.');
18
+ }
19
+ return context;
20
+ }
@@ -0,0 +1,180 @@
1
+ import React from 'react';
2
+ import {
3
+ View,
4
+ Text,
5
+ TouchableOpacity,
6
+ StyleSheet,
7
+ type StyleProp,
8
+ type ViewStyle,
9
+ type TextStyle,
10
+ } from 'react-native';
11
+ import { useRulerRangeContext } from './RulerRangeContext';
12
+ import type { RangeHeaderContext } from './types';
13
+
14
+ export interface RulerRangeHeaderProps {
15
+ children?: React.ReactNode;
16
+ style?: StyleProp<ViewStyle>;
17
+ dateStyle?: StyleProp<TextStyle>;
18
+ format?: (date: Date) => string;
19
+ render?: (context: RangeHeaderContext) => React.ReactNode;
20
+ }
21
+
22
+ export function RulerRangeHeader({
23
+ children,
24
+ style,
25
+ dateStyle,
26
+ format: customFormat,
27
+ render,
28
+ }: RulerRangeHeaderProps = {}) {
29
+ const {
30
+ startDate,
31
+ endDate,
32
+ spanDays,
33
+ activeHandle,
34
+ scrollToHandle,
35
+ theme,
36
+ headerConfig,
37
+ locale = 'default',
38
+ } = useRulerRangeContext();
39
+
40
+ const headerStyle = style ?? headerConfig?.style;
41
+
42
+ if (children) {
43
+ return <View style={[styles.headerContainer, headerStyle]}>{children}</View>;
44
+ }
45
+
46
+ const customRenderer = render ?? headerConfig?.render;
47
+ if (customRenderer) {
48
+ return (
49
+ <>
50
+ {customRenderer({
51
+ start: startDate,
52
+ end: endDate,
53
+ spanDays,
54
+ activeHandle,
55
+ scrollToHandle,
56
+ })}
57
+ </>
58
+ );
59
+ }
60
+
61
+ const formatFn = customFormat ?? headerConfig?.format;
62
+
63
+ const startFormatted = formatFn
64
+ ? formatFn(startDate)
65
+ : startDate.toLocaleDateString(locale, {
66
+ month: 'short',
67
+ day: 'numeric',
68
+ });
69
+ const endFormatted = formatFn
70
+ ? formatFn(endDate)
71
+ : endDate.toLocaleDateString(locale, {
72
+ month: 'short',
73
+ day: 'numeric',
74
+ });
75
+
76
+ const resolvedDateStyle = dateStyle ?? headerConfig?.dateStyle;
77
+
78
+ return (
79
+ <View style={[styles.headerContainer, headerStyle]}>
80
+ {/* Start Date Button */}
81
+ <TouchableOpacity
82
+ activeOpacity={0.7}
83
+ onPress={() => scrollToHandle('start', true)}
84
+ style={[
85
+ styles.pill,
86
+ activeHandle === 'start' && {
87
+ borderColor: theme.accentColor,
88
+ backgroundColor: `${theme.accentColor}15`,
89
+ },
90
+ ]}
91
+ >
92
+ <Text style={[styles.subLabel, { color: theme.labelColor }]}>START</Text>
93
+ <Text
94
+ style={[
95
+ styles.dateLabel,
96
+ { color: activeHandle === 'start' ? theme.accentColor : theme.labelColor },
97
+ resolvedDateStyle,
98
+ ]}
99
+ >
100
+ {startFormatted}
101
+ </Text>
102
+ </TouchableOpacity>
103
+
104
+ {/* Arrow & Span Indicator */}
105
+ <View style={styles.arrowContainer}>
106
+ <Text style={[styles.arrowText, { color: theme.labelColor }]}>➔</Text>
107
+ <Text style={[styles.spanText, { color: theme.accentColor }]}>
108
+ {spanDays} {spanDays === 1 ? 'day' : 'days'}
109
+ </Text>
110
+ </View>
111
+
112
+ {/* End Date Button */}
113
+ <TouchableOpacity
114
+ activeOpacity={0.7}
115
+ onPress={() => scrollToHandle('end', true)}
116
+ style={[
117
+ styles.pill,
118
+ activeHandle === 'end' && {
119
+ borderColor: theme.accentColor,
120
+ backgroundColor: `${theme.accentColor}15`,
121
+ },
122
+ ]}
123
+ >
124
+ <Text style={[styles.subLabel, { color: theme.labelColor }]}>END</Text>
125
+ <Text
126
+ style={[
127
+ styles.dateLabel,
128
+ { color: activeHandle === 'end' ? theme.accentColor : theme.labelColor },
129
+ resolvedDateStyle,
130
+ ]}
131
+ >
132
+ {endFormatted}
133
+ </Text>
134
+ </TouchableOpacity>
135
+ </View>
136
+ );
137
+ }
138
+
139
+ const styles = StyleSheet.create({
140
+ headerContainer: {
141
+ flexDirection: 'row',
142
+ alignItems: 'center',
143
+ justifyContent: 'center',
144
+ width: '100%',
145
+ paddingHorizontal: 20,
146
+ marginBottom: 16,
147
+ minHeight: 48,
148
+ },
149
+ pill: {
150
+ paddingVertical: 6,
151
+ paddingHorizontal: 14,
152
+ borderRadius: 20,
153
+ borderWidth: 1.5,
154
+ borderColor: 'transparent',
155
+ alignItems: 'center',
156
+ },
157
+ subLabel: {
158
+ fontSize: 9,
159
+ fontWeight: '700',
160
+ letterSpacing: 0.5,
161
+ marginBottom: 2,
162
+ },
163
+ dateLabel: {
164
+ fontSize: 14,
165
+ fontWeight: '700',
166
+ },
167
+ arrowContainer: {
168
+ marginHorizontal: 12,
169
+ alignItems: 'center',
170
+ },
171
+ arrowText: {
172
+ fontSize: 12,
173
+ fontWeight: '600',
174
+ },
175
+ spanText: {
176
+ fontSize: 10,
177
+ fontWeight: '600',
178
+ marginTop: 2,
179
+ },
180
+ });
@@ -0,0 +1,47 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { useRulerRangeContext } from './RulerRangeContext';
4
+
5
+ export interface RulerRangeHighlightProps {
6
+ style?: StyleProp<ViewStyle>;
7
+ color?: string;
8
+ }
9
+
10
+ export function RulerRangeHighlight({ style, color }: RulerRangeHighlightProps = {}) {
11
+ const { startOffset, endOffset, tickSpacing, rulerHeight, theme, highlightConfig } =
12
+ useRulerRangeContext();
13
+
14
+ if (highlightConfig?.render) {
15
+ return <>{highlightConfig.render(startOffset, endOffset, tickSpacing)}</>;
16
+ }
17
+
18
+ const highlightColor = color ?? highlightConfig?.color ?? theme.rangeHighlightColor;
19
+ const left = startOffset * tickSpacing - tickSpacing / 2;
20
+ const width = Math.max(tickSpacing, (endOffset - startOffset + 1) * tickSpacing);
21
+
22
+ return (
23
+ <View
24
+ pointerEvents="none"
25
+ style={[
26
+ styles.highlight,
27
+ {
28
+ left,
29
+ width,
30
+ height: rulerHeight,
31
+ backgroundColor: highlightColor,
32
+ },
33
+ highlightConfig?.style,
34
+ style,
35
+ ]}
36
+ />
37
+ );
38
+ }
39
+
40
+ const styles = StyleSheet.create({
41
+ highlight: {
42
+ position: 'absolute',
43
+ bottom: 0,
44
+ borderRadius: 8,
45
+ zIndex: 1,
46
+ },
47
+ });
@@ -0,0 +1,85 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
3
+ import { useRulerRangeContext } from './RulerRangeContext';
4
+ import { DEFAULTS } from './defaults';
5
+
6
+ export interface RulerRangeIndicatorProps {
7
+ width?: number;
8
+ height?: number;
9
+ color?: string;
10
+ /**
11
+ * Offset [x, y] in pixels of the range handle needle.
12
+ * Default: [0, 0] (anchored to bottom).
13
+ */
14
+ offset?: [number, number];
15
+ children?: React.ReactNode;
16
+ style?: StyleProp<ViewStyle>;
17
+ render?: (handle: 'start' | 'end', date: Date) => React.ReactNode;
18
+ }
19
+
20
+ export function RulerRangeIndicator({
21
+ children,
22
+ width: customWidth,
23
+ height: customHeight,
24
+ color: customColor,
25
+ offset: customOffset,
26
+ style,
27
+ render,
28
+ }: RulerRangeIndicatorProps = {}) {
29
+ const { indicatorConfig, theme, activeHandle, startDate, endDate } = useRulerRangeContext();
30
+
31
+ const activeDate = activeHandle === 'start' ? startDate : endDate;
32
+ const customRenderer = render ?? indicatorConfig?.render;
33
+ const offset = customOffset ?? indicatorConfig?.offset ?? [0, 0];
34
+ const [offsetX = 0, offsetY = 0] = offset;
35
+ const indicatorStyle = style ?? indicatorConfig?.style;
36
+
37
+ if (children || customRenderer) {
38
+ return (
39
+ <View
40
+ pointerEvents="none"
41
+ style={[
42
+ styles.indicator,
43
+ {
44
+ bottom: offsetY,
45
+ transform: [{ translateX: offsetX }],
46
+ },
47
+ indicatorStyle,
48
+ ]}
49
+ >
50
+ {children ?? customRenderer?.(activeHandle, activeDate)}
51
+ </View>
52
+ );
53
+ }
54
+
55
+ const width = customWidth ?? indicatorConfig?.width ?? DEFAULTS.indicatorWidth;
56
+ const height = customHeight ?? indicatorConfig?.height ?? DEFAULTS.indicatorHeight;
57
+ const color = customColor ?? indicatorConfig?.color ?? theme.indicatorColor;
58
+
59
+ return (
60
+ <View
61
+ pointerEvents="none"
62
+ style={[
63
+ styles.indicator,
64
+ {
65
+ bottom: offsetY,
66
+ transform: [{ translateX: offsetX }],
67
+ width,
68
+ height,
69
+ backgroundColor: color,
70
+ borderRadius: width / 2,
71
+ },
72
+ indicatorStyle,
73
+ ]}
74
+ />
75
+ );
76
+ }
77
+
78
+ const styles = StyleSheet.create({
79
+ indicator: {
80
+ position: 'absolute',
81
+ zIndex: 10,
82
+ alignItems: 'center',
83
+ justifyContent: 'center',
84
+ },
85
+ });
@@ -0,0 +1,87 @@
1
+ import React from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import type { RulerDateRangePickerProps, RangeIndicatorConfig } from './types';
4
+ import { useRulerDateRangePicker } from './useRulerDateRangePicker';
5
+ import { RulerRangeContext } from './RulerRangeContext';
6
+
7
+ export interface RulerRangeRootProps extends RulerDateRangePickerProps {
8
+ children?: React.ReactNode;
9
+ }
10
+
11
+ export function RulerRangeRoot(props: RulerRangeRootProps) {
12
+ const {
13
+ children,
14
+ style,
15
+ indicator,
16
+ highlight,
17
+ testID,
18
+ } = props;
19
+
20
+ const rangeRuler = useRulerDateRangePicker(props);
21
+ const indicatorConfig: RangeIndicatorConfig | undefined =
22
+ typeof indicator === 'object' ? indicator : undefined;
23
+ const headerConfig =
24
+ typeof props.header === 'object' ? props.header : undefined;
25
+ const highlightConfig =
26
+ typeof highlight === 'object' ? highlight : undefined;
27
+
28
+ const contextValue = {
29
+ ...rangeRuler,
30
+ indicatorConfig,
31
+ headerConfig,
32
+ highlightConfig,
33
+ locale: props.locale,
34
+ };
35
+
36
+ return (
37
+ <RulerRangeContext.Provider value={contextValue}>
38
+ <View
39
+ testID={testID}
40
+ accessible={true}
41
+ accessibilityRole="adjustable"
42
+ accessibilityLabel={props.accessibilityLabel ?? 'Date range picker'}
43
+ accessibilityValue={{
44
+ text: `${rangeRuler.startDateStr} to ${rangeRuler.endDateStr}`,
45
+ }}
46
+ accessibilityActions={[
47
+ { name: 'increment', label: `Next day for ${rangeRuler.activeHandle} date` },
48
+ { name: 'decrement', label: `Previous day for ${rangeRuler.activeHandle} date` },
49
+ ]}
50
+ onAccessibilityAction={(event) => {
51
+ const isStart = rangeRuler.activeHandle === 'start';
52
+ const target = new Date(isStart ? rangeRuler.startDate : rangeRuler.endDate);
53
+ if (event.nativeEvent.actionName === 'increment') {
54
+ target.setDate(target.getDate() + 1);
55
+ } else if (event.nativeEvent.actionName === 'decrement') {
56
+ target.setDate(target.getDate() - 1);
57
+ }
58
+ if (isStart) {
59
+ rangeRuler.setRange(target, rangeRuler.endDate);
60
+ } else {
61
+ rangeRuler.setRange(rangeRuler.startDate, target);
62
+ }
63
+ }}
64
+ style={[
65
+ styles.container,
66
+ {
67
+ minHeight: rangeRuler.height,
68
+ backgroundColor: rangeRuler.theme.background,
69
+ },
70
+ style,
71
+ ]}
72
+ >
73
+ {children}
74
+ </View>
75
+ </RulerRangeContext.Provider>
76
+ );
77
+ }
78
+
79
+ const styles = StyleSheet.create({
80
+ container: {
81
+ justifyContent: 'center',
82
+ alignItems: 'center',
83
+ position: 'relative',
84
+ width: '100%',
85
+ paddingVertical: 8,
86
+ },
87
+ });
@@ -0,0 +1,68 @@
1
+ import React from 'react';
2
+ import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native';
3
+ import Animated, { useAnimatedStyle } from 'react-native-reanimated';
4
+ import { useRulerRangeContext } from './RulerRangeContext';
5
+ import { RulerRangeHighlight } from './RulerRangeHighlight';
6
+ import { Tick } from './Tick';
7
+ import type { ResolvedTick } from './types';
8
+
9
+ export interface RulerRangeTicksProps {
10
+ style?: StyleProp<ViewStyle>;
11
+ renderTick?: (tick: ResolvedTick, tickSpacing: number, rulerHeight: number) => React.ReactNode;
12
+ showHighlight?: boolean;
13
+ }
14
+
15
+ /**
16
+ * Animated ticks container for the date range picker with Reanimated transform
17
+ * and connected highlight span.
18
+ */
19
+ export function RulerRangeTicks({
20
+ style,
21
+ renderTick,
22
+ showHighlight = true,
23
+ }: RulerRangeTicksProps = {}) {
24
+ const { translateX, visibleTicks, tickSpacing, rulerHeight, width } =
25
+ useRulerRangeContext();
26
+
27
+ const animatedStyle = useAnimatedStyle(
28
+ () => ({
29
+ transform: [{ translateX: translateX.value }],
30
+ }),
31
+ [translateX]
32
+ );
33
+
34
+ return (
35
+ <Animated.View
36
+ style={[
37
+ styles.ticksWrapper,
38
+ { left: width / 2 },
39
+ animatedStyle,
40
+ style,
41
+ ]}
42
+ >
43
+ {showHighlight && <RulerRangeHighlight />}
44
+ {visibleTicks.map((tick) =>
45
+ renderTick ? (
46
+ <React.Fragment key={tick.offset}>
47
+ {renderTick(tick, tickSpacing, rulerHeight)}
48
+ </React.Fragment>
49
+ ) : (
50
+ <Tick
51
+ key={tick.offset}
52
+ tick={tick}
53
+ tickSpacing={tickSpacing}
54
+ rulerHeight={rulerHeight}
55
+ />
56
+ )
57
+ )}
58
+ </Animated.View>
59
+ );
60
+ }
61
+
62
+ const styles = StyleSheet.create({
63
+ ticksWrapper: {
64
+ position: 'absolute',
65
+ height: '100%',
66
+ width: 0,
67
+ },
68
+ });