@dreamstack-us/kaal 0.0.1 → 0.0.3
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 +165 -0
- package/lib/module/components/CalendarGrid/CalendarGrid.js +125 -29
- package/lib/module/components/CalendarGrid/CalendarGrid.js.map +1 -1
- package/lib/module/components/CalendarGrid/CalendarGrid.styles.js +22 -17
- package/lib/module/components/CalendarGrid/CalendarGrid.styles.js.map +1 -1
- package/lib/module/components/CalendarGrid/CalendarGrid.web.js +265 -0
- package/lib/module/components/CalendarGrid/CalendarGrid.web.js.map +1 -0
- package/lib/module/components/CalendarGrid/DayCell.js +77 -53
- package/lib/module/components/CalendarGrid/DayCell.js.map +1 -1
- package/lib/module/components/CalendarGrid/DayCell.web.js +124 -0
- package/lib/module/components/CalendarGrid/DayCell.web.js.map +1 -0
- package/lib/module/components/CalendarGrid/index.js +1 -1
- package/lib/module/components/CalendarGrid/index.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.android.js +53 -21
- package/lib/module/components/DatePicker/DatePicker.android.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.ios.js +55 -23
- package/lib/module/components/DatePicker/DatePicker.ios.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.styles.js +19 -20
- package/lib/module/components/DatePicker/DatePicker.styles.js.map +1 -1
- package/lib/module/components/DatePicker/DatePicker.web.js +59 -21
- package/lib/module/components/DatePicker/DatePicker.web.js.map +1 -1
- package/lib/module/components/TimePicker/ClockFace.js +27 -7
- package/lib/module/components/TimePicker/ClockFace.js.map +1 -1
- package/lib/module/components/TimePicker/ClockFace.web.js +253 -0
- package/lib/module/components/TimePicker/ClockFace.web.js.map +1 -0
- package/lib/module/components/TimePicker/MaterialTimePicker.js +68 -16
- package/lib/module/components/TimePicker/MaterialTimePicker.js.map +1 -1
- package/lib/module/components/TimePicker/MaterialTimePicker.web.js +231 -0
- package/lib/module/components/TimePicker/MaterialTimePicker.web.js.map +1 -0
- package/lib/module/components/TimePicker/TimePicker.android.js +13 -6
- package/lib/module/components/TimePicker/TimePicker.android.js.map +1 -1
- package/lib/module/components/TimePicker/TimePicker.ios.js +14 -7
- package/lib/module/components/TimePicker/TimePicker.ios.js.map +1 -1
- package/lib/module/components/TimePicker/TimePicker.styles.js +53 -45
- package/lib/module/components/TimePicker/TimePicker.styles.js.map +1 -1
- package/lib/module/components/TimePicker/TimePicker.web.js +24 -12
- package/lib/module/components/TimePicker/TimePicker.web.js.map +1 -1
- package/lib/module/components/TimePicker/TimeWheelPicker.js +45 -10
- package/lib/module/components/TimePicker/TimeWheelPicker.js.map +1 -1
- package/lib/module/components/TimePicker/TimeWheelPicker.web.js +339 -0
- package/lib/module/components/TimePicker/TimeWheelPicker.web.js.map +1 -0
- package/lib/module/components/TimePicker/index.js +3 -3
- package/lib/module/components/TimePicker/index.js.map +1 -1
- package/lib/module/components/WheelPicker/WheelPicker.js +21 -2
- package/lib/module/components/WheelPicker/WheelPicker.js.map +1 -1
- package/lib/module/components/WheelPicker/WheelPicker.styles.js +13 -8
- package/lib/module/components/WheelPicker/WheelPicker.styles.js.map +1 -1
- package/lib/module/components/WheelPicker/WheelPicker.web.js +146 -57
- package/lib/module/components/WheelPicker/WheelPicker.web.js.map +1 -1
- package/lib/module/context/ThemeOverrideContext.js +34 -0
- package/lib/module/context/ThemeOverrideContext.js.map +1 -0
- package/lib/module/index.js +3 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/utils/validation.js +74 -34
- package/lib/module/utils/validation.js.map +1 -1
- package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts +24 -3
- package/lib/typescript/components/CalendarGrid/CalendarGrid.d.ts.map +1 -1
- package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts +12 -10
- package/lib/typescript/components/CalendarGrid/CalendarGrid.styles.d.ts.map +1 -1
- package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts +33 -0
- package/lib/typescript/components/CalendarGrid/CalendarGrid.web.d.ts.map +1 -0
- package/lib/typescript/components/CalendarGrid/DayCell.d.ts +3 -0
- package/lib/typescript/components/CalendarGrid/DayCell.d.ts.map +1 -1
- package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts +15 -0
- package/lib/typescript/components/CalendarGrid/DayCell.web.d.ts.map +1 -0
- package/lib/typescript/components/DatePicker/DatePicker.android.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.d.ts +27 -4
- package/lib/typescript/components/DatePicker/DatePicker.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.ios.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts +12 -13
- package/lib/typescript/components/DatePicker/DatePicker.styles.d.ts.map +1 -1
- package/lib/typescript/components/DatePicker/DatePicker.web.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/ClockFace.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/ClockFace.web.d.ts +12 -0
- package/lib/typescript/components/TimePicker/ClockFace.web.d.ts.map +1 -0
- package/lib/typescript/components/TimePicker/MaterialTimePicker.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts +12 -0
- package/lib/typescript/components/TimePicker/MaterialTimePicker.web.d.ts.map +1 -0
- package/lib/typescript/components/TimePicker/TimePicker.android.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimePicker.ios.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts +29 -25
- package/lib/typescript/components/TimePicker/TimePicker.styles.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimePicker.web.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimeWheelPicker.d.ts.map +1 -1
- package/lib/typescript/components/TimePicker/TimeWheelPicker.web.d.ts +11 -0
- package/lib/typescript/components/TimePicker/TimeWheelPicker.web.d.ts.map +1 -0
- package/lib/typescript/components/WheelPicker/WheelPicker.d.ts +14 -1
- package/lib/typescript/components/WheelPicker/WheelPicker.d.ts.map +1 -1
- package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts +9 -7
- package/lib/typescript/components/WheelPicker/WheelPicker.styles.d.ts.map +1 -1
- package/lib/typescript/components/WheelPicker/WheelPicker.web.d.ts.map +1 -1
- package/lib/typescript/context/ThemeOverrideContext.d.ts +23 -0
- package/lib/typescript/context/ThemeOverrideContext.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/types/datepicker.d.ts +78 -3
- package/lib/typescript/types/datepicker.d.ts.map +1 -1
- package/lib/typescript/types/timepicker.d.ts +62 -0
- package/lib/typescript/types/timepicker.d.ts.map +1 -1
- package/lib/typescript/utils/validation.d.ts +47 -27
- package/lib/typescript/utils/validation.d.ts.map +1 -1
- package/package.json +8 -8
- package/src/components/CalendarGrid/CalendarGrid.styles.ts +21 -17
- package/src/components/CalendarGrid/CalendarGrid.tsx +265 -85
- package/src/components/CalendarGrid/CalendarGrid.web.tsx +396 -0
- package/src/components/CalendarGrid/DayCell.tsx +122 -61
- package/src/components/CalendarGrid/DayCell.web.tsx +171 -0
- package/src/components/DatePicker/DatePicker.android.tsx +48 -24
- package/src/components/DatePicker/DatePicker.ios.tsx +51 -27
- package/src/components/DatePicker/DatePicker.styles.ts +18 -22
- package/src/components/DatePicker/DatePicker.tsx +35 -4
- package/src/components/DatePicker/DatePicker.web.tsx +55 -23
- package/src/components/TimePicker/ClockFace.tsx +34 -8
- package/src/components/TimePicker/ClockFace.web.tsx +303 -0
- package/src/components/TimePicker/MaterialTimePicker.tsx +144 -13
- package/src/components/TimePicker/MaterialTimePicker.web.tsx +271 -0
- package/src/components/TimePicker/TimePicker.android.tsx +9 -1
- package/src/components/TimePicker/TimePicker.ios.tsx +10 -6
- package/src/components/TimePicker/TimePicker.styles.ts +52 -45
- package/src/components/TimePicker/TimePicker.web.tsx +17 -7
- package/src/components/TimePicker/TimeWheelPicker.tsx +60 -6
- package/src/components/TimePicker/TimeWheelPicker.web.tsx +401 -0
- package/src/components/WheelPicker/WheelPicker.styles.ts +12 -8
- package/src/components/WheelPicker/WheelPicker.tsx +24 -2
- package/src/components/WheelPicker/WheelPicker.web.tsx +153 -57
- package/src/context/ThemeOverrideContext.tsx +38 -0
- package/src/index.ts +13 -0
- package/src/types/datepicker.ts +87 -3
- package/src/types/timepicker.ts +74 -0
- package/src/utils/validation.ts +111 -55
- package/lib/module/unistyles.js +0 -9
- package/lib/module/unistyles.js.map +0 -1
- package/lib/typescript/unistyles.d.ts +0 -3
- package/lib/typescript/unistyles.d.ts.map +0 -1
- package/src/unistyles.ts +0 -6
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/// <reference lib="dom" />
|
|
2
|
+
import type React from 'react';
|
|
3
|
+
import { memo, useCallback, useMemo, useRef } from 'react';
|
|
4
|
+
import { View } from 'react-native';
|
|
5
|
+
import { useTimePickerOverrides } from '../../context/ThemeOverrideContext';
|
|
6
|
+
import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
|
|
7
|
+
import type { ClockMode, TimeValue } from '../../types/timepicker';
|
|
8
|
+
import { styles } from './TimePicker.styles';
|
|
9
|
+
|
|
10
|
+
// Clock dimensions
|
|
11
|
+
const CLOCK_SIZE = 256;
|
|
12
|
+
const CLOCK_CENTER = CLOCK_SIZE / 2;
|
|
13
|
+
const OUTER_RADIUS = 96;
|
|
14
|
+
const SELECTION_DOT_RADIUS = 20;
|
|
15
|
+
const CENTER_DOT_RADIUS = 4;
|
|
16
|
+
|
|
17
|
+
// Hour positions (12 at top, clockwise)
|
|
18
|
+
const HOURS_12 = [12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];
|
|
19
|
+
const MINUTE_LABELS = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55];
|
|
20
|
+
|
|
21
|
+
// Default colors (dark theme)
|
|
22
|
+
const DEFAULT_COLORS = {
|
|
23
|
+
clockBackground: '#3F384C',
|
|
24
|
+
handColor: '#4DA6FF',
|
|
25
|
+
selectionDotColor: '#4DA6FF',
|
|
26
|
+
textColor: '#E6E1E5',
|
|
27
|
+
textSelectedColor: '#FFFFFF',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Converts clock position (0-11 for hours, 0-59 for minutes) to angle in degrees
|
|
32
|
+
* 12 o'clock is at -90 degrees (top)
|
|
33
|
+
*/
|
|
34
|
+
const positionToAngle = (position: number, isMinutes = false): number => {
|
|
35
|
+
if (isMinutes) {
|
|
36
|
+
return position * 6 - 90; // 360/60 = 6 degrees per minute
|
|
37
|
+
}
|
|
38
|
+
return position * 30 - 90; // 360/12 = 30 degrees per position
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Gets x,y coordinates on circle from angle and radius
|
|
43
|
+
*/
|
|
44
|
+
const getPointOnCircle = (
|
|
45
|
+
angleDegrees: number,
|
|
46
|
+
radius: number,
|
|
47
|
+
): { x: number; y: number } => {
|
|
48
|
+
const rad = (angleDegrees * Math.PI) / 180;
|
|
49
|
+
return {
|
|
50
|
+
x: CLOCK_CENTER + radius * Math.cos(rad),
|
|
51
|
+
y: CLOCK_CENTER + radius * Math.sin(rad),
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
interface ClockFaceProps {
|
|
56
|
+
value: TimeValue;
|
|
57
|
+
onChange: (time: TimeValue) => void;
|
|
58
|
+
mode: ClockMode;
|
|
59
|
+
onModeChange?: (mode: ClockMode) => void;
|
|
60
|
+
is24Hour?: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export const ClockFace: React.FC<ClockFaceProps> = memo(
|
|
64
|
+
({ value, onChange, mode, onModeChange, is24Hour = false }) => {
|
|
65
|
+
const overrides = useTimePickerOverrides();
|
|
66
|
+
const svgRef = useRef<SVGSVGElement>(null);
|
|
67
|
+
const isDragging = useRef(false);
|
|
68
|
+
const { hour: hour12, period } = to12Hour(value.hours);
|
|
69
|
+
|
|
70
|
+
// Build colors from overrides
|
|
71
|
+
const colors = useMemo(
|
|
72
|
+
() => ({
|
|
73
|
+
clockBackground:
|
|
74
|
+
overrides?.clockBackground ?? DEFAULT_COLORS.clockBackground,
|
|
75
|
+
handColor: overrides?.clockHandColor ?? DEFAULT_COLORS.handColor,
|
|
76
|
+
selectionDotColor:
|
|
77
|
+
overrides?.clockSelectionColor ?? DEFAULT_COLORS.selectionDotColor,
|
|
78
|
+
textColor: overrides?.clockTextColor ?? DEFAULT_COLORS.textColor,
|
|
79
|
+
textSelectedColor:
|
|
80
|
+
overrides?.clockTextSelectedColor ?? DEFAULT_COLORS.textSelectedColor,
|
|
81
|
+
}),
|
|
82
|
+
[overrides],
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Calculate hand end position based on current value
|
|
86
|
+
const handAngle = useMemo(() => {
|
|
87
|
+
if (mode === 'hours') {
|
|
88
|
+
const hourIndex = HOURS_12.indexOf(hour12);
|
|
89
|
+
return positionToAngle(hourIndex >= 0 ? hourIndex : 0);
|
|
90
|
+
}
|
|
91
|
+
return positionToAngle(value.minutes, true);
|
|
92
|
+
}, [mode, hour12, value.minutes]);
|
|
93
|
+
|
|
94
|
+
const handEndPos = getPointOnCircle(handAngle, OUTER_RADIUS);
|
|
95
|
+
|
|
96
|
+
const handleInteraction = useCallback(
|
|
97
|
+
(clientX: number, clientY: number) => {
|
|
98
|
+
if (!svgRef.current) return;
|
|
99
|
+
|
|
100
|
+
const rect = svgRef.current.getBoundingClientRect();
|
|
101
|
+
const x = clientX - rect.left;
|
|
102
|
+
const y = clientY - rect.top;
|
|
103
|
+
|
|
104
|
+
// Convert touch coordinates to angle
|
|
105
|
+
const dx = x - CLOCK_CENTER;
|
|
106
|
+
const dy = y - CLOCK_CENTER;
|
|
107
|
+
const angle = Math.atan2(dy, dx) * (180 / Math.PI);
|
|
108
|
+
|
|
109
|
+
// Normalize angle to 0-360, with 0 at top (12 o'clock)
|
|
110
|
+
const normalized = (((angle + 90) % 360) + 360) % 360;
|
|
111
|
+
|
|
112
|
+
if (mode === 'hours') {
|
|
113
|
+
// Round to nearest hour position
|
|
114
|
+
const position = Math.round(normalized / 30) % 12;
|
|
115
|
+
const hour = HOURS_12[position];
|
|
116
|
+
if (hour !== undefined) {
|
|
117
|
+
const newHours = to24Hour(hour, period);
|
|
118
|
+
onChange({ hours: newHours, minutes: value.minutes });
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
// Round to nearest minute
|
|
122
|
+
const minutes = Math.round(normalized / 6) % 60;
|
|
123
|
+
onChange({ hours: value.hours, minutes });
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
[mode, period, value, onChange],
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
const handleInteractionEnd = useCallback(() => {
|
|
130
|
+
// Switch to minutes mode after selecting hours
|
|
131
|
+
if (mode === 'hours' && onModeChange) {
|
|
132
|
+
// Small delay for visual feedback
|
|
133
|
+
setTimeout(() => onModeChange('minutes'), 200);
|
|
134
|
+
}
|
|
135
|
+
}, [mode, onModeChange]);
|
|
136
|
+
|
|
137
|
+
const handleMouseDown = useCallback(
|
|
138
|
+
(e: React.MouseEvent<SVGSVGElement>) => {
|
|
139
|
+
isDragging.current = true;
|
|
140
|
+
handleInteraction(e.clientX, e.clientY);
|
|
141
|
+
},
|
|
142
|
+
[handleInteraction],
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
const handleMouseMove = useCallback(
|
|
146
|
+
(e: React.MouseEvent<SVGSVGElement>) => {
|
|
147
|
+
if (!isDragging.current) return;
|
|
148
|
+
handleInteraction(e.clientX, e.clientY);
|
|
149
|
+
},
|
|
150
|
+
[handleInteraction],
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const handleMouseUp = useCallback(() => {
|
|
154
|
+
if (isDragging.current) {
|
|
155
|
+
isDragging.current = false;
|
|
156
|
+
handleInteractionEnd();
|
|
157
|
+
}
|
|
158
|
+
}, [handleInteractionEnd]);
|
|
159
|
+
|
|
160
|
+
const handleTouchStart = useCallback(
|
|
161
|
+
(e: React.TouchEvent<SVGSVGElement>) => {
|
|
162
|
+
isDragging.current = true;
|
|
163
|
+
const touch = e.touches[0];
|
|
164
|
+
if (touch) {
|
|
165
|
+
handleInteraction(touch.clientX, touch.clientY);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
[handleInteraction],
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const handleTouchMove = useCallback(
|
|
172
|
+
(e: React.TouchEvent<SVGSVGElement>) => {
|
|
173
|
+
if (!isDragging.current) return;
|
|
174
|
+
const touch = e.touches[0];
|
|
175
|
+
if (touch) {
|
|
176
|
+
handleInteraction(touch.clientX, touch.clientY);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
[handleInteraction],
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
const handleTouchEnd = useCallback(() => {
|
|
183
|
+
if (isDragging.current) {
|
|
184
|
+
isDragging.current = false;
|
|
185
|
+
handleInteractionEnd();
|
|
186
|
+
}
|
|
187
|
+
}, [handleInteractionEnd]);
|
|
188
|
+
|
|
189
|
+
// Render clock numbers
|
|
190
|
+
const numbers = useMemo(() => {
|
|
191
|
+
if (mode === 'hours') {
|
|
192
|
+
return HOURS_12.map((hour, index) => {
|
|
193
|
+
const angle = positionToAngle(index);
|
|
194
|
+
const pos = getPointOnCircle(angle, OUTER_RADIUS);
|
|
195
|
+
const isSelected = hour12 === hour;
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<text
|
|
199
|
+
key={hour}
|
|
200
|
+
x={pos.x}
|
|
201
|
+
y={pos.y}
|
|
202
|
+
textAnchor="middle"
|
|
203
|
+
dominantBaseline="central"
|
|
204
|
+
fontSize={14}
|
|
205
|
+
fontWeight={isSelected ? '500' : '400'}
|
|
206
|
+
fill={isSelected ? colors.textSelectedColor : colors.textColor}
|
|
207
|
+
style={{ userSelect: 'none' }}
|
|
208
|
+
>
|
|
209
|
+
{hour}
|
|
210
|
+
</text>
|
|
211
|
+
);
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return MINUTE_LABELS.map((minute, index) => {
|
|
216
|
+
const angle = positionToAngle(index);
|
|
217
|
+
const pos = getPointOnCircle(angle, OUTER_RADIUS);
|
|
218
|
+
const isSelected = value.minutes === minute;
|
|
219
|
+
|
|
220
|
+
return (
|
|
221
|
+
<text
|
|
222
|
+
key={minute}
|
|
223
|
+
x={pos.x}
|
|
224
|
+
y={pos.y}
|
|
225
|
+
textAnchor="middle"
|
|
226
|
+
dominantBaseline="central"
|
|
227
|
+
fontSize={14}
|
|
228
|
+
fontWeight={isSelected ? '500' : '400'}
|
|
229
|
+
fill={isSelected ? colors.textSelectedColor : colors.textColor}
|
|
230
|
+
style={{ userSelect: 'none' }}
|
|
231
|
+
>
|
|
232
|
+
{minute.toString().padStart(2, '0')}
|
|
233
|
+
</text>
|
|
234
|
+
);
|
|
235
|
+
});
|
|
236
|
+
}, [mode, hour12, value.minutes, colors]);
|
|
237
|
+
|
|
238
|
+
return (
|
|
239
|
+
<View style={styles.clockContainer}>
|
|
240
|
+
<svg
|
|
241
|
+
ref={svgRef}
|
|
242
|
+
width={CLOCK_SIZE}
|
|
243
|
+
height={CLOCK_SIZE}
|
|
244
|
+
viewBox={`0 0 ${CLOCK_SIZE} ${CLOCK_SIZE}`}
|
|
245
|
+
onMouseDown={handleMouseDown}
|
|
246
|
+
onMouseMove={handleMouseMove}
|
|
247
|
+
onMouseUp={handleMouseUp}
|
|
248
|
+
onMouseLeave={handleMouseUp}
|
|
249
|
+
onTouchStart={handleTouchStart}
|
|
250
|
+
onTouchMove={handleTouchMove}
|
|
251
|
+
onTouchEnd={handleTouchEnd}
|
|
252
|
+
style={{ cursor: 'pointer', touchAction: 'none' }}
|
|
253
|
+
role="img"
|
|
254
|
+
aria-label={`Clock face for selecting ${mode === 'hours' ? 'hours' : 'minutes'}`}
|
|
255
|
+
>
|
|
256
|
+
<title>
|
|
257
|
+
{mode === 'hours'
|
|
258
|
+
? 'Hour selection clock'
|
|
259
|
+
: 'Minute selection clock'}
|
|
260
|
+
</title>
|
|
261
|
+
{/* Background circle */}
|
|
262
|
+
<circle
|
|
263
|
+
cx={CLOCK_CENTER}
|
|
264
|
+
cy={CLOCK_CENTER}
|
|
265
|
+
r={CLOCK_SIZE / 2 - 4}
|
|
266
|
+
fill={colors.clockBackground}
|
|
267
|
+
/>
|
|
268
|
+
|
|
269
|
+
{/* Selection dot (behind numbers) */}
|
|
270
|
+
<circle
|
|
271
|
+
cx={handEndPos.x}
|
|
272
|
+
cy={handEndPos.y}
|
|
273
|
+
r={SELECTION_DOT_RADIUS}
|
|
274
|
+
fill={colors.selectionDotColor}
|
|
275
|
+
/>
|
|
276
|
+
|
|
277
|
+
{/* Clock hand */}
|
|
278
|
+
<line
|
|
279
|
+
x1={CLOCK_CENTER}
|
|
280
|
+
y1={CLOCK_CENTER}
|
|
281
|
+
x2={handEndPos.x}
|
|
282
|
+
y2={handEndPos.y}
|
|
283
|
+
stroke={colors.handColor}
|
|
284
|
+
strokeWidth={2}
|
|
285
|
+
/>
|
|
286
|
+
|
|
287
|
+
{/* Center dot */}
|
|
288
|
+
<circle
|
|
289
|
+
cx={CLOCK_CENTER}
|
|
290
|
+
cy={CLOCK_CENTER}
|
|
291
|
+
r={CENTER_DOT_RADIUS}
|
|
292
|
+
fill={colors.handColor}
|
|
293
|
+
/>
|
|
294
|
+
|
|
295
|
+
{/* Numbers */}
|
|
296
|
+
<g>{numbers}</g>
|
|
297
|
+
</svg>
|
|
298
|
+
</View>
|
|
299
|
+
);
|
|
300
|
+
},
|
|
301
|
+
);
|
|
302
|
+
|
|
303
|
+
ClockFace.displayName = 'ClockFace';
|
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import { memo, useCallback, useState } from 'react';
|
|
2
|
+
import { memo, useCallback, useMemo, useState } from 'react';
|
|
3
3
|
import { Pressable, Text, View } from 'react-native';
|
|
4
|
+
import { useTimePickerOverrides } from '../../context/ThemeOverrideContext';
|
|
4
5
|
import { to12Hour, to24Hour } from '../../hooks/useTimePicker';
|
|
5
6
|
import type { ClockMode, TimePeriod, TimeValue } from '../../types/timepicker';
|
|
6
7
|
import { ClockFace } from './ClockFace';
|
|
7
8
|
import { styles } from './TimePicker.styles';
|
|
8
9
|
|
|
10
|
+
// Default colors (dark theme)
|
|
11
|
+
const DEFAULT_COLORS = {
|
|
12
|
+
containerBackground: '#2C2C2E',
|
|
13
|
+
headerColor: '#8E8E93',
|
|
14
|
+
timeFieldBackground: '#3A3A3C',
|
|
15
|
+
timeFieldActiveBackground: '#4DA6FF',
|
|
16
|
+
textColor: '#FFFFFF',
|
|
17
|
+
separatorColor: '#FFFFFF',
|
|
18
|
+
borderColor: '#48484A',
|
|
19
|
+
periodActiveBackground: 'rgba(77, 166, 255, 0.2)',
|
|
20
|
+
periodTextColor: '#8E8E93',
|
|
21
|
+
periodTextActiveColor: '#4DA6FF',
|
|
22
|
+
actionButtonColor: '#4DA6FF',
|
|
23
|
+
};
|
|
24
|
+
|
|
9
25
|
interface MaterialTimePickerProps {
|
|
10
26
|
value: TimeValue;
|
|
11
27
|
onChange: (time: TimeValue) => void;
|
|
@@ -16,6 +32,7 @@ interface MaterialTimePickerProps {
|
|
|
16
32
|
|
|
17
33
|
export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
18
34
|
({ value, onChange, is24Hour = false, onCancel, onConfirm }) => {
|
|
35
|
+
const overrides = useTimePickerOverrides();
|
|
19
36
|
const [mode, setMode] = useState<ClockMode>('hours');
|
|
20
37
|
const { hour: hour12, period } = to12Hour(value.hours);
|
|
21
38
|
|
|
@@ -45,10 +62,96 @@ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
|
45
62
|
: hour12.toString().padStart(2, '0');
|
|
46
63
|
const displayMinute = value.minutes.toString().padStart(2, '0');
|
|
47
64
|
|
|
65
|
+
// Build override styles
|
|
66
|
+
const containerStyle = useMemo(
|
|
67
|
+
() => ({
|
|
68
|
+
backgroundColor:
|
|
69
|
+
overrides?.containerBackground ?? DEFAULT_COLORS.containerBackground,
|
|
70
|
+
}),
|
|
71
|
+
[overrides],
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
const headerStyle = useMemo(
|
|
75
|
+
() => ({
|
|
76
|
+
color: overrides?.headerColor ?? DEFAULT_COLORS.headerColor,
|
|
77
|
+
}),
|
|
78
|
+
[overrides],
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const timeFieldStyle = useMemo(
|
|
82
|
+
() => ({
|
|
83
|
+
backgroundColor:
|
|
84
|
+
overrides?.timeFieldBackground ?? DEFAULT_COLORS.timeFieldBackground,
|
|
85
|
+
}),
|
|
86
|
+
[overrides],
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
const timeFieldActiveStyle = useMemo(
|
|
90
|
+
() => ({
|
|
91
|
+
backgroundColor:
|
|
92
|
+
overrides?.timeFieldActiveBackground ??
|
|
93
|
+
DEFAULT_COLORS.timeFieldActiveBackground,
|
|
94
|
+
}),
|
|
95
|
+
[overrides],
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
const textStyle = useMemo(
|
|
99
|
+
() => ({
|
|
100
|
+
color: overrides?.textColor ?? DEFAULT_COLORS.textColor,
|
|
101
|
+
}),
|
|
102
|
+
[overrides],
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const separatorStyle = useMemo(
|
|
106
|
+
() => ({
|
|
107
|
+
color: overrides?.separatorColor ?? DEFAULT_COLORS.separatorColor,
|
|
108
|
+
}),
|
|
109
|
+
[overrides],
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const periodContainerStyle = useMemo(
|
|
113
|
+
() => ({
|
|
114
|
+
borderColor: overrides?.periodBorderColor ?? DEFAULT_COLORS.borderColor,
|
|
115
|
+
}),
|
|
116
|
+
[overrides],
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
const periodActiveStyle = useMemo(
|
|
120
|
+
() => ({
|
|
121
|
+
backgroundColor:
|
|
122
|
+
overrides?.periodActiveBackground ??
|
|
123
|
+
DEFAULT_COLORS.periodActiveBackground,
|
|
124
|
+
}),
|
|
125
|
+
[overrides],
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
const periodTextStyle = useMemo(
|
|
129
|
+
() => ({
|
|
130
|
+
color: overrides?.periodTextColor ?? DEFAULT_COLORS.periodTextColor,
|
|
131
|
+
}),
|
|
132
|
+
[overrides],
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const periodTextActiveStyle = useMemo(
|
|
136
|
+
() => ({
|
|
137
|
+
color:
|
|
138
|
+
overrides?.periodTextActiveColor ??
|
|
139
|
+
DEFAULT_COLORS.periodTextActiveColor,
|
|
140
|
+
}),
|
|
141
|
+
[overrides],
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const actionButtonTextStyle = useMemo(
|
|
145
|
+
() => ({
|
|
146
|
+
color: overrides?.actionButtonColor ?? DEFAULT_COLORS.actionButtonColor,
|
|
147
|
+
}),
|
|
148
|
+
[overrides],
|
|
149
|
+
);
|
|
150
|
+
|
|
48
151
|
return (
|
|
49
|
-
<View style={styles.materialContainer}>
|
|
152
|
+
<View style={[styles.materialContainer, containerStyle]}>
|
|
50
153
|
{/* Header */}
|
|
51
|
-
<Text style={styles.materialHeader}>Select time</Text>
|
|
154
|
+
<Text style={[styles.materialHeader, headerStyle]}>Select time</Text>
|
|
52
155
|
|
|
53
156
|
{/* Time Input Display */}
|
|
54
157
|
<View style={styles.timeInputContainer}>
|
|
@@ -58,12 +161,17 @@ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
|
58
161
|
onPress={handleHourPress}
|
|
59
162
|
style={[
|
|
60
163
|
styles.timeField,
|
|
61
|
-
|
|
164
|
+
timeFieldStyle,
|
|
165
|
+
mode === 'hours' && [
|
|
166
|
+
styles.timeFieldActive,
|
|
167
|
+
timeFieldActiveStyle,
|
|
168
|
+
],
|
|
62
169
|
]}
|
|
63
170
|
>
|
|
64
171
|
<Text
|
|
65
172
|
style={[
|
|
66
173
|
styles.timeFieldText,
|
|
174
|
+
textStyle,
|
|
67
175
|
mode === 'hours' && styles.timeFieldTextActive,
|
|
68
176
|
]}
|
|
69
177
|
>
|
|
@@ -72,19 +180,24 @@ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
|
72
180
|
</Pressable>
|
|
73
181
|
|
|
74
182
|
{/* Separator */}
|
|
75
|
-
<Text style={styles.timeSeparator}>:</Text>
|
|
183
|
+
<Text style={[styles.timeSeparator, separatorStyle]}>:</Text>
|
|
76
184
|
|
|
77
185
|
{/* Minute Field */}
|
|
78
186
|
<Pressable
|
|
79
187
|
onPress={handleMinutePress}
|
|
80
188
|
style={[
|
|
81
189
|
styles.timeField,
|
|
82
|
-
|
|
190
|
+
timeFieldStyle,
|
|
191
|
+
mode === 'minutes' && [
|
|
192
|
+
styles.timeFieldActive,
|
|
193
|
+
timeFieldActiveStyle,
|
|
194
|
+
],
|
|
83
195
|
]}
|
|
84
196
|
>
|
|
85
197
|
<Text
|
|
86
198
|
style={[
|
|
87
199
|
styles.timeFieldText,
|
|
200
|
+
textStyle,
|
|
88
201
|
mode === 'minutes' && styles.timeFieldTextActive,
|
|
89
202
|
]}
|
|
90
203
|
>
|
|
@@ -95,19 +208,26 @@ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
|
95
208
|
|
|
96
209
|
{/* AM/PM Toggle (only for 12-hour format) */}
|
|
97
210
|
{!is24Hour && (
|
|
98
|
-
<View style={styles.periodToggleContainer}>
|
|
211
|
+
<View style={[styles.periodToggleContainer, periodContainerStyle]}>
|
|
99
212
|
<Pressable
|
|
100
213
|
onPress={() => handlePeriodChange('AM')}
|
|
101
214
|
style={[
|
|
102
215
|
styles.periodButton,
|
|
103
216
|
styles.periodButtonTop,
|
|
104
|
-
period === 'AM' &&
|
|
217
|
+
period === 'AM' && [
|
|
218
|
+
styles.periodButtonActive,
|
|
219
|
+
periodActiveStyle,
|
|
220
|
+
],
|
|
105
221
|
]}
|
|
106
222
|
>
|
|
107
223
|
<Text
|
|
108
224
|
style={[
|
|
109
225
|
styles.periodButtonText,
|
|
110
|
-
|
|
226
|
+
periodTextStyle,
|
|
227
|
+
period === 'AM' && [
|
|
228
|
+
styles.periodButtonTextActive,
|
|
229
|
+
periodTextActiveStyle,
|
|
230
|
+
],
|
|
111
231
|
]}
|
|
112
232
|
>
|
|
113
233
|
AM
|
|
@@ -117,13 +237,20 @@ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
|
117
237
|
onPress={() => handlePeriodChange('PM')}
|
|
118
238
|
style={[
|
|
119
239
|
styles.periodButton,
|
|
120
|
-
period === 'PM' &&
|
|
240
|
+
period === 'PM' && [
|
|
241
|
+
styles.periodButtonActive,
|
|
242
|
+
periodActiveStyle,
|
|
243
|
+
],
|
|
121
244
|
]}
|
|
122
245
|
>
|
|
123
246
|
<Text
|
|
124
247
|
style={[
|
|
125
248
|
styles.periodButtonText,
|
|
126
|
-
|
|
249
|
+
periodTextStyle,
|
|
250
|
+
period === 'PM' && [
|
|
251
|
+
styles.periodButtonTextActive,
|
|
252
|
+
periodTextActiveStyle,
|
|
253
|
+
],
|
|
127
254
|
]}
|
|
128
255
|
>
|
|
129
256
|
PM
|
|
@@ -151,12 +278,16 @@ export const MaterialTimePicker: React.FC<MaterialTimePickerProps> = memo(
|
|
|
151
278
|
<View style={styles.actionButtonsContainer}>
|
|
152
279
|
{onCancel && (
|
|
153
280
|
<Pressable style={styles.actionButton} onPress={onCancel}>
|
|
154
|
-
<Text style={styles.actionButtonText}>
|
|
281
|
+
<Text style={[styles.actionButtonText, actionButtonTextStyle]}>
|
|
282
|
+
Cancel
|
|
283
|
+
</Text>
|
|
155
284
|
</Pressable>
|
|
156
285
|
)}
|
|
157
286
|
{onConfirm && (
|
|
158
287
|
<Pressable style={styles.actionButton} onPress={onConfirm}>
|
|
159
|
-
<Text style={styles.actionButtonText}>
|
|
288
|
+
<Text style={[styles.actionButtonText, actionButtonTextStyle]}>
|
|
289
|
+
OK
|
|
290
|
+
</Text>
|
|
160
291
|
</Pressable>
|
|
161
292
|
)}
|
|
162
293
|
</View>
|