Files
siftlode/frontend/src/components/ui/SelectTrigger.tsx
T
peter dac9d89f91 feat(design): the field family, the dropdown trigger, native controls, the no-raw-input lint rule (E28 S3c)
- 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
2026-09-24 23:48:22 +02:00

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>
);
});