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-bookmarkpnpm dlx velaui add @velaui/doc-bookmarkyarn dlx shadcn@latest add @velaui/doc-bookmarkbunx --bun shadcn@latest add @velaui/doc-bookmarkveni add doc-bookmarkInstalls the copy of vela-ui that veni-builder serves to Venizia apps; veni-builder serves the Radix UI copy only, so this tab exists for that base alone.
npx shadcn@latest add @velaui/doc-bookmarkpnpm dlx velaui add @velaui/doc-bookmarkyarn dlx shadcn@latest add @velaui/doc-bookmarkbunx --bun shadcn@latest add @velaui/doc-bookmarkExamples
- Default
- Empty
Usage
Props
| Prop | Type | Default |
|---|---|---|
className | string | — |
onChange | (url: string) => void | — |
readOnly | boolean | — |
url | string | "" |
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 };