Game UI
Tile
An inventory or weapon-wheel slot, with the selection frame.
stablev0.3.0 Chromium 103 ui-scale safe
Install
Copies the file into
components/ds/, with its registry dependencies and npm packages. See Installation for the one-time setup.bunx shadcn@3 add @fastrp/tileExamples
Default
API
<Tile />One slot of an inventory, a weapon wheel or a shop: a translucent square, its item centred, a colour rising from its foot, and a dark band with the label and count. The selected one takes the white frame and pops — the way GTA VI marks the slot under the stick.
| Prop | Type | Default | Notes |
|---|---|---|---|
| children | ReactNode | — | The item's icon or image. |
| count | ReactNode | — | A count or an ammo figure, right-aligned in the foot. |
| empty | boolean | false | An empty slot: a faint plus where the item would be. |
| label | ReactNode | — | A name or a slot label under the icon. |
| selected | boolean | false | The focused slot: the white frame and the glow at full strength. |
| tone | "primary" | "none" | "pink" | "teal" | "purple" | "cyan" | primary |
Dependencies
npm
- lucide-react@^0.469.0
Registry
- @fastrp/utils
Source
components/ds/tile.tsxShowHide
import { Plus } from 'lucide-react';
import type * as React from 'react';
import { cn } from '@/lib/utils';
// @fast-ds [email protected]
/** The colour that rises from a tile's foot: a category, a rarity. */
const GLOW = {
primary: 'from-primary/70',
pink: 'from-pink/70',
teal: 'from-teal/70',
purple: 'from-purple/70',
cyan: 'from-cyan/70',
none: 'from-transparent',
} as const;
export type TileProps = Omit<React.ComponentProps<'button'>, 'children'> & {
/** The item's icon or image. */
children?: React.ReactNode;
/** A name or a slot label under the icon. */
label?: React.ReactNode;
/** A count or an ammo figure, right-aligned in the foot. */
count?: React.ReactNode;
/** The focused slot: the white frame and the glow at full strength. */
selected?: boolean;
/** An empty slot: a faint plus where the item would be. */
empty?: boolean;
tone?: keyof typeof GLOW;
};
/**
* One slot of an inventory, a weapon wheel or a shop: a translucent square, its item centred,
* a colour rising from its foot, and a dark band with the label and count. The selected one
* takes the white frame and pops — the way GTA VI marks the slot under the stick.
*/
function Tile({
children,
label,
count,
selected = false,
empty = false,
tone = 'primary',
className,
type = 'button',
...props
}: TileProps) {
return (
<button
data-slot="tile"
data-selected={selected}
aria-pressed={selected}
data-sfx="select"
data-sfx-hover="navigate"
type={type}
className={cn(
'group relative flex aspect-square w-full select-none flex-col overflow-hidden rounded-md border border-foreground/10 bg-foreground/[0.06] text-foreground transition-[background-color,box-shadow,transform] duration-fast hover:bg-foreground/10 active:scale-[0.97] focus-visible:outline-none focus-visible:select-frame data-[selected=true]:animate-pop data-[selected=true]:bg-foreground/[0.12] data-[selected=true]:select-frame',
className,
)}
{...props}
>
<span
aria-hidden
className={cn(
'pointer-events-none absolute inset-x-0 bottom-0 h-1/2 bg-gradient-to-t to-transparent opacity-0 transition-opacity duration-base group-hover:opacity-50 group-data-[selected=true]:opacity-100',
GLOW[tone],
)}
/>
<span className="relative flex min-h-0 flex-1 items-center justify-center p-3 [&_img]:max-h-full [&_img]:object-contain [&_img]:[-webkit-user-drag:none] [&_svg]:size-9 [&_svg]:stroke-[1.5]">
{empty ? <Plus className="text-foreground/30" /> : children}
</span>
{(label || count !== undefined) && (
<span className="relative flex items-center justify-between gap-2 bg-ink/70 px-2.5 py-1.5 font-display text-label uppercase">
<span className="truncate text-foreground/85">{label}</span>
{count !== undefined && <span className="tabular-nums text-foreground">{count}</span>}
</span>
)}
</button>
);
}
export { Tile };