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.
- package/README.md +186 -60
- package/lib/commonjs/LensTick.js +83 -0
- package/lib/commonjs/LensTick.js.map +1 -0
- package/lib/commonjs/Ruler.js +38 -0
- package/lib/commonjs/Ruler.js.map +1 -0
- package/lib/commonjs/RulerContext.js +17 -0
- package/lib/commonjs/RulerContext.js.map +1 -0
- package/lib/commonjs/RulerDatePicker.js +38 -351
- package/lib/commonjs/RulerDatePicker.js.map +1 -1
- package/lib/commonjs/RulerDateRangePicker.js +56 -0
- package/lib/commonjs/RulerDateRangePicker.js.map +1 -0
- package/lib/commonjs/RulerHeader.js +106 -0
- package/lib/commonjs/RulerHeader.js.map +1 -0
- package/lib/commonjs/RulerIndicator.js +67 -0
- package/lib/commonjs/RulerIndicator.js.map +1 -0
- package/lib/commonjs/RulerLensTicks.js +55 -0
- package/lib/commonjs/RulerLensTicks.js.map +1 -0
- package/lib/commonjs/RulerLensTrack.js +52 -0
- package/lib/commonjs/RulerLensTrack.js.map +1 -0
- package/lib/commonjs/RulerRange.js +39 -0
- package/lib/commonjs/RulerRange.js.map +1 -0
- package/lib/commonjs/RulerRangeContext.js +17 -0
- package/lib/commonjs/RulerRangeContext.js.map +1 -0
- package/lib/commonjs/RulerRangeHeader.js +154 -0
- package/lib/commonjs/RulerRangeHeader.js.map +1 -0
- package/lib/commonjs/RulerRangeHighlight.js +50 -0
- package/lib/commonjs/RulerRangeHighlight.js.map +1 -0
- package/lib/commonjs/RulerRangeIndicator.js +71 -0
- package/lib/commonjs/RulerRangeIndicator.js.map +1 -0
- package/lib/commonjs/RulerRangeRoot.js +80 -0
- package/lib/commonjs/RulerRangeRoot.js.map +1 -0
- package/lib/commonjs/RulerRangeTicks.js +57 -0
- package/lib/commonjs/RulerRangeTicks.js.map +1 -0
- package/lib/commonjs/RulerRangeTrack.js +46 -0
- package/lib/commonjs/RulerRangeTrack.js.map +1 -0
- package/lib/commonjs/RulerResetButton.js +46 -0
- package/lib/commonjs/RulerResetButton.js.map +1 -0
- package/lib/commonjs/RulerRoot.js +87 -0
- package/lib/commonjs/RulerRoot.js.map +1 -0
- package/lib/commonjs/RulerTicks.js +51 -0
- package/lib/commonjs/RulerTicks.js.map +1 -0
- package/lib/commonjs/RulerTrack.js +46 -0
- package/lib/commonjs/RulerTrack.js.map +1 -0
- package/lib/commonjs/Tick.js +41 -36
- package/lib/commonjs/Tick.js.map +1 -1
- package/lib/commonjs/defaults.js +20 -19
- package/lib/commonjs/defaults.js.map +1 -1
- package/lib/commonjs/index.js +287 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/presets.js +108 -0
- package/lib/commonjs/presets.js.map +1 -0
- package/lib/commonjs/tickResolver.js +101 -0
- package/lib/commonjs/tickResolver.js.map +1 -0
- package/lib/commonjs/useHaptics.js +1 -1
- package/lib/commonjs/useHaptics.web.js +43 -0
- package/lib/commonjs/useHaptics.web.js.map +1 -0
- package/lib/commonjs/useRulerDatePicker.js +264 -0
- package/lib/commonjs/useRulerDatePicker.js.map +1 -0
- package/lib/commonjs/useRulerDateRangePicker.js +313 -0
- package/lib/commonjs/useRulerDateRangePicker.js.map +1 -0
- package/lib/commonjs/utils.js +61 -47
- package/lib/commonjs/utils.js.map +1 -1
- package/lib/module/LensTick.js +77 -0
- package/lib/module/LensTick.js.map +1 -0
- package/lib/module/Ruler.js +35 -0
- package/lib/module/Ruler.js.map +1 -0
- package/lib/module/RulerContext.js +12 -0
- package/lib/module/RulerContext.js.map +1 -0
- package/lib/module/RulerDatePicker.js +38 -350
- package/lib/module/RulerDatePicker.js.map +1 -1
- package/lib/module/RulerDateRangePicker.js +52 -0
- package/lib/module/RulerDateRangePicker.js.map +1 -0
- package/lib/module/RulerHeader.js +101 -0
- package/lib/module/RulerHeader.js.map +1 -0
- package/lib/module/RulerIndicator.js +62 -0
- package/lib/module/RulerIndicator.js.map +1 -0
- package/lib/module/RulerLensTicks.js +50 -0
- package/lib/module/RulerLensTicks.js.map +1 -0
- package/lib/module/RulerLensTrack.js +47 -0
- package/lib/module/RulerLensTrack.js.map +1 -0
- package/lib/module/RulerRange.js +36 -0
- package/lib/module/RulerRange.js.map +1 -0
- package/lib/module/RulerRangeContext.js +12 -0
- package/lib/module/RulerRangeContext.js.map +1 -0
- package/lib/module/RulerRangeHeader.js +149 -0
- package/lib/module/RulerRangeHeader.js.map +1 -0
- package/lib/module/RulerRangeHighlight.js +45 -0
- package/lib/module/RulerRangeHighlight.js.map +1 -0
- package/lib/module/RulerRangeIndicator.js +66 -0
- package/lib/module/RulerRangeIndicator.js.map +1 -0
- package/lib/module/RulerRangeRoot.js +75 -0
- package/lib/module/RulerRangeRoot.js.map +1 -0
- package/lib/module/RulerRangeTicks.js +51 -0
- package/lib/module/RulerRangeTicks.js.map +1 -0
- package/lib/module/RulerRangeTrack.js +41 -0
- package/lib/module/RulerRangeTrack.js.map +1 -0
- package/lib/module/RulerResetButton.js +41 -0
- package/lib/module/RulerResetButton.js.map +1 -0
- package/lib/module/RulerRoot.js +82 -0
- package/lib/module/RulerRoot.js.map +1 -0
- package/lib/module/RulerTicks.js +45 -0
- package/lib/module/RulerTicks.js.map +1 -0
- package/lib/module/RulerTrack.js +41 -0
- package/lib/module/RulerTrack.js.map +1 -0
- package/lib/module/Tick.js +41 -36
- package/lib/module/Tick.js.map +1 -1
- package/lib/module/defaults.js +19 -18
- package/lib/module/defaults.js.map +1 -1
- package/lib/module/index.js +46 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/presets.js +103 -0
- package/lib/module/presets.js.map +1 -0
- package/lib/module/tickResolver.js +97 -0
- package/lib/module/tickResolver.js.map +1 -0
- package/lib/module/useHaptics.js +1 -1
- package/lib/module/useHaptics.js.map +1 -1
- package/lib/module/useHaptics.web.js +39 -0
- package/lib/module/useHaptics.web.js.map +1 -0
- package/lib/module/useRulerDatePicker.js +260 -0
- package/lib/module/useRulerDatePicker.js.map +1 -0
- package/lib/module/useRulerDateRangePicker.js +310 -0
- package/lib/module/useRulerDateRangePicker.js.map +1 -0
- package/lib/module/utils.js +59 -45
- package/lib/module/utils.js.map +1 -1
- package/lib/typescript/LensTick.d.ts +20 -0
- package/lib/typescript/LensTick.d.ts.map +1 -0
- package/lib/typescript/Ruler.d.ts +31 -0
- package/lib/typescript/Ruler.d.ts.map +1 -0
- package/lib/typescript/RulerContext.d.ts +11 -0
- package/lib/typescript/RulerContext.d.ts.map +1 -0
- package/lib/typescript/RulerDatePicker.d.ts +10 -7
- package/lib/typescript/RulerDatePicker.d.ts.map +1 -1
- package/lib/typescript/RulerDateRangePicker.d.ts +26 -0
- package/lib/typescript/RulerDateRangePicker.d.ts.map +1 -0
- package/lib/typescript/RulerHeader.d.ts +14 -0
- package/lib/typescript/RulerHeader.d.ts.map +1 -0
- package/lib/typescript/RulerIndicator.d.ts +17 -0
- package/lib/typescript/RulerIndicator.d.ts.map +1 -0
- package/lib/typescript/RulerLensTicks.d.ts +12 -0
- package/lib/typescript/RulerLensTicks.d.ts.map +1 -0
- package/lib/typescript/RulerLensTrack.d.ts +13 -0
- package/lib/typescript/RulerLensTrack.d.ts.map +1 -0
- package/lib/typescript/RulerRange.d.ts +32 -0
- package/lib/typescript/RulerRange.d.ts.map +1 -0
- package/lib/typescript/RulerRangeContext.d.ts +11 -0
- package/lib/typescript/RulerRangeContext.d.ts.map +1 -0
- package/lib/typescript/RulerRangeHeader.d.ts +12 -0
- package/lib/typescript/RulerRangeHeader.d.ts.map +1 -0
- package/lib/typescript/RulerRangeHighlight.d.ts +8 -0
- package/lib/typescript/RulerRangeHighlight.d.ts.map +1 -0
- package/lib/typescript/RulerRangeIndicator.d.ts +17 -0
- package/lib/typescript/RulerRangeIndicator.d.ts.map +1 -0
- package/lib/typescript/RulerRangeRoot.d.ts +7 -0
- package/lib/typescript/RulerRangeRoot.d.ts.map +1 -0
- package/lib/typescript/RulerRangeTicks.d.ts +14 -0
- package/lib/typescript/RulerRangeTicks.d.ts.map +1 -0
- package/lib/typescript/RulerRangeTrack.d.ts +8 -0
- package/lib/typescript/RulerRangeTrack.d.ts.map +1 -0
- package/lib/typescript/RulerResetButton.d.ts +10 -0
- package/lib/typescript/RulerResetButton.d.ts.map +1 -0
- package/lib/typescript/RulerRoot.d.ts +7 -0
- package/lib/typescript/RulerRoot.d.ts.map +1 -0
- package/lib/typescript/RulerTicks.d.ts +9 -0
- package/lib/typescript/RulerTicks.d.ts.map +1 -0
- package/lib/typescript/RulerTrack.d.ts +8 -0
- package/lib/typescript/RulerTrack.d.ts.map +1 -0
- package/lib/typescript/Tick.d.ts +3 -16
- package/lib/typescript/Tick.d.ts.map +1 -1
- package/lib/typescript/defaults.d.ts +6 -9
- package/lib/typescript/defaults.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +37 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/presets.d.ts +27 -0
- package/lib/typescript/presets.d.ts.map +1 -0
- package/lib/typescript/tickResolver.d.ts +10 -0
- package/lib/typescript/tickResolver.d.ts.map +1 -0
- package/lib/typescript/types.d.ts +302 -85
- package/lib/typescript/types.d.ts.map +1 -1
- package/lib/typescript/useHaptics.web.d.ts +17 -0
- package/lib/typescript/useHaptics.web.d.ts.map +1 -0
- package/lib/typescript/useRulerDatePicker.d.ts +26 -0
- package/lib/typescript/useRulerDatePicker.d.ts.map +1 -0
- package/lib/typescript/useRulerDateRangePicker.d.ts +40 -0
- package/lib/typescript/useRulerDateRangePicker.d.ts.map +1 -0
- package/lib/typescript/utils.d.ts +14 -12
- package/lib/typescript/utils.d.ts.map +1 -1
- package/package.json +25 -7
- package/src/LensTick.tsx +90 -0
- package/src/Ruler.ts +32 -0
- package/src/RulerContext.tsx +20 -0
- package/src/RulerDatePicker.tsx +41 -439
- package/src/RulerDateRangePicker.tsx +53 -0
- package/src/RulerHeader.tsx +125 -0
- package/src/RulerIndicator.tsx +84 -0
- package/src/RulerLensTicks.tsx +50 -0
- package/src/RulerLensTrack.tsx +45 -0
- package/src/RulerRange.ts +33 -0
- package/src/RulerRangeContext.tsx +20 -0
- package/src/RulerRangeHeader.tsx +180 -0
- package/src/RulerRangeHighlight.tsx +47 -0
- package/src/RulerRangeIndicator.tsx +85 -0
- package/src/RulerRangeRoot.tsx +87 -0
- package/src/RulerRangeTicks.tsx +68 -0
- package/src/RulerRangeTrack.tsx +39 -0
- package/src/RulerResetButton.tsx +59 -0
- package/src/RulerRoot.tsx +93 -0
- package/src/RulerTicks.tsx +57 -0
- package/src/RulerTrack.tsx +39 -0
- package/src/Tick.tsx +60 -49
- package/src/defaults.ts +21 -19
- package/src/index.ts +83 -1
- package/src/presets.ts +108 -0
- package/src/tickResolver.ts +123 -0
- package/src/types.ts +354 -102
- package/src/useHaptics.web.ts +55 -0
- package/src/useRulerDatePicker.ts +373 -0
- package/src/useRulerDateRangePicker.ts +468 -0
- package/src/utils.ts +67 -68
- package/lib/commonjs/package.json +0 -1
- 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
|
+
});
|