~ knotless Search/
↑↓ move↵ open⇧↵ all results

chadtmiller.com/pds.js

An atproto PDS in JavaScript, for Cloudflare Workers and Node

0
0
main
81 lines · 2.5 KB Raw History
import { useState } from 'react';
import { Prose } from '#/components/molecules/prose.jsx';
import { cn } from '#/lib/utils.js';

/**
 * A field for words that will be read as markdown, with the reading beside
 * the writing.
 *
 * What is typed here is rendered wherever it is read, so a writer who cannot
 * see that finds out after publishing. The preview is the same renderer the
 * page uses, so a fenced block that reads wrong here reads wrong there.
 *
 * Preview is a tab rather than a second pane, because at this width two
 * columns would give each of them half a line of code.
 */
export function MarkdownField({
  value,
  onChange,
  placeholder,
  rows = 8,
  disabled,
  label,
  autoFocus,
  className,
}) {
  const [preview, setPreview] = useState(false);

  return (
    <div className={cn('flex flex-col', className)}>
      <div className="flex items-center gap-1 border-border border-b">
        {[
          { key: 'write', label: 'Write' },
          { key: 'preview', label: 'Preview' },
        ].map((tab) => {
          const current = (tab.key === 'preview') === preview;
          return (
            <button
              key={tab.key}
              type="button"
              onClick={() => setPreview(tab.key === 'preview')}
              className={cn(
                '-mb-px cursor-pointer border-b-2 px-3 py-2 text-[13px]',
                current
                  ? 'border-key text-foreground'
                  : 'border-transparent text-faint hover:text-muted-foreground',
              )}
            >
              {tab.label}
            </button>
          );
        })}
      </div>

      {preview ? (
        // The written height, so the field does not jump as a reader looks.
        <div
          className="overflow-y-auto px-3 py-2.5"
          style={{ minHeight: `${rows * 1.6 + 1.25}rem` }}
        >
          {value.trim() ? (
            <Prose text={value} className="max-w-3xl" />
          ) : (
            <p className="text-[12.5px] text-faint">Nothing to preview yet.</p>
          )}
        </div>
      ) : (
        <textarea
          value={value}
          onChange={(event) => onChange(event.target.value)}
          placeholder={placeholder}
          aria-label={label}
          rows={rows}
          disabled={disabled}
          // biome-ignore lint/a11y/noAutofocus: a page opened to write one thing
          autoFocus={autoFocus}
          className="w-full resize-y border-0 bg-transparent px-3 py-2.5 font-mono text-[13px] text-foreground placeholder:text-faint focus:outline-none"
        />
      )}
    </div>
  );
}