UNPKG

@helpwave/hightide

Version:

helpwave's component and theming library

1 lines 10.2 kB
{"version":3,"sources":["../../../src/components/user-input/ToggleableInput.tsx","../../../src/hooks/useSaveDelay.ts","../../../src/util/noop.ts"],"sourcesContent":["import type { HTMLInputTypeAttribute, InputHTMLAttributes } from 'react'\nimport { useEffect, useRef, useState } from 'react'\nimport { Pencil } from 'lucide-react'\nimport clsx from 'clsx'\nimport { useSaveDelay } from '../../hooks/useSaveDelay'\nimport { noop } from '../../util/noop'\n\ntype InputProps = {\n /**\n * The value\n */\n value: string,\n /**\n * @default 'text'\n */\n type?: HTMLInputTypeAttribute,\n /**\n * Callback for when the input's value changes\n * This is pretty much required but made optional for the rare cases where it actually isn't need such as when used with disabled\n * That could be enforced through a union type but that seems a bit overkill\n * @default noop\n */\n onChangeText?: (text: string) => void,\n onEditCompleted?: (text: string) => void,\n labelClassName?: string,\n initialState?: 'editing' | 'display',\n size?: number,\n disclaimer?: string,\n} & Omit<InputHTMLAttributes<HTMLInputElement>, 'value' | 'label' | 'type' | 'crossOrigin'>\n\n/**\n * A Text input component for inputting text. It changes appearance upon entering the edit mode and switches\n * back to display mode on loss of focus or on enter\n *\n * The State is managed by the parent\n */\nexport const ToggleableInput = ({\n type = 'text',\n value,\n onChange = noop,\n onChangeText = noop,\n onEditCompleted = noop,\n labelClassName = '',\n initialState = 'display',\n size = 20,\n disclaimer,\n onBlur,\n ...restProps\n }: InputProps) => {\n const [isEditing, setIsEditing] = useState(initialState !== 'display')\n const { restartTimer, clearUpdateTimer } = useSaveDelay(() => undefined, 3000)\n const ref = useRef<HTMLInputElement>(null)\n\n const onEditCompletedWrapper = (text: string) => {\n onEditCompleted(text)\n clearUpdateTimer()\n }\n\n useEffect(() => {\n if (isEditing) {\n ref.current?.focus()\n }\n }, [isEditing])\n\n return (\n <div>\n <div\n className=\"row items-center w-full gap-x-2 overflow-hidden\"\n onClick={() => !isEditing ? setIsEditing(!isEditing) : undefined}\n >\n <div className={clsx('row overflow-hidden', { 'flex-1': isEditing })}>\n {isEditing ? (\n <input\n ref={ref}\n {...restProps}\n value={value}\n type={type}\n onChange={event => {\n const value = event.target.value\n restartTimer(() => {\n onEditCompletedWrapper(value)\n })\n onChangeText(value)\n onChange(event)\n }}\n onBlur={(event) => {\n if (onBlur) {\n onBlur(event)\n }\n onEditCompletedWrapper(value)\n setIsEditing(false)\n }}\n onKeyDown={event => {\n if (event.key === 'Enter') {\n setIsEditing(false)\n onEditCompletedWrapper(value)\n }\n }}\n className={clsx(labelClassName, `w-full border-none rounded-none ring-0 outline-0 shadow-transparent decoration-primary p-0 underline-offset-4`, {\n underline: isEditing\n })}\n onFocus={event => event.target.select()}\n />\n ) : (\n <span\n className={clsx(labelClassName, 'max-w-xs break-words overflow-hidden')}\n >\n {value}\n </span>\n )}\n </div>\n <Pencil\n className={clsx(`cursor-pointer`, { 'text-transparent': isEditing })}\n size={size}\n style={{ minWidth: `${size}px` }}\n />\n </div>\n {(isEditing && disclaimer) && (\n <label className=\"text-negative\">\n {disclaimer}\n </label>\n )}\n </div>\n )\n}\n\nexport const ToggleableInputUncontrolled = ({\n value: initialValue,\n onChangeText = noop,\n ...restProps\n }: InputProps) => {\n const [value, setValue] = useState(initialValue)\n\n useEffect(() => {\n setValue(initialValue)\n }, [initialValue])\n\n return (\n <ToggleableInput\n value={value}\n onChangeText={text => {\n setValue(text)\n onChangeText(text)\n }}\n {...restProps}\n />\n )\n}\n","import { useEffect, useState } from 'react'\n\nexport function useSaveDelay(setNotificationStatus: (isShowing: boolean) => void, delay: number) {\n const [updateTimer, setUpdateTimer] = useState<NodeJS.Timeout | undefined>(undefined)\n const [notificationTimer, setNotificationTimer] = useState<NodeJS.Timeout | undefined>(undefined)\n\n const restartTimer = (onSave: () => void) => {\n clearTimeout(updateTimer)\n setUpdateTimer(setTimeout(() => {\n onSave()\n setNotificationStatus(true)\n // Show Saved Notification for fade animation duration\n clearTimeout(notificationTimer)\n setNotificationTimer(setTimeout(() => {\n setNotificationStatus(false)\n clearTimeout(notificationTimer)\n }, delay))\n clearTimeout(updateTimer)\n }, delay))\n }\n\n const clearUpdateTimer = (hasSaved = true) => {\n clearTimeout(updateTimer)\n if (hasSaved) {\n setNotificationStatus(true)\n clearTimeout(notificationTimer)\n setNotificationTimer(setTimeout(() => {\n setNotificationStatus(false)\n clearTimeout(notificationTimer)\n }, delay))\n } else {\n setNotificationStatus(false)\n }\n }\n\n useEffect(() => {\n return () => {\n clearTimeout(updateTimer)\n clearTimeout(notificationTimer)\n }\n }, []) // eslint-disable-line react-hooks/exhaustive-deps\n\n return { restartTimer, clearUpdateTimer }\n}","export const noop = () => undefined\n"],"mappings":"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","names":["useEffect","useState","useState","useEffect","value"]}