@idealyst/datepicker 1.1.8 → 1.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/package.json +19 -9
- package/src/DateInput.native.tsx +31 -27
- package/src/DateInput.web.tsx +28 -29
- package/src/DatePicker.native.tsx +124 -62
- package/src/DatePicker.styles.ts +218 -0
- package/src/DatePicker.tsx +2 -0
- package/src/DatePicker.web.tsx +133 -76
- package/src/DateTimePicker.native.tsx +12 -7
- package/src/DateTimePicker.styles.ts +43 -0
- package/src/DateTimePicker.web.tsx +13 -9
- package/src/IconSvg.web.tsx +34 -0
- package/src/InputStyles.ts +127 -0
- package/src/TimeInput.native.tsx +31 -27
- package/src/TimeInput.web.tsx +28 -29
- package/src/TimePicker.native.tsx +58 -30
- package/src/TimePicker.styles.ts +104 -0
- package/src/TimePicker.tsx +2 -0
- package/src/TimePicker.web.tsx +61 -32
- package/src/styles.ts +0 -254
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import MdiIcon from '@mdi/react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Internal component for rendering SVG icons directly from MDI paths.
|
|
6
|
+
* The path prop should be provided by the Babel plugin transformation.
|
|
7
|
+
*/
|
|
8
|
+
interface IconSvgProps {
|
|
9
|
+
path?: string;
|
|
10
|
+
size?: string | number;
|
|
11
|
+
color?: string;
|
|
12
|
+
style?: React.CSSProperties;
|
|
13
|
+
'aria-label'?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const IconSvg: React.FC<IconSvgProps> = ({
|
|
17
|
+
path,
|
|
18
|
+
size = '1em',
|
|
19
|
+
color = 'currentColor',
|
|
20
|
+
style,
|
|
21
|
+
'aria-label': ariaLabel,
|
|
22
|
+
}) => {
|
|
23
|
+
return (
|
|
24
|
+
<MdiIcon
|
|
25
|
+
style={style}
|
|
26
|
+
path={path}
|
|
27
|
+
size={size}
|
|
28
|
+
color={color}
|
|
29
|
+
aria-label={ariaLabel}
|
|
30
|
+
/>
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default IconSvg;
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared input styles for DateInput and TimeInput.
|
|
3
|
+
*/
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
|
|
6
|
+
import type { Theme as BaseTheme } from '@idealyst/theme';
|
|
7
|
+
|
|
8
|
+
// Required: Unistyles must see StyleSheet usage in original source to process this file
|
|
9
|
+
void StyleSheet;
|
|
10
|
+
|
|
11
|
+
// Wrap theme for $iterator support
|
|
12
|
+
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
13
|
+
|
|
14
|
+
export type InputDynamicProps = {
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
error?: boolean;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Shared input styles with theme reactivity.
|
|
21
|
+
*/
|
|
22
|
+
export const dateTimeInputStyles = defineStyle('DateTimeInput', (theme: Theme) => ({
|
|
23
|
+
// Input container for DateInput/TimeInput
|
|
24
|
+
inputContainer: ({ disabled = false, error = false }: InputDynamicProps) => ({
|
|
25
|
+
flexDirection: 'row' as const,
|
|
26
|
+
alignItems: 'center' as const,
|
|
27
|
+
borderWidth: 1,
|
|
28
|
+
borderRadius: 6,
|
|
29
|
+
overflow: 'hidden' as const,
|
|
30
|
+
borderColor: error ? theme.intents.error.primary : theme.colors.border.primary,
|
|
31
|
+
backgroundColor: disabled ? theme.colors.surface.secondary : theme.colors.surface.primary,
|
|
32
|
+
_web: {
|
|
33
|
+
display: 'flex',
|
|
34
|
+
flexDirection: 'row',
|
|
35
|
+
alignItems: 'center',
|
|
36
|
+
border: `1px solid ${error ? theme.intents.error.primary : theme.colors.border.primary}`,
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
// Text input inside the input container
|
|
41
|
+
textInput: ({ disabled = false }: InputDynamicProps) => ({
|
|
42
|
+
flex: 1,
|
|
43
|
+
padding: 8,
|
|
44
|
+
paddingHorizontal: 12,
|
|
45
|
+
fontSize: 14,
|
|
46
|
+
backgroundColor: 'transparent',
|
|
47
|
+
color: disabled ? theme.colors.text.tertiary : theme.colors.text.primary,
|
|
48
|
+
_web: {
|
|
49
|
+
outline: 'none',
|
|
50
|
+
border: 'none',
|
|
51
|
+
},
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
// Icon button inside input
|
|
55
|
+
iconButton: ({ disabled = false }: InputDynamicProps) => ({
|
|
56
|
+
width: 32,
|
|
57
|
+
height: 32,
|
|
58
|
+
alignItems: 'center' as const,
|
|
59
|
+
justifyContent: 'center' as const,
|
|
60
|
+
marginRight: 4,
|
|
61
|
+
borderRadius: 4,
|
|
62
|
+
opacity: disabled ? 0.4 : 1,
|
|
63
|
+
_web: {
|
|
64
|
+
display: 'flex',
|
|
65
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
66
|
+
},
|
|
67
|
+
}),
|
|
68
|
+
|
|
69
|
+
// Error text below input
|
|
70
|
+
errorText: (_props: InputDynamicProps) => ({
|
|
71
|
+
marginTop: 4,
|
|
72
|
+
fontSize: 12,
|
|
73
|
+
color: theme.intents.error.primary,
|
|
74
|
+
}),
|
|
75
|
+
|
|
76
|
+
// Label text above input
|
|
77
|
+
labelText: (_props: InputDynamicProps) => ({
|
|
78
|
+
marginBottom: 4,
|
|
79
|
+
fontSize: 14,
|
|
80
|
+
fontWeight: '500' as const,
|
|
81
|
+
color: theme.colors.text.primary,
|
|
82
|
+
}),
|
|
83
|
+
|
|
84
|
+
// Modal backdrop
|
|
85
|
+
modalBackdrop: (_props: InputDynamicProps) => ({
|
|
86
|
+
flex: 1,
|
|
87
|
+
justifyContent: 'center' as const,
|
|
88
|
+
alignItems: 'center' as const,
|
|
89
|
+
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
90
|
+
_web: {
|
|
91
|
+
display: 'flex',
|
|
92
|
+
},
|
|
93
|
+
}),
|
|
94
|
+
|
|
95
|
+
// Popover content wrapper
|
|
96
|
+
popoverContent: (_props: InputDynamicProps) => ({
|
|
97
|
+
backgroundColor: theme.colors.surface.primary,
|
|
98
|
+
borderRadius: 6,
|
|
99
|
+
...theme.shadows.lg,
|
|
100
|
+
overflow: 'hidden' as const,
|
|
101
|
+
}),
|
|
102
|
+
|
|
103
|
+
// Close button
|
|
104
|
+
closeButton: ({ disabled = false }: InputDynamicProps) => ({
|
|
105
|
+
marginTop: 8,
|
|
106
|
+
paddingVertical: 8,
|
|
107
|
+
paddingHorizontal: 16,
|
|
108
|
+
alignItems: 'center' as const,
|
|
109
|
+
justifyContent: 'center' as const,
|
|
110
|
+
borderRadius: 4,
|
|
111
|
+
opacity: disabled ? 0.4 : 1,
|
|
112
|
+
_web: {
|
|
113
|
+
display: 'flex',
|
|
114
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
115
|
+
},
|
|
116
|
+
}),
|
|
117
|
+
|
|
118
|
+
closeButtonText: (_props: InputDynamicProps) => ({
|
|
119
|
+
fontSize: 14,
|
|
120
|
+
color: theme.intents.primary.primary,
|
|
121
|
+
}),
|
|
122
|
+
|
|
123
|
+
// Icon color helper
|
|
124
|
+
iconColor: ({ disabled = false }: InputDynamicProps) => ({
|
|
125
|
+
color: theme.colors.text.primary,
|
|
126
|
+
}),
|
|
127
|
+
}));
|
package/src/TimeInput.native.tsx
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
2
|
+
import { View, Text, TextInput, TouchableOpacity, Modal } from 'react-native';
|
|
3
|
+
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
4
4
|
import { TimePicker } from './TimePicker';
|
|
5
|
-
import {
|
|
5
|
+
import { dateTimeInputStyles } from './InputStyles';
|
|
6
6
|
import type { TimeInputProps } from './types';
|
|
7
7
|
|
|
8
8
|
export const TimeInput: React.FC<TimeInputProps> = ({
|
|
@@ -19,6 +19,19 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
19
19
|
const [open, setOpen] = useState(false);
|
|
20
20
|
const [inputValue, setInputValue] = useState('');
|
|
21
21
|
|
|
22
|
+
// Get dynamic styles - call as functions for theme reactivity
|
|
23
|
+
const styles = dateTimeInputStyles;
|
|
24
|
+
const labelTextStyle = (styles.labelText as any)({});
|
|
25
|
+
const inputContainerStyle = (styles.inputContainer as any)({ disabled, error: !!error });
|
|
26
|
+
const textInputStyle = (styles.textInput as any)({ disabled });
|
|
27
|
+
const iconButtonStyle = (styles.iconButton as any)({ disabled });
|
|
28
|
+
const errorTextStyle = (styles.errorText as any)({});
|
|
29
|
+
const modalBackdropStyle = (styles.modalBackdrop as any)({});
|
|
30
|
+
const popoverContentStyle = (styles.popoverContent as any)({});
|
|
31
|
+
const closeButtonStyle = (styles.closeButton as any)({ disabled: false });
|
|
32
|
+
const closeButtonTextStyle = (styles.closeButtonText as any)({});
|
|
33
|
+
const iconStyle = (styles.iconColor as any)({ disabled });
|
|
34
|
+
|
|
22
35
|
// Format time to string
|
|
23
36
|
const formatTime = (date: Date | undefined): string => {
|
|
24
37
|
if (!date) return '';
|
|
@@ -89,40 +102,32 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
89
102
|
onChange(date);
|
|
90
103
|
};
|
|
91
104
|
|
|
92
|
-
// Apply variants for input container
|
|
93
|
-
datePickerStyles.useVariants({
|
|
94
|
-
disabled,
|
|
95
|
-
error: !!error,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
105
|
return (
|
|
99
106
|
<View style={style}>
|
|
100
107
|
{label && (
|
|
101
|
-
<Text
|
|
108
|
+
<Text style={labelTextStyle}>
|
|
102
109
|
{label}
|
|
103
110
|
</Text>
|
|
104
111
|
)}
|
|
105
|
-
<View style={
|
|
106
|
-
<
|
|
112
|
+
<View style={inputContainerStyle}>
|
|
113
|
+
<TextInput
|
|
107
114
|
value={inputValue}
|
|
108
115
|
onChangeText={handleInputChange}
|
|
109
116
|
onBlur={handleInputBlur}
|
|
110
117
|
placeholder={placeholder}
|
|
111
118
|
editable={!disabled}
|
|
112
|
-
style={
|
|
119
|
+
style={textInputStyle}
|
|
113
120
|
/>
|
|
114
|
-
<
|
|
115
|
-
|
|
116
|
-
size="sm"
|
|
121
|
+
<TouchableOpacity
|
|
122
|
+
style={iconButtonStyle}
|
|
117
123
|
onPress={() => !disabled && setOpen(true)}
|
|
118
124
|
disabled={disabled}
|
|
119
|
-
style={{ marginRight: 4 }}
|
|
120
125
|
>
|
|
121
|
-
<
|
|
122
|
-
</
|
|
126
|
+
<MaterialCommunityIcons name="clock-outline" size={18} style={iconStyle} />
|
|
127
|
+
</TouchableOpacity>
|
|
123
128
|
</View>
|
|
124
129
|
{error && (
|
|
125
|
-
<Text
|
|
130
|
+
<Text style={errorTextStyle}>
|
|
126
131
|
{error}
|
|
127
132
|
</Text>
|
|
128
133
|
)}
|
|
@@ -133,8 +138,8 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
133
138
|
animationType="fade"
|
|
134
139
|
onRequestClose={() => setOpen(false)}
|
|
135
140
|
>
|
|
136
|
-
<View style={
|
|
137
|
-
<View style={
|
|
141
|
+
<View style={modalBackdropStyle}>
|
|
142
|
+
<View style={popoverContentStyle}>
|
|
138
143
|
<TimePicker
|
|
139
144
|
value={value ?? undefined}
|
|
140
145
|
onChange={handleTimeChange}
|
|
@@ -142,13 +147,12 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
142
147
|
minuteStep={minuteStep}
|
|
143
148
|
disabled={disabled}
|
|
144
149
|
/>
|
|
145
|
-
<
|
|
146
|
-
|
|
150
|
+
<TouchableOpacity
|
|
151
|
+
style={closeButtonStyle}
|
|
147
152
|
onPress={() => setOpen(false)}
|
|
148
|
-
style={{ marginTop: 8 }}
|
|
149
153
|
>
|
|
150
|
-
Close
|
|
151
|
-
</
|
|
154
|
+
<Text style={closeButtonTextStyle}>Close</Text>
|
|
155
|
+
</TouchableOpacity>
|
|
152
156
|
</View>
|
|
153
157
|
</View>
|
|
154
158
|
</Modal>
|
package/src/TimeInput.web.tsx
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
2
|
import { getWebProps } from 'react-native-unistyles/web';
|
|
3
|
-
import {
|
|
3
|
+
import { mdiClockOutline } from '@mdi/js';
|
|
4
4
|
import { PositionedPortal } from '@idealyst/components/internal';
|
|
5
|
+
import { IconSvg } from './IconSvg.web';
|
|
5
6
|
import { TimePicker } from './TimePicker';
|
|
6
|
-
import {
|
|
7
|
+
import { dateTimeInputStyles } from './InputStyles';
|
|
7
8
|
import type { TimeInputProps } from './types';
|
|
8
9
|
|
|
9
10
|
export const TimeInput: React.FC<TimeInputProps> = ({
|
|
@@ -21,6 +22,17 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
21
22
|
const [inputValue, setInputValue] = useState('');
|
|
22
23
|
const triggerRef = useRef<HTMLDivElement>(null);
|
|
23
24
|
|
|
25
|
+
const styles = dateTimeInputStyles;
|
|
26
|
+
|
|
27
|
+
// Get dynamic styles
|
|
28
|
+
const labelTextStyle = (styles.labelText as any)({});
|
|
29
|
+
const inputContainerStyle = (styles.inputContainer as any)({ disabled, error: !!error });
|
|
30
|
+
const textInputStyle = (styles.textInput as any)({ disabled });
|
|
31
|
+
const iconButtonStyle = (styles.iconButton as any)({ disabled });
|
|
32
|
+
const errorTextStyle = (styles.errorText as any)({});
|
|
33
|
+
const popoverContentStyle = (styles.popoverContent as any)({});
|
|
34
|
+
const iconColorStyle = (styles.iconColor as any)({ disabled });
|
|
35
|
+
|
|
24
36
|
// Format time to string
|
|
25
37
|
const formatTime = (date: Date | undefined): string => {
|
|
26
38
|
if (!date) return '';
|
|
@@ -97,22 +109,13 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
97
109
|
onChange(date);
|
|
98
110
|
};
|
|
99
111
|
|
|
100
|
-
//
|
|
101
|
-
|
|
102
|
-
disabled,
|
|
103
|
-
error: !!error,
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// Get web props for styled elements
|
|
107
|
-
const containerProps = getWebProps([datePickerStyles.inputContainer]);
|
|
108
|
-
const inputProps = getWebProps([datePickerStyles.textInput]);
|
|
112
|
+
// Get web props
|
|
113
|
+
const containerProps = getWebProps([inputContainerStyle]);
|
|
109
114
|
|
|
110
115
|
return (
|
|
111
|
-
<
|
|
116
|
+
<div style={style as React.CSSProperties}>
|
|
112
117
|
{label && (
|
|
113
|
-
<
|
|
114
|
-
{label}
|
|
115
|
-
</Text>
|
|
118
|
+
<span style={labelTextStyle}>{label}</span>
|
|
116
119
|
)}
|
|
117
120
|
<div ref={triggerRef} {...containerProps}>
|
|
118
121
|
<input
|
|
@@ -122,22 +125,18 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
122
125
|
onBlur={handleInputBlur}
|
|
123
126
|
placeholder={placeholder}
|
|
124
127
|
disabled={disabled}
|
|
125
|
-
{
|
|
128
|
+
style={textInputStyle}
|
|
126
129
|
/>
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
onPress={() => !disabled && setOpen(!open)}
|
|
130
|
+
<button
|
|
131
|
+
style={iconButtonStyle}
|
|
132
|
+
onClick={() => !disabled && setOpen(!open)}
|
|
131
133
|
disabled={disabled}
|
|
132
|
-
style={{ marginRight: 4 }}
|
|
133
134
|
>
|
|
134
|
-
<
|
|
135
|
-
</
|
|
135
|
+
<IconSvg path={mdiClockOutline} size={18} color={iconColorStyle.color} />
|
|
136
|
+
</button>
|
|
136
137
|
</div>
|
|
137
138
|
{error && (
|
|
138
|
-
<
|
|
139
|
-
{error}
|
|
140
|
-
</Text>
|
|
139
|
+
<span style={errorTextStyle}>{error}</span>
|
|
141
140
|
)}
|
|
142
141
|
|
|
143
142
|
<PositionedPortal
|
|
@@ -149,7 +148,7 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
149
148
|
onEscapeKey={() => setOpen(false)}
|
|
150
149
|
zIndex={9999}
|
|
151
150
|
>
|
|
152
|
-
<
|
|
151
|
+
<div style={popoverContentStyle}>
|
|
153
152
|
<TimePicker
|
|
154
153
|
value={value ?? undefined}
|
|
155
154
|
onChange={handleTimeChange}
|
|
@@ -157,8 +156,8 @@ export const TimeInput: React.FC<TimeInputProps> = ({
|
|
|
157
156
|
minuteStep={minuteStep}
|
|
158
157
|
disabled={disabled}
|
|
159
158
|
/>
|
|
160
|
-
</
|
|
159
|
+
</div>
|
|
161
160
|
</PositionedPortal>
|
|
162
|
-
</
|
|
161
|
+
</div>
|
|
163
162
|
);
|
|
164
163
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { View } from 'react-native';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
2
|
+
import { View, Text, TouchableOpacity } from 'react-native';
|
|
3
|
+
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
|
4
|
+
import { timePickerStyles } from './TimePicker.styles';
|
|
5
5
|
import type { TimePickerProps } from './types';
|
|
6
6
|
|
|
7
7
|
export const TimePicker: React.FC<TimePickerProps> = ({
|
|
@@ -12,7 +12,18 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
12
12
|
disabled = false,
|
|
13
13
|
style,
|
|
14
14
|
}) => {
|
|
15
|
-
|
|
15
|
+
// Get dynamic styles - call as functions for theme reactivity
|
|
16
|
+
const styles = timePickerStyles;
|
|
17
|
+
const timePickerStyle = (styles.timePicker as any)({ disabled });
|
|
18
|
+
const timeColumnsStyle = (styles.timeColumns as any)({});
|
|
19
|
+
const timeColumnStyle = (styles.timeColumn as any)({});
|
|
20
|
+
const timeSeparatorStyle = (styles.timeSeparator as any)({});
|
|
21
|
+
const separatorTextStyle = (styles.separatorText as any)({});
|
|
22
|
+
const timeValueStyle = (styles.timeValue as any)({});
|
|
23
|
+
const arrowButtonStyle = (styles.arrowButton as any)({ disabled });
|
|
24
|
+
const periodButtonStyle = (styles.periodButton as any)({ disabled });
|
|
25
|
+
const periodButtonTextStyle = (styles.periodButtonText as any)({});
|
|
26
|
+
const iconStyle = (styles.iconColor as any)({});
|
|
16
27
|
|
|
17
28
|
const currentDate = value || new Date();
|
|
18
29
|
const hours = currentDate.getHours();
|
|
@@ -55,50 +66,67 @@ export const TimePicker: React.FC<TimePickerProps> = ({
|
|
|
55
66
|
};
|
|
56
67
|
|
|
57
68
|
return (
|
|
58
|
-
<View style={[
|
|
59
|
-
<View style={
|
|
69
|
+
<View style={[timePickerStyle, style]}>
|
|
70
|
+
<View style={timeColumnsStyle}>
|
|
60
71
|
{/* Hours column */}
|
|
61
|
-
<View style={
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
<View style={timeColumnStyle}>
|
|
73
|
+
<TouchableOpacity
|
|
74
|
+
style={arrowButtonStyle}
|
|
75
|
+
onPress={incrementHours}
|
|
76
|
+
disabled={disabled}
|
|
77
|
+
>
|
|
78
|
+
<MaterialCommunityIcons name="chevron-up" size={20} style={iconStyle} />
|
|
79
|
+
</TouchableOpacity>
|
|
80
|
+
<Text style={timeValueStyle}>
|
|
66
81
|
{String(displayHours).padStart(2, '0')}
|
|
67
82
|
</Text>
|
|
68
|
-
<
|
|
69
|
-
|
|
70
|
-
|
|
83
|
+
<TouchableOpacity
|
|
84
|
+
style={arrowButtonStyle}
|
|
85
|
+
onPress={decrementHours}
|
|
86
|
+
disabled={disabled}
|
|
87
|
+
>
|
|
88
|
+
<MaterialCommunityIcons name="chevron-down" size={20} style={iconStyle} />
|
|
89
|
+
</TouchableOpacity>
|
|
71
90
|
</View>
|
|
72
91
|
|
|
73
92
|
{/* Separator */}
|
|
74
|
-
<View style={
|
|
75
|
-
<Text
|
|
93
|
+
<View style={timeSeparatorStyle}>
|
|
94
|
+
<Text style={separatorTextStyle}>:</Text>
|
|
76
95
|
</View>
|
|
77
96
|
|
|
78
97
|
{/* Minutes column */}
|
|
79
|
-
<View style={
|
|
80
|
-
<
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
<View style={timeColumnStyle}>
|
|
99
|
+
<TouchableOpacity
|
|
100
|
+
style={arrowButtonStyle}
|
|
101
|
+
onPress={incrementMinutes}
|
|
102
|
+
disabled={disabled}
|
|
103
|
+
>
|
|
104
|
+
<MaterialCommunityIcons name="chevron-up" size={20} style={iconStyle} />
|
|
105
|
+
</TouchableOpacity>
|
|
106
|
+
<Text style={timeValueStyle}>
|
|
84
107
|
{String(minutes).padStart(2, '0')}
|
|
85
108
|
</Text>
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
<TouchableOpacity
|
|
110
|
+
style={arrowButtonStyle}
|
|
111
|
+
onPress={decrementMinutes}
|
|
112
|
+
disabled={disabled}
|
|
113
|
+
>
|
|
114
|
+
<MaterialCommunityIcons name="chevron-down" size={20} style={iconStyle} />
|
|
115
|
+
</TouchableOpacity>
|
|
89
116
|
</View>
|
|
90
117
|
|
|
91
118
|
{/* AM/PM toggle for 12-hour mode */}
|
|
92
119
|
{is12Hour && (
|
|
93
|
-
<View style={
|
|
94
|
-
<
|
|
95
|
-
|
|
96
|
-
size="sm"
|
|
120
|
+
<View style={timeColumnStyle}>
|
|
121
|
+
<TouchableOpacity
|
|
122
|
+
style={periodButtonStyle}
|
|
97
123
|
onPress={togglePeriod}
|
|
98
124
|
disabled={disabled}
|
|
99
125
|
>
|
|
100
|
-
{
|
|
101
|
-
|
|
126
|
+
<Text style={periodButtonTextStyle}>
|
|
127
|
+
{isPM ? 'PM' : 'AM'}
|
|
128
|
+
</Text>
|
|
129
|
+
</TouchableOpacity>
|
|
102
130
|
</View>
|
|
103
131
|
)}
|
|
104
132
|
</View>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TimePicker styles using defineStyle with dynamic props.
|
|
3
|
+
*/
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
import { defineStyle, ThemeStyleWrapper } from '@idealyst/theme';
|
|
6
|
+
import type { Theme as BaseTheme } from '@idealyst/theme';
|
|
7
|
+
|
|
8
|
+
// Required: Unistyles must see StyleSheet usage in original source to process this file
|
|
9
|
+
void StyleSheet;
|
|
10
|
+
|
|
11
|
+
// Wrap theme for $iterator support
|
|
12
|
+
type Theme = ThemeStyleWrapper<BaseTheme>;
|
|
13
|
+
|
|
14
|
+
export type TimePickerDynamicProps = {
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* TimePicker styles with theme reactivity.
|
|
20
|
+
*/
|
|
21
|
+
export const timePickerStyles = defineStyle('TimePicker', (theme: Theme) => ({
|
|
22
|
+
// Time picker container
|
|
23
|
+
timePicker: ({ disabled = false }: TimePickerDynamicProps) => ({
|
|
24
|
+
padding: 12,
|
|
25
|
+
backgroundColor: theme.colors.surface.primary,
|
|
26
|
+
borderRadius: 6,
|
|
27
|
+
opacity: disabled ? 0.6 : 1,
|
|
28
|
+
}),
|
|
29
|
+
|
|
30
|
+
// Time columns container
|
|
31
|
+
timeColumns: (_props: TimePickerDynamicProps) => ({
|
|
32
|
+
flexDirection: 'row' as const,
|
|
33
|
+
gap: 8,
|
|
34
|
+
alignItems: 'center' as const,
|
|
35
|
+
_web: {
|
|
36
|
+
display: 'flex',
|
|
37
|
+
},
|
|
38
|
+
}),
|
|
39
|
+
|
|
40
|
+
// Individual time column (hours, minutes, period)
|
|
41
|
+
timeColumn: (_props: TimePickerDynamicProps) => ({
|
|
42
|
+
alignItems: 'center' as const,
|
|
43
|
+
gap: 2,
|
|
44
|
+
_web: {
|
|
45
|
+
display: 'flex',
|
|
46
|
+
},
|
|
47
|
+
}),
|
|
48
|
+
|
|
49
|
+
// Time separator (colon)
|
|
50
|
+
timeSeparator: (_props: TimePickerDynamicProps) => ({
|
|
51
|
+
paddingHorizontal: 2,
|
|
52
|
+
}),
|
|
53
|
+
|
|
54
|
+
separatorText: (_props: TimePickerDynamicProps) => ({
|
|
55
|
+
fontSize: 24,
|
|
56
|
+
fontWeight: '600' as const,
|
|
57
|
+
color: theme.colors.text.primary,
|
|
58
|
+
}),
|
|
59
|
+
|
|
60
|
+
// Time value display
|
|
61
|
+
timeValue: (_props: TimePickerDynamicProps) => ({
|
|
62
|
+
fontSize: 24,
|
|
63
|
+
fontWeight: '600' as const,
|
|
64
|
+
color: theme.colors.text.primary,
|
|
65
|
+
}),
|
|
66
|
+
|
|
67
|
+
// Arrow button (up/down)
|
|
68
|
+
arrowButton: ({ disabled = false }: TimePickerDynamicProps) => ({
|
|
69
|
+
width: 32,
|
|
70
|
+
height: 32,
|
|
71
|
+
alignItems: 'center' as const,
|
|
72
|
+
justifyContent: 'center' as const,
|
|
73
|
+
borderRadius: 4,
|
|
74
|
+
opacity: disabled ? 0.4 : 1,
|
|
75
|
+
_web: {
|
|
76
|
+
display: 'flex',
|
|
77
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
|
|
81
|
+
// Period toggle button (AM/PM)
|
|
82
|
+
periodButton: ({ disabled = false }: TimePickerDynamicProps) => ({
|
|
83
|
+
paddingVertical: 6,
|
|
84
|
+
paddingHorizontal: 12,
|
|
85
|
+
borderWidth: 1,
|
|
86
|
+
borderColor: theme.colors.border.primary,
|
|
87
|
+
borderRadius: 4,
|
|
88
|
+
opacity: disabled ? 0.4 : 1,
|
|
89
|
+
_web: {
|
|
90
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
91
|
+
},
|
|
92
|
+
}),
|
|
93
|
+
|
|
94
|
+
periodButtonText: (_props: TimePickerDynamicProps) => ({
|
|
95
|
+
fontSize: 14,
|
|
96
|
+
fontWeight: '500' as const,
|
|
97
|
+
color: theme.colors.text.primary,
|
|
98
|
+
}),
|
|
99
|
+
|
|
100
|
+
// Icon color helper
|
|
101
|
+
iconColor: (_props: TimePickerDynamicProps) => ({
|
|
102
|
+
color: theme.colors.text.primary,
|
|
103
|
+
}),
|
|
104
|
+
}));
|