@gnome-ui/react-native 1.10.0 → 1.12.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 CHANGED
@@ -294,6 +294,43 @@ announces the same thing, mirroring the web version's `aria-label` on its
294
294
  icon span. Unlike the web `Link`, RN has no tab concept, so `external` is
295
295
  purely presentational — `href` always opens the same way regardless.
296
296
 
297
+ ### LinkedGroup
298
+
299
+ ```tsx
300
+ import { Button, LinkedGroup } from '@gnome-ui/react-native';
301
+
302
+ <LinkedGroup>
303
+ <Button>Cut</Button>
304
+ <Button>Copy</Button>
305
+ <Button>Paste</Button>
306
+ </LinkedGroup>
307
+ ```
308
+
309
+ Renders children as a single visually-connected unit with no gap and
310
+ merged borders — the canonical GNOME pattern for button groups and
311
+ segmented inputs. Mirrors `@gnome-ui/react`'s `LinkedGroup`, itself
312
+ mirroring the libadwaita `.linked` style class.
313
+
314
+ The web version reaches every child's border-radius via a CSS `> *`
315
+ universal child selector — RN has no equivalent way for a parent `View`
316
+ to reach into an arbitrary child's own internally-computed styles.
317
+ Reimagined as the same `cloneElement`-onto-children technique
318
+ `Popover`/`Tooltip` already use on their own trigger: each child gets a
319
+ computed corner-radius/negative-margin override merged onto whatever
320
+ `style` it already has, generalizing the "zero the shared inner corners,
321
+ keep `theme.radiusMd` on the outer ones, overlap by 1 dp to collapse the
322
+ shared border" recipe `SplitButton` already proved for its own fixed
323
+ two-piece connected border. This only works because every component in
324
+ this package already merges a passed-in `style` prop last — the same
325
+ assumption `Popover`'s own trigger-cloning already depends on, so any
326
+ custom child passed to `LinkedGroup` needs to follow that same
327
+ convention.
328
+
329
+ The web CSS also raises a hovered/focused child's `z-index` so its own
330
+ border isn't visually covered by the next sibling's overlapping edge —
331
+ dropped here: RN is touch-first (no `:hover`), and no component in this
332
+ package currently renders an escaping focus ring that overlap could clip.
333
+
297
334
  ### TextField
298
335
 
299
336
  ```tsx
@@ -1464,6 +1501,41 @@ thin composition of those same three pieces. `label` is required since the
1464
1501
  button has no visible text. Built as a genuine prerequisite for `Drawer`'s
1465
1502
  `rail`, not scope creep — every piece it composes already existed.
1466
1503
 
1504
+ ### CopyButton
1505
+
1506
+ ```tsx
1507
+ import { CopyButton } from '@gnome-ui/react-native';
1508
+
1509
+ <CopyButton value="CVE-2024-3094" />
1510
+ <CopyButton value={installCommand} label="Copy install command" copiedLabel="Added to clipboard" />
1511
+ ```
1512
+
1513
+ Icon button that copies `value` to the clipboard, swapping to a checkmark
1514
+ and a "Copied!" tooltip for `resetDelay` ms (default 2000) as confirmation
1515
+ — mirrors `@gnome-ui/react`'s `CopyButton`. RN has no `navigator.clipboard`,
1516
+ so this is built on `@react-native-clipboard/clipboard` (a new peer
1517
+ dependency, deliberately chosen over `expo-clipboard` so this package
1518
+ works the same in bare RN and Expo, not just this repo's own Expo example
1519
+ app — the same "no new peer dependency without a deliberate decision"
1520
+ standard `AnimatedIcon`'s `react-native-svg` addition was held to).
1521
+
1522
+ **Real environment finding**: `@react-native-clipboard/clipboard`'s native
1523
+ module isn't part of Expo Go's preinstalled module set, so any app using
1524
+ this component needs a custom dev client (`npx expo prebuild` +
1525
+ `expo run:ios`/`run:android`) rather than plain Expo Go — confirmed by
1526
+ hitting `TurboModuleRegistry.getEnforcing(...): 'RNCClipboard' could not
1527
+ be found` in Expo Go before building this repo's own example app a dev
1528
+ client to verify the component on-device.
1529
+
1530
+ Its `setString` is synchronous and void — unlike the web version's
1531
+ `navigator.clipboard.writeText`, which returns a `Promise` that can
1532
+ reject, there's no error channel to observe under normal operation.
1533
+ `onCopyError` is kept for API parity (and wraps the native call in a
1534
+ `try`/`catch` defensively) but in practice won't fire the way it can on
1535
+ web. The live-region announcement uses `role="status"` directly — RN's
1536
+ newer `Role` union does include `"status"`, unlike the older
1537
+ `AccessibilityRole` enum `Toast` had to substitute `"alert"` for.
1538
+
1467
1539
  ### Drawer
1468
1540
 
1469
1541
  ```tsx
