mirror of
https://github.com/supabase/supabase.git
synced 2026-05-09 02:09:50 -04:00
56de26fe22
This PR migrates the whole monorepo to use Tailwind v4: - Removed `@tailwindcss/container-queries` plugin since it's included by default in v4, - Bump all instances of Tailwind to v4. Made minimal changes to the shared config to remove non-supported features (`alpha` mentions), - Migrate all apps to be compatible with v4 configs, - Fix the `typography.css` import in 3 apps, - Add missing rules which were included by default in v3, - Run `pnpm dlx @tailwindcss/upgrade` on all apps, which renames a lot of classes - Rename all misnamed classes according to https://tailwindcss.com/docs/upgrade-guide#renamed-utilities in all apps. --------- Co-authored-by: Jordi Enric <jordi.err@gmail.com>
289 lines
7.6 KiB
TypeScript
289 lines
7.6 KiB
TypeScript
import { format } from 'date-fns'
|
|
import { Clock } from 'lucide-react'
|
|
import { useEffect, useState } from 'react'
|
|
import { cn } from 'ui'
|
|
|
|
import type { TimeSplitInputProps, TimeType } from './DatePicker.types'
|
|
import {
|
|
isUnixMicro,
|
|
unixMicroToIsoTimestamp,
|
|
} from '@/components/interfaces/Settings/Logs/Logs.utils'
|
|
|
|
const inputStyle = cn(
|
|
'w-6 p-0 text-center text-xs text-foreground outline-hidden cursor-text',
|
|
'ring-0 focus:ring-0 ring-none border-none bg-transparent'
|
|
)
|
|
|
|
export const TimeSplitInput = ({
|
|
type,
|
|
time,
|
|
setTime,
|
|
setStartTime,
|
|
setEndTime,
|
|
startTime,
|
|
endTime,
|
|
startDate,
|
|
endDate,
|
|
}: TimeSplitInputProps) => {
|
|
const [focus, setFocus] = useState(false)
|
|
|
|
function handleOnBlur() {
|
|
const _time = time
|
|
|
|
if (_time.HH.length === 1) _time.HH = '0' + _time.HH
|
|
if (_time.mm.length === 1) _time.mm = '0' + _time.mm
|
|
if (_time.ss.length === 1) _time.ss = '0' + _time.ss
|
|
|
|
if (!_time.HH) _time.HH = '00'
|
|
if (!_time.mm) _time.mm = '00'
|
|
if (!_time.ss) _time.ss = '00'
|
|
|
|
let endTimeChanges = false
|
|
let endTimePayload = endTime
|
|
|
|
let startTimeChanges = false
|
|
let startTimePayload = startTime
|
|
|
|
// Only run time conflicts if
|
|
// startDate and endDate are the same date
|
|
|
|
if (format(new Date(startDate), 'dd/mm/yyyy') == format(new Date(endDate), 'dd/mm/yyyy')) {
|
|
// checks if start time is ahead of end time
|
|
|
|
if (type === 'start') {
|
|
if (_time.HH && Number(_time.HH) > Number(endTime.HH)) {
|
|
endTimePayload.HH = _time.HH
|
|
endTimeChanges = true
|
|
}
|
|
|
|
if (
|
|
// also check the hour
|
|
_time.HH &&
|
|
Number(_time.HH) >= Number(endTime.HH) &&
|
|
// check the minutes
|
|
_time.mm &&
|
|
Number(_time.mm) > Number(endTime.mm)
|
|
) {
|
|
endTimePayload.mm = _time.mm
|
|
endTimeChanges = true
|
|
}
|
|
|
|
if (
|
|
// also check the hour
|
|
_time.HH &&
|
|
Number(_time.HH) >= Number(endTime.HH) &&
|
|
// check the minutes
|
|
_time.mm &&
|
|
Number(_time.mm) >= Number(endTime.mm) &&
|
|
// check the seconds
|
|
_time.ss &&
|
|
Number(_time.ss) > Number(endTime.ss)
|
|
) {
|
|
endTimePayload.ss = _time.ss
|
|
endTimeChanges = true
|
|
}
|
|
}
|
|
|
|
if (type === 'end') {
|
|
if (_time.HH && Number(_time.HH) < Number(startTime.HH)) {
|
|
startTimePayload.HH = _time.HH
|
|
startTimeChanges = true
|
|
}
|
|
|
|
if (
|
|
// also check the hour
|
|
_time.HH &&
|
|
Number(_time.HH) <= Number(startTime.HH) &&
|
|
// check the minutes
|
|
_time.mm &&
|
|
Number(_time.mm) < Number(startTime.mm)
|
|
) {
|
|
startTimePayload.mm = _time.mm
|
|
startTimeChanges = true
|
|
}
|
|
|
|
if (
|
|
// also check the hour
|
|
_time.HH &&
|
|
Number(_time.HH) <= Number(startTime.HH) &&
|
|
// check the minutes
|
|
_time.mm &&
|
|
Number(_time.mm) <= Number(startTime.mm) &&
|
|
// check the seconds
|
|
_time.ss &&
|
|
Number(_time.ss) < Number(startTime.ss)
|
|
) {
|
|
startTimePayload.ss = _time.ss
|
|
startTimeChanges = true
|
|
}
|
|
}
|
|
}
|
|
|
|
setTime({ ..._time })
|
|
|
|
if (endTimeChanges) {
|
|
setEndTime({ ...endTimePayload })
|
|
}
|
|
if (startTimeChanges) {
|
|
setStartTime({ ...startTimePayload })
|
|
}
|
|
|
|
setFocus(false)
|
|
}
|
|
|
|
function handleOnChange(value: string, valueType: TimeType) {
|
|
const payload = {
|
|
HH: time.HH,
|
|
mm: time.mm,
|
|
ss: time.ss,
|
|
}
|
|
if (value.length > 2) return
|
|
|
|
switch (valueType) {
|
|
case 'HH':
|
|
if (value && Number(value) > 23) return
|
|
break
|
|
case 'mm':
|
|
if (value && Number(value) > 59) return
|
|
break
|
|
case 'ss':
|
|
if (value && Number(value) > 59) return
|
|
break
|
|
default:
|
|
break
|
|
}
|
|
|
|
payload[valueType] = value
|
|
setTime({ ...payload })
|
|
}
|
|
|
|
const handleFocus = (event: React.FocusEvent<HTMLInputElement>) => {
|
|
event.target.select()
|
|
setFocus(true)
|
|
// Prevent parent dialog from stealing focus
|
|
event.stopPropagation()
|
|
}
|
|
|
|
const handleClick = (event: React.MouseEvent<HTMLInputElement>) => {
|
|
event.stopPropagation()
|
|
}
|
|
|
|
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
|
// Allow only numbers and navigation keys
|
|
const allowedKeys = ['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter']
|
|
const isNumber = /^[0-9]$/.test(event.key)
|
|
|
|
if (!isNumber && !allowedKeys.includes(event.key)) {
|
|
event.preventDefault()
|
|
}
|
|
|
|
// Prevent parent dialog from stealing focus on keydown
|
|
event.stopPropagation()
|
|
}
|
|
|
|
const handleInput = (event: React.FormEvent<HTMLInputElement>) => {
|
|
// Prevent parent dialog from stealing focus on input
|
|
event.stopPropagation()
|
|
}
|
|
|
|
function handlePaste(event: ClipboardEvent) {
|
|
event.preventDefault()
|
|
event.stopPropagation()
|
|
|
|
navigator.clipboard.readText().then((text) => {
|
|
let date: null | Date = null
|
|
if (isUnixMicro(text)) {
|
|
date = new Date(unixMicroToIsoTimestamp(text))
|
|
} else {
|
|
date = new Date(Number(text))
|
|
}
|
|
|
|
if (isNaN(date.getTime())) {
|
|
console.warn('Invalid date or timestamp in clipboard')
|
|
return
|
|
}
|
|
|
|
if (date) {
|
|
// Offset the date by 1s to make sure it will find the logs in that second
|
|
if (type === 'start') {
|
|
date.setSeconds(date.getSeconds() - 1)
|
|
}
|
|
if (type === 'end') {
|
|
date.setSeconds(date.getSeconds() + 1)
|
|
}
|
|
|
|
setTime({
|
|
HH: date.getHours().toString().padStart(2, '0'),
|
|
mm: date.getMinutes().toString().padStart(2, '0'),
|
|
ss: date.getSeconds().toString().padStart(2, '0'),
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
useEffect(() => {
|
|
document.addEventListener('paste', handlePaste)
|
|
return () => document.removeEventListener('paste', handlePaste)
|
|
}, [startDate, endDate])
|
|
|
|
return (
|
|
<div
|
|
className={`
|
|
flex h-7 items-center justify-center
|
|
gap-0 rounded-sm border border-strong bg-surface-100 text-xs text-foreground-light
|
|
${focus && ' border-stronger outline outline-2 outline-border'}
|
|
hover:border-stronger transition-colors
|
|
`}
|
|
>
|
|
<div className="mr-1 text-foreground-lighter">
|
|
<Clock size={14} strokeWidth={1.5} />
|
|
</div>
|
|
|
|
<input
|
|
type="text"
|
|
onBlur={() => handleOnBlur()}
|
|
onFocus={handleFocus}
|
|
onClick={handleClick}
|
|
onKeyDown={handleKeyDown}
|
|
onInput={handleInput}
|
|
pattern="[0-23]*"
|
|
placeholder="00"
|
|
onChange={(e) => handleOnChange(e.target.value, 'HH')}
|
|
aria-label="Hours"
|
|
className={inputStyle}
|
|
value={time.HH}
|
|
/>
|
|
<span className="text-foreground-lighter">:</span>
|
|
<input
|
|
type="text"
|
|
onBlur={() => handleOnBlur()}
|
|
onFocus={handleFocus}
|
|
onClick={handleClick}
|
|
onKeyDown={handleKeyDown}
|
|
onInput={handleInput}
|
|
pattern="[0-59]*"
|
|
placeholder="00"
|
|
onChange={(e) => handleOnChange(e.target.value, 'mm')}
|
|
aria-label="Minutes"
|
|
className={inputStyle}
|
|
value={time.mm}
|
|
/>
|
|
<span className="text-foreground-lighter">:</span>
|
|
<input
|
|
type="text"
|
|
onBlur={() => handleOnBlur()}
|
|
onFocus={handleFocus}
|
|
onClick={handleClick}
|
|
onKeyDown={handleKeyDown}
|
|
onInput={handleInput}
|
|
pattern="[0-59]*"
|
|
placeholder="00"
|
|
onChange={(e) => handleOnChange(e.target.value, 'ss')}
|
|
aria-label="Seconds"
|
|
className={inputStyle}
|
|
value={time.ss}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|