flikkui 0.2.0-beta.1 → 0.2.0-beta.4
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 +12 -539
- package/dist/components/ai/PromptInput/PromptInput.js +94 -4
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.d.ts +27 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.js +62 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.d.ts +10 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.theme.js +12 -0
- package/dist/components/ai/PromptSuggestions/PromptSuggestion.types.d.ts +53 -0
- package/dist/components/ai/PromptSuggestions/index.d.ts +4 -2
- package/dist/components/ai/index.d.ts +2 -12
- package/dist/components/charts/ActivityRings/ActivityRings.js +70 -58
- package/dist/components/charts/ActivityRings/ActivityRings.theme.js +0 -1
- package/dist/components/charts/ActivityRings/ActivityRings.types.d.ts +17 -0
- package/dist/components/charts/BarChart/BarChart.js +8 -4
- package/dist/components/charts/BarChart/BarChart.types.d.ts +14 -0
- package/dist/components/charts/DonutChart/DonutChart.js +11 -8
- package/dist/components/charts/DonutChart/DonutChart.theme.d.ts +3 -0
- package/dist/components/charts/DonutChart/DonutChart.theme.js +5 -4
- package/dist/components/charts/DonutChart/donut-utils.d.ts +5 -0
- package/dist/components/charts/DonutChart/donut-utils.js +26 -1
- package/dist/components/charts/Heatmap/Heatmap.theme.js +2 -2
- package/dist/components/charts/shared/ChartAxis/XAxis.d.ts +2 -2
- package/dist/components/charts/shared/ChartAxis/XAxis.js +4 -4
- package/dist/components/charts/shared/ChartAxis/YAxis.d.ts +2 -2
- package/dist/components/charts/shared/ChartAxis/YAxis.js +8 -7
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.d.ts +1 -1
- package/dist/components/charts/shared/ChartGrid/HorizontalGrid.js +2 -2
- package/dist/components/charts/theme/chart.theme.d.ts +1 -1
- package/dist/components/charts/theme/chart.theme.js +39 -39
- package/dist/components/core/Accordion/Accordion.d.ts +1 -1
- package/dist/components/core/Accordion/Accordion.js +2 -2
- package/dist/components/core/Accordion/Accordion.types.d.ts +8 -0
- package/dist/components/core/Badge/Badge.js +11 -15
- package/dist/components/core/Badge/Badge.theme.js +7 -21
- package/dist/components/core/Badge/Badge.types.d.ts +9 -1
- package/dist/components/core/Button/Button.js +2 -2
- package/dist/components/core/Button/Button.theme.js +1 -1
- package/dist/components/core/Button/Button.types.d.ts +8 -0
- package/dist/components/core/Card/Card.js +8 -2
- package/dist/components/core/Card/Card.theme.js +1 -1
- package/dist/components/core/Card/Card.types.d.ts +24 -1
- package/dist/components/core/Drawer/Drawer.d.ts +1 -1
- package/dist/components/core/Drawer/Drawer.js +10 -40
- package/dist/components/core/Drawer/Drawer.theme.js +2 -1
- package/dist/components/core/Drawer/Drawer.types.d.ts +8 -0
- package/dist/components/core/Dropdown/Dropdown.d.ts +1 -1
- package/dist/components/core/Dropdown/Dropdown.js +2 -2
- package/dist/components/core/Dropdown/Dropdown.types.d.ts +8 -0
- package/dist/components/core/Metric/Metric.d.ts +1 -1
- package/dist/components/core/Metric/Metric.js +9 -5
- package/dist/components/core/Metric/Metric.theme.d.ts +1 -1
- package/dist/components/core/Metric/Metric.theme.js +38 -28
- package/dist/components/core/Metric/Metric.types.d.ts +27 -8
- package/dist/components/core/Modal/Modal.d.ts +1 -1
- package/dist/components/core/Modal/Modal.js +17 -40
- package/dist/components/core/Modal/Modal.theme.js +8 -3
- package/dist/components/core/Modal/Modal.types.d.ts +18 -0
- package/dist/components/core/Modal/index.d.ts +1 -1
- package/dist/components/core/Notification/Notification.js +67 -0
- package/dist/components/core/Pill/Pill.d.ts +6 -11
- package/dist/components/core/Pill/Pill.theme.d.ts +2 -2
- package/dist/components/core/Pill/Pill.types.d.ts +9 -22
- package/dist/components/core/Pill/index.d.ts +1 -1
- package/dist/components/core/Popover/Popover.d.ts +1 -1
- package/dist/components/core/Popover/Popover.js +2 -2
- package/dist/components/core/Popover/Popover.types.d.ts +8 -0
- package/dist/components/core/Progress/Progress.d.ts +28 -0
- package/dist/components/core/Progress/Progress.js +114 -0
- package/dist/components/core/Progress/Progress.theme.d.ts +5 -0
- package/dist/components/core/Progress/Progress.theme.js +33 -0
- package/dist/components/core/Progress/Progress.types.d.ts +92 -0
- package/dist/components/core/Progress/index.d.ts +2 -0
- package/dist/components/core/Tabs/Tabs.js +2 -2
- package/dist/components/core/Tabs/Tabs.types.d.ts +8 -0
- package/dist/components/core/Tag/Tag.animations.d.ts +3 -0
- package/dist/components/core/Tag/Tag.animations.js +31 -0
- package/dist/components/core/Tag/Tag.d.ts +14 -0
- package/dist/components/core/Tag/Tag.js +45 -0
- package/dist/components/core/Tag/Tag.theme.d.ts +2 -0
- package/dist/components/core/Tag/Tag.theme.js +21 -0
- package/dist/components/core/Tag/Tag.types.d.ts +40 -0
- package/dist/components/core/Tag/index.d.ts +3 -0
- package/dist/components/core/Tooltip/Tooltip.d.ts +1 -1
- package/dist/components/core/Tooltip/Tooltip.js +3 -3
- package/dist/components/core/Tooltip/Tooltip.theme.js +1 -1
- package/dist/components/core/Tooltip/Tooltip.types.d.ts +17 -0
- package/dist/components/core/index.d.ts +2 -1
- package/dist/components/core/index.js +3 -2
- package/dist/components/effects/CustomCursor/CustomCursor.d.ts +0 -13
- package/dist/components/effects/CustomCursor/CustomCursor.js +26 -2
- package/dist/components/effects/CustomCursor/CustomCursor.theme.js +12 -1
- package/dist/components/effects/CustomCursor/CustomCursor.types.d.ts +14 -1
- package/dist/components/forms/Combobox/Combobox.d.ts +25 -0
- package/dist/components/forms/Combobox/Combobox.js +412 -0
- package/dist/components/forms/Combobox/Combobox.theme.d.ts +6 -0
- package/dist/components/forms/Combobox/Combobox.theme.js +60 -0
- package/dist/components/forms/Combobox/Combobox.types.d.ts +111 -0
- package/dist/components/forms/Combobox/index.d.ts +3 -0
- package/dist/components/forms/FileUpload/FileUpload.js +68 -0
- package/dist/components/forms/Input/Input.js +25 -28
- package/dist/components/forms/Input/inputMasks.d.ts +15 -0
- package/dist/components/forms/Input/inputMasks.js +72 -1
- package/dist/components/forms/InputTag/InputTag.d.ts +40 -0
- package/dist/components/forms/InputTag/InputTag.js +491 -0
- package/dist/components/forms/InputTag/InputTag.theme.d.ts +2 -0
- package/dist/components/forms/InputTag/InputTag.theme.js +16 -0
- package/dist/components/forms/InputTag/InputTag.types.d.ts +107 -0
- package/dist/components/forms/InputTag/index.d.ts +3 -0
- package/dist/components/forms/Select/Select.d.ts +101 -2
- package/dist/components/forms/Select/Select.js +128 -132
- package/dist/components/forms/Select/Select.theme.js +10 -14
- package/dist/components/forms/Select/Select.types.d.ts +6 -2
- package/dist/components/forms/Select/index.d.ts +7 -4
- package/dist/components/forms/Select/useSelectState.d.ts +66 -0
- package/dist/components/forms/Select/useSelectState.js +134 -0
- package/dist/components/forms/SelectExpand/SelectExpand.animations.d.ts +20 -0
- package/dist/components/forms/SelectExpand/SelectExpand.animations.js +74 -0
- package/dist/components/forms/SelectExpand/SelectExpand.d.ts +9 -0
- package/dist/components/forms/SelectExpand/SelectExpand.js +223 -0
- package/dist/components/forms/SelectExpand/SelectExpand.theme.d.ts +5 -0
- package/dist/components/forms/SelectExpand/SelectExpand.theme.js +74 -0
- package/dist/components/forms/SelectExpand/SelectExpand.types.d.ts +126 -0
- package/dist/components/forms/SelectExpand/index.d.ts +4 -0
- package/dist/components/forms/Switch/Switch.js +3 -3
- package/dist/components/forms/Switch/Switch.theme.d.ts +1 -1
- package/dist/components/forms/Switch/Switch.theme.js +2 -2
- package/dist/components/forms/TimePicker/TimePicker.animations.d.ts +0 -46
- package/dist/components/forms/TimePicker/TimePicker.d.ts +15 -6
- package/dist/components/forms/TimePicker/TimePicker.js +285 -124
- package/dist/components/forms/TimePicker/TimePicker.theme.d.ts +1 -1
- package/dist/components/forms/TimePicker/TimePicker.theme.js +39 -22
- package/dist/components/forms/TimePicker/TimePicker.types.d.ts +88 -34
- package/dist/components/forms/TimePicker/TimePickerContent.d.ts +7 -10
- package/dist/components/forms/TimePicker/TimePickerContent.js +149 -16
- package/dist/components/forms/TimePicker/TimePickerTrigger.d.ts +3 -3
- package/dist/components/forms/TimePicker/TimePickerTrigger.js +22 -19
- package/dist/components/forms/TimePicker/WheelColumn.d.ts +14 -0
- package/dist/components/forms/TimePicker/WheelColumn.js +90 -0
- package/dist/components/forms/TimePicker/index.d.ts +4 -1
- package/dist/components/forms/TimePicker/useWheelPicker.d.ts +37 -0
- package/dist/components/forms/TimePicker/useWheelPicker.js +138 -0
- package/dist/components/forms/forms.theme.d.ts +14 -0
- package/dist/components/forms/forms.theme.js +31 -0
- package/dist/components/forms/index.d.ts +9 -3
- package/dist/components/forms/index.js +73 -2
- package/dist/hooks/index.d.ts +0 -4
- package/dist/icons/Icon.d.ts +7 -0
- package/dist/icons/Icon.js +6 -2
- package/dist/index.js +62 -63
- package/dist/styles.css +1 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/optimisticErrors.js +1 -70
- package/package.json +1 -1
- package/dist/components/ai/EditingIndicator/EditingIndicator.animations.d.ts +0 -31
- package/dist/components/ai/EditingIndicator/EditingIndicator.animations.js +0 -115
- package/dist/components/ai/EditingIndicator/EditingIndicator.d.ts +0 -35
- package/dist/components/ai/EditingIndicator/EditingIndicator.js +0 -94
- package/dist/components/ai/EditingIndicator/EditingIndicator.theme.d.ts +0 -2
- package/dist/components/ai/EditingIndicator/EditingIndicator.theme.js +0 -13
- package/dist/components/ai/EditingIndicator/EditingIndicator.types.d.ts +0 -54
- package/dist/components/ai/EditingIndicator/index.d.ts +0 -9
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.d.ts +0 -3
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.js +0 -126
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.theme.d.ts +0 -2
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.theme.js +0 -8
- package/dist/components/ai/GenerativeRenderer/GenerativeRenderer.types.d.ts +0 -45
- package/dist/components/ai/GenerativeRenderer/index.d.ts +0 -3
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.animations.d.ts +0 -17
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.animations.js +0 -56
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.d.ts +0 -38
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.js +0 -110
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.theme.d.ts +0 -2
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.theme.js +0 -13
- package/dist/components/ai/PresenceIndicator/PresenceIndicator.types.d.ts +0 -53
- package/dist/components/ai/PresenceIndicator/index.d.ts +0 -8
- package/dist/components/ai/PresenceProvider/PresenceContext.d.ts +0 -24
- package/dist/components/ai/PresenceProvider/PresenceContext.js +0 -34
- package/dist/components/ai/PresenceProvider/PresenceProvider.d.ts +0 -32
- package/dist/components/ai/PresenceProvider/PresenceProvider.js +0 -321
- package/dist/components/ai/PresenceProvider/PresenceProvider.types.d.ts +0 -140
- package/dist/components/ai/PresenceProvider/adapters/MockAdapter.d.ts +0 -102
- package/dist/components/ai/PresenceProvider/adapters/MockAdapter.js +0 -331
- package/dist/components/ai/PresenceProvider/adapters/PresenceAdapter.d.ts +0 -93
- package/dist/components/ai/PresenceProvider/adapters/SupabaseAdapter.d.ts +0 -134
- package/dist/components/ai/PresenceProvider/adapters/WebSocketAdapter.d.ts +0 -149
- package/dist/components/ai/PresenceProvider/adapters/index.d.ts +0 -11
- package/dist/components/ai/PresenceProvider/index.d.ts +0 -10
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.d.ts +0 -27
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.js +0 -61
- package/dist/components/ai/PromptSuggestions/PromptSuggestions.types.d.ts +0 -65
- package/dist/components/ai/VersionSlider/VersionSlider.d.ts +0 -3
- package/dist/components/ai/VersionSlider/VersionSlider.js +0 -97
- package/dist/components/ai/VersionSlider/VersionSlider.theme.d.ts +0 -2
- package/dist/components/ai/VersionSlider/VersionSlider.theme.js +0 -18
- package/dist/components/ai/VersionSlider/VersionSlider.types.d.ts +0 -77
- package/dist/components/ai/VersionSlider/index.d.ts +0 -3
- package/dist/components/core/Pill/Pill.animations.js +0 -25
- package/dist/components/core/Pill/Pill.js +0 -145
- package/dist/components/core/Pill/Pill.theme.js +0 -65
- package/dist/components/core/RetryBoundary/RetryBoundary.d.ts +0 -35
- package/dist/components/core/RetryBoundary/RetryBoundary.js +0 -154
- package/dist/components/core/RetryBoundary/RetryBoundary.theme.d.ts +0 -2
- package/dist/components/core/RetryBoundary/RetryBoundary.theme.js +0 -7
- package/dist/components/core/RetryBoundary/RetryBoundary.types.d.ts +0 -51
- package/dist/components/core/RetryBoundary/index.d.ts +0 -3
- package/dist/components/forms/OptimisticForm/OptimisticForm.d.ts +0 -33
- package/dist/components/forms/OptimisticForm/OptimisticForm.js +0 -87
- package/dist/components/forms/OptimisticForm/OptimisticForm.theme.d.ts +0 -2
- package/dist/components/forms/OptimisticForm/OptimisticForm.theme.js +0 -8
- package/dist/components/forms/OptimisticForm/OptimisticForm.types.d.ts +0 -74
- package/dist/components/forms/OptimisticForm/index.d.ts +0 -3
- package/dist/hooks/useOptimisticMutation.d.ts +0 -109
- package/dist/hooks/useOptimisticMutation.js +0 -171
- package/dist/hooks/usePresence.d.ts +0 -88
- package/dist/utils/presenceUtils.d.ts +0 -66
- package/dist/utils/presenceUtils.js +0 -107
|
@@ -2,36 +2,35 @@ import React__default, { createContext, useState, useRef, useCallback, useEffect
|
|
|
2
2
|
import { FormLabel } from '../FormLabel/FormLabel.js';
|
|
3
3
|
import { timePickerTheme } from './TimePicker.theme.js';
|
|
4
4
|
import { cn } from '../../../utils/cn.js';
|
|
5
|
-
import { useClickOutside } from '../../../utils/useClickOutside.js';
|
|
6
5
|
import { TimePickerTrigger } from './TimePickerTrigger.js';
|
|
7
6
|
import { TimePickerContent } from './TimePickerContent.js';
|
|
7
|
+
import { WheelColumn } from './WheelColumn.js';
|
|
8
|
+
import { formatTimeInput, isValidTimeInput } from '../Input/inputMasks.js';
|
|
8
9
|
|
|
9
10
|
const TimePickerContext = createContext(undefined);
|
|
10
11
|
/**
|
|
11
12
|
* Utility functions for time handling
|
|
12
13
|
*/
|
|
13
14
|
// Parse time string into components - enhanced to handle various input formats
|
|
14
|
-
const parseTime = (timeString, format =
|
|
15
|
-
if (!timeString || timeString.trim() ===
|
|
15
|
+
const parseTime = (timeString, format = "24h") => {
|
|
16
|
+
if (!timeString || timeString.trim() === "")
|
|
16
17
|
return null;
|
|
17
18
|
// Clean the input string
|
|
18
19
|
const cleanTime = timeString.trim().toUpperCase();
|
|
19
20
|
// Handle various input formats
|
|
20
21
|
let parsedHour = 0;
|
|
21
22
|
let parsedMinute = 0;
|
|
22
|
-
let parsedSecond = 0;
|
|
23
23
|
let parsedPeriod;
|
|
24
24
|
// Try different parsing strategies
|
|
25
|
-
// Strategy 1: Standard format with colons (14:30, 2:30 PM
|
|
26
|
-
const standardRegex = format ===
|
|
27
|
-
? /^(\d{1,2}):(\d{2})
|
|
28
|
-
: /^(\d{1,2}):(\d{2})
|
|
25
|
+
// Strategy 1: Standard format with colons (14:30, 2:30 PM)
|
|
26
|
+
const standardRegex = format === "12h"
|
|
27
|
+
? /^(\d{1,2}):(\d{2})\s*(AM|PM)?$/i
|
|
28
|
+
: /^(\d{1,2}):(\d{2})$/;
|
|
29
29
|
let match = cleanTime.match(standardRegex);
|
|
30
30
|
if (match) {
|
|
31
31
|
parsedHour = parseInt(match[1], 10);
|
|
32
32
|
parsedMinute = parseInt(match[2], 10);
|
|
33
|
-
|
|
34
|
-
parsedPeriod = match[4] ? match[4] : undefined;
|
|
33
|
+
parsedPeriod = match[3] ? match[3] : undefined;
|
|
35
34
|
}
|
|
36
35
|
else {
|
|
37
36
|
// Strategy 2: Flexible format (230, 230p, 1430, etc.)
|
|
@@ -39,7 +38,11 @@ const parseTime = (timeString, format = '24h') => {
|
|
|
39
38
|
match = cleanTime.match(flexibleRegex);
|
|
40
39
|
if (match) {
|
|
41
40
|
const digits = match[1];
|
|
42
|
-
parsedPeriod = match[2]
|
|
41
|
+
parsedPeriod = match[2]
|
|
42
|
+
? match[2].length === 1
|
|
43
|
+
? `${match[2]}M`
|
|
44
|
+
: match[2]
|
|
45
|
+
: undefined;
|
|
43
46
|
if (digits.length <= 2) {
|
|
44
47
|
// Just hours (2, 14)
|
|
45
48
|
parsedHour = parseInt(digits, 10);
|
|
@@ -61,55 +64,51 @@ const parseTime = (timeString, format = '24h') => {
|
|
|
61
64
|
}
|
|
62
65
|
}
|
|
63
66
|
// Convert 12-hour to 24-hour for internal consistency
|
|
64
|
-
if (format ===
|
|
65
|
-
if (parsedPeriod ===
|
|
67
|
+
if (format === "12h" && parsedPeriod) {
|
|
68
|
+
if (parsedPeriod === "AM" && parsedHour === 12) {
|
|
66
69
|
parsedHour = 0;
|
|
67
70
|
}
|
|
68
|
-
else if (parsedPeriod ===
|
|
71
|
+
else if (parsedPeriod === "PM" && parsedHour !== 12) {
|
|
69
72
|
parsedHour += 12;
|
|
70
73
|
}
|
|
71
74
|
}
|
|
72
75
|
// Validate parsed values
|
|
73
|
-
if (parsedHour < 0 ||
|
|
76
|
+
if (parsedHour < 0 ||
|
|
77
|
+
parsedHour > 23 ||
|
|
78
|
+
parsedMinute < 0 ||
|
|
79
|
+
parsedMinute > 59) {
|
|
74
80
|
return null;
|
|
75
81
|
}
|
|
76
82
|
return {
|
|
77
83
|
hour: parsedHour,
|
|
78
84
|
minute: parsedMinute,
|
|
79
|
-
|
|
80
|
-
period: format === '12h' ? (parsedHour >= 12 ? 'PM' : 'AM') : undefined
|
|
85
|
+
period: format === "12h" ? (parsedHour >= 12 ? "PM" : "AM") : undefined,
|
|
81
86
|
};
|
|
82
87
|
};
|
|
83
88
|
// Format time components into string
|
|
84
|
-
const formatTime = (time, format =
|
|
85
|
-
let { hour, minute,
|
|
86
|
-
if (format ===
|
|
89
|
+
const formatTime = (time, format = "24h") => {
|
|
90
|
+
let { hour, minute, period } = time;
|
|
91
|
+
if (format === "12h") {
|
|
87
92
|
// Convert to 12-hour format
|
|
88
93
|
if (hour === 0) {
|
|
89
94
|
hour = 12;
|
|
90
|
-
period =
|
|
95
|
+
period = "AM";
|
|
91
96
|
}
|
|
92
97
|
else if (hour === 12) {
|
|
93
|
-
period =
|
|
98
|
+
period = "PM";
|
|
94
99
|
}
|
|
95
100
|
else if (hour > 12) {
|
|
96
101
|
hour = hour - 12;
|
|
97
|
-
period =
|
|
102
|
+
period = "PM";
|
|
98
103
|
}
|
|
99
104
|
else {
|
|
100
|
-
period =
|
|
105
|
+
period = "AM";
|
|
101
106
|
}
|
|
102
|
-
|
|
103
|
-
? `${hour}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`
|
|
104
|
-
: `${hour}:${minute.toString().padStart(2, '0')}`;
|
|
105
|
-
return `${timeStr} ${period}`;
|
|
107
|
+
return `${hour}:${minute.toString().padStart(2, "0")} ${period}`;
|
|
106
108
|
}
|
|
107
109
|
else {
|
|
108
110
|
// 24-hour format
|
|
109
|
-
|
|
110
|
-
? `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}:${second.toString().padStart(2, '0')}`
|
|
111
|
-
: `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')}`;
|
|
112
|
-
return timeStr;
|
|
111
|
+
return `${hour.toString().padStart(2, "0")}:${minute.toString().padStart(2, "0")}`;
|
|
113
112
|
}
|
|
114
113
|
};
|
|
115
114
|
// Validate time against constraints
|
|
@@ -117,12 +116,12 @@ const validateTime = (time, minTime, maxTime) => {
|
|
|
117
116
|
const { hour, minute } = time;
|
|
118
117
|
// Basic validation
|
|
119
118
|
if (hour < 0 || hour > 23 || minute < 0 || minute > 59) {
|
|
120
|
-
return { isValid: false, error:
|
|
119
|
+
return { isValid: false, error: "Invalid time" };
|
|
121
120
|
}
|
|
122
121
|
// Convert to minutes for comparison
|
|
123
122
|
const timeMinutes = hour * 60 + minute;
|
|
124
123
|
if (minTime) {
|
|
125
|
-
const minParsed = parseTime(minTime,
|
|
124
|
+
const minParsed = parseTime(minTime, "24h");
|
|
126
125
|
if (minParsed) {
|
|
127
126
|
const minMinutes = minParsed.hour * 60 + minParsed.minute;
|
|
128
127
|
if (timeMinutes < minMinutes) {
|
|
@@ -131,7 +130,7 @@ const validateTime = (time, minTime, maxTime) => {
|
|
|
131
130
|
}
|
|
132
131
|
}
|
|
133
132
|
if (maxTime) {
|
|
134
|
-
const maxParsed = parseTime(maxTime,
|
|
133
|
+
const maxParsed = parseTime(maxTime, "24h");
|
|
135
134
|
if (maxParsed) {
|
|
136
135
|
const maxMinutes = maxParsed.hour * 60 + maxParsed.minute;
|
|
137
136
|
if (timeMinutes > maxMinutes) {
|
|
@@ -143,7 +142,7 @@ const validateTime = (time, minTime, maxTime) => {
|
|
|
143
142
|
};
|
|
144
143
|
// Generate time options for selectors
|
|
145
144
|
const generateHours = (format) => {
|
|
146
|
-
if (format ===
|
|
145
|
+
if (format === "12h") {
|
|
147
146
|
return Array.from({ length: 12 }, (_, i) => i + 1);
|
|
148
147
|
}
|
|
149
148
|
return Array.from({ length: 24 }, (_, i) => i);
|
|
@@ -151,141 +150,303 @@ const generateHours = (format) => {
|
|
|
151
150
|
const generateMinutes = (step) => {
|
|
152
151
|
return Array.from({ length: 60 / step }, (_, i) => i * step);
|
|
153
152
|
};
|
|
154
|
-
const generateSeconds = () => {
|
|
155
|
-
return Array.from({ length: 60 }, (_, i) => i);
|
|
156
|
-
};
|
|
157
|
-
const TimeSelectorColumn = ({ label, value, options, onChange, disabled = false, size = 'md', theme }) => {
|
|
158
|
-
const scrollRef = useRef(null);
|
|
159
|
-
// Auto-scroll to selected value
|
|
160
|
-
useEffect(() => {
|
|
161
|
-
if (scrollRef.current) {
|
|
162
|
-
const selectedIndex = options.findIndex(option => option === value);
|
|
163
|
-
if (selectedIndex >= 0) {
|
|
164
|
-
const itemHeight = 40; // Approximate item height
|
|
165
|
-
const scrollTop = selectedIndex * itemHeight - 80; // Center in view
|
|
166
|
-
scrollRef.current.scrollTop = Math.max(0, scrollTop);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}, [value, options]);
|
|
170
|
-
return (React__default.createElement("div", { className: cn(theme.selectorColumnStyle) },
|
|
171
|
-
React__default.createElement("div", { className: cn(theme.selectorHeaderStyle) },
|
|
172
|
-
React__default.createElement("span", { className: cn(theme.selectorHeaderLabelStyle) }, label)),
|
|
173
|
-
React__default.createElement("div", { ref: scrollRef, className: "flex-1 overflow-y-auto", style: { scrollbarWidth: 'thin' } }, options.map((option) => {
|
|
174
|
-
var _a;
|
|
175
|
-
return (React__default.createElement("button", { key: option, type: "button", "data-selected": value === option, "data-disabled": disabled, className: cn(theme.selectorItemStyle, (_a = theme.selectorItemSizes) === null || _a === void 0 ? void 0 : _a[size]), onClick: () => !disabled && onChange(option), disabled: disabled }, typeof option === 'number' ? option.toString().padStart(2, '0') : option));
|
|
176
|
-
}))));
|
|
177
|
-
};
|
|
178
153
|
/**
|
|
179
154
|
* Main TimePicker Component
|
|
155
|
+
*
|
|
156
|
+
* Features:
|
|
157
|
+
* - iOS-style scroll wheel picker
|
|
158
|
+
* - Combobox-style masked input trigger with direct keyboard entry
|
|
159
|
+
* - Live bidirectional sync between input and wheels
|
|
160
|
+
* - 12h and 24h format support with AM/PM toggle
|
|
161
|
+
* - Cancel/Save workflow for confirming selection
|
|
162
|
+
* - Validation with min/max time constraints
|
|
180
163
|
*/
|
|
181
|
-
const TimePicker = ({ value, defaultValue, onChange, format =
|
|
164
|
+
const TimePicker = ({ value, defaultValue, onChange, format = "24h", step = 1, minTime, maxTime, disabled = false, placeholder = format === "12h"
|
|
165
|
+
? "HH:MM"
|
|
166
|
+
: "HH:MM", size = "md", state = "default", clearable = true, label, helperText, iconStart, iconEnd, className, labelClassName, helperTextClassName, theme = {}, id, ...props }) => {
|
|
182
167
|
var _a;
|
|
183
168
|
// Internal state
|
|
184
169
|
const [isOpen, setIsOpen] = useState(false);
|
|
185
170
|
const [internalValue, setInternalValue] = useState(defaultValue);
|
|
186
171
|
const [validationError, setValidationError] = useState();
|
|
187
|
-
|
|
172
|
+
// Pending state for wheel picker (before Save is clicked)
|
|
173
|
+
const [pendingTime, setPendingTime] = useState(null);
|
|
174
|
+
// Masked input value
|
|
175
|
+
const [maskedValue, setMaskedValue] = useState("");
|
|
176
|
+
// Track whether the input or wheels initiated the last change (prevent feedback loops)
|
|
177
|
+
const syncSourceRef = useRef(null);
|
|
188
178
|
// Refs
|
|
189
179
|
const containerRef = useRef(null);
|
|
190
180
|
const triggerRef = useRef(null);
|
|
191
|
-
|
|
192
|
-
|
|
181
|
+
const inputRef = useRef(null);
|
|
182
|
+
const dropdownRef = useRef(null);
|
|
193
183
|
// Determine current value (controlled vs uncontrolled)
|
|
194
184
|
const currentValue = value !== undefined ? value : internalValue;
|
|
195
|
-
|
|
196
|
-
// Parse current time
|
|
185
|
+
// Parse current time for display
|
|
197
186
|
const currentTime = parseTime(currentValue, format) || {
|
|
198
|
-
hour: format ===
|
|
187
|
+
hour: format === "12h" ? 12 : 0,
|
|
199
188
|
minute: 0,
|
|
200
|
-
|
|
201
|
-
period: format === '12h' ? 'AM' : undefined
|
|
189
|
+
period: format === "12h" ? "AM" : undefined,
|
|
202
190
|
};
|
|
191
|
+
// Get display value for trigger
|
|
192
|
+
const displayValue = currentValue ? formatTime(currentTime, format) : "";
|
|
203
193
|
// Merge theme with defaults
|
|
204
194
|
const mergedTheme = { ...timePickerTheme, ...theme };
|
|
205
195
|
// Generate unique ID
|
|
206
196
|
const inputId = id || `timepicker-${Math.random().toString(36).substr(2, 9)}`;
|
|
207
197
|
// Determine if component is in error state
|
|
208
|
-
const isInvalid = state ===
|
|
209
|
-
const finalState = isInvalid ?
|
|
210
|
-
//
|
|
211
|
-
const
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
198
|
+
const isInvalid = state === "invalid" || Boolean(validationError);
|
|
199
|
+
const finalState = isInvalid ? "invalid" : state;
|
|
200
|
+
// Generate selector options
|
|
201
|
+
const hourOptions = generateHours(format);
|
|
202
|
+
const minuteOptions = generateMinutes(step);
|
|
203
|
+
const periodOptions = ["AM", "PM"];
|
|
204
|
+
// Get the time to show in wheel picker (pending or current)
|
|
205
|
+
const wheelTime = pendingTime || currentTime;
|
|
206
|
+
// Convert 24h hour to 12h display hour for wheel
|
|
207
|
+
const getDisplayHour = (hour24) => {
|
|
208
|
+
if (format === "12h") {
|
|
209
|
+
if (hour24 === 0)
|
|
210
|
+
return 12;
|
|
211
|
+
if (hour24 > 12)
|
|
212
|
+
return hour24 - 12;
|
|
213
|
+
return hour24;
|
|
214
|
+
}
|
|
215
|
+
return hour24;
|
|
216
|
+
};
|
|
217
|
+
// Convert 12h display hour back to 24h for storage
|
|
218
|
+
const get24Hour = (displayHour, period) => {
|
|
219
|
+
if (period === "AM") {
|
|
220
|
+
return displayHour === 12 ? 0 : displayHour;
|
|
218
221
|
}
|
|
219
222
|
else {
|
|
220
|
-
|
|
223
|
+
return displayHour === 12 ? 12 : displayHour + 12;
|
|
221
224
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
+
};
|
|
226
|
+
// ============================================
|
|
227
|
+
// Input handlers
|
|
228
|
+
// ============================================
|
|
229
|
+
// Handle masked input change
|
|
230
|
+
const handleInputChange = useCallback((rawValue) => {
|
|
231
|
+
const formatKey = format === "12h" ? "12h" : "24h";
|
|
232
|
+
const formatted = formatTimeInput(rawValue, formatKey);
|
|
233
|
+
setMaskedValue(formatted);
|
|
234
|
+
// If complete valid time, sync to wheels
|
|
235
|
+
if (isValidTimeInput(formatted, formatKey)) {
|
|
236
|
+
const match = formatted.match(/^(\d{2}):(\d{2})$/);
|
|
237
|
+
if (match) {
|
|
238
|
+
let hour = parseInt(match[1], 10);
|
|
239
|
+
const minute = parseInt(match[2], 10);
|
|
240
|
+
// For 12h, convert display hour to 24h using current period
|
|
241
|
+
if (format === "12h") {
|
|
242
|
+
const currentPeriod = (pendingTime === null || pendingTime === void 0 ? void 0 : pendingTime.period) || currentTime.period || "AM";
|
|
243
|
+
hour = get24Hour(hour, currentPeriod);
|
|
244
|
+
}
|
|
245
|
+
syncSourceRef.current = "input";
|
|
246
|
+
setPendingTime((prev) => ({
|
|
247
|
+
hour,
|
|
248
|
+
minute,
|
|
249
|
+
period: format === "12h" ? ((prev === null || prev === void 0 ? void 0 : prev.period) || currentTime.period || "AM") : undefined,
|
|
250
|
+
}));
|
|
251
|
+
}
|
|
225
252
|
}
|
|
226
|
-
//
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const handleDropdownToggle = () => {
|
|
231
|
-
if (!disabled) {
|
|
232
|
-
setIsOpen(!isOpen);
|
|
253
|
+
// Open dropdown if not already open
|
|
254
|
+
if (!isOpen) {
|
|
255
|
+
setPendingTime((prev) => prev || { ...currentTime });
|
|
256
|
+
setIsOpen(true);
|
|
233
257
|
}
|
|
234
|
-
};
|
|
258
|
+
}, [format, isOpen, pendingTime, currentTime]);
|
|
259
|
+
// Handle input focus — open dropdown
|
|
260
|
+
const handleInputFocus = useCallback(() => {
|
|
261
|
+
if (disabled)
|
|
262
|
+
return;
|
|
263
|
+
if (!isOpen) {
|
|
264
|
+
setPendingTime({ ...currentTime });
|
|
265
|
+
setIsOpen(true);
|
|
266
|
+
// Initialize maskedValue from current display
|
|
267
|
+
if (currentValue) {
|
|
268
|
+
const displayHour = getDisplayHour(currentTime.hour);
|
|
269
|
+
const formatted = `${displayHour.toString().padStart(2, "0")}:${currentTime.minute.toString().padStart(2, "0")}`;
|
|
270
|
+
setMaskedValue(formatted);
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}, [disabled, isOpen, currentTime, currentValue]);
|
|
274
|
+
// Handle input key down
|
|
275
|
+
const handleInputKeyDown = useCallback((e) => {
|
|
276
|
+
var _a;
|
|
277
|
+
if (e.key === "Enter") {
|
|
278
|
+
e.preventDefault();
|
|
279
|
+
handleSave();
|
|
280
|
+
}
|
|
281
|
+
else if (e.key === "Escape") {
|
|
282
|
+
e.preventDefault();
|
|
283
|
+
handleCancel();
|
|
284
|
+
}
|
|
285
|
+
else if (e.key === "Tab") {
|
|
286
|
+
handleCancel();
|
|
287
|
+
}
|
|
288
|
+
else if (e.key === "Backspace") {
|
|
289
|
+
// Handle backspace across the colon: if cursor is right after ":",
|
|
290
|
+
// delete both the colon and the preceding digit
|
|
291
|
+
const input = e.currentTarget;
|
|
292
|
+
const cursorPos = (_a = input.selectionStart) !== null && _a !== void 0 ? _a : 0;
|
|
293
|
+
if (cursorPos === 3 && maskedValue[2] === ":") {
|
|
294
|
+
e.preventDefault();
|
|
295
|
+
// Remove the digit before colon and the colon itself → keep just first char
|
|
296
|
+
const newValue = maskedValue.slice(0, 1);
|
|
297
|
+
setMaskedValue(newValue);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}, [maskedValue]);
|
|
301
|
+
// Handle AM/PM period toggle
|
|
302
|
+
const handlePeriodToggle = useCallback(() => {
|
|
303
|
+
setPendingTime((prev) => {
|
|
304
|
+
const current = prev || currentTime;
|
|
305
|
+
const newPeriod = current.period === "AM" ? "PM" : "AM";
|
|
306
|
+
const displayHour = getDisplayHour(current.hour);
|
|
307
|
+
const hour24 = get24Hour(displayHour, newPeriod);
|
|
308
|
+
syncSourceRef.current = "wheel";
|
|
309
|
+
return { ...current, hour: hour24, period: newPeriod };
|
|
310
|
+
});
|
|
311
|
+
}, [currentTime, format]);
|
|
312
|
+
// ============================================
|
|
313
|
+
// Wheel handlers
|
|
314
|
+
// ============================================
|
|
315
|
+
// Handle hour change from wheel
|
|
316
|
+
const handleHourChange = useCallback((newHour) => {
|
|
317
|
+
const hour = typeof newHour === "string" ? parseInt(newHour) : newHour;
|
|
318
|
+
syncSourceRef.current = "wheel";
|
|
319
|
+
setPendingTime((prev) => {
|
|
320
|
+
const current = prev || currentTime;
|
|
321
|
+
if (format === "12h") {
|
|
322
|
+
// Convert back to 24h
|
|
323
|
+
const hour24 = get24Hour(hour, current.period || "AM");
|
|
324
|
+
return { ...current, hour: hour24 };
|
|
325
|
+
}
|
|
326
|
+
return { ...current, hour };
|
|
327
|
+
});
|
|
328
|
+
}, [currentTime, format]);
|
|
329
|
+
// Handle minute change from wheel
|
|
330
|
+
const handleMinuteChange = useCallback((newMinute) => {
|
|
331
|
+
const minute = typeof newMinute === "string" ? parseInt(newMinute) : newMinute;
|
|
332
|
+
syncSourceRef.current = "wheel";
|
|
333
|
+
setPendingTime((prev) => {
|
|
334
|
+
const current = prev || currentTime;
|
|
335
|
+
return { ...current, minute };
|
|
336
|
+
});
|
|
337
|
+
}, [currentTime]);
|
|
338
|
+
// Handle period change from wheel (12h format only)
|
|
339
|
+
const handlePeriodChange = useCallback((newPeriod) => {
|
|
340
|
+
const period = newPeriod;
|
|
341
|
+
syncSourceRef.current = "wheel";
|
|
342
|
+
setPendingTime((prev) => {
|
|
343
|
+
const current = prev || currentTime;
|
|
344
|
+
// Recalculate hour based on new period
|
|
345
|
+
const displayHour = getDisplayHour(current.hour);
|
|
346
|
+
const hour24 = get24Hour(displayHour, period);
|
|
347
|
+
return { ...current, hour: hour24, period };
|
|
348
|
+
});
|
|
349
|
+
}, [currentTime, format]);
|
|
350
|
+
// ============================================
|
|
351
|
+
// Save / Cancel / Clear
|
|
352
|
+
// ============================================
|
|
353
|
+
// Handle Save button - commit pending time
|
|
354
|
+
const handleSave = useCallback(() => {
|
|
355
|
+
if (pendingTime) {
|
|
356
|
+
// Validate time
|
|
357
|
+
const validation = validateTime(pendingTime, minTime, maxTime);
|
|
358
|
+
if (!validation.isValid) {
|
|
359
|
+
setValidationError(validation.error);
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
else {
|
|
363
|
+
setValidationError(undefined);
|
|
364
|
+
}
|
|
365
|
+
const formattedTime = formatTime(pendingTime, format);
|
|
366
|
+
// Update internal state if uncontrolled
|
|
367
|
+
if (value === undefined) {
|
|
368
|
+
setInternalValue(formattedTime);
|
|
369
|
+
}
|
|
370
|
+
// Call onChange callback
|
|
371
|
+
onChange === null || onChange === void 0 ? void 0 : onChange(formattedTime);
|
|
372
|
+
}
|
|
373
|
+
setIsOpen(false);
|
|
374
|
+
setPendingTime(null);
|
|
375
|
+
setMaskedValue("");
|
|
376
|
+
}, [pendingTime, format, minTime, maxTime, value, onChange]);
|
|
377
|
+
// Handle Cancel button - discard pending time
|
|
378
|
+
const handleCancel = useCallback(() => {
|
|
379
|
+
setIsOpen(false);
|
|
380
|
+
setPendingTime(null);
|
|
381
|
+
setMaskedValue("");
|
|
382
|
+
}, []);
|
|
383
|
+
// Portal-aware click-outside detection (matches Select/Combobox pattern)
|
|
384
|
+
useEffect(() => {
|
|
385
|
+
if (!isOpen)
|
|
386
|
+
return;
|
|
387
|
+
const handleClickOutside = (event) => {
|
|
388
|
+
if (dropdownRef.current &&
|
|
389
|
+
!dropdownRef.current.contains(event.target) &&
|
|
390
|
+
containerRef.current &&
|
|
391
|
+
!containerRef.current.contains(event.target)) {
|
|
392
|
+
handleCancel();
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
document.addEventListener("mousedown", handleClickOutside);
|
|
396
|
+
return () => document.removeEventListener("mousedown", handleClickOutside);
|
|
397
|
+
}, [isOpen, handleCancel]);
|
|
235
398
|
// Handle clear button click
|
|
236
399
|
const handleClear = () => {
|
|
237
|
-
setInputValue('');
|
|
238
400
|
if (value === undefined) {
|
|
239
401
|
setInternalValue(undefined);
|
|
240
402
|
}
|
|
241
|
-
onChange === null || onChange === void 0 ? void 0 : onChange(
|
|
403
|
+
onChange === null || onChange === void 0 ? void 0 : onChange("");
|
|
404
|
+
setMaskedValue("");
|
|
242
405
|
setValidationError(undefined);
|
|
243
406
|
};
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
407
|
+
// ============================================
|
|
408
|
+
// Bidirectional sync: wheels → input
|
|
409
|
+
// ============================================
|
|
410
|
+
useEffect(() => {
|
|
411
|
+
if (pendingTime && syncSourceRef.current === "wheel") {
|
|
412
|
+
const displayHour = getDisplayHour(pendingTime.hour);
|
|
413
|
+
const formatted = `${displayHour.toString().padStart(2, "0")}:${pendingTime.minute.toString().padStart(2, "0")}`;
|
|
414
|
+
setMaskedValue(formatted);
|
|
248
415
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
const minuteOptions = generateMinutes(step);
|
|
253
|
-
const secondOptions = generateSeconds();
|
|
254
|
-
const periodOptions = ['AM', 'PM'];
|
|
416
|
+
// Reset sync source after processing
|
|
417
|
+
syncSourceRef.current = null;
|
|
418
|
+
}, [pendingTime, format]);
|
|
255
419
|
// Context value
|
|
256
420
|
const contextValue = {
|
|
257
421
|
isOpen,
|
|
258
422
|
setIsOpen,
|
|
259
423
|
theme: mergedTheme,
|
|
260
424
|
triggerRef,
|
|
261
|
-
|
|
425
|
+
inputRef,
|
|
426
|
+
hasValue: Boolean(displayValue),
|
|
262
427
|
size,
|
|
263
428
|
state: finalState,
|
|
264
429
|
};
|
|
430
|
+
// Current period for trigger display
|
|
431
|
+
const currentPeriod = (pendingTime === null || pendingTime === void 0 ? void 0 : pendingTime.period) || currentTime.period || (format === "12h" ? "AM" : undefined);
|
|
432
|
+
// Compute the value shown in the input:
|
|
433
|
+
// - When open: show maskedValue (user is editing)
|
|
434
|
+
// - When closed with a committed value: show formatted HH:MM from currentValue
|
|
435
|
+
// - When closed without a value: show "" (placeholder visible)
|
|
436
|
+
const closedInputDisplay = currentValue
|
|
437
|
+
? `${getDisplayHour(currentTime.hour).toString().padStart(2, "0")}:${currentTime.minute.toString().padStart(2, "0")}`
|
|
438
|
+
: "";
|
|
439
|
+
const inputDisplayValue = isOpen ? maskedValue : closedInputDisplay;
|
|
265
440
|
return (React__default.createElement(TimePickerContext.Provider, { value: contextValue },
|
|
266
441
|
React__default.createElement("div", { ref: containerRef, className: cn(mergedTheme.containerStyle, className) },
|
|
267
442
|
label && (React__default.createElement(FormLabel, { htmlFor: inputId, className: labelClassName, state: finalState, required: props.required }, label)),
|
|
268
|
-
React__default.createElement(TimePickerTrigger, { id: inputId, disabled: disabled, isOpen: isOpen, state: finalState, size: size, clearable: clearable, onClear: handleClear,
|
|
269
|
-
React__default.createElement(TimePickerContent, { isOpen: isOpen,
|
|
270
|
-
React__default.createElement("
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
hour: typeof hour === 'string' ? parseInt(hour) : hour
|
|
274
|
-
}), disabled: disabled, size: size, theme: mergedTheme }),
|
|
275
|
-
React__default.createElement(TimeSelectorColumn, { label: "Min", value: currentTime.minute, options: minuteOptions, onChange: (minute) => handleTimeChange({
|
|
276
|
-
...currentTime,
|
|
277
|
-
minute: typeof minute === 'string' ? parseInt(minute) : minute
|
|
278
|
-
}), disabled: disabled, size: size, theme: mergedTheme }),
|
|
279
|
-
showSeconds && (React__default.createElement(TimeSelectorColumn, { label: "Sec", value: currentTime.second || 0, options: secondOptions, onChange: (second) => handleTimeChange({
|
|
280
|
-
...currentTime,
|
|
281
|
-
second: typeof second === 'string' ? parseInt(second) : second
|
|
282
|
-
}), disabled: disabled, size: size, theme: mergedTheme })),
|
|
283
|
-
format === '12h' && (React__default.createElement(TimeSelectorColumn, { label: "Period", value: currentTime.period || 'AM', options: periodOptions, onChange: (period) => handleTimeChange({
|
|
284
|
-
...currentTime,
|
|
285
|
-
period: period
|
|
286
|
-
}), disabled: disabled, size: size, theme: mergedTheme })))),
|
|
443
|
+
React__default.createElement(TimePickerTrigger, { id: inputId, disabled: disabled, isOpen: isOpen, state: finalState, size: size, clearable: clearable, onClear: handleClear, iconStart: iconStart, iconEnd: iconEnd, inputValue: inputDisplayValue, onInputChange: handleInputChange, onInputFocus: handleInputFocus, onInputKeyDown: handleInputKeyDown, format: format, period: currentPeriod, onPeriodToggle: handlePeriodToggle, placeholder: placeholder }),
|
|
444
|
+
React__default.createElement(TimePickerContent, { isOpen: isOpen, onCancel: handleCancel, onSave: handleSave, portal: true, placement: "bottom-start", offset: 4, dropdownRef: dropdownRef },
|
|
445
|
+
React__default.createElement(WheelColumn, { label: "Hour", options: hourOptions, value: getDisplayHour(wheelTime.hour), onChange: handleHourChange, theme: mergedTheme }),
|
|
446
|
+
React__default.createElement(WheelColumn, { label: "Min", options: minuteOptions, value: wheelTime.minute, onChange: handleMinuteChange, theme: mergedTheme }),
|
|
447
|
+
format === "12h" && (React__default.createElement(WheelColumn, { label: "", options: periodOptions, value: wheelTime.period || "AM", onChange: handlePeriodChange, theme: mergedTheme }))),
|
|
287
448
|
(helperText || validationError) && (React__default.createElement("p", { className: cn(mergedTheme.helperTextStyle, (_a = mergedTheme.helperTextStates) === null || _a === void 0 ? void 0 : _a[finalState], helperTextClassName) }, validationError || helperText)))));
|
|
288
449
|
};
|
|
289
|
-
TimePicker.displayName =
|
|
450
|
+
TimePicker.displayName = "TimePicker";
|
|
290
451
|
|
|
291
452
|
export { TimePicker, TimePickerContext };
|
|
@@ -9,8 +9,8 @@ import { formsBaseTheme } from '../forms.theme.js';
|
|
|
9
9
|
const timePickerTheme = {
|
|
10
10
|
// Container wrapper
|
|
11
11
|
containerStyle: formsBaseTheme.wrapperStyle,
|
|
12
|
-
// Trigger base styles - extends common form input group style (same as Select/DatePicker)
|
|
13
|
-
baseStyle: formsBaseTheme.inputGroupBaseStyle
|
|
12
|
+
// Trigger base styles - extends common form input group style (same as Input/Select/DatePicker)
|
|
13
|
+
baseStyle: formsBaseTheme.inputGroupBaseStyle,
|
|
14
14
|
// Trigger size variants - composed from atomic tokens
|
|
15
15
|
triggerSizes: {
|
|
16
16
|
sm: `${formsBaseTheme.sizes.sm.text} ${formsBaseTheme.sizes.sm.padding} ${formsBaseTheme.sizes.sm.height}`,
|
|
@@ -30,41 +30,58 @@ const timePickerTheme = {
|
|
|
30
30
|
// Icon padding styles - inherited from common form theme
|
|
31
31
|
iconStartPadding: formsBaseTheme.iconStyles.padding.left,
|
|
32
32
|
iconEndPadding: formsBaseTheme.iconStyles.padding.right,
|
|
33
|
+
// Placeholder text style - matches Select/Combobox placeholder styling
|
|
34
|
+
placeholderTextStyle: formsBaseTheme.placeholderStyle,
|
|
35
|
+
// Selected value text style
|
|
36
|
+
valueTextStyle: "text-[var(--color-text-primary)] dark:text-[var(--color-neutral-200)]",
|
|
33
37
|
// Clear button styles (positioned between left content and right icon)
|
|
34
38
|
clearButtonStyle: "absolute right-8 top-1/2 -translate-y-1/2 p-1 rounded text-[var(--color-text-muted)] hover:text-[var(--color-text-primary)] hover:bg-[var(--color-background-secondary)] transition-colors z-10",
|
|
35
39
|
// Dropdown container styles (TimePicker-specific)
|
|
36
40
|
// Width is controlled by trigger size via estimatedWidth parameter
|
|
37
|
-
dropdownStyle: "fixed z-[1000]
|
|
41
|
+
dropdownStyle: "fixed z-[1000] bg-white border border-[var(--color-border)] rounded-[var(--form-rounded)] shadow-lg overflow-hidden " +
|
|
42
|
+
"dark:bg-[var(--color-background-secondary)] ",
|
|
38
43
|
// Dropdown size variants - primarily for internal spacing/padding
|
|
39
44
|
dropdownSizes: {
|
|
40
45
|
sm: "",
|
|
41
46
|
md: "",
|
|
42
47
|
lg: "",
|
|
43
48
|
},
|
|
44
|
-
// Time selector container
|
|
45
|
-
selectorContainerStyle: "flex divide-x divide-[var(--color-border)] h-48 overflow-hidden",
|
|
46
|
-
// Individual selector column
|
|
47
|
-
selectorColumnStyle: "flex-1 flex flex-col overflow-hidden scroll-smooth",
|
|
48
|
-
// Column header styles
|
|
49
|
-
selectorHeaderStyle: "sticky top-0 bg-white/95 backdrop-blur-sm p-2 border-b border-[var(--color-border)]",
|
|
50
|
-
// Column header label styles
|
|
51
|
-
selectorHeaderLabelStyle: "text-xs font-semibold text-[var(--color-text-secondary)] uppercase tracking-wide",
|
|
52
|
-
// Time selector items
|
|
53
|
-
selectorItemStyle: "flex items-center justify-center py-2 px-3 text-sm font-medium text-[var(--color-text-secondary)] hover:bg-[var(--color-background-secondary)] cursor-pointer transition-colors border-b border-[var(--color-border)] last:border-b-0 min-h-[2.5rem] data-[selected=true]:bg-[var(--color-primary-50)] data-[selected=true]:text-[var(--color-primary-700)] data-[selected=true]:font-semibold data-[selected=true]:border-l-2 data-[selected=true]:border-l-[var(--color-primary-600)] data-[disabled=true]:text-[var(--color-text-disabled)] data-[disabled=true]:cursor-not-allowed data-[disabled=true]:hover:bg-transparent",
|
|
54
|
-
// Selector item size variants
|
|
55
|
-
selectorItemSizes: {
|
|
56
|
-
sm: "py-1.5 px-2 text-xs min-h-[2rem]",
|
|
57
|
-
md: "py-2 px-3 text-sm min-h-[2.5rem]",
|
|
58
|
-
lg: "py-3 px-4 text-base min-h-[3rem]",
|
|
59
|
-
},
|
|
60
49
|
// Helper text style - inherited from common form theme
|
|
61
50
|
helperTextStyle: formsBaseTheme.helperText,
|
|
62
51
|
// Helper text state variants
|
|
63
52
|
helperTextStates: {
|
|
64
|
-
default: "text-[var(--color-text-muted)]",
|
|
65
|
-
disabled: "text-[var(--color-text-disabled)]",
|
|
66
|
-
invalid: "text-
|
|
53
|
+
default: "text-[var(--color-text-muted)] dark:text-[var(--color-neutral-400)]",
|
|
54
|
+
disabled: "text-[var(--color-text-disabled)] dark:text-[var(--color-neutral-600)]",
|
|
55
|
+
invalid: "text-[var(--color-danger)] dark:text-[var(--color-danger-400)]",
|
|
67
56
|
},
|
|
57
|
+
// ============================================
|
|
58
|
+
// Wheel Picker Styles (iOS-style scroll wheel)
|
|
59
|
+
// ============================================
|
|
60
|
+
// Wheel columns container - horizontal flex with dividers
|
|
61
|
+
wheelContainerStyle: "flex overflow-hidden",
|
|
62
|
+
// Individual wheel column - relative positioning for highlight overlay
|
|
63
|
+
wheelColumnStyle: "flex-1 flex flex-col relative",
|
|
64
|
+
// Wheel column header label
|
|
65
|
+
wheelHeaderStyle: "text-center py-2 text-xs font-semibold text-[var(--color-text-muted)] uppercase",
|
|
66
|
+
// Center highlight row - positioned behind selected item
|
|
67
|
+
wheelHighlightStyle: "absolute -mt-4 inset-x-0 bg-[var(--color-border)] pointer-events-none z-0",
|
|
68
|
+
// Wheel item - centered with transition for smooth opacity/color changes
|
|
69
|
+
wheelItemStyle: "flex items-center justify-center text-[var(--color-text-muted)] transition-colors select-none cursor-pointer relative z-10",
|
|
70
|
+
// Wheel item active (selected) style - stronger text color and weight
|
|
71
|
+
wheelItemActiveStyle: "text-[var(--color-text-primary)] font-semibold",
|
|
72
|
+
// Footer container - flexbox for action buttons
|
|
73
|
+
footerStyle: "flex justify-between items-center gap-2 px-2 py-2 border-t border-[var(--color-border)] " +
|
|
74
|
+
"dark:border-[var(--color-neutral-700)]",
|
|
75
|
+
// Inner input element style - transparent bg, no outline, tabular-nums for aligned digits
|
|
76
|
+
inputStyle: "bg-transparent outline-none flex-1 tabular-nums min-w-0 " +
|
|
77
|
+
"text-[var(--color-text-primary)] placeholder:text-[var(--color-text-placeholder)] " +
|
|
78
|
+
"dark:text-[var(--color-neutral-200)] dark:placeholder:text-[var(--color-neutral-500)]",
|
|
79
|
+
// AM/PM period toggle button
|
|
80
|
+
periodToggleStyle: "px-1.5 py-0.5 text-xs font-semibold rounded " +
|
|
81
|
+
"bg-[var(--color-background-tertiary)] text-[var(--color-text-primary)] " +
|
|
82
|
+
"hover:bg-[var(--color-background-quaternary)] transition-colors cursor-pointer select-none " +
|
|
83
|
+
"dark:bg-[var(--color-neutral-700)] dark:text-[var(--color-neutral-200)] " +
|
|
84
|
+
"dark:hover:bg-[var(--color-neutral-600)]",
|
|
68
85
|
};
|
|
69
86
|
|
|
70
87
|
export { timePickerTheme };
|