nitro-web 0.0.145 → 0.0.147
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/client/index.ts +5 -5
- package/components/partials/element/calendar.tsx +63 -40
- package/components/partials/element/filters.tsx +98 -69
- package/components/partials/element/timepicker.tsx +119 -0
- package/components/partials/form/field-color.tsx +27 -19
- package/components/partials/form/field-currency.tsx +108 -102
- package/components/partials/form/field-date.tsx +167 -93
- package/components/partials/form/field.tsx +16 -29
- package/components/partials/styleguide.tsx +94 -40
- package/package.json +3 -4
- package/types/util.d.ts +3 -8
- package/types/util.d.ts.map +1 -1
- package/util.js +9 -24
- package/components/partials/form/field-time.tsx +0 -214
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
import { format, parse } from 'date-fns'
|
|
2
|
-
import { Button, Dropdown } from 'nitro-web'
|
|
3
|
-
import { dayButtonClassName } from '../element/calendar'
|
|
4
|
-
|
|
5
|
-
type Timestamp = number // timestamp on epoch day
|
|
6
|
-
type DropdownRef = {
|
|
7
|
-
setIsActive: (value: boolean) => void
|
|
8
|
-
}
|
|
9
|
-
export type FieldTimeProps = React.InputHTMLAttributes<HTMLInputElement> & {
|
|
10
|
-
name: string
|
|
11
|
-
id?: string
|
|
12
|
-
onChange?: (e: { target: { name: string, value: null|number } }) => void
|
|
13
|
-
value?: string | Timestamp;
|
|
14
|
-
Icon?: React.ReactNode
|
|
15
|
-
dir?: 'bottom-left'|'bottom-right'|'top-left'|'top-right'
|
|
16
|
-
// tz?: string
|
|
17
|
-
}
|
|
18
|
-
type TimePickerProps = {
|
|
19
|
-
date?: Date
|
|
20
|
-
onChange: (value: Timestamp) => void
|
|
21
|
-
// tz?: string
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function FieldTime({ onChange, value, Icon, dir = 'bottom-left', ...props }: FieldTimeProps) {
|
|
25
|
-
// time is viewed and set in local timezone, and saved as timestamp on epoch day.
|
|
26
|
-
// Note: timestamp is better than saving seconds so we can easily view this in a particular timezone
|
|
27
|
-
const localePattern = 'hh:mmaa'
|
|
28
|
-
const dropdownRef = useRef<DropdownRef>(null)
|
|
29
|
-
const id = props.id || props.name
|
|
30
|
-
|
|
31
|
-
// Convert the value to a valid time value
|
|
32
|
-
const validValue = useMemo(() => {
|
|
33
|
-
const num = typeof value === 'string' ? parseInt(value) : value
|
|
34
|
-
return typeof num === 'number' && !isNaN(num) ? num : new Date(0).getTime()
|
|
35
|
-
}, [value])
|
|
36
|
-
|
|
37
|
-
// Hold the input value in state
|
|
38
|
-
const [inputValue, setInputValue] = useState(() => getInputValue(validValue))
|
|
39
|
-
|
|
40
|
-
function onTimePickerChange(value: Timestamp) {
|
|
41
|
-
setInputValue(getInputValue(value))
|
|
42
|
-
if (onChange) onChange({ target: { name: props.name, value: value }})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function getInputValue(timestamp: Timestamp) {
|
|
46
|
-
// Get the input-value in local timezone
|
|
47
|
-
return typeof timestamp === 'number' ? format(new Date(timestamp), localePattern) : ''
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function onInputChange(e: React.ChangeEvent<HTMLInputElement>) {
|
|
51
|
-
// Assume the string is in local timezone, and calls onChange with "raw" values (should update state, thus updating the value).
|
|
52
|
-
setInputValue(e.target.value) // keep the input value in sync
|
|
53
|
-
const [, _hour, _minute, _second, _period] = e.target.value.match(/(\d{1,2}):(\d{2})(:\d{2})?\s*(am|pm)/i) || []
|
|
54
|
-
if (!_hour || !_minute) return
|
|
55
|
-
const hour24 = parseInt(_hour) < 12 && _period.match(/pm/i) ? parseInt(_hour) + 12 : parseInt(_hour)
|
|
56
|
-
const minute = parseInt(_minute)
|
|
57
|
-
|
|
58
|
-
// Assume the time string is in the local timezone, and convert to UTC date from epoch
|
|
59
|
-
const localDate = new Date(0)
|
|
60
|
-
localDate.setHours(hour24, minute, _second ? parseInt(_second) : 0, 0)
|
|
61
|
-
const value = localDate.getTime()
|
|
62
|
-
if (onChange) onChange({ target: { name: props.name, value: value }})
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
function onNowClick() {
|
|
66
|
-
const epochDay = new Date(0)
|
|
67
|
-
const now = new Date()
|
|
68
|
-
// now set hours, minutes, seconds to now but on epoch day
|
|
69
|
-
epochDay.setHours(now.getHours(), now.getMinutes(), now.getSeconds(), 0)
|
|
70
|
-
onTimePickerChange(epochDay.getTime())
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
return (
|
|
74
|
-
<Dropdown
|
|
75
|
-
ref={dropdownRef}
|
|
76
|
-
menuToggles={false}
|
|
77
|
-
// animate={false}
|
|
78
|
-
// menuIsOpen={true}
|
|
79
|
-
minWidth={0}
|
|
80
|
-
dir={dir}
|
|
81
|
-
menuContent={
|
|
82
|
-
<div>
|
|
83
|
-
<div className="flex justify-center h-[250px]">
|
|
84
|
-
<TimePicker date={new Date(validValue)} onChange={onTimePickerChange} />
|
|
85
|
-
</div>
|
|
86
|
-
<div className="flex justify-between p-2 border-t border-gray-100">
|
|
87
|
-
<Button color="secondary" size="xs" onClick={() => onNowClick()}>Now</Button>
|
|
88
|
-
<Button color="primary" size="xs" onClick={() => dropdownRef.current?.setIsActive(false)}>Done</Button>
|
|
89
|
-
</div>
|
|
90
|
-
</div>
|
|
91
|
-
}
|
|
92
|
-
>
|
|
93
|
-
<div className="grid grid-cols-1">
|
|
94
|
-
{Icon}
|
|
95
|
-
<input
|
|
96
|
-
{...props}
|
|
97
|
-
id={id}
|
|
98
|
-
autoComplete="off"
|
|
99
|
-
className={(props.className||'')}// + props.className?.includes('is-invalid') ? ' is-invalid' : ''}
|
|
100
|
-
value={inputValue}
|
|
101
|
-
onChange={onInputChange}
|
|
102
|
-
onBlur={() => setInputValue(getInputValue(validValue))} // onChange should of updated the value -> validValue by this point
|
|
103
|
-
type="text"
|
|
104
|
-
/>
|
|
105
|
-
</div>
|
|
106
|
-
</Dropdown>
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
export function TimePicker({ date, onChange }: TimePickerProps) {
|
|
111
|
-
const refs = {
|
|
112
|
-
hour: useRef<HTMLDivElement>(null),
|
|
113
|
-
minute: useRef<HTMLDivElement>(null),
|
|
114
|
-
period: useRef<HTMLDivElement>(null),
|
|
115
|
-
}
|
|
116
|
-
const lists = [
|
|
117
|
-
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], // hours
|
|
118
|
-
[
|
|
119
|
-
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26,
|
|
120
|
-
27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
|
|
121
|
-
51, 52, 53, 54, 55, 56, 57, 58, 59,
|
|
122
|
-
], // minutes
|
|
123
|
-
['AM', 'PM'], // AM/PM
|
|
124
|
-
]
|
|
125
|
-
|
|
126
|
-
// Get current values from date or use defaults
|
|
127
|
-
const hour = date ? parseInt(format(date, 'h')) : undefined
|
|
128
|
-
const minute = date ? parseInt(format(date, 'm')) : undefined
|
|
129
|
-
const period = date ? format(date, 'a') : undefined
|
|
130
|
-
|
|
131
|
-
// Scroll into view when the date changes
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
if (hour !== undefined) scrollIntoView('hour', hour)
|
|
134
|
-
if (minute !== undefined) scrollIntoView('minute', minute)
|
|
135
|
-
if (period) scrollIntoView('period', period)
|
|
136
|
-
}, [date])
|
|
137
|
-
|
|
138
|
-
const handleTimeChange = (type: 'hour' | 'minute' | 'period', value: string | number) => {
|
|
139
|
-
// Creates a new date object in the local timezone, and calls onChange with the timestamp
|
|
140
|
-
const newDate = new Date(date || new Date())
|
|
141
|
-
|
|
142
|
-
if (type === 'hour') {
|
|
143
|
-
// Parse the time with the new hour value
|
|
144
|
-
const timeString = `${value}:${format(newDate, 'mm')} ${format(newDate, 'a')}`
|
|
145
|
-
const updatedDate = parse(timeString, 'h:mm a', newDate)
|
|
146
|
-
newDate.setHours(updatedDate.getHours(), updatedDate.getMinutes())
|
|
147
|
-
} else if (type === 'minute') {
|
|
148
|
-
// Parse the time with the new minute value
|
|
149
|
-
const timeString = `${format(newDate, 'h')}:${value} ${format(newDate, 'a')}`
|
|
150
|
-
const updatedDate = parse(timeString, 'h:mm a', newDate)
|
|
151
|
-
newDate.setMinutes(updatedDate.getMinutes())
|
|
152
|
-
} else if (type === 'period') {
|
|
153
|
-
// Parse the time with the new period value
|
|
154
|
-
const timeString = `${format(newDate, 'h')}:${format(newDate, 'mm')} ${value}`
|
|
155
|
-
const updatedDate = parse(timeString, 'h:mm a', newDate)
|
|
156
|
-
newDate.setHours(updatedDate.getHours())
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
onChange(newDate.getTime())
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function scrollIntoView (type: 'hour' | 'minute' | 'period', value: string | number) {
|
|
163
|
-
const container = refs[type].current
|
|
164
|
-
if (!container) return
|
|
165
|
-
const element = container.querySelector(`[data-val="${value}"]`) as HTMLElement
|
|
166
|
-
if (!element) return
|
|
167
|
-
|
|
168
|
-
const target =
|
|
169
|
-
element.offsetTop
|
|
170
|
-
- (container.clientHeight / 2)
|
|
171
|
-
+ (element.clientHeight / 2)
|
|
172
|
-
|
|
173
|
-
container.scrollTo({ top: target, behavior: 'smooth' })
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
return (
|
|
177
|
-
lists.map((list, i) => {
|
|
178
|
-
const type = i === 0 ? 'hour' : i === 1 ? 'minute' : 'period'
|
|
179
|
-
const currentValue = i === 0 ? hour : i === 1 ? minute : period
|
|
180
|
-
|
|
181
|
-
return (
|
|
182
|
-
<div
|
|
183
|
-
key={i}
|
|
184
|
-
ref={refs[type]}
|
|
185
|
-
className="w-[60px] relative overflow-hidden hover:overflow-y-auto border-l border-gray-100 sm-scrollbar first:border-l-0"
|
|
186
|
-
>
|
|
187
|
-
<div className="w-[60px] absolute flex flex-col items-center py-2">
|
|
188
|
-
{/* using absolute since the scrollbar takes up space */}
|
|
189
|
-
{list.map(item => (
|
|
190
|
-
<div
|
|
191
|
-
className="py-[1px] flex group cursor-pointer"
|
|
192
|
-
data-val={item}
|
|
193
|
-
key={item}
|
|
194
|
-
onClick={(_e) => {
|
|
195
|
-
handleTimeChange(type, item)
|
|
196
|
-
}}
|
|
197
|
-
>
|
|
198
|
-
<button
|
|
199
|
-
key={item}
|
|
200
|
-
className={
|
|
201
|
-
`${dayButtonClassName} rounded-full flex justify-center items-center group-hover:bg-gray-100 `
|
|
202
|
-
+ (item === currentValue ? '!bg-input-border-focus text-white' : '')
|
|
203
|
-
}
|
|
204
|
-
>
|
|
205
|
-
{item.toString().padStart(2, '0').toLowerCase()}
|
|
206
|
-
</button>
|
|
207
|
-
</div>
|
|
208
|
-
))}
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
)
|
|
212
|
-
})
|
|
213
|
-
)
|
|
214
|
-
}
|