@@ -1494,6 +1566,44 @@ no exit keyframes at all, so this follows `Dialog`'s simpler animation
1494
1566
  shape instead. `backdrop-filter: blur(4px)` has no port (no native blur
1495
1567
  dependency in this package).
1496
1568
 
1569
+ ### Toolbar
1570
+
1571
+ ```tsx
1572
+ import { Button, Spacer, Toolbar } from '@gnome-ui/react-native';
1573
+
1574
+ <Toolbar>
1575
+ <Button variant="flat">Cancel</Button>
1576
+ <Spacer />
1577
+ <Button variant="flat">Done</Button>
1578
+ </Toolbar>
1579
+ ```
1580
+
1581
+ Horizontal action bar following the libadwaita `.toolbar` pattern —
1582
+ mirrors `@gnome-ui/react`'s `Toolbar`. Directly portable, no web-only APIs
1583
+ — a flex row with `theme.space1` (6 dp) padding and gap, the standard
1584
+ spacing for rows of flat buttons in header bars, action bars, and tool
1585
+ rows. Use `Button variant="flat"` for buttons that blend into the bar, or
1586
+ `variant="raised"` for one that needs explicit elevation within a flat
1587
+ context. The web CSS's `color`/`font-family` on `.toolbar` are dropped —
1588
+ RN has no style inheritance from a parent `View` down to child `Text`
1589
+ the way CSS `color` cascades, so a value there would reach nothing (every
1590
+ child, e.g. `Button`, already sets its own explicit colors).
1591
+
1592
+ ### Spacer
1593
+
1594
+ ```tsx
1595
+ import { Spacer } from '@gnome-ui/react-native';
1596
+ ```
1597
+
1598
+ Invisible `flex: 1` filler for `Toolbar` and `HeaderBar` — mirrors
1599
+ `@gnome-ui/react`'s `Spacer`. Place between leading and trailing groups to
1600
+ push trailing items to the end. `accessible={false}` mirrors the web
1601
+ version's `aria-hidden="true"` — the same "purely decorative, exclude
1602
+ from the accessibility tree entirely" call `Separator` already made,
1603
+ rather than reaching for `role`'s newer `"separator"` value (which
1604
+ exists, but implies a divider a screen reader user might care about — a
1605
+ plain flex filler has no such meaning).
1606
+
1497
1607
  ### AvatarGroup
1498
1608
 
1499
1609
  ```tsx
@@ -2330,6 +2440,36 @@ nesting one `Pressable` inside another would create two overlapping tap
2330
2440
  targets. `aria-labelledby` has no RN equivalent, so `accessibilityLabel`
2331
2441
  combines the title and subtitle instead.
2332
2442
 
2443
+ ### SwitchRow
2444
+
2445
+ ```tsx
2446
+ import { BoxedList, SwitchRow } from '@gnome-ui/react-native';
2447
+
2448
+ <BoxedList>
2449
+ <SwitchRow
2450
+ title="Wi-Fi"
2451
+ subtitle="Home Network"
2452
+ checked={wifi}
2453
+ onCheckedChange={setWifi}
2454
+ />
2455
+ </BoxedList>
2456
+ ```
2457
+
2458
+ Activatable row with an integrated switch, for use inside a `BoxedList`
2459
+ — mirrors `@gnome-ui/react`'s `SwitchRow`. The entire row is a single
2460
+ pressable; pressing anywhere toggles the switch, which is why this isn't
2461
+ `ActionRow` + a trailing `Switch` — `AdwSwitchRow` makes the whole row the
2462
+ interactive element, the same shape `CheckRow` already established for its
2463
+ checkbox. Use for a single on/off setting; prefer `CheckRow` for
2464
+ multi-select scenarios. Supports both controlled (`checked`) and
2465
+ uncontrolled (`defaultChecked`) modes, the same `isControlled`/internal-
2466
+ state-fallback shape already used by `Expander`/`ComboRow`/`Popover`/
2467
+ `CheckRow`. The switch visual reuses `Switch`'s exact track/thumb animation
2468
+ recipe, but as plain non-interactive `Animated.View`s rather than the real
2469
+ `Switch` component — nesting one `Pressable` inside another would create
2470
+ two overlapping tap targets. `aria-labelledby` has no RN equivalent, so
2471
+ `accessibilityLabel` combines the title and subtitle instead.
2472
+
2333
2473
  ### ExpanderRow
2334
2474
 
2335
2475
  ```tsx
