teableio/teable

View on GitHub
packages/sdk/src/components/editor/text/Editor.tsx

Summary

Maintainability
A
25 mins
Test Coverage
import { SingleLineTextDisplayType } from '@teable/core';
import type { ISingleLineTextFieldOptions } from '@teable/core';
import { Link, Mail, Phone } from '@teable/icons';
import { Button, Input, cn } from '@teable/ui-lib';
import type { ForwardRefRenderFunction } from 'react';
import { forwardRef, useImperativeHandle, useRef, useState } from 'react';
import type { ICellEditor, IEditorRef } from '../type';
import { onMixedTextClick } from './utils';

interface ITextEditor extends ICellEditor<string | null> {
  options: ISingleLineTextFieldOptions;
}

const TextEditorBase: ForwardRefRenderFunction<IEditorRef<string>, ITextEditor> = (props, ref) => {
  const { value, options, onChange, className, readonly, style, saveOnBlur = true } = props;
  const [text, setText] = useState<string>(value || '');
  const inputRef = useRef<HTMLInputElement | null>(null);
  const showAs = options.showAs;

  useImperativeHandle(ref, () => ({
    focus: () => inputRef.current?.focus(),
    setValue: (value?: string) => setText(value || ''),
    saveValue,
  }));

  const onChangeInner = (e: React.ChangeEvent<HTMLInputElement>) => {
    setText(e.target.value);
  };

  const saveValue = () => {
    onChange?.(text ? text.trim() : null);
  };

  const onJump = (type: SingleLineTextDisplayType) => {
    onMixedTextClick(type, text);
  };

  const getIcon = (type: SingleLineTextDisplayType) => {
    switch (type) {
      case SingleLineTextDisplayType.Url:
        return <Link className="size-4" />;
      case SingleLineTextDisplayType.Email:
        return <Mail className="size-4" />;
      case SingleLineTextDisplayType.Phone:
        return <Phone className="size-4" />;
      default:
        return null;
    }
  };

  return (
    <div className="flex w-full items-center space-x-2">
      <Input
        ref={inputRef}
        style={style}
        className={cn('h-10 sm:h-8', className)}
        value={text}
        onChange={onChangeInner}
        onBlur={() => saveOnBlur && saveValue()}
        readOnly={readonly}
      />
      {showAs && (
        <Button variant="outline" size="sm" className="px-2" onClick={() => onJump(showAs.type)}>
          {getIcon(showAs.type)}
        </Button>
      )}
    </div>
  );
};

export const TextEditor = forwardRef(TextEditorBase);