@gnome-ui/react-native 1.1.0 → 1.2.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 +142 -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/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 +4 -0
- package/dist/index.js +1129 -469
- 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` shipped — `Slider`,
|
|
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,147 @@ 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
|
+
|
|
793
926
|
## Installation
|
|
794
927
|
|
|
795
928
|
```bash
|
|
796
|
-
npm install @gnome-ui/react-native react-native
|
|
929
|
+
npm install @gnome-ui/react-native react-native react-native-svg
|
|
797
930
|
```
|
|
798
931
|
|
|
932
|
+
`react-native-svg` is a peer dependency, only needed for `Icon`/
|
|
933
|
+
`AnimatedIcon` — it ships as one of Expo Go's included native modules, so
|
|
934
|
+
Expo projects on `npx expo install react-native-svg` need no extra native
|
|
935
|
+
build step; bare RN projects need it linked as usual for a native module.
|
|
936
|
+
|
|
799
937
|
## Example app
|
|
800
938
|
|
|
801
939
|
[`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,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;
|