@gnome-ui/react-native 1.8.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,19 +25,30 @@ React Native component library following the [GNOME Human Interface Guidelines](
25
25
  > (Tier 20), `Chip` (Tier 7), `IconButton`/`Drawer` (Tier 8/Tier 20), and
26
26
  > `Clamp` (Tier 6), `Box` (Tier 20), `WrapBox`/`ToggleGroup` (Tier 7), and
27
27
  > `InlineViewSwitcher` (Tier 8), `PreferencesGroup` (Tier 13), and
28
- > `EntryRow`/`PasswordEntryRow`/`ComboRow` (Tier 12), `ColorPicker`
28
+ > `EntryRow`/`PasswordEntryRow`/`ComboRow`/`SpinRow` (Tier 12), `SplitButton`
29
+ > (Tier 8), `ColorPicker`
29
30
  > (Tier 20), `Bin` (Tier 15), `Blockquote` (Tier 20), `ButtonRow`
30
31
  > (Tier 8), `Callout` (Tier 20), `CheckRow` (Tier 12), `ExpanderRow`
31
32
  > (Tier 8), `FieldGroup` (Tier 20), and `MultiSelectDropdown` (Tier 20)
32
33
  > also shipped, along with `FilterableMultiSelectDropdown` — an original
33
34
  > `@gnome-ui/react`-only component (not a GNOME HIG port) built once its
34
35
  > prerequisite `MultiSelectDropdown` landed — and `PasswordField`/
35
- > `RangeSlider`/`StatusBadge` (all Tier 20). `BottomTabBar` also shipped —
36
+ > `RangeSlider`/`StatusBadge`/`WidgetManager` (all Tier 20) `WidgetManager`
37
+ > was previously deferred as low-priority, built once named directly since
38
+ > every piece it composes had already shipped. `BottomTabBar` also shipped —
36
39
  > a React Native-only original with no `@gnome-ui/react` source at all
37
40
  > (desktop apps don't have a bottom tab bar pattern to mirror), built on
38
41
  > explicit request for the iOS/Android fixed bottom-navigation shape.
39
- > Component ports from `@gnome-ui/react` continue tier by tiersee this
40
- > package's own [ROADMAP.md](./ROADMAP.md) for full
42
+ > `useBreakpoint` and `BreakpointBin` (both Tier 6) also shipped the
43
+ > package's first adaptive-layout primitives, built on `useWindowDimensions`
44
+ > and `onLayout` respectively since there's no CSS media query/container
45
+ > query to lean on. `ButtonContent` (Tier 15) also shipped — an icon+label
46
+ > layout helper mostly redundant with `Button`'s own `leadingIcon`/
47
+ > `trailingIcon`, kept for composing the same spacing outside `Button`
48
+ > itself. `TagInput` (Tier 20) also shipped — a `WrapBox` + `Chip`
49
+ > composition, unblocked once both had shipped. Component ports from
50
+ > `@gnome-ui/react` continue tier by
51
+ > tier — see this package's own [ROADMAP.md](./ROADMAP.md) for full
41
52
  > per-tier status against all 130 `@gnome-ui/react` components, and the
42
53
  > main [ROADMAP.md](../../ROADMAP.md) Priority 3 for the framework
43
54
  > expansion this package belongs to.
@@ -176,6 +187,42 @@ yourself, matching the resolved label color (`theme.accentFgColor`,
176
187
  `theme.destructiveFgColor`, `theme.windowFgColor`, …) if you want them to
177
188
  match.
178
189
 
190
+ ### SplitButton
191
+
192
+ ```tsx
193
+ import { SplitButton, Button } from '@gnome-ui/react-native';
194
+
195
+ <SplitButton
196
+ label="Save"
197
+ onPress={() => save()}
198
+ dropdownContent={
199
+ <Button variant="flat" size="sm" onPress={() => saveAs()}>
200
+ Save as Template
201
+ </Button>
202
+ }
203
+ />;
204
+ ```
205
+
206
+ Primary action button with an attached dropdown arrow, mirroring
207
+ `AdwSplitButton` and `@gnome-ui/react`'s own `SplitButton`. Pressing the
208
+ label half fires `onPress`; pressing the arrow half opens a floating panel
209
+ with `dropdownContent` (menus, options, etc.). Supports the same
210
+ `default`/`suggested`/`destructive` variants as `Button`.
211
+
212
+ The label half is a real `Button` — its resting/pressed/disabled colors
213
+ come for free. The arrow half is a hand-rolled `Pressable`, not a second
214
+ `Button`: `Popover` (which supplies the floating panel) clones a prop-level
215
+ `accessibilityState` onto its trigger, and RN merges a spread prop object
216
+ outright rather than key-by-key — nesting a full `Button` there would
217
+ silently clobber `Button`'s own internal `accessibilityState={{ disabled
218
+ }}`. The hand-rolled `Pressable` owns `accessibilityState={{ disabled }}`
219
+ itself instead, letting `Popover`'s clone merge in `expanded` alongside it.
220
+ Its resting colors are derived from a small local color formula mirroring
221
+ `Button`'s own (unexported) one, so the two halves render pixel-identical
222
+ colors; they read as one connected control via zeroed shared inner corner
223
+ radii plus a 1px separator, the RN equivalent of the web CSS's split
224
+ `border-radius` and `.separator` span.
225
+
179
226
  ### Text
180
227
 
181
228
  ```tsx
