import { IconChevronDown } from "@tabler/icons-react"; import { forwardRef, useEffect, useRef, useState, type ReactNode } from "react"; import { ActionButton } from "../design-system/components.js"; import type { ActionButtonProps } from "../design-system/types.js"; import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "../ui/collapsible.js"; import { cn } from "../utils.js"; export interface ShareTriggerProps extends Pick< ActionButtonProps, | "aria-label" | "aria-expanded" | "aria-controls" | "className" | "disabled" | "emphasis" | "intent" | "onClick" | "onPress" | "pending" | "size" | "title" > { label?: ReactNode; } /** The shared text-only trigger for resource sharing surfaces. */ export const ShareTrigger = forwardRef( function ShareTrigger( { label = "Share", "aria-label": ariaLabel, title, ...props }, ref, ) { return ( {label} ); }, ); ShareTrigger.displayName = "ShareTrigger"; export interface ShareCopyRowProps { value: string; label?: ReactNode; description?: ReactNode; disabled?: boolean; className?: string; copyLabel?: ReactNode; copiedLabel?: ReactNode; onCopy: (value: string) => Promise | boolean | void; } /** Compact copy-only row. The underlying URL is deliberately not rendered. */ export function ShareCopyRow({ value, label, description, disabled = false, className, copyLabel = "Copy", copiedLabel = "Copied", onCopy, }: ShareCopyRowProps) { const [copied, setCopied] = useState(false); const resetTimer = useRef | null>(null); useEffect( () => () => { if (resetTimer.current) clearTimeout(resetTimer.current); }, [], ); const handleCopy = async () => { if (disabled) return; const result = await onCopy(value); if (result === false) return; setCopied(true); if (resetTimer.current) clearTimeout(resetTimer.current); resetTimer.current = setTimeout(() => setCopied(false), 1_400); }; return (
{label ?
{label}
: null} {description ? (
{description}
) : null}
void handleCopy()} className="h-9 shrink-0" > {copied ? copiedLabel : copyLabel}
); } export interface ShareDisclosureSectionProps { children: ReactNode; label: ReactNode; fallbackLabel?: string; defaultOpen?: boolean; open?: boolean; onOpenChange?: (open: boolean) => void; className?: string; contentClassName?: string; } /** Shared expandable boundary for optional share-access details. */ export function ShareDisclosureSection({ children, label, fallbackLabel = "Share details", defaultOpen = false, open, onOpenChange, className, contentClassName, }: ShareDisclosureSectionProps) { const [internalOpen, setInternalOpen] = useState(defaultOpen); const isOpen = open ?? internalOpen; const accessibleLabel = typeof label === "string" ? label : fallbackLabel; const handleOpenChange = (nextOpen: boolean) => { if (open === undefined) setInternalOpen(nextOpen); onOpenChange?.(nextOpen); }; return ( {label} {children} ); } export type ShareAgentsSectionProps = Omit< ShareDisclosureSectionProps, "label" | "fallbackLabel" > & { label?: ReactNode }; /** Shared expandable boundary for agent-readable sharing details. */ export function ShareAgentsSection({ label = "Share with agents", ...props }: ShareAgentsSectionProps) { return ( ); } export type SharePeopleSectionProps = Omit< ShareDisclosureSectionProps, "label" | "fallbackLabel" > & { label?: ReactNode }; /** Shared expandable boundary for individual people access. */ export function SharePeopleSection({ label = "People with access", ...props }: SharePeopleSectionProps) { return ( ); }