Trade strip: smaller pills, four visible, scroll hint

The pills were sized for a form, not a dense strip: at px-4 py-3 only
two and a bit fitted across a 390px phone, so the trade filter read as
a two-trade list rather than a scrollable set.

- Chip gains a `size` prop. 'sm' (px-3 py-1.5, text-meta) is for
  horizontal strips; 'md' stays the default everywhere a chip is a
  primary choice, so nothing else moves.
- Four trades now fit with the fifth cut at the edge, which is what
  tells you the row scrolls.
- Added a right-edge fade. The scrollbar is hidden, so without it a row
  that simply ends at the bezel reads as the whole list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
serfowi
2026-08-21 03:22:01 -04:00
co-authored by Claude Opus 5
parent 176ba187c8
commit 8f3509d1dd
2 changed files with 36 additions and 12 deletions
+23 -9
View File
@@ -66,22 +66,36 @@ export function ShowcaseDeck({
the drag gesture and never has to stay legible on a bright image. */} the drag gesture and never has to stay legible on a bright image. */}
<div className="min-w-0 shrink-0 px-4 pt-[3.25rem]"> <div className="min-w-0 shrink-0 px-4 pt-[3.25rem]">
{selected ? ( {selected ? (
<Chip selected onClick={() => setCategoryId(null)}> <Chip selected size="sm" onClick={() => setCategoryId(null)}>
{selected.name} {selected.name}
<X className="h-4 w-4" aria-hidden /> <X className="h-3.5 w-3.5" aria-hidden />
<span className="sr-only">Show all trades</span> <span className="sr-only">Show all trades</span>
</Chip> </Chip>
) : ( ) : (
<div // The strip scrolls sideways through every trade. The right-hand fade
className="-mx-4 flex gap-2 overflow-x-auto px-4 pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" // is the only affordance saying so — the scrollbar is hidden, and a
role="group" // row that simply ends at the bezel reads as the whole list.
aria-label="Filter by trade" <div className="relative -mx-4">
> <div
className="flex gap-1.5 overflow-x-auto px-4 pb-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
role="group"
aria-label="Filter by trade"
>
{categories.map((c) => ( {categories.map((c) => (
<Chip key={c.id} className="shrink-0" onClick={() => setCategoryId(c.id)}> <Chip
key={c.id}
size="sm"
className="shrink-0"
onClick={() => setCategoryId(c.id)}
>
{c.name} {c.name}
</Chip> </Chip>
))} ))}
</div>
<div
aria-hidden
className="pointer-events-none absolute inset-y-0 right-0 w-8 bg-gradient-to-l from-page to-transparent"
/>
</div> </div>
)} )}
</div> </div>
+13 -3
View File
@@ -9,18 +9,28 @@ import { cn } from '@/lib/utils';
*/ */
export function Chip({ export function Chip({
selected = false, selected = false,
size = 'md',
className, className,
children, children,
...props ...props
}: React.ButtonHTMLAttributes<HTMLButtonElement> & { selected?: boolean }) { }: React.ButtonHTMLAttributes<HTMLButtonElement> & {
selected?: boolean;
/**
* 'sm' is for dense horizontal strips — the trade filter has to fit four
* trades across a 390px phone before the fifth is cut off as a scroll hint.
* 'md' stays the default everywhere a chip is a primary choice.
*/
size?: 'sm' | 'md';
}) {
return ( return (
<button <button
type="button" type="button"
aria-pressed={selected} aria-pressed={selected}
{...props} {...props}
className={cn( className={cn(
'inline-flex items-center gap-1.5 rounded-pill border-[1.5px] px-4 py-3 text-body-sm', 'inline-flex items-center rounded-pill border-[1.5px]',
'transition-[color,background-color,border-color] duration-[120ms] ease-standard', 'transition-[color,background-color,border-color] duration-[120ms] ease-standard',
size === 'sm' ? 'gap-1 px-3 py-1.5 text-meta' : 'gap-1.5 px-4 py-3 text-body-sm',
'disabled:pointer-events-none disabled:opacity-45', 'disabled:pointer-events-none disabled:opacity-45',
selected selected
? 'border-brand-500 bg-brand-100 font-semibold text-ink-950' ? 'border-brand-500 bg-brand-100 font-semibold text-ink-950'
@@ -28,7 +38,7 @@ export function Chip({
className, className,
)} )}
> >
{selected && <Check className="h-4 w-4" aria-hidden />} {selected && <Check className={size === 'sm' ? 'h-3.5 w-3.5' : 'h-4 w-4'} aria-hidden />}
{children} {children}
</button> </button>
); );