refactor(ui): render explicit CaretDown on Select; normalize affordance across platforms
Native <select> chrome varies: macOS renders a dual-arrow indicator, Windows and Linux render a single caret, and on dark themes the native caret can drop to near-invisible contrast. The agent Edit modal (image 5 in the audit) had five side-by-side Selects where the indicator read as a subtle decoration, making the field look closer to a filled-in text input than a pick-from-list. Switched Select to: - `appearance-none` to strip native chrome - Phosphor CaretDown rendered absolutely on the right, bold weight, muted-foreground so it reads against both card and background - Padding-right increased to reserve space so long option labels do not overlap the caret - Opacity drop when disabled so the affordance tracks the field state Scope: only the shared Select primitive; every caller inherits the fix automatically. No API change. Covers J10.
This commit is contained in:
1 parent
36bed1b0f1
commit
e87170f105
1 file changed
+34
-16
@@ -1,3 +1,13 @@
|
||||
import { CaretDown } from "./icons";
|
||||
|
||||
/**
|
||||
* Native <select> + explicit caret icon. We render our own CaretDown so
|
||||
* the affordance is consistent across platforms (macOS shows dual-arrow,
|
||||
* Windows/Linux a single caret of varying contrast) and visible against
|
||||
* dark themes. `appearance: none` collapses the native chrome so the
|
||||
* caret we render is the only visual indicator; padding-right reserves
|
||||
* space for it so long option labels don't overlap.
|
||||
*/
|
||||
export function Select({
|
||||
value,
|
||||
onChange,
|
||||
@@ -16,22 +26,30 @@ export function Select({
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const select = (
|
||||
<select
|
||||
className={`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full max-w-full border bg-card text-foreground ${
|
||||
compact
|
||||
? "rounded-lg border-border px-2.5 py-1.5 text-[13px] font-medium"
|
||||
: "rounded-lg border-border px-3.5 py-2.5 text-[14px]"
|
||||
} disabled:cursor-not-allowed disabled:opacity-60 truncate`}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value} className="bg-card text-foreground">
|
||||
{option.label.length > 80 ? option.label.slice(0, 77) + "..." : option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<div className="relative">
|
||||
<select
|
||||
className={`focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 w-full max-w-full appearance-none border bg-card text-foreground ${
|
||||
compact
|
||||
? "rounded-lg border-border pl-2.5 pr-7 py-1.5 text-[13px] font-medium"
|
||||
: "rounded-lg border-border pl-3.5 pr-9 py-2.5 text-[14px]"
|
||||
} disabled:cursor-not-allowed disabled:opacity-60 truncate`}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
onChange={(event) => onChange(event.target.value)}
|
||||
>
|
||||
{options.map((option) => (
|
||||
<option key={option.value} value={option.value} className="bg-card text-foreground">
|
||||
{option.label.length > 80 ? option.label.slice(0, 77) + "..." : option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
<CaretDown
|
||||
size={compact ? 12 : 14}
|
||||
weight="bold"
|
||||
className={`pointer-events-none absolute top-1/2 -translate-y-1/2 text-muted-foreground ${compact ? "right-2" : "right-3"} ${disabled ? "opacity-40" : ""}`}
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
if (label) {
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user