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
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
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>
|
||||
);
|
||||
});
|
||||
Reference in New Issue
Block a user