Skip to content
Snippets Groups Projects
font-size-toolbar-button.tsx 4.07 KiB
'use client';
import { useState } from 'react';

import { cn } from '@udecode/cn';
import { type TElement, getAboveNode, getMarks } from '@udecode/plate-common';
import {
  focusEditor,
  useEditorPlugin,
  useEditorSelector,
} from '@udecode/plate-common/react';
import { BaseFontSizePlugin, toUnitLess } from '@udecode/plate-font';
import { FontSizePlugin } from '@udecode/plate-font/react';
import { HEADING_KEYS } from '@udecode/plate-heading';
import { Minus, Plus } from 'lucide-react';

import { Popover, PopoverContent, PopoverTrigger } from './popover';
import { ToolbarButton } from './toolbar';

const DEFAULT_FONT_SIZE = '12';

const FONT_SIZE_MAP = {
  [HEADING_KEYS.h1]: '36',
  [HEADING_KEYS.h2]: '24',
  [HEADING_KEYS.h3]: '20',
} as const;

const FONT_SIZES = [
  '8',
  '9',
  '10',
  '12',
  '14',
  '16',
  '18',
  '24',
  '30',
  '36',
  '48',
  '60',
  '72',
  '96',
] as const;

export function FontSizeToolbarButton() {
  const [inputValue, setInputValue] = useState(DEFAULT_FONT_SIZE);
  const [isFocused, setIsFocused] = useState(false);
  const { api, editor } = useEditorPlugin(BaseFontSizePlugin);

  const cursorFontSize = useEditorSelector((editor) => {
    const marks = getMarks(editor);
    const fontSize = marks?.[FontSizePlugin.key];

    if (fontSize) {
      return toUnitLess(fontSize as string);
    }

    const [node] =
      getAboveNode<TElement>(editor, {
        at: editor.selection?.focus,
      }) || [];

    return node?.type && node.type in FONT_SIZE_MAP
      ? FONT_SIZE_MAP[node.type as keyof typeof FONT_SIZE_MAP]
      : DEFAULT_FONT_SIZE;
  }, []);

  const handleInputChange = () => {
    const newSize = toUnitLess(inputValue);

    if (Number.parseInt(newSize) < 1 || Number.parseInt(newSize) > 100) {
      focusEditor(editor);

      return;
    }
    if (newSize !== toUnitLess(cursorFontSize)) {
      api.fontSize.setMark(`${newSize}px`);
    }

    focusEditor(editor);
  };

  const handleFontSizeChange = (delta: number) => {
    const newSize = Number(displayValue) + delta;
    api.fontSize.setMark(`${newSize}px`);
    focusEditor(editor);
  };

  const displayValue = isFocused ? inputValue : cursorFontSize;

  return (
    <div className='flex h-7 items-center gap-1 rounded-md bg-zinc-100/60 p-0  dark:bg-zinc-800/60'>
      <ToolbarButton onClick={() => handleFontSizeChange(-1)}>
        <Minus />
      </ToolbarButton>

      <Popover open={isFocused} modal={false}>
        <PopoverTrigger asChild>
          <input
            className={cn(
              'h-full w-10 shrink-0 bg-transparent px-1 text-center text-xs hover:bg-zinc-100 dark:hover:bg-zinc-800'
            )}
            value={displayValue}
            onBlur={() => {
              setIsFocused(false);
              handleInputChange();
            }}
            onChange={(e) => setInputValue(e.target.value)}
            onFocus={() => {
              setIsFocused(true);
              setInputValue(toUnitLess(cursorFontSize));
            }}
            onKeyDown={(e) => {
              if (e.key === 'Enter') {
                e.preventDefault();
                handleInputChange();
              }
            }}
            data-plate-focus='true'
            type='text'
          />
        </PopoverTrigger>
        <PopoverContent
          className='w-10 px-px py-1'
          onOpenAutoFocus={(e) => e.preventDefault()}
        >
          {FONT_SIZES.map((size) => (
            <button
              key={size}
              className={cn(
                'flex h-8 w-full items-center justify-center  text-sm hover:bg-zinc-100 data-[highlighted=true]:bg-zinc-100 dark:hover:bg-zinc-800 dark:data-[highlighted=true]:bg-zinc-800'
              )}
              onClick={() => {
                api.fontSize.setMark(`${size}px`);
                setIsFocused(false);
              }}
              data-highlighted={size === displayValue}
              type='button'
            >
              {size}
            </button>
          ))}
        </PopoverContent>
      </Popover>

      <ToolbarButton onClick={() => handleFontSizeChange(1)}>
        <Plus />
      </ToolbarButton>
    </div>
  );
}