@gnome-ui/react-native 1.7.0 → 1.9.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.
Files changed (38) hide show
  1. package/README.md +423 -3
  2. package/dist/components/Blockquote/Blockquote.d.ts +40 -0
  3. package/dist/components/Blockquote/index.d.ts +2 -0
  4. package/dist/components/BottomTabBar/BottomTabBar.d.ts +106 -0
  5. package/dist/components/BottomTabBar/index.d.ts +2 -0
  6. package/dist/components/BreakpointBin/BreakpointBin.d.ts +65 -0
  7. package/dist/components/BreakpointBin/index.d.ts +2 -0
  8. package/dist/components/ButtonContent/ButtonContent.d.ts +47 -0
  9. package/dist/components/ButtonContent/index.d.ts +2 -0
  10. package/dist/components/ButtonRow/ButtonRow.d.ts +41 -0
  11. package/dist/components/ButtonRow/index.d.ts +2 -0
  12. package/dist/components/Callout/Callout.d.ts +36 -0
  13. package/dist/components/Callout/index.d.ts +2 -0
  14. package/dist/components/CheckRow/CheckRow.d.ts +47 -0
  15. package/dist/components/CheckRow/index.d.ts +2 -0
  16. package/dist/components/ExpanderRow/ExpanderRow.d.ts +58 -0
  17. package/dist/components/ExpanderRow/index.d.ts +2 -0
  18. package/dist/components/FieldGroup/FieldGroup.d.ts +52 -0
  19. package/dist/components/FieldGroup/index.d.ts +2 -0
  20. package/dist/components/FilterableMultiSelectDropdown/FilterableMultiSelectDropdown.d.ts +46 -0
  21. package/dist/components/FilterableMultiSelectDropdown/index.d.ts +2 -0
  22. package/dist/components/Icon/Icon.d.ts +11 -1
  23. package/dist/components/MultiSelectDropdown/MultiSelectDropdown.d.ts +55 -0
  24. package/dist/components/MultiSelectDropdown/index.d.ts +2 -0
  25. package/dist/components/PasswordField/PasswordField.d.ts +48 -0
  26. package/dist/components/PasswordField/index.d.ts +2 -0
  27. package/dist/components/RangeSlider/RangeSlider.d.ts +73 -0
  28. package/dist/components/RangeSlider/index.d.ts +2 -0
  29. package/dist/components/StatusBadge/StatusBadge.d.ts +47 -0
  30. package/dist/components/StatusBadge/index.d.ts +2 -0
  31. package/dist/hooks/index.d.ts +1 -0
  32. package/dist/hooks/useBreakpoint.d.ts +79 -0
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.js +2248 -801
  37. package/dist/index.js.map +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -26,9 +26,24 @@ React Native component library following the [GNOME Human Interface Guidelines](
26
26
  > `Clamp` (Tier 6), `Box` (Tier 20), `WrapBox`/`ToggleGroup` (Tier 7), and
27
27
  > `InlineViewSwitcher` (Tier 8), `PreferencesGroup` (Tier 13), and
28
28
  > `EntryRow`/`PasswordEntryRow`/`ComboRow` (Tier 12), `ColorPicker`
29
- > (Tier 20), and `Bin` (Tier 15) also shipped. Component ports from
30
- > `@gnome-ui/react` continue tier by tier see this package's own
31
- > [ROADMAP.md](./ROADMAP.md) for full
29
+ > (Tier 20), `Bin` (Tier 15), `Blockquote` (Tier 20), `ButtonRow`
30
+ > (Tier 8), `Callout` (Tier 20), `CheckRow` (Tier 12), `ExpanderRow`
31
+ > (Tier 8), `FieldGroup` (Tier 20), and `MultiSelectDropdown` (Tier 20)
32
+ > also shipped, along with `FilterableMultiSelectDropdown` — an original
33
+ > `@gnome-ui/react`-only component (not a GNOME HIG port) built once its
34
+ > prerequisite `MultiSelectDropdown` landed — and `PasswordField`/
35
+ > `RangeSlider`/`StatusBadge` (all Tier 20). `BottomTabBar` also shipped —
36
+ > a React Native-only original with no `@gnome-ui/react` source at all
37
+ > (desktop apps don't have a bottom tab bar pattern to mirror), built on
38
+ > explicit request for the iOS/Android fixed bottom-navigation shape.
39
+ > `useBreakpoint` and `BreakpointBin` (both Tier 6) also shipped — the
40
+ > package's first adaptive-layout primitives, built on `useWindowDimensions`
41
+ > and `onLayout` respectively since there's no CSS media query/container
42
+ > query to lean on. `ButtonContent` (Tier 15) also shipped — an icon+label
43
+ > layout helper mostly redundant with `Button`'s own `leadingIcon`/
44
+ > `trailingIcon`, kept for composing the same spacing outside `Button`
45
+ > itself. Component ports from `@gnome-ui/react` continue tier by
46
+ > tier — see this package's own [ROADMAP.md](./ROADMAP.md) for full
32
47
  > per-tier status against all 130 `@gnome-ui/react` components, and the
33
48
  > main [ROADMAP.md](../../ROADMAP.md) Priority 3 for the framework
34
49
  > expansion this package belongs to.
@@ -488,6 +503,10 @@ button is the RN-idiomatic stand-in. The clear (×) button appears whenever
488
503
  glyphs (`🔍`/`×`) rather than `@gnome-ui/icons`, matching every other
489
504
  no-SVG-dependency component in this package.
490
505
 
506
+ `inline` drops the header-bar background/border so the bar blends into any
507
+ surface — a card, a plain content area, a custom container — instead of
508
+ looking like it belongs to a `HeaderBar`.
509
+
491
510
  Dropped relative to `@gnome-ui/react`'s `SearchBar`: the `suggestions` /
492
511
  `onSuggestionSelect` / `loadingSuggestions` / `renderSuggestion` /
493
512
  `suggestionsLabel` autocomplete popover — it depends on a portal +
@@ -2074,6 +2093,407 @@ already has no default visual styling — no background, no border — so
2074
2093
  unlike the web port there's no CSS reset to strip; this is a pure
2075
2094
  passthrough.
2076
2095
 
2096
+ ### Blockquote
2097
+
2098
+ ```tsx
2099
+ import { Blockquote } from '@gnome-ui/react-native';
2100
+
2101
+ <Blockquote variant="info" cite="Ada Lovelace, 1842">
2102
+ The Analytical Engine has no pretensions to originate anything.
2103
+ </Blockquote>
2104
+ ```
2105
+
2106
+ Pull-quote with a colored left border, mirroring `@gnome-ui/react`'s
2107
+ `Blockquote`. Five visual variants (`default`/`info`/`warning`/`error`/
2108
+ `success`) share the same severity scale as `Banner`/`Chip`, with an
2109
+ optional leading `icon` and a `cite` attribution line rendered below the
2110
+ quote. The web version's `color-mix(in srgb, <color> 8%/14%, transparent)`
2111
+ tinted backgrounds have no RN equivalent — resolved as an 8-digit
2112
+ `#RRGGBBAA` alpha suffix on the variant's own hex token instead, the same
2113
+ substitution `Chip`'s selected-state tint already established. `<blockquote>`/
2114
+ `<footer>`/`<cite>` have no RN element equivalent, so this renders as a
2115
+ plain `View` with no semantic role.
2116
+
2117
+ ### ButtonContent
2118
+
2119
+ ```tsx
2120
+ import { ButtonContent, Icon } from '@gnome-ui/react-native';
2121
+ import { Save } from '@gnome-ui/icons';
2122
+
2123
+ <Pressable onPress={save}>
2124
+ <ButtonContent icon={<Icon icon={Save} size="sm" />} label="Save" color="accent" />
2125
+ </Pressable>;
2126
+ ```
2127
+
2128
+ Icon + label layout helper mirroring `AdwButtonContent` — the same 6 dp
2129
+ gap, vertically-centered row every button in this package already
2130
+ produces internally via `leadingIcon`/`trailingIcon`. **Mostly redundant
2131
+ with `Button`'s own `leadingIcon`/`trailingIcon` props** for anything that
2132
+ actually is a `Button` — those already lay the icon and a themed,
2133
+ variant-colored label out identically, with no separate color prop
2134
+ needed. Reach for `ButtonContent` when composing icon+label content for
2135
+ something that *isn't* this package's `Button` instead: a bespoke
2136
+ `Pressable`, a custom card action, anywhere the same Adwaita spacing
2137
+ convention is wanted outside `Button` itself.
2138
+
2139
+ Unlike the web version, there's no `currentColor` to inherit the
2140
+ surrounding button's text color from, so a `color` prop (the same
2141
+ `TextColor` union `Text`/`ButtonRow` already use) needs to be passed
2142
+ explicitly to match — e.g. `color="accent"` beside a `suggested`-style
2143
+ action, `color="destructive"` beside a destructive one. The icon slot is
2144
+ marked `accessibilityElementsHidden`/`importantForAccessibility="no-hide-
2145
+ descendants"`, the RN equivalent of the web version's `aria-hidden="true"`
2146
+ on its icon `<span>`.
2147
+
2148
+ ### ButtonRow
2149
+
2150
+ ```tsx
2151
+ import { BoxedList, ButtonRow } from '@gnome-ui/react-native';
2152
+
2153
+ <BoxedList>
2154
+ <ButtonRow title="Save changes" variant="suggested" onPress={handleSave} />
2155
+ <ButtonRow title="Delete account" variant="destructive" onPress={handleDelete} />
2156
+ </BoxedList>
2157
+ ```
2158
+
2159
+ Full-width activatable row styled as a button, for use inside a `BoxedList`
2160
+ — mirrors `AdwButtonRow` and `@gnome-ui/react`'s own `ButtonRow`. Use when
2161
+ an entire list row should trigger a single action with a centered label;
2162
+ prefer `ActionRow` with `interactive` when the row also needs a
2163
+ title/subtitle layout. Rebuilt with `Pressable` rather than ported from the
2164
+ web `<button>` — the same pressed-state-overlay recipe `ActionRow`/`Card`
2165
+ already established (`theme.activeOverlay` stands in for the web's
2166
+ `:hover`/`:active` background transition). The title's color reuses `Text`'s
2167
+ own `TextColor` union (`"accent"`/`"destructive"` resolve to the exact same
2168
+ tokens the source CSS's `suggested`/`destructive` variants reference), and
2169
+ gets `flex: 1` + `textAlign: 'center'` — ported straight from the source
2170
+ CSS's `.title` — so the label stays centered even when only one of
2171
+ `leading`/`trailing` is present. The variant color does not propagate to
2172
+ `leading`/`trailing` icons (no `currentColor` equivalent in RN), the same
2173
+ dropped nicety `Chip` already accepted for its own icons.
2174
+
2175
+ ### Callout
2176
+
2177
+ ```tsx
2178
+ import { Callout } from '@gnome-ui/react-native';
2179
+
2180
+ <Callout variant="tip" dismissible onDismiss={() => setVisible(false)}>
2181
+ Press and hold a row to reveal more actions.
2182
+ </Callout>
2183
+ ```
2184
+
2185
+ Inline, dismissible admonition box for contextual help text within forms
2186
+ and cards — mirrors `@gnome-ui/react`'s `Callout`. Unlike `Banner` (a
2187
+ persistent, edge-to-edge strip at the top of a view) and `Toast` (a
2188
+ temporary notification), `Callout` is a contained, tinted box meant to sit
2189
+ inline alongside the content it annotates. Three variants (`info`/
2190
+ `warning`/`tip`) each pair a tinted background/border with a leading icon;
2191
+ `Icon`'s fixed named palette (`blue`/`yellow`/`green`) stands in for the
2192
+ web's arbitrary `color-mix()` icon tint — `blue3`/`green4` happen to match
2193
+ `accentBgColor`/`successBgColor` exactly in every theme this package
2194
+ ships, so it's not an approximation for `info`/`tip`. `role="note"` ports
2195
+ 1:1 from RN's web-aligned `Role` union (same as `Dialog`/`Tooltip`).
2196
+
2197
+ ### CheckRow
2198
+
2199
+ ```tsx
2200
+ import { BoxedList, CheckRow } from '@gnome-ui/react-native';
2201
+
2202
+ <BoxedList>
2203
+ <CheckRow
2204
+ title="Wi-Fi"
2205
+ subtitle="Home Network"
2206
+ checked={wifi}
2207
+ onCheckedChange={setWifi}
2208
+ />
2209
+ </BoxedList>
2210
+ ```
2211
+
2212
+ Activatable row with an integrated checkbox, for use inside a `BoxedList`
2213
+ — mirrors `@gnome-ui/react`'s `CheckRow`. The entire row is a single
2214
+ pressable; pressing anywhere toggles the checked state. Use for multi-select
2215
+ scenarios; prefer a single `Switch` row for a standalone on/off setting.
2216
+ Supports both controlled (`checked`) and uncontrolled (`defaultChecked`)
2217
+ modes, the same `isControlled`/internal-state-fallback shape already used
2218
+ by `Expander`/`ComboRow`/`Popover`. The checkbox visual reuses `Checkbox`'s
2219
+ exact border/background animation and checkmark-fade-in recipe, but as a
2220
+ plain non-interactive `View` rather than the real `Checkbox` component —
2221
+ nesting one `Pressable` inside another would create two overlapping tap
2222
+ targets. `aria-labelledby` has no RN equivalent, so `accessibilityLabel`
2223
+ combines the title and subtitle instead.
2224
+
2225
+ ### ExpanderRow
2226
+
2227
+ ```tsx
2228
+ import { ActionRow, BoxedList, ExpanderRow } from '@gnome-ui/react-native';
2229
+
2230
+ <BoxedList>
2231
+ <ExpanderRow title="Advanced" subtitle="Proxy, DNS, and MTU settings">
2232
+ <ActionRow title="Proxy" subtitle="Automatic" />
2233
+ <ActionRow title="DNS" subtitle="Automatic" />
2234
+ </ExpanderRow>
2235
+ </BoxedList>
2236
+ ```
2237
+
2238
+ Collapsible `ActionRow` that reveals nested rows on activation — mirrors
2239
+ `@gnome-ui/react`'s `ExpanderRow`. The header row toggles a smooth reveal
2240
+ animation exposing child rows; supports both controlled (`expanded`) and
2241
+ uncontrolled (`defaultExpanded`) modes. The reveal panel reuses the
2242
+ standalone `Expander`'s exact `Animated.View` height-driven recipe almost
2243
+ verbatim — only the chevron differs, using `PanDown` (rotating 0deg →
2244
+ 180deg, matching the source CSS's straight down-arrow) instead of
2245
+ `Expander`'s own `PanEnd` triangle. Nested children get a real `Separator`
2246
+ inserted before each one automatically. Use `trailing` for a value label
2247
+ or a `Switch`; stop event propagation inside interactive trailing widgets
2248
+ so the row's own toggle isn't triggered. Unlike `CheckRow`/`ButtonRow`,
2249
+ there is no `disabled` prop — the source component doesn't expose one.
2250
+
2251
+ ### FieldGroup
2252
+
2253
+ ```tsx
2254
+ import { FieldGroup, RadioButton } from '@gnome-ui/react-native';
2255
+
2256
+ <FieldGroup label="Notification method" helperText="Choose how you want to be notified.">
2257
+ <LabeledRadio label="Email" selected={method === 'email'} onSelect={() => setMethod('email')} />
2258
+ <LabeledRadio label="SMS" selected={method === 'sms'} onSelect={() => setMethod('sms')} />
2259
+ </FieldGroup>
2260
+ ```
2261
+
2262
+ Generic form-field grouping with a shared label, help text, and error
2263
+ message, for arbitrary fields outside a `BoxedList` — mirrors
2264
+ `@gnome-ui/react`'s `FieldGroup`. `PreferencesGroup` is scoped specifically
2265
+ to wrapping settings rows inside a `BoxedList`; use `FieldGroup` for a
2266
+ plain labeled grouping around any set of related form controls (e.g. a
2267
+ `RadioButton` group or several `Checkbox`es sharing one label and error),
2268
+ independent of the settings-page layout. `<fieldset>`/`<legend>` have no
2269
+ RN element equivalent — this renders as a `View` with `role="group"` and a
2270
+ themed `Text` label. The hint/error text reuses `TextField`'s exact
2271
+ `error ?? helperText` recipe. Unlike the web version's native
2272
+ `<fieldset disabled>`, `disabled` here only dims the group visually — RN
2273
+ has no equivalent of a fieldset automatically disabling every descendant
2274
+ control, so each child still needs disabling individually.
2275
+
2276
+ ### MultiSelectDropdown
2277
+
2278
+ ```tsx
2279
+ import { MultiSelectDropdown } from '@gnome-ui/react-native';
2280
+
2281
+ <MultiSelectDropdown
2282
+ options={[
2283
+ { value: 'wifi', label: 'Wi-Fi' },
2284
+ { value: 'bluetooth', label: 'Bluetooth' },
2285
+ ]}
2286
+ value={permissions}
2287
+ onChange={setPermissions}
2288
+ placeholder="Select permissions"
2289
+ />
2290
+ ```
2291
+
2292
+ Checkbox-list variant of `Dropdown` for selecting multiple values from a
2293
+ single trigger — mirrors `@gnome-ui/react`'s `MultiSelectDropdown`. Use it
2294
+ over `Dropdown`/`ComboRow` (single-select only) whenever more than one
2295
+ value can be chosen at once. Toggling an option keeps the panel open, so
2296
+ the user can pick several in a row; close it via a backdrop tap. Reuses
2297
+ `Dropdown`'s `Modal` + backdrop + independently-measured trigger-rect/
2298
+ panel-height positioning almost verbatim, and gives each option row a
2299
+ leading checkbox-square visual instead of `Dropdown`'s single trailing
2300
+ checkmark.
2301
+
2302
+ ### FilterableMultiSelectDropdown
2303
+
2304
+ ```tsx
2305
+ import { FilterableMultiSelectDropdown } from '@gnome-ui/react-native';
2306
+
2307
+ <FilterableMultiSelectDropdown
2308
+ options={languages}
2309
+ value={selected}
2310
+ onChange={setSelected}
2311
+ filterPlaceholder="Search languages…"
2312
+ />
2313
+ ```
2314
+
2315
+ `MultiSelectDropdown` plus a filter field for narrowing long option lists
2316
+ — mirrors `@gnome-ui/react`'s `FilterableMultiSelectDropdown`, an original
2317
+ component specific to that package (not a GNOME HIG port). Opening the
2318
+ panel auto-focuses a filter `TextInput` pinned above the list; typing
2319
+ narrows the options to those whose label or description contains the
2320
+ query (case-insensitive) — filtering only affects what's shown, values
2321
+ selected before a query hides their option stay selected. An empty
2322
+ filtered result shows a centered "No results" message. The web version's
2323
+ filter-field keyboard navigation (↑/↓ roving highlight, Home/End,
2324
+ Enter-to-toggle) has no RN port, the same "no keyboard focus to drive it"
2325
+ reasoning `Dropdown` already established — the `TextInput` and its
2326
+ software keyboard still work natively, only the roving-highlight layer on
2327
+ top is dropped.
2328
+
2329
+ ### PasswordField
2330
+
2331
+ ```tsx
2332
+ import { PasswordField } from '@gnome-ui/react-native';
2333
+
2334
+ <PasswordField
2335
+ label="Password"
2336
+ value={password}
2337
+ onChangeText={setPassword}
2338
+ helperText="At least 8 characters"
2339
+ autoComplete="new-password"
2340
+ />
2341
+ ```
2342
+
2343
+ Single-line password input with a peek toggle to reveal the value as plain
2344
+ text — mirrors `@gnome-ui/react`'s `PasswordField`. `TextField` plus the
2345
+ exact `secureTextEntry`/`IconButton` reveal recipe `PasswordEntryRow`
2346
+ already established, at `size="sm"` (28 dp circular), positioned
2347
+ absolutely at the input's trailing edge and vertically centered — the
2348
+ input gets extra trailing padding so typed text never runs under the
2349
+ button. Use this over `TextField` with a manually-set `secureTextEntry`
2350
+ (which has no reveal affordance). Pass `revealable={false}` to hide the
2351
+ toggle entirely (e.g. for a PIN field).
2352
+
2353
+ ### RangeSlider
2354
+
2355
+ ```tsx
2356
+ import { RangeSlider } from '@gnome-ui/react-native';
2357
+
2358
+ <RangeSlider
2359
+ value={priceRange}
2360
+ onChange={setPriceRange}
2361
+ minLabel="Minimum price"
2362
+ maxLabel="Maximum price"
2363
+ />
2364
+ ```
2365
+
2366
+ Dual-thumb slider for selecting a min/max range — mirrors
2367
+ `@gnome-ui/react`'s `RangeSlider`. Distinct from `Slider` (a single value)
2368
+ — use this for range filters (price, date range, age, etc.) where both
2369
+ bounds are adjustable. Reuses `Slider`'s exact `PanResponder`/`locationX`
2370
+ pixel-positioning technique: a single responder spans the whole track and,
2371
+ on touch-down, picks whichever thumb is nearer — that thumb then stays
2372
+ locked to the gesture for the rest of the drag, even if the touch drifts
2373
+ closer to the other thumb. Touching anywhere on the track immediately
2374
+ jumps the nearest thumb there, so there's no separate "drag the track"
2375
+ affordance to port. `minDistance` keeps the two thumbs from crossing or
2376
+ overlapping. The web's keyboard navigation has no RN port — replaced by
2377
+ two independent `accessibilityRole="adjustable"` elements (one per thumb),
2378
+ the same VoiceOver/TalkBack increment/decrement analog `Slider` already
2379
+ established.
2380
+
2381
+ ### StatusBadge
2382
+
2383
+ ```tsx
2384
+ import { StatusBadge } from '@gnome-ui/react-native';
2385
+
2386
+ <StatusBadge variant="success">published</StatusBadge>
2387
+ <StatusBadge variant="warning">beta</StatusBadge>
2388
+ <StatusBadge variant="new">new</StatusBadge>
2389
+ ```
2390
+
2391
+ Pill-shaped text label for entity status — published, beta, new, etc. —
2392
+ mirrors `@gnome-ui/react`'s `StatusBadge`. Unlike `Badge` (numeric counts,
2393
+ optional anchor/dot mode), this is a simpler sibling designed purely for
2394
+ short human-readable state labels: no anchor positioning, no dot mode, no
2395
+ counter. Six variants (`success`/`warning`/`error`/`new`/`accent`/
2396
+ `neutral`) reuse `Badge`'s exact color-mapping shape, plus a `new` (purple)
2397
+ variant `Badge` doesn't have.
2398
+
2399
+ ### BottomTabBar
2400
+
2401
+ ```tsx
2402
+ import { BottomTabBar } from '@gnome-ui/react-native';
2403
+ import { useSafeAreaInsets } from 'react-native-safe-area-context';
2404
+
2405
+ const insets = useSafeAreaInsets();
2406
+
2407
+ <BottomTabBar
2408
+ items={[
2409
+ { value: 'home', label: 'Home', icon: HomeOutline, activeIcon: HomeFilled },
2410
+ { value: 'search', label: 'Search', icon: Search },
2411
+ { value: 'profile', label: 'Profile', icon: Person, badge: true },
2412
+ ]}
2413
+ value={tab}
2414
+ onChange={setTab}
2415
+ bottomInset={insets.bottom}
2416
+ />
2417
+ ```
2418
+
2419
+ Fixed bottom navigation bar — the iOS/Android "tab bar" pattern (Music,
2420
+ Instagram, most system apps): a small, fixed set of top-level
2421
+ destinations, each an icon + label, always visible at the foot of the
2422
+ screen. No GNOME/libadwaita widget mirrors this, so it's an original
2423
+ component for this package, not a port.
2424
+
2425
+ Distinct from the existing `TabBar`/`TabItem` (an in-page, horizontally
2426
+ scrollable content switcher) — that one signals "active" purely via a
2427
+ background pill + bold weight + accent underline, never by tinting the
2428
+ icon/label themselves. A bottom tab bar's whole visual signature is the
2429
+ opposite: the active icon+label *are* the app's accent color, so this is
2430
+ the first consumer of `Icon`'s new `tintColor` prop (an arbitrary-fill
2431
+ override, since `color="blue"` always means a fixed swatch, never
2432
+ whatever accent the app actually configured via `GnomeProvider
2433
+ accentColor`). Pass `activeIcon` for the filled-vs-outline convention iOS/
2434
+ Android system tab bars both use; omit it to reuse `icon` for both states,
2435
+ tinted differently. `badge` reuses the real `Badge` component's `anchor`
2436
+ mode — `true` for a dot, a number for a count (capped at `"99+"`).
2437
+ `bottomInset` lets you thread in your own `useSafeAreaInsets().bottom` —
2438
+ this package takes no dependency on `react-native-safe-area-context`
2439
+ itself.
2440
+
2441
+ ### useBreakpoint
2442
+
2443
+ ```tsx
2444
+ import { useBreakpoint } from '@gnome-ui/react-native';
2445
+
2446
+ const { isNarrow, isMedium, width } = useBreakpoint();
2447
+
2448
+ return isNarrow ? <CompactLayout /> : <RegularLayout />;
2449
+ ```
2450
+
2451
+ Tracks the window width against the same GNOME/libadwaita canonical
2452
+ breakpoints as `@gnome-ui/react`'s hook of the same name — `narrow` (≤ 400
2453
+ dp, split views collapse), `medium` (≤ 550 dp, `ViewSwitcher` moves to a
2454
+ bottom bar), `wide` (≤ 860 dp, outer pane of a nested split view
2455
+ collapses). Built on `useWindowDimensions` rather than `Dimensions.get` +
2456
+ a manual listener, since it already re-renders subscribers on every
2457
+ rotation/resize — there's no CSS media query to lean on here, unlike the
2458
+ web version's `window.innerWidth` + `resize` listener.
2459
+
2460
+ Also exports `bucketForWidth`/`resolveResponsive`/`ResponsiveValue` — the
2461
+ same small pure-function toolkit the web hook exports, for picking a
2462
+ value that varies by breakpoint (`{ base: 3, wide: 2, narrow: 1 }`-shaped
2463
+ maps). These aren't wired into any component's props yet; they're
2464
+ exported now so a future adaptive component (`Sidebar`'s `mode` prop,
2465
+ `NavigationSplitView`, …) doesn't have to redefine the bucket-fallback
2466
+ logic from scratch.
2467
+
2468
+ ### BreakpointBin
2469
+
2470
+ ```tsx
2471
+ import { BreakpointBin } from '@gnome-ui/react-native';
2472
+
2473
+ <BreakpointBin breakpoints={[{ name: 'compact', maxWidth: 400 }]}>
2474
+ {({ activeBreakpoint }) =>
2475
+ activeBreakpoint === 'compact' ? <CompactCard /> : <WideCard />
2476
+ }
2477
+ </BreakpointBin>;
2478
+ ```
2479
+
2480
+ The per-*component* (container-query) sibling of `useBreakpoint` — reacts
2481
+ to **its own width**, not the window, so the same component can render
2482
+ differently depending on how much space its parent gives it, regardless
2483
+ of device size. Two `BreakpointBin`s with identical `breakpoints` can be
2484
+ in different states side by side.
2485
+
2486
+ `@gnome-ui/react`'s version watches itself with `ResizeObserver`; RN has
2487
+ no such API, so this measures via `onLayout` instead — fired on mount and
2488
+ again on every subsequent resize of the wrapping `View` (a parent's flex
2489
+ layout reflowing, a device rotation, an ancestor `BreakpointBin` flipping
2490
+ column↔row). The active breakpoint is the smallest `maxWidth` ≥ the
2491
+ current width, declaration order doesn't matter (sorted internally), and
2492
+ `activeBreakpoint` is `null` when the container is wider than every
2493
+ threshold. Unlike the web version there's no `data-breakpoint` attribute
2494
+ to expose for CSS targeting (RN has no attribute selectors) — branch on
2495
+ `activeBreakpoint` directly inside the render prop instead.
2496
+
2077
2497
  ## Installation
