@gnome-ui/react-native 1.3.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +463 -4
  2. package/dist/components/Avatar/Avatar.d.ts +53 -0
  3. package/dist/components/Avatar/index.d.ts +2 -0
  4. package/dist/components/Badge/Badge.d.ts +48 -0
  5. package/dist/components/Badge/index.d.ts +2 -0
  6. package/dist/components/BottomSheet/BottomSheet.d.ts +82 -0
  7. package/dist/components/BottomSheet/index.d.ts +2 -0
  8. package/dist/components/Chip/Chip.d.ts +63 -0
  9. package/dist/components/Chip/index.d.ts +2 -0
  10. package/dist/components/Divider/Divider.d.ts +36 -0
  11. package/dist/components/Divider/index.d.ts +2 -0
  12. package/dist/components/Drawer/Drawer.d.ts +117 -0
  13. package/dist/components/Drawer/index.d.ts +2 -0
  14. package/dist/components/Expander/Expander.d.ts +64 -0
  15. package/dist/components/Expander/index.d.ts +2 -0
  16. package/dist/components/FileTypeIcon/FileTypeIcon.d.ts +45 -0
  17. package/dist/components/FileTypeIcon/fileType.d.ts +8 -0
  18. package/dist/components/FileTypeIcon/index.d.ts +3 -0
  19. package/dist/components/Highlight/Highlight.d.ts +42 -0
  20. package/dist/components/Highlight/index.d.ts +2 -0
  21. package/dist/components/IconButton/IconButton.d.ts +34 -0
  22. package/dist/components/IconButton/index.d.ts +2 -0
  23. package/dist/components/LevelBar/LevelBar.d.ts +72 -0
  24. package/dist/components/LevelBar/index.d.ts +2 -0
  25. package/dist/components/Overlay/Overlay.d.ts +41 -0
  26. package/dist/components/Overlay/index.d.ts +2 -0
  27. package/dist/components/Popover/Popover.d.ts +91 -0
  28. package/dist/components/Popover/index.d.ts +2 -0
  29. package/dist/components/SegmentedBar/SegmentedBar.d.ts +64 -0
  30. package/dist/components/SegmentedBar/index.d.ts +2 -0
  31. package/dist/components/SpinButton/SpinButton.d.ts +57 -0
  32. package/dist/components/SpinButton/index.d.ts +2 -0
  33. package/dist/index.cjs +1 -1
  34. package/dist/index.cjs.map +1 -1
  35. package/dist/index.d.ts +15 -0
  36. package/dist/index.js +2089 -662
  37. package/dist/index.js.map +1 -1
  38. package/package.json +1 -1
