@gnome-ui/react-native 1.6.0 → 1.7.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 +208 -2
- package/dist/components/Bin/Bin.d.ts +18 -0
- package/dist/components/Bin/index.d.ts +2 -0
- package/dist/components/ColorPicker/ColorPicker.d.ts +81 -0
- package/dist/components/ColorPicker/ColorSwatch.d.ts +50 -0
- package/dist/components/ColorPicker/index.d.ts +4 -0
- package/dist/components/ComboRow/ComboRow.d.ts +77 -0
- package/dist/components/ComboRow/index.d.ts +2 -0
- package/dist/components/EntryRow/EntryRow.d.ts +77 -0
- package/dist/components/EntryRow/index.d.ts +2 -0
- package/dist/components/PasswordEntryRow/PasswordEntryRow.d.ts +45 -0
- package/dist/components/PasswordEntryRow/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 +1102 -784
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -24,8 +24,9 @@ React Native component library following the [GNOME Human Interface Guidelines](
|
|
|
24
24
|
> `SegmentedBar`/`AvatarGroup`/`AvatarRotator`/`CoachMark`/`CoachMarkTour`
|
|
25
25
|
> (Tier 20), `Chip` (Tier 7), `IconButton`/`Drawer` (Tier 8/Tier 20), and
|
|
26
26
|
> `Clamp` (Tier 6), `Box` (Tier 20), `WrapBox`/`ToggleGroup` (Tier 7), and
|
|
27
|
-
> `InlineViewSwitcher` (Tier 8),
|
|
28
|
-
>
|
|
27
|
+
> `InlineViewSwitcher` (Tier 8), `PreferencesGroup` (Tier 13), and
|
|
28
|
+
> `EntryRow`/`PasswordEntryRow`/`ComboRow` (Tier 12), `ColorPicker`
|
|
29
|
+
> (Tier 20), and `Bin` (Tier 15) also shipped. Component ports from
|
|
29
30
|
> `@gnome-ui/react` continue tier by tier — see this package's own
|
|
30
31
|
> [ROADMAP.md](./ROADMAP.md) for full
|
|
31
32
|
> per-tier status against all 130 `@gnome-ui/react` components, and the
|
|
@@ -1868,6 +1869,211 @@ own title. `min-width: 0` on the header text has no port and needs none: it's
|
|
|
1868
1869
|
the classic CSS flexbox override for a min-content floor Yoga doesn't apply
|
|
1869
1870
|
in the first place.
|
|
1870
1871
|
|
|
1872
|
+
### EntryRow
|
|
1873
|
+
|
|
1874
|
+
```tsx
|
|
1875
|
+
import { EntryRow } from '@gnome-ui/react-native';
|
|
1876
|
+
|
|
1877
|
+
const [name, setName] = useState('');
|
|
1878
|
+
|
|
1879
|
+
<BoxedList>
|
|
1880
|
+
<EntryRow title="Display name" value={name} onValueChange={setName} />
|
|
1881
|
+
<EntryRow
|
|
1882
|
+
title="Email"
|
|
1883
|
+
value={email}
|
|
1884
|
+
onValueChange={setEmail}
|
|
1885
|
+
keyboardType="email-address"
|
|
1886
|
+
leading={<Icon icon={MailRead} />}
|
|
1887
|
+
trailing={<IconButton icon={Delete} label="Clear" onPress={() => setEmail('')} />}
|
|
1888
|
+
/>
|
|
1889
|
+
</BoxedList>
|
|
1890
|
+
```
|
|
1891
|
+
|
|
1892
|
+
Row with an inline text entry field — mirrors `AdwEntryRow` and
|
|
1893
|
+
`@gnome-ui/react`'s own `EntryRow`. The `title` rises above the input as a
|
|
1894
|
+
small label once the field is focused or has content, and stands in for the
|
|
1895
|
+
placeholder until then. Use it inside a `BoxedList` for settings that take
|
|
1896
|
+
free-form text. Controlled (`value`) and uncontrolled (`defaultValue`) modes
|
|
1897
|
+
both work, and every remaining `TextInput` prop passes through.
|
|
1898
|
+
|
|
1899
|
+
The float is one JS-driven `Animated.Value` (`useNativeDriver: false`):
|
|
1900
|
+
`fontSize` is part of the transition and can't be native-driven, and mixing a
|
|
1901
|
+
native with a JS value on one component throws — the same trade-off
|
|
1902
|
+
`Expander` and `InlineViewSwitcher` already accepted. `useReducedMotion()`
|
|
1903
|
+
snaps between the two states instead.
|
|
1904
|
+
|
|
1905
|
+
**The label's travel is measured, not hardcoded.** The web expresses the
|
|
1906
|
+
resting position as `top: 50%; transform: translateY(-50%)` and the floated
|
|
1907
|
+
one as `top: 6px`, but RN can't interpolate between a percentage and a fixed
|
|
1908
|
+
offset — so the field reports its own height through `onLayout` and the
|
|
1909
|
+
distance is derived from it, which also keeps the label centred if you make
|
|
1910
|
+
the row taller than the 56 dp minimum.
|
|
1911
|
+
|
|
1912
|
+
The `:focus` inset ring is dropped rather than approximated. `TextField`'s
|
|
1913
|
+
own precedent — recolor the border on focus — doesn't transfer, because an
|
|
1914
|
+
`EntryRow` has no border of its own: it's a row inside a `BoxedList`, and
|
|
1915
|
+
adding one would shift the list's geometry. On a touch device the state is
|
|
1916
|
+
already unmistakable: the label floats up, the text fades in, and the
|
|
1917
|
+
keyboard opens.
|
|
1918
|
+
|
|
1919
|
+
Two deliberate divergences from the web version. The visible label is hidden
|
|
1920
|
+
from assistive tech and the `title` becomes the input's `accessibilityLabel`
|
|
1921
|
+
— RN has no `<label htmlFor>`, so otherwise the label would be announced as
|
|
1922
|
+
loose text next to an unnamed field (pass `accessibilityLabel` to override).
|
|
1923
|
+
And `testID` lands on the row rather than the input, matching every other
|
|
1924
|
+
component in this package; reach the field itself by its accessible name.
|
|
1925
|
+
|
|
1926
|
+
### PasswordEntryRow
|
|
1927
|
+
|
|
1928
|
+
```tsx
|
|
1929
|
+
import { PasswordEntryRow } from '@gnome-ui/react-native';
|
|
1930
|
+
|
|
1931
|
+
<BoxedList>
|
|
1932
|
+
<PasswordEntryRow title="Password" value={password} onValueChange={setPassword} />
|
|
1933
|
+
|
|
1934
|
+
{/* Registration and change-password forms */}
|
|
1935
|
+
<PasswordEntryRow
|
|
1936
|
+
title="New password"
|
|
1937
|
+
value={next}
|
|
1938
|
+
onValueChange={setNext}
|
|
1939
|
+
autoComplete="new-password"
|
|
1940
|
+
/>
|
|
1941
|
+
</BoxedList>
|
|
1942
|
+
```
|
|
1943
|
+
|
|
1944
|
+
Password entry row with a built-in reveal/conceal toggle — mirrors
|
|
1945
|
+
`AdwPasswordEntryRow` and `@gnome-ui/react`'s own `PasswordEntryRow`. It's an
|
|
1946
|
+
`EntryRow` that masks its input and always carries a trailing button to show
|
|
1947
|
+
or hide what's been typed, so don't add your own through `trailing` — that
|
|
1948
|
+
slot is for anything that should sit *before* the reveal button.
|
|
1949
|
+
|
|
1950
|
+
`type={revealed ? 'text' : 'password'}` becomes RN's `secureTextEntry`, and
|
|
1951
|
+
`autoComplete` defaults to `"current-password"`, which is what lets password
|
|
1952
|
+
managers and the platform keyboard offer a saved credential.
|
|
1953
|
+
|
|
1954
|
+
The reveal control is the already-shipped `IconButton` rather than a
|
|
1955
|
+
hand-rolled pressable, which costs one visual detail: `IconButton` is
|
|
1956
|
+
circular (it's `Button` at `shape="circular"`) where the web's
|
|
1957
|
+
`.revealButton` is a 32 dp square with a 6 dp radius. A circular flat icon
|
|
1958
|
+
button is the idiomatic touch control and keeps the row consistent with every
|
|
1959
|
+
other icon action here. The CSS's resting `opacity: 0.55` is dropped too — it
|
|
1960
|
+
exists so the button can brighten on hover, and with no hover on a touch
|
|
1961
|
+
device a permanently dimmed control is just harder to see.
|
|
1962
|
+
|
|
1963
|
+
The web needs `e.stopPropagation()` so pressing the button doesn't also
|
|
1964
|
+
trigger the row's focus-the-input click. RN's responder system routes a touch
|
|
1965
|
+
to the innermost pressable, so there's nothing to stop.
|
|
1966
|
+
|
|
1967
|
+
### ComboRow
|
|
1968
|
+
|
|
1969
|
+
```tsx
|
|
1970
|
+
import { ComboRow } from '@gnome-ui/react-native';
|
|
1971
|
+
|
|
1972
|
+
<BoxedList>
|
|
1973
|
+
<ComboRow
|
|
1974
|
+
title="Language"
|
|
1975
|
+
subtitle="Used across the whole app"
|
|
1976
|
+
value={language}
|
|
1977
|
+
onValueChange={setLanguage}
|
|
1978
|
+
options={[
|
|
1979
|
+
{ value: 'en', label: 'English' },
|
|
1980
|
+
{ value: 'es', label: 'Español' },
|
|
1981
|
+
]}
|
|
1982
|
+
/>
|
|
1983
|
+
</BoxedList>
|
|
1984
|
+
```
|
|
1985
|
+
|
|
1986
|
+
Settings row with an inline combo selector at the trailing edge — mirrors
|
|
1987
|
+
`AdwComboRow` and `@gnome-ui/react`'s own `ComboRow`. Use it inside a
|
|
1988
|
+
`BoxedList` for a setting that picks one of a set of options. Controlled
|
|
1989
|
+
(`value`) and uncontrolled (`defaultValue`) modes both work; the trigger
|
|
1990
|
+
falls back to `"—"` when nothing is selected.
|
|
1991
|
+
|
|
1992
|
+
**This is a composition of `ActionRow` + `Dropdown`, where the web version
|
|
1993
|
+
hand-rolls its own listbox inline** — around 200 lines re-implementing the
|
|
1994
|
+
trigger, the flip-up placement, outside-click dismissal, roving
|
|
1995
|
+
`aria-activedescendant` and the whole keyboard layer, none of which is
|
|
1996
|
+
meaningfully different from that package's own `Dropdown`. Nothing forced the
|
|
1997
|
+
duplication visually either: `.row` is `ActionRow`'s exact metrics and
|
|
1998
|
+
`.trigger` is `Dropdown`'s exact trigger. Composing the two already-shipped
|
|
1999
|
+
components means the flip-to-fit placement, the tap-outside dismissal and the
|
|
2000
|
+
`Modal`-based list all come along for free rather than being rebuilt.
|
|
2001
|
+
|
|
2002
|
+
`Dropdown` is controlled-only, so the uncontrolled state lives in `ComboRow`
|
|
2003
|
+
— same behaviour as the web version, one level up. The keyboard layer drops
|
|
2004
|
+
as it does everywhere else here.
|
|
2005
|
+
|
|
2006
|
+
### ColorPicker / ColorSwatch
|
|
2007
|
+
|
|
2008
|
+
```tsx
|
|
2009
|
+
import { ColorPicker, ColorSwatch, GNOME_PALETTE } from '@gnome-ui/react-native';
|
|
2010
|
+
|
|
2011
|
+
const [color, setColor] = useState('#3584e4');
|
|
2012
|
+
|
|
2013
|
+
<ColorPicker value={color} onChange={setColor} />
|
|
2014
|
+
|
|
2015
|
+
// Custom colors, with your own picker behind the "+"
|
|
2016
|
+
<ColorPicker
|
|
2017
|
+
value={color}
|
|
2018
|
+
onChange={setColor}
|
|
2019
|
+
allowCustom
|
|
2020
|
+
onRequestCustom={() => setPickerOpen(true)}
|
|
2021
|
+
/>
|
|
2022
|
+
```
|
|
2023
|
+
|
|
2024
|
+
Color palette picker following the Adwaita `GtkColorButton` + swatch pattern
|
|
2025
|
+
— mirrors `@gnome-ui/react`'s own `ColorPicker`. Renders a wrapping row of
|
|
2026
|
+
circular `ColorSwatch` items backed by a radio group, defaulting to
|
|
2027
|
+
`GNOME_PALETTE` (the 9 Adwaita named colors, the same set `Avatar` uses).
|
|
2028
|
+
`ColorSwatch` is exported for standalone use; sizes are **22 / 30 / 38 dp**.
|
|
2029
|
+
|
|
2030
|
+
**`allowCustom` is the one prop that changes meaning.** On the web it wires a
|
|
2031
|
+
hidden `<input type="color">` and the browser supplies the whole picker UI;
|
|
2032
|
+
RN has no such control, and an HSV picker is a component in its own right
|
|
2033
|
+
rather than a detail of this one. So the prop keeps its *visible* behaviour —
|
|
2034
|
+
the "+" button, and a `value` outside the palette shown as its own selected
|
|
2035
|
+
swatch — while the press is handed to a new **`onRequestCustom`** callback
|
|
2036
|
+
for your app to answer with whatever picker it has. The result round-trips
|
|
2037
|
+
through `value`/`onChange` exactly as before.
|
|
2038
|
+
|
|
2039
|
+
The web's three `box-shadow` rings collapse into real box-model pieces, since
|
|
2040
|
+
RN gives a `View` one border: the resting `inset 0 0 0 1px` hairline becomes
|
|
2041
|
+
`borderWidth: 1`, the selected `inset 0 0 0 2px rgb(255 255 255 / .9)`
|
|
2042
|
+
becomes a 2 dp white border, and the outer `0 0 0 2px var(--swatch-color)`
|
|
2043
|
+
becomes a wrapper painted in the swatch color. That wrapper is **always**
|
|
2044
|
+
rendered with the same 2 dp padding and only changes color: a box-shadow ring
|
|
2045
|
+
costs no layout space on the web while a real padded wrapper does, so
|
|
2046
|
+
reserving it unconditionally is what keeps the row from reflowing as the
|
|
2047
|
+
selection moves.
|
|
2048
|
+
|
|
2049
|
+
`filter: drop-shadow(...)` on the checkmark has no RN counterpart, so the
|
|
2050
|
+
path is drawn twice — a translucent black copy offset 1 dp down, then the
|
|
2051
|
+
white one on top. That's what the filter renders, and it's why it exists:
|
|
2052
|
+
without it the check disappears on the yellow swatch. The checkmark is
|
|
2053
|
+
hand-drawn with `react-native-svg` rather than taken from `@gnome-ui/icons`,
|
|
2054
|
+
mirroring the web version, which hand-draws it too — it's a stroked path, and
|
|
2055
|
+
`Icon`'s palette has no white to give it. The container is a `WrapBox`, and
|
|
2056
|
+
the "+" button's `border: 1.5px dashed` ports directly.
|
|
2057
|
+
|
|
2058
|
+
### Bin
|
|
2059
|
+
|
|
2060
|
+
```tsx
|
|
2061
|
+
import { Bin } from '@gnome-ui/react-native';
|
|
2062
|
+
|
|
2063
|
+
<Bin style={{ maxWidth: 480 }}>
|
|
2064
|
+
<ExpensiveChart data={series} />
|
|
2065
|
+
</Bin>
|
|
2066
|
+
```
|
|
2067
|
+
|
|
2068
|
+
Single-child container with no visual styling — mirrors `AdwBin` and
|
|
2069
|
+
`@gnome-ui/react`'s own `Bin`. A transparent passthrough `View` forwarding
|
|
2070
|
+
every prop (and a ref to the underlying `View`) straight through, useful as
|
|
2071
|
+
a neutral base for custom components that need to apply layout or size
|
|
2072
|
+
constraints without introducing any chrome of their own. A plain RN `View`
|
|
2073
|
+
already has no default visual styling — no background, no border — so
|
|
2074
|
+
unlike the web port there's no CSS reset to strip; this is a pure
|
|
2075
|
+
passthrough.
|
|
2076
|
+
|
|
1871
2077
|
## Installation
|
|
1872
2078
|
|
|
1873
2079
|
```bash
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { View, ViewProps } from 'react-native';
|
|
2
|
+
export type BinProps = ViewProps;
|
|
3
|
+
/**
|
|
4
|
+
* Single-child container with no visual styling.
|
|
5
|
+
*
|
|
6
|
+
* A transparent wrapper that forwards all `View` props (and a ref to the
|
|
7
|
+
* underlying `View`) straight through — useful as a neutral base for custom
|
|
8
|
+
* components that need to apply layout or size constraints without
|
|
9
|
+
* introducing any chrome of their own.
|
|
10
|
+
*
|
|
11
|
+
* Mirrors `AdwBin` and `@gnome-ui/react`'s own `Bin`. A plain RN `View`
|
|
12
|
+
* already has no default visual styling (no background, no border), so
|
|
13
|
+
* unlike the web port there's no CSS reset to strip — this is a pure
|
|
14
|
+
* passthrough.
|
|
15
|
+
*
|
|
16
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.Bin.html
|
|
17
|
+
*/
|
|
18
|
+
export declare const Bin: import('react').ForwardRefExoticComponent<ViewProps & import('react').RefAttributes<View>>;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
2
|
+
import { ColorSwatchSize } from './ColorSwatch';
|
|
3
|
+
export interface ColorPickerColor {
|
|
4
|
+
/** Color value (hex recommended). */
|
|
5
|
+
value: string;
|
|
6
|
+
/** Human-readable name, used as the swatch's accessible label. */
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
/** Default Adwaita-named palette (matches the `Avatar` color set). */
|
|
10
|
+
export declare const GNOME_PALETTE: ColorPickerColor[];
|
|
11
|
+
export interface ColorPickerProps {
|
|
12
|
+
/** Currently selected color value. */
|
|
13
|
+
value?: string;
|
|
14
|
+
/** Called when the user selects a color. */
|
|
15
|
+
onChange?: (value: string) => void;
|
|
16
|
+
/** Palette to display. Defaults to `GNOME_PALETTE` (the 9 Adwaita colors). */
|
|
17
|
+
colors?: ColorPickerColor[];
|
|
18
|
+
/**
|
|
19
|
+
* Show a "+" button after the palette, and render any `value` outside the
|
|
20
|
+
* palette as its own selected swatch. Pressing either calls
|
|
21
|
+
* `onRequestCustom` — RN has no `<input type="color">`, so the picker UI
|
|
22
|
+
* itself is the consuming app's to provide. Defaults to `false`.
|
|
23
|
+
*/
|
|
24
|
+
allowCustom?: boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Called when the "+" button (or the current custom swatch) is pressed.
|
|
27
|
+
* Open your own color picker here and feed the result back through
|
|
28
|
+
* `value`/`onChange`.
|
|
29
|
+
*/
|
|
30
|
+
onRequestCustom?: () => void;
|
|
31
|
+
/** Swatch size. Defaults to `"md"`. */
|
|
32
|
+
size?: ColorSwatchSize;
|
|
33
|
+
/** Accessible name for the group. Defaults to `"Color"`. */
|
|
34
|
+
accessibilityLabel?: string;
|
|
35
|
+
disabled?: boolean;
|
|
36
|
+
style?: StyleProp<ViewStyle>;
|
|
37
|
+
testID?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Color palette picker following the Adwaita `GtkColorButton` + swatch
|
|
41
|
+
* pattern, mirroring `@gnome-ui/react`'s own `ColorPicker`. Renders a
|
|
42
|
+
* wrapping row of circular `ColorSwatch` items backed by a radio group.
|
|
43
|
+
*
|
|
44
|
+
* **`allowCustom` is the one prop that changes meaning.** On the web it
|
|
45
|
+
* wires a hidden `<input type="color">` and the browser supplies the whole
|
|
46
|
+
* picker UI; RN has no such control, and building an HSV picker would be a
|
|
47
|
+
* component in its own right rather than a detail of this one. So the prop
|
|
48
|
+
* keeps its *visible* behaviour — the "+" button, and a `value` outside the
|
|
49
|
+
* palette shown as its own selected swatch — while the press is handed to
|
|
50
|
+
* `onRequestCustom` for the app to answer with whatever picker it has. Round
|
|
51
|
+
* trips through `value`/`onChange` exactly as before.
|
|
52
|
+
*
|
|
53
|
+
* The container is a `WrapBox` (`display: flex; flex-wrap: wrap; gap: 8`
|
|
54
|
+
* with nothing else in `.picker`), and the "+" button's `border: 1.5px
|
|
55
|
+
* dashed` ports directly — `borderStyle: 'dashed'` is one of the few CSS
|
|
56
|
+
* border tricks RN does support. Its plus glyph comes from `@gnome-ui/icons`
|
|
57
|
+
* rather than the web's hand-drawn path, since `Add` is the same mark and
|
|
58
|
+
* already resolves to the foreground color `.customButton` asks for.
|
|
59
|
+
*
|
|
60
|
+
* As in `ToggleGroup`, the group takes `accessibilityRole="radiogroup"` but
|
|
61
|
+
* deliberately not `accessible`, which on iOS would collapse the swatches
|
|
62
|
+
* into one unreachable element. Arrow-key navigation and the roving
|
|
63
|
+
* `tabIndex` drop, as everywhere else here.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* const [color, setColor] = useState('#3584e4');
|
|
67
|
+
*
|
|
68
|
+
* <ColorPicker value={color} onChange={setColor} />
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* // Custom colors, with your own picker behind the "+"
|
|
72
|
+
* <ColorPicker
|
|
73
|
+
* value={color}
|
|
74
|
+
* onChange={setColor}
|
|
75
|
+
* allowCustom
|
|
76
|
+
* onRequestCustom={() => setPickerOpen(true)}
|
|
77
|
+
* />
|
|
78
|
+
*
|
|
79
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ColorButton.html
|
|
80
|
+
*/
|
|
81
|
+
export declare const ColorPicker: ({ value, onChange, colors, allowCustom, onRequestCustom, size, accessibilityLabel, disabled, style, testID, }: ColorPickerProps) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { PressableProps, StyleProp, View, ViewStyle } from 'react-native';
|
|
2
|
+
export type ColorSwatchSize = 'sm' | 'md' | 'lg';
|
|
3
|
+
/** `.swatch-sm` / `-md` / `-lg`. */
|
|
4
|
+
export declare const SWATCH_DIAMETER: Record<ColorSwatchSize, number>;
|
|
5
|
+
/**
|
|
6
|
+
* Width of the selected state's outer ring. Reserved as padding on every
|
|
7
|
+
* swatch, selected or not, so selecting one never reflows the row — the web
|
|
8
|
+
* gets this for free because `box-shadow` rings don't take up space.
|
|
9
|
+
*/
|
|
10
|
+
export declare const RING_WIDTH = 2;
|
|
11
|
+
export interface ColorSwatchProps extends Omit<PressableProps, 'children' | 'style' | 'onPress' | 'disabled'> {
|
|
12
|
+
/** Color value displayed as the swatch background. */
|
|
13
|
+
color: string;
|
|
14
|
+
/** Whether this swatch is the currently selected color. */
|
|
15
|
+
selected?: boolean;
|
|
16
|
+
/** Swatch diameter. Defaults to `"md"`. */
|
|
17
|
+
size?: ColorSwatchSize;
|
|
18
|
+
/** Called with `color` when the swatch is pressed. */
|
|
19
|
+
onSelect?: (color: string) => void;
|
|
20
|
+
/** Accessible name. Defaults to the color value. */
|
|
21
|
+
accessibilityLabel?: string;
|
|
22
|
+
disabled?: boolean;
|
|
23
|
+
style?: StyleProp<ViewStyle>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Single circular color swatch. Usable standalone or composed inside
|
|
27
|
+
* `ColorPicker`, and shows a white checkmark when `selected`.
|
|
28
|
+
*
|
|
29
|
+
* The web's three `box-shadow` rings collapse into real box-model pieces,
|
|
30
|
+
* since RN gives a `View` exactly one border: the resting
|
|
31
|
+
* `inset 0 0 0 1px` hairline becomes `borderWidth: 1`, the selected state's
|
|
32
|
+
* `inset 0 0 0 2px rgb(255 255 255 / .9)` becomes a 2 dp white border, and
|
|
33
|
+
* the outer `0 0 0 2px var(--swatch-color)` becomes a wrapper painted in the
|
|
34
|
+
* swatch color. That wrapper is always rendered with the same 2 dp padding
|
|
35
|
+
* and only changes color, because a `box-shadow` ring costs no layout space
|
|
36
|
+
* on the web while a real padded wrapper does — reserving it unconditionally
|
|
37
|
+
* is what keeps the row from reflowing as the selection moves.
|
|
38
|
+
*
|
|
39
|
+
* The checkmark is hand-drawn with `react-native-svg` rather than taken from
|
|
40
|
+
* `@gnome-ui/icons`, mirroring the web version, which also hand-draws it:
|
|
41
|
+
* it's a stroked path, and `Icon`'s palette has no white to give it anyway.
|
|
42
|
+
* `filter: drop-shadow(...)` has no RN counterpart, so the path is drawn
|
|
43
|
+
* twice — a translucent black copy offset 1 dp down, then the white one on
|
|
44
|
+
* top — which is what that filter renders and is why it exists: without it
|
|
45
|
+
* the check disappears on a yellow swatch.
|
|
46
|
+
*
|
|
47
|
+
* `:hover { transform: scale(1.12) }` drops with hover; the selected
|
|
48
|
+
* `scale(1.05)` ports as-is.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ColorSwatch: import('react').ForwardRefExoticComponent<ColorSwatchProps & import('react').RefAttributes<View>>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { StyleProp, ViewStyle } from 'react-native';
|
|
3
|
+
import { DropdownOption } from '../Dropdown';
|
|
4
|
+
/** Same shape as `Dropdown`'s own option — re-exported so `ComboRow` reads self-contained. */
|
|
5
|
+
export type ComboRowOption<V extends string = string> = DropdownOption<V>;
|
|
6
|
+
export interface ComboRowProps<V extends string = string> {
|
|
7
|
+
/** Primary label. */
|
|
8
|
+
title: string;
|
|
9
|
+
/** Secondary line below the title. */
|
|
10
|
+
subtitle?: string;
|
|
11
|
+
/** Icon or image placed at the leading edge. */
|
|
12
|
+
leading?: ReactNode;
|
|
13
|
+
/** The list of selectable options. */
|
|
14
|
+
options: ComboRowOption<V>[];
|
|
15
|
+
/** The currently selected value (controlled). */
|
|
16
|
+
value?: V;
|
|
17
|
+
/** Initial value when uncontrolled. */
|
|
18
|
+
defaultValue?: V;
|
|
19
|
+
/** Called when the user selects an option. */
|
|
20
|
+
onValueChange?: (value: V) => void;
|
|
21
|
+
/** Shown in the trigger while nothing is selected. Defaults to `"—"`. */
|
|
22
|
+
placeholder?: string;
|
|
23
|
+
/** Accessible name for the selector. Defaults to `title`. */
|
|
24
|
+
accessibilityLabel?: string;
|
|
25
|
+
/** Disables the row and its selector. */
|
|
26
|
+
disabled?: boolean;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
testID?: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Settings row with an inline combo selector at the trailing edge, mirroring
|
|
32
|
+
* `AdwComboRow` and `@gnome-ui/react`'s own `ComboRow`. Use inside a
|
|
33
|
+
* `BoxedList` for a setting that picks one of a set of options.
|
|
34
|
+
*
|
|
35
|
+
* **This is a composition of `ActionRow` + `Dropdown`, where the web version
|
|
36
|
+
* hand-rolls its own listbox inline** — around 200 lines re-implementing the
|
|
37
|
+
* trigger, the flip-up placement, outside-click dismissal, roving
|
|
38
|
+
* `aria-activedescendant` and the whole keyboard layer, none of which is
|
|
39
|
+
* meaningfully different from that package's own `Dropdown`. Nothing forced
|
|
40
|
+
* the duplication visually either: `.row` is `ActionRow`'s exact metrics
|
|
41
|
+
* (12/24 dp padding, 52 dp min-height) and `.trigger` is `Dropdown`'s exact
|
|
42
|
+
* trigger (card background, 1 px shade border turning accent when open,
|
|
43
|
+
* `radius-md`, chevron). So this port composes the two already-shipped,
|
|
44
|
+
* already-verified components instead — which also means the flip-to-fit
|
|
45
|
+
* placement, the tap-outside dismissal and the `Modal`-based list all come
|
|
46
|
+
* along for free rather than being rebuilt and re-debugged.
|
|
47
|
+
*
|
|
48
|
+
* The web keeps its own `useState` for the uncontrolled case; `Dropdown` is
|
|
49
|
+
* controlled-only, so that state lives here — same behaviour, one level up.
|
|
50
|
+
*
|
|
51
|
+
* The keyboard layer (`↑`/`↓`/`Home`/`End`/`Enter`/`Escape`) drops as it
|
|
52
|
+
* does everywhere else in this package, and `Dropdown` already provides the
|
|
53
|
+
* touch equivalents it was built with.
|
|
54
|
+
*
|
|
55
|
+
* The dimming for `disabled` is applied here rather than left to
|
|
56
|
+
* `ActionRow`, which only dims in its `interactive` branch — on a plain
|
|
57
|
+
* (non-pressable) row its `disabled` prop currently reaches a `View` that
|
|
58
|
+
* does nothing with it. Making this row `interactive` isn't the answer: the
|
|
59
|
+
* `Dropdown` is the control, and the row itself has nothing to press.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* <BoxedList>
|
|
63
|
+
* <ComboRow
|
|
64
|
+
* title="Language"
|
|
65
|
+
* subtitle="Used across the whole app"
|
|
66
|
+
* value={language}
|
|
67
|
+
* onValueChange={setLanguage}
|
|
68
|
+
* options={[
|
|
69
|
+
* { value: 'en', label: 'English' },
|
|
70
|
+
* { value: 'es', label: 'Español' },
|
|
71
|
+
* ]}
|
|
72
|
+
* />
|
|
73
|
+
* </BoxedList>
|
|
74
|
+
*
|
|
75
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.ComboRow.html
|
|
76
|
+
*/
|
|
77
|
+
export declare const ComboRow: <V extends string = string>({ title, subtitle, leading, options, value: controlledValue, defaultValue, onValueChange, placeholder, accessibilityLabel, disabled, style, testID, }: ComboRowProps<V>) => import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { TextInput as RNTextInput, StyleProp, TextInputProps, ViewStyle } from 'react-native';
|
|
3
|
+
export interface EntryRowProps extends Omit<TextInputProps, 'style' | 'value' | 'defaultValue' | 'editable' | 'testID'> {
|
|
4
|
+
/**
|
|
5
|
+
* Acts as a floating label: shown small above the input once the field has
|
|
6
|
+
* content or focus, and as the placeholder while it's empty and unfocused.
|
|
7
|
+
*/
|
|
8
|
+
title: string;
|
|
9
|
+
/** Controlled value. Omit for an uncontrolled field. */
|
|
10
|
+
value?: string;
|
|
11
|
+
/** Initial value when uncontrolled. */
|
|
12
|
+
defaultValue?: string;
|
|
13
|
+
/** Called when the input value changes. */
|
|
14
|
+
onValueChange?: (value: string) => void;
|
|
15
|
+
/** Icon or widget placed at the leading edge. */
|
|
16
|
+
leading?: ReactNode;
|
|
17
|
+
/** Icon or widget placed at the trailing edge (e.g. a clear or reveal button). */
|
|
18
|
+
trailing?: ReactNode;
|
|
19
|
+
disabled?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Applied to the row, not the input — the convention every other component
|
|
22
|
+
* in this package follows. Reach the field itself with the accessible name
|
|
23
|
+
* (`getByLabelText(title)`).
|
|
24
|
+
*/
|
|
25
|
+
testID?: string;
|
|
26
|
+
style?: StyleProp<ViewStyle>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Row with an inline text entry field, mirroring `AdwEntryRow` and
|
|
30
|
+
* `@gnome-ui/react`'s own `EntryRow`. The `title` rises above the input as a
|
|
31
|
+
* small label once the field is focused or has content, and stands in for
|
|
32
|
+
* the placeholder until then. Use inside a `BoxedList` for settings that
|
|
33
|
+
* take free-form text.
|
|
34
|
+
*
|
|
35
|
+
* The float is one JS-driven `Animated.Value` (`useNativeDriver: false`):
|
|
36
|
+
* `fontSize` is part of the transition and can't be native-driven, and
|
|
37
|
+
* mixing a native with a JS value on one component throws — the same
|
|
38
|
+
* trade-off `Expander` and `InlineViewSwitcher` already accepted.
|
|
39
|
+
* `useReducedMotion()` snaps between the two states instead.
|
|
40
|
+
*
|
|
41
|
+
* **The label's travel is measured, not hardcoded.** The web can express its
|
|
42
|
+
* resting position as `top: 50%; transform: translateY(-50%)` and its
|
|
43
|
+
* floated one as `top: 6px`, but RN can't interpolate between a percentage
|
|
44
|
+
* and a fixed offset, so the row reports its own height through `onLayout`
|
|
45
|
+
* and the distance is derived from it. That also keeps the label centred if
|
|
46
|
+
* a consumer makes the row taller than the 56 dp minimum.
|
|
47
|
+
*
|
|
48
|
+
* The `:focus` inset ring is dropped rather than approximated. `TextField`'s
|
|
49
|
+
* own precedent — recolor the border on focus — doesn't transfer, because an
|
|
50
|
+
* `EntryRow` has no border of its own to recolor: it's a row inside a
|
|
51
|
+
* `BoxedList`, and adding one would shift the list's geometry. On a touch
|
|
52
|
+
* device the state is already unmistakable anyway: the label floats up, the
|
|
53
|
+
* text fades in, and the keyboard opens. The `prefers-contrast: more` block
|
|
54
|
+
* is a variation on that same ring, so it goes with it.
|
|
55
|
+
*
|
|
56
|
+
* Tapping anywhere on the row focuses the field, the same affordance the web
|
|
57
|
+
* version's row-level `onClick` provides.
|
|
58
|
+
*
|
|
59
|
+
* The visible label is hidden from assistive tech and the `title` becomes
|
|
60
|
+
* the input's `accessibilityLabel` instead. On the web the two are bound by
|
|
61
|
+
* `<label htmlFor>`, which RN has no equivalent for — left as-is, the label
|
|
62
|
+
* would be announced as loose text next to an unnamed field. `testID` lands
|
|
63
|
+
* on the row rather than the input (unlike the web version, which spreads
|
|
64
|
+
* every remaining prop onto the `<input>`), matching what every other
|
|
65
|
+
* component in this package does; reach the field itself by its accessible
|
|
66
|
+
* name.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const [name, setName] = useState('');
|
|
70
|
+
*
|
|
71
|
+
* <BoxedList>
|
|
72
|
+
* <EntryRow title="Display name" value={name} onValueChange={setName} />
|
|
73
|
+
* </BoxedList>
|
|
74
|
+
*
|
|
75
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.EntryRow.html
|
|
76
|
+
*/
|
|
77
|
+
export declare const EntryRow: import('react').ForwardRefExoticComponent<EntryRowProps & import('react').RefAttributes<RNTextInput>>;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { TextInput } from 'react-native';
|
|
3
|
+
import { EntryRowProps } from '../EntryRow';
|
|
4
|
+
export interface PasswordEntryRowProps extends Omit<EntryRowProps, 'secureTextEntry' | 'trailing'> {
|
|
5
|
+
/** Additional trailing widgets placed before the reveal button. */
|
|
6
|
+
trailing?: ReactNode;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Password entry row with a built-in reveal/conceal toggle, mirroring
|
|
10
|
+
* `AdwPasswordEntryRow` and `@gnome-ui/react`'s own `PasswordEntryRow`. It's
|
|
11
|
+
* an `EntryRow` that masks its input and always carries a trailing button to
|
|
12
|
+
* show or hide what's been typed. Use inside a `BoxedList` for password
|
|
13
|
+
* settings fields — and don't add your own reveal button through `trailing`,
|
|
14
|
+
* which is for anything that should sit *before* this one.
|
|
15
|
+
*
|
|
16
|
+
* `type={revealed ? 'text' : 'password'}` becomes RN's own
|
|
17
|
+
* `secureTextEntry`, and `autoComplete="current-password"` ports as-is —
|
|
18
|
+
* RN's `autoComplete` accepts the same value and is what lets password
|
|
19
|
+
* managers and the platform keyboard offer a saved credential. Pass
|
|
20
|
+
* `"new-password"` on registration or change-password forms.
|
|
21
|
+
*
|
|
22
|
+
* The reveal control is the already-shipped `IconButton` rather than a
|
|
23
|
+
* hand-rolled pressable, which costs one visual detail: `IconButton` is
|
|
24
|
+
* circular (it's `Button` at `shape="circular"`), where the web's
|
|
25
|
+
* `.revealButton` is a 32 dp square with a 6 dp radius. A circular flat icon
|
|
26
|
+
* button is the idiomatic touch control and keeps this row consistent with
|
|
27
|
+
* every other icon action in the package, so it's the better trade than
|
|
28
|
+
* introducing a fifth flat-pressable implementation. The CSS's resting
|
|
29
|
+
* `opacity: 0.55` is dropped too: it exists so the button can brighten on
|
|
30
|
+
* hover, and with no hover on a touch device a permanently dimmed control is
|
|
31
|
+
* just harder to see — the same reasoning that collapses `:hover` everywhere
|
|
32
|
+
* else here.
|
|
33
|
+
*
|
|
34
|
+
* The web needs `e.stopPropagation()` so pressing the button doesn't also
|
|
35
|
+
* trigger the row's focus-the-input click. RN's responder system routes a
|
|
36
|
+
* touch to the innermost pressable, so there's nothing to stop.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* <BoxedList>
|
|
40
|
+
* <PasswordEntryRow title="Password" value={password} onValueChange={setPassword} />
|
|
41
|
+
* </BoxedList>
|
|
42
|
+
*
|
|
43
|
+
* @see https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/class.PasswordEntryRow.html
|
|
44
|
+
*/
|
|
45
|
+
export declare const PasswordEntryRow: import('react').ForwardRefExoticComponent<PasswordEntryRowProps & import('react').RefAttributes<TextInput>>;
|