@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 +254 -4
- package/dist/components/BreakpointBin/BreakpointBin.d.ts +65 -0
- package/dist/components/BreakpointBin/index.d.ts +2 -0
- package/dist/components/ButtonContent/ButtonContent.d.ts +47 -0
- package/dist/components/ButtonContent/index.d.ts +2 -0
- package/dist/components/SpinRow/SpinRow.d.ts +64 -0
- package/dist/components/SpinRow/index.d.ts +2 -0
- package/dist/components/SplitButton/SplitButton.d.ts +46 -0
- package/dist/components/SplitButton/index.d.ts +2 -0
- package/dist/components/TagInput/TagInput.d.ts +59 -0
- package/dist/components/TagInput/index.d.ts +2 -0
- package/dist/components/WidgetManager/WidgetManager.d.ts +92 -0
- package/dist/components/WidgetManager/index.d.ts +2 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useBreakpoint.d.ts +79 -0
- package/dist/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +7 -0
- package/dist/index.js +1385 -919
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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), `
|
|
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)
|
|
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
|
-
>
|
|
40
|
-
> package's
|
|
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,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,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,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,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;
|