@@ -497,6 +544,10 @@ button is the RN-idiomatic stand-in. The clear (×) button appears whenever
497
544
  glyphs (`🔍`/`×`) rather than `@gnome-ui/icons`, matching every other
498
545
  no-SVG-dependency component in this package.
499
546
 
547
+ `inline` drops the header-bar background/border so the bar blends into any
548
+ surface — a card, a plain content area, a custom container — instead of
549
+ looking like it belongs to a `HeaderBar`.
550
+
500
551
  Dropped relative to `@gnome-ui/react`'s `SearchBar`: the `suggestions` /
501
552
  `onSuggestionSelect` / `loadingSuggestions` / `renderSuggestion` /
502
553
  `suggestionsLabel` autocomplete popover — it depends on a portal +
@@ -1675,6 +1726,41 @@ zero height before `alignItems` gets to stretch anything into it. Caught
1675
1726
  on-device; it's a no-op in the ordinary case where the container hugs its
1676
1727
  content rather than having a fixed height.
1677
1728
 
1729
+ ### TagInput
1730
+
1731
+ ```tsx
1732
+ import { TagInput } from '@gnome-ui/react-native';
1733
+
1734
+ const [tags, setTags] = useState(['react', 'gnome']);
1735
+
1736
+ <TagInput label="Tags" value={tags} onChange={setTags} placeholder="Add a tag…" />;
1737
+ ```
1738
+
1739
+ Type-to-add multi-value input rendering entries as removable `Chip`s in a
1740
+ `WrapBox`, mirroring `@gnome-ui/react`'s own `TagInput`. Type and press
1741
+ Return, or type a `,`, to commit the draft as a tag; paste a comma or
1742
+ newline-separated list to add several at once; Backspace with an empty
1743
+ draft removes the last tag; tap a chip's `×` to remove that one.
1744
+
1745
+ The web version wires typed-`,` and pasted-list handling as two separate
1746
+ handlers (`onKeyDown`'s `,` case, `onPaste`). RN's `TextInput` has no
1747
+ `paste` event to mirror — but a paste still flows through `onChangeText`
1748
+ with the full resulting text, exactly like a typed `,` does, so both
1749
+ collapse into one handler here: whenever the text contains a `,` or
1750
+ newline, split on it and commit every non-empty part. Return is handled via
1751
+ `onSubmitEditing` (no `Enter` keystroke to catch on a touch keyboard), and
1752
+ Backspace-on-empty uses `onKeyPress` — the one `TextInput` event that still
1753
+ fires with the field already empty (`onChangeText` doesn't fire deleting
1754
+ from nothing).
1755
+
1756
+ The tag box is a `Pressable` (mirrors the web version's
1757
+ `onClick={() => inputRef.current?.focus()}` on the container) with
1758
+ `accessible={false}` set explicitly: `Pressable` defaults `accessible` to
1759
+ `true`, which would otherwise collapse every `Chip`'s remove button and the
1760
+ draft input into a single VoiceOver stop — the same container-swallows-
1761
+ subtree trap documented for a bare `View` plus `accessibilityRole`, hit
1762
+ here via `Pressable`'s own default instead.
1763
+
1678
1764
  ### StatusPage
1679
1765
 
1680
1766
  ```tsx
@@ -2012,6 +2098,38 @@ components means the flip-to-fit placement, the tap-outside dismissal and the
2012
2098
  — same behaviour as the web version, one level up. The keyboard layer drops
2013
2099
  as it does everywhere else here.
2014
2100
 
2101
+ ### SpinRow
2102
+
2103
+ ```tsx
2104
+ import { SpinRow } from '@gnome-ui/react-native';
2105
+
2106
+ <BoxedList>
2107
+ <SpinRow
2108
+ title="Volume"
2109
+ subtitle="Output level"
2110
+ value={volume}
2111
+ onValueChange={setVolume}
2112
+ min={0}
2113
+ max={100}
2114
+ />
2115
+ </BoxedList>
2116
+ ```
2117
+
2118
+ Settings row with an integrated spin button for numeric values — mirrors
2119
+ `AdwSpinRow` and `@gnome-ui/react`'s own `SpinRow`. Use it inside a
2120
+ `BoxedList` for settings with numeric ranges (volume, timeout duration,
2121
+ count limits, etc.). Controlled (`value`) and uncontrolled (`defaultValue`)
2122
+ modes both work.
2123
+
2124
+ **This is a composition of `ActionRow` + `SpinButton`, the same shape as
2125
+ `ComboRow`'s `ActionRow` + `Dropdown` composition above.** `SpinButton` is
2126
+ controlled-only, so the uncontrolled `defaultValue` state lives in `SpinRow`,
2127
+ one level up. The web version's keyboard interaction (↑/↓ one step, Page
2128
+ Up/Down ten steps, Home/End to bounds) drops as it does everywhere else in
2129
+ this package — `SpinButton` already provides the touch/screen-reader
2130
+ equivalents it was built with (tap the visible −/+ buttons, or the
2131
+ `accessibilityRole="adjustable"` increment/decrement actions).
2132
+
2015
2133
  ### ColorPicker / ColorSwatch
2016
2134
 
2017
2135
  ```tsx
@@ -2104,6 +2222,37 @@ substitution `Chip`'s selected-state tint already established. `<blockquote>`/
2104
2222
  `<footer>`/`<cite>` have no RN element equivalent, so this renders as a
2105
2223
  plain `View` with no semantic role.
2106
2224
 
2225
+ ### ButtonContent
2226
+
2227
+ ```tsx
2228
+ import { ButtonContent, Icon } from '@gnome-ui/react-native';
2229
+ import { Save } from '@gnome-ui/icons';
2230
+
2231
+ <Pressable onPress={save}>
2232
+ <ButtonContent icon={<Icon icon={Save} size="sm" />} label="Save" color="accent" />
2233
+ </Pressable>;
2234
+ ```
2235
+
2236
+ Icon + label layout helper mirroring `AdwButtonContent` — the same 6 dp
2237
+ gap, vertically-centered row every button in this package already
2238
+ produces internally via `leadingIcon`/`trailingIcon`. **Mostly redundant
2239
+ with `Button`'s own `leadingIcon`/`trailingIcon` props** for anything that
2240
+ actually is a `Button` — those already lay the icon and a themed,
2241
+ variant-colored label out identically, with no separate color prop
2242
+ needed. Reach for `ButtonContent` when composing icon+label content for
2243
+ something that *isn't* this package's `Button` instead: a bespoke
2244
+ `Pressable`, a custom card action, anywhere the same Adwaita spacing
2245
+ convention is wanted outside `Button` itself.
2246
+
2247
+ Unlike the web version, there's no `currentColor` to inherit the
2248
+ surrounding button's text color from, so a `color` prop (the same
2249
+ `TextColor` union `Text`/`ButtonRow` already use) needs to be passed
2250
+ explicitly to match — e.g. `color="accent"` beside a `suggested`-style
2251
+ action, `color="destructive"` beside a destructive one. The icon slot is
2252
+ marked `accessibilityElementsHidden`/`importantForAccessibility="no-hide-
2253
+ descendants"`, the RN equivalent of the web version's `aria-hidden="true"`
2254
+ on its icon `<span>`.
2255
+
2107
2256
  ### ButtonRow
