oxios 1.23.0

Oxios Agent OS — Agent Operating System powered by oxi-sdk
/**
 * Editor settings popover for the knowledge-base markdown editor.
 *
 * Surfaces every configurable aspect of the editor in three groups:
 *  - Typography: font size, line height, font family (CSS custom properties)
 *  - Editor: line numbers, active-line highlight, fold gutter, bracket matching
 *  - Live rendering: live-preview widgets, token hiding, and per-type fold extensions
 *
 * All values read from / write to the client-side `useEditorPrefs` store —
 * changes apply instantly to any mounted MarkdownEditor (CSS vars are live;
 * extensions reconfigure via the reactive `extensions` prop).
 */
import { RotateCcw, Settings2 } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { Button } from '@/components/ui/button'
import { Label } from '@/components/ui/label'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { Select } from '@/components/ui/select'
import { Separator } from '@/components/ui/separator'
import { Slider } from '@/components/ui/slider'
import { Switch } from '@/components/ui/switch'
import { FONT_PRESETS, useEditorPrefs } from '@/stores/editor-prefs'

/** A single toggle row: label + description on the left, switch on the right. */
function ToggleRow({
  label,
  description,
  checked,
  onCheckedChange,
}: {
  label: string
  description?: string
  checked: boolean
  onCheckedChange: (v: boolean) => void
}) {
  return (
    <div className="flex items-center justify-between gap-3 py-1">
      <div className="min-w-0">
        <div className="text-sm">{label}</div>
        {description && <div className="text-xs text-muted-foreground">{description}</div>}
      </div>
      <Switch checked={checked} onCheckedChange={onCheckedChange} />
    </div>
  )
}

/** A color picker row: label + description on the left, swatch + clear on the right. */
function ColorRow({
  label,
  description,
  value,
  onChange,
}: {
  label: string
  description?: string
  value: string
  onChange: (v: string) => void
}) {
  return (
    <div className="flex items-center justify-between gap-3 py-1">
      <div className="min-w-0">
        <div className="text-sm">{label}</div>
        {description && <div className="text-xs text-muted-foreground">{description}</div>}
      </div>
      <div className="flex items-center gap-1.5 shrink-0">
        {value && (
          <button
            type="button"
            className="text-xs text-muted-foreground hover:text-foreground transition-colors"
            onClick={() => onChange('')}
          >
            <RotateCcw className="h-3 w-3" />
          </button>
        )}
        <input
          type="color"
          value={value || '#71717a'}
          onChange={(e) => onChange(e.target.value)}
          className="h-7 w-7 rounded border border-border cursor-pointer bg-transparent"
        />
      </div>
    </div>
  )
}

