@@ -1,6 +1,6 @@ | |||
import * as React from 'react'; | |||
import { TextControl } from '@tesseract-design/web-base'; | |||
import { delegateTriggerEvent, useClientSide } from '@modal-sh/react-utils'; | |||
import { useClientSide, useProxyInput } from '@modal-sh/react-utils'; | |||
import PhoneInput, { Country, Value } from 'react-phone-number-input/input'; | |||
import clsx from 'clsx'; | |||
@@ -82,49 +82,13 @@ export const PhoneNumberInput = React.forwardRef< | |||
const labelId = React.useId(); | |||
const defaultId = React.useId(); | |||
const id = idProp ?? defaultId; | |||
const defaultRef = React.useRef<PhoneNumberInputDerivedElement>(null); | |||
React.useEffect(() => { | |||
const { current: currentRaw } = defaultRef; | |||
const current = currentRaw as unknown as PhoneNumberInputDerivedElement; | |||
const forwardedRefProxy = new Proxy(current, { | |||
get(target, prop, receiver) { | |||
return Reflect.get(target, prop, receiver) as unknown; | |||
}, | |||
set(target, prop, newValue, receiver) { | |||
if (prop === 'value') { | |||
setPhoneNumber(newValue as string); | |||
current.value = newValue as string; | |||
return true; | |||
} | |||
return Reflect.set(target, prop, newValue, receiver); | |||
}, | |||
}) as unknown as PhoneNumberInputDerivedElement; | |||
if (typeof forwardedRef === 'function') { | |||
forwardedRef(forwardedRefProxy); | |||
return; | |||
} | |||
if (typeof forwardedRef === 'object' && forwardedRef) { | |||
const mutableForwardedRef = forwardedRef as React.MutableRefObject< | |||
PhoneNumberInputDerivedElement | |||
>; | |||
mutableForwardedRef.current = forwardedRefProxy; | |||
} | |||
}, [forwardedRef, defaultRef]); | |||
const handlePhoneInputChange = (phoneNumberValue: Value) => { | |||
if (!(typeof defaultRef === 'object' && defaultRef)) { | |||
return; | |||
} | |||
const { current: input } = defaultRef; | |||
if (!input) { | |||
return; | |||
} | |||
setPhoneNumber(phoneNumberValue); | |||
delegateTriggerEvent('change', input, phoneNumberValue ?? ''); | |||
}; | |||
const { | |||
defaultRef, | |||
handleChange: handlePhoneInputChange, | |||
} = useProxyInput<PhoneNumberInputDerivedElement, Value>({ | |||
forwardedRef, | |||
valueSetterFn: (v) => setPhoneNumber(v), | |||
}); | |||
const commonInputStyles = clsx( | |||
'bg-negative rounded-inherit w-full peer block font-inherit tabular-nums', | |||
@@ -0,0 +1,78 @@ | |||
import * as React from 'react'; | |||
import { delegateTriggerEvent } from '../event'; | |||
type ValueType = string | number | readonly string[]; | |||
interface HTMLElementWithValue extends HTMLElement { | |||
value: ValueType; | |||
} | |||
export interface UseProxyInputOptions<Element extends HTMLElementWithValue, Value = ValueType> { | |||
forwardedRef: React.Ref<Element>; | |||
valueSetterFn: (value: Value) => void; | |||
transformChangeHandlerArgs?: (...e: unknown[]) => Value; | |||
} | |||
export const useProxyInput = < | |||
T extends HTMLElementWithValue = HTMLElementWithValue, | |||
V = ValueType, | |||
>(options: UseProxyInputOptions<T, V>) => { | |||
const { | |||
forwardedRef, | |||
valueSetterFn, | |||
transformChangeHandlerArgs = (e) => e as V, | |||
} = options; | |||
const defaultRef = React.useRef<T>(null); | |||
React.useEffect(() => { | |||
const { current: currentRaw } = defaultRef; | |||
const current = currentRaw as unknown as T; | |||
const forwardedRefProxy = new Proxy(current, { | |||
get(target, prop, receiver) { | |||
return Reflect.get(target, prop, receiver) as unknown; | |||
}, | |||
set(target, prop, newValue, receiver) { | |||
if (prop === 'value') { | |||
const thisNewValue = newValue as V; | |||
const actualNewValue = ( | |||
Array.isArray(thisNewValue) ? thisNewValue.join('') : thisNewValue?.toString() ?? '' | |||
) as V; | |||
valueSetterFn(actualNewValue); | |||
current.value = actualNewValue as string; | |||
return true; | |||
} | |||
return Reflect.set(target, prop, newValue, receiver); | |||
}, | |||
}) as unknown as T; | |||
if (typeof forwardedRef === 'function') { | |||
forwardedRef(forwardedRefProxy); | |||
return; | |||
} | |||
if (typeof forwardedRef === 'object' && forwardedRef) { | |||
const mutableForwardedRef = forwardedRef as React.MutableRefObject<T>; | |||
mutableForwardedRef.current = forwardedRefProxy; | |||
} | |||
}); | |||
const handleChange = React.useCallback((e: unknown) => { | |||
if (!(typeof defaultRef === 'object' && defaultRef)) { | |||
return; | |||
} | |||
const { current: input } = defaultRef; | |||
if (!input) { | |||
return; | |||
} | |||
valueSetterFn(transformChangeHandlerArgs(e)); | |||
delegateTriggerEvent('change', input, e); | |||
}, [valueSetterFn, transformChangeHandlerArgs, defaultRef]); | |||
return React.useMemo(() => ({ | |||
defaultRef, | |||
handleChange, | |||
}), [ | |||
defaultRef, | |||
handleChange, | |||
]); | |||
}; |
@@ -0,0 +1,6 @@ | |||
import * as React from 'react'; | |||
export const useFallbackId = (id?: string) => { | |||
const defaultId = React.useId(); | |||
return id || defaultId; | |||
}; |
@@ -1,2 +1,4 @@ | |||
export * from './hooks/client-side'; | |||
export * from './hooks/form'; | |||
export * from './hooks/id'; | |||
export * from './event'; |
@@ -22,23 +22,51 @@ const TemporalPage: NextPage = () => { | |||
/> | |||
</Subsection> | |||
<Subsection title="With Ref"> | |||
<Formatted.PhoneNumberInput | |||
label="Phone" | |||
name="phone" | |||
enhanced | |||
border | |||
ref={phoneNumberRef} | |||
onChange={(e) => { console.log('change', e.currentTarget.name, e.currentTarget, e.currentTarget.value)}} | |||
/> | |||
<ActionButton | |||
onClick={() => { | |||
if (phoneNumberRef.current) { | |||
phoneNumberRef.current.value = '+639123456789'; | |||
} | |||
}} | |||
> | |||
Set Value | |||
</ActionButton> | |||
<div className="flex gap-4 flex-wrap"> | |||
<div> | |||
<Formatted.PhoneNumberInput | |||
label="Phone" | |||
name="phone" | |||
enhanced | |||
border | |||
ref={phoneNumberRef} | |||
onChange={(e) => { console.log('change', e.currentTarget.name, e.currentTarget, e.currentTarget.value)}} | |||
/> | |||
</div> | |||
<div> | |||
<ActionButton | |||
onClick={() => { | |||
if (phoneNumberRef.current) { | |||
phoneNumberRef.current.value = '+639123456789'; | |||
} | |||
}} | |||
> | |||
Set Value | |||
</ActionButton> | |||
</div> | |||
<div> | |||
<ActionButton | |||
onClick={() => { | |||
if (phoneNumberRef.current) { | |||
phoneNumberRef.current.value = '+63465123456'; | |||
} | |||
}} | |||
> | |||
Set Other Value | |||
</ActionButton> | |||
</div> | |||
<div> | |||
<ActionButton | |||
onClick={() => { | |||
if (phoneNumberRef.current) { | |||
phoneNumberRef.current.value = '+63288888888'; | |||
} | |||
}} | |||
> | |||
Set Yet Other Value | |||
</ActionButton> | |||
</div> | |||
</div> | |||
</Subsection> | |||
</Section> | |||
<Section title="EmailInput"> | |||