2108
2257
 
2109
2258
  ```tsx
@@ -2355,6 +2504,51 @@ counter. Six variants (`success`/`warning`/`error`/`new`/`accent`/
2355
2504
  `neutral`) reuse `Badge`'s exact color-mapping shape, plus a `new` (purple)
2356
2505
  variant `Badge` doesn't have.
2357
2506
 
2507
+ ### WidgetManager
2508
+
2509
+ ```tsx
2510
+ import { WidgetManager, type WidgetDefinition } from '@gnome-ui/react-native';
2511
+
2512
+ const catalog: WidgetDefinition[] = [
2513
+ { id: 'clock', label: 'Clock', description: 'Shows the current time', render: () => <ClockWidget /> },
2514
+ { id: 'weather', label: 'Weather', render: () => <WeatherWidget /> },
2515
+ ];
2516
+
2517
+ <WidgetManager title="My Dashboard" catalog={catalog} value={widgetIds} onChange={setWidgetIds} />;
2518
+ ```
2519
+
2520
+ Card that manages a controlled collection of "widgets" — pick which ones
2521
+ are visible from a `catalog`, each rendering its own arbitrary content via
2522
+ `render()` — mirrors `@gnome-ui/react`'s own `WidgetManager`. The header's
2523
+ edit button toggles a local `editing` state: in view mode only the added
2524
+ widgets (or an empty-state message) show; in edit mode a dashed "add
2525
+ widget" trigger also appears, opening a catalog picker (`pickerSurface`:
2526
+ `"dialog"`, `"bottomSheet"`, or `"drawer"`). Adding/removing is staged
2527
+ inside the picker and only applied — via `onChange` — when the user
2528
+ confirms; canceling or dismissing discards the staging. Widgets can only be
2529
+ removed through the picker, never inline in the card.
2530
+
2531
+ Every piece this composes already existed: `ActionRow`+`BoxedList` for the
2532
+ catalog rows, `Button`/`IconButton`/`Icon`/`StatusPage` for the rest of the
2533
+ chrome, and `Dialog`/`BottomSheet`/`Drawer` for the three `pickerSurface`
2534
+ options. The web version's option is called `"modal"`, after its own
2535
+ `Modal` component — this package's `Modal` counterpart is `Dialog` (RN's
2536
+ own `Modal` primitive is a different, lower-level thing), so the option is
2537
+ named after what it actually renders here instead of ported verbatim. None
2538
+ of those three overlay components scroll their `children` for you, unlike
2539
+ the web version's `overflow-y: auto` body, so the catalog list gets its own
2540
+ capped `ScrollView` before being handed to whichever surface renders it.
2541
+ `Dialog` already renders its own confirm/cancel row from a `buttons` array;
2542
+ only `bottomSheet`/`drawer` need the hand-rolled footer row the web source
2543
+ itself calls out ("Modal uses its own actions").
2544
+
2545
+ Not ported: `aria-pressed` on the edit toggle — `Button`/`IconButton` set
2546
+ their own internal `accessibilityState` on the underlying `Pressable`, and
2547
+ a second `accessibilityState` prop passed in here would silently replace
2548
+ rather than merge with it (the same `Popover`-trigger clobber `SplitButton`
2549
+ already worked around) — dropped rather than routed around for one
2550
+ decorative toggle-state announcement.
2551
+
2358
2552
  ### BottomTabBar
2359
2553
 
2360
2554
  ```tsx
