a tool for shared writing and social publishing
1"use client"; 2import { useEffect, useRef, useState, type JSX } from "react"; 3import { onMouseDown } from "src/utils/iosInputMouseDown"; 4import { isIOS } from "src/utils/isDevice"; 5 6export const Input = ( 7 props: { 8 textarea?: boolean; 9 } & JSX.IntrinsicElements["input"] & 10 JSX.IntrinsicElements["textarea"], 11) => { 12 let { textarea, ...inputProps } = props; 13 let ref = useRef<HTMLInputElement>(null); 14 useEffect(() => { 15 if (!isIOS()) return; 16 if (props.autoFocus) { 17 focusElement(ref.current); 18 } 19 }, [props.autoFocus]); 20 21 if (textarea) return <textarea {...inputProps} />; 22 return ( 23 <input 24 {...inputProps} 25 autoFocus={isIOS() ? false : props.autoFocus} 26 ref={ref} 27 onMouseDown={onMouseDown} 28 /> 29 ); 30}; 31 32export const AsyncValueInput = ( 33 props: { 34 textarea?: boolean; 35 } & JSX.IntrinsicElements["input"] & 36 JSX.IntrinsicElements["textarea"], 37) => { 38 let [intermediateState, setIntermediateState] = useState( 39 props.value as string, 40 ); 41 42 useEffect(() => { 43 setIntermediateState(props.value as string); 44 }, [props.value]); 45 46 return ( 47 <Input 48 {...props} 49 value={intermediateState} 50 onChange={async (e) => { 51 if (!props.onChange) return; 52 setIntermediateState(e.currentTarget.value); 53 await Promise.all([ 54 props.onChange(e as React.ChangeEvent<HTMLInputElement>), 55 ]); 56 }} 57 /> 58 ); 59}; 60 61export const focusElement = (el?: HTMLInputElement | null) => { 62 if (!isIOS()) { 63 el?.focus(); 64 return; 65 } 66 67 let fakeInput = document.createElement("input"); 68 fakeInput.setAttribute("type", "text"); 69 fakeInput.style.position = "fixed"; 70 fakeInput.style.height = "0px"; 71 fakeInput.style.width = "0px"; 72 fakeInput.style.fontSize = "16px"; // disable auto zoom 73 document.body.appendChild(fakeInput); 74 fakeInput.focus(); 75 setTimeout(() => { 76 if (!el) return; 77 el.style.transform = "translateY(-2000px)"; 78 el?.focus(); 79 fakeInput.remove(); 80 el.value = " "; 81 el.setSelectionRange(1, 1); 82 requestAnimationFrame(() => { 83 if (el) { 84 el.style.transform = ""; 85 } 86 }); 87 setTimeout(() => { 88 if (!el) return; 89 el.value = ""; 90 el.setSelectionRange(0, 0); 91 }, 50); 92 }, 20); 93}; 94 95export const InputWithLabel = ( 96 props: { 97 label: string; 98 textarea?: boolean; 99 } & JSX.IntrinsicElements["input"] & 100 JSX.IntrinsicElements["textarea"], 101) => { 102 let { label, textarea, ...inputProps } = props; 103 let style = `appearance-none w-full font-normal not-italic bg-transparent text-base text-primary focus:outline-0 ${props.className} outline-hidden resize-none`; 104 return ( 105 <label className=" input-with-border flex flex-col gap-px text-sm text-tertiary font-bold italic leading-tight py-1! px-[6px]!"> 106 {props.label} 107 {textarea ? ( 108 <textarea {...inputProps} className={style} /> 109 ) : ( 110 <Input {...inputProps} className={style} /> 111 )} 112 </label> 113 ); 114};