@gnome-ui/react-native 1.1.0 → 1.3.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 +190 -4
- package/dist/components/AnimatedIcon/AnimatedIcon.d.ts +38 -0
- package/dist/components/AnimatedIcon/index.d.ts +2 -0
- package/dist/components/Dropdown/Dropdown.d.ts +56 -0
- package/dist/components/Dropdown/index.d.ts +2 -0
- package/dist/components/Icon/Icon.d.ts +56 -0
- package/dist/components/Icon/index.d.ts +2 -0
- package/dist/components/Icon/shared.d.ts +19 -0
- package/dist/components/Slider/Slider.d.ts +56 -0
- package/dist/components/Slider/index.d.ts +2 -0
- package/dist/components/Tooltip/Tooltip.d.ts +88 -0
- package/dist/components/Tooltip/index.d.ts +2 -0
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1289 -470
- package/dist/index.js.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -14,9 +14,12 @@ React Native component library following the [GNOME Human Interface Guidelines](
|
|
|
14
14
|
> `Link`, `TextField`, `Switch`, `Checkbox`, `RadioButton`), Tier 2 Layout &
|
|
15
15
|
> Containers (`Separator`, `Card`, `BoxedList`, `ActionRow`, `HeaderBar`),
|
|
16
16
|
> and Tier 3 Navigation (`Tabs`, `ViewSwitcher`, `Sidebar`, `SearchBar`,
|
|
17
|
-
> `PathBar`) fully ported. Tier 4 Feedback
|
|
18
|
-
> `
|
|
19
|
-
>
|
|
17
|
+
> `PathBar`) fully ported. Tier 4 Feedback: `Spinner`, `ProgressBar`,
|
|
18
|
+
> `Skeleton`, `Toast`/`Toaster`, `Banner`, `Dialog`, `Tooltip`, and
|
|
19
|
+
> `AnimatedIcon` (which brought a new `Icon` component along with it, as its
|
|
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
|
|
20
23
|
> ports from `@gnome-ui/react` continue tier by tier. See
|
|
21
24
|
> [ROADMAP.md](../../ROADMAP.md) Priority 3.
|
|
22
25
|
|
|
@@ -790,12 +793,195 @@ consistency with the established pattern, but this is the component where
|
|
|
790
793
|
that tradeoff matters most — worth prioritizing for real-device screen
|
|
791
794
|
reader verification before it's treated as settled.
|
|
792
795
|
|
|
796
|
+
### Tooltip
|
|
797
|
+
|
|
798
|
+
```tsx
|
|
799
|
+
import { Button, Tooltip } from '@gnome-ui/react-native';
|
|
800
|
+
|
|
801
|
+
<Tooltip label="Save file" placement="top">
|
|
802
|
+
<Button accessibilityLabel="Save">Save</Button>
|
|
803
|
+
</Tooltip>;
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
Floating informational label. Positioned automatically and flips to the
|
|
807
|
+
opposite side (then to whichever side actually fits) when the preferred
|
|
808
|
+
placement has no room — the same algorithm as `@gnome-ui/react`'s
|
|
809
|
+
`Tooltip`, measured with `measureInWindow()` instead of
|
|
810
|
+
`getBoundingClientRect()`.
|
|
811
|
+
|
|
812
|
+
**Trigger differs from the web version by necessity**: the web `Tooltip`
|
|
813
|
+
only shows on mouse hover / keyboard focus — touch has no hover state, so
|
|
814
|
+
it explicitly never shows on touch. RN is touch-first, so the primary
|
|
815
|
+
trigger here is **long-press** (`delayLongPress={delay}`, released via
|
|
816
|
+
`onPressOut`) — the standard mobile "peek" idiom. `onHoverIn`/`onHoverOut`
|
|
817
|
+
are also wired for hover-capable input (trackpad/mouse on iPad, or a
|
|
818
|
+
pointer-driven RN target) and `onFocus`/`onBlur` for external-keyboard
|
|
819
|
+
accessibility, both delayed the same way the web version delays hover.
|
|
820
|
+
|
|
821
|
+
Built on RN's own `Modal` (transparent, `pointerEvents="box-none"`), the
|
|
822
|
+
same portal-substitute `Dialog` uses. `role="tooltip"` ports 1:1 — RN's
|
|
823
|
+
`Role` union already has a `"tooltip"` value. `aria-describedby` has no RN
|
|
824
|
+
equivalent, so the label is set as the trigger's `accessibilityHint`
|
|
825
|
+
instead (unless the trigger already provides its own). The bubble's arrow
|
|
826
|
+
reuses the same zero-size / transparent-border-on-three-sides triangle
|
|
827
|
+
trick as the web CSS — RN `View`s support per-side `border*Color` too, the
|
|
828
|
+
same technique `Spinner`'s ring already relies on.
|
|
829
|
+
|
|
830
|
+
Not ported: repositioning on scroll/resize while visible (RN has no global
|
|
831
|
+
scroll event, and a long-press is naturally cancelled by a scroll gesture
|
|
832
|
+
starting). `tooltipBgColor`/`tooltipFgColor` aren't real `@gnome-ui/core`
|
|
833
|
+
tokens (only CSS var fallbacks), so the RN port hardcodes the same literal
|
|
834
|
+
light/dark values, the same workaround `Spinner`'s track color established.
|
|
835
|
+
|
|
836
|
+
### Icon
|
|
837
|
+
|
|
838
|
+
```tsx
|
|
839
|
+
import { Search } from '@gnome-ui/icons';
|
|
840
|
+
import { Icon } from '@gnome-ui/react-native';
|
|
841
|
+
|
|
842
|
+
<Icon icon={Search} label="Search" size="lg" color="blue" />;
|
|
843
|
+
```
|
|
844
|
+
|
|
845
|
+
Renders an icon as an inline SVG via `react-native-svg` (a new peer
|
|
846
|
+
dependency — this package's first). Accepts the same `AnyIconDefinition`
|
|
847
|
+
union as `@gnome-ui/react`'s `Icon`: a structured `paths`-based
|
|
848
|
+
`IconDefinition` from `@gnome-ui/icons`, a `simple-icons` `SimpleIcon`, or a
|
|
849
|
+
plain `{ path }` object. `color` picks a named GNOME palette hue
|
|
850
|
+
(`theme.blue3`, `theme.red3`, …) — RN has no `currentColor` to inherit from
|
|
851
|
+
a parent the way the web version does, so omitting `color` resolves to the
|
|
852
|
+
theme's default foreground color explicitly instead.
|
|
853
|
+
|
|
854
|
+
`animated` icons (`Syncing`, `Recording`, `Downloading`, `Connecting`) carry
|
|
855
|
+
raw `svg` markup instead of `paths` — rendered here through `react-native-
|
|
856
|
+
svg`'s `SvgXml`. It parses the structural elements (`<g>`/`<path>`/
|
|
857
|
+
`<circle>`) but has no CSS engine, so the markup's embedded `<style>`/
|
|
858
|
+
`@keyframes` block is silently dropped and the shapes render at their
|
|
859
|
+
authored rest position — which happens to be exactly the desired inert,
|
|
860
|
+
static-frame behavior for a plain `<Icon>`, no special-casing needed. Wrap
|
|
861
|
+
in `<AnimatedIcon>` to actually play the motion.
|
|
862
|
+
|
|
863
|
+
### AnimatedIcon
|
|
864
|
+
|
|
865
|
+
```tsx
|
|
866
|
+
import { Syncing } from '@gnome-ui/icons';
|
|
867
|
+
import { AnimatedIcon } from '@gnome-ui/react-native';
|
|
868
|
+
|
|
869
|
+
<AnimatedIcon icon={Syncing} playing={isSyncing} label="Syncing" />;
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
Plays the motion for a known `animated` icon (`Syncing`, `Recording`,
|
|
873
|
+
`Downloading`, `Connecting`) — rendered through plain `<Icon>`, these show a
|
|
874
|
+
static frame instead, same as `@gnome-ui/react`'s `AnimatedIcon`.
|
|
875
|
+
|
|
876
|
+
Unlike the web version (which plays a CSS animation embedded in the icon's
|
|
877
|
+
raw `svg` markup via a `--gnome-icon-play-state` custom property), RN has
|
|
878
|
+
no CSS engine to interpret `@keyframes` at all. Each of the 4 known icons'
|
|
879
|
+
motion is instead hand-built with `Animated`, matched by referential
|
|
880
|
+
identity against `@gnome-ui/icons`' own exports (`Syncing` → full-turn
|
|
881
|
+
rotation, `Recording` → opacity pulse, `Downloading` → a translate+opacity
|
|
882
|
+
"drop" on the arrow over a static tray, `Connecting` → three signal dots
|
|
883
|
+
pulsing in a staggered sweep) — an icon `AnimatedIcon` doesn't recognize
|
|
884
|
+
(a future 5th animated icon, or a consumer-authored one) falls back to the
|
|
885
|
+
static `<Icon>` frame rather than throwing. Regardless of `playing`, the
|
|
886
|
+
animation is always paused when the OS reduced-motion setting is on.
|
|
887
|
+
|
|
888
|
+
### Dropdown
|
|
889
|
+
|
|
890
|
+
```tsx
|
|
891
|
+
import { Dropdown } from '@gnome-ui/react-native';
|
|
892
|
+
|
|
893
|
+
<Dropdown
|
|
894
|
+
options={[
|
|
895
|
+
{ value: 'blue', label: 'Blue' },
|
|
896
|
+
{ value: 'green', label: 'Green', description: 'A calm accent' },
|
|
897
|
+
]}
|
|
898
|
+
value={accentColor}
|
|
899
|
+
onChange={setAccentColor}
|
|
900
|
+
placeholder="Accent color"
|
|
901
|
+
/>;
|
|
902
|
+
```
|
|
903
|
+
|
|
904
|
+
Expandable option list following the Adwaita combo-row pattern, mirroring
|
|
905
|
+
`@gnome-ui/react`'s `Dropdown`.
|
|
906
|
+
|
|
907
|
+
Built on RN's own `Modal` (transparent) — the same portal-substitute
|
|
908
|
+
`Dialog`/`Tooltip` already use — with a full-screen backdrop `Pressable`
|
|
909
|
+
that closes the list on an outside tap (the RN analog of the web version's
|
|
910
|
+
document-level "click outside" listener; unlike `Tooltip`'s backdrop, this
|
|
911
|
+
one isn't `pointerEvents="box-none"`, since it's meant to catch that tap
|
|
912
|
+
rather than pass it through). `open` flips synchronously on trigger press;
|
|
913
|
+
the trigger's on-screen rect and the panel's own rendered height each
|
|
914
|
+
resolve independently into state, combined by a separate effect into the
|
|
915
|
+
final position and flip-up/flip-down direction — the same two-independent-
|
|
916
|
+
async-measurements pattern `Tooltip` established.
|
|
917
|
+
|
|
918
|
+
Keyboard navigation (↑/↓ roving highlight, Home/End, type-ahead) has no
|
|
919
|
+
port — RN's touch-first model has no keyboard focus to drive it, the same
|
|
920
|
+
reasoning that already dropped `TabBar`'s roving-tabindex arrow keys.
|
|
921
|
+
Selection is by direct tap only. `role="combobox"` on the trigger ports
|
|
922
|
+
1:1; RN's `Role` union has no `"listbox"` value, so the panel uses
|
|
923
|
+
`role="list"` instead — the same closest-available substitution `BoxedList`
|
|
924
|
+
already established for a plain list container.
|
|
925
|
+
|
|
926
|
+
### Slider
|
|
927
|
+
|
|
928
|
+
```tsx
|
|
929
|
+
import { Slider } from '@gnome-ui/react-native';
|
|
930
|
+
|
|
931
|
+
<Slider
|
|
932
|
+
value={volume}
|
|
933
|
+
onChange={setVolume}
|
|
934
|
+
accessibilityLabel="Volume"
|
|
935
|
+
marks={[
|
|
936
|
+
{ value: 0, label: 'Min' },
|
|
937
|
+
{ value: 100, label: 'Max' },
|
|
938
|
+
]}
|
|
939
|
+
/>;
|
|
940
|
+
```
|
|
941
|
+
|
|
942
|
+
Draggable range control following the Adwaita `GtkScale` pattern, mirroring
|
|
943
|
+
`@gnome-ui/react`'s `Slider`.
|
|
944
|
+
|
|
945
|
+
Touch drag is handled with RN's own `PanResponder` (this package's first use
|
|
946
|
+
of it) reading each touch event's `locationX` — the position relative to the
|
|
947
|
+
track view itself, recalculated by RN on every touch/move — so no
|
|
948
|
+
`measureInWindow` round-trip is needed at all, unlike `Tooltip`/`Dropdown`'s
|
|
949
|
+
trigger-rect measurement. `min`/`max`/`step` clamping and snapping is ported
|
|
950
|
+
verbatim from the web version's pure-JS math.
|
|
951
|
+
|
|
952
|
+
The web version's keyboard interaction (← / → one step, Page Up/Down ten
|
|
953
|
+
steps, Home/End to the bounds) has no RN equivalent — a touch-first device
|
|
954
|
+
has no keyboard driving those keys. Rather than dropping value-adjustment
|
|
955
|
+
accessibility entirely (the reasoning that dropped `Dropdown`'s/`TabBar`'s
|
|
956
|
+
keyboard nav), `accessibilityRole="adjustable"` +
|
|
957
|
+
`onAccessibilityAction`/`accessibilityActions` wires up the "increment"/
|
|
958
|
+
"decrement" actions VoiceOver's swipe-up/down and TalkBack's local-context
|
|
959
|
+
menu generate for an adjustable element — the real native analog of
|
|
960
|
+
keyboard stepping, one step per action. The bigger Page Up/Down and
|
|
961
|
+
Home/End jumps have no equivalent screen-reader gesture on either platform,
|
|
962
|
+
so only single-step adjustment is ported.
|
|
963
|
+
|
|
964
|
+
RN's `transform` only accepts pixel offsets, unlike the CSS `%` units the
|
|
965
|
+
web version's `left: X%; transform: translate(-50%, -50%)` thumb/tick
|
|
966
|
+
centering trick needs — so those are positioned with a plain pixel `left`
|
|
967
|
+
computed from the track's `onLayout`-measured width instead. Mark labels
|
|
968
|
+
use a different trick, since (unlike the thumb/ticks) their own rendered
|
|
969
|
+
width isn't a known constant: a zero-width `View` with
|
|
970
|
+
`alignItems: 'center'` at the mark's percentage `left` lets Yoga center the
|
|
971
|
+
`Text` child around that point regardless of how wide the label renders,
|
|
972
|
+
with no measurement needed.
|
|
973
|
+
|
|
793
974
|
## Installation
|
|
794
975
|
|
|
795
976
|
```bash
|
|
796
|
-
npm install @gnome-ui/react-native react-native
|
|
977
|
+
npm install @gnome-ui/react-native react-native react-native-svg
|
|
797
978
|
```
|
|
798
979
|
|
|
980
|
+
`react-native-svg` is a peer dependency, only needed for `Icon`/
|
|
981
|
+
`AnimatedIcon` — it ships as one of Expo Go's included native modules, so
|
|
982
|
+
Expo projects on `npx expo install react-native-svg` need no extra native
|
|
983
|
+
build step; bare RN projects need it linked as usual for a native module.
|
|
984
|
+
|
|
799
985
|
## Example app
|
|
800
986
|
|
|
801
987
|
[`apps/react-native-example`](../../apps/react-native-example) is a
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { IconDefinition } from '@gnome-ui/icons';
|
|
2
|
+
import { IconProps } from '../Icon';
|
|
3
|
+
export interface AnimatedIconProps extends Omit<IconProps, 'icon'> {
|
|
4
|
+
/** Animated icon from `@gnome-ui/icons` (`animated: true` — `Syncing`, `Recording`, `Downloading`, `Connecting`). */
|
|
5
|
+
icon: IconDefinition;
|
|
6
|
+
/**
|
|
7
|
+
* Whether the animation plays. Defaults to `true`.
|
|
8
|
+
*
|
|
9
|
+
* Regardless of this prop, the animation is always paused when the OS
|
|
10
|
+
* reduced-motion setting is on — callers don't need to check
|
|
11
|
+
* `useReducedMotion()` themselves.
|
|
12
|
+
*/
|
|
13
|
+
playing?: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Plays the motion for a known `animated` icon from `@gnome-ui/icons`
|
|
17
|
+
* (`Syncing`, `Recording`, `Downloading`, `Connecting`) — rendered through
|
|
18
|
+
* plain `<Icon>`, these show a static frame instead.
|
|
19
|
+
*
|
|
20
|
+
* Unlike `@gnome-ui/react`'s `AnimatedIcon` (which plays a CSS animation
|
|
21
|
+
* embedded in the icon's raw `svg` markup via a `--gnome-icon-play-state`
|
|
22
|
+
* custom property), RN has no CSS engine to interpret `@keyframes` at all —
|
|
23
|
+
* `<Icon>` already drops the `<style>` block silently (see its own doc
|
|
24
|
+
* comment), and this component instead hand-builds each of the 4 known
|
|
25
|
+
* icons' exact motion with `Animated`, matched against `RECIPES` by
|
|
26
|
+
* reference. When playing and reduced motion is off, it renders its own
|
|
27
|
+
* `<Svg>` with the matching `*Motion` recipe as children; otherwise (not
|
|
28
|
+
* playing, reduced motion on, or an icon `RECIPES` doesn't recognize) it
|
|
29
|
+
* defers straight to `<Icon>`.
|
|
30
|
+
*
|
|
31
|
+
* Useful for progress, sync, recording, download, and connection states, or
|
|
32
|
+
* cross-fading between icons by swapping `icon` while `playing` stays true.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* import { Syncing } from "@gnome-ui/icons";
|
|
36
|
+
* <AnimatedIcon icon={Syncing} playing={isSyncing} label="Syncing" />
|
|
37
|
+
*/
|
|
38
|
+
export declare const AnimatedIcon: ({ icon, playing, size, width, height, label, color, }: AnimatedIconProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
export interface DropdownOption<V extends string = string> {
|
|
3
|
+
/** The value submitted / returned on selection. */
|
|
4
|
+
value: V;
|
|
5
|
+
/** Display label shown in the list and trigger. */
|
|
6
|
+
label: string;
|
|
7
|
+
/** Optional descriptive text shown below the label. */
|
|
8
|
+
description?: string;
|
|
9
|
+
/** Whether the option is selectable. */
|
|
10
|
+
disabled?: boolean;
|
|
11
|
+
}
|
|
12
|
+
export interface DropdownProps<V extends string = string> {
|
|
13
|
+
/** The list of selectable options. */
|
|
14
|
+
options: DropdownOption<V>[];
|
|
15
|
+
/** The currently selected value. */
|
|
16
|
+
value?: V;
|
|
17
|
+
/** Called when the user selects an option. */
|
|
18
|
+
onChange?: (value: V) => void;
|
|
19
|
+
/** Placeholder shown when no option is selected. */
|
|
20
|
+
placeholder?: string;
|
|
21
|
+
/** Accessible label for the control. */
|
|
22
|
+
accessibilityLabel?: string;
|
|
23
|
+
/** Disables the entire control. */
|
|
24
|
+
disabled?: boolean;
|
|
25
|
+
style?: StyleProp<ViewStyle>;
|
|
26
|
+
testID?: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Expandable option list following the Adwaita combo-row / drop-down
|
|
30
|
+
* pattern, mirroring `@gnome-ui/react`'s `Dropdown`.
|
|
31
|
+
*
|
|
32
|
+
* Built on RN's own `Modal` (transparent) — the same portal-substitute
|
|
33
|
+
* `Dialog`/`Tooltip` already use — with a full-screen backdrop `Pressable`
|
|
34
|
+
* (not `pointerEvents="box-none"` like `Tooltip`'s: unlike a tooltip, this
|
|
35
|
+
* panel is meant to catch and close on an outside tap, the RN analog of the
|
|
36
|
+
* web version's document-level "click outside" listener). `open` flips
|
|
37
|
+
* synchronously on trigger press; the trigger's on-screen rect
|
|
38
|
+
* (`measureInWindow`) and the panel's own rendered height (`onLayout`) each
|
|
39
|
+
* resolve independently into state, combined by a separate effect into the
|
|
40
|
+
* final `top`/`left`/`width`/`flipUp` — the same two-independent-
|
|
41
|
+
* async-measurements pattern `Tooltip` established (and for the same
|
|
42
|
+
* reason: gating `open` itself on the native measurement callback would
|
|
43
|
+
* make the component untestable in this package's Jest environment, where
|
|
44
|
+
* `measureInWindow` never calls back).
|
|
45
|
+
*
|
|
46
|
+
* Keyboard navigation (↑/↓ roving highlight, Home/End, type-ahead) has no
|
|
47
|
+
* port — RN's touch-first model has no keyboard focus to drive it, the
|
|
48
|
+
* same reasoning that already dropped `TabBar`'s roving-tabindex arrow
|
|
49
|
+
* keys. Selection is by direct tap only.
|
|
50
|
+
*
|
|
51
|
+
* `role="combobox"` on the trigger ports 1:1. RN's `Role` union has no
|
|
52
|
+
* `"listbox"` value (unlike `"option"`, which does exist) — the panel uses
|
|
53
|
+
* `role="list"` instead, the same closest-available substitution `BoxedList`
|
|
54
|
+
* already established for a plain list container.
|
|
55
|
+
*/
|
|
56
|
+
export declare const Dropdown: <V extends string = string>({ options, value, onChange, placeholder, accessibilityLabel, disabled, style, testID, }: DropdownProps<V>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { AnyIconDefinition } from '@gnome-ui/icons';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
export type IconSize = 'sm' | 'md' | 'lg';
|
|
4
|
+
/** Named GNOME palette color for the icon. `"default"` (or omitting the prop) resolves to the theme's default foreground color. */
|
|
5
|
+
export type IconColor = 'default' | 'blue' | 'green' | 'yellow' | 'orange' | 'red' | 'purple' | 'brown';
|
|
6
|
+
export interface IconProps {
|
|
7
|
+
/** Icon from `@gnome-ui/icons`, a `simple-icons` icon, or a raw `{ path }` object. */
|
|
8
|
+
icon: AnyIconDefinition;
|
|
9
|
+
/**
|
|
10
|
+
* Rendered size.
|
|
11
|
+
* - `sm` — 12 px
|
|
12
|
+
* - `md` — 16 px (default)
|
|
13
|
+
* - `lg` — 20 px
|
|
14
|
+
*
|
|
15
|
+
* Override with `width`/`height` for non-standard sizes.
|
|
16
|
+
*/
|
|
17
|
+
size?: IconSize;
|
|
18
|
+
width?: number;
|
|
19
|
+
height?: number;
|
|
20
|
+
/** Accessible label. Omit for decorative icons — they are hidden from screen readers. */
|
|
21
|
+
label?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Named GNOME palette color. Omit (or pass `"default"`) to use the
|
|
24
|
+
* theme's default foreground color — RN has no `currentColor`
|
|
25
|
+
* equivalent to inherit from a parent, unlike the web version.
|
|
26
|
+
*/
|
|
27
|
+
color?: IconColor;
|
|
28
|
+
/** Forwarded to the underlying `Svg` — useful for a `transform` (e.g. a rotated disclosure chevron) or `margin`. */
|
|
29
|
+
style?: StyleProp<ViewStyle>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Renders an icon as an inline SVG, via `react-native-svg`.
|
|
33
|
+
*
|
|
34
|
+
* Accepts icons from `@gnome-ui/icons`, any `simple-icons` icon, or a plain
|
|
35
|
+
* `{ path }` object — same `AnyIconDefinition` union as `@gnome-ui/react`'s
|
|
36
|
+
* `Icon`. Pass `label` only when the icon conveys meaning on its own.
|
|
37
|
+
*
|
|
38
|
+
* `animated` icons (`Syncing`, `Recording`, …) carry raw `svg` markup
|
|
39
|
+
* instead of `paths` — rendered here through `SvgXml`, which parses the
|
|
40
|
+
* structural elements (`<g>`/`<path>`/`<circle>`) but has no CSS engine, so
|
|
41
|
+
* the embedded `<style>`/`@keyframes` block is silently dropped and the
|
|
42
|
+
* shapes render at their authored rest position. That happens to be exactly
|
|
43
|
+
* the desired "inert, static frame" behavior for a plain `<Icon>` — no
|
|
44
|
+
* special-casing needed here; wrap in `<AnimatedIcon>` to actually play the
|
|
45
|
+
* motion (RN has to hand-build that with `Animated`, since there is no CSS
|
|
46
|
+
* animation engine to interpret the markup's keyframes).
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* import { Search } from "@gnome-ui/icons";
|
|
50
|
+
* <Icon icon={Search} label="Search" />
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* import { siGithub } from "simple-icons";
|
|
54
|
+
* <Icon icon={siGithub} label="GitHub" />
|
|
55
|
+
*/
|
|
56
|
+
export declare const Icon: ({ icon, size, width, height, label, color, style }: IconProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useGnomeTheme } from '../../GnomeProvider';
|
|
2
|
+
import { IconColor, IconSize } from './Icon';
|
|
3
|
+
/**
|
|
4
|
+
* Non-component helpers shared between `Icon` and `AnimatedIcon` — kept in
|
|
5
|
+
* their own module (not exported from `Icon.tsx` itself) so both files stay
|
|
6
|
+
* component-only for Fast Refresh, per the project's `react-refresh/
|
|
7
|
+
* only-export-components` lint rule.
|
|
8
|
+
*/
|
|
9
|
+
export declare const ICON_SIZE_MAP: Record<IconSize, number>;
|
|
10
|
+
export declare function resolveIconColor(theme: ReturnType<typeof useGnomeTheme>, color: IconColor | undefined): string;
|
|
11
|
+
export declare function iconAccessibilityProps(label: string | undefined): {
|
|
12
|
+
accessible: true;
|
|
13
|
+
accessibilityLabel: string;
|
|
14
|
+
accessibilityRole: "image";
|
|
15
|
+
} | {
|
|
16
|
+
accessible: false;
|
|
17
|
+
accessibilityRole: "none";
|
|
18
|
+
accessibilityLabel?: undefined;
|
|
19
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { StyleProp, ViewProps, ViewStyle } from 'react-native';
|
|
2
|
+
export interface SliderMark {
|
|
3
|
+
value: number;
|
|
4
|
+
label?: string;
|
|
5
|
+
}
|
|
6
|
+
export interface SliderProps extends Omit<ViewProps, 'style'> {
|
|
7
|
+
/** Current value. Must be between `min` and `max`. */
|
|
8
|
+
value: number;
|
|
9
|
+
/** Called when the value changes. */
|
|
10
|
+
onChange: (value: number) => void;
|
|
11
|
+
/** Minimum value. Defaults to `0`. */
|
|
12
|
+
min?: number;
|
|
13
|
+
/** Maximum value. Defaults to `100`. */
|
|
14
|
+
max?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Granularity of each step.
|
|
17
|
+
* - The `adjustable` accessibility action (VoiceOver/TalkBack swipe up/down) moves by one step.
|
|
18
|
+
* Defaults to `1`.
|
|
19
|
+
*/
|
|
20
|
+
step?: number;
|
|
21
|
+
/** Disables the control. */
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Marks to display along the track.
|
|
25
|
+
* Each mark can have an optional label rendered below the track.
|
|
26
|
+
*/
|
|
27
|
+
marks?: SliderMark[];
|
|
28
|
+
/** Accessible label. Required — RN has no visible-label association to fall back on. */
|
|
29
|
+
accessibilityLabel?: string;
|
|
30
|
+
style?: StyleProp<ViewStyle>;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Draggable range slider following the Adwaita `GtkScale` pattern.
|
|
34
|
+
*
|
|
35
|
+
* - Touch drag (via `PanResponder`) sets the value continuously.
|
|
36
|
+
* - `accessibilityRole="adjustable"` + `onAccessibilityAction` handles the
|
|
37
|
+
* "increment"/"decrement" actions VoiceOver (swipe up/down) and TalkBack
|
|
38
|
+
* generate for an adjustable element — RN's native analog of the web
|
|
39
|
+
* version's ← / → keyboard stepping, since a touch-first device has no
|
|
40
|
+
* keyboard to drive `ArrowLeft`/`ArrowRight`/`Home`/`End`/`PageUp`/
|
|
41
|
+
* `PageDown` with. Those bigger/edge jumps have no equivalent gesture on
|
|
42
|
+
* either platform's screen reader, so only single-step increment/decrement
|
|
43
|
+
* is ported.
|
|
44
|
+
* - Optional tick marks with labels.
|
|
45
|
+
* - Fills the track from the left up to the thumb (accent colour).
|
|
46
|
+
*
|
|
47
|
+
* RN's `transform` only accepts pixel offsets, not the CSS `%` units the
|
|
48
|
+
* web version's `left: X%; transform: translate(-50%, -50%)` centering
|
|
49
|
+
* trick relies on — so the thumb and tick marks are positioned with a
|
|
50
|
+
* plain pixel `left` (`(pct / 100) * trackWidth - size / 2`, measured via
|
|
51
|
+
* `onLayout`) instead, and vertical centering comes from the track's own
|
|
52
|
+
* `justifyContent: 'center'` rather than a transform.
|
|
53
|
+
*
|
|
54
|
+
* @see https://developer.gnome.org/hig/patterns/controls/sliders.html
|
|
55
|
+
*/
|
|
56
|
+
export declare const Slider: ({ value, onChange, min, max, step, disabled, marks, accessibilityLabel, style, onLayout, ...viewProps }: SliderProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ReactElement, Ref } from 'react';
|
|
2
|
+
import { PressableProps, View } from 'react-native';
|
|
3
|
+
export type TooltipPlacement = 'top' | 'bottom' | 'left' | 'right';
|
|
4
|
+
export interface TooltipProps {
|
|
5
|
+
/**
|
|
6
|
+
* The tooltip label. Keep it short — a noun phrase or brief description.
|
|
7
|
+
* Do not duplicate information already visible on screen.
|
|
8
|
+
*/
|
|
9
|
+
label: string;
|
|
10
|
+
/**
|
|
11
|
+
* Preferred placement relative to the trigger.
|
|
12
|
+
* The tooltip flips automatically if there is not enough space.
|
|
13
|
+
* Defaults to `"top"`.
|
|
14
|
+
*/
|
|
15
|
+
placement?: TooltipPlacement;
|
|
16
|
+
/**
|
|
17
|
+
* Delay in milliseconds before the tooltip appears, on both the
|
|
18
|
+
* long-press and hover/focus triggers. Defaults to `500`. Set to `0` for
|
|
19
|
+
* instant.
|
|
20
|
+
*/
|
|
21
|
+
delay?: number;
|
|
22
|
+
/**
|
|
23
|
+
* The element that triggers the tooltip.
|
|
24
|
+
* Must be a single element built on `Pressable` (e.g. `Button`, `Card`)
|
|
25
|
+
* that forwards its `ref` to the underlying `View`.
|
|
26
|
+
*/
|
|
27
|
+
children: ReactElement<PressableProps & {
|
|
28
|
+
ref?: Ref<View>;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Informational tooltip following the Adwaita / GNOME HIG pattern.
|
|
33
|
+
*
|
|
34
|
+
* Wraps a single trigger element and shows a floating label. Positioned
|
|
35
|
+
* automatically and flips if there is not enough space, mirroring
|
|
36
|
+
* `@gnome-ui/react`'s `Tooltip`.
|
|
37
|
+
*
|
|
38
|
+
* **Trigger differs from the web version by necessity**: the web `Tooltip`
|
|
39
|
+
* shows only on mouse hover / keyboard focus — touch has no hover state, so
|
|
40
|
+
* it explicitly never shows on touch. RN is touch-first, so the primary
|
|
41
|
+
* trigger here is long-press (`delayLongPress={delay}`, released via
|
|
42
|
+
* `onPressOut`) — the standard mobile "peek" idiom. `onHoverIn`/`onHoverOut`
|
|
43
|
+
* are also wired for hover-capable input (trackpad/mouse on iPad or a
|
|
44
|
+
* pointer-driven RN target) and `onFocus`/`onBlur` for external-keyboard
|
|
45
|
+
* accessibility, both delayed the same way the web version delays hover.
|
|
46
|
+
*
|
|
47
|
+
* Built on RN's own `Modal` (transparent, `pointerEvents="box-none"`), the
|
|
48
|
+
* same portal-substitute `Dialog` uses — no DOM `Portal` equivalent exists.
|
|
49
|
+
* Unlike `Dialog`, `visible` is set synchronously on trigger (not gated on
|
|
50
|
+
* the async `measureInWindow` result): the trigger's on-screen rect and the
|
|
51
|
+
* tooltip's own rendered size each resolve independently into state, and a
|
|
52
|
+
* separate effect computes the final position only once both have arrived.
|
|
53
|
+
* Gating `visible` itself on `measureInWindow`'s callback was tried first
|
|
54
|
+
* and dropped — that callback never fires in this package's Jest
|
|
55
|
+
* environment (no real native view to measure), which would make the
|
|
56
|
+
* component untestable, and in production it would also lose a real
|
|
57
|
+
* quick-tap-then-release press if the callback resolved after `onPressOut`
|
|
58
|
+
* had already fired.
|
|
59
|
+
*
|
|
60
|
+
* `role="tooltip"` ports 1:1 — RN's newer web-aligned `Role` union (the
|
|
61
|
+
* same one `Dialog` uses for `role="dialog"`) already has a `"tooltip"`
|
|
62
|
+
* value, no substitution needed. `aria-describedby` has no RN equivalent
|
|
63
|
+
* (no cross-platform description-relationship prop exists), so the label is
|
|
64
|
+
* set as the trigger's `accessibilityHint` instead — read after the
|
|
65
|
+
* trigger's own label by VoiceOver/TalkBack, the nearest analog. The CSS
|
|
66
|
+
* arrow reuses the same zero-size / transparent-border-on-three-sides
|
|
67
|
+
* triangle trick as the web version — RN Views support per-side
|
|
68
|
+
* `border*Color` just like CSS, the same technique `Spinner`'s ring
|
|
69
|
+
* already relies on, applied here to a triangle instead of an arc.
|
|
70
|
+
*
|
|
71
|
+
* Not ported: repositioning on scroll/resize while visible (the web
|
|
72
|
+
* version listens for both) — RN has no global scroll event, and a
|
|
73
|
+
* long-press is naturally cancelled by a scroll gesture starting, so the
|
|
74
|
+
* window to go stale is negligible for the touch trigger; orientation
|
|
75
|
+
* change mid-hover is an accepted edge case for a component this transient.
|
|
76
|
+
* `tooltipBgColor`/`tooltipFgColor` are not real `@gnome-ui/core` tokens
|
|
77
|
+
* (only referenced as CSS var fallbacks, never defined) — hardcoded to the
|
|
78
|
+
* same literal light/dark values as the web fallback, the same workaround
|
|
79
|
+
* `Spinner`'s track color already established for this class of gap.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* <Tooltip label="Save file">
|
|
83
|
+
* <Button accessibilityLabel="Save"><Icon icon={Save} /></Button>
|
|
84
|
+
* </Tooltip>
|
|
85
|
+
*
|
|
86
|
+
* @see https://developer.gnome.org/hig/patterns/feedback/tooltips.html
|
|
87
|
+
*/
|
|
88
|
+
export declare const Tooltip: ({ label, placement: preferredPlacement, delay, children, }: TooltipProps) => import("react/jsx-runtime").JSX.Element;
|