export function EditorSettingsPopover() {
  const { t } = useTranslation()
  const prefs = useEditorPrefs()

  return (
    <Popover>
      <PopoverTrigger asChild>
        <Button
          variant="ghost"
          size="icon"
          className="h-8 w-8"
          aria-label={t('knowledge.editorSettings')}
        >
          <Settings2 className="h-4 w-4" />
        </Button>
      </PopoverTrigger>
      <PopoverContent align="end" className="w-80 p-0">
        <div className="max-h-[70vh] overflow-y-auto">
          {/* ── Typography ─────────────────────────────────────── */}
          <div className="px-4 pt-4">
            <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
              {t('knowledge.editorPrefTypography')}
            </Label>
          </div>
          <div className="space-y-3 px-4 py-3">
            <div className="space-y-1.5">
              <div className="flex items-center justify-between">
                <span className="text-sm">{t('knowledge.editorPrefFontSize')}</span>
                <span className="text-xs tabular-nums text-muted-foreground">
                  {prefs.fontSize}px
                </span>
              </div>
              <Slider
                value={[prefs.fontSize]}
                onValueChange={(v) => v[0] !== undefined && prefs.setPref('fontSize', v[0])}
                min={10}
                max={24}
                step={1}
              />
            </div>
            <div className="space-y-1.5">
              <div className="flex items-center justify-between">
                <span className="text-sm">{t('knowledge.editorPrefLineHeight')}</span>
                <span className="text-xs tabular-nums text-muted-foreground">
                  {prefs.lineHeight.toFixed(1)}
                </span>
              </div>
              <Slider
                value={[prefs.lineHeight]}
                onValueChange={(v) => v[0] !== undefined && prefs.setPref('lineHeight', v[0])}
                min={1.0}
                max={2.4}
                step={0.1}
              />
            </div>
            <div className="space-y-1.5">
              <span className="text-sm">{t('knowledge.editorPrefFontFamily')}</span>
              <Select
                value={prefs.fontFamily}
                onValueChange={(v) => prefs.setPref('fontFamily', v)}
                options={FONT_PRESETS.map((p) => ({ value: p.value, label: p.label }))}
              />
            </div>
          </div>

          <Separator />

          {/* ── Editor chrome ──────────────────────────────────── */}
          <div className="px-4 pt-3">
            <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
              {t('knowledge.editorPrefEditor')}
            </Label>
          </div>
          <div className="space-y-0.5 px-4 py-2">
            <ToggleRow
              label={t('knowledge.editorPrefLineNumbers')}
              checked={prefs.lineNumbers}
              onCheckedChange={(v) => prefs.setPref('lineNumbers', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefActiveLine')}
              checked={prefs.activeLineHighlight}
              onCheckedChange={(v) => prefs.setPref('activeLineHighlight', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefFoldGutter')}
              checked={prefs.foldGutter}
              onCheckedChange={(v) => prefs.setPref('foldGutter', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefBracketMatching')}
              checked={prefs.bracketMatching}
              onCheckedChange={(v) => prefs.setPref('bracketMatching', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefStatusBar')}
              description={t('knowledge.editorPrefStatusBarDesc')}
              checked={prefs.showStatusBar}
              onCheckedChange={(v) => prefs.setPref('showStatusBar', v)}
            />
          </div>

          <Separator />

          {/* ── Live rendering ─────────────────────────────────── */}
          <div className="px-4 pt-3">
            <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
              {t('knowledge.editorPrefLiveRendering')}
            </Label>
          </div>
          <div className="space-y-0.5 px-4 py-2">
            <ToggleRow
              label={t('knowledge.editorPrefLivePreview')}
              description={t('knowledge.editorPrefLivePreviewDesc')}
              checked={prefs.livePreview}
              onCheckedChange={(v) => prefs.setPref('livePreview', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefTokenHiding')}
              description={t('knowledge.editorPrefTokenHidingDesc')}
              checked={prefs.tokenHiding}
              onCheckedChange={(v) => prefs.setPref('tokenHiding', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefMermaidFold')}
              checked={prefs.mermaidFold}
              onCheckedChange={(v) => prefs.setPref('mermaidFold', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefMathFold')}
              checked={prefs.mathFold}
              onCheckedChange={(v) => prefs.setPref('mathFold', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefImageFold')}
              checked={prefs.imageFold}
              onCheckedChange={(v) => prefs.setPref('imageFold', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefTableFold')}
              checked={prefs.tableFold}
              onCheckedChange={(v) => prefs.setPref('tableFold', v)}
            />
            <ToggleRow
              label={t('knowledge.editorPrefEmojiFold')}
              checked={prefs.emojiFold}
              onCheckedChange={(v) => prefs.setPref('emojiFold', v)}
            />
          </div>

          <Separator />

          {/* ── Markdown colors ────────────────────────────────── */}
          <div className="px-4 pt-3">
            <Label className="text-xs font-semibold uppercase tracking-wide text-muted-foreground">
              {t('knowledge.editorPrefMarkdownColors')}
            </Label>
          </div>
          {/* Heading colors — compact grid, one swatch per level */}
          <div className="px-4 py-2">
            <div className="flex items-center justify-between">
              <span className="text-sm">{t('knowledge.editorPrefHeadingColors')}</span>
              {Object.values(prefs.headingColors).some(Boolean) && (
                <button
                  type="button"
                  className="text-xs text-muted-foreground hover:text-foreground transition-colors"
                  onClick={() =>
                    prefs.setPref('headingColors', {
                      h1: '',
                      h2: '',
                      h3: '',
                      h4: '',
                      h5: '',
                      h6: '',
                    })
                  }
                >
                  <RotateCcw className="h-3 w-3" />
                </button>
              )}
            </div>
            <div className="mt-2 grid grid-cols-6 gap-2">
              {(['h1', 'h2', 'h3', 'h4', 'h5', 'h6'] as const).map((lvl) => {
                const hasCustom = Boolean(prefs.headingColors[lvl])
                return (
                  <div key={lvl} className="flex flex-col items-center gap-1">
                    <input
                      type="color"
                      value={prefs.headingColors[lvl] || '#71717a'}
                      onChange={(e) =>
                        prefs.setPref('headingColors', {
                          ...prefs.headingColors,
                          [lvl]: e.target.value,
                        })
                      }
                      className={`h-7 w-full rounded border cursor-pointer bg-transparent ${
                        hasCustom
                          ? 'ring-2 ring-primary ring-offset-1 ring-offset-background'
                          : 'border-border'
                      }`}
                    />
                    <span className="text-[10px] text-muted-foreground">{lvl.toUpperCase()}</span>
                  </div>
                )
              })}
            </div>
          </div>
          {/* Marker and link colors */}
          <div className="space-y-0.5 px-4 pb-2">
            <ColorRow
              label={t('knowledge.editorPrefMarkerColor')}
              description={t('knowledge.editorPrefMarkerColorDesc')}
              value={prefs.markerColor}
              onChange={(v) => prefs.setPref('markerColor', v)}
            />
            <ColorRow
              label={t('knowledge.editorPrefLinkColor')}
              description={t('knowledge.editorPrefLinkColorDesc')}
              value={prefs.linkColor}
              onChange={(v) => prefs.setPref('linkColor', v)}
            />
          </div>

          <Separator />

          {/* ── Reset ──────────────────────────────────────────── */}
          <div className="px-4 py-3">
            <Button
              variant="ghost"
              size="sm"
              className="w-full text-muted-foreground"
              onClick={() => prefs.reset()}
            >
              <RotateCcw className="h-3.5 w-3.5 mr-1.5" />
              {t('knowledge.editorPrefReset')}
            </Button>
          </div>
        </div>
      </PopoverContent>
    </Popover>
  )
}