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};