@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 +358 -0
- package/dist/components/CopyButton/CopyButton.d.ts +44 -0
- package/dist/components/CopyButton/index.d.ts +2 -0
- package/dist/components/LinkedGroup/LinkedGroup.d.ts +50 -0
- package/dist/components/LinkedGroup/index.d.ts +2 -0
- package/dist/components/NavigationSplitView/NavigationSplitView.d.ts +69 -0
- package/dist/components/NavigationSplitView/index.d.ts +2 -0
- package/dist/components/RatingStars/RatingStars.d.ts +48 -0
- package/dist/components/RatingStars/index.d.ts +2 -0
- package/dist/components/ScrollToTop/ScrollToTop.d.ts +105 -0
- package/dist/components/ScrollToTop/index.d.ts +2 -0
- package/dist/components/Spacer/Spacer.d.ts +25 -0
- package/dist/components/Spacer/index.d.ts +2 -0
- package/dist/components/StepIndicator/StepIndicator.d.ts +56 -0
- package/dist/components/StepIndicator/index.d.ts +2 -0
- package/dist/components/SwitchRow/SwitchRow.d.ts +43 -0
- package/dist/components/SwitchRow/index.d.ts +2 -0
- package/dist/components/Timeline/Timeline.d.ts +87 -0
- package/dist/components/Timeline/index.d.ts +2 -0
- package/dist/components/Toolbar/Toolbar.d.ts +25 -0
- package/dist/components/Toolbar/index.d.ts +2 -0
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +10 -0
- package/dist/index.js +1450 -785
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
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,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,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,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;
|