package/README.md CHANGED
@@ -18,10 +18,16 @@ 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 in progress: `Dropdown` and `Slider` shipped —
22
- > `Spin Button`, `Avatar`, `Badge`, and `Popover` remain. Component
23
- > ports from `@gnome-ui/react` continue tier by tier. See
24
- > [ROADMAP.md](../../ROADMAP.md) Priority 3.
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`/`FileTypeIcon`/
24
+ > `SegmentedBar` (Tier 20), `Chip` (Tier 7), and `IconButton`/`Drawer`
25
+ > (Tier 8/Tier 20) also shipped. Component ports from
26
+ > `@gnome-ui/react` continue tier by tier — see this package's own
27
+ > [ROADMAP.md](./ROADMAP.md) for full
28
+ > per-tier status against all 130 `@gnome-ui/react` components, and the
29
+ > main [ROADMAP.md](../../ROADMAP.md) Priority 3 for the framework
30
+ > expansion this package belongs to.
25
31
 
26
32
  ## How it works
27
33
 
@@ -971,6 +977,459 @@ width isn't a known constant: a zero-width `View` with
971
977
  `Text` child around that point regardless of how wide the label renders,
972
978
  with no measurement needed.
973
979
 
980
+ ### SpinButton
981
+
982
+ ```tsx
983
+ import { SpinButton } from '@gnome-ui/react-native';
984
+
985
+ <SpinButton value={quantity} onChange={setQuantity} min={0} max={10} accessibilityLabel="Quantity" />;
986
+ ```
987
+
988
+ Numeric −/+ stepper following the Adwaita `GtkSpinButton` pattern, mirroring
989
+ `@gnome-ui/react`'s `SpinButton`. The `min`/`max`/`step`/`decimals`/`wrap`/
990
+ `format` clamp-and-format math ports verbatim (pure JS, no DOM involved).
991
+
992
+ The primary interaction is tapping the visible −/+ buttons, same as a
993
+ sighted mouse user on the web version. The web version's keyboard
994
+ interaction (↑/↓ one step, Page Up/Down ten steps, Home/End to bounds) has
995
+ no RN equivalent — a touch-first device has no keyboard to drive it, the
996
+ same reasoning `Slider` already applied. Rather than dropping value
997
+ adjustment accessibility entirely, single-step increment/decrement reuses
998
+ `Slider`'s exact `accessibilityRole="adjustable"` +
999
+ `onAccessibilityAction`/`accessibilityActions` recipe (VoiceOver's
1000
+ swipe-up/down, TalkBack's local-context menu) — the bigger Page Up/Down and
1001
+ Home/End jumps have no equivalent screen-reader gesture on either platform,
1002
+ so those alone are dropped, same as `Slider`. The visible −/+ buttons and
1003
+ value text are hidden from the accessibility tree
1004
+ (`accessibilityElementsHidden`/`importantForAccessibility="no"`, mirroring
1005
+ the web version's `aria-hidden`/`tabIndex={-1}` on both `<button>`s and the
1006
+ value `<span>`) so a screen reader user gets one adjustable stop, not three.
1007
+
1008
+ ### Avatar
1009
+
1010
+ ```tsx
1011
+ import { Avatar } from '@gnome-ui/react-native';
1012
+
1013
+ <Avatar name="Grace Hopper" size="lg" />
1014
+ <Avatar src="https://example.com/alice.jpg" alt="Alice's profile photo" />;
1015
+ ```
1016
+
1017
+ Circular avatar with image or initials fallback, mirroring `@gnome-ui/react`'s
1018
+ `Avatar`. The color-hash and initials-extraction math ports verbatim (pure
1019
+ JS, no DOM involved).
1020
+
1021
+ The outer container carries `role="img"` + `accessibilityLabel` — RN's newer
1022
+ web-aligned `Role` union has an `"img"` value, a direct 1:1 port of the web
1023
+ version's `role="img"`, no substitution needed (same as `ProgressBar`'s
1024
+ `role="progressbar"`). The image/initials underneath are hidden from the
1025
+ accessibility tree, mirroring the web version's `aria-hidden` on both, so a
1026
+ screen reader gets one stop, not two — same reasoning as `SpinButton`'s
1027
+ hidden −/+ buttons.
1028
+
1029
+ The web CSS's `box-shadow: inset 0 0 0 1px …` ring becomes a real 1px
1030
+ `borderWidth`/`borderColor` here (RN has no inset shadow) — the same
1031
+ substitution `Slider`'s thumb border already used for a ring effect.
1032
+
1033
+ ### Badge
1034
+
1035
+ ```tsx
1036
+ import { Avatar, Badge } from '@gnome-ui/react-native';
1037
+
1038
+ <Badge variant="error" anchor={<Avatar name="Alice Bob" />}>3</Badge>
1039
+ <Badge dot variant="success" />;
1040
+ ```
1041
+
1042
+ Counter or status indicator, optionally overlaid on another element,
1043
+ mirroring `@gnome-ui/react`'s `Badge`. `children` renders as a themed `Text`
1044
+ label when it's a string or number (the common case — counts and short
1045
+ text); any other node renders as-is, the same convention `Button`'s
1046
+ `children` already established.
1047
+
1048
+ The web CSS's `box-shadow: 0 0 0 2px var(--gnome-window-bg-color)` ring
1049
+ (always present, separating the badge from whatever's behind it) has no RN
1050
+ equivalent that avoids affecting layout — RN's `border*` shrinks the content
1051
+ box instead of drawing outside it. Reproduced instead with an outer wrapping
1052
+ `View` (2px padding, `theme.windowBgColor` background, pill radius) around
1053
+ the actual colored badge, so the ring appears to spread outward exactly like
1054
+ the web version's non-blurred shadow, without eating into the badge's own
1055
+ text padding.
1056
+
1057
+ ### Popover
1058
+
1059
+ ```tsx
1060
+ import { Button, Popover, Text } from '@gnome-ui/react-native';
1061
+
1062
+ <Popover content={<Text>Rich content here</Text>}>
1063
+ <Button>Open</Button>
1064
+ </Popover>;
1065
+ ```
1066
+
1067
+ Floating panel anchored to a trigger element, following the Adwaita
1068
+ `GtkPopover` pattern, mirroring `@gnome-ui/react`'s `Popover`. Unlike
1069
+ `Tooltip`, it can hold rich interactive content (buttons, links, forms).
1070
+
1071
+ Reuses this package's own established pieces rather than re-deriving them:
1072
+ `Tooltip`'s `cloneElement`-onto-an-arbitrary-trigger architecture and
1073
+ 4-placement fallback-cascade positioning (no arrow-offset-shift-when-clamped
1074
+ — same simplification `Tooltip` already accepted), and `Dropdown`'s
1075
+ toggle-on-press + full-screen backdrop `Pressable` that closes on an outside
1076
+ tap plus reduced-motion fade-in.
1077
+
1078
+ **Deliberate divergence from `Dropdown`'s backdrop structure**: `Dropdown`
1079
+ nests its panel directly inside the backdrop `Pressable` and gets away with
1080
+ it because almost every pixel of its panel is itself a `Pressable` option
1081
+ row, which claims the touch responder before it can bubble to the backdrop.
1082
+ A popover's `content` is arbitrary — likely to have inert padding/whitespace
1083
+ with no `Pressable` of its own — so nesting the same way would let a tap on
1084
+ inert panel space fall through to the backdrop and close the popover, unlike
1085
+ the web version's `.contains()` check (which never closes on *any* tap
1086
+ inside the panel). Fixed with `onStartShouldSetResponder={() => true}` on
1087
+ the panel itself: it claims the touch responder for any touch RN's
1088
+ negotiation hasn't already given to a deeper `Pressable` inside `content`,
1089
+ without making the panel itself behave like a button.
1090
+
1091
+ `BackHandler`'s `hardwareBackPress` (wired the same way `Dialog` already
1092
+ does) is the Android analog of the web version's document-level Escape
1093
+ listener. Focus-trapping and focus-restore-on-close have no port — no DOM
1094
+ `document.activeElement`/`querySelector` equivalent exists in RN, the same
1095
+ gap already present in `Dialog`/`Tooltip`/`Dropdown`.
1096
+
1097
+ The web version's rotated-square-with-matching-background arrow is replaced
1098
+ with `Tooltip`'s simpler transparent-border-triangle technique — the same
1099
+ visual affordance, a much simpler RN-native primitive.
1100
+
1101
+ ### BottomSheet
1102
+
1103
+ ```tsx
1104
+ import { BottomSheet, Button } from '@gnome-ui/react-native';
1105
+
1106
+ <Button onPress={() => setOpen(true)}>Open</Button>
1107
+ <BottomSheet open={open} title="Options" onClose={() => setOpen(false)}>
1108
+ <Text>Rich content here</Text>
1109
+ </BottomSheet>;
1110
+ ```
1111
+
1112
+ Slide-up panel that overlays content from the bottom edge, mirroring
1113
+ `AdwBottomSheet` (libadwaita 1.6+) and `@gnome-ui/react`'s `BottomSheet`.
1114
+ Reuses `Dialog`'s backdrop-opacity-on-an-`AnimatedPressable` +
1115
+ no-op-`Pressable`-around-the-card recipe, and `BackHandler`'s
1116
+ `hardwareBackPress` as the Android analog of the web version's Escape
1117
+ listener.
1118
+
1119
+ **Real drag-to-dismiss**, not a fixed-panel simplification: `PanResponder`
1120
+ (the same core API `Slider` already proved handles a threshold gesture)
1121
+ drives a single `Animated.Value` shared with the entrance/exit animation —
1122
+ dragging the handle bar past 150 px (same constant as the web version)
1123
+ requests a close; releasing short of that springs back to `0`. A real
1124
+ slide-up needs the sheet's own height first (RN's `transform` has no
1125
+ percentage-of-self units, the same `Slider`/`Avatar` pitfall) — the sheet
1126
+ renders once off-screen, measured via `onLayout`, before animating in.
1127
+
1128
+ **A real, timed exit animation, unlike `Dialog`**: `Dialog`'s web source has
1129
+ no exit keyframes at all, but `BottomSheet`'s does — ported with a local
1130
+ `visible` state that lags one animation behind the `open` prop, flipping to
1131
+ `false` only in the exit `Animated.timing`'s own completion callback.
1132
+
1133
+ The web version's `backdrop-filter: blur(4px)` has no port (no native blur
1134
+ view dependency, same reasoning that dropped `Sidebar`'s blurred variant),
1135
+ and `useBodyScrollLock` needs no RN equivalent (`Modal` already blocks all
1136
+ background interaction). `children`, when a plain string, is wrapped in
1137
+ `Text` before rendering — RN throws if a raw string is a `View`'s child,
1138
+ unlike the web version's plain `<div>{children}</div>`.
1139
+
1140
+ ### Overlay
1141
+
1142
+ ```tsx
1143
+ import { Overlay, Button } from '@gnome-ui/react-native';
1144
+
1145
+ <Button onPress={() => setOpen(true)}>Open</Button>
1146
+ <Overlay open={open} onDismiss={() => setOpen(false)}>
1147
+ <YourOwnCard />
1148
+ </Overlay>;
1149
+ ```
1150
+
1151
+ Standalone backdrop/scrim layer with a fade transition and
1152
+ press-to-dismiss — the shared building block behind `Dialog`, `Dropdown`,
1153
+ `Popover`, and `BottomSheet`'s own backdrops, extracted here for building
1154
+ custom overlay UI, mirroring `@gnome-ui/react`'s `Overlay`.
1155
+
1156
+ Deliberately minimal, same as the web version: no focus trap, no
1157
+ `BackHandler`/Escape handling, no `role` — use `Dialog`/`Popover`/
1158
+ `BottomSheet` directly when you need those. Reuses `Dialog`'s exact
1159
+ backdrop recipe (`AnimatedPressable` + a no-op `Pressable` wrapping
1160
+ `children`, so a tap on your own content never bubbles to the backdrop and
1161
+ dismisses it) and `BottomSheet`'s real, timed exit animation technique — a
1162
+ local `visible` state that lags the `open` prop by one `Animated.timing`,
1163
+ flipping to `false` only in that animation's own completion callback.
1164
+
1165
+ **Not retrofitted into `Dialog`/`Dropdown`/`Popover`/`BottomSheet`** — each
1166
+ already ships and is fully tested with its own inline copy of this same
1167
+ backdrop pattern (with small per-component differences: `Popover` claims
1168
+ the touch responder differently than the no-op-`Pressable` wrapper the
1169
+ others use). Extracting `Overlay` as a new standalone primitive was the
1170
+ scoped ask; retrofitting four already-shipped components to share it is a
1171
+ separate, riskier refactor this turn didn't take on.
1172
+
1173
+ ### LevelBar
1174
+
1175
+ ```tsx
1176
+ import { LevelBar } from '@gnome-ui/react-native';
1177
+
1178
+ <LevelBar value={0.15} low={0.25} high={0.75} accessibilityLabel="Battery" />
1179
+ <LevelBar value={0.6} discrete numBlocks={5} accessibilityLabel="Signal strength" />;
1180
+ ```
1181
+
1182
+ Discrete level indicator with color-coded low/high offset zones, mirroring
1183
+ `GtkLevelBar` and `@gnome-ui/react`'s `LevelBar`. Use for a gauge/
1184
+ measurement display (disk usage, battery, signal strength) — not for task
1185
+ progress (`ProgressBar`) or a proportional category breakdown
1186
+ (`SegmentedBar`).
1187
+
1188
+ The continuous fill reuses `ProgressBar`'s exact animation technique rather
1189
+ than animating `width` directly: a fixed `width: '100%'` fill with
1190
+ `transformOrigin: 'left'` and an animated `transform: [{ scaleX }]`, so the
1191
+ whole thing runs on `useNativeDriver: true` — a JS-driven `width` animation
1192
+ schedules its next frame via a plain `setTimeout` that routinely fires
1193
+ after a test's `render()` returns but before unmount, producing a spurious
1194
+ "update not wrapped in act()" warning, the same reasoning `ProgressBar`'s
1195
+ own docstring documents. `useReducedMotion()` mirrors `ProgressBar`'s
1196
+ determinate behavior (duration drops to `0`, an immediate jump).
1197
+
1198
+ Discrete mode's per-block color transition has no port — a value change is
1199
+ a plain, unanimated color swap per block, a decorative nicety rather than a
1200
+ behavior gap. `role="meter"` ports 1:1 from RN's newer web-aligned `Role`
1201
+ union (unlike `AccessibilityRole`, which has no `"meter"` value at all).
1202
+
1203
+ ### Expander
1204
+
1205
+ ```tsx
1206
+ import { Expander } from '@gnome-ui/react-native';
1207
+
1208
+ <Expander label="Show advanced options">
1209
+ <TextField label="Custom endpoint" />
1210
+ </Expander>
1211
+ ```
1212
+
1213
+ Standalone disclosure triangle + collapsible content, mirroring `GtkExpander`
1214
+ and `@gnome-ui/react`'s `Expander`. A bare, unstyled counterpart to
1215
+ `ExpanderRow`; use it outside a settings-row context (e.g. "Show advanced
1216
+ options" in a form, or "Show details" under an error message).
1217
+
1218
+ The web version clips the panel with a CSS grid-height animation and rides
1219
+ the content's `padding-top` on a second, separate transition, so a collapsed
1220
+ expander doesn't reserve blank space for hidden padding. RN has no CSS grid
1221
+ to lean on, so the panel is a single `Animated.View` whose numeric `height`
1222
+ is driven directly (`useNativeDriver: false`, the same accepted trade-off
1223
+ `Checkbox`/`RadioButton`/`Switch`/`AnimatedIcon` already make for
1224
+ non-transform properties) — since the content's own `onLayout` measurement
1225
+ already includes its `paddingTop`, one animated height reproduces the web
1226
+ version's two-transition result. Content stays mounted while collapsed
1227
+ (`accessibilityElementsHidden`/`importantForAccessibility="no"`, the same
1228
+ substitution for the web's `inert` used elsewhere in this package), and on
1229
+ first mount with `defaultExpanded` the panel briefly renders at its natural,
1230
+ unmeasured height so the initial reveal doesn't pop once layout resolves.
1231
+
1232
+ The chevron is `PanEnd` (GNOME's own `pan-end-symbolic` disclosure triangle)
1233
+ rotating 0deg → 90deg on an `Animated.Value`, the same `interpolate`-to-
1234
+ `rotate` recipe `Spinner` uses for its own spin.
1235
+
1236
+ ### Divider
1237
+
1238
+ ```tsx
1239
+ import { Divider } from '@gnome-ui/react-native';
1240
+
1241
+ <Divider>OR</Divider>
1242
+ <Divider>Continue with</Divider>
1243
+ <Divider />
1244
+ ```
1245
+
1246
+ Horizontal rule with an optional centered label — the common auth/login-form
1247
+ pattern ("Sign in" / **OR** / "Continue with Google"). Mirrors
1248
+ `@gnome-ui/react`'s `Divider`. For a bare dividing line with no label, use
1249
+ `Separator` instead — it also supports a vertical orientation, which
1250
+ `Divider` does not.
1251
+
1252
+ `role="separator"` ports 1:1 from RN's newer web-aligned `Role` union (the
1253
+ same one `Avatar`/`Badge`/`LevelBar` already reach for) — unlike
1254
+ `Separator`'s own `accessible={false}`, since a labelled `Divider` ("OR") is
1255
+ exactly the kind of content a screen reader user needs read aloud, rather
1256
+ than a purely decorative line. The label reuses `Text`'s
1257
+ `variant="caption" color="dim"` verbatim, which already resolves to the same
1258
+ font-size/weight/dim-opacity the web version's `.label` class hard-codes.
1259
+
1260
+ ### Highlight
1261
+
1262
+ ```tsx
1263
+ import { Highlight } from '@gnome-ui/react-native';
1264
+
1265
+ <Highlight text="Preferences for accessibility" query="access" />
1266
+ <Highlight text="The quick brown fox" query={['quick', 'fox']} />
1267
+ ```
1268
+
1269
+ Wraps every occurrence of `query` within `text` in a highlighted inline run
1270
+ — mirrors `@gnome-ui/react`'s `Highlight`, which wraps matches in a `<mark>`.
1271
+ Pairs with `SearchBar`'s suggestion list and any filterable list to show
1272
+ users which part of a result matched what they typed.
1273
+
1274
+ The outer span is the themed `Text` component (so callers get the same
1275
+ `variant`/`color` API as everywhere else), but each matched run is a plain,
1276
+ unthemed RN `Text` carrying only the highlight's own overrides — RN's `Text`
1277
+ is the one primitive that inherits ambient `fontSize`/`color`/`fontFamily`
1278
+ from a parent `Text` when nested, the same way the web version's `<mark>`
1279
+ inherits from its surrounding text and only overrides
1280
+ `background-color`/`font-weight`. Reaching for the themed `Text` for the
1281
+ marked runs too would reset them to its own default `variant="body"` sizing
1282
+ instead of inheriting whatever variant the caller chose for the whole
1283
+ string.
1284
+
1285
+ The web version's translucent `color-mix(in srgb, accent 30%, transparent)`
1286
+ background has no RN equivalent (`color-mix` is CSS-only) — resolved to a
1287
+ literal 8-digit `#RRGGBBAA` hex instead, since `accentBgColor` is always a
1288
+ plain 6-digit hex across all four theme variants. `border-radius` on the
1289
+ `<mark>` has no reliable port either: RN only paints `backgroundColor` on an
1290
+ inline (nested) `Text` run, not `borderRadius` — a decorative nicety
1291
+ dropped, not a behavior gap. `prefers-contrast: more`'s solid-background/
1292
+ white-text swap ports via `useResolvedContrast()`, the same hook `Button`
1293
+ already uses for its own high-contrast branching.
1294
+
1295
+ ### FileTypeIcon
1296
+
1297
+ ```tsx
1298
+ import { FileTypeIcon } from '@gnome-ui/react-native';
1299
+
1300
+ <FileTypeIcon name="report.pdf" />
1301
+ <FileTypeIcon mimeType="image/png" />
1302
+ <FileTypeIcon name="cover.jpg" thumbnail={thumbnailUrl} />
1303
+ <FileTypeIcon isFolder />
1304
+ ```
1305
+
1306
+ Small icon — optionally a thumbnail — resolved from a file's MIME type or
1307
+ name extension. Useful for file-manager-style listings. Mirrors
1308
+ `@gnome-ui/react`'s `FileTypeIcon`, falling back to the generic file icon
1309
+ (freedesktop's `text-x-generic`) when the type can't be resolved.
1310
+
1311
+ `fileType.ts`'s category-resolution logic (MIME type / extension → one of
1312
+ 13 categories, plus the freedesktop icon and generated label per category)
1313
+ is pure, DOM-free TS — duplicated verbatim from `@gnome-ui/react` rather
1314
+ than imported cross-package, the same `Icon.tsx` precedent already
1315
+ established for logic that isn't worth a shared package for one file's
1316
+ worth of code. `role="img"` + `accessibilityLabel` ports 1:1, and the
1317
+ thumbnail reuses `Avatar`'s own `Image`/`resizeMode="cover"` recipe, sized
1318
+ from `Icon`'s own size map so swapping between the resolved icon and a
1319
+ thumbnail never shifts layout.
1320
+
1321
+ ### Chip
1322
+
1323
+ ```tsx
1324
+ import { Chip } from '@gnome-ui/react-native';
1325
+
1326
+ <Chip label="React" />
1327
+ <Chip label="React" onRemove={() => {}} />
1328
+ <Chip label="React" selectable selected={selected} onToggle={() => setSelected((s) => !s)} />
1329
+ ```
1330
+
1331
+ Compact pill-shaped label for tags, filters, and selection states. Mirrors
1332
+ `@gnome-ui/react`'s `Chip`. Three usage modes: **static** (just a visual
1333
+ label), **removable** (add `onRemove` for a × button), and **selectable**
1334
+ (add `selectable` + `selected` + `onToggle` for toggle behavior — same
1335
+ `isInteractive = selectable && !onRemove` precedence as the web version,
1336
+ so passing both renders the remove button, not a toggle). Pair with
1337
+ `WrapBox` for multi-chip layouts.
1338
+
1339
+ The selected background/border tint
1340
+ (`color-mix(in srgb, accent 15%/50%, transparent)`) resolves to a literal
1341
+ 8-digit `#RRGGBBAA` hex, the same `Highlight` precedent. The web version's
1342
+ `:hover`/`:active` background transitions collapse into a single
1343
+ pressed-state overlay tinted by `theme.activeOverlay` (the same
1344
+ `ActionRow`/`Card` recipe), since touch has no hover. The leading icon and
1345
+ remove (×) icon stay in the default foreground color rather than tracking
1346
+ the selected accent text (`color: inherit` on the web) — RN's `Icon` has
1347
+ no `currentColor` equivalent and only accepts a fixed named-swatch
1348
+ palette, none of which tracks the app's configurable accent color, so
1349
+ this is a decorative nicety dropped, not a behavior gap.
1350
+ `accessibilityRole="checkbox"` on the selectable form ports 1:1, the same
1351
+ `Checkbox` precedent.
1352
+
1353
+ ### SegmentedBar
1354
+
1355
+ ```tsx
1356
+ import { SegmentedBar } from '@gnome-ui/react-native';
1357
+
1358
+ <SegmentedBar
1359
+ values={[
1360
+ { label: 'TypeScript', value: 60, color: '#3178c6' },
1361
+ { label: 'JavaScript', value: 30, color: '#f7df1e' },
1362
+ { label: 'CSS', value: 10, color: '#563d7c' },
1363
+ ]}
1364
+ />
1365
+ ```
1366
+
1367
+ Horizontal bar split into proportional segments, one per category. Mirrors
1368
+ `@gnome-ui/react`'s `SegmentedBar`. Typical use case: repository language
1369
+ distribution. Values are normalized proportionally when they don't sum to
1370
+ 100.
1371
+
1372
+ The web version's hover interaction (dim every segment but the one under
1373
+ the pointer, brighten that one via `filter: brightness()`) is rebuilt for
1374
+ touch rather than dropped: each segment is a `Pressable`, and touching one
1375
+ dims the rest immediately via `onPressIn`/`onPressOut` — deliberately not
1376
+ gated behind `Tooltip`'s own long-press delay, since this feedback is the
1377
+ RN analog of a `Pressable`'s own instant `pressed` state, not the "peek"
1378
+ affordance a tooltip reveal is. Each segment is also wrapped in `Tooltip`
1379
+ (`placement="top"`, `delay={200}`, ported 1:1) for the label/percentage
1380
+ readout — `Tooltip` clones its own handlers onto the child while still
1381
+ calling the child's original ones, so the dim/highlight and the tooltip
1382
+ compose cleanly on the same `Pressable`. `filter: brightness(1.15)` on the
1383
+ actively-touched segment has no RN equivalent — dropped as a decorative
1384
+ nicety, since the touched segment already reads as highlighted by
1385
+ contrast once every other segment dims to 35% opacity.
1386
+
1387
+ ### IconButton
1388
+
1389
+ ```tsx
1390
+ import { IconButton } from '@gnome-ui/react-native';
1391
+ import { Search } from '@gnome-ui/icons';
1392
+
1393
+ <IconButton icon={Search} label="Search" />
1394
+ <IconButton icon={Search} label="Search" tooltip="Search files" />
1395
+ ```
1396
+
1397
+ Icon-only action button composed from `Button`, `Icon`, and optionally
1398
+ `Tooltip` — mirrors `@gnome-ui/react`'s `IconButton`, itself already just a
1399
+ thin composition of those same three pieces. `label` is required since the
1400
+ button has no visible text. Built as a genuine prerequisite for `Drawer`'s
1401
+ `rail`, not scope creep — every piece it composes already existed.
1402
+
1403
+ ### Drawer
1404
+
1405
+ ```tsx
1406
+ import { Drawer } from '@gnome-ui/react-native';
1407
+
1408
+ <Drawer open={open} title="Details" onClose={() => setOpen(false)}>
1409
+ <Text>Drawer content can be any React node passed as children.</Text>
1410
+ </Drawer>
1411
+ ```
1412
+
1413
+ Slide-in panel for supplementary content, anchored to the left or right
1414
+ edge. Mirrors `@gnome-ui/react`'s `Drawer`. Supports a `rail` (an
1415
+ `IconButton` strip on the drawer's inner edge for switching panels without
1416
+ closing it) and nested-drawer width auto-scaling via context — a `Drawer`
1417
+ opened from within another drawer's content automatically renders
1418
+ narrower (`0.85^depth`, floored at 240px), so stacked drawers read as a
1419
+ drill-in hierarchy.
1420
+
1421
+ Floats with a margin on every side and all four corners rounded, matching
1422
+ `@gnome-ui/react`'s own recent CSS update to the same look — positioned
1423
+ within the padded backdrop via `justifyContent` rather than the web CSS's
1424
+ `margin: auto` on the drawer itself, since RN auto-margin support was
1425
+ unverified for this Yoga version (confirmed correct with an on-device
1426
+ debug-color check before trusting it; `BottomSheet` already proves the
1427
+ same `justifyContent: 'flex-end'` mechanism on its own vertical axis).
1428
+ Unlike `BottomSheet`, there's no drag-to-dismiss — the web source defines
1429
+ no exit keyframes at all, so this follows `Dialog`'s simpler animation
1430
+ shape instead. `backdrop-filter: blur(4px)` has no port (no native blur
1431
+ dependency in this package).
1432
+
974
1433
  ## Installation
975
1434
 
976
1435
  ```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,2 @@
1
+ export type { AvatarColor, AvatarProps, AvatarSize } from './Avatar';
2
+ export { Avatar } from './Avatar';
@@ -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,2 @@
1
+ export type { BadgeProps, BadgeVariant } from './Badge';
2
+ export { Badge } from './Badge';