@gnome-ui/react-native 1.9.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 +156 -3
- 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/index.cjs +2 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +873 -496
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -25,14 +25,17 @@ 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.
|
|
@@ -42,7 +45,9 @@ React Native component library following the [GNOME Human Interface Guidelines](
|
|
|
42
45
|
> query to lean on. `ButtonContent` (Tier 15) also shipped — an icon+label
|
|
43
46
|
> layout helper mostly redundant with `Button`'s own `leadingIcon`/
|
|
44
47
|
> `trailingIcon`, kept for composing the same spacing outside `Button`
|
|
45
|
-
> itself.
|
|
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
|
|
46
51
|
> tier — see this package's own [ROADMAP.md](./ROADMAP.md) for full
|
|
47
52
|
> per-tier status against all 130 `@gnome-ui/react` components, and the
|
|
48
53
|
> main [ROADMAP.md](../../ROADMAP.md) Priority 3 for the framework
|
|
@@ -182,6 +187,42 @@ yourself, matching the resolved label color (`theme.accentFgColor`,
|
|
|
182
187
|
`theme.destructiveFgColor`, `theme.windowFgColor`, …) if you want them to
|
|
183
188
|
match.
|
|
184
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
|
+
|
|
185
226
|
### Text
|
|
186
227
|
|
|
187
228
|
```tsx
|
|
@@ -1685,6 +1726,41 @@ zero height before `alignItems` gets to stretch anything into it. Caught
|
|
|
1685
1726
|
on-device; it's a no-op in the ordinary case where the container hugs its
|
|
1686
1727
|
content rather than having a fixed height.
|
|
1687
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
|
+
|
|
1688
1764
|
### StatusPage
|
|
1689
1765
|
|
|
1690
1766
|
```tsx
|
|
@@ -2022,6 +2098,38 @@ components means the flip-to-fit placement, the tap-outside dismissal and the
|
|
|
2022
2098
|
— same behaviour as the web version, one level up. The keyboard layer drops
|
|
2023
2099
|
as it does everywhere else here.
|
|
2024
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
|
+
|
|
2025
2133
|
### ColorPicker / ColorSwatch
|
|
2026
2134
|
|
|
2027
2135
|
```tsx
|
|
@@ -2396,6 +2504,51 @@ counter. Six variants (`success`/`warning`/`error`/`new`/`accent`/
|
|
|
2396
2504
|
`neutral`) reuse `Badge`'s exact color-mapping shape, plus a `new` (purple)
|
|
2397
2505
|
variant `Badge` doesn't have.
|
|
2398
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
|
+
|
|
2399
2552
|
### BottomTabBar
|
|
2400
2553
|
|
|
2401
2554
|
```tsx
|
|
@@ -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;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { IconDefinition } from '@gnome-ui/icons';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
4
|
+
export interface WidgetDefinition {
|
|
5
|
+
/** Stable unique identifier. Also the value stored in `value`. */
|
|
6
|
+
id: string;
|
|
7
|
+
/** Row label shown in the picker catalog and above the widget once added. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Optional secondary line shown below `label` in the picker row. */
|
|
10
|
+
description?: string;
|
|
11
|
+
/** Icon shown in the picker row and in the widget's own header once added. */
|
|
12
|
+
icon?: IconDefinition;
|
|
13
|
+
/** Renders the widget's body once it has been added to the manager. */
|
|
14
|
+
render: () => ReactNode;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `@gnome-ui/react`'s `pickerSurface` calls this option `"modal"`, naming it
|
|
18
|
+
* after its own `Modal` component. This package's `Modal` counterpart is
|
|
19
|
+
* `Dialog` (RN's own `Modal` primitive is a different, lower-level thing —
|
|
20
|
+
* see the main `ROADMAP.md`'s note on that exact naming trap), so the option
|
|
21
|
+
* is named after what it actually renders here instead of ported verbatim.
|
|
22
|
+
*/
|
|
23
|
+
export type WidgetManagerPickerSurface = 'dialog' | 'bottomSheet' | 'drawer';
|
|
24
|
+
export interface WidgetManagerProps {
|
|
25
|
+
/** Full catalog of widgets available to add. */
|
|
26
|
+
catalog: WidgetDefinition[];
|
|
27
|
+
/** Controlled list of added widget ids, in display order. */
|
|
28
|
+
value: string[];
|
|
29
|
+
/** Called with the new id list when the user confirms the picker. */
|
|
30
|
+
onChange: (value: string[]) => void;
|
|
31
|
+
/** Header title. */
|
|
32
|
+
title: string;
|
|
33
|
+
/** Icon shown at the leading edge of the header. */
|
|
34
|
+
icon?: IconDefinition;
|
|
35
|
+
/** Which overlay renders the catalog picker. Defaults to `"dialog"`. */
|
|
36
|
+
pickerSurface?: WidgetManagerPickerSurface;
|
|
37
|
+
/** Label for the dashed "add widget" trigger. Defaults to `"Add Widget"`. */
|
|
38
|
+
addTriggerLabel?: string;
|
|
39
|
+
/** Message shown when there are no widgets and not in edit mode. Defaults to `"No widgets added"`. */
|
|
40
|
+
emptyStateLabel?: string;
|
|
41
|
+
/** Heading of the picker overlay. Defaults to `"Widgets"`. */
|
|
42
|
+
pickerTitle?: string;
|
|
43
|
+
/** Label for a catalog row's add action. Defaults to `"Add"`. */
|
|
44
|
+
addLabel?: string;
|
|
45
|
+
/** Label for a catalog row's remove action (already staged). Defaults to `"Remove"`. */
|
|
46
|
+
removeLabel?: string;
|
|
47
|
+
/** Label for the picker's confirm action. Defaults to `"Accept"`. */
|
|
48
|
+
confirmLabel?: string;
|
|
49
|
+
/** Label for the picker's cancel action. Defaults to `"Cancel"`. */
|
|
50
|
+
cancelLabel?: string;
|
|
51
|
+
/** Accessible name of the header's edit-mode toggle button. Defaults to `"Edit widgets"`. */
|
|
52
|
+
editLabel?: string;
|
|
53
|
+
style?: StyleProp<ViewStyle>;
|
|
54
|
+
testID?: string;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Card that manages a controlled collection of "widgets" — pick which ones
|
|
58
|
+
* are visible from a catalog, each rendering its own arbitrary content.
|
|
59
|
+
* Mirrors `@gnome-ui/react`'s own `WidgetManager`.
|
|
60
|
+
*
|
|
61
|
+
* The header's edit button toggles a local `editing` state: in view mode
|
|
62
|
+
* only the added widgets (or an empty-state message) are shown; in edit
|
|
63
|
+
* mode a dashed "add widget" trigger also appears, opening a catalog picker
|
|
64
|
+
* (`pickerSurface`: `"dialog"`, `"bottomSheet"`, or `"drawer"`, each backed
|
|
65
|
+
* by the already-shipped `Dialog`/`BottomSheet`/`Drawer`). Adding/removing
|
|
66
|
+
* is staged inside the picker and only applied — via `onChange` — when the
|
|
67
|
+
* user confirms; canceling or dismissing the picker discards the staging.
|
|
68
|
+
* Widgets can only be removed through the picker, never inline in the card.
|
|
69
|
+
*
|
|
70
|
+
* The catalog list is wrapped in its own `ScrollView` (capped at 360dp)
|
|
71
|
+
* before being handed to whichever picker surface renders it — unlike the
|
|
72
|
+
* web version's `overflow-y: auto` on the `Modal`/`BottomSheet`/`Drawer`
|
|
73
|
+
* body, none of this package's three overlay components scroll their
|
|
74
|
+
* `children` for you, so a long catalog needs that scroll container built
|
|
75
|
+
* in here rather than assumed.
|
|
76
|
+
*
|
|
77
|
+
* `Dialog` already renders its own confirm/cancel action row from a
|
|
78
|
+
* `buttons` array, so only `bottomSheet`/`drawer` need the hand-rolled
|
|
79
|
+
* footer row the web version calls `pickerFooter` — the exact same split
|
|
80
|
+
* the web source itself documents ("Modal uses its own actions").
|
|
81
|
+
*
|
|
82
|
+
* Not ported: `aria-pressed` on the edit toggle — `Button`/`IconButton` set
|
|
83
|
+
* their own internal `accessibilityState={{ disabled }}` on the underlying
|
|
84
|
+
* `Pressable`, and RN merges a spread prop object outright rather than
|
|
85
|
+
* key-by-key, so passing a second `accessibilityState` here would silently
|
|
86
|
+
* replace rather than merge with it (the same `Popover`-trigger clobber
|
|
87
|
+
* `SplitButton` already worked around) — dropped rather than routed around
|
|
88
|
+
* for one decorative toggle-state announcement.
|
|
89
|
+
*
|
|
90
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.WidgetManager.html
|
|
91
|
+
*/
|
|
92
|
+
export declare const WidgetManager: ({ catalog, value, onChange, title, icon, pickerSurface, addTriggerLabel, emptyStateLabel, pickerTitle, addLabel, removeLabel, confirmLabel, cancelLabel, editLabel, style, testID, }: WidgetManagerProps) => import("react/jsx-runtime").JSX.Element;
|