/** * Fab — the floating action button: one screen, one thing it is mostly for. * * ```tsx * } accessibilityLabel="New note" onPress={compose} /> * ``` * * ## It floats, which is the whole problem with it * * A button pinned over the content is a button covering some of it, and the * bottom-right corner of a scrolling list is exactly where the last row goes. So * a Fab is right when a screen has *one* action worth that trade and wrong when * it has three — a corner with three buttons in it is a toolbar that has been * put in the wrong place. Where a screen has several, the honest shapes are a * `ButtonGroup` in a bar or, if one of them really does lead, this with a * `Fab.Group` behind it. * * It positions itself absolutely against its nearest positioned ancestor, which * on a screen means the screen. Give the content below it enough bottom padding * to scroll clear — this cannot know how tall your list is, and a Fab sitting on * the last row forever is the failure people actually hit. * * ## Extended, and when to bother * * ```tsx * }>Write * ``` * * A lone glyph is a guess unless the glyph is a plus. `extended` spells the * action out, costs the width, and is worth it for anything a plus would not * have said. * * ## The speed dial * * ```tsx * } accessibilityLabel="Add"> * } label="Photo" onPress={addPhoto} /> * } label="File" onPress={addFile} /> * * ``` * * The actions come out of the button one after another rather than together — * a stagger of a few frames each, which is enough to read as a list unfolding * instead of a menu appearing. Every action carries its label beside it, because * a column of unlabelled circles is a quiz. * * ## The menu * * ```tsx * } accessibilityLabel="Add"> * } label="Photo" onPress={addPhoto} /> * } label="File" onPress={addFile} /> * * ``` * * `layout="menu"` unfolds one panel of rows out of the button instead of a * column of buttons — the shape the platform's own menus take. Each row is a * label with its glyph, on the side the appearance puts it or `iconPlacement` * says, and the panel springs out of the corner the * trigger sits in, so it reads as the button opening rather than a sheet * arriving. Drawn in glass, the panel is one piece of the material; the rows * are content on it. * * ## Glass * * ```tsx * } accessibilityLabel="New note" /> * ``` * * `glass` draws the button in the material iOS 26 uses for its own floating * controls, in place of the variant's fill. A control floating over content is * exactly what that material is for: it refracts what scrolls under it rather * than covering it, and lifts its own edge, so the shadow goes too. * * Every variant takes the plain material, because a tint is a dimmer: measured * over content, a colour laid on the glass turns it back into a fill and a * monochrome one only greys it. The glyph reads in the ordinary foreground * colour, and in red on `destructive` — the colour that carries meaning goes * on the glyph, where it stays legible. Pressed, it answers the way the platform's * own glass controls do: the material swells and brightens under the finger, * in place of this component's press scale. The material exists on iOS 26 and * above with the optional `expo-glass-effect` installed. Everywhere else — older iOS, * Android, web, Reduce Transparency on — the flag is inert and the button keeps * its ordinary fill, so nothing has to be written twice. * * Opening also drops a scrim over the screen. Not for looks: an open dial is * modal — the next tap either picks something or closes it — and a scrim is what * says so, as well as what catches the tap that closes it. * * ## Where a group has to be written * * A `Fab.Group` draws its scrim and its buttons as two absolutely positioned * siblings in whatever it is written inside, so put it in the screen's root * container. That is where its `offset` is measured from, and it is what the * scrim covers. * * It stays in the screen's own view tree rather than being lifted out of it, * which is the part that matters after navigation: pushing a screen over this * one hides the dial with everything else on it. A group lifted to the top of * the app would still be drawn over the screen that replaced it. */ import { type ReactNode } from 'react'; import { View, type ViewProps } from 'react-native'; import { type VariantProps } from 'tailwind-variants'; import { type AnimatedPressableProps } from '../../primitives/animated-pressable.js'; /** Which corner the button sits in. */ export type FabPlacement = 'bottom-right' | 'bottom-center' | 'bottom-left'; /** How the menu is drawn: the platform's own shape, or rows with a glyph well. */ export type FabMenuAppearance = 'platform' | 'wells'; /** Which side of a menu row the glyph sits on. */ export type FabMenuIconPlacement = 'leading' | 'trailing'; declare const fabVariants: import("tailwind-variants").TVReturnType<{ size: { sm: { root: string; label: string; }; md: { root: string; label: string; }; lg: { root: string; label: string; }; }; extended: { true: {}; false: {}; }; variant: { primary: { root: string; label: string; }; secondary: { root: string; label: string; }; surface: { root: string; label: string; }; destructive: { root: string; label: string; }; }; disabled: { true: { root: string; }; }; glass: { true: { root: string; }; }; }, { root: string; content: string; label: string; }, undefined, { size: { sm: { root: string; label: string; }; md: { root: string; label: string; }; lg: { root: string; label: string; }; }; extended: { true: {}; false: {}; }; variant: { primary: { root: string; label: string; }; secondary: { root: string; label: string; }; surface: { root: string; label: string; }; destructive: { root: string; label: string; }; }; disabled: { true: { root: string; }; }; glass: { true: { root: string; }; }; }, { root: string; content: string; label: string; }, import("tailwind-variants").TVReturnType<{ size: { sm: { root: string; label: string; }; md: { root: string; label: string; }; lg: { root: string; label: string; }; }; extended: { true: {}; false: {}; }; variant: { primary: { root: string; label: string; }; secondary: { root: string; label: string; }; surface: { root: string; label: string; }; destructive: { root: string; label: string; }; }; disabled: { true: { root: string; }; }; glass: { true: { root: string; }; }; }, { root: string; content: string; label: string; }, undefined, unknown, unknown, undefined>>; type FabVariantProps = VariantProps; /** How big the button is. */ export type FabSize = NonNullable; /** What the button is drawn in. */ export type FabVariant = NonNullable; export interface FabProps extends Omit, Omit { className?: string; /** The glyph. Sized by you — this is the one thing that should not guess. */ icon?: ReactNode; /** * The label, which turns the circle into a stadium. Needs `extended`; a * label with nowhere to go is a label that gets clipped by the circle. */ children?: ReactNode; /** Spell the action out beside the glyph. */ extended?: boolean; /** * Pin it over the content, in a corner. * * Left out, it is an ordinary button in the flow — which is what you want * inside a `Fab.Group`, or when the screen already has somewhere for it to * sit. */ placement?: FabPlacement; /** Distance from the edges when `placement` is set, in points. */ offset?: number; /** Placement-aware view style. Press-state styling belongs in `className`. */ style?: ViewProps['style']; disabled?: boolean; /** * A tick on press. Off by default — needs the optional `expo-haptics`, and * is silent without it. */ haptics?: boolean; /** * Draw it in Liquid Glass — the material iOS 26 uses for its own floating * controls — instead of the variant's fill. Every variant takes the plain * material, with its glyph in the foreground colour and in red on * `destructive`. Pressed, the material swells and brightens the way the * platform's own glass controls do. * * Needs iOS 26 and the optional `expo-glass-effect`. Below that, on Android, * on web, or with Reduce Transparency on, it does nothing and the button * keeps its ordinary fill. */ glass?: boolean; /** Required for an icon-only button. A lone glyph reads out as nothing. */ accessibilityLabel?: string; } /** * What opens out of the trigger: a column of buttons, one panel of rows, or * the platform's own menu. */ export type FabGroupLayout = 'dial' | 'menu' | 'native'; export interface FabGroupProps extends Omit { className?: string; /** The glyph on the trigger. */ icon?: ReactNode; /** The trigger's label, if it should be extended while closed. */ label?: string; /** `Fab.Action` children, in the order they should unfold. */ children?: ReactNode; /** Controlled open state. */ open?: boolean; onOpenChange?: (open: boolean) => void; /** Which corner of the *screen* the whole dial parks in. */ placement?: FabPlacement; /** Distance from the screen's edges, in points. Add your safe-area inset. */ offset?: number; /** * What opens out of the trigger. `dial`, the default, is a column of round * buttons with their labels beside them. `menu` is one panel of rows — a * label with its glyph, on the side the appearance puts it — that springs * out of the trigger's corner, the way the platform's own menus do. * `native` hands the menu to the platform: SwiftUI on iOS, Jetpack Compose * on Android. * * A native menu is drawn by the platform, so `className` and the theme * tokens do not reach it, and the rows take `label` and `systemImage` rather * than an `icon` element. `blur` still applies: the scrim behind the menu is * ours, so the page recedes the way it does behind the dial and the panel. * * On iOS the platform owns the menu's open state, so `open` and * `onOpenChange` do nothing there. Android's menu is controlled and honours * both. * * Where the platform menu cannot be drawn — on the web, or without * `@expo/ui` installed — this falls back to `menu`. */ layout?: FabGroupLayout; /** * How a menu is drawn. `platform`, the default, is the shape the platform's * own menus take: a hairline between rows and the glyph after the label. * `wells` is tighter, with the glyph leading in a tinted well and each row * its own pill — a menu the app designed rather than the system. * * Menu layout only. */ appearance?: FabMenuAppearance; /** Which side of a menu row the glyph sits on. Each appearance has its own default. */ iconPlacement?: FabMenuIconPlacement; /** The menu panel's width in points. Each appearance has its own default. */ menuWidth?: number; /** The menu panel's corner radius in points. Each appearance has its own default. */ menuRadius?: number; /** Extra classes for the menu panel. */ menuClassName?: string; /** Extra classes for every menu row. A row's own `className` comes after. */ rowClassName?: string; size?: FabSize; variant?: FabVariant; disabled?: boolean; haptics?: boolean; /** * Draw the trigger and its actions in Liquid Glass. The same flag as on * `Fab`, with the same floor: iOS 26 and `expo-glass-effect`, inert * elsewhere. */ glass?: boolean; /** Frost the screen behind the open dial instead of dimming it. */ blur?: boolean; /** Required — the trigger is a lone glyph until it is opened. */ accessibilityLabel?: string; /** * Turn the trigger's glyph a quarter circle while the dial is open. * * On by default, and it is doing real work when the glyph is a plus: the * same mark becomes a cross, which says "this closes now" without a second * icon that has to be swapped in. Turn it off for a glyph that means * something at one angle only. */ rotateOnOpen?: boolean; } export interface FabActionProps extends Omit { className?: string; /** The glyph. */ icon?: ReactNode; /** * The glyph for a native menu row, as an SF Symbol name. * * `layout="native"` only, and iOS only — SwiftUI names its symbols rather * than taking a view for them, so `icon` cannot cross over. Ignored * everywhere else, so a group can carry both and be right on either path. */ systemImage?: string; /** What it does, beside the glyph. A column of unlabelled circles is a quiz. */ label?: string; onPress?: () => void; disabled?: boolean; /** Draws it in the destructive colour, for the one that removes something. */ destructive?: boolean; /** Extra classes for the label — the chip in a dial, the row's text in a menu. */ labelClassName?: string; } export declare const Fab: import("react").ForwardRefExoticComponent> & { Group: import("react").ForwardRefExoticComponent>; Action: import("react").ForwardRefExoticComponent>; }; export {}; //# sourceMappingURL=index.d.ts.map