Something went wrong. Try again.
[READ-ONLY] Mirror of https://github.com/lukebennett88/luke-ui. ui.lukebennett.dev
Something went wrong. Try again.
3.6 kB · 120 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121import { FieldDescription, FieldError, FieldLabel } from '@luke-ui/react/primitives/field';import { TextInput, TextInputControl, TextInputRoot } from '@luke-ui/react/primitives/text-input';import { Stack } from '@luke-ui/react/stack';import { useIsMutating, useMutation, useQueryClient } from '@tanstack/react-query';import { useState } from 'react';import type { ProfileUpdate } from '../api/schemas.js';import { profileSchema } from '../api/schemas.js';import { settingsApi, settingsMutationErrorMessage } from '../api/settings-api.js';import { settingsQueryKey } from '../api/settings-query.js';import * as styles from '../styles/settings.css.js';import { SettingsRowControl, SettingsRowShell } from './settings-section.js';
type ProfileField = 'displayName' | 'title' | 'username';
export function ProfileTextField({ field, hint, label, placeholder, value,}: { field: ProfileField; hint?: string; label: string; placeholder?: string; value: string;}) { const queryClient = useQueryClient(); const [draft, setDraft] = useState<string>(); const [validationError, setValidationError] = useState<string>(); const mutationKey = [...settingsQueryKey, 'profile-field', field] as const; const mutation = useMutation({ mutationFn: (patch: ProfileUpdate) => settingsApi.updateProfile(patch), mutationKey, onSuccess: (settings) => { queryClient.setQueryData(settingsQueryKey, settings); setDraft(undefined); }, }); const isPending = useIsMutating({ exact: true, mutationKey }) > 0; const errorMessage = validationError ?? settingsMutationErrorMessage(mutation.error, field);
function revert() { if (isPending) return; setDraft(undefined); setValidationError(undefined); mutation.reset(); }
function commit() { if (isPending) return; const nextDraft = draft ?? value; if (nextDraft === value) { setValidationError(undefined); mutation.reset(); return; } const parsed = profileSchema.shape[field].safeParse(nextDraft); if (!parsed.success) { setValidationError(parsed.error.issues[0]?.message); return; } setValidationError(undefined); mutation.reset(); mutation.mutate({ [field]: parsed.data }); }
return ( <SettingsRowShell> <TextInputRoot className={styles.profileFieldShell} isInvalid={Boolean(errorMessage)} isReadOnly={isPending} name={field} onBlur={commit} onChange={(nextDraft) => { if (isPending) return; setDraft(nextDraft); setValidationError(undefined); mutation.reset(); }} size="small" validationBehavior="aria" value={draft ?? value} > <Stack flexGrow="1" gap="space.4" minInlineSize="0"> <FieldLabel>{label}</FieldLabel> {hint ? <FieldDescription>{hint}</FieldDescription> : null} </Stack> <SettingsRowControl> <Stack alignItems="flex-end" className={styles.profileTextFieldControl} gap="space.4" minInlineSize="0" > <TextInputControl className={styles.profileTextField}> <TextInput onKeyDown={(event) => { if (event.key === 'Escape') { event.preventDefault(); revert(); return; } if (event.key !== 'Enter' || event.nativeEvent.isComposing) return; event.preventDefault(); commit(); }} placeholder={placeholder} /> </TextInputControl> <FieldError> {errorMessage ? <span role="alert">{errorMessage}</span> : null} </FieldError> </Stack> </SettingsRowControl> </TextInputRoot> </SettingsRowShell> );}