Skip to content

Commit b2684b4

Browse files
committed
Refactor
1 parent 0442226 commit b2684b4

File tree

2 files changed

+70
-62
lines changed

2 files changed

+70
-62
lines changed

packages/gitbook/src/components/AIChat/AIChatInput.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function AIChatInput(props: {
5656
label="Assistant chat input"
5757
placeholder={tString(language, 'ai_chat_input_placeholder')}
5858
onChange={(event) => onChange(event.target.value)}
59-
onSubmit={() => onSubmit(value)}
59+
onSubmit={(val) => onSubmit(val as string)}
6060
value={value}
6161
submitButton={{
6262
label: tString(language, 'send'),

packages/gitbook/src/components/primitives/Input.tsx

Lines changed: 69 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ const SIZE_CLASSES = {
5151
};
5252

5353
/**
54-
* Input base component with core functionality and shared styles.
54+
* Input component with core functionality (submitting, clearing, validating) and shared styles.
5555
*/
5656
export const Input = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, InputProps>(
5757
(props, passedRef) => {
@@ -81,40 +81,47 @@ export const Input = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, In
8181
...rest
8282
} = props;
8383

84-
const [value, setValue] = React.useState(initialValue ?? '');
84+
const [internalValue, setInternalValue] = React.useState(initialValue ?? '');
8585
const [submitted, setSubmitted] = React.useState(false);
8686
const inputRef = React.useRef<HTMLInputElement | HTMLTextAreaElement>(null);
8787
const ref = passedRef ?? inputRef;
8888

8989
const language = useLanguage();
90+
const isControlled = 'value' in props;
91+
const value = isControlled ? (initialValue ?? '') : internalValue;
9092
const hasValue = value.toString().trim();
9193
const hasValidValue =
9294
hasValue &&
9395
(maxLength ? value.toString().length <= maxLength : true) &&
9496
(minLength ? value.toString().length >= minLength : true);
9597

96-
React.useEffect(() => {
97-
setValue(initialValue ?? '');
98-
}, [initialValue]);
99-
10098
const handleChange = (event: React.ChangeEvent<HybridInputElement>) => {
101-
setValue(event.target.value);
99+
const newValue = event.target.value;
100+
if (!isControlled) {
101+
setInternalValue(newValue);
102+
}
102103
onChange?.(event);
103104

104-
// Auto-resize
105105
if (multiline && resize && 'current' in ref && ref.current) {
106106
ref.current.style.height = 'auto';
107107
ref.current.style.height = `${ref.current.scrollHeight}px`;
108108
}
109109
};
110110

111+
const handleClear = () => {
112+
if (!('current' in ref) || !ref.current) return;
113+
114+
const syntheticEvent = {
115+
target: { value: '' },
116+
currentTarget: ref.current,
117+
} as React.ChangeEvent<HybridInputElement>;
118+
119+
handleChange(syntheticEvent);
120+
};
121+
111122
const handleClick = () => {
112123
if (!('current' in ref)) return;
113-
114-
const element = ref.current;
115-
if (element) {
116-
element.focus();
117-
}
124+
ref.current?.focus();
118125
};
119126

120127
const handleSubmit = () => {
@@ -124,38 +131,37 @@ export const Input = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, In
124131
}
125132
};
126133

127-
const input = (
128-
<input
129-
className={tcls(
130-
'peer -m-2 max-h-64 grow resize-none text-left outline-none placeholder:text-tint/8 aria-busy:cursor-progress',
131-
SIZE_CLASSES[sizing].input
132-
)}
133-
type="text"
134-
ref={ref as React.Ref<HTMLInputElement>}
135-
value={value}
136-
size={1} // This will make the input have the smallest possible width (1 character) so we can grow it with flexbox
137-
onKeyDown={(event) => {
138-
if (event.key === 'Enter' && !event.shiftKey && value.toString().trim()) {
139-
event.preventDefault();
140-
handleSubmit();
141-
}
142-
if (event.key === 'Escape') {
143-
event.preventDefault();
144-
event.currentTarget.blur();
145-
}
146-
onKeyDown?.(event as React.KeyboardEvent<HybridInputElement>);
147-
}}
148-
aria-busy={ariaBusy}
149-
onChange={handleChange}
150-
aria-label={ariaLabel ?? label}
151-
placeholder={placeholder ? placeholder : label}
152-
disabled={disabled}
153-
maxLength={maxLength}
154-
minLength={minLength}
155-
{...(rest as React.InputHTMLAttributes<HTMLInputElement>)}
156-
/>
134+
const handleKeyDown = (event: React.KeyboardEvent<HybridInputElement>) => {
135+
if (event.key === 'Enter' && !event.shiftKey && hasValue) {
136+
event.preventDefault();
137+
handleSubmit();
138+
} else if (event.key === 'Escape') {
139+
event.preventDefault();
140+
event.currentTarget.blur();
141+
}
142+
onKeyDown?.(event);
143+
};
144+
145+
const inputClassName = tcls(
146+
'peer -m-2 max-h-64 grow resize-none text-left outline-none placeholder:text-tint/8 aria-busy:cursor-progress',
147+
SIZE_CLASSES[sizing].input
157148
);
158149

150+
const inputProps = {
151+
className: inputClassName,
152+
ref: ref as React.Ref<HTMLInputElement | HTMLTextAreaElement>,
153+
value: value,
154+
onKeyDown: handleKeyDown,
155+
'aria-busy': ariaBusy,
156+
onChange: handleChange,
157+
'aria-label': ariaLabel ?? label,
158+
placeholder: placeholder ?? label,
159+
disabled: disabled,
160+
maxLength: maxLength,
161+
minLength: minLength,
162+
...rest,
163+
};
164+
159165
return (
160166
<div
161167
className={tcls(
@@ -206,33 +212,37 @@ export const Input = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, In
206212
multiline ? 'mt-0.5' : '',
207213
hasValue ? 'group-focus-within/input:flex' : ''
208214
)}
209-
onClick={() => {
210-
handleChange({
211-
target: {
212-
value: '',
213-
},
214-
} as React.ChangeEvent<HybridInputElement>);
215-
}}
215+
onClick={handleClear}
216216
/>
217217
) : null}
218-
{multiline ? <textarea {...input.props} /> : input}
218+
{multiline ? (
219+
<textarea
220+
{...(inputProps as React.TextareaHTMLAttributes<HTMLTextAreaElement>)}
221+
/>
222+
) : (
223+
<input
224+
{...(inputProps as React.InputHTMLAttributes<HTMLInputElement>)}
225+
type="text"
226+
size={1}
227+
/>
228+
)}
219229

220-
<div className={multiline ? 'absolute top-2.5 right-2.5' : ''}>
221-
{keyboardShortcut !== false ? (
222-
typeof keyboardShortcut === 'object' ? (
230+
{keyboardShortcut !== false ? (
231+
<div className={multiline ? 'absolute top-2.5 right-2.5' : ''}>
232+
{typeof keyboardShortcut === 'object' ? (
223233
<KeyboardShortcut {...keyboardShortcut} />
224234
) : onSubmit && !submitted && hasValue ? (
225235
<KeyboardShortcut
226236
keys={['enter']}
227237
className="hidden bg-tint-base group-focus-within/input:flex"
228238
/>
229-
) : null
230-
) : null}
231-
</div>
239+
) : null}
240+
</div>
241+
) : null}
232242
</div>
233243
{trailing || submitButton || maxLength ? (
234244
<div className="flex items-center gap-2 empty:hidden">
235-
{trailing ? trailing : null}
245+
{trailing}
236246
{maxLength && !submitted && value.toString().length > maxLength * 0.8 ? (
237247
<span
238248
className={tcls(
@@ -266,9 +276,7 @@ export const Input = React.forwardRef<HTMLInputElement | HTMLTextAreaElement, In
266276
disabled={disabled || !hasValidValue}
267277
iconOnly={!multiline}
268278
className="ml-auto"
269-
{...(typeof submitButton === 'object'
270-
? { ...submitButton }
271-
: undefined)}
279+
{...(typeof submitButton === 'object' ? submitButton : {})}
272280
/>
273281
) : null}
274282
</div>

0 commit comments

Comments
 (0)