@gnome-ui/react-native 1.0.0 → 1.1.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 +112 -2
- package/dist/components/Banner/Banner.d.ts +50 -0
- package/dist/components/Banner/index.d.ts +2 -0
- package/dist/components/Dialog/Dialog.d.ts +105 -0
- package/dist/components/Dialog/index.d.ts +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +561 -310
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,8 +15,8 @@ React Native component library following the [GNOME Human Interface Guidelines](
|
|
|
15
15
|
> Containers (`Separator`, `Card`, `BoxedList`, `ActionRow`, `HeaderBar`),
|
|
16
16
|
> and Tier 3 Navigation (`Tabs`, `ViewSwitcher`, `Sidebar`, `SearchBar`,
|
|
17
17
|
> `PathBar`) fully ported. Tier 4 Feedback in progress: `Spinner`,
|
|
18
|
-
> `ProgressBar`, `Skeleton`,
|
|
19
|
-
>
|
|
18
|
+
> `ProgressBar`, `Skeleton`, `Toast`/`Toaster`, `Banner`, and `Dialog`
|
|
19
|
+
> shipped — `Tooltip`, `Status Page`, and `AnimatedIcon` remain. Component
|
|
20
20
|
> ports from `@gnome-ui/react` continue tier by tier. See
|
|
21
21
|
> [ROADMAP.md](../../ROADMAP.md) Priority 3.
|
|
22
22
|
|
|
@@ -680,6 +680,116 @@ skips straight to the settled state. RN's `AccessibilityRole` union has no
|
|
|
680
680
|
closest available role, paired with `accessibilityLiveRegion="polite"`
|
|
681
681
|
(Android's live-region API) as the nearest match to `aria-live="polite"`.
|
|
682
682
|
|
|
683
|
+
### Banner
|
|
684
|
+
|
|
685
|
+
```tsx
|
|
686
|
+
import { Banner } from '@gnome-ui/react-native';
|
|
687
|
+
|
|
688
|
+
<Banner variant="info">A new version is available.</Banner>;
|
|
689
|
+
<Banner variant="error" actionLabel="Retry" onAction={() => {}}>
|
|
690
|
+
Sync failed
|
|
691
|
+
</Banner>;
|
|
692
|
+
<Banner variant="success" dismissible onDismiss={() => {}}>
|
|
693
|
+
Changes saved successfully.
|
|
694
|
+
</Banner>;
|
|
695
|
+
```
|
|
696
|
+
|
|
697
|
+
Persistent message strip for the top of a view. `variant` is `"info"`
|
|
698
|
+
(default) | `"warning"` | `"error"` | `"success"`, each mapping to the
|
|
699
|
+
matching `theme.<variant>BgColor`/`<variant>FgColor` token pair. Unlike
|
|
700
|
+
`Toast`, it never auto-dismisses — it stays until the user acts or presses
|
|
701
|
+
the optional dismiss button, so there's no `duration` prop at all.
|
|
702
|
+
|
|
703
|
+
Same accessibility substitution as `Toast`: RN's `AccessibilityRole` union
|
|
704
|
+
has no "status" value (the web version's `role="status"`), so `"alert"` +
|
|
705
|
+
`accessibilityLiveRegion="polite"` stands in for it. The banner itself is a
|
|
706
|
+
plain `View`, not `Pressable` (only its action/dismiss buttons are
|
|
707
|
+
interactive), so — per the `BoxedList` lesson that a bare `View` isn't an
|
|
708
|
+
accessibility element by default — `accessible` is set explicitly alongside
|
|
709
|
+
`accessibilityRole`.
|
|
710
|
+
|
|
711
|
+
The web version's per-variant `:hover`/`:active` background tint on the
|
|
712
|
+
action/dismiss buttons (a light overlay on the darker info/error/success
|
|
713
|
+
backgrounds, a dark one on the light warning background) collapses to a
|
|
714
|
+
single `Pressable`-pressed-state overlay, the same simplification `Toast`
|
|
715
|
+
and `Card` already made for their own press states.
|
|
716
|
+
|
|
717
|
+
### Dialog
|
|
718
|
+
|
|
719
|
+
```tsx
|
|
720
|
+
import { Dialog } from '@gnome-ui/react-native';
|
|
721
|
+
|
|
722
|
+
// Standard
|
|
723
|
+
<Dialog open={open} title="About Sync" onClose={() => setOpen(false)}>
|
|
724
|
+
Files are synced automatically every 15 minutes.
|
|
725
|
+
</Dialog>;
|
|
726
|
+
|
|
727
|
+
// With buttons
|
|
728
|
+
<Dialog
|
|
729
|
+
open={open}
|
|
730
|
+
title="Discard changes?"
|
|
731
|
+
onClose={() => setOpen(false)}
|
|
732
|
+
buttons={[
|
|
733
|
+
{ label: 'Keep editing', onPress: () => setOpen(false) },
|
|
734
|
+
{ label: 'Discard', variant: 'destructive', onPress: () => setOpen(false) },
|
|
735
|
+
]}
|
|
736
|
+
>
|
|
737
|
+
Your changes have not been saved.
|
|
738
|
+
</Dialog>;
|
|
739
|
+
|
|
740
|
+
// Alert — role="alertdialog" + responses/onResponse
|
|
741
|
+
<Dialog
|
|
742
|
+
open={open}
|
|
743
|
+
role="alertdialog"
|
|
744
|
+
title="Delete file?"
|
|
745
|
+
responses={[
|
|
746
|
+
{ id: 'cancel', label: 'Cancel' },
|
|
747
|
+
{ id: 'delete', label: 'Delete', variant: 'destructive' },
|
|
748
|
+
]}
|
|
749
|
+
onResponse={(id) => setOpen(false)}
|
|
750
|
+
>
|
|
751
|
+
This action cannot be undone.
|
|
752
|
+
</Dialog>;
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
Blocking modal dialog. **Standard** takes `title` + `children` + `buttons[]`
|
|
756
|
+
with per-button `onPress`; **Alert** (`role="alertdialog"`) takes
|
|
757
|
+
`responses[]` + a single `onResponse(id)` instead — the same two-API shape
|
|
758
|
+
as `@gnome-ui/react`'s `Dialog`, since `AlertDialog` there is a mode of the
|
|
759
|
+
same component rather than a separate one. `AboutDialog` (a distinct
|
|
760
|
+
`@gnome-ui/react` component, not a `Dialog` variant) has no RN port yet.
|
|
761
|
+
|
|
762
|
+
Built on RN's own `Modal` (`transparent`, `animationType="none"` — the
|
|
763
|
+
entrance is a custom `Animated.timing`) rather than the web version's DOM
|
|
764
|
+
`Portal` + manual focus trap: `Modal` already floats above everything with
|
|
765
|
+
no portal target needed, and already blocks interaction with the screen
|
|
766
|
+
behind it, so there's no `useBodyScrollLock` port. Its `onRequestClose`
|
|
767
|
+
fires on the **Android hardware back button** — the direct analog of the
|
|
768
|
+
web version's document-level Escape listener (iOS has no back button, so
|
|
769
|
+
this is Android-only, matching the platform's own convention). Focus
|
|
770
|
+
trapping (`Tab`/`Shift+Tab` cycling between focusable elements) has no
|
|
771
|
+
port at all — there's no keyboard `Tab` concept in RN's touch-first model,
|
|
772
|
+
the same reasoning that already dropped `TabBar`'s roving-tabindex arrow
|
|
773
|
+
keys.
|
|
774
|
+
|
|
775
|
+
`role` is set via RN's newer, web-aligned `role` prop (not
|
|
776
|
+
`accessibilityRole`) — its `Role` union has real `"dialog"`/`"alertdialog"`
|
|
777
|
+
values, unlike the older `AccessibilityRole` enum `Toast`/`Banner` had to
|
|
778
|
+
substitute `"alert"` into for the web's `role="status"`.
|
|
779
|
+
`accessibilityViewIsModal` (iOS-only) is the closest match to
|
|
780
|
+
`aria-modal="true"`, restricting VoiceOver to the dialog's subtree. The
|
|
781
|
+
dialog card sets `accessible` explicitly (a bare `View` with `role` isn't
|
|
782
|
+
an accessibility element by default — the same `BoxedList` lesson) —
|
|
783
|
+
**this shares the same open, unverified-on-a-real-device accessibility
|
|
784
|
+
question already flagged for `BoxedList`/`TabBar`/`ViewSwitcher`'s
|
|
785
|
+
container-role pattern**: `accessible={true}` on a container may collapse
|
|
786
|
+
its subtree into one opaque VoiceOver stop, which for `Dialog` specifically
|
|
787
|
+
would mean its footer buttons become unreachable via VoiceOver even though
|
|
788
|
+
they're independently `Pressable`. Kept for `getByRole` testability and
|
|
789
|
+
consistency with the established pattern, but this is the component where
|
|
790
|
+
that tradeoff matters most — worth prioritizing for real-device screen
|
|
791
|
+
reader verification before it's treated as settled.
|
|
792
|
+
|
|
683
793
|
## Installation
|
|
684
794
|
|
|
685
795
|
```bash
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewProps, ViewStyle } from 'react-native';
|
|
3
|
+
export type BannerVariant = 'info' | 'warning' | 'error' | 'success';
|
|
4
|
+
export interface BannerProps extends Omit<ViewProps, 'style'> {
|
|
5
|
+
/**
|
|
6
|
+
* Visual emphasis level.
|
|
7
|
+
* - `info` (default) — neutral, accent-colored. Use for tips and notices.
|
|
8
|
+
* - `warning` — yellow. Use for recoverable problems.
|
|
9
|
+
* - `error` — red. Use for failures that need attention.
|
|
10
|
+
* - `success` — green. Use for confirmations.
|
|
11
|
+
*/
|
|
12
|
+
variant?: BannerVariant;
|
|
13
|
+
/** The message text. Keep it short — one or two sentences. */
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
/**
|
|
16
|
+
* Label for the optional action button placed at the trailing end.
|
|
17
|
+
* When provided, `onAction` should also be supplied.
|
|
18
|
+
*/
|
|
19
|
+
actionLabel?: string;
|
|
20
|
+
/** Called when the user presses the action button. */
|
|
21
|
+
onAction?: () => void;
|
|
22
|
+
/**
|
|
23
|
+
* When true a dismiss (×) button is shown at the trailing edge.
|
|
24
|
+
* Provide `onDismiss` to handle removal from the list.
|
|
25
|
+
*/
|
|
26
|
+
dismissible?: boolean;
|
|
27
|
+
/** Called when the user presses the dismiss button. */
|
|
28
|
+
onDismiss?: () => void;
|
|
29
|
+
style?: StyleProp<ViewStyle>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Persistent message strip displayed at the top of a view, following the
|
|
33
|
+
* Adwaita `AdwBanner` pattern. Use for important information that persists
|
|
34
|
+
* until the user acts or explicitly dismisses it — unlike `Toast`, it never
|
|
35
|
+
* auto-dismisses.
|
|
36
|
+
*
|
|
37
|
+
* RN's `AccessibilityRole` union has no "status" value (the web version's
|
|
38
|
+
* `role="status"`); `"alert"` is the closest available role, paired with
|
|
39
|
+
* `accessibilityLiveRegion="polite"` (Android's live-region API) as the
|
|
40
|
+
* nearest match to `aria-live="polite"` — the same substitution `Toast`
|
|
41
|
+
* already established for the identical web role pair. Since the banner
|
|
42
|
+
* itself is a plain `View` (not `Pressable` — only its buttons are
|
|
43
|
+
* interactive), `accessible` is set explicitly alongside `accessibilityRole`,
|
|
44
|
+
* per the `BoxedList` lesson that a bare `View` isn't an accessibility
|
|
45
|
+
* element by default.
|
|
46
|
+
*
|
|
47
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Banner.html
|
|
48
|
+
* @see https://developer.gnome.org/hig/patterns/feedback/banners.html
|
|
49
|
+
*/
|
|
50
|
+
export declare const Banner: ({ variant, children, actionLabel, onAction, dismissible, onDismiss, style, ...viewProps }: BannerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
export type DialogButtonVariant = 'default' | 'suggested' | 'destructive';
|
|
4
|
+
export interface DialogButton {
|
|
5
|
+
/** Button label. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Visual variant. Defaults to `"default"`. */
|
|
8
|
+
variant?: DialogButtonVariant;
|
|
9
|
+
/** Called when the button is pressed. */
|
|
10
|
+
onPress: () => void;
|
|
11
|
+
/** Whether the button is disabled. */
|
|
12
|
+
disabled?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export type AlertDialogResponseVariant = DialogButtonVariant;
|
|
15
|
+
export interface AlertDialogResponse {
|
|
16
|
+
/**
|
|
17
|
+
* Unique identifier returned via `onResponse`.
|
|
18
|
+
* Use `"cancel"` by convention for the dismissive action.
|
|
19
|
+
*/
|
|
20
|
+
id: string;
|
|
21
|
+
/** Button label. */
|
|
22
|
+
label: string;
|
|
23
|
+
/** Visual emphasis. Defaults to `"default"`. */
|
|
24
|
+
variant?: AlertDialogResponseVariant;
|
|
25
|
+
/** Disables the button. */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface DialogProps {
|
|
29
|
+
/** Whether the dialog is visible. */
|
|
30
|
+
open: boolean;
|
|
31
|
+
/** Dialog heading. */
|
|
32
|
+
title?: ReactNode;
|
|
33
|
+
/** Body content. */
|
|
34
|
+
children?: ReactNode;
|
|
35
|
+
/** Action buttons (standard dialog API). */
|
|
36
|
+
buttons?: DialogButton[];
|
|
37
|
+
/** Called on Android back button / backdrop press. */
|
|
38
|
+
onClose?: () => void;
|
|
39
|
+
/** Whether pressing the backdrop closes the dialog. Defaults to `true`. */
|
|
40
|
+
closeOnBackdrop?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Use `"alertdialog"` for confirmations and destructive warnings — screen
|
|
43
|
+
* readers announce it immediately. Defaults to `"dialog"`.
|
|
44
|
+
*
|
|
45
|
+
* When using `role="alertdialog"`, prefer the `responses`/`onResponse` API
|
|
46
|
+
* over `buttons`/`onPress` for semantic clarity.
|
|
47
|
+
*/
|
|
48
|
+
role?: 'dialog' | 'alertdialog';
|
|
49
|
+
/**
|
|
50
|
+
* Response buttons (AlertDialog API). Alternative to `buttons` — each
|
|
51
|
+
* response has a semantic `id` returned via `onResponse`. The Android back
|
|
52
|
+
* button and backdrop press fire the first non-destructive response.
|
|
53
|
+
*/
|
|
54
|
+
responses?: AlertDialogResponse[];
|
|
55
|
+
/**
|
|
56
|
+
* Called with the `id` of the response button pressed.
|
|
57
|
+
* Required when `responses` is provided.
|
|
58
|
+
*/
|
|
59
|
+
onResponse?: (id: string) => void;
|
|
60
|
+
style?: StyleProp<ViewStyle>;
|
|
61
|
+
/** Forwarded to the backdrop — useful for testing. */
|
|
62
|
+
testID?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Blocking modal dialog following the Adwaita pattern.
|
|
66
|
+
*
|
|
67
|
+
* **Standard** — `title` + `children` + `buttons[]`.
|
|
68
|
+
*
|
|
69
|
+
* **Alert** — add `role="alertdialog"` + `responses[]` + `onResponse`.
|
|
70
|
+
* Uses a semantic response id instead of per-button `onPress`. The Android
|
|
71
|
+
* back button / backdrop press fire the first non-destructive response.
|
|
72
|
+
* Mirrors `AdwAlertDialog`.
|
|
73
|
+
*
|
|
74
|
+
* Built on RN's own `Modal` rather than `@gnome-ui/react`'s DOM `Portal` +
|
|
75
|
+
* manual focus trap: `Modal` already floats above everything with no portal
|
|
76
|
+
* target needed, already blocks interaction with the screen behind it (no
|
|
77
|
+
* `useBodyScrollLock` equivalent needed), and its `onRequestClose` fires on
|
|
78
|
+
* the Android hardware back button — the direct analog of the web version's
|
|
79
|
+
* document-level Escape listener. Focus-trapping (`Tab`/`Shift+Tab` cycling)
|
|
80
|
+
* has no port: there is no keyboard `Tab` concept in RN's touch-first model,
|
|
81
|
+
* the same reasoning that already dropped `TabBar`'s roving-tabindex arrow
|
|
82
|
+
* keys.
|
|
83
|
+
*
|
|
84
|
+
* `role` is passed straight through as RN's own `role` prop (not
|
|
85
|
+
* `accessibilityRole`) — RN's newer, web-aligned `Role` union has real
|
|
86
|
+
* `"dialog"`/`"alertdialog"` values, unlike the older `AccessibilityRole`
|
|
87
|
+
* enum `Toast`/`Banner` had to substitute `"alert"` into for the web's
|
|
88
|
+
* `role="status"`. `accessibilityViewIsModal` (iOS) is the closest match to
|
|
89
|
+
* `aria-modal="true"`, restricting VoiceOver to the dialog's subtree.
|
|
90
|
+
*
|
|
91
|
+
* There is no exit animation on either platform — the source CSS only
|
|
92
|
+
* defines entrance keyframes, and `Modal`'s `visible={false}` unmounts
|
|
93
|
+
* immediately, matching the web version returning `null` outright when
|
|
94
|
+
* `!open`. Entrance is an `Animated.timing` fading + scaling + sliding the
|
|
95
|
+
* card in (mirrors `@keyframes dialog-in`) alongside a plain backdrop fade
|
|
96
|
+
* (`@keyframes backdrop-in`), replayed via a `useEffect` keyed on `open`
|
|
97
|
+
* since — unlike `Toast`, which mounts once per instance — the same
|
|
98
|
+
* `Dialog` element toggles `open` repeatedly while `Modal` itself
|
|
99
|
+
* mounts/unmounts internally. `useReducedMotion()` skips straight to the
|
|
100
|
+
* settled state.
|
|
101
|
+
*
|
|
102
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Dialog.html
|
|
103
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.AlertDialog.html
|
|
104
|
+
*/
|
|
105
|
+
export declare const Dialog: ({ open, title, children, buttons, onClose, closeOnBackdrop, role, responses, onResponse, style, testID, }: DialogProps) => import("react/jsx-runtime").JSX.Element;
|