import { type ComponentType, type ReactNode } from "react"; import { type GestureResponderEvent } from "react-native"; import { type ColorTokens, type StyleProp, type ViewStyle, type TextStyle } from "../../style/index.js"; import { Button as WebButton } from "../../atoms/button/button.js"; import { Avatar as WebAvatar } from "../../atoms/avatar/avatar.js"; import { type DropdownProps } from "../../atoms/dropdown/dropdown.js"; import { type Surface, type Density } from "./navbars.styles.js"; export interface NavbarSkin { /** iOS/web dim a pressed link; Android uses a ripple instead (null). */ pressedOpacity: number | null; /** Android ripple over a pressed link; null on iOS/web. */ ripple: ((t: ColorTokens) => { color: string; borderless: boolean; }) | null; /** * Where the keyboard focus ring sits on the link Pressable: INSET_FOCUS_RING draws it * just inside a link that a clipping bar would otherwise cut; omitted, it sits around * the link. The ring itself is the kit Pressable's (the palette's `ring`). No-op * natively. */ focusRing?: ViewStyle; /** The bar row: height, padding, gap, and flex layout, per density. */ bar: (t: ColorTokens, density: Density) => ViewStyle; /** The token-driven background fill (follows the scheme/glass surface). */ surface: (t: ColorTokens) => ViewStyle; /** The surface-axis treatment (hairline / outline / floating card). */ surfaceContainer: (t: ColorTokens, surface: Surface) => ViewStyle; /** The left cluster row (brand + links). */ leftGroup: (t: ColorTokens, density: Density) => ViewStyle; /** The brand lockup row (`brandContent` beside the `brand` wordmark). */ brandGroup: (t: ColorTokens, density: Density) => ViewStyle; /** The brand wordmark type. */ brand: (t: ColorTokens, density: Density) => TextStyle; /** The links row. */ linksRow: (t: ColorTokens, density: Density) => ViewStyle; /** A nav link tile (padding + active fill). */ linkTile: (t: ColorTokens, active: boolean) => ViewStyle; /** A nav link label (weight + active/inactive color). */ linkLabel: (t: ColorTokens, active: boolean) => TextStyle; /** * A resting link's instant hover look (its tile fill and label ink), declared on the * web only (`webHover`); null where there is none. */ linkHover: ((t: ColorTokens) => { tile: ViewStyle; label: TextStyle; }) | null; /** * How the active tile's fill renders as glass. `tint` keeps the skin's hue at a * translucent ceiling with the ordinary foreground ink (web's accent tile, * Android's 12% primary pill); `brand` paints the brand fill as glass whose * under-fill is densified until the primary-foreground ink reads at 4.5:1 (iOS's * primary capsule). */ linkSelection: "tint" | "brand"; /** The right cluster row (action + avatar). */ rightGroup: (t: ColorTokens, density: Density) => ViewStyle; } export interface NavbarProps { /** Brand or product name shown at the left, in a semibold face. */ brand?: string; /** * A brand ELEMENT for the left cluster (a logo mark, a workspace switcher). * It LEADS the cluster, so passing it beside `brand` renders the mark first and * keeps the wordmark beside it; passing it alone makes the mark the whole brand. * It takes the left cluster's own gap, so it needs no wrapper. */ brandContent?: ReactNode; /** * Where the brand leads (an app's home, `/`). With it, or with `onBrandPress`, the brand * lockup is one link: a real anchor on the web (an ``, so middle-click and * modifier-click open it in a new tab), named by `brand`, with the kit's focus ring. */ brandHref?: string; /** Called when the brand is pressed; on the web a modifier-click on a `brandHref` stays the browser's. */ onBrandPress?: (event: GestureResponderEvent) => void; /** * Draw a short vertical separator between the brand and the links (the kit * ``), as the hand-off's floating docs pill does. It * shows only while both the brand and the links row are on the bar, so a collapsed * bar or one without links never ends its brand on a stray rule. */ brandDivider?: boolean; /** * Ordered navigation link labels rendered in the middle row. Omit it (or pass an * empty array) for a bar with no middle nav: neither the links row nor the * narrow menu button that stands in for it renders. */ links?: string[]; /** * Destinations for `links`, by index (parallel to it). A link with an href renders as * a real anchor on the web (an ``, so middle-click, modifier-click and * open-in-new-tab work); natively it is ignored. A plain click still selects the link * and calls `onSelect` with the press event, so an app that routes on the client * calls `event.preventDefault()` there; a modifier or middle click is left to the * browser and selects nothing. Omit it (or an entry) for a link without a destination. */ hrefs?: string[]; /** Index of the active link (CONTROLLED; its label reads in the foreground color). Omit for uncontrolled use. */ active?: number; /** Initial active link for uncontrolled use (a bare navbar moves the active link on press). */ defaultActive?: number; /** * Free-form trailing controls for the right cluster: a ghost search button, icon * buttons, a notification dropdown, an account menu. They LEAD the cluster, ahead * of the built-in `actionLabel` button and `avatar`, and take the cluster's own * gap, so they need no wrapper. Keep them few and compact: the bar is a fixed * height and the slot is rendered as given at every width. */ actions?: ReactNode; /** Optional primary action; renders a