- Adopt BoardUI (Pro init): theme tokens, Inter, .dark mode, Button, SegmentedControl, Meter, Skeleton and ThemeToggle; remap the app's color aliases onto BoardUI tokens and swap lucide for Remix icons - Retint BoardUI's neutral ramp to cool slate with a salmon accent; drop the dotted background; raise muted text contrast - Simplify the landing page and remove the overlap explorer - Planner: explicit "I have this" toggle on grocery lines, pantry panel above the menu with the staples list spelled out, sticky aisle header fix, and a "Download PDF" plan export (jsPDF, lazy-loaded) - Recipes: replace the family cabbage soup with Cade's Cabbage Soup (adds Bone-In Ham Steak to the catalog); remove Ham, Bean & Cabbage Soup - Remove unused BoardUI components, dependencies and exports Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
70 lines
1.9 KiB
TypeScript
70 lines
1.9 KiB
TypeScript
"use client";
|
||
|
||
import type { ReactNode, Ref } from "react";
|
||
import { RiInformationFill } from "@remixicon/react";
|
||
import type { LabelProps as AriaLabelProps } from "react-aria-components";
|
||
import { Label as AriaLabel } from "react-aria-components";
|
||
import { cx } from "@/utils/cx";
|
||
|
||
/**
|
||
* Field label. Wraps `react-aria-components`' Label so the parent `TextField`
|
||
* automatically wires `htmlFor` / `aria-labelledby`.
|
||
*
|
||
* Visuals come from Figma:
|
||
* - Inter Medium 14/20, color text/primary
|
||
* - 2px gap between label + required asterisk + info icon
|
||
* - asterisk is text/error-primary (red/500)
|
||
* - info icon is 16×16 in foreground/icon/quaternary
|
||
*
|
||
* Pass `isRequired` to show the asterisk. Pass `tooltip` to show the info
|
||
* icon (currently a static glyph — when the Tooltip component lands it'll
|
||
* wire up here).
|
||
*/
|
||
|
||
export interface LabelProps extends AriaLabelProps {
|
||
children: ReactNode;
|
||
isRequired?: boolean;
|
||
/** Reserved for invalid-aware styling (asterisk color, etc.) once we need it. */
|
||
isInvalid?: boolean;
|
||
/** Show the info icon next to the label. */
|
||
tooltip?: boolean | string;
|
||
ref?: Ref<HTMLLabelElement>;
|
||
}
|
||
|
||
export function Label({
|
||
isRequired = false,
|
||
isInvalid: _isInvalid,
|
||
tooltip,
|
||
className,
|
||
children,
|
||
...props
|
||
}: LabelProps) {
|
||
void _isInvalid;
|
||
return (
|
||
<AriaLabel
|
||
data-label="true"
|
||
{...props}
|
||
className={cx(
|
||
"flex cursor-default items-center gap-0.5",
|
||
"text-body-medium text-text-primary",
|
||
className,
|
||
)}
|
||
>
|
||
{children}
|
||
{isRequired && (
|
||
<span
|
||
aria-hidden="true"
|
||
className="text-body-medium text-text-error-primary"
|
||
>
|
||
*
|
||
</span>
|
||
)}
|
||
{tooltip && (
|
||
<RiInformationFill
|
||
className="size-4 shrink-0 text-foreground-icon-quaternary"
|
||
aria-hidden
|
||
/>
|
||
)}
|
||
</AriaLabel>
|
||
);
|
||
}
|