2078
2498
 
2079
2499
  ```bash
@@ -0,0 +1,40 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, ViewProps, ViewStyle } from 'react-native';
3
+ export type BlockquoteVariant = 'default' | 'info' | 'warning' | 'error' | 'success';
4
+ export interface BlockquoteProps extends Omit<ViewProps, 'style'> {
5
+ /**
6
+ * Visual emphasis style.
7
+ * - `"default"` (default) — neutral left border; use for general quotations.
8
+ * - `"info"` — accent blue; use for tips and noteworthy passages.
9
+ * - `"warning"` — yellow; use for cautionary content.
10
+ * - `"error"` — red; use for critical or deprecated content.
11
+ * - `"success"` — green; use for positive or approved content.
12
+ */
13
+ variant?: BlockquoteVariant;
14
+ /** Optional icon rendered to the left of the quoted text, e.g. an `<Icon>`. */
15
+ icon?: ReactNode;
16
+ /** Attribution rendered below the quote — author name, source title, etc. */
17
+ cite?: ReactNode;
18
+ /** The quoted content. */
19
+ children: ReactNode;
20
+ style?: StyleProp<ViewStyle>;
21
+ }
22
+ /**
23
+ * Styled pull-quote with a colored left border, mirroring `@gnome-ui/react`'s
24
+ * `Blockquote`. Five visual variants share the same severity scale as
25
+ * `Banner`/`Chip`.
26
+ *
27
+ * `<blockquote>`/`<footer>`/`<cite>` have no RN element equivalent — this
28
+ * renders as a plain `View` with no semantic role, the same "no landmark
29
+ * role available" gap `PathBar`'s dropped `<nav>` and `HeaderBar`'s dropped
30
+ * `<header>` already established; the quoted text and citation are plain
31
+ * `Text` reads with no assistive-tech grouping to port.
32
+ *
33
+ * @example
34
+ * <Blockquote variant="info" cite="Ada Lovelace, 1842">
35
+ * The Analytical Engine has no pretensions to originate anything.
36
+ * </Blockquote>
37
+ *
38
+ * @see https://developer.gnome.org/hig/
39
+ */
40
+ export declare const Blockquote: ({ variant, icon, cite, children, style, ...viewProps }: BlockquoteProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { BlockquoteProps, BlockquoteVariant } from './Blockquote';
2
+ export { Blockquote } from './Blockquote';
@@ -0,0 +1,106 @@
1
+ import { AnyIconDefinition } from '@gnome-ui/icons';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ export interface BottomTabBarItem<V extends string = string> {
4
+ /** The value reported to `onChange` when this tab is pressed. */
5
+ value: V;
6
+ /** Label shown below the icon. */
7
+ label: string;
8
+ /** Icon shown while this tab is not the selected one. */
9
+ icon: AnyIconDefinition;
10
+ /**
11
+ * Icon shown while this tab *is* selected — the filled-vs-outline
12
+ * convention iOS/Android system tab bars both use (e.g. a hollow heart
13
+ * outline that becomes solid when active). Optional: omit to reuse
14
+ * `icon` for both states, tinted differently.
15
+ */
16
+ activeIcon?: AnyIconDefinition;
17
+ /**
18
+ * Notification indicator. `true` renders a small dot (unread/pending,
19
+ * no count); a `number` renders a counted badge (values above 99 render
20
+ * as `"99+"`, matching `TabItem`'s own count-badge convention).
21
+ */
22
+ badge?: number | boolean;
23
+ /** Disables this one tab. */
24
+ disabled?: boolean;
25
+ }
26
+ export interface BottomTabBarProps<V extends string = string> {
27
+ /** The tabs to render, in order. */
28
+ items: BottomTabBarItem<V>[];
29
+ /** The currently selected tab's value. */
30
+ value: V;
31
+ /** Called when the user presses a tab. */
32
+ onChange: (value: V) => void;
33
+ /**
34
+ * Extra bottom padding, in addition to the bar's own vertical padding —
35
+ * pass your own `useSafeAreaInsets().bottom` here so the bar clears the
36
+ * home indicator / gesture area on notched devices. This package takes
37
+ * no dependency on `react-native-safe-area-context` itself (the same
38
+ * "no new peer dependency without a deliberate decision" standard
39
+ * `AnimatedIcon`'s `react-native-svg` addition was held to) — the
40
+ * consumer's own app almost certainly already has it for its root
41
+ * layout, so threading the value in here is cheaper than this package
42
+ * depending on it too. Defaults to `0`.
43
+ */
44
+ bottomInset?: number;
45
+ style?: StyleProp<ViewStyle>;
46
+ testID?: string;
47
+ }
48
+ /**
49
+ * Fixed bottom navigation bar — the iOS/Android "tab bar" pattern (Music,
50
+ * Instagram, most system apps): a small, fixed set of top-level
51
+ * destinations, each an icon + label, always visible at the foot of the
52
+ * screen. No GNOME/libadwaita widget mirrors this (desktop apps don't use
53
+ * bottom navigation), so this is an original component for this package,
54
+ * not a port — built to fill a real gap once mobile "modern app shell"
55
+ * navigation was requested.
56
+ *
57
+ * Distinct from the existing `TabBar`/`TabItem` (an in-page, horizontally
58
+ * scrollable content switcher mirroring `@gnome-ui/react`'s `Tabs`) — that
59
+ * one reuses `headerbarFgColor` for every tab's label regardless of
60
+ * selection state, signaling "active" purely via a background pill +
61
+ * bold weight + accent underline, never by tinting the icon/label
62
+ * themselves. A bottom tab bar's whole visual signature is the opposite:
63
+ * the active icon+label *are* the app's accent color. `Icon`'s `color`
64
+ * prop can't express that on its own — it only resolves to a fixed
65
+ * named-palette swatch (`color="blue"` always means `theme.blue3`, never
66
+ * whatever accent the app actually configured via `GnomeProvider
67
+ * accentColor`) — so this is the first consumer of `Icon`'s new
68
+ * `tintColor` prop, passing `theme.accentColor` directly for the selected
69
+ * tab and leaving it unset (dimmed default) for the rest.
70
+ *
71
+ * The optional notification indicator reuses the real `Badge` component
72
+ * for the dot/pill itself, but positions it manually rather than through
73
+ * `Badge`'s own `anchor` mode — `anchor` wraps `[anchor, badge]` in a
74
+ * `View` with a hardcoded `alignSelf: 'flex-start'`, which is fine for
75
+ * `Badge`'s usual context (a row where the anchor is meant to hug the
76
+ * start) but silently broke centering here: a bare icon (no badge) is a
77
+ * direct child of this component's centered column, while a badged icon's
78
+ * extra `anchor`-mode wrapper overrode that centering and pinned itself to
79
+ * the left edge — the two looked visibly misaligned side by side. Every
80
+ * tab now gets the identical `position: 'relative'` wrapper shape
81
+ * regardless of whether it has a badge, so centering behaves identically
82
+ * across all of them; the badge itself is a plain (non-`anchor`) `Badge`,
83
+ * absolutely positioned and re-sized for this icon's actual scale (`size="lg"`
84
+ * is only 20px — `Badge`'s own default dot/offset sizing assumes a much
85
+ * larger anchor like an avatar or `IconButton`, and would otherwise nearly
86
+ * cover the icon rather than sit as a small corner accent). Press feedback
87
+ * is the same `pressed ? theme.activeOverlay : 'transparent'` recipe every
88
+ * other `Pressable` in this package already uses.
89
+ *
90
+ * `bottomInset` stands in for real safe-area awareness without this
91
+ * package taking on `react-native-safe-area-context` as a dependency —
92
+ * see its own doc comment.
93
+ *
94
+ * @example
95
+ * <BottomTabBar
96
+ * items={[
97
+ * { value: 'home', label: 'Home', icon: HomeOutline, activeIcon: HomeFilled },
98
+ * { value: 'search', label: 'Search', icon: Search },
99
+ * { value: 'profile', label: 'Profile', icon: Person, badge: true },
100
+ * ]}
101
+ * value={tab}
102
+ * onChange={setTab}
103
+ * bottomInset={insets.bottom}
104
+ * />
105
+ */
106
+ export declare const BottomTabBar: <V extends string = string>({ items, value, onChange, bottomInset, style, testID, }: BottomTabBarProps<V>) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { BottomTabBarItem, BottomTabBarProps } from './BottomTabBar';
2
+ export { BottomTabBar } from './BottomTabBar';
@@ -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';