@@ -2397,6 +2591,62 @@ mode — `true` for a dot, a number for a count (capped at `"99+"`).
2397
2591
  this package takes no dependency on `react-native-safe-area-context`
2398
2592
  itself.
2399
2593
 
2594
+ ### useBreakpoint
2595
+
2596
+ ```tsx
2597
+ import { useBreakpoint } from '@gnome-ui/react-native';
2598
+
2599
+ const { isNarrow, isMedium, width } = useBreakpoint();
2600
+
2601
+ return isNarrow ? <CompactLayout /> : <RegularLayout />;
2602
+ ```
2603
+
2604
+ Tracks the window width against the same GNOME/libadwaita canonical
2605
+ breakpoints as `@gnome-ui/react`'s hook of the same name — `narrow` (≤ 400
2606
+ dp, split views collapse), `medium` (≤ 550 dp, `ViewSwitcher` moves to a
2607
+ bottom bar), `wide` (≤ 860 dp, outer pane of a nested split view
2608
+ collapses). Built on `useWindowDimensions` rather than `Dimensions.get` +
2609
+ a manual listener, since it already re-renders subscribers on every
2610
+ rotation/resize — there's no CSS media query to lean on here, unlike the
2611
+ web version's `window.innerWidth` + `resize` listener.
2612
+
2613
+ Also exports `bucketForWidth`/`resolveResponsive`/`ResponsiveValue` — the
2614
+ same small pure-function toolkit the web hook exports, for picking a
2615
+ value that varies by breakpoint (`{ base: 3, wide: 2, narrow: 1 }`-shaped
2616
+ maps). These aren't wired into any component's props yet; they're
2617
+ exported now so a future adaptive component (`Sidebar`'s `mode` prop,
2618
+ `NavigationSplitView`, …) doesn't have to redefine the bucket-fallback
2619
+ logic from scratch.
2620
+
2621
+ ### BreakpointBin
2622
+
2623
+ ```tsx
2624
+ import { BreakpointBin } from '@gnome-ui/react-native';
2625
+
2626
+ <BreakpointBin breakpoints={[{ name: 'compact', maxWidth: 400 }]}>
2627
+ {({ activeBreakpoint }) =>
2628
+ activeBreakpoint === 'compact' ? <CompactCard /> : <WideCard />
2629
+ }
2630
+ </BreakpointBin>;
2631
+ ```
2632
+
2633
+ The per-*component* (container-query) sibling of `useBreakpoint` — reacts
2634
+ to **its own width**, not the window, so the same component can render
2635
+ differently depending on how much space its parent gives it, regardless
2636
+ of device size. Two `BreakpointBin`s with identical `breakpoints` can be
2637
+ in different states side by side.
2638
+
2639
+ `@gnome-ui/react`'s version watches itself with `ResizeObserver`; RN has
2640
+ no such API, so this measures via `onLayout` instead — fired on mount and
2641
+ again on every subsequent resize of the wrapping `View` (a parent's flex
2642
+ layout reflowing, a device rotation, an ancestor `BreakpointBin` flipping
2643
+ column↔row). The active breakpoint is the smallest `maxWidth` ≥ the
2644
+ current width, declaration order doesn't matter (sorted internally), and
2645
+ `activeBreakpoint` is `null` when the container is wider than every
2646
+ threshold. Unlike the web version there's no `data-breakpoint` attribute
2647
+ to expose for CSS targeting (RN has no attribute selectors) — branch on
2648
+ `activeBreakpoint` directly inside the render prop instead.
2649
+
2400
2650
  ## Installation
