@gnome-ui/react-native 1.2.0 → 1.4.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 +372 -4
- package/dist/components/Avatar/Avatar.d.ts +53 -0
- package/dist/components/Avatar/index.d.ts +2 -0
- package/dist/components/Badge/Badge.d.ts +48 -0
- package/dist/components/Badge/index.d.ts +2 -0
- package/dist/components/BottomSheet/BottomSheet.d.ts +82 -0
- package/dist/components/BottomSheet/index.d.ts +2 -0
- package/dist/components/Divider/Divider.d.ts +36 -0
- package/dist/components/Divider/index.d.ts +2 -0
- package/dist/components/Expander/Expander.d.ts +64 -0
- package/dist/components/Expander/index.d.ts +2 -0
- package/dist/components/Highlight/Highlight.d.ts +42 -0
- package/dist/components/Highlight/index.d.ts +2 -0
- package/dist/components/LevelBar/LevelBar.d.ts +72 -0
- package/dist/components/LevelBar/index.d.ts +2 -0
- package/dist/components/Overlay/Overlay.d.ts +41 -0
- package/dist/components/Overlay/index.d.ts +2 -0
- package/dist/components/Popover/Popover.d.ts +91 -0
- package/dist/components/Popover/index.d.ts +2 -0
- package/dist/components/Slider/Slider.d.ts +56 -0
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/SpinButton/SpinButton.d.ts +57 -0
- package/dist/components/SpinButton/index.d.ts +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +1505 -395
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,10 +18,15 @@ React Native component library following the [GNOME Human Interface Guidelines](
|
|
|
18
18
|
> `Skeleton`, `Toast`/`Toaster`, `Banner`, `Dialog`, `Tooltip`, and
|
|
19
19
|
> `AnimatedIcon` (which brought a new `Icon` component along with it, as its
|
|
20
20
|
> own public component) shipped — `Status Page` skipped for now. Tier 5
|
|
21
|
-
> Advanced Controls
|
|
22
|
-
> `
|
|
23
|
-
>
|
|
24
|
-
>
|
|
21
|
+
> Advanced Controls fully ported: `Dropdown`, `Slider`, `SpinButton`,
|
|
22
|
+
> `Avatar`, `Badge`, and `Popover`. Beyond Tier 5, `BottomSheet` (Tier 14)
|
|
23
|
+
> and `Overlay`/`LevelBar`/`Expander`/`Divider`/`Highlight` (Tier 20) also
|
|
24
|
+
> shipped. Component ports from
|
|
25
|
+
> `@gnome-ui/react` continue tier by tier — see this package's own
|
|
26
|
+
> [ROADMAP.md](./ROADMAP.md) for full
|
|
27
|
+
> per-tier status against all 130 `@gnome-ui/react` components, and the
|
|
28
|
+
> main [ROADMAP.md](../../ROADMAP.md) Priority 3 for the framework
|
|
29
|
+
> expansion this package belongs to.
|
|
25
30
|
|
|
26
31
|
## How it works
|
|
27
32
|
|
|
@@ -923,6 +928,369 @@ Selection is by direct tap only. `role="combobox"` on the trigger ports
|
|
|
923
928
|
`role="list"` instead — the same closest-available substitution `BoxedList`
|
|
924
929
|
already established for a plain list container.
|
|
925
930
|
|
|
931
|
+
### Slider
|
|
932
|
+
|
|
933
|
+
```tsx
|
|
934
|
+
import { Slider } from '@gnome-ui/react-native';
|
|
935
|
+
|
|
936
|
+
<Slider
|
|
937
|
+
value={volume}
|
|
938
|
+
onChange={setVolume}
|
|
939
|
+
accessibilityLabel="Volume"
|
|
940
|
+
marks={[
|
|
941
|
+
{ value: 0, label: 'Min' },
|
|
942
|
+
{ value: 100, label: 'Max' },
|
|
943
|
+
]}
|
|
944
|
+
/>;
|
|
945
|
+
```
|
|
946
|
+
|
|
947
|
+
Draggable range control following the Adwaita `GtkScale` pattern, mirroring
|
|
948
|
+
`@gnome-ui/react`'s `Slider`.
|
|
949
|
+
|
|
950
|
+
Touch drag is handled with RN's own `PanResponder` (this package's first use
|
|
951
|
+
of it) reading each touch event's `locationX` — the position relative to the
|
|
952
|
+
track view itself, recalculated by RN on every touch/move — so no
|
|
953
|
+
`measureInWindow` round-trip is needed at all, unlike `Tooltip`/`Dropdown`'s
|
|
954
|
+
trigger-rect measurement. `min`/`max`/`step` clamping and snapping is ported
|
|
955
|
+
verbatim from the web version's pure-JS math.
|
|
956
|
+
|
|
957
|
+
The web version's keyboard interaction (← / → one step, Page Up/Down ten
|
|
958
|
+
steps, Home/End to the bounds) has no RN equivalent — a touch-first device
|
|
959
|
+
has no keyboard driving those keys. Rather than dropping value-adjustment
|
|
960
|
+
accessibility entirely (the reasoning that dropped `Dropdown`'s/`TabBar`'s
|
|
961
|
+
keyboard nav), `accessibilityRole="adjustable"` +
|
|
962
|
+
`onAccessibilityAction`/`accessibilityActions` wires up the "increment"/
|
|
963
|
+
"decrement" actions VoiceOver's swipe-up/down and TalkBack's local-context
|
|
964
|
+
menu generate for an adjustable element — the real native analog of
|
|
965
|
+
keyboard stepping, one step per action. The bigger Page Up/Down and
|
|
966
|
+
Home/End jumps have no equivalent screen-reader gesture on either platform,
|
|
967
|
+
so only single-step adjustment is ported.
|
|
968
|
+
|
|
969
|
+
RN's `transform` only accepts pixel offsets, unlike the CSS `%` units the
|
|
970
|
+
web version's `left: X%; transform: translate(-50%, -50%)` thumb/tick
|
|
971
|
+
centering trick needs — so those are positioned with a plain pixel `left`
|
|
972
|
+
computed from the track's `onLayout`-measured width instead. Mark labels
|
|
973
|
+
use a different trick, since (unlike the thumb/ticks) their own rendered
|
|
974
|
+
width isn't a known constant: a zero-width `View` with
|
|
975
|
+
`alignItems: 'center'` at the mark's percentage `left` lets Yoga center the
|
|
976
|
+
`Text` child around that point regardless of how wide the label renders,
|
|
977
|
+
with no measurement needed.
|
|
978
|
+
|
|
979
|
+
### SpinButton
|
|
980
|
+
|
|
981
|
+
```tsx
|
|
982
|
+
import { SpinButton } from '@gnome-ui/react-native';
|
|
983
|
+
|
|
984
|
+
<SpinButton value={quantity} onChange={setQuantity} min={0} max={10} accessibilityLabel="Quantity" />;
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
Numeric −/+ stepper following the Adwaita `GtkSpinButton` pattern, mirroring
|
|
988
|
+
`@gnome-ui/react`'s `SpinButton`. The `min`/`max`/`step`/`decimals`/`wrap`/
|
|
989
|
+
`format` clamp-and-format math ports verbatim (pure JS, no DOM involved).
|
|
990
|
+
|
|
991
|
+
The primary interaction is tapping the visible −/+ buttons, same as a
|
|
992
|
+
sighted mouse user on the web version. The web version's keyboard
|
|
993
|
+
interaction (↑/↓ one step, Page Up/Down ten steps, Home/End to bounds) has
|
|
994
|
+
no RN equivalent — a touch-first device has no keyboard to drive it, the
|
|
995
|
+
same reasoning `Slider` already applied. Rather than dropping value
|
|
996
|
+
adjustment accessibility entirely, single-step increment/decrement reuses
|
|
997
|
+
`Slider`'s exact `accessibilityRole="adjustable"` +
|
|
998
|
+
`onAccessibilityAction`/`accessibilityActions` recipe (VoiceOver's
|
|
999
|
+
swipe-up/down, TalkBack's local-context menu) — the bigger Page Up/Down and
|
|
1000
|
+
Home/End jumps have no equivalent screen-reader gesture on either platform,
|
|
1001
|
+
so those alone are dropped, same as `Slider`. The visible −/+ buttons and
|
|
1002
|
+
value text are hidden from the accessibility tree
|
|
1003
|
+
(`accessibilityElementsHidden`/`importantForAccessibility="no"`, mirroring
|
|
1004
|
+
the web version's `aria-hidden`/`tabIndex={-1}` on both `<button>`s and the
|
|
1005
|
+
value `<span>`) so a screen reader user gets one adjustable stop, not three.
|
|
1006
|
+
|
|
1007
|
+
### Avatar
|
|
1008
|
+
|
|
1009
|
+
```tsx
|
|
1010
|
+
import { Avatar } from '@gnome-ui/react-native';
|
|
1011
|
+
|
|
1012
|
+
<Avatar name="Grace Hopper" size="lg" />
|
|
1013
|
+
<Avatar src="https://example.com/alice.jpg" alt="Alice's profile photo" />;
|
|
1014
|
+
```
|
|
1015
|
+
|
|
1016
|
+
Circular avatar with image or initials fallback, mirroring `@gnome-ui/react`'s
|
|
1017
|
+
`Avatar`. The color-hash and initials-extraction math ports verbatim (pure
|
|
1018
|
+
JS, no DOM involved).
|
|
1019
|
+
|
|
1020
|
+
The outer container carries `role="img"` + `accessibilityLabel` — RN's newer
|
|
1021
|
+
web-aligned `Role` union has an `"img"` value, a direct 1:1 port of the web
|
|
1022
|
+
version's `role="img"`, no substitution needed (same as `ProgressBar`'s
|
|
1023
|
+
`role="progressbar"`). The image/initials underneath are hidden from the
|
|
1024
|
+
accessibility tree, mirroring the web version's `aria-hidden` on both, so a
|
|
1025
|
+
screen reader gets one stop, not two — same reasoning as `SpinButton`'s
|
|
1026
|
+
hidden −/+ buttons.
|
|
1027
|
+
|
|
1028
|
+
The web CSS's `box-shadow: inset 0 0 0 1px …` ring becomes a real 1px
|
|
1029
|
+
`borderWidth`/`borderColor` here (RN has no inset shadow) — the same
|
|
1030
|
+
substitution `Slider`'s thumb border already used for a ring effect.
|
|
1031
|
+
|
|
1032
|
+
### Badge
|
|
1033
|
+
|
|
1034
|
+
```tsx
|
|
1035
|
+
import { Avatar, Badge } from '@gnome-ui/react-native';
|
|
1036
|
+
|
|
1037
|
+
<Badge variant="error" anchor={<Avatar name="Alice Bob" />}>3</Badge>
|
|
1038
|
+
<Badge dot variant="success" />;
|
|
1039
|
+
```
|
|
1040
|
+
|
|
1041
|
+
Counter or status indicator, optionally overlaid on another element,
|
|
1042
|
+
mirroring `@gnome-ui/react`'s `Badge`. `children` renders as a themed `Text`
|
|
1043
|
+
label when it's a string or number (the common case — counts and short
|
|
1044
|
+
text); any other node renders as-is, the same convention `Button`'s
|
|
1045
|
+
`children` already established.
|
|
1046
|
+
|
|
1047
|
+
The web CSS's `box-shadow: 0 0 0 2px var(--gnome-window-bg-color)` ring
|
|
1048
|
+
(always present, separating the badge from whatever's behind it) has no RN
|
|
1049
|
+
equivalent that avoids affecting layout — RN's `border*` shrinks the content
|
|
1050
|
+
box instead of drawing outside it. Reproduced instead with an outer wrapping
|
|
1051
|
+
`View` (2px padding, `theme.windowBgColor` background, pill radius) around
|
|
1052
|
+
the actual colored badge, so the ring appears to spread outward exactly like
|
|
1053
|
+
the web version's non-blurred shadow, without eating into the badge's own
|
|
1054
|
+
text padding.
|
|
1055
|
+
|
|
1056
|
+
### Popover
|
|
1057
|
+
|
|
1058
|
+
```tsx
|
|
1059
|
+
import { Button, Popover, Text } from '@gnome-ui/react-native';
|
|
1060
|
+
|
|
1061
|
+
<Popover content={<Text>Rich content here</Text>}>
|
|
1062
|
+
<Button>Open</Button>
|
|
1063
|
+
</Popover>;
|
|
1064
|
+
```
|
|
1065
|
+
|
|
1066
|
+
Floating panel anchored to a trigger element, following the Adwaita
|
|
1067
|
+
`GtkPopover` pattern, mirroring `@gnome-ui/react`'s `Popover`. Unlike
|
|
1068
|
+
`Tooltip`, it can hold rich interactive content (buttons, links, forms).
|
|
1069
|
+
|
|
1070
|
+
Reuses this package's own established pieces rather than re-deriving them:
|
|
1071
|
+
`Tooltip`'s `cloneElement`-onto-an-arbitrary-trigger architecture and
|
|
1072
|
+
4-placement fallback-cascade positioning (no arrow-offset-shift-when-clamped
|
|
1073
|
+
— same simplification `Tooltip` already accepted), and `Dropdown`'s
|
|
1074
|
+
toggle-on-press + full-screen backdrop `Pressable` that closes on an outside
|
|
1075
|
+
tap plus reduced-motion fade-in.
|
|
1076
|
+
|
|
1077
|
+
**Deliberate divergence from `Dropdown`'s backdrop structure**: `Dropdown`
|
|
1078
|
+
nests its panel directly inside the backdrop `Pressable` and gets away with
|
|
1079
|
+
it because almost every pixel of its panel is itself a `Pressable` option
|
|
1080
|
+
row, which claims the touch responder before it can bubble to the backdrop.
|
|
1081
|
+
A popover's `content` is arbitrary — likely to have inert padding/whitespace
|
|
1082
|
+
with no `Pressable` of its own — so nesting the same way would let a tap on
|
|
1083
|
+
inert panel space fall through to the backdrop and close the popover, unlike
|
|
1084
|
+
the web version's `.contains()` check (which never closes on *any* tap
|
|
1085
|
+
inside the panel). Fixed with `onStartShouldSetResponder={() => true}` on
|
|
1086
|
+
the panel itself: it claims the touch responder for any touch RN's
|
|
1087
|
+
negotiation hasn't already given to a deeper `Pressable` inside `content`,
|
|
1088
|
+
without making the panel itself behave like a button.
|
|
1089
|
+
|
|
1090
|
+
`BackHandler`'s `hardwareBackPress` (wired the same way `Dialog` already
|
|
1091
|
+
does) is the Android analog of the web version's document-level Escape
|
|
1092
|
+
listener. Focus-trapping and focus-restore-on-close have no port — no DOM
|
|
1093
|
+
`document.activeElement`/`querySelector` equivalent exists in RN, the same
|
|
1094
|
+
gap already present in `Dialog`/`Tooltip`/`Dropdown`.
|
|
1095
|
+
|
|
1096
|
+
The web version's rotated-square-with-matching-background arrow is replaced
|
|
1097
|
+
with `Tooltip`'s simpler transparent-border-triangle technique — the same
|
|
1098
|
+
visual affordance, a much simpler RN-native primitive.
|
|
1099
|
+
|
|
1100
|
+
### BottomSheet
|
|
1101
|
+
|
|
1102
|
+
```tsx
|
|
1103
|
+
import { BottomSheet, Button } from '@gnome-ui/react-native';
|
|
1104
|
+
|
|
1105
|
+
<Button onPress={() => setOpen(true)}>Open</Button>
|
|
1106
|
+
<BottomSheet open={open} title="Options" onClose={() => setOpen(false)}>
|
|
1107
|
+
<Text>Rich content here</Text>
|
|
1108
|
+
</BottomSheet>;
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
Slide-up panel that overlays content from the bottom edge, mirroring
|
|
1112
|
+
`AdwBottomSheet` (libadwaita 1.6+) and `@gnome-ui/react`'s `BottomSheet`.
|
|
1113
|
+
Reuses `Dialog`'s backdrop-opacity-on-an-`AnimatedPressable` +
|
|
1114
|
+
no-op-`Pressable`-around-the-card recipe, and `BackHandler`'s
|
|
1115
|
+
`hardwareBackPress` as the Android analog of the web version's Escape
|
|
1116
|
+
listener.
|
|
1117
|
+
|
|
1118
|
+
**Real drag-to-dismiss**, not a fixed-panel simplification: `PanResponder`
|
|
1119
|
+
(the same core API `Slider` already proved handles a threshold gesture)
|
|
1120
|
+
drives a single `Animated.Value` shared with the entrance/exit animation —
|
|
1121
|
+
dragging the handle bar past 150 px (same constant as the web version)
|
|
1122
|
+
requests a close; releasing short of that springs back to `0`. A real
|
|
1123
|
+
slide-up needs the sheet's own height first (RN's `transform` has no
|
|
1124
|
+
percentage-of-self units, the same `Slider`/`Avatar` pitfall) — the sheet
|
|
1125
|
+
renders once off-screen, measured via `onLayout`, before animating in.
|
|
1126
|
+
|
|
1127
|
+
**A real, timed exit animation, unlike `Dialog`**: `Dialog`'s web source has
|
|
1128
|
+
no exit keyframes at all, but `BottomSheet`'s does — ported with a local
|
|
1129
|
+
`visible` state that lags one animation behind the `open` prop, flipping to
|
|
1130
|
+
`false` only in the exit `Animated.timing`'s own completion callback.
|
|
1131
|
+
|
|
1132
|
+
The web version's `backdrop-filter: blur(4px)` has no port (no native blur
|
|
1133
|
+
view dependency, same reasoning that dropped `Sidebar`'s blurred variant),
|
|
1134
|
+
and `useBodyScrollLock` needs no RN equivalent (`Modal` already blocks all
|
|
1135
|
+
background interaction). `children`, when a plain string, is wrapped in
|
|
1136
|
+
`Text` before rendering — RN throws if a raw string is a `View`'s child,
|
|
1137
|
+
unlike the web version's plain `<div>{children}</div>`.
|
|
1138
|
+
|
|
1139
|
+
### Overlay
|
|
1140
|
+
|
|
1141
|
+
```tsx
|
|
1142
|
+
import { Overlay, Button } from '@gnome-ui/react-native';
|
|
1143
|
+
|
|
1144
|
+
<Button onPress={() => setOpen(true)}>Open</Button>
|
|
1145
|
+
<Overlay open={open} onDismiss={() => setOpen(false)}>
|
|
1146
|
+
<YourOwnCard />
|
|
1147
|
+
</Overlay>;
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
Standalone backdrop/scrim layer with a fade transition and
|
|
1151
|
+
press-to-dismiss — the shared building block behind `Dialog`, `Dropdown`,
|
|
1152
|
+
`Popover`, and `BottomSheet`'s own backdrops, extracted here for building
|
|
1153
|
+
custom overlay UI, mirroring `@gnome-ui/react`'s `Overlay`.
|
|
1154
|
+
|
|
1155
|
+
Deliberately minimal, same as the web version: no focus trap, no
|
|
1156
|
+
`BackHandler`/Escape handling, no `role` — use `Dialog`/`Popover`/
|
|
1157
|
+
`BottomSheet` directly when you need those. Reuses `Dialog`'s exact
|
|
1158
|
+
backdrop recipe (`AnimatedPressable` + a no-op `Pressable` wrapping
|
|
1159
|
+
`children`, so a tap on your own content never bubbles to the backdrop and
|
|
1160
|
+
dismisses it) and `BottomSheet`'s real, timed exit animation technique — a
|
|
1161
|
+
local `visible` state that lags the `open` prop by one `Animated.timing`,
|
|
1162
|
+
flipping to `false` only in that animation's own completion callback.
|
|
1163
|
+
|
|
1164
|
+
**Not retrofitted into `Dialog`/`Dropdown`/`Popover`/`BottomSheet`** — each
|
|
1165
|
+
already ships and is fully tested with its own inline copy of this same
|
|
1166
|
+
backdrop pattern (with small per-component differences: `Popover` claims
|
|
1167
|
+
the touch responder differently than the no-op-`Pressable` wrapper the
|
|
1168
|
+
others use). Extracting `Overlay` as a new standalone primitive was the
|
|
1169
|
+
scoped ask; retrofitting four already-shipped components to share it is a
|
|
1170
|
+
separate, riskier refactor this turn didn't take on.
|
|
1171
|
+
|
|
1172
|
+
### LevelBar
|
|
1173
|
+
|
|
1174
|
+
```tsx
|
|
1175
|
+
import { LevelBar } from '@gnome-ui/react-native';
|
|
1176
|
+
|
|
1177
|
+
<LevelBar value={0.15} low={0.25} high={0.75} accessibilityLabel="Battery" />
|
|
1178
|
+
<LevelBar value={0.6} discrete numBlocks={5} accessibilityLabel="Signal strength" />;
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
Discrete level indicator with color-coded low/high offset zones, mirroring
|
|
1182
|
+
`GtkLevelBar` and `@gnome-ui/react`'s `LevelBar`. Use for a gauge/
|
|
1183
|
+
measurement display (disk usage, battery, signal strength) — not for task
|
|
1184
|
+
progress (`ProgressBar`) or a proportional category breakdown
|
|
1185
|
+
(`SegmentedBar`).
|
|
1186
|
+
|
|
1187
|
+
The continuous fill reuses `ProgressBar`'s exact animation technique rather
|
|
1188
|
+
than animating `width` directly: a fixed `width: '100%'` fill with
|
|
1189
|
+
`transformOrigin: 'left'` and an animated `transform: [{ scaleX }]`, so the
|
|
1190
|
+
whole thing runs on `useNativeDriver: true` — a JS-driven `width` animation
|
|
1191
|
+
schedules its next frame via a plain `setTimeout` that routinely fires
|
|
1192
|
+
after a test's `render()` returns but before unmount, producing a spurious
|
|
1193
|
+
"update not wrapped in act()" warning, the same reasoning `ProgressBar`'s
|
|
1194
|
+
own docstring documents. `useReducedMotion()` mirrors `ProgressBar`'s
|
|
1195
|
+
determinate behavior (duration drops to `0`, an immediate jump).
|
|
1196
|
+
|
|
1197
|
+
Discrete mode's per-block color transition has no port — a value change is
|
|
1198
|
+
a plain, unanimated color swap per block, a decorative nicety rather than a
|
|
1199
|
+
behavior gap. `role="meter"` ports 1:1 from RN's newer web-aligned `Role`
|
|
1200
|
+
union (unlike `AccessibilityRole`, which has no `"meter"` value at all).
|
|
1201
|
+
|
|
1202
|
+
### Expander
|
|
1203
|
+
|
|
1204
|
+
```tsx
|
|
1205
|
+
import { Expander } from '@gnome-ui/react-native';
|
|
1206
|
+
|
|
1207
|
+
<Expander label="Show advanced options">
|
|
1208
|
+
<TextField label="Custom endpoint" />
|
|
1209
|
+
</Expander>
|
|
1210
|
+
```
|
|
1211
|
+
|
|
1212
|
+
Standalone disclosure triangle + collapsible content, mirroring `GtkExpander`
|
|
1213
|
+
and `@gnome-ui/react`'s `Expander`. A bare, unstyled counterpart to
|
|
1214
|
+
`ExpanderRow`; use it outside a settings-row context (e.g. "Show advanced
|
|
1215
|
+
options" in a form, or "Show details" under an error message).
|
|
1216
|
+
|
|
1217
|
+
The web version clips the panel with a CSS grid-height animation and rides
|
|
1218
|
+
the content's `padding-top` on a second, separate transition, so a collapsed
|
|
1219
|
+
expander doesn't reserve blank space for hidden padding. RN has no CSS grid
|
|
1220
|
+
to lean on, so the panel is a single `Animated.View` whose numeric `height`
|
|
1221
|
+
is driven directly (`useNativeDriver: false`, the same accepted trade-off
|
|
1222
|
+
`Checkbox`/`RadioButton`/`Switch`/`AnimatedIcon` already make for
|
|
1223
|
+
non-transform properties) — since the content's own `onLayout` measurement
|
|
1224
|
+
already includes its `paddingTop`, one animated height reproduces the web
|
|
1225
|
+
version's two-transition result. Content stays mounted while collapsed
|
|
1226
|
+
(`accessibilityElementsHidden`/`importantForAccessibility="no"`, the same
|
|
1227
|
+
substitution for the web's `inert` used elsewhere in this package), and on
|
|
1228
|
+
first mount with `defaultExpanded` the panel briefly renders at its natural,
|
|
1229
|
+
unmeasured height so the initial reveal doesn't pop once layout resolves.
|
|
1230
|
+
|
|
1231
|
+
The chevron is `PanEnd` (GNOME's own `pan-end-symbolic` disclosure triangle)
|
|
1232
|
+
rotating 0deg → 90deg on an `Animated.Value`, the same `interpolate`-to-
|
|
1233
|
+
`rotate` recipe `Spinner` uses for its own spin.
|
|
1234
|
+
|
|
1235
|
+
### Divider
|
|
1236
|
+
|
|
1237
|
+
```tsx
|
|
1238
|
+
import { Divider } from '@gnome-ui/react-native';
|
|
1239
|
+
|
|
1240
|
+
<Divider>OR</Divider>
|
|
1241
|
+
<Divider>Continue with</Divider>
|
|
1242
|
+
<Divider />
|
|
1243
|
+
```
|
|
1244
|
+
|
|
1245
|
+
Horizontal rule with an optional centered label — the common auth/login-form
|
|
1246
|
+
pattern ("Sign in" / **OR** / "Continue with Google"). Mirrors
|
|
1247
|
+
`@gnome-ui/react`'s `Divider`. For a bare dividing line with no label, use
|
|
1248
|
+
`Separator` instead — it also supports a vertical orientation, which
|
|
1249
|
+
`Divider` does not.
|
|
1250
|
+
|
|
1251
|
+
`role="separator"` ports 1:1 from RN's newer web-aligned `Role` union (the
|
|
1252
|
+
same one `Avatar`/`Badge`/`LevelBar` already reach for) — unlike
|
|
1253
|
+
`Separator`'s own `accessible={false}`, since a labelled `Divider` ("OR") is
|
|
1254
|
+
exactly the kind of content a screen reader user needs read aloud, rather
|
|
1255
|
+
than a purely decorative line. The label reuses `Text`'s
|
|
1256
|
+
`variant="caption" color="dim"` verbatim, which already resolves to the same
|
|
1257
|
+
font-size/weight/dim-opacity the web version's `.label` class hard-codes.
|
|
1258
|
+
|
|
1259
|
+
### Highlight
|
|
1260
|
+
|
|
1261
|
+
```tsx
|
|
1262
|
+
import { Highlight } from '@gnome-ui/react-native';
|
|
1263
|
+
|
|
1264
|
+
<Highlight text="Preferences for accessibility" query="access" />
|
|
1265
|
+
<Highlight text="The quick brown fox" query={['quick', 'fox']} />
|
|
1266
|
+
```
|
|
1267
|
+
|
|
1268
|
+
Wraps every occurrence of `query` within `text` in a highlighted inline run
|
|
1269
|
+
— mirrors `@gnome-ui/react`'s `Highlight`, which wraps matches in a `<mark>`.
|
|
1270
|
+
Pairs with `SearchBar`'s suggestion list and any filterable list to show
|
|
1271
|
+
users which part of a result matched what they typed.
|
|
1272
|
+
|
|
1273
|
+
The outer span is the themed `Text` component (so callers get the same
|
|
1274
|
+
`variant`/`color` API as everywhere else), but each matched run is a plain,
|
|
1275
|
+
unthemed RN `Text` carrying only the highlight's own overrides — RN's `Text`
|
|
1276
|
+
is the one primitive that inherits ambient `fontSize`/`color`/`fontFamily`
|
|
1277
|
+
from a parent `Text` when nested, the same way the web version's `<mark>`
|
|
1278
|
+
inherits from its surrounding text and only overrides
|
|
1279
|
+
`background-color`/`font-weight`. Reaching for the themed `Text` for the
|
|
1280
|
+
marked runs too would reset them to its own default `variant="body"` sizing
|
|
1281
|
+
instead of inheriting whatever variant the caller chose for the whole
|
|
1282
|
+
string.
|
|
1283
|
+
|
|
1284
|
+
The web version's translucent `color-mix(in srgb, accent 30%, transparent)`
|
|
1285
|
+
background has no RN equivalent (`color-mix` is CSS-only) — resolved to a
|
|
1286
|
+
literal 8-digit `#RRGGBBAA` hex instead, since `accentBgColor` is always a
|
|
1287
|
+
plain 6-digit hex across all four theme variants. `border-radius` on the
|
|
1288
|
+
`<mark>` has no reliable port either: RN only paints `backgroundColor` on an
|
|
1289
|
+
inline (nested) `Text` run, not `borderRadius` — a decorative nicety
|
|
1290
|
+
dropped, not a behavior gap. `prefers-contrast: more`'s solid-background/
|
|
1291
|
+
white-text swap ports via `useResolvedContrast()`, the same hook `Button`
|
|
1292
|
+
already uses for its own high-contrast branching.
|
|
1293
|
+
|
|
926
1294
|
## Installation
|
|
927
1295
|
|
|
928
1296
|
```bash
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
export type AvatarSize = 'sm' | 'md' | 'lg' | 'xl';
|
|
3
|
+
/**
|
|
4
|
+
* Named color palette for the initials fallback.
|
|
5
|
+
* Mirrors libadwaita's avatar color set.
|
|
6
|
+
*/
|
|
7
|
+
export type AvatarColor = 'blue' | 'green' | 'yellow' | 'orange' | 'red' | 'purple' | 'brown' | 'teal' | 'slate';
|
|
8
|
+
export interface AvatarProps {
|
|
9
|
+
/**
|
|
10
|
+
* Full name used to generate initials and — when `color` is omitted —
|
|
11
|
+
* to deterministically pick a background color.
|
|
12
|
+
*/
|
|
13
|
+
name?: string;
|
|
14
|
+
/** Image URL. When provided the initials fallback is hidden. */
|
|
15
|
+
src?: string;
|
|
16
|
+
/** Accessible label. Defaults to `name`, then `"Avatar"`. */
|
|
17
|
+
alt?: string;
|
|
18
|
+
/** Size of the avatar. Defaults to `"md"`. */
|
|
19
|
+
size?: AvatarSize;
|
|
20
|
+
/**
|
|
21
|
+
* Override the auto-derived background color for the initials fallback.
|
|
22
|
+
* When omitted a color is derived from `name` via a stable hash.
|
|
23
|
+
*/
|
|
24
|
+
color?: AvatarColor;
|
|
25
|
+
style?: StyleProp<ViewStyle>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Circular avatar with image or initials fallback.
|
|
29
|
+
*
|
|
30
|
+
* Follows the Adwaita `AdwAvatar` pattern — deterministic color from name,
|
|
31
|
+
* up to two initials when no image is supplied. Rebuilt with `View`/`Image`/
|
|
32
|
+
* `Text` rather than ported from `@gnome-ui/react`'s DOM-based JSX, but
|
|
33
|
+
* mirrors its prop API and color-hash/initials math (pure JS, ported
|
|
34
|
+
* verbatim).
|
|
35
|
+
*
|
|
36
|
+
* The outer container carries `role="img"` + `accessibilityLabel` — RN's
|
|
37
|
+
* newer web-aligned `Role` union has an `"img"` value, a direct 1:1 port of
|
|
38
|
+
* the web version's `role="img"`, no substitution needed (same as
|
|
39
|
+
* `ProgressBar`'s `role="progressbar"`). The image/initials underneath are
|
|
40
|
+
* hidden from the accessibility tree
|
|
41
|
+
* (`accessibilityElementsHidden`/`importantForAccessibility="no"`, mirroring
|
|
42
|
+
* the web version's `aria-hidden` on both), so a screen reader gets one
|
|
43
|
+
* stop, not two — same reasoning as `SpinButton`'s hidden −/+ buttons.
|
|
44
|
+
*
|
|
45
|
+
* The web CSS's `box-shadow: inset 0 0 0 1px …` ring (drawn on top, doesn't
|
|
46
|
+
* affect layout) becomes a real 1px `borderWidth`/`borderColor` here (RN has
|
|
47
|
+
* no inset shadow) — the same substitution `Slider`'s thumb border already
|
|
48
|
+
* used for a ring effect, at the cost of a negligible 1px content-box inset
|
|
49
|
+
* the web version doesn't have.
|
|
50
|
+
*
|
|
51
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Avatar.html
|
|
52
|
+
*/
|
|
53
|
+
export declare const Avatar: ({ name, src, alt, size, color, style }: AvatarProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
export type BadgeVariant = 'accent' | 'success' | 'warning' | 'error' | 'neutral';
|
|
4
|
+
export interface BadgeProps {
|
|
5
|
+
/**
|
|
6
|
+
* Visual style. Defaults to `"accent"`.
|
|
7
|
+
* - `accent` — blue, for counts and highlights.
|
|
8
|
+
* - `success` — green, for positive status.
|
|
9
|
+
* - `warning` — yellow, for cautionary status.
|
|
10
|
+
* - `error` — red, for failures or urgent counts.
|
|
11
|
+
* - `neutral` — gray, for inactive or secondary counts.
|
|
12
|
+
*/
|
|
13
|
+
variant?: BadgeVariant;
|
|
14
|
+
/**
|
|
15
|
+
* When true, renders a small dot with no label — used for unread/online indicators.
|
|
16
|
+
* The `children` are ignored in dot mode.
|
|
17
|
+
*/
|
|
18
|
+
dot?: boolean;
|
|
19
|
+
/** Number or short text to display. Keep to 1–3 characters. String/number render as a themed label; other nodes render as-is. */
|
|
20
|
+
children?: ReactNode;
|
|
21
|
+
/**
|
|
22
|
+
* When provided, the badge is positioned over this child element at the
|
|
23
|
+
* top-right corner.
|
|
24
|
+
*/
|
|
25
|
+
anchor?: ReactNode;
|
|
26
|
+
style?: StyleProp<ViewStyle>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Counter or status indicator, optionally overlaid on another element.
|
|
30
|
+
*
|
|
31
|
+
* Rebuilt with `View`/`Text` rather than ported from `@gnome-ui/react`'s
|
|
32
|
+
* DOM-based JSX, but mirrors its prop API. `children` renders as a themed
|
|
33
|
+
* `Text` label when it's a string or number (the common case — counts and
|
|
34
|
+
* short text); any other node renders as-is, the same convention `Button`'s
|
|
35
|
+
* `children` already established.
|
|
36
|
+
*
|
|
37
|
+
* The web CSS's `box-shadow: 0 0 0 2px var(--gnome-window-bg-color)` ring
|
|
38
|
+
* (always present, separating the badge from whatever's behind it) has no
|
|
39
|
+
* RN equivalent that avoids affecting layout — RN's `border*` shrinks the
|
|
40
|
+
* content box instead of drawing outside it. Reproduced instead with an
|
|
41
|
+
* outer wrapping `View` (2px padding, `theme.windowBgColor` background,
|
|
42
|
+
* pill radius) around the actual colored badge, so the ring appears to
|
|
43
|
+
* spread outward exactly like the web version's non-blurred shadow, without
|
|
44
|
+
* eating into the badge's own text padding.
|
|
45
|
+
*
|
|
46
|
+
* @see https://developer.gnome.org/hig/patterns/feedback/badges.html
|
|
47
|
+
*/
|
|
48
|
+
export declare const Badge: ({ variant, dot, children, anchor, style }: BadgeProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
export interface BottomSheetProps {
|
|
4
|
+
/** Whether the sheet is visible. */
|
|
5
|
+
open: boolean;
|
|
6
|
+
/** Optional heading shown below the drag handle. */
|
|
7
|
+
title?: ReactNode;
|
|
8
|
+
children?: ReactNode;
|
|
9
|
+
/** Called when the user dismisses the sheet (backdrop press, Android back button, or drag down). */
|
|
10
|
+
onClose?: () => void;
|
|
11
|
+
/** Whether pressing the backdrop closes the sheet. Defaults to `true`. */
|
|
12
|
+
closeOnBackdrop?: boolean;
|
|
13
|
+
style?: StyleProp<ViewStyle>;
|
|
14
|
+
/** Applied to the backdrop, mirroring `Dialog`'s own `testID`. */
|
|
15
|
+
testID?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Slide-up panel that overlays content from the bottom edge, mirroring
|
|
19
|
+
* `AdwBottomSheet` (libadwaita 1.6+) and `@gnome-ui/react`'s `BottomSheet`.
|
|
20
|
+
*
|
|
21
|
+
* Rebuilt with `View`/`Modal`/`PanResponder` rather than ported from the web
|
|
22
|
+
* version's DOM `Portal` + manual focus trap + raw pointer events —
|
|
23
|
+
* reusing this package's own established pieces: `Dialog`'s backdrop-
|
|
24
|
+
* opacity-on-an-`AnimatedPressable` + no-op-`Pressable`-around-the-card
|
|
25
|
+
* (so a tap on the card never bubbles to the backdrop) recipe, and
|
|
26
|
+
* `BackHandler`'s `hardwareBackPress` as the Android analog of the web
|
|
27
|
+
* version's Escape listener (same pattern `Dialog`/`Popover` already use).
|
|
28
|
+
* Focus-trapping and focus-restore-on-close have no port — no DOM
|
|
29
|
+
* `document.activeElement`/`querySelector` equivalent exists in RN, the
|
|
30
|
+
* same gap already present in every other floating component here.
|
|
31
|
+
*
|
|
32
|
+
* **Real drag-to-dismiss, not a fixed-panel simplification**: unlike the
|
|
33
|
+
* web version's raw `PointerEvent` handlers directly mutating
|
|
34
|
+
* `style.transform`, this uses `PanResponder` (the same core RN API
|
|
35
|
+
* `Slider` already proved handles a threshold-based drag gesture) driving
|
|
36
|
+
* a single `Animated.Value` — the *same* value used for the slide-in/
|
|
37
|
+
* slide-out entrance animation. `PanResponder`'s `gestureState.dy` is the
|
|
38
|
+
* cumulative vertical delta since the gesture started, so a plain
|
|
39
|
+
* `translateY.setValue(Math.max(0, gestureState.dy))` during
|
|
40
|
+
* `onPanResponderMove` reproduces the web version's own
|
|
41
|
+
* `Math.max(0, e.clientY - dragStartY.current)` clamp exactly, and a
|
|
42
|
+
* release past `DRAG_CLOSE_THRESHOLD` (150 px, same constant as the web
|
|
43
|
+
* version) animates the rest of the way down before calling `onClose`,
|
|
44
|
+
* otherwise it springs back to `0`. The gesture responder is attached only
|
|
45
|
+
* to the handle bar `View`, not the whole sheet, mirroring the web
|
|
46
|
+
* version's `onPointerDown` being scoped to `.handle` alone.
|
|
47
|
+
*
|
|
48
|
+
* **A real slide-up needs the sheet's own height first** — RN's
|
|
49
|
+
* `transform` has no percentage-of-self units (the same `Slider`/`Avatar`
|
|
50
|
+
* pitfall), unlike the web version's `translateY(100%)`. The sheet renders
|
|
51
|
+
* once off-screen (measured via `onLayout`, starting `translateY` at that
|
|
52
|
+
* measured height) before animating to `0` — the same "resolve own
|
|
53
|
+
* rendered size, then animate" two-step every other sized-on-open
|
|
54
|
+
* component in this package (`Dialog`, `Dropdown`, `Popover`) already
|
|
55
|
+
* needs, just for a transform offset instead of a floating position.
|
|
56
|
+
*
|
|
57
|
+
* **Real, timed exit animation, unlike `Dialog`**: `Dialog`'s web source
|
|
58
|
+
* has no exit keyframes at all (`Modal`'s `visible={false}` unmounts
|
|
59
|
+
* immediately, matching the web version's plain `return null`) — but
|
|
60
|
+
* `BottomSheet`'s web source explicitly animates both the backdrop fade
|
|
61
|
+
* and the sheet's slide-out before removing it. Ported with a local
|
|
62
|
+
* `visible` state that lags one animation behind the `open` prop: closing
|
|
63
|
+
* starts the exit `Animated.timing`s and only flips `visible` to `false`
|
|
64
|
+
* (unmounting the `Modal`'s content) in the animation's own completion
|
|
65
|
+
* callback — not a `setTimeout` racing a hardcoded duration like the web
|
|
66
|
+
* version, since `Animated`'s callback already fires exactly when the
|
|
67
|
+
* animation actually finishes.
|
|
68
|
+
*
|
|
69
|
+
* The web version's `backdrop-filter: blur(4px)` has no port — this
|
|
70
|
+
* package has no native blur view dependency, the same reasoning that
|
|
71
|
+
* already dropped `Sidebar`'s blurred `variant`. `useBodyScrollLock` has no
|
|
72
|
+
* RN equivalent needed: `Modal` already blocks all interaction with
|
|
73
|
+
* whatever's behind it, there is no scrollable "body" to lock separately.
|
|
74
|
+
*
|
|
75
|
+
* `role="dialog"` + `accessibilityViewIsModal` port 1:1 from `Dialog`'s own
|
|
76
|
+
* precedent. `children`, when a plain string, must be wrapped in `Text`
|
|
77
|
+
* before rendering — RN throws if a raw string is a `View`'s child, unlike
|
|
78
|
+
* the web version's `<div>{children}</div>`, which needed no such check.
|
|
79
|
+
*
|
|
80
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.BottomSheet.html
|
|
81
|
+
*/
|
|
82
|
+
export declare const BottomSheet: ({ open, title, children, onClose, closeOnBackdrop, style, testID, }: BottomSheetProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
export interface DividerProps {
|
|
4
|
+
/** Optional centered label (e.g. `"OR"`, `"Continue with"`). */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/** Accessible name. Defaults to `children` when it's a string. */
|
|
7
|
+
accessibilityLabel?: string;
|
|
8
|
+
style?: StyleProp<ViewStyle>;
|
|
9
|
+
testID?: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Horizontal rule with an optional centered label — common auth/login-form
|
|
13
|
+
* pattern ("Sign in" / **OR** / "Continue with Google"). Mirrors
|
|
14
|
+
* `@gnome-ui/react`'s `Divider`.
|
|
15
|
+
*
|
|
16
|
+
* For a bare dividing line with no label, use `Separator` instead — it also
|
|
17
|
+
* supports a vertical orientation, which `Divider` does not.
|
|
18
|
+
*
|
|
19
|
+
* `role="separator"` ports 1:1 from RN's newer web-aligned `Role` union
|
|
20
|
+
* (the same one `Avatar`/`Badge`/`LevelBar` already reach for), unlike
|
|
21
|
+
* `Separator`'s own choice to render as `accessible={false}` — that
|
|
22
|
+
* component is purely decorative with nothing for a screen reader to
|
|
23
|
+
* announce, while a labelled `Divider` ("OR") is exactly the kind of
|
|
24
|
+
* content a screen reader user needs read aloud, so it stays in the
|
|
25
|
+
* accessibility tree instead. The label reuses `Text`'s `variant="caption"
|
|
26
|
+
* color="dim"` verbatim rather than hand-rolled styles, since that
|
|
27
|
+
* combination already resolves to the same font-size/weight/dim-opacity
|
|
28
|
+
* the web version's `.label` class hard-codes.
|
|
29
|
+
*
|
|
30
|
+
* The web version's `aria-orientation="horizontal"` has no port — `Divider`
|
|
31
|
+
* has no `orientation` prop at all (unlike `Separator`), so there is only
|
|
32
|
+
* ever one orientation to announce.
|
|
33
|
+
*
|
|
34
|
+
* @see https://developer.gnome.org/hig/patterns/containers.html
|
|
35
|
+
*/
|
|
36
|
+
export declare const Divider: ({ children, accessibilityLabel, style, testID }: DividerProps) => import("react/jsx-runtime").JSX.Element;
|