/** * SelectionMode — turning a list into one you can pick several things out of. * * ```tsx * * * ( * open(item)}> * … * * )} * /> * * } destructive onPress={remove}> * Delete * * * * ``` * * ## Two ways to present it * * On a screen it is a *mode*: the list is there to be read, and a long press * turns it into one you can pick from. In a sheet it is a *picker*: * `SelectionMode.Sheet` was opened in order to choose something, so it is * choosing from the moment it appears, with the actions in the sheet's footer. * * ## The items stay yours * * `SelectionMode.Item` wraps whatever you put in it rather than replacing it. * It adds the circle and takes over what a press means; what the item looks * like is yours. That is what lets one component hold a row of people, a grid * of colours, a run of slides and a list of files without growing a prop for * each of them. * * ## A mode has to be obvious * * There are two states and the list behaves differently in each: normally a tap * opens a row, and in selection a tap picks it. That is only safe if leaving is * always available and never hidden — hence a cancel in the header, the Android * back button, and the count in front of the reader the whole time. * * Entering is a long press on a row, which is the gesture the platform has used * for this for fifteen years, and the row you pressed is the first one picked. * Entering with nothing selected leaves the reader in a changed list with no * explanation of what changed. * * ## Selection is a set of values, not of rows * * The component holds ids, never indices or elements. A list that reorders, * pages in more rows or drops one underneath the reader would invalidate * anything positional; a set of ids survives all three, and is also the shape * the action at the end needs — deleting takes ids. */ import { type ReactNode } from 'react'; import { type ViewProps } from 'react-native'; import { type VariantProps } from 'tailwind-variants'; declare const selectionVariants: import("tailwind-variants").TVReturnType<{ selected: { true: { circle: string; ring: string; }; }; /** * The action laid out along its label instead of above it. * * A screen's bar carries three or four actions side by side, so each one is * a narrow column and the label belongs under the glyph. A sheet's footer * usually carries one, full width — stacked there it is a tall block that * costs the list a row of its own for no gain. */ compact: { true: { action: string; actionLabel: string; }; }; destructive: { true: { actionLabel: string; }; }; disabled: { true: { action: string; }; }; /** * The group's surface, which depends on what is behind it. * * `card` is a step *down* from the background on a screen, which is what a * grouped list wants there. A sheet is already `popover`, and popover sits * above card in every dark theme — so the same card inside a sheet is * darker than the sheet around it and reads as a hole rather than a raised * set of rows. * * `muted` is an alpha tint, so it steps up from whatever it is drawn on and * is correct against either. It is only used in the sheet because the * screen's card is right where it is. */ surface: { screen: { group: string; }; sheet: { group: string; }; }; /** * Flush to the bottom edge, or lifted off it. * * `bar` is the platform shape — full width against the edge, a hairline * along the top, and the same background as the screen's own chrome. It is * the default because it is what a list with a selection in it does on both * platforms, and because it does not take width away from the list. */ placement: { bar: { bar: string; }; floating: { bar: string; }; }; }, { circle: string; fill: string; header: string; title: string; close: string; group: string; ring: string; bar: string; action: string; actionLabel: string; groupLabel: string; }, undefined, { selected: { true: { circle: string; ring: string; }; }; /** * The action laid out along its label instead of above it. * * A screen's bar carries three or four actions side by side, so each one is * a narrow column and the label belongs under the glyph. A sheet's footer * usually carries one, full width — stacked there it is a tall block that * costs the list a row of its own for no gain. */ compact: { true: { action: string; actionLabel: string; }; }; destructive: { true: { actionLabel: string; }; }; disabled: { true: { action: string; }; }; /** * The group's surface, which depends on what is behind it. * * `card` is a step *down* from the background on a screen, which is what a * grouped list wants there. A sheet is already `popover`, and popover sits * above card in every dark theme — so the same card inside a sheet is * darker than the sheet around it and reads as a hole rather than a raised * set of rows. * * `muted` is an alpha tint, so it steps up from whatever it is drawn on and * is correct against either. It is only used in the sheet because the * screen's card is right where it is. */ surface: { screen: { group: string; }; sheet: { group: string; }; }; /** * Flush to the bottom edge, or lifted off it. * * `bar` is the platform shape — full width against the edge, a hairline * along the top, and the same background as the screen's own chrome. It is * the default because it is what a list with a selection in it does on both * platforms, and because it does not take width away from the list. */ placement: { bar: { bar: string; }; floating: { bar: string; }; }; }, { circle: string; fill: string; header: string; title: string; close: string; group: string; ring: string; bar: string; action: string; actionLabel: string; groupLabel: string; }, import("tailwind-variants").TVReturnType<{ selected: { true: { circle: string; ring: string; }; }; /** * The action laid out along its label instead of above it. * * A screen's bar carries three or four actions side by side, so each one is * a narrow column and the label belongs under the glyph. A sheet's footer * usually carries one, full width — stacked there it is a tall block that * costs the list a row of its own for no gain. */ compact: { true: { action: string; actionLabel: string; }; }; destructive: { true: { actionLabel: string; }; }; disabled: { true: { action: string; }; }; /** * The group's surface, which depends on what is behind it. * * `card` is a step *down* from the background on a screen, which is what a * grouped list wants there. A sheet is already `popover`, and popover sits * above card in every dark theme — so the same card inside a sheet is * darker than the sheet around it and reads as a hole rather than a raised * set of rows. * * `muted` is an alpha tint, so it steps up from whatever it is drawn on and * is correct against either. It is only used in the sheet because the * screen's card is right where it is. */ surface: { screen: { group: string; }; sheet: { group: string; }; }; /** * Flush to the bottom edge, or lifted off it. * * `bar` is the platform shape — full width against the edge, a hairline * along the top, and the same background as the screen's own chrome. It is * the default because it is what a list with a selection in it does on both * platforms, and because it does not take width away from the list. */ placement: { bar: { bar: string; }; floating: { bar: string; }; }; }, { circle: string; fill: string; header: string; title: string; close: string; group: string; ring: string; bar: string; action: string; actionLabel: string; groupLabel: string; }, undefined, unknown, unknown, undefined>>; type SelectionVariantProps = VariantProps; interface SelectionModeContextValue { active: boolean; enter: (value?: string) => void; exit: () => void; selected: string[]; isSelected: (value: string) => boolean; toggle: (value: string) => void; selectAll: () => void; clear: () => void; /** True when everything selectable is picked, and there is something to pick. */ allSelected: boolean; count: number; /** How many rows `values` says there are, or 0 when it was not given. */ total: number; max?: number; haptics: boolean; /** * Whether the selection is being presented in a sheet. * * A sheet is opened *in order to* pick something, so there is no mode to * enter and nothing to long-press for — and the action bar belongs to the * sheet's footer rather than floating over the screen. */ sheet: boolean; } /** * Read the selection from anywhere inside a `SelectionMode` — for a header of * your own, a count somewhere else on the screen, or an action that has to know * what is picked. */ export declare function useSelectionMode(): SelectionModeContextValue; export interface SelectionModeProps extends ViewProps { className?: string; /** * Every value that can be picked, in list order. * * Only "select all" and the "n of m" in the header need it — picking rows one * at a time works without it. Give it the same ids you give the list. */ values?: string[]; /** Controlled selection mode. Leave it out and a long press turns it on. */ active?: boolean; /** Whether selection mode starts on. */ defaultActive?: boolean; onActiveChange?: (active: boolean) => void; /** Controlled selection. */ selected?: string[]; defaultSelected?: string[]; onSelectedChange?: (selected: string[]) => void; /** * The most that can be picked at once. * * A row that would go over it does not toggle on, and "select all" stops at * the limit rather than refusing. Leave it out for no limit. */ max?: number; /** * A tick when a row is picked and when the mode is entered. Off by default — * needs the optional `expo-haptics`, and is silent without it. */ haptics?: boolean; children: ReactNode; } declare function SelectionModeRoot({ className, values, active: activeProp, defaultActive, onActiveChange, selected: selectedProp, defaultSelected, onSelectedChange, max, haptics, children, ...props }: SelectionModeProps): import("react").JSX.Element; declare namespace SelectionModeRoot { var displayName: string; } export interface SelectionModeIndicatorProps { className?: string; /** Which row this stands for. Defaults to the row it is inside. */ value?: string; } /** * The circle at the left of a row. * * Round rather than square, and that is the convention doing real work: a * square box is a form control the reader is filling in, a round one is a thing * they are picking out of a list. `Checkbox` is the former and stays that way. * * `Item` draws one for you. This is exported for a row that wants it somewhere * else — over a photo's corner, at the end instead of the start. */ declare function SelectionModeIndicator({ className, value }: SelectionModeIndicatorProps): import("react").JSX.Element; declare namespace SelectionModeIndicator { var displayName: string; } export interface SelectionModeItemProps extends Omit { className?: string; /** This row's id. What ends up in `selected`. */ value: string; /** What the row does whenever selection does not own its press. */ onPress?: () => void; /** * Stop this row entering selection mode, and being picked once in it. Its * ordinary `onPress` still runs, including while selection is active. For a * header row, an advert, a "load more" — anything in the list that is not one * of the things being chosen between. */ disabled?: boolean; /** Draw the circle without waiting for the mode. */ alwaysShowIndicator?: boolean; /** * How being picked is drawn. * * `leading` puts the circle in front of the item, which is what a row wants. * `ring` draws a ring around whatever you gave it instead — for a swatch, a * thumbnail or a photo, where a circle beside it would be a second thing to * look at and the item itself can carry the state. `none` draws nothing and * leaves it to you; read `useSelectionMode().isSelected`. */ indicator?: 'leading' | 'ring' | 'none'; children: ReactNode; } /** * One row, with the circle in front of it. * * The press behaviour is the whole component: off mode, a press is the row's * own and a long press turns the mode on with this row picked; in it, a press * picks and unpicks. A row excluded from selection keeps its ordinary press in * both states. Two meanings for one gesture is exactly why the mode has to be * visible from the header. */ declare function SelectionModeItem({ className, value, onPress, disabled, alwaysShowIndicator, indicator, children, ...props }: SelectionModeItemProps): import("react").JSX.Element; declare namespace SelectionModeItem { var displayName: string; } export interface SelectionModeGroupProps extends ViewProps { className?: string; /** * Lay the items out in a grid this many across instead of stacking them. * * For things recognised by sight rather than read — swatches, thumbnails, * slides. A grid of six colours is one glance; the same six as rows is a * scroll. * * Ignored when `horizontal` is set. */ columns?: number; /** * Lay the items out in one row that scrolls sideways. * * For a strip of small things next to other controls — swatches above a * slider, filters above a list. A grid of the same items claims as many rows * as it needs and pushes everything below it off the sheet; a strip costs one * row whatever the count. * * Wins over `columns`, which asks for the opposite arrangement. */ horizontal?: boolean; /** How wide each item is in a horizontal strip, in points. */ itemWidth?: number; /** Space between items in a grid or a strip, in points. */ gap?: number; /** * A caption above the items, on the leading edge. * * Worth setting on anything picked by sight. A strip of colours with nothing * in front of it is a row of circles the reader has to work out the purpose * of, and a screen reader has nothing at all to announce it by — so this is * also the group's accessibility label. */ label?: string; /** Extra classes for that caption. */ labelClassName?: string; /** Hairlines between stacked items. On by default; off in a grid or a strip. */ separators?: boolean; children: ReactNode; } /** * A rounded card holding a run of items. * * Grouping is what makes a sheet of choices readable: one card of options with * hairlines between them reads as a set, and the same rows loose on the sheet's * background read as a list that has not finished loading. It is also what the * platform's own sheets do. * * Stacked by default, with a rule between each item. Pass `columns` for a grid. */ declare function SelectionModeGroup({ className, columns, horizontal, itemWidth, gap, label, labelClassName, separators, children, style, ...props }: SelectionModeGroupProps): string | number | bigint | boolean | Iterable | Promise> | Iterable | null | undefined> | import("react").JSX.Element | null | undefined; declare namespace SelectionModeGroup { var displayName: string; } export interface SelectionModeHeaderProps extends ViewProps { className?: string; /** The word in front of the count. */ title?: string; /** Hide the select-all control, for a list where picking everything is wrong. */ hideSelectAll?: boolean; /** Replaces the whole header's contents, keeping only its layout. */ children?: ReactNode; } /** * The bar that says the mode is on: a way out, how many are picked, and all * of them at once. * * Rendered only while the mode is on, and it is the thing that makes the mode * legible — a list whose rows have quietly changed what a tap does, with no * banner saying so, is a list that loses somebody's work. */ declare function SelectionModeHeader({ className, title, hideSelectAll, children, ...props }: SelectionModeHeaderProps): import("react").JSX.Element; declare namespace SelectionModeHeader { var displayName: string; } export interface SelectionModeBarProps extends ViewProps, Pick { className?: string; /** * Room under the actions, in points — your safe-area inset. * * A bar against the bottom edge sits over the home indicator on a phone that * has one, and an action under a home indicator is an action that takes two * tries. `floating` uses it as the gap on all four sides instead. */ inset?: number; /** * Keep the bar up with nothing picked. * * Off by default: every action on it needs something to act on, and a row of * buttons that all refuse is worse than a row that is not there yet. */ showWhenEmpty?: boolean; children: ReactNode; } /** * The actions, across the bottom of the list. * * Over the list rather than under it, because the list is as long as it is and * a bar in the flow would be somewhere off the end of it. **Pad the bottom of * your list so the last row can clear this** — nothing here can work out how * tall the list is. * * Flush to the edge by default. A bar inset from the sides is a card floating * over a list, which reads as something that arrived rather than as the mode * the screen is in — and it takes width away from the actions, which are the * one row of controls on screen that must not be cramped. */ declare function SelectionModeBar({ className, placement, inset, showWhenEmpty, children, style, ...props }: SelectionModeBarProps): import("react").JSX.Element | null; declare namespace SelectionModeBar { var displayName: string; } export interface SelectionModeActionProps extends Omit, Pick { className?: string; /** The glyph above the label. */ icon?: ReactNode; /** * What it does. Handed the selection, so the common case needs no other * wiring — and leaving the mode afterwards is up to you, because whether the * list still makes sense depends on what you did to it. */ onPress?: (selected: string[]) => void; /** Leave selection mode after the action runs. */ exitOnPress?: boolean; disabled?: boolean; /** Extra classes for the label. */ labelClassName?: string; children?: ReactNode; } /** * One action in the bar: a glyph with its name under it. * * Labelled, always. A row of bare glyphs at the bottom of a screen is a row of * guesses, and one of them usually deletes something. */ declare function SelectionModeAction({ className, icon, onPress, exitOnPress, disabled, destructive, labelClassName, children, ...props }: SelectionModeActionProps): import("react").JSX.Element; declare namespace SelectionModeAction { var displayName: string; } export interface SelectionModeSheetProps { className?: string; /** Controlled open state of the sheet. */ open?: boolean; defaultOpen?: boolean; onOpenChange?: (open: boolean) => void; /** The word in front of the count. */ title?: string; /** Hide the select-all control. */ hideSelectAll?: boolean; /** * How tall the sheet opens. * * `full` by default, and deliberately not `auto`. A sheet that sizes to its * content gives its scrolling body no height to fill, and a list inside a box * of no height draws nothing — which looks like an empty sheet rather than * like a missing style. * * Full rather than half because a picker spends a header and a footer before * it draws a single row. At half a screen that leaves four or five rows for * the thing the sheet was opened to do, and the reader scrolls a list that * would have fitted. Pass `half` for a sheet of two or three choices. */ size?: 'auto' | 'half' | 'full'; /** * The things to pick between, and optionally a `SelectionMode.Bar` of * actions. The bar is lifted into the sheet's footer wherever it is written. */ children: ReactNode; } /** * The whole selection, presented in a bottom sheet. * * A picker rather than a mode. The list on a screen has to be *turned into* one * you can pick from — hence the long press, the cancel and the count — but a * sheet was opened in order to pick something, so it is picking from the moment * it appears and there is nothing to enter or leave. * * What goes in it is anything: a column of friends, a grid of colours, a run of * slides. `SelectionMode.Item` wraps whatever you give it, so the sheet does * not need to know what it is holding. * * ```tsx * * * {people.map((person) => ( * * … * * ))} * * } onPress={share}>Send * * * * ``` */ declare function SelectionModeSheet({ className, open, defaultOpen, onOpenChange, title, hideSelectAll, size, children, }: SelectionModeSheetProps): import("react").JSX.Element; declare namespace SelectionModeSheet { var displayName: string; } export declare const SelectionMode: typeof SelectionModeRoot & { Sheet: typeof SelectionModeSheet; Group: typeof SelectionModeGroup; Item: typeof SelectionModeItem; Indicator: typeof SelectionModeIndicator; Header: typeof SelectionModeHeader; Bar: typeof SelectionModeBar; Action: typeof SelectionModeAction; }; export {}; //# sourceMappingURL=index.d.ts.map