Vela UI Docsvenizia.ai

Doc Bookmark

A link shown as a card with its host and URL, and an input while empty or being edited.

npx shadcn@latest add @velaui/doc-bookmark
npx shadcn@latest add @velaui/doc-bookmark

Examples

  • Default
  • Empty

View code

Doc Bookmark · Default

Installation

pnpm dlx velaui add @velaui/doc-bookmark
pnpm dlx velaui add @velaui/doc-bookmark

Code

doc-bookmark-default.tsx
import * as React from "react";

import { DocBookmark } from "@/components/ui/doc-bookmark";

export default function Example() {
  const [url, setUrl] = React.useState("https://venizia.dev/docs/launch");
  return (
    <div className="w-full max-w-xl p-6">
      <DocBookmark url={url} onChange={setUrl} />
    </div>
  );
}

View code

Doc Bookmark · Empty

Installation

pnpm dlx velaui add @velaui/doc-bookmark
pnpm dlx velaui add @velaui/doc-bookmark

Code

doc-bookmark-empty.tsx
import * as React from "react";

import { DocBookmark } from "@/components/ui/doc-bookmark";

export default function Example() {
  const [url, setUrl] = React.useState("");
  return (
    <div className="w-full max-w-xl p-6">
      <DocBookmark url={url} onChange={setUrl} />
    </div>
  );
}

Usage

import * as React from "react";

import { DocBookmark } from "@/components/ui/doc-bookmark";

export default function Example() {
  const [url, setUrl] = React.useState("https://venizia.dev/docs/launch");
  return (
    <div className="w-full max-w-xl p-6">
      <DocBookmark url={url} onChange={setUrl} />
    </div>
  );
}

Props

PropTypeDefault
classNamestring—
onChange(url: string) => void—
readOnlyboolean—
urlstring""

Takes these and every prop of DocBookmarkProps.

Dependencies

npm
none besides React
Installs with it
nothing else

Source

components/ui/doc-bookmark.tsx
import * as React from "react";

import { cn } from "@/lib/utils";

export interface DocBookmarkProps {
  url?: string;
  readOnly?: boolean;
  onChange?: (url: string) => void;
  className?: string;
}

/** A link as a card: host and URL, with an input while empty or being edited. */
const host = (url: string) => {
  try {
    return new URL(url).host;
  } catch {
    return url;
  }
};

function DocBookmark({ url = "", readOnly, onChange, className }: DocBookmarkProps) {
  const [editing, setEditing] = React.useState(false);
  const save = (value: string) => {
    // Enter and the blur it causes both land here; only a changed link is reported.
    if (/^https?:\/\/\S+$/.test(value.trim()) && value.trim() !== url) onChange?.(value.trim());
    setEditing(false);
  };
  if (!readOnly && (!url || editing)) {
    return (
      <input
        autoFocus={editing}
        aria-label="Bookmark URL"
        defaultValue={url}
        placeholder="Paste a link and press Enter"
        className={cn("my-2 w-full rounded-md border border-border-default bg-transparent px-3 py-2 text-sm outline-none", className)}
        onKeyDown={(e) => e.key === "Enter" && save(e.currentTarget.value)}
        onBlur={(e) => save(e.currentTarget.value)}
      />
    );
  }
  return (
    <div className={cn("my-2 flex items-center gap-2 rounded-md border border-border-default px-3 py-2", className)}>
      <a href={url} target="_blank" rel="noopener noreferrer" className="min-w-0 flex-1">
        <div className="truncate text-sm font-medium">{host(url)}</div>
        <div className="truncate text-xs text-text-muted">{url}</div>
      </a>
      {!readOnly && (
        <button type="button" className="text-xs text-text-muted underline pointer-coarse:min-h-11 pointer-coarse:min-w-11" onClick={() => setEditing(true)}>
          Edit
        </button>
      )}
    </div>
  );
}

export { DocBookmark };