@@ -2407,6 +2547,40 @@ panel-height positioning almost verbatim, and gives each option row a
2407
2547
  leading checkbox-square visual instead of `Dropdown`'s single trailing
2408
2548
  checkmark.
2409
2549
 
2550
+ ### NavigationSplitView
2551
+
2552
+ ```tsx
2553
+ import { NavigationSplitView } from '@gnome-ui/react-native';
2554
+
2555
+ const [showContent, setShowContent] = useState(false);
2556
+
2557
+ <NavigationSplitView
2558
+ showContent={showContent}
2559
+ sidebar={<MailList onSelect={() => setShowContent(true)} />}
2560
+ content={<MailDetail onBack={() => setShowContent(false)} />}
2561
+ />;
2562
+ ```
2563
+
2564
+ Two-pane sidebar + content layout following the Adwaita
2565
+ `AdwNavigationSplitView` pattern — mirrors `@gnome-ui/react`'s
2566
+ `NavigationSplitView`. On wide screens (`useBreakpoint().isNarrow ===
2567
+ false`, > 400 dp) both panes render side by side, separated by a
2568
+ `Separator`. On narrow screens only one pane shows at a time; `showContent`
2569
+ switches between the sidebar list and the detail view.
2570
+
2571
+ The web version's `clamp(min, fraction * 100%, max)` sidebar width has no
2572
+ RN equivalent, so the container measures its own width via `onLayout` and
2573
+ the same clamp is computed in JS. Narrow-mode pane switching is animated
2574
+ (`translateX`) rather than an instant `display: 'none'` swap like
2575
+ `TabPanel` — both panes stay laid out and absolutely positioned, sliding
2576
+ via one shared `Animated.Value`, since RN `transform` has no
2577
+ percentage-of-self units to use a bare `-100%`/`100%` the way the web CSS
2578
+ does. The web's `inert` attribute (removes the hidden pane from the a11y
2579
+ tree and tab order while it stays mounted off-screen) has no single RN
2580
+ equivalent — reproduced with `accessibilityElementsHidden` +
2581
+ `importantForAccessibility="no-hide-descendants"` plus `pointerEvents="none"`
2582
+ so the off-screen pane can't intercept touches meant for the visible one.
2583
+
2410
2584
  ### FilterableMultiSelectDropdown
2411
2585
 
2412
2586
  ```tsx
@@ -2486,6 +2660,35 @@ two independent `accessibilityRole="adjustable"` elements (one per thumb),
2486
2660
  the same VoiceOver/TalkBack increment/decrement analog `Slider` already
2487
2661
  established.
2488
2662
 
2663
+ ### RatingStars
2664
+
2665
+ ```tsx
2666
+ import { RatingStars } from '@gnome-ui/react-native';
2667
+
2668
+ // Read-only — omit onChange
2669
+ <RatingStars value={4.2} accessibilityLabel="Average rating: 4.2 out of 5" />
2670
+
2671
+ // Interactive — pass onChange
2672
+ <RatingStars value={rating} onChange={setRating} />
2673
+ ```
2674
+
2675
+ Star rating display and input — mirrors `@gnome-ui/react`'s `RatingStars`.
2676
+ Renders `role="radiogroup"` of `role="radio"` stars when `onChange` is
2677
+ provided, or a static `role="img"` when it isn't (e.g. showing an
2678
+ average/read-only rating); passing `disabled` always falls back to the
2679
+ read-only display even with `onChange` provided. The web version's
2680
+ `radiogroup` layer also owns an `onKeyDown` handler for arrow-key
2681
+ roving-tabindex navigation and a mouse-hover preview that doesn't commit
2682
+ until clicked — neither has a touch counterpart, so both drop, the same
2683
+ standing convention `ToggleGroup` already established for this package.
2684
+ What's left, tapping a star to commit that rating, is a strict subset of
2685
+ the web interaction rather than an approximation of it. Each star's fill
2686
+ uses `tintColor={theme.warningBgColor}` rather than `Icon`'s fixed
2687
+ `color="yellow"` — the source CSS reads `var(--gnome-warning-bg-color)`
2688
+ directly with no `color-mix()` darkening step, so the semantic warning
2689
+ token (already tracking dark mode and every contrast level) is the exact
2690
+ match, not an approximation through the fixed palette.
2691
+
2489
2692
  ### StatusBadge
2490
2693
 
2491
2694
  ```tsx
