SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
2.7 KB · 87 lines tsx
Raw Blame History
1"use client";2import * as React from "react";3import { ResponsiveDialog } from "@/components/ui/sheet";4import { Button } from "@/components/ui/button";5import { Input, Textarea } from "@/components/ui/input";67export interface PromptDialogProps {8  open: boolean;9  onOpenChange: (open: boolean) => void;10  title: React.ReactNode;11  description?: React.ReactNode;12  label?: string;13  placeholder?: string;14  defaultValue?: string;15  confirmLabel?: string;16  multiline?: boolean;17  maxLength?: number;18  onSubmit: (value: string) => Promise<void> | void;19}2021/**22 * Replacement for `window.prompt()`: bottom sheet on phones, small dialog on desktop.23 * Resolves with the trimmed value; empty submissions are ignored.24 */25export function PromptDialog({ open, onOpenChange, title, description, label, placeholder, defaultValue = "", confirmLabel = "Save", multiline, maxLength = 200, onSubmit }: PromptDialogProps) {26  const [value, setValue] = React.useState(defaultValue);27  const [busy, setBusy] = React.useState(false);28  const ref = React.useRef<HTMLInputElement & HTMLTextAreaElement>(null);29  React.useEffect(() => {30    if (open) {31      // eslint-disable-next-line react-hooks/set-state-in-effect32      setValue(defaultValue);33      setTimeout(() => {34        ref.current?.focus();35        ref.current?.select();36      }, 60);37    }38  }, [open, defaultValue]);3940  const submit = async () => {41    const v = value.trim();42    if (!v || busy) return;43    setBusy(true);44    try {45      await onSubmit(v);46      onOpenChange(false);47    } finally {48      setBusy(false);49    }50  };5152  return (53    <ResponsiveDialog54      open={open}55      onOpenChange={onOpenChange}56      title={title}57      description={description}58      size="sm"59      footer={60        <div className="flex gap-2 sm:justify-end">61          <Button variant="ghost" className="flex-1 sm:flex-none" onClick={() => onOpenChange(false)} disabled={busy}>62            Cancel63          </Button>64          <Button className="flex-1 sm:flex-none" loading={busy} disabled={!value.trim()} onClick={submit}>65            {confirmLabel}66          </Button>67        </div>68      }69    >70      <form71        className="space-y-1.5 pt-1"72        onSubmit={(e) => {73          e.preventDefault();74          void submit();75        }}76      >77        {label ? <label className="text-[13px] font-medium text-fg-muted">{label}</label> : null}78        {multiline ? (79          <Textarea ref={ref} value={value} onChange={(e) => setValue(e.target.value)} placeholder={placeholder} maxLength={maxLength} className="min-h-[120px]" />80        ) : (81          <Input ref={ref} value={value} onChange={(e) => setValue(e.target.value)} placeholder={placeholder} maxLength={maxLength} className="h-11 sm:h-9" />82        )}83      </form>84    </ResponsiveDialog>85  );86}87