2401
2651
 
2402
2652
  ```bash
@@ -0,0 +1,65 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, ViewProps, ViewStyle } from 'react-native';
3
+ export interface BreakpointDefinition {
4
+ /** Identifier for this breakpoint, e.g. `"compact"` or `"narrow"`. */
5
+ name: string;
6
+ /**
7
+ * Container width threshold in dp.
8
+ * This breakpoint becomes active when the container width is ≤ this value.
9
+ */
10
+ maxWidth: number;
11
+ }
12
+ export interface BreakpointBinState {
13
+ /**
14
+ * Name of the currently active breakpoint, or `null` when the container
15
+ * is wider than all defined breakpoints.
16
+ */
17
+ activeBreakpoint: string | null;
18
+ /** Current container width in dp (from `onLayout`). */
19
+ width: number;
20
+ }
21
+ export interface BreakpointBinProps extends Omit<ViewProps, 'children' | 'style'> {
22
+ /**
23
+ * Breakpoint definitions. The active breakpoint is the smallest
24
+ * `maxWidth` ≥ the container's current width — declaration order doesn't
25
+ * matter, they're sorted internally.
26
+ */
27
+ breakpoints: BreakpointDefinition[];
28
+ /**
29
+ * Render prop that receives the current breakpoint state.
30
+ *
31
+ * @example
32
+ * ```tsx
33
+ * <BreakpointBin breakpoints={[{ name: "compact", maxWidth: 400 }]}>
34
+ * {({ activeBreakpoint }) =>
35
+ * activeBreakpoint === "compact" ? <CompactLayout /> : <WideLayout />
36
+ * }
37
+ * </BreakpointBin>
38
+ * ```
39
+ */
40
+ children: (state: BreakpointBinState) => ReactNode;
41
+ style?: StyleProp<ViewStyle>;
42
+ }
43
+ /**
44
+ * Container that fires layout changes when **its own width** crosses
45
+ * defined thresholds — the CSS container-query equivalent of
46
+ * `AdwBreakpointBin` (libadwaita 1.9 / GNOME 50), and the per-*component*
47
+ * sibling of `useBreakpoint` (which watches the window instead).
48
+ *
49
+ * `@gnome-ui/react`'s version watches its own width via `ResizeObserver`;
50
+ * RN has no such API, so this measures the same thing via `onLayout` —
51
+ * fired on mount and again on every subsequent resize of the `View` itself
52
+ * (e.g. the parent's flex layout reflowing, or a device rotation changing
53
+ * how much space this container is given). Composable the same way: the
54
+ * same component can render differently depending on how much space its
55
+ * parent gives it, regardless of the window size — two `BreakpointBin`s
56
+ * side by side with identical `breakpoints` can be in different states.
57
+ *
58
+ * Unlike the web version, there's no `data-breakpoint` attribute to expose
59
+ * for CSS targeting (RN has no attribute selectors) — branch on
60
+ * `activeBreakpoint` directly inside the render prop instead, exactly like
61
+ * every story below already does.
62
+ *
63
+ * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.BreakpointBin.html
64
+ */
65
+ export declare const BreakpointBin: ({ breakpoints, children, style, onLayout, ...viewProps }: BreakpointBinProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { BreakpointBinProps, BreakpointBinState, BreakpointDefinition, } from './BreakpointBin';
2
+ export { BreakpointBin } from './BreakpointBin';
@@ -0,0 +1,47 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, ViewProps, ViewStyle } from 'react-native';
3
+ import { TextColor } from '../Text';
4
+ export type ButtonContentIconPosition = 'start' | 'end';
5
+ export interface ButtonContentProps extends Omit<ViewProps, 'style'> {
6
+ /** Icon placed next to the label. Rendered as-is — size/color it yourself. */
7
+ icon?: ReactNode;
8
+ /** Text label. */
9
+ label: string;
10
+ /**
11
+ * Position of the icon relative to the label.
12
+ * @default 'start'
13
+ */
14
+ iconPosition?: ButtonContentIconPosition;
15
+ /**
16
+ * Label color. RN has no `currentColor` equivalent, so unlike the web
17
+ * version (which inherits the parent button's text color via CSS), this
18
+ * needs to be told explicitly which color to match — e.g. `"accent"`
19
+ * when placed inside a `suggested` `Button`. Defaults to `"default"`.
20
+ */
21
+ color?: TextColor;
22
+ style?: StyleProp<ViewStyle>;
23
+ }
24
+ /**
25
+ * Icon + label layout helper for buttons that contain both an icon and
26
+ * text — the same 6 dp gap / vertically-centered row every button in this
27
+ * package already produces internally. Mirrors `AdwButtonContent`.
28
+ *
29
+ * **Mostly redundant with `Button`'s own `leadingIcon`/`trailingIcon`
30
+ * props** — pass those instead for anything that's actually a `Button`,
31
+ * they already lay the icon and (themed, variant-colored) label out
32
+ * identically and need no separate color prop. Reach for `ButtonContent`
33
+ * when composing icon+label content for something that *isn't* this
34
+ * package's `Button` — a bespoke `Pressable`, a `ButtonRow` title slot, a
35
+ * `Chip`'s custom content — anywhere the exact same Adwaita icon/label
36
+ * spacing convention is wanted outside `Button` itself.
37
+ *
38
+ * @example
39
+ * ```tsx
40
+ * <Pressable onPress={save}>
41
+ * <ButtonContent icon={<Icon icon={DocumentSave} />} label="Save" />
42
+ * </Pressable>
43
+ * ```
44
+ *
45
+ * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ButtonContent.html
46
+ */
47
+ export declare const ButtonContent: ({ icon, label, iconPosition, color, style, ...viewProps }: ButtonContentProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { ButtonContentIconPosition, ButtonContentProps } from './ButtonContent';
2
+ export { ButtonContent } from './ButtonContent';
@@ -0,0 +1,64 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ export interface SpinRowProps {
4
+ /** Primary label. */
5
+ title: string;
6
+ /** Secondary line below the title. */
7
+ subtitle?: string;
8
+ /** Icon or image placed at the leading edge. */
9
+ leading?: ReactNode;
10
+ /** Current value (controlled). */
11
+ value?: number;
12
+ /** Initial value when uncontrolled. Defaults to `0`. */
13
+ defaultValue?: number;
14
+ /** Called when the value changes. */
15
+ onValueChange?: (value: number) => void;
16
+ /** Minimum allowed value. Defaults to `0`. */
17
+ min?: number;
18
+ /** Maximum allowed value. Defaults to `100`. */
19
+ max?: number;
20
+ /** Amount to increment/decrement per step. Defaults to `1`. */
21
+ step?: number;
22
+ /** Number of decimal places shown. Derived from `step` when omitted. */
23
+ decimals?: number;
24
+ /** Accessible name for the spin button. Defaults to `title`. */
25
+ accessibilityLabel?: string;
26
+ /** Disables the row and its spin button. */
27
+ disabled?: boolean;
28
+ style?: StyleProp<ViewStyle>;
29
+ testID?: string;
30
+ }
31
+ /**
32
+ * Settings row with an integrated spin button for numeric values, mirroring
33
+ * `AdwSpinRow` and `@gnome-ui/react`'s own `SpinRow`. Use inside a
34
+ * `BoxedList` for settings with numeric ranges (volume, timeout duration,
35
+ * count limits, etc.).
36
+ *
37
+ * **This is a composition of `ActionRow` + `SpinButton`** — same reasoning
38
+ * as `ComboRow` (`ActionRow` + `Dropdown`): the row layout and the numeric
39
+ * stepper are each already shipped, verified components, so nothing here is
40
+ * rebuilt. `SpinButton` is controlled-only, so the uncontrolled `defaultValue`
41
+ * state lives here, one level up, the same shape `ComboRow` already
42
+ * established for `Dropdown`.
43
+ *
44
+ * The web version's keyboard interaction (↑/↓ one step, Page Up/Down ten
45
+ * steps, Home/End to bounds) drops as it does everywhere else in this
46
+ * package — `SpinButton` already provides the touch/screen-reader
47
+ * equivalents it was built with (tap the visible −/+ buttons, or the
48
+ * `accessibilityRole="adjustable"` increment/decrement actions).
49
+ *
50
+ * @example
51
+ * <BoxedList>
52
+ * <SpinRow
53
+ * title="Volume"
54
+ * subtitle="Output level"
55
+ * value={volume}
56
+ * onValueChange={setVolume}
57
+ * min={0}
58
+ * max={100}
59
+ * />
60
+ * </BoxedList>
61
+ *
62
+ * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SpinRow.html
63
+ */
64
+ export declare const SpinRow: ({ title, subtitle, leading, value: controlledValue, defaultValue, onValueChange, min, max, step, decimals, accessibilityLabel, disabled, style, testID, }: SpinRowProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { SpinRowProps } from './SpinRow';
2
+ export { SpinRow } from './SpinRow';
@@ -0,0 +1,46 @@
1
+ import { ReactNode } from 'react';
2
+ import { GestureResponderEvent, StyleProp, ViewStyle } from 'react-native';
3
+ export type SplitButtonVariant = 'default' | 'suggested' | 'destructive';
4
+ export interface SplitButtonProps {
5
+ /** Label shown in the primary button. */
6
+ label: string;
7
+ /** Visual style. Applies to both the primary and toggle halves. */
8
+ variant?: SplitButtonVariant;
9
+ /** Content rendered inside the popover panel when the arrow is pressed. */
10
+ dropdownContent: ReactNode;
11
+ /** Accessible label for the dropdown toggle button. Defaults to `"More options"`. */
12
+ dropdownLabel?: string;
13
+ /** Called when the primary (label) half is pressed. */
14
+ onPress?: (event: GestureResponderEvent) => void;
15
+ /** Disables both halves. */
16
+ disabled?: boolean;
17
+ style?: StyleProp<ViewStyle>;
18
+ testID?: string;
19
+ }
20
+ /**
21
+ * Primary action button with an attached dropdown arrow, mirroring
22
+ * `AdwSplitButton` and `@gnome-ui/react`'s own `SplitButton`. Pressing the
23
+ * label half fires `onPress`; pressing the arrow half opens a floating
24
+ * panel with `dropdownContent` (menus, options, etc.).
25
+ *
26
+ * The primary half is a real `Button` — its resting/pressed/disabled colors
27
+ * come for free. The arrow half is a hand-rolled `Pressable` rather than a
28
+ * second `Button`: nesting `Popover` (whose `children` clones a prop-level
29
+ * `accessibilityState`/`onPress` onto its trigger) around a full `Button`
30
+ * would silently clobber `Button`'s own internal `accessibilityState={{
31
+ * disabled }}` — RN merges a spread prop object outright, it doesn't merge
32
+ * key-by-key — so the arrow half derives the exact same variant colors via
33
+ * `getVariantColors` above instead, and owns `accessibilityState` itself
34
+ * (only `{ disabled }`, letting `Popover`'s clone merge in `expanded`).
35
+ * The two halves are visually connected by zeroing the shared inner corner
36
+ * radii and painting a 1px separator between them, the RN equivalent of the
37
+ * web CSS's `border-radius` split + `.separator` span.
38
+ *
39
+ * `Popover` supplies the floating panel, `Modal`-based positioning,
40
+ * outside-tap dismissal, and Android back-button handling — no repositioning
41
+ * math of its own needed here, unlike the web version's manual
42
+ * `getBoundingClientRect`/scroll-and-resize-tracking `place()` effect.
43
+ *
44
+ * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.SplitButton.html
45
+ */
46
+ export declare const SplitButton: ({ label, variant, dropdownContent, dropdownLabel, onPress, disabled, style, testID, }: SplitButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { SplitButtonProps, SplitButtonVariant } from './SplitButton';
2
+ export { SplitButton } from './SplitButton';
@@ -0,0 +1,59 @@
1
+ import { StyleProp, ViewStyle } from 'react-native';
2
+ export interface TagInputProps {
3
+ /** Current list of tags. */
4
+ value: string[];
5
+ /** Called when a tag is added or removed. */
6
+ onChange: (value: string[]) => void;
7
+ /** Visible label rendered above the input. */
8
+ label?: string;
9
+ /** Placeholder shown in the draft input while empty. */
10
+ placeholder?: string;
11
+ /** Helper text rendered below the input. Hidden when `error` is set. */
12
+ helperText?: string;
13
+ /**
14
+ * Error message rendered below the input in place of `helperText`.
15
+ * Also applies the error visual state to the border.
16
+ */
17
+ error?: string;
18
+ /** Maximum number of tags allowed. Once reached, the draft input is hidden. */
19
+ maxTags?: number;
20
+ /** Reject a new tag that already exists (case-insensitive). Defaults to `true`. */
21
+ preventDuplicates?: boolean;
22
+ /** Disables the whole control. */
23
+ disabled?: boolean;
24
+ style?: StyleProp<ViewStyle>;
25
+ testID?: string;
26
+ }
27
+ /**
28
+ * Type-to-add multi-value input rendering entries as removable `Chip`s in a
29
+ * `WrapBox`, mirroring `@gnome-ui/react`'s own `TagInput`.
30
+ *
31
+ * `WrapBox`/`Chip` alone only support static, pre-populated display —
32
+ * `TagInput` adds interactive entry: type and press Return, or type a `,`,
33
+ * to commit a tag; paste a comma/newline-separated list to add several at
34
+ * once; Backspace on an empty draft removes the last one.
35
+ *
36
+ * The web version wires typed-`,` and pasted-list handling as two separate
37
+ * handlers (`onKeyDown`'s `,` case, `onPaste`). RN's `TextInput` has no
38
+ * `paste` event to mirror — but a paste still flows through `onChangeText`
39
+ * with the full resulting text, exactly like a typed `,` does, so both
40
+ * collapse into one `handleChangeText`: whenever the text contains a `,` or
41
+ * newline, split on it and commit every non-empty part (typing "foo,"
42
+ * commits "foo" via the same path a paste of "foo,bar" commits both).
43
+ * Return is handled separately via `onSubmitEditing`, since RN has no
44
+ * `Enter` keystroke to catch on a single-line field the way the web version
45
+ * catches it in `onKeyDown`. Backspace-on-empty uses `onKeyPress`, the one
46
+ * RN `TextInput` event that still fires with the field already empty
47
+ * (`onChangeText` doesn't fire deleting from nothing).
48
+ *
49
+ * The tag box is a `Pressable` (mirrors the web version's
50
+ * `onClick={() => inputRef.current?.focus()}` on the container) with
51
+ * `accessible={false}` explicitly — `Pressable` defaults `accessible` to
52
+ * `true`, which would collapse every `Chip`'s remove button and the draft
53
+ * input into a single VoiceOver stop, the same container-swallows-subtree
54
+ * trap already documented for `ToggleGroup`/`Sidebar`'s bare-`View`-plus-role
55
+ * case, here hit via `Pressable`'s own default instead.
56
+ *
57
+ * @see https://developer.gnome.org/hig/patterns/controls/text-fields.html
58
+ */
59
+ export declare const TagInput: ({ value, onChange, label, placeholder, helperText, error, maxTags, preventDuplicates, disabled, style, testID, }: TagInputProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { TagInputProps } from './TagInput';
2
+ export { TagInput } from './TagInput';