@vvedantb/native-ui 0.3.13 → 0.3.15
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 +7 -3
- package/package.json +1 -1
- package/src/Select.tsx +5 -2
- package/src/brand/AppIcons.tsx +5 -8
- package/src/brand/AppMark.tsx +5 -8
- package/src/brand/GoogleMark.tsx +55 -0
- package/src/brand/index.ts +2 -0
- package/src/entries/brand.ts +1 -1
- package/src/examples/ParityGallery.tsx +5 -0
- package/src/index.ts +1 -1
- package/src/primitives/icon.tsx +5 -3
- package/src/primitives/select.tsx +12 -10
- package/src/selectA11y.ts +26 -0
- package/src/svgA11y.ts +43 -0
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ Optional, only if you import those components:
|
|
|
23
23
|
|
|
24
24
|
- `@gorhom/bottom-sheet` — `BottomSheetContainer`
|
|
25
25
|
- `@react-native-community/datetimepicker` and `dayjs` — `DateTimeField`
|
|
26
|
-
- `react-native-svg` — the app marks (`APP_ICONS`, `Icon*`, `AppMark`, `AppWordmark`) and `PricingModal`. Apps with the icon peers above already have it: `lucide-react-native` and `@tabler/icons-react-native` both require it.
|
|
26
|
+
- `react-native-svg` — the app marks (`APP_ICONS`, `Icon*`, `AppMark`, `AppWordmark`), `GoogleMark` and `PricingModal`. Apps with the icon peers above already have it: `lucide-react-native` and `@tabler/icons-react-native` both require it.
|
|
27
27
|
|
|
28
28
|
Optional and loaded only when installed, so the package imports and runs without them:
|
|
29
29
|
|
|
@@ -57,7 +57,7 @@ The root entry exports everything. It still imports `BottomSheetContainer` and `
|
|
|
57
57
|
| Entry | Exports | Optional peers it reaches |
|
|
58
58
|
| --- | --- | --- |
|
|
59
59
|
| `@vvedantb/native-ui/tab-bar` | `FloatingTabBar`, `FloatingTabBarProps`, `FLOATING_INSET`, `FLOATING_TAB_BAR_HEIGHT`, `FLOATING_TAB_BAR_HEIGHT_LARGE`, `tabBarReserve` | `expo-blur`, `expo-haptics` (loaders) |
|
|
60
|
-
| `@vvedantb/native-ui/brand` | `AppMark`, `AppWordmark`, `APP_ICONS`, `IconVibot` and the other `Icon*`, the SVG helpers (`APP_MARKS`, `appMarkSvg`, `appTileSvg` and the rest), `FAMILY_APPS`, `familyApp`, `V_PRO_APP_BENEFITS` | none |
|
|
60
|
+
| `@vvedantb/native-ui/brand` | `AppMark`, `AppWordmark`, `GoogleMark` (from 0.3.15), `APP_ICONS`, `IconVibot` and the other `Icon*`, the SVG helpers (`APP_MARKS`, `appMarkSvg`, `appTileSvg` and the rest), `FAMILY_APPS`, `familyApp`, `V_PRO_APP_BENEFITS` | none |
|
|
61
61
|
| `@vvedantb/native-ui/button` | `Button`, `ButtonProps`, `ButtonSize`, `ButtonVariant` | none |
|
|
62
62
|
| `@vvedantb/native-ui/primitives` | `Button`, `Input`, `SearchInput`, `Textarea`, `Label`, `Select`, `Switch`, `LabeledSwitchRow`, `Checkbox`, `Card` and its parts, `Badge`, `StatusDot`, `Avatar`, `Separator`, `Progress`, `Spinner`, `Skeleton`, `EmptyState`, `ListRow`, `Chip`, `HeaderIconButton`, `Tabs`, `PillTabs`, `Dialog`, `AlertDialog`, `ConfirmDialog`, `Sheet`, `toast`, `Toaster` | `expo-blur`, `expo-haptics` (loaders) |
|
|
63
63
|
| `@vvedantb/native-ui/search-input` | `SearchInput`, `SearchInputProps` | none |
|
|
@@ -138,7 +138,7 @@ import { NativeUiProvider, Button, Card } from "@vvedantb/native-ui";
|
|
|
138
138
|
| Navigation | `Tabs` (+ `TabsList`, `TabsTrigger`, `TabsContent`), `PillTabs`, `FloatingTabBar`, `FloatingFAB`, `HeaderIconButton` |
|
|
139
139
|
| Overlays | `Dialog`, `AlertDialog`, `ConfirmDialog`, `Sheet` (RN `Modal`, each with the web part names), `BottomSheetContainer` (`@gorhom/bottom-sheet`) |
|
|
140
140
|
| Feedback | `toast()` and `<Toaster />` |
|
|
141
|
-
| Brand and upgrade | `APP_ICONS`, `IconVibot` and the other `Icon*`, `AppMark`, `AppWordmark`, `FAMILY_APPS`, `V_PRO_APP_BENEFITS`, `UpgradeToProCard`, `PricingModal` |
|
|
141
|
+
| Brand and upgrade | `APP_ICONS`, `IconVibot` and the other `Icon*`, `AppMark`, `AppWordmark`, `GoogleMark` (0.3.15), `FAMILY_APPS`, `V_PRO_APP_BENEFITS`, `UpgradeToProCard`, `PricingModal` |
|
|
142
142
|
| Settings and layout | `PageTitle`, `PageHeader` (0.3.13), `SettingsSection`, `SettingsField`, `SettingsToggleRow`, `SettingsStack`, `ScreenContainer`, `SafeAreaContainer`, `FloatingSurface`, `AmbientBackground` |
|
|
143
143
|
|
|
144
144
|
Overlays hold their own open state, or take `open` and `onOpenChange`:
|
|
@@ -197,6 +197,10 @@ The components keep the web sizes on screen and reach a 44 pt touch target with
|
|
|
197
197
|
|
|
198
198
|
Text scales with the system font size. Labelled controls have minimum heights, so a large Dynamic Type size grows them instead of clipping the label (the `SearchInput` lock grows with the font scale); only the `FloatingTabBar` labels are capped (1.5×), to fit the fixed bar. With Reduce Motion on, dialogs, sheets, toasts, the `Select` popover, `PricingModal`, `Progress` and `Skeleton` do not animate. Dialogs, sheets, toasts, `FloatingTabBar` and `FloatingFAB` stay inside the safe-area insets, and dialog and sheet fields stay above the keyboard on iOS and Android.
|
|
199
199
|
|
|
200
|
+
The brand marks (`APP_ICONS`, the `Icon*` exports, `AppMark` and `GoogleMark`) are images named by `title`, and hidden from assistive tech without one. From 0.3.14 they set the props each platform reads: `accessibilityRole="image"` and `accessibilityLabel` on iOS and Android, `role="img"` and `aria-label` on web. Without a title they set `accessibilityElementsHidden` and `importantForAccessibility="no-hide-descendants"` on native and `aria-hidden` on web. `testID` is `data-testid` on web. On web `react-native-svg` passes its props straight to the DOM `<svg>`, and React Native Web does not map `accessible`, the `accessibility*` props or `testID` there. So do not put them on an `Svg` or on a Tabler icon: put them on the parent `View` or `Pressable`. The package draws no Lucide icon, as a Lucide icon always sets `testID` on its `Svg`. On web the `Select` options are Radix DOM nodes, so from 0.3.14 they take their name and test id as `aria-label` and `data-testid` there, and as `accessibilityLabel` and `testID` on iOS and Android.
|
|
201
|
+
|
|
202
|
+
`GoogleMark` (from 0.3.15) is the four-colour Google "G", 18 by default, for a "Continue with Google" button: `<Button variant="outline" leftIcon={<GoogleMark />}>Continue with Google</Button>`. Its colours are fixed, so it takes no `color`. Leave it without a `title` there, as the label names the button.
|
|
203
|
+
|
|
200
204
|
## Floating UI / Android shadows
|
|
201
205
|
|
|
202
206
|
Do not put `elevation` or NativeWind `shadow-*` on nested or translucent chrome. `floatingShadow` is iOS `shadow*` and Android/web `boxShadow`. `FloatingSurface` clips on an inner view.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vvedantb/native-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.15",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Shared React Native / Expo UI primitives. Floating surfaces, forms, overlays, toasts, settings chrome, tab bar and the family app marks.",
|
|
6
6
|
"type": "module",
|
package/src/Select.tsx
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
+
import { Platform } from "react-native";
|
|
2
3
|
import {
|
|
3
4
|
NativeSelectScrollView,
|
|
4
5
|
Select as PrimitiveSelect,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
typeStyle,
|
|
18
19
|
} from "./controlStyles";
|
|
19
20
|
import { palette } from "./palette";
|
|
21
|
+
import { selectItemA11yProps } from "./selectA11y";
|
|
20
22
|
import { useLargerText } from "./theme";
|
|
21
23
|
import { useScheme } from "./useScheme";
|
|
22
24
|
|
|
@@ -138,10 +140,11 @@ function Select<TValue = string | number | null>({
|
|
|
138
140
|
? `${testID}-${String(option.value ?? "null")}`
|
|
139
141
|
: undefined;
|
|
140
142
|
return (
|
|
143
|
+
// On web the item is a Radix DOM node: the name and test id
|
|
144
|
+
// go as `aria-label` and `data-testid` there.
|
|
141
145
|
<SelectItem
|
|
142
146
|
key={`${String(option.value)}-${option.label}`}
|
|
143
|
-
testID
|
|
144
|
-
accessibilityLabel={option.label}
|
|
147
|
+
{...selectItemA11yProps({ label: option.label, testID: optionId }, Platform.OS)}
|
|
145
148
|
label={option.label}
|
|
146
149
|
value={String(option.value ?? NULL_SENTINEL)}
|
|
147
150
|
/>
|
package/src/brand/AppIcons.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { Platform, type StyleProp, type ViewStyle } from "react-native";
|
|
2
2
|
import Svg, { G, Path } from "react-native-svg";
|
|
3
|
+
import { svgA11yProps } from "../svgA11y";
|
|
3
4
|
import { tokens } from "../tokens";
|
|
4
5
|
import { useScheme } from "../useScheme";
|
|
5
6
|
import {
|
|
@@ -19,10 +20,11 @@ export type AppIconProps = {
|
|
|
19
20
|
color?: string;
|
|
20
21
|
/** Stroke width in rendered points, for stroke glyphs. Default: the glyph's own weight. Fill glyphs ignore it. */
|
|
21
22
|
stroke?: number;
|
|
22
|
-
/** Accessible name. Without it the icon is hidden from assistive tech. */
|
|
23
|
+
/** Accessible name. Without it the icon is hidden from assistive tech. On web: `role="img"` and `aria-label`, or `aria-hidden`. */
|
|
23
24
|
title?: string;
|
|
24
25
|
/** `"center"` (default) centres the glyph in its square. `"start"` left-aligns its ink, for vertical lists. */
|
|
25
26
|
align?: "center" | "start";
|
|
27
|
+
/** Set as `data-testid` on web, where `Svg` does not map `testID`. */
|
|
26
28
|
testID?: string;
|
|
27
29
|
style?: StyleProp<ViewStyle>;
|
|
28
30
|
};
|
|
@@ -70,12 +72,7 @@ function createAppIcon(app: AppName, displayName: string): AppIconComponent {
|
|
|
70
72
|
align === "start" ? APP_MARK_VIEWBOX_START[app] : APP_MARK_VIEWBOX[app]
|
|
71
73
|
}
|
|
72
74
|
preserveAspectRatio="xMidYMid meet"
|
|
73
|
-
|
|
74
|
-
accessibilityRole={title ? "image" : undefined}
|
|
75
|
-
accessibilityLabel={title}
|
|
76
|
-
accessibilityElementsHidden={!title}
|
|
77
|
-
importantForAccessibility={title ? "auto" : "no-hide-descendants"}
|
|
78
|
-
testID={testID}
|
|
75
|
+
{...svgA11yProps({ title, testID }, Platform.OS)}
|
|
79
76
|
style={style}
|
|
80
77
|
>
|
|
81
78
|
<G {...glyphProps}>
|
package/src/brand/AppMark.tsx
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { Platform, type StyleProp, type ViewStyle } from "react-native";
|
|
2
2
|
import Svg, { G, Path, Rect } from "react-native-svg";
|
|
3
|
+
import { svgA11yProps } from "../svgA11y";
|
|
3
4
|
import { APP_ICONS } from "./AppIcons";
|
|
4
5
|
import {
|
|
5
6
|
APP_MARKS,
|
|
@@ -18,8 +19,9 @@ export type AppMarkProps = {
|
|
|
18
19
|
variant?: AppMarkVariant;
|
|
19
20
|
/** Width and height in points. Default 24. */
|
|
20
21
|
size?: number;
|
|
21
|
-
/** Accessible name. Without it the mark is hidden from assistive tech. */
|
|
22
|
+
/** Accessible name. Without it the mark is hidden from assistive tech. On web: `role="img"` and `aria-label`, or `aria-hidden`. */
|
|
22
23
|
title?: string;
|
|
24
|
+
/** Set as `data-testid` on web, where `Svg` does not map `testID`. */
|
|
23
25
|
testID?: string;
|
|
24
26
|
style?: StyleProp<ViewStyle>;
|
|
25
27
|
};
|
|
@@ -64,12 +66,7 @@ export function AppMark({
|
|
|
64
66
|
height={size}
|
|
65
67
|
viewBox={MARK_VIEWBOX}
|
|
66
68
|
preserveAspectRatio="xMidYMid meet"
|
|
67
|
-
|
|
68
|
-
accessibilityRole={title ? "image" : undefined}
|
|
69
|
-
accessibilityLabel={title}
|
|
70
|
-
accessibilityElementsHidden={!title}
|
|
71
|
-
importantForAccessibility={title ? "auto" : "no-hide-descendants"}
|
|
72
|
-
testID={testID}
|
|
69
|
+
{...svgA11yProps({ title, testID }, Platform.OS)}
|
|
73
70
|
style={style}
|
|
74
71
|
>
|
|
75
72
|
<Rect width={1024} height={1024} rx={TILE_RADIUS} fill={tone.bg} />
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Platform, type StyleProp, type ViewStyle } from "react-native";
|
|
2
|
+
import Svg, { Path } from "react-native-svg";
|
|
3
|
+
import { svgA11yProps } from "../svgA11y";
|
|
4
|
+
|
|
5
|
+
export type GoogleMarkProps = {
|
|
6
|
+
/** Width and height in points. Default 18. */
|
|
7
|
+
size?: number;
|
|
8
|
+
/** Accessible name. Without it the mark is hidden from assistive tech, as it should be beside a "Continue with Google" label. On web: `role="img"` and `aria-label`, or `aria-hidden`. */
|
|
9
|
+
title?: string;
|
|
10
|
+
/** Set as `data-testid` on web, where `Svg` does not map `testID`. */
|
|
11
|
+
testID?: string;
|
|
12
|
+
style?: StyleProp<ViewStyle>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The four paths of the Google "G" on the 24 grid, each in its brand colour. */
|
|
16
|
+
const GOOGLE_G = [
|
|
17
|
+
{
|
|
18
|
+
fill: "#4285F4",
|
|
19
|
+
d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
fill: "#34A853",
|
|
23
|
+
d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
fill: "#FBBC05",
|
|
27
|
+
d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z",
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
fill: "#EA4335",
|
|
31
|
+
d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z",
|
|
32
|
+
},
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The multicolour Google "G", for a "Continue with Google" button:
|
|
37
|
+
* `<Button variant="outline" leftIcon={<GoogleMark />}>`. The four colours are
|
|
38
|
+
* Google's and fixed, so it takes no `color`.
|
|
39
|
+
*/
|
|
40
|
+
export function GoogleMark({ size = 18, title, testID, style }: GoogleMarkProps) {
|
|
41
|
+
return (
|
|
42
|
+
<Svg
|
|
43
|
+
width={size}
|
|
44
|
+
height={size}
|
|
45
|
+
viewBox="0 0 24 24"
|
|
46
|
+
preserveAspectRatio="xMidYMid meet"
|
|
47
|
+
{...svgA11yProps({ title, testID }, Platform.OS)}
|
|
48
|
+
style={style}
|
|
49
|
+
>
|
|
50
|
+
{GOOGLE_G.map(({ fill, d }) => (
|
|
51
|
+
<Path key={fill} d={d} fill={fill} />
|
|
52
|
+
))}
|
|
53
|
+
</Svg>
|
|
54
|
+
);
|
|
55
|
+
}
|
package/src/brand/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { AppMark } from "./AppMark";
|
|
|
7
7
|
export type { AppMarkProps } from "./AppMark";
|
|
8
8
|
export { AppWordmark } from "./AppWordmark";
|
|
9
9
|
export type { AppWordmarkProps, ProductAppName } from "./AppWordmark";
|
|
10
|
+
export { GoogleMark } from "./GoogleMark";
|
|
11
|
+
export type { GoogleMarkProps } from "./GoogleMark";
|
|
10
12
|
export {
|
|
11
13
|
APP_MARKS,
|
|
12
14
|
APP_MARK_CROP_VIEWBOX,
|
package/src/entries/brand.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
// `@vvedantb/native-ui/brand`: the family app marks and their pure data
|
|
1
|
+
// `@vvedantb/native-ui/brand`: the family app marks and their pure data, and `GoogleMark`.
|
|
2
2
|
export * from "../brand";
|
|
@@ -43,6 +43,7 @@ import {
|
|
|
43
43
|
DialogTitle,
|
|
44
44
|
DialogTrigger,
|
|
45
45
|
EmptyState,
|
|
46
|
+
GoogleMark,
|
|
46
47
|
Input,
|
|
47
48
|
Label,
|
|
48
49
|
LabeledSwitchRow,
|
|
@@ -215,7 +216,11 @@ function GalleryScreen() {
|
|
|
215
216
|
})}
|
|
216
217
|
<AppMark app="vibot" variant="tile" size={40} />
|
|
217
218
|
<AppMark app="vibot" variant="tile-light" size={40} />
|
|
219
|
+
<GoogleMark size={28} />
|
|
218
220
|
</View>
|
|
221
|
+
<Button variant="outline" leftIcon={<GoogleMark />} onPress={() => toast("Continue with Google")}>
|
|
222
|
+
Continue with Google
|
|
223
|
+
</Button>
|
|
219
224
|
</Section>
|
|
220
225
|
|
|
221
226
|
<Section title="Button">
|
package/src/index.ts
CHANGED
|
@@ -144,7 +144,7 @@ export { SettingsField } from "./settings/SettingsField";
|
|
|
144
144
|
export { SettingsToggleRow } from "./settings/SettingsToggleRow";
|
|
145
145
|
export { SettingsStack } from "./settings/SettingsStack";
|
|
146
146
|
export { PINK_50, PINK_100, PINK_600, PINK_700, NEUTRAL_100, NEUTRAL_500, NEUTRAL_900, Colors } from "./colors";
|
|
147
|
-
// Family app marks and their pure data (copied from `@vvedantb/ui/brand`)
|
|
147
|
+
// Family app marks and their pure data (copied from `@vvedantb/ui/brand`), and `GoogleMark`.
|
|
148
148
|
export * from "./brand";
|
|
149
149
|
export { UpgradeToProCard } from "./marketing/UpgradeToProCard";
|
|
150
150
|
export type { UpgradeToProCardProps } from "./marketing/UpgradeToProCard";
|
package/src/primitives/icon.tsx
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import type {
|
|
2
|
+
import type { IconProps as TablerIconProps } from "@tabler/icons-react-native";
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Tabler, not Lucide: a Lucide icon always sets `testID` on its `Svg`, which
|
|
5
|
+
// on web is a DOM `<svg>`, so React logs it as an unknown DOM prop.
|
|
6
|
+
type IconProps = TablerIconProps & {
|
|
7
|
+
as: React.ComponentType<TablerIconProps>;
|
|
6
8
|
className?: string;
|
|
7
9
|
};
|
|
8
10
|
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import * as SelectPrimitive from "@rn-primitives/select";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
ChevronDownIcon,
|
|
6
|
-
ChevronUpIcon,
|
|
7
|
-
} from "lucide-react-native";
|
|
2
|
+
// Tabler, not Lucide: a Lucide icon always sets `testID` on its `Svg`, which
|
|
3
|
+
// on web is a DOM `<svg>`, so React logs it as an unknown DOM prop.
|
|
4
|
+
import { IconCheck, IconChevronDown, IconChevronUp } from "@tabler/icons-react-native";
|
|
8
5
|
import * as React from "react";
|
|
9
6
|
import { Platform, ScrollView, StyleSheet, Text, View } from "react-native";
|
|
10
7
|
import { FadeIn, FadeOut } from "react-native-reanimated";
|
|
@@ -72,7 +69,7 @@ const SelectTrigger = React.forwardRef<
|
|
|
72
69
|
{...props}
|
|
73
70
|
>
|
|
74
71
|
<>{children}</>
|
|
75
|
-
<
|
|
72
|
+
<IconChevronDown size={16} color={tokens.color.muted[scheme]} />
|
|
76
73
|
</SelectPrimitive.Trigger>
|
|
77
74
|
);
|
|
78
75
|
});
|
|
@@ -153,6 +150,11 @@ const SelectLabel = React.forwardRef<
|
|
|
153
150
|
);
|
|
154
151
|
});
|
|
155
152
|
|
|
153
|
+
/**
|
|
154
|
+
* On web `SelectPrimitive.Item` passes these props to a Radix DOM node, not a
|
|
155
|
+
* React Native view, so give it `aria-label` and `data-testid` there, not
|
|
156
|
+
* `accessibilityLabel` or `testID` (`selectItemA11yProps`).
|
|
157
|
+
*/
|
|
156
158
|
type SelectItemProps = React.ComponentPropsWithoutRef<
|
|
157
159
|
typeof SelectPrimitive.Item
|
|
158
160
|
> & {
|
|
@@ -177,7 +179,7 @@ const SelectItem = React.forwardRef<
|
|
|
177
179
|
>
|
|
178
180
|
<View className="absolute right-2 size-3.5 items-center justify-center">
|
|
179
181
|
<SelectPrimitive.ItemIndicator>
|
|
180
|
-
<
|
|
182
|
+
<IconCheck size={16} color={tokens.color.muted[scheme]} />
|
|
181
183
|
</SelectPrimitive.ItemIndicator>
|
|
182
184
|
</View>
|
|
183
185
|
<SelectPrimitive.ItemText
|
|
@@ -220,7 +222,7 @@ function SelectScrollUpButton(
|
|
|
220
222
|
className="items-center justify-center py-1"
|
|
221
223
|
{...props}
|
|
222
224
|
>
|
|
223
|
-
<
|
|
225
|
+
<IconChevronUp size={16} color="#737373" />
|
|
224
226
|
</SelectPrimitive.ScrollUpButton>
|
|
225
227
|
);
|
|
226
228
|
}
|
|
@@ -239,7 +241,7 @@ function SelectScrollDownButton(
|
|
|
239
241
|
className="items-center justify-center py-1"
|
|
240
242
|
{...props}
|
|
241
243
|
>
|
|
242
|
-
<
|
|
244
|
+
<IconChevronDown size={16} color="#737373" />
|
|
243
245
|
</SelectPrimitive.ScrollDownButton>
|
|
244
246
|
);
|
|
245
247
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility and test props for a `Select` option. On web
|
|
3
|
+
* `@rn-primitives/select` passes the props of its `Item` through a `Slot` to
|
|
4
|
+
* the Radix `Select.Item`, a DOM `<div>`. React Native Web never sees them, so
|
|
5
|
+
* React logs `accessibilityLabel` and `testID` there as unknown DOM props. Web
|
|
6
|
+
* gets `aria-label` and `data-testid`; iOS and Android keep the native props.
|
|
7
|
+
* No React Native import, so the tests can load this file.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type SelectItemA11yProps =
|
|
11
|
+
| { "aria-label": string; "data-testid"?: string }
|
|
12
|
+
| { accessibilityLabel: string; testID?: string };
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Spread on the `SelectItem`. Pass `Platform.OS` as `os`. Without `testID` the
|
|
16
|
+
* result has no test id key, not one set to `undefined`.
|
|
17
|
+
*/
|
|
18
|
+
export function selectItemA11yProps(
|
|
19
|
+
{ label, testID }: { label: string; testID?: string },
|
|
20
|
+
os: string,
|
|
21
|
+
): SelectItemA11yProps {
|
|
22
|
+
if (os === "web") {
|
|
23
|
+
return testID === undefined ? { "aria-label": label } : { "aria-label": label, "data-testid": testID };
|
|
24
|
+
}
|
|
25
|
+
return testID === undefined ? { accessibilityLabel: label } : { accessibilityLabel: label, testID };
|
|
26
|
+
}
|
package/src/svgA11y.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Accessibility and test props for a `react-native-svg` `Svg`. React Native
|
|
3
|
+
* Web maps `accessible`, the `accessibility*` props and `testID` on `View`
|
|
4
|
+
* and `Text`, but on web `Svg` is a DOM `<svg>` that takes its props as they
|
|
5
|
+
* are, so React logs each one as an unknown DOM prop. Web gets ARIA and
|
|
6
|
+
* `data-testid`; iOS and Android keep the native props. No React Native
|
|
7
|
+
* import, so the tests can load this file.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export type SvgA11yProps =
|
|
11
|
+
| { role: "img"; "aria-label": string; "data-testid"?: string }
|
|
12
|
+
| { "aria-hidden": true; "data-testid"?: string }
|
|
13
|
+
| { accessible: true; accessibilityRole: "image"; accessibilityLabel: string; testID?: string }
|
|
14
|
+
| {
|
|
15
|
+
accessible: false;
|
|
16
|
+
accessibilityElementsHidden: true;
|
|
17
|
+
importantForAccessibility: "no-hide-descendants";
|
|
18
|
+
testID?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Spread on the `Svg`. With `title` it is an image with that name; without it,
|
|
23
|
+
* it is hidden from assistive tech. Pass `Platform.OS` as `os`. Without
|
|
24
|
+
* `testID` the result has no test id key, not one set to `undefined`.
|
|
25
|
+
*/
|
|
26
|
+
export function svgA11yProps(
|
|
27
|
+
{ title, testID }: { title?: string; testID?: string },
|
|
28
|
+
os: string,
|
|
29
|
+
): SvgA11yProps {
|
|
30
|
+
if (os === "web") {
|
|
31
|
+
const id = testID === undefined ? {} : { "data-testid": testID };
|
|
32
|
+
return title ? { role: "img", "aria-label": title, ...id } : { "aria-hidden": true, ...id };
|
|
33
|
+
}
|
|
34
|
+
const id = testID === undefined ? {} : { testID };
|
|
35
|
+
return title
|
|
36
|
+
? { accessible: true, accessibilityRole: "image", accessibilityLabel: title, ...id }
|
|
37
|
+
: {
|
|
38
|
+
accessible: false,
|
|
39
|
+
accessibilityElementsHidden: true,
|
|
40
|
+
importantForAccessibility: "no-hide-descendants",
|
|
41
|
+
...id,
|
|
42
|
+
};
|
|
43
|
+
}
|