Files
astrolabe/src/app/components/IconButton.tsx
T

54 lines
1.6 KiB
TypeScript

/**
* IconButton — the shared icon-only button primitive (arch 09 §4 + §5).
*
* A square button holding a single `<Icon>` (the controlled-vocabulary glyph
* primitive) and nothing else. Because there is no visible text, an accessible
* name is required: `label` becomes `aria-label` (APG button pattern) and, unless
* overridden via `title`, the hover tooltip.
*
* Always ghost — borderless toolbar utility; the transparent border holds the
* box size. Sizes are the control scale: `md` = `--control-height` (32px
* square, the default), `lg` = `--control-height-lg` (40px — beside a lg
* Button), `sm` = 24px (only nested inside another 32px control, e.g. a search
* field's clear button).
*
* Hover follows the same one-elevation-step rule as Button, via the same
* `--control-hover-fill` custom property; an open disclosure trigger
* (`aria-expanded="true"`) holds the `--layer-02` fill.
*/
import type { ComponentPropsWithRef } from 'react';
import styles from './IconButton.module.css';
export interface IconButtonProps extends ComponentPropsWithRef<'button'> {
/** Accessible name (required — there is no visible text). */
label: string;
size?: 'sm' | 'md' | 'lg';
}
export function IconButton({
label,
size = 'md',
className,
type = 'button',
title,
...rest
}: IconButtonProps) {
return (
<button
type={type}
aria-label={label}
title={title ?? label}
className={[
styles.iconButton,
size === 'sm' && styles.sm,
size === 'lg' && styles.lg,
className,
]
.filter(Boolean)
.join(' ')}
{...rest}
/>
);
}