@@ -2504,6 +2707,112 @@ counter. Six variants (`success`/`warning`/`error`/`new`/`accent`/
2504
2707
  `neutral`) reuse `Badge`'s exact color-mapping shape, plus a `new` (purple)
2505
2708
  variant `Badge` doesn't have.
2506
2709
 
2710
+ ### StepIndicator
2711
+
2712
+ ```tsx
2713
+ import { StepIndicator } from '@gnome-ui/react-native';
2714
+
2715
+ <StepIndicator steps={5} currentStep={1} />
2716
+
2717
+ <StepIndicator
2718
+ steps={['Account', 'Profile', 'Payment', 'Confirm']}
2719
+ currentStep={2}
2720
+ onStepClick={setCurrentStep}
2721
+ />
2722
+
2723
+ <StepIndicator steps={4} currentStep={2} orientation="vertical" />
2724
+ ```
2725
+
2726
+ Numbered "Step X of Y" progress indicator for onboarding/wizard flows —
2727
+ mirrors `@gnome-ui/react`'s `StepIndicator`. Directly portable, no web-only
2728
+ APIs involved: each step's circle derives its state (upcoming/current/
2729
+ completed) purely from `currentStep`. Each circle animates its own
2730
+ border/background color (an independent pair of 0/1 `Animated.Value`s,
2731
+ one for "accented border" and one for "filled background", since they
2732
+ flip on different transitions) — the content swap between number and
2733
+ checkmark and the connector-line recolor are instant, matching the source
2734
+ CSS exactly (only `background-color`/`border-color` transition there).
2735
+
2736
+ The connecting line between circles reuses the CSS trick verbatim —
2737
+ `position: absolute; left: '50%'; width: '100%'` inside each equal-width
2738
+ flex item, so the line runs from one circle's center to the next's;
2739
+ `left`/`width` percentages are valid RN position/dimension values, unlike
2740
+ the `transform: translateX('50%')` trick this package avoids elsewhere.
2741
+ The checkmark uses `tintColor` rather than `color`, since RN icons have no
2742
+ `currentColor` to inherit — the same call `BottomTabBar`'s active-tab icon
2743
+ already made for tracking the *configurable* accent.
2744
+
2745
+ The outer container sets `role="navigation"` **without** `accessible` —
2746
+ the `ToggleGroup`-established pattern for a grouping role over multiple
2747
+ independently-focusable children (each step circle): `accessible` here
2748
+ would collapse the whole indicator into one VoiceOver stop on iOS. Assert
2749
+ `element.props.role` on a `testID` in tests instead of
2750
+ `getByRole('navigation')`. A completed step's circle becomes a `Pressable`
2751
+ only when `onStepClick` is provided — the current and upcoming steps are
2752
+ never pressable, matching the web version.
2753
+
2754
+ ### Timeline
2755
+
2756
+ ```tsx
2757
+ import { Timeline } from '@gnome-ui/react-native';
2758
+
2759
+ <Timeline
2760
+ items={[
2761
+ { leading: <Text color="dim">10:00</Text>, icon: <Icon icon={Check} tintColor={theme.accentFgColor} />, content: <Text>Approved</Text> },
2762
+ { content: <Text>Pending review</Text> },
2763
+ ]}
2764
+ />
2765
+
2766
+ <Timeline orientation="horizontal" variant="dotted" items={steps} />
2767
+ ```
2768
+
2769
+ Ordered sequence of events connected by a visual timeline — mirrors
2770
+ `@gnome-ui/react`'s `Timeline`. An original composition (no direct
2771
+ libadwaita widget), following GNOME HIG activity-feed/stepper patterns.
2772
+
2773
+ The web version aligns every item's `leading` column (vertical) or row
2774
+ (horizontal) via CSS subgrid, so timestamps/labels line up across items
2775
+ regardless of how wide/tall any single one of them is. RN/Yoga has no
2776
+ grid or subgrid at all, so that alignment is reproduced by measurement
2777
+ instead — the same `onLayout` + `Record<index, size>` +
2778
+ "largest-so-far wins" technique `Slider`'s mark labels already
2779
+ established. The node track itself additionally gets a fixed width
2780
+ (vertical, 24 dp, matching the source CSS's literal grid column) or
2781
+ height (horizontal, 28 dp, the larger of the dot/icon node sizes) so
2782
+ `content` starts at the same position across items even when dot and
2783
+ icon nodes are mixed in the same list.
2784
+
2785
+ `orientation="horizontal"` wraps itself in a horizontal `ScrollView`,
2786
+ reimagining the web CSS's `overflow-x: auto`. The web version's
2787
+ `grid-auto-columns: minmax(72px, 1fr)` also grows items to fill leftover
2788
+ space when the row doesn't overflow; that half doesn't port (a
2789
+ `ScrollView`'s content isn't bounded the way a CSS grid track is) — each
2790
+ item gets a flat 72 dp `minWidth` instead, unconditionally scrollable.
2791
+
2792
+ **Real, on-device-confirmed platform bug found while building `variant="dotted"`**:
2793
+ RN's `borderStyle: 'dotted'` renders nothing at all — no error, just
2794
+ invisible — unless *every* side shares the same width and color; the
2795
+ direct 1:1 port of the web CSS's single-side `border-left`/`border-top`
2796
+ dotted line (one bordered side, the other three left unset) silently
2797
+ produced no line whatsoever. Confirmed via the iOS Simulator with
2798
+ saturated debug colors: a uniform four-side `borderWidth`/`borderColor`
2799
+ renders the dotted pattern correctly, but reintroducing even three
2800
+ `transparent` sides (uniform width, per-side color) suppresses it again.
2801
+ Fixed by drawing the dotted connector as a narrow (6 dp) box with a
2802
+ uniform dotted border on all four sides instead of a single bordered
2803
+ edge — visually indistinguishable from a single dotted line at this
2804
+ thickness. **Any future dotted/dashed RN border needs all sides
2805
+ width-and-color-uniform — a single-side border in that style silently
2806
+ renders invisible.**
2807
+
2808
+ `icon`/`leading`/`content` are plain `ReactNode`, the same as `PathBar`'s
2809
+ segment `icon` — the consumer sizes and colors their own icon (e.g.
2810
+ `tintColor={theme.accentFgColor}`), since RN has no `currentColor` for
2811
+ this component to tint an arbitrary child with. `role="list"`/
2812
+ `role="listitem"` are set without `accessible`, the same
2813
+ `ToggleGroup`/`StepIndicator`-established pattern for a grouping role
2814
+ over children that may themselves contain focusable content.
2815
+
2507
2816
  ### WidgetManager
2508
2817
 
2509
2818
  ```tsx
@@ -2647,6 +2956,55 @@ threshold. Unlike the web version there's no `data-breakpoint` attribute
2647
2956
  to expose for CSS targeting (RN has no attribute selectors) — branch on
2648
2957
  `activeBreakpoint` directly inside the render prop instead.
2649
2958
 
2959
+ ### ScrollToTop
2960
+
2961
+ ```tsx
2962
+ import { ScrollToTop } from '@gnome-ui/react-native';
2963
+
2964
+ const scrollRef = useRef<ScrollView>(null);
2965
+ const [scrollY, setScrollY] = useState(0);
2966
+
2967
+ <View style={{ flex: 1 }}>
2968
+ <ScrollView
2969
+ ref={scrollRef}
2970
+ onScroll={(e) => setScrollY(e.nativeEvent.contentOffset.y)}
2971
+ scrollEventThrottle={16}
2972
+ >
2973
+ {/* ... */}
2974
+ </ScrollView>
2975
+ <ScrollToTop
2976
+ scrollY={scrollY}
2977
+ onPress={() => scrollRef.current?.scrollTo({ y: 0, animated: true })}
2978
+ />
2979
+ </View>;
2980
+ ```
2981
+
2982
+ Absolutely-positioned OSD button that scrolls a `ScrollView`/`FlatList`
2983
+ back to the top — mirrors `@gnome-ui/react`'s `ScrollToTop`, reimagined
2984
+ rather than ported 1:1: RN has no page-level scroll event to observe
2985
+ internally, so the web version's `useScrollToTopVisibility` (a `window`/
2986
+ element `scroll` listener) doesn't port at all. `visible="auto"` (the
2987
+ default) is instead a pure function of a `scrollY` number prop the
2988
+ consumer feeds from their own `ScrollView`'s `onScroll` — no internal
2989
+ state or listener needed. There's likewise no way for this component to
2990
+ scroll a `ScrollView`/`FlatList` on the consumer's behalf the way the web
2991
+ version calls `scrollTarget.scrollTo(...)` itself, so pressing the button
2992
+ calls the required `onPress` instead — typically
2993
+ `scrollRef.current?.scrollTo({ y: 0, animated: true })`.
2994
+
2995
+ Same "no `document.body`/portal target" gap `Toaster` already documents —
2996
+ mount this yourself as the last child of the `View` wrapping your
2997
+ scrollable content so it paints on top; `pointerEvents="box-none"` (the
2998
+ same technique `Toaster` uses) keeps the empty space around the button
2999
+ from intercepting touches meant for the content underneath. The web
3000
+ version's resting `opacity: 0.5`-until-hover/focus is dropped, the same
3001
+ call `PasswordEntryRow`'s reveal button already made — that effect exists
3002
+ purely so the control can brighten on hover, and touch has no hover.
3003
+ `topInset`/`bottomInset` let you thread in your own
3004
+ `useSafeAreaInsets()` values for a `"top-*"`/`"bottom-*"` position, same
3005
+ as `BottomTabBar`'s `bottomInset` — this package takes no dependency on
3006
+ `react-native-safe-area-context` itself.
3007
+
2650
3008
  ## Installation
2651
3009
 
2652
3010
  ```bash
@@ -0,0 +1,44 @@
1
+ import { IconButtonProps } from '../IconButton';
2
+ export interface CopyButtonProps extends Omit<IconButtonProps, 'icon' | 'label' | 'onPress' | 'tooltip'> {
3
+ /** The text copied to the clipboard when the button is activated. */
4
+ value: string;
5
+ /** Accessible label and tooltip shown before copying. Defaults to `"Copy"`. */
6
+ label?: string;
7
+ /** Accessible label and tooltip shown briefly after a successful copy. Defaults to `"Copied!"`. */
8
+ copiedLabel?: string;
9
+ /** How long the "copied" confirmation state is shown, in milliseconds. Defaults to `2000`. */
10
+ resetDelay?: number;
11
+ /**
12
+ * Called after `value` is written to the clipboard.
13
+ * Named `onCopied` (not `onCopy`), mirroring the web version's own naming.
14
+ */
15
+ onCopied?: (value: string) => void;
16
+ /**
17
+ * Called if the copy attempt throws. Kept for API parity with the web
18
+ * version's `onCopyError` (whose `navigator.clipboard.writeText` can
19
+ * reject, e.g. on permission denial) — but `@react-native-clipboard/
20
+ * clipboard`'s `setString` is a synchronous, void-returning native call
21
+ * with no error channel at all, so in normal operation this won't fire
22
+ * the way it can on web. Kept as a defensive catch around the native
23
+ * call (a real, if unlikely, synchronous throw) rather than removed.
24
+ */
25
+ onCopyError?: (error: unknown) => void;
26
+ }
27
+ /**
28
+ * Icon button that copies `value` to the clipboard, swapping to a checkmark
29
+ * and a "Copied!" tooltip for `resetDelay` ms as confirmation — mirrors
30
+ * `@gnome-ui/react`'s `CopyButton`.
31
+ *
32
+ * RN has no `navigator.clipboard`, so this is built on
33
+ * `@react-native-clipboard/clipboard` (the community-standard clipboard
34
+ * module, not `expo-clipboard` — chosen so this package works the same in
35
+ * bare RN and Expo, not just this repo's own Expo example app). Its
36
+ * `setString` is synchronous and void — see `onCopyError`'s doc for what
37
+ * that means for error handling.
38
+ *
39
+ * The live-region announcement uses `role="status"` directly (RN's newer
40
+ * `Role` union does include `"status"`, unlike the older
41
+ * `AccessibilityRole` enum `Toast` had to substitute `"alert"` for) plus
42
+ * `accessibilityLiveRegion="polite"` for Android's announcement mechanism.
43
+ */
44
+ export declare const CopyButton: ({ value, label, copiedLabel, resetDelay, onCopied, onCopyError, ...props }: CopyButtonProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { CopyButtonProps } from './CopyButton';
2
+ export { CopyButton } from './CopyButton';
@@ -0,0 +1,50 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, View, ViewStyle } from 'react-native';
3
+ export interface LinkedGroupProps {
4
+ /**
5
+ * Each child must accept and merge a `style` prop the way every
6
+ * `@gnome-ui/react-native` component already does (`style` last in its
7
+ * own internal style array) — see the component doc for why.
8
+ */
9
+ children: ReactNode;
10
+ /** Stack children vertically instead of horizontally. */
11
+ vertical?: boolean;
12
+ style?: StyleProp<ViewStyle>;
13
+ testID?: string;
14
+ }
15
+ /**
16
+ * Renders children as a single visually-connected unit with no gap and
17
+ * merged borders — the canonical GNOME pattern for button groups and
18
+ * segmented inputs. Mirrors `@gnome-ui/react`'s `LinkedGroup`, itself
19
+ * mirroring the libadwaita `.linked` style class.
20
+ *
21
+ * The web version reaches every child's border-radius via a CSS
22
+ * `> *` universal child selector — RN has no equivalent way for a parent
23
+ * `View` to reach into an arbitrary child's own internally-computed
24
+ * styles. Reimagined instead as the same `cloneElement`-onto-children
25
+ * technique `Popover`/`Tooltip` already use on their own trigger:
26
+ * each child gets a computed corner-radius/negative-margin override
27
+ * merged onto whatever `style` it already has, using the same
28
+ * "zero the shared inner corners, keep `theme.radiusMd` on the outer
29
+ * ones, overlap by 1 dp to collapse the shared border" recipe
30
+ * `SplitButton` already proved for its own two-piece connected border —
31
+ * generalized here from a fixed two children to an arbitrary list. This
32
+ * only works because every component in this package already merges a
33
+ * passed-in `style` prop last, the same assumption `Popover`'s own
34
+ * trigger-cloning already depends on.
35
+ *
36
+ * The web CSS also raises a hovered/focused child's `z-index` so its own
37
+ * border isn't visually covered by the next sibling's overlapping edge —
38
+ * dropped here: RN is touch-first (no `:hover`), and no component in this
39
+ * package currently renders an escaping focus ring that overlap could
40
+ * clip, so there's nothing yet for the z-index bump to protect.
41
+ *
42
+ * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/1-latest/style-classes.html#linked-style-class
43
+ *
44
+ * @example
45
+ * <LinkedGroup>
46
+ * <Button variant="flat">Bold</Button>
47
+ * <Button variant="flat">Italic</Button>
48
+ * </LinkedGroup>
49
+ */
50
+ export declare const LinkedGroup: import('react').ForwardRefExoticComponent<LinkedGroupProps & import('react').RefAttributes<View>>;
@@ -0,0 +1,2 @@
1
+ export type { LinkedGroupProps } from './LinkedGroup';
2
+ export { LinkedGroup } from './LinkedGroup';
@@ -0,0 +1,69 @@
1
+ import { ReactNode } from 'react';
2
+ import { StyleProp, ViewStyle } from 'react-native';
3
+ export interface NavigationSplitViewProps {
4
+ /**
5
+ * The sidebar / list pane (left side on wide screens).
6
+ * On narrow screens this is the "list" view shown when `showContent` is false.
7
+ */
8
+ sidebar: ReactNode;
9
+ /**
10
+ * The detail / content pane (right side on wide screens).
11
+ * On narrow screens this is the "detail" view shown when `showContent` is true.
12
+ */
13
+ content: ReactNode;
14
+ /**
15
+ * Controls which pane is visible on narrow screens (≤ 400 dp).
16
+ * - `false` (default) — show the sidebar list.
17
+ * - `true` — show the content detail.
18
+ *
19
+ * Has no effect on wide screens where both panes are visible simultaneously.
20
+ */
21
+ showContent?: boolean;
22
+ /** Minimum sidebar width in dp. Defaults to `180`. */
23
+ minSidebarWidth?: number;
24
+ /** Maximum sidebar width in dp. Defaults to `280`. */
25
+ maxSidebarWidth?: number;
26
+ /** Fraction of total width given to the sidebar (0–1). Defaults to `0.25`. */
27
+ sidebarWidthFraction?: number;
28
+ style?: StyleProp<ViewStyle>;
29
+ testID?: string;
30
+ }
31
+ /**
32
+ * Two-pane sidebar + content layout following the Adwaita
33
+ * `AdwNavigationSplitView` pattern — mirrors `@gnome-ui/react`'s
34
+ * `NavigationSplitView`.
35
+ *
36
+ * On **wide** screens (`useBreakpoint().isNarrow === false`, > 400 dp) both
37
+ * panes are visible side-by-side, separated by a `Separator`. On **narrow**
38
+ * screens only one pane is shown at a time; `showContent` switches between
39
+ * the sidebar list and the detail view.
40
+ *
41
+ * The web version's `clamp(min, fraction * 100%, max)` sidebar width has no
42
+ * RN equivalent (`StyleSheet` values aren't CSS `calc`/`clamp` expressions),
43
+ * so the container measures its own width via `onLayout` and the same clamp
44
+ * is computed in JS — `0` until the first layout pass resolves, the same
45
+ * one-frame imprecision this package already accepts elsewhere (e.g.
46
+ * `ProgressBar`'s `trackWidth`-dependent math).
47
+ *
48
+ * Narrow-mode pane switching is animated (`translateX`, matching the web
49
+ * CSS's own `transition: transform`), so — unlike `TabPanel`'s simpler
50
+ * `display: 'none'` swap — both panes stay laid out and absolutely
51
+ * positioned rather than being removed from flow, using the same measured
52
+ * container width for the slide distance (RN `transform` has no
53
+ * percentage-of-self units, so this can't be a bare `-100%`/`100%` the way
54
+ * the web version's CSS is). One shared `Animated.Value` drives both
55
+ * panes' opposite-direction translation, the same "skip the animation on
56
+ * initial mount, only animate subsequent prop changes" guard `Switch`/
57
+ * `StepIndicator` already established for a controlled boolean prop.
58
+ *
59
+ * The web's `inert` attribute (removes the hidden pane from both the a11y
60
+ * tree and the tab order while it stays mounted off-screen) has no single
61
+ * RN equivalent — reproduced with `accessibilityElementsHidden` +
62
+ * `importantForAccessibility="no-hide-descendants"` (the whole-subtree a11y
63
+ * exclusion, not just the single-element `"no"` `PathBar`'s decorative
64
+ * separator uses) plus `pointerEvents="none"` so the off-screen pane can't
65
+ * intercept touches meant for the visible one.
66
+ *
67
+ * @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.NavigationSplitView.html
68
+ */
69
+ export declare const NavigationSplitView: ({ sidebar, content, showContent, minSidebarWidth, maxSidebarWidth, sidebarWidthFraction, style, testID, }: NavigationSplitViewProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { NavigationSplitViewProps } from './NavigationSplitView';
2
+ export { NavigationSplitView } from './NavigationSplitView';
@@ -0,0 +1,48 @@
1
+ import { StyleProp, ViewStyle } from 'react-native';
2
+ import { IconSize } from '../Icon';
3
+ export interface RatingStarsProps {
4
+ /** Current rating, between `0` and `max`. */
5
+ value: number;
6
+ /** Number of stars. Defaults to `5`. */
7
+ max?: number;
8
+ /**
9
+ * Called when the user picks a rating. Omit to render a read-only
10
+ * display (e.g. an average rating) instead of an interactive input.
11
+ */
12
+ onChange?: (value: number) => void;
13
+ /** Star size. Defaults to `"md"`. */
14
+ size?: IconSize;
15
+ /** Renders as read-only even when `onChange` is provided. */
16
+ disabled?: boolean;
17
+ /** Accessible label. Defaults to `"Rating"` (interactive) or a generated `"N out of M stars"` (read-only). */
18
+ accessibilityLabel?: string;
19
+ style?: StyleProp<ViewStyle>;
20
+ testID?: string;
21
+ }
22
+ /**
23
+ * Star rating display and input. Mirrors `@gnome-ui/react`'s `RatingStars`.
24
+ *
25
+ * Renders `role="radiogroup"` of `role="radio"` stars when `onChange` is
26
+ * provided, or a static `role="img"` when it isn't — e.g. for showing an
27
+ * average/read-only rating.
28
+ *
29
+ * The web version's `role="radiogroup"` layer also owns an `onKeyDown`
30
+ * handler for arrow-key roving-tabindex navigation and a mouse-hover
31
+ * preview that doesn't commit until clicked; neither has a touch
32
+ * counterpart, so both drop — same standing convention `ToggleGroup`
33
+ * already established for this package (its own doc comment covers the
34
+ * reasoning). What's left, tapping a star to commit that rating, is a
35
+ * strict subset of the web interaction, not an approximation of it.
36
+ *
37
+ * Each star's fill color is `tintColor={theme.warningBgColor}` rather than
38
+ * `Icon`'s own fixed `color="yellow"` (→ `theme.yellow4`, a different hex):
39
+ * the source CSS reads `var(--gnome-warning-bg-color, #f6d32d)` directly
40
+ * with no `color-mix()` darkening step, so the semantic warning token
41
+ * itself — already tracking dark mode and every contrast level — is the
42
+ * exact match, not an approximation through the fixed palette. Stars carry
43
+ * no `label` on `Icon` (decorative — `iconAccessibilityProps` already hides
44
+ * an unlabeled icon from the tree); the accessible name lives on the
45
+ * container (read-only) or each `Pressable` (interactive) instead, mirroring
46
+ * `ToggleGroupItem`'s icon-only items.
47
+ */
48
+ export declare const RatingStars: ({ value, max, onChange, size, disabled, accessibilityLabel, style, testID, }: RatingStarsProps) => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,2 @@
1
+ export type { RatingStarsProps } from './RatingStars';
2
+ export { RatingStars } from './RatingStars';