- ui/Input, Textarea, InnerInput + fieldClass (A4): the >= 3:1 edge, a 2px accent focus edge, the danger edge for invalid, md/sm/xs/heading rungs, 1rem and 44 on touch - ui/SelectTrigger (A5): SortControl, FilterSelect, the view switcher and the date filter on one trigger, a pill in a toolbar, r8 in a form - ui/NativeControls (A6): Checkbox, CheckRow, Slider, ColorInput - every raw input swept (~100 sites); inputCls retired; local/input-primitive - useReturnFocus: an in-place editor hands the focus back to its trigger on Enter/Escape (the playlist and Plex title renames, the priority number); the Plex playlist rename gains Escape and an accessible name - back to top: a round 48 FAB (owner decision); DESIGN.md A2 and the log
51 lines
2.0 KiB
TypeScript
51 lines
2.0 KiB
TypeScript
import { forwardRef } from "react";
|
|
import type { ButtonHTMLAttributes, ReactNode } from "react";
|
|
import clsx from "clsx";
|
|
import { ChevronDown } from "lucide-react";
|
|
import { FocusSafeButton } from "./FocusSafeButton";
|
|
|
|
// The dropdown trigger (docs/DESIGN.md A5, E28 S3c): the one button that opens a list of choices —
|
|
// `SortControl`, `FilterSelect`, the view switcher, the date filter. By context: a pill at the toolbar's
|
|
// 28 (in a toolbar the pill means "a filter or a choice"), or r8 at the form's 32, full width, lining up
|
|
// with the inputs around it. Open is the accent edge and the chevron turned over. The popover wiring
|
|
// (ref, `getReferenceProps`, the accessible name carrying the value) is the caller's.
|
|
export const SelectTrigger = forwardRef<
|
|
HTMLButtonElement,
|
|
ButtonHTMLAttributes<HTMLButtonElement> & {
|
|
context: "toolbar" | "form";
|
|
open: boolean;
|
|
/** The muted "Sort:" prefix before the value. */
|
|
label?: ReactNode;
|
|
/** A leading glyph (the view's icon, a calendar). */
|
|
icon?: ReactNode;
|
|
/** Between the value and the chevron (the sort direction arrow). */
|
|
trailing?: ReactNode;
|
|
}
|
|
>(function SelectTrigger(
|
|
{ context, open, label, icon, trailing, className, children, ...rest },
|
|
ref,
|
|
) {
|
|
return (
|
|
<FocusSafeButton
|
|
ref={ref}
|
|
type="button"
|
|
className={clsx(
|
|
"glass-card glass-hover inline-flex shrink-0 items-center gap-1.5 border text-left text-sm transition touch-target",
|
|
context === "toolbar" ? "h-7 rounded-full px-3" : "h-8 w-full rounded-lg px-2.5",
|
|
open ? "border-accent" : "border-border",
|
|
className,
|
|
)}
|
|
{...rest}
|
|
>
|
|
{icon}
|
|
{label != null && <span className="text-muted">{label}:</span>}
|
|
<span className={clsx("text-fg", context === "form" && "flex-1 truncate")}>{children}</span>
|
|
{trailing}
|
|
<ChevronDown
|
|
aria-hidden="true"
|
|
className={clsx("size-3.5 shrink-0 text-muted transition-transform", open && "rotate-180")}
|
|
/>
|
|
</FocusSafeButton>
|
|
);
|
|
});
|