@mrmeg/expo-ui 0.27.1 → 0.29.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/CHANGELOG.md +666 -0
- package/LLM_USAGE.md +241 -37
- package/README.md +591 -136
- package/bin/expo-ui-icons.mjs +509 -0
- package/dist/components/Accordion.js +12 -6
- package/dist/components/AnimatedView.d.ts +1 -1
- package/dist/components/AnimatedView.js +5 -2
- package/dist/components/Avatar.d.ts +1 -1
- package/dist/components/Avatar.js +2 -2
- package/dist/components/Badge.js +5 -1
- package/dist/components/BottomSheet.d.ts +29 -1
- package/dist/components/BottomSheet.js +107 -30
- package/dist/components/Button.d.ts +20 -4
- package/dist/components/Button.js +60 -43
- package/dist/components/Card.d.ts +1 -1
- package/dist/components/Card.js +24 -4
- package/dist/components/Carousel.js +2 -2
- package/dist/components/Checkbox.js +51 -44
- package/dist/components/Collapsible.js +7 -3
- package/dist/components/Dialog.js +87 -16
- package/dist/components/Drawer.d.ts +1 -1
- package/dist/components/Drawer.js +65 -55
- package/dist/components/DropdownMenu.js +11 -7
- package/dist/components/EmptyState.d.ts +2 -2
- package/dist/components/EmptyState.js +5 -2
- package/dist/components/Hydrated.d.ts +31 -0
- package/dist/components/Hydrated.js +30 -0
- package/dist/components/Icon.d.ts +71 -28
- package/dist/components/Icon.js +70 -28
- package/dist/components/IconGlyph.d.ts +15 -0
- package/dist/components/IconGlyph.js +32 -0
- package/dist/components/IconGlyph.native.d.ts +10 -0
- package/dist/components/IconGlyph.native.js +30 -0
- package/dist/components/InputOTP.js +5 -2
- package/dist/components/Item.d.ts +103 -4
- package/dist/components/Item.js +168 -15
- package/dist/components/KeyboardAvoidingView.d.ts +1 -1
- package/dist/components/KeyboardAvoidingView.js +4 -1
- package/dist/components/Label.js +3 -2
- package/dist/components/MaxWidthContainer.js +1 -1
- package/dist/components/Notification.js +45 -27
- package/dist/components/Popover.d.ts +18 -5
- package/dist/components/Popover.js +72 -8
- package/dist/components/Progress.js +8 -6
- package/dist/components/RadioGroup.js +52 -49
- package/dist/components/Screen.d.ts +61 -0
- package/dist/components/Screen.js +62 -0
- package/dist/components/SectionHeader.d.ts +1 -1
- package/dist/components/SegmentedControl.d.ts +12 -4
- package/dist/components/SegmentedControl.js +94 -4
- package/dist/components/Select.js +21 -26
- package/dist/components/Skeleton.js +24 -10
- package/dist/components/Slider.d.ts +11 -3
- package/dist/components/Slider.js +73 -6
- package/dist/components/StatCard.d.ts +4 -2
- package/dist/components/StatCard.js +3 -1
- package/dist/components/StyledText.d.ts +7 -1
- package/dist/components/StyledText.js +5 -3
- package/dist/components/Switch.d.ts +1 -1
- package/dist/components/Switch.js +20 -30
- package/dist/components/Tabs.d.ts +2 -2
- package/dist/components/Tabs.js +13 -24
- package/dist/components/TextInput.d.ts +1 -1
- package/dist/components/TextInput.js +71 -26
- package/dist/components/ThemeSelector.d.ts +52 -0
- package/dist/components/ThemeSelector.js +90 -0
- package/dist/components/Toggle.d.ts +5 -49
- package/dist/components/Toggle.js +53 -29
- package/dist/components/ToggleGroup.d.ts +4 -8
- package/dist/components/ToggleGroup.js +52 -34
- package/dist/components/Tooltip.d.ts +1 -1
- package/dist/components/Tooltip.js +9 -3
- package/dist/components/UIProvider.d.ts +22 -2
- package/dist/components/UIProvider.js +15 -3
- package/dist/components/bottomSheetDismiss.d.ts +19 -0
- package/dist/components/bottomSheetDismiss.js +44 -0
- package/dist/components/iconBuiltins.generated.d.ts +143 -0
- package/dist/components/iconBuiltins.generated.js +25 -0
- package/dist/components/iconTypes.d.ts +37 -0
- package/dist/components/iconTypes.js +1 -0
- package/dist/components/index.d.ts +47 -44
- package/dist/components/index.js +3 -0
- package/dist/components/keyboardDismiss.js +12 -4
- package/dist/components/nativeTextField.android.js +13 -9
- package/dist/constants/colors.d.ts +40 -5
- package/dist/constants/colors.js +26 -8
- package/dist/constants/fonts.d.ts +41 -2
- package/dist/constants/fonts.js +46 -7
- package/dist/constants/index.d.ts +5 -4
- package/dist/constants/index.js +1 -0
- package/dist/constants/interaction.d.ts +18 -0
- package/dist/constants/interaction.js +18 -0
- package/dist/constants/spacing.d.ts +2 -1
- package/dist/constants/spacing.js +2 -1
- package/dist/design-system.json +429 -132
- package/dist/hooks/index.d.ts +12 -7
- package/dist/hooks/index.js +5 -0
- package/dist/hooks/useDimensions.d.ts +13 -8
- package/dist/hooks/useDimensions.js +123 -48
- package/dist/hooks/useFocusVisible.d.ts +30 -0
- package/dist/hooks/useFocusVisible.js +45 -0
- package/dist/hooks/useFontStyle.d.ts +2 -2
- package/dist/hooks/useFontStyle.js +3 -2
- package/dist/hooks/useHydrated.d.ts +13 -0
- package/dist/hooks/useHydrated.js +19 -0
- package/dist/hooks/useNotificationOffset.d.ts +20 -0
- package/dist/hooks/useNotificationOffset.js +34 -0
- package/dist/hooks/useResources.d.ts +21 -20
- package/dist/hooks/useResources.js +72 -68
- package/dist/hooks/useScalePress.d.ts +8 -5
- package/dist/hooks/useScalePress.js +16 -9
- package/dist/hooks/useShape.d.ts +24 -0
- package/dist/hooks/useShape.js +24 -0
- package/dist/hooks/useStaggeredEntrance.js +9 -7
- package/dist/hooks/useTheme.d.ts +3 -3
- package/dist/hooks/useTheme.js +177 -66
- package/dist/hooks/useWindowInsets.d.ts +13 -0
- package/dist/hooks/useWindowInsets.js +23 -0
- package/dist/icons/LICENSE.lucide +43 -0
- package/dist/icons/lucide.json +2144 -0
- package/dist/index.d.ts +5 -5
- package/dist/lib/composeRefs.d.ts +8 -0
- package/dist/lib/composeRefs.js +20 -0
- package/dist/lib/haptics.d.ts +12 -0
- package/dist/lib/haptics.js +21 -0
- package/dist/lib/index.d.ts +6 -5
- package/dist/lib/index.js +1 -0
- package/dist/lib/interFonts.d.ts +15 -0
- package/dist/lib/interFonts.js +1 -0
- package/dist/lib/interFonts.native.d.ts +3 -0
- package/dist/lib/interFonts.native.js +20 -0
- package/dist/lib/popoverPlacement.d.ts +36 -0
- package/dist/lib/popoverPlacement.js +29 -0
- package/dist/lib/themedStyles.d.ts +1 -1
- package/dist/lib/useAnimatedValue.d.ts +15 -0
- package/dist/lib/useAnimatedValue.js +19 -0
- package/dist/lib/webHitTarget.d.ts +40 -0
- package/dist/lib/webHitTarget.js +37 -0
- package/dist/state/feedbackStore.d.ts +24 -0
- package/dist/state/feedbackStore.js +16 -0
- package/dist/state/globalUIStore.d.ts +27 -0
- package/dist/state/globalUIStore.js +23 -0
- package/dist/state/index.d.ts +7 -5
- package/dist/state/index.js +2 -0
- package/dist/state/initialScheme.d.ts +27 -0
- package/dist/state/initialScheme.js +29 -0
- package/dist/state/notify.d.ts +1 -1
- package/dist/state/themeColorScope.d.ts +1 -1
- package/dist/state/themeStore.d.ts +69 -16
- package/dist/state/themeStore.js +65 -38
- package/llms-full.md +59 -10
- package/llms.txt +10 -1
- package/package.json +79 -4
- package/dist/components/iconRegistry.generated.d.ts +0 -148
- package/dist/components/iconRegistry.generated.js +0 -296
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { StyleProp, ViewStyle } from "react-native";
|
|
3
|
-
import { type TextProps } from "./StyledText";
|
|
4
|
-
import { type IconName, type ThemeColorName } from "./Icon";
|
|
3
|
+
import { type TextProps } from "./StyledText.js";
|
|
4
|
+
import { type IconName, type ThemeColorName } from "./Icon.js";
|
|
5
|
+
/**
|
|
6
|
+
* What a row tells the controls inside it: the title's text when it is a plain
|
|
7
|
+
* string (the accessible name on every platform), and — once ids can be
|
|
8
|
+
* trusted — the id its `ItemTitle` renders under (`nativeID`), so a composed
|
|
9
|
+
* title can still be linked with `aria-labelledby`.
|
|
10
|
+
*
|
|
11
|
+
* `titleId` is `undefined` during SSR and the hydration pass on web: `useId()`
|
|
12
|
+
* values do not survive Expo Router's streamed hydration (see `useHydrated`),
|
|
13
|
+
* so ids only appear in the first client render after it.
|
|
14
|
+
*/
|
|
15
|
+
export interface ItemLabel {
|
|
16
|
+
titleId: string | undefined;
|
|
17
|
+
title: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The row label a control inside an `Item` can borrow, or `null` outside a
|
|
21
|
+
* row. `Switch`, `Checkbox` and `Toggle` use it when they have no label of
|
|
22
|
+
* their own: `aria-labelledby={titleId}` on web, `accessibilityLabel={title}`
|
|
23
|
+
* on native. Apps composing their own controls can do the same.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useItemLabel(): ItemLabel | null;
|
|
26
|
+
type OwnLabelProps = {
|
|
27
|
+
accessibilityLabel?: string;
|
|
28
|
+
"aria-label"?: string;
|
|
29
|
+
"aria-labelledby"?: string;
|
|
30
|
+
accessibilityLabelledBy?: string | string[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Accessibility props a control inside an `Item` should spread when it has no
|
|
34
|
+
* label of its own — its own props always win, so this returns `{}` whenever
|
|
35
|
+
* any label prop is set, or outside a row. A plain-text title becomes the
|
|
36
|
+
* control's `accessibilityLabel` (`aria-label` on web) on every platform, in
|
|
37
|
+
* the server HTML too. A composed title has no text to copy, so the control
|
|
38
|
+
* points at the title's id instead — `aria-labelledby` on web,
|
|
39
|
+
* `accessibilityLabelledBy` on Android — once that id exists (after hydration
|
|
40
|
+
* on web; see `ItemLabel`).
|
|
41
|
+
*/
|
|
42
|
+
export declare function useItemControlLabel(own: OwnLabelProps): OwnLabelProps;
|
|
5
43
|
export interface ItemProps {
|
|
6
44
|
children?: React.ReactNode;
|
|
7
45
|
/** Makes the row pressable with scale feedback (mirrors Card's pattern). */
|
|
8
46
|
onPress?: () => void;
|
|
9
47
|
/** Disables press handling when `onPress` is set. */
|
|
10
48
|
disabled?: boolean;
|
|
11
|
-
/**
|
|
49
|
+
/**
|
|
50
|
+
* Renders a hairline divider below the row, starting under the title (past
|
|
51
|
+
* the `ItemMedia` slot when there is one). Inside an `ItemGroup` the group
|
|
52
|
+
* sets this for every row but the last; pass `false` to drop one row's line.
|
|
53
|
+
*/
|
|
12
54
|
separator?: boolean;
|
|
13
55
|
/** Custom style override for the row. */
|
|
14
56
|
style?: StyleProp<ViewStyle>;
|
|
@@ -20,7 +62,9 @@ export interface ItemProps {
|
|
|
20
62
|
* `spacing.rowPaddingX`) and gap, a 44pt min height on native (40 on web),
|
|
21
63
|
* and an optional pressable scale interaction. Compose
|
|
22
64
|
* with `ItemMedia`, `ItemContent` (+ `ItemTitle`/`ItemDescription`), and
|
|
23
|
-
* `ItemActions`.
|
|
65
|
+
* `ItemActions`. Stack rows in an `ItemGroup`, which draws the separators;
|
|
66
|
+
* the row's own padding is the screen's only horizontal inset, so don't wrap
|
|
67
|
+
* rows in a padded or bordered container.
|
|
24
68
|
*
|
|
25
69
|
* @example
|
|
26
70
|
* ```tsx
|
|
@@ -37,6 +81,60 @@ export interface ItemProps {
|
|
|
37
81
|
* ```
|
|
38
82
|
*/
|
|
39
83
|
export declare function Item({ children, onPress, disabled, separator, style }: ItemProps): React.JSX.Element;
|
|
84
|
+
export interface ItemGroupProps {
|
|
85
|
+
/**
|
|
86
|
+
* Section label above the rows: an uppercase eyebrow in the muted color,
|
|
87
|
+
* announced as a header.
|
|
88
|
+
*/
|
|
89
|
+
title?: string;
|
|
90
|
+
/** Supporting copy under the title. */
|
|
91
|
+
description?: string;
|
|
92
|
+
/** Helper text under the rows, e.g. what the settings above it do. */
|
|
93
|
+
footer?: string;
|
|
94
|
+
/**
|
|
95
|
+
* The rows: `Item`s, or components that render one. Each direct child is
|
|
96
|
+
* one row; the group draws the hairline under every row but the last.
|
|
97
|
+
*/
|
|
98
|
+
children?: React.ReactNode;
|
|
99
|
+
/** Style override for the group container (outer margins, width). */
|
|
100
|
+
style?: StyleProp<ViewStyle>;
|
|
101
|
+
/** Test id for the group container. */
|
|
102
|
+
testID?: string;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* ItemGroup
|
|
106
|
+
*
|
|
107
|
+
* A flat grouped list: an optional eyebrow title and description, full-width
|
|
108
|
+
* `Item` rows separated by inset hairlines, and optional footer text. No
|
|
109
|
+
* border, radius, shadow, or fill: the header and footer sit on the row
|
|
110
|
+
* padding (`spacing.rowPaddingX`), so the group needs no horizontal padding
|
|
111
|
+
* from its parent. Space groups apart with `spacing.sectionSpacing`.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```tsx
|
|
115
|
+
* <ItemGroup title="Account" footer="Signed in as jane@example.com">
|
|
116
|
+
* <Item onPress={openProfile}>
|
|
117
|
+
* <ItemMedia icon="user" />
|
|
118
|
+
* <ItemContent>
|
|
119
|
+
* <ItemTitle>Edit profile</ItemTitle>
|
|
120
|
+
* </ItemContent>
|
|
121
|
+
* <ItemActions>
|
|
122
|
+
* <Icon name="chevron-right" size={18} color="mutedForeground" />
|
|
123
|
+
* </ItemActions>
|
|
124
|
+
* </Item>
|
|
125
|
+
* <Item>
|
|
126
|
+
* <ItemMedia icon="bell" />
|
|
127
|
+
* <ItemContent>
|
|
128
|
+
* <ItemTitle>Notifications</ItemTitle>
|
|
129
|
+
* </ItemContent>
|
|
130
|
+
* <ItemActions>
|
|
131
|
+
* <Switch checked={enabled} onCheckedChange={setEnabled} />
|
|
132
|
+
* </ItemActions>
|
|
133
|
+
* </Item>
|
|
134
|
+
* </ItemGroup>
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
export declare function ItemGroup({ title, description, footer, children, style, testID }: ItemGroupProps): React.JSX.Element;
|
|
40
138
|
export interface ItemMediaProps {
|
|
41
139
|
/** Square size of the media slot in pixels. @default 40 */
|
|
42
140
|
size?: number;
|
|
@@ -92,3 +190,4 @@ export interface ItemActionsProps {
|
|
|
92
190
|
* icon, a `Switch`, value text, etc.).
|
|
93
191
|
*/
|
|
94
192
|
export declare function ItemActions({ children, style }: ItemActionsProps): React.JSX.Element;
|
|
193
|
+
export {};
|
package/dist/components/Item.js
CHANGED
|
@@ -1,14 +1,93 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React, { Children, createContext, isValidElement, use, useId } from "react";
|
|
2
3
|
import { View, Pressable, StyleSheet, Platform, Animated, } from "react-native";
|
|
3
|
-
import { StyledText, CaptionText } from "./StyledText.js";
|
|
4
|
+
import { StyledText, CaptionText, EyebrowText } from "./StyledText.js";
|
|
5
|
+
import { useHydrated } from "../hooks/useHydrated.js";
|
|
4
6
|
import { Icon } from "./Icon.js";
|
|
5
7
|
import { useTheme } from "../hooks/useTheme.js";
|
|
6
8
|
import { useScalePress } from "../hooks/useScalePress.js";
|
|
9
|
+
import { useFocusVisible } from "../hooks/useFocusVisible.js";
|
|
7
10
|
import { spacing } from "../constants/spacing.js";
|
|
8
|
-
|
|
9
|
-
//
|
|
11
|
+
import { interaction } from "../constants/interaction.js";
|
|
12
|
+
// Default ItemMedia footprint (see ItemMedia's `size` prop).
|
|
10
13
|
const DEFAULT_MEDIA_SIZE = 40;
|
|
11
|
-
const
|
|
14
|
+
const ItemGroupRowContext = createContext(null);
|
|
15
|
+
const SEPARATED_ROW = { separator: true };
|
|
16
|
+
const LAST_ROW = { separator: false };
|
|
17
|
+
const ItemLabelContext = createContext(null);
|
|
18
|
+
/** True inside `ItemContent`, where the row's title lives. */
|
|
19
|
+
const ItemContentContext = createContext(false);
|
|
20
|
+
/**
|
|
21
|
+
* The row's title, read synchronously from its element tree: the first
|
|
22
|
+
* `ItemTitle` directly inside a direct `ItemContent`. `found` is false when
|
|
23
|
+
* the row has no such title (a media-only or custom row), so controls in it
|
|
24
|
+
* borrow nothing. `text` is the title when its children are strings (or
|
|
25
|
+
* numbers); a composed title (`<ItemTitle>{name} <Badge/></ItemTitle>`) gives
|
|
26
|
+
* `undefined` — web still links by id, native then needs an explicit label.
|
|
27
|
+
*/
|
|
28
|
+
function scanTitle(children) {
|
|
29
|
+
for (const child of Children.toArray(children)) {
|
|
30
|
+
if (!isValidElement(child))
|
|
31
|
+
continue;
|
|
32
|
+
if (child.type === ItemContent) {
|
|
33
|
+
for (const inner of Children.toArray(child.props.children)) {
|
|
34
|
+
if (isValidElement(inner) && inner.type === ItemTitle) {
|
|
35
|
+
const parts = Children.toArray(inner.props.children);
|
|
36
|
+
const plain = parts.length > 0 && parts.every((part) => typeof part === "string" || typeof part === "number");
|
|
37
|
+
return { found: true, text: plain ? parts.join("") : undefined };
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { found: false, text: undefined };
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The row label a control inside an `Item` can borrow, or `null` outside a
|
|
46
|
+
* row. `Switch`, `Checkbox` and `Toggle` use it when they have no label of
|
|
47
|
+
* their own: `aria-labelledby={titleId}` on web, `accessibilityLabel={title}`
|
|
48
|
+
* on native. Apps composing their own controls can do the same.
|
|
49
|
+
*/
|
|
50
|
+
export function useItemLabel() {
|
|
51
|
+
return use(ItemLabelContext);
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Accessibility props a control inside an `Item` should spread when it has no
|
|
55
|
+
* label of its own — its own props always win, so this returns `{}` whenever
|
|
56
|
+
* any label prop is set, or outside a row. A plain-text title becomes the
|
|
57
|
+
* control's `accessibilityLabel` (`aria-label` on web) on every platform, in
|
|
58
|
+
* the server HTML too. A composed title has no text to copy, so the control
|
|
59
|
+
* points at the title's id instead — `aria-labelledby` on web,
|
|
60
|
+
* `accessibilityLabelledBy` on Android — once that id exists (after hydration
|
|
61
|
+
* on web; see `ItemLabel`).
|
|
62
|
+
*/
|
|
63
|
+
export function useItemControlLabel(own) {
|
|
64
|
+
const label = use(ItemLabelContext);
|
|
65
|
+
if (!label ||
|
|
66
|
+
own.accessibilityLabel !== undefined ||
|
|
67
|
+
own["aria-label"] !== undefined ||
|
|
68
|
+
own["aria-labelledby"] !== undefined ||
|
|
69
|
+
own.accessibilityLabelledBy !== undefined) {
|
|
70
|
+
return {};
|
|
71
|
+
}
|
|
72
|
+
if (label.title !== undefined)
|
|
73
|
+
return { accessibilityLabel: label.title };
|
|
74
|
+
if (label.titleId === undefined)
|
|
75
|
+
return {};
|
|
76
|
+
return Platform.OS === "web" ? { "aria-labelledby": label.titleId } : { accessibilityLabelledBy: label.titleId };
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Where a row's hairline starts: under the title when the row leads with an
|
|
80
|
+
* `ItemMedia` (row padding + media + gap, so a custom `size` lines up too),
|
|
81
|
+
* otherwise at the row padding.
|
|
82
|
+
*/
|
|
83
|
+
function separatorInset(children) {
|
|
84
|
+
for (const child of Children.toArray(children)) {
|
|
85
|
+
if (isValidElement(child) && child.type === ItemMedia) {
|
|
86
|
+
return spacing.rowPaddingX + (child.props.size ?? DEFAULT_MEDIA_SIZE) + spacing.rowGap;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return spacing.rowPaddingX;
|
|
90
|
+
}
|
|
12
91
|
/**
|
|
13
92
|
* Item
|
|
14
93
|
*
|
|
@@ -16,7 +95,9 @@ const SEPARATOR_INSET = spacing.rowPaddingX + DEFAULT_MEDIA_SIZE + spacing.rowGa
|
|
|
16
95
|
* `spacing.rowPaddingX`) and gap, a 44pt min height on native (40 on web),
|
|
17
96
|
* and an optional pressable scale interaction. Compose
|
|
18
97
|
* with `ItemMedia`, `ItemContent` (+ `ItemTitle`/`ItemDescription`), and
|
|
19
|
-
* `ItemActions`.
|
|
98
|
+
* `ItemActions`. Stack rows in an `ItemGroup`, which draws the separators;
|
|
99
|
+
* the row's own padding is the screen's only horizontal inset, so don't wrap
|
|
100
|
+
* rows in a padded or bordered container.
|
|
20
101
|
*
|
|
21
102
|
* @example
|
|
22
103
|
* ```tsx
|
|
@@ -32,20 +113,78 @@ const SEPARATOR_INSET = spacing.rowPaddingX + DEFAULT_MEDIA_SIZE + spacing.rowGa
|
|
|
32
113
|
* </Item>
|
|
33
114
|
* ```
|
|
34
115
|
*/
|
|
35
|
-
export function Item({ children, onPress, disabled, separator
|
|
36
|
-
const { theme } = useTheme();
|
|
116
|
+
export function Item({ children, onPress, disabled, separator, style }) {
|
|
117
|
+
const { theme, getFocusRingStyle } = useTheme();
|
|
118
|
+
const groupRow = use(ItemGroupRowContext);
|
|
119
|
+
const showSeparator = separator ?? groupRow?.separator ?? false;
|
|
120
|
+
const reactId = useId();
|
|
121
|
+
const hydrated = useHydrated();
|
|
122
|
+
const { found: hasTitle, text: title } = scanTitle(children);
|
|
123
|
+
// Ids only after hydration on web (see ItemLabel); native has no hydration.
|
|
124
|
+
const titleId = Platform.OS !== "web" || hydrated ? reactId : undefined;
|
|
125
|
+
const label = React.useMemo(() => (hasTitle ? { titleId, title } : null), [hasTitle, titleId, title]);
|
|
37
126
|
const { animatedStyle, pressHandlers } = useScalePress({
|
|
38
127
|
disabled: !onPress || !!disabled,
|
|
39
128
|
scaleTo: 0.98,
|
|
40
|
-
haptic: false,
|
|
41
129
|
});
|
|
42
|
-
const
|
|
43
|
-
const
|
|
130
|
+
const focus = useFocusVisible();
|
|
131
|
+
const row = (_jsx(View, { style: [styles.row, style], children: _jsx(ItemLabelContext.Provider, { value: label, children: children }) }));
|
|
132
|
+
const content = showSeparator ? (_jsxs(View, { children: [row, _jsx(View, { style: [
|
|
133
|
+
styles.separator,
|
|
134
|
+
{ marginLeft: separatorInset(children), backgroundColor: theme.colors.border },
|
|
135
|
+
] })] })) : (row);
|
|
44
136
|
if (onPress) {
|
|
45
|
-
return (_jsx(Pressable, { onPress: onPress, disabled: disabled, accessibilityRole: "button", accessibilityState: { disabled: !!disabled }, ...pressHandlers,
|
|
137
|
+
return (_jsx(Pressable, { onPress: onPress, disabled: disabled, accessibilityRole: "button", accessibilityState: { disabled: !!disabled }, ...pressHandlers, onFocus: focus.onFocus, onBlur: focus.onBlur, style: ({ pressed }) => [
|
|
138
|
+
{ borderRadius: spacing.radiusSm },
|
|
139
|
+
Platform.OS === "web" && { cursor: "pointer", outlineStyle: "none" },
|
|
140
|
+
pressed && { opacity: interaction.pressedOpacity },
|
|
141
|
+
focus.focused && !disabled && getFocusRingStyle(),
|
|
142
|
+
], children: _jsx(Animated.View, { style: animatedStyle, children: content }) }));
|
|
46
143
|
}
|
|
47
144
|
return content;
|
|
48
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* ItemGroup
|
|
148
|
+
*
|
|
149
|
+
* A flat grouped list: an optional eyebrow title and description, full-width
|
|
150
|
+
* `Item` rows separated by inset hairlines, and optional footer text. No
|
|
151
|
+
* border, radius, shadow, or fill: the header and footer sit on the row
|
|
152
|
+
* padding (`spacing.rowPaddingX`), so the group needs no horizontal padding
|
|
153
|
+
* from its parent. Space groups apart with `spacing.sectionSpacing`.
|
|
154
|
+
*
|
|
155
|
+
* @example
|
|
156
|
+
* ```tsx
|
|
157
|
+
* <ItemGroup title="Account" footer="Signed in as jane@example.com">
|
|
158
|
+
* <Item onPress={openProfile}>
|
|
159
|
+
* <ItemMedia icon="user" />
|
|
160
|
+
* <ItemContent>
|
|
161
|
+
* <ItemTitle>Edit profile</ItemTitle>
|
|
162
|
+
* </ItemContent>
|
|
163
|
+
* <ItemActions>
|
|
164
|
+
* <Icon name="chevron-right" size={18} color="mutedForeground" />
|
|
165
|
+
* </ItemActions>
|
|
166
|
+
* </Item>
|
|
167
|
+
* <Item>
|
|
168
|
+
* <ItemMedia icon="bell" />
|
|
169
|
+
* <ItemContent>
|
|
170
|
+
* <ItemTitle>Notifications</ItemTitle>
|
|
171
|
+
* </ItemContent>
|
|
172
|
+
* <ItemActions>
|
|
173
|
+
* <Switch checked={enabled} onCheckedChange={setEnabled} />
|
|
174
|
+
* </ItemActions>
|
|
175
|
+
* </Item>
|
|
176
|
+
* </ItemGroup>
|
|
177
|
+
* ```
|
|
178
|
+
*/
|
|
179
|
+
export function ItemGroup({ title, description, footer, children, style, testID }) {
|
|
180
|
+
const { theme } = useTheme();
|
|
181
|
+
const rows = Children.toArray(children);
|
|
182
|
+
// react-native-web renders an unlevelled header as h1; level the title as h2
|
|
183
|
+
// there. Native has no heading levels and takes the role alone. Read at render
|
|
184
|
+
// time (not module load) so tests can flip `Platform.OS`.
|
|
185
|
+
const headingLevel = Platform.OS === "web" ? { "aria-level": 2 } : undefined;
|
|
186
|
+
return (_jsxs(View, { style: style, testID: testID, children: [(!!title || !!description) && (_jsxs(View, { style: styles.groupHeader, children: [!!title && (_jsx(EyebrowText, { accessibilityRole: "header", ...headingLevel, style: { color: theme.colors.mutedForeground }, children: title })), !!description && (_jsx(CaptionText, { style: { color: theme.colors.textDim }, children: description }))] })), rows.map((row, index) => (_jsx(ItemGroupRowContext.Provider, { value: index < rows.length - 1 ? SEPARATED_ROW : LAST_ROW, children: row }, isValidElement(row) && row.key != null ? row.key : index))), !!footer && (_jsx(View, { style: styles.groupFooter, children: _jsx(CaptionText, { style: { color: theme.colors.mutedForeground }, children: footer }) }))] }));
|
|
187
|
+
}
|
|
49
188
|
/**
|
|
50
189
|
* ItemMedia
|
|
51
190
|
*
|
|
@@ -73,7 +212,7 @@ export function ItemMedia({ size = DEFAULT_MEDIA_SIZE, icon, iconColor = "mutedF
|
|
|
73
212
|
* content). Sits between `ItemMedia` and `ItemActions`.
|
|
74
213
|
*/
|
|
75
214
|
export function ItemContent({ children, style }) {
|
|
76
|
-
return _jsx(View, { style: [styles.content, style], children: children });
|
|
215
|
+
return (_jsx(View, { style: [styles.content, style], children: _jsx(ItemContentContext.Provider, { value: true, children: children }) }));
|
|
77
216
|
}
|
|
78
217
|
/**
|
|
79
218
|
* ItemTitle
|
|
@@ -81,7 +220,13 @@ export function ItemContent({ children, style }) {
|
|
|
81
220
|
* `Item` row title — label weight (medium) at body size.
|
|
82
221
|
*/
|
|
83
222
|
export function ItemTitle({ children, style, ...props }) {
|
|
84
|
-
|
|
223
|
+
// The row's title (the one inside ItemContent) carries the id the row hands
|
|
224
|
+
// to its controls; titles used elsewhere (ItemMedia initials, a value in
|
|
225
|
+
// ItemActions) render without it.
|
|
226
|
+
const label = use(ItemLabelContext);
|
|
227
|
+
const inContent = use(ItemContentContext);
|
|
228
|
+
const nativeID = inContent ? label?.titleId : undefined;
|
|
229
|
+
return (_jsx(StyledText, { size: "body", fontWeight: "medium", nativeID: nativeID, ...props, style: style, children: children }));
|
|
85
230
|
}
|
|
86
231
|
/**
|
|
87
232
|
* ItemDescription
|
|
@@ -101,7 +246,7 @@ export function ItemDescription({ children, style, ...props }) {
|
|
|
101
246
|
export function ItemActions({ children, style }) {
|
|
102
247
|
return _jsx(View, { style: [styles.actions, style], children: children });
|
|
103
248
|
}
|
|
104
|
-
const styles = StyleSheet.create({
|
|
249
|
+
const styles = /*#__PURE__*/ StyleSheet.create({
|
|
105
250
|
row: {
|
|
106
251
|
flexDirection: "row",
|
|
107
252
|
alignItems: "center",
|
|
@@ -122,6 +267,14 @@ const styles = StyleSheet.create({
|
|
|
122
267
|
},
|
|
123
268
|
separator: {
|
|
124
269
|
height: StyleSheet.hairlineWidth,
|
|
125
|
-
|
|
270
|
+
},
|
|
271
|
+
groupHeader: {
|
|
272
|
+
paddingHorizontal: spacing.rowPaddingX,
|
|
273
|
+
paddingBottom: spacing.xs,
|
|
274
|
+
gap: spacing.xxs,
|
|
275
|
+
},
|
|
276
|
+
groupFooter: {
|
|
277
|
+
paddingHorizontal: spacing.rowPaddingX,
|
|
278
|
+
paddingTop: spacing.xs,
|
|
126
279
|
},
|
|
127
280
|
});
|
|
@@ -18,5 +18,5 @@ export declare function useKeyboardAvoidance(): boolean;
|
|
|
18
18
|
* so screens can avoid the soft keyboard with `automaticOffset`; web renders a
|
|
19
19
|
* plain `View`.
|
|
20
20
|
*/
|
|
21
|
-
export declare function KeyboardAvoidingView({ children, style, behavior, automaticOffset, contentContainerStyle, keyboardVerticalOffset, ...props }: KeyboardAvoidingViewProps): React.JSX.Element;
|
|
21
|
+
export declare function KeyboardAvoidingView({ children, style, behavior: behaviorProp, automaticOffset, contentContainerStyle, keyboardVerticalOffset, ...props }: KeyboardAvoidingViewProps): React.JSX.Element;
|
|
22
22
|
export {};
|
|
@@ -14,7 +14,10 @@ export function useKeyboardAvoidance() {
|
|
|
14
14
|
* so screens can avoid the soft keyboard with `automaticOffset`; web renders a
|
|
15
15
|
* plain `View`.
|
|
16
16
|
*/
|
|
17
|
-
export function KeyboardAvoidingView({ children, style, behavior
|
|
17
|
+
export function KeyboardAvoidingView({ children, style, behavior: behaviorProp, automaticOffset = true, contentContainerStyle, keyboardVerticalOffset, ...props }) {
|
|
18
|
+
// Resolved in the body rather than as a default parameter: the React
|
|
19
|
+
// Compiler can't reorder a computed default, and skipped the component.
|
|
20
|
+
const behavior = behaviorProp === undefined ? (Platform.OS === "ios" ? "padding" : "height") : behaviorProp;
|
|
18
21
|
if (Platform.OS === "web") {
|
|
19
22
|
return (_jsx(KeyboardAvoidanceContext.Provider, { value: true, children: _jsx(View, { style: style, ...props, children: children }) }));
|
|
20
23
|
}
|
package/dist/components/Label.js
CHANGED
|
@@ -4,6 +4,7 @@ import * as LabelPrimitive from "@rn-primitives/label";
|
|
|
4
4
|
import { useTheme } from "../hooks/useTheme.js";
|
|
5
5
|
import { useFontStyle } from "../hooks/useFontStyle.js";
|
|
6
6
|
import { spacing } from "../constants/spacing.js";
|
|
7
|
+
import { interaction } from "../constants/interaction.js";
|
|
7
8
|
import { StyledText } from "./StyledText.js";
|
|
8
9
|
import { createThemedStyles } from "../lib/themedStyles.js";
|
|
9
10
|
const SIZE_CONFIGS = {
|
|
@@ -85,7 +86,7 @@ const createStyles = (theme) => StyleSheet.create({
|
|
|
85
86
|
color: theme.colors.destructive,
|
|
86
87
|
},
|
|
87
88
|
disabledLabel: {
|
|
88
|
-
opacity:
|
|
89
|
+
opacity: interaction.disabledOpacity,
|
|
89
90
|
},
|
|
90
91
|
});
|
|
91
|
-
const themedStyles = createThemedStyles(createStyles);
|
|
92
|
+
const themedStyles = /*#__PURE__*/ createThemedStyles(createStyles);
|
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useCallback,
|
|
2
|
+
import { useCallback, useEffect, useEffectEvent, useMemo, useRef } from "react";
|
|
3
3
|
import { Animated, Easing, StyleSheet, View, ActivityIndicator, Pressable, Platform } from "react-native";
|
|
4
|
-
import {
|
|
4
|
+
import { useStore } from "zustand";
|
|
5
5
|
import { Icon } from "./Icon.js";
|
|
6
6
|
import { useTheme, withAlpha } from "../hooks/useTheme.js";
|
|
7
7
|
import { useReducedMotion } from "../hooks/useReduceMotion.js";
|
|
8
|
+
import { useWindowInsets } from "../hooks/useWindowInsets.js";
|
|
8
9
|
import { spacing } from "../constants/spacing.js";
|
|
9
10
|
import { durations } from "../constants/motion.js";
|
|
10
11
|
import { StyledText } from "./StyledText.js";
|
|
11
12
|
import { palette } from "../constants/colors.js";
|
|
12
13
|
import { translateText } from "../lib/i18n.js";
|
|
13
14
|
import { createThemedStyles } from "../lib/themedStyles.js";
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
import { useAnimatedValue } from "../lib/useAnimatedValue.js";
|
|
16
|
+
import { shouldUseNativeDriver } from "../lib/animations.js";
|
|
17
|
+
import { globalUIStore, selectNotificationOffset } from "../state/globalUIStore.js";
|
|
18
|
+
const timingIn = { duration: durations.fast, easing: Easing.out(Easing.quad), useNativeDriver: shouldUseNativeDriver };
|
|
19
|
+
const timingOut = { duration: durations.instant, easing: Easing.in(Easing.quad), useNativeDriver: shouldUseNativeDriver };
|
|
20
|
+
/**
|
|
21
|
+
* Run an action's handler, then dismiss, even when the handler throws. A
|
|
22
|
+
* module-level function because the React Compiler can't compile a
|
|
23
|
+
* `try`/`finally` and would otherwise skip the whole component.
|
|
24
|
+
*/
|
|
25
|
+
function runThenDismiss(run, dismiss) {
|
|
26
|
+
try {
|
|
27
|
+
run();
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
dismiss();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
17
33
|
/**
|
|
18
34
|
* Notification
|
|
19
35
|
*
|
|
@@ -35,22 +51,27 @@ const timingOut = { duration: durations.instant, easing: Easing.in(Easing.quad),
|
|
|
35
51
|
export const Notification = () => {
|
|
36
52
|
const { theme, getShadowStyle } = useTheme();
|
|
37
53
|
const reduceMotion = useReducedMotion();
|
|
38
|
-
|
|
39
|
-
|
|
54
|
+
// Window insets, not the provider context alone: a toast host above the
|
|
55
|
+
// app's SafeAreaProvider (or inside a Modal) used to fall back to 20 pt and
|
|
56
|
+
// sit under the Dynamic Island.
|
|
57
|
+
const insets = useWindowInsets();
|
|
58
|
+
// Read the store through zustand's `useStore`, not by calling
|
|
59
|
+
// `globalUIStore()` directly: the React Compiler recognizes hooks by their
|
|
60
|
+
// `use` prefix, so it treated that call as a plain function, cached its
|
|
61
|
+
// result, and skipped the hook on the next render (React error #311).
|
|
62
|
+
const { alert, hide } = useStore(globalUIStore);
|
|
63
|
+
// Bars a layout registered with `useNotificationOffset` (a tab bar, a
|
|
64
|
+
// floating header), measured from the window edge like the insets. Select
|
|
65
|
+
// the stable record and derive the maxima here: a selector that built a new
|
|
66
|
+
// object per call would never satisfy `useSyncExternalStore`'s snapshot check.
|
|
67
|
+
const notificationOffsets = useStore(globalUIStore, (state) => state.notificationOffsets);
|
|
68
|
+
const offset = useMemo(() => selectNotificationOffset({ notificationOffsets }), [notificationOffsets]);
|
|
40
69
|
const styles = themedStyles(theme);
|
|
41
70
|
const position = alert?.position ?? "top";
|
|
42
71
|
const isBottom = position === "bottom";
|
|
43
72
|
// Just opacity + translateY — no scale (scale = bouncy feel)
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
opacityRef.current = new Animated.Value(0);
|
|
47
|
-
}
|
|
48
|
-
const opacity = opacityRef.current;
|
|
49
|
-
const translateYRef = useRef(null);
|
|
50
|
-
if (translateYRef.current === null) {
|
|
51
|
-
translateYRef.current = new Animated.Value(0);
|
|
52
|
-
}
|
|
53
|
-
const translateY = translateYRef.current;
|
|
73
|
+
const opacity = useAnimatedValue(0);
|
|
74
|
+
const translateY = useAnimatedValue(0);
|
|
54
75
|
const wasVisibleRef = useRef(false);
|
|
55
76
|
const timerRef = useRef(null);
|
|
56
77
|
const hideNotification = useCallback(() => {
|
|
@@ -88,12 +109,7 @@ export const Notification = () => {
|
|
|
88
109
|
const action = alert?.action;
|
|
89
110
|
if (!action)
|
|
90
111
|
return;
|
|
91
|
-
|
|
92
|
-
action.onPress();
|
|
93
|
-
}
|
|
94
|
-
finally {
|
|
95
|
-
animateOut();
|
|
96
|
-
}
|
|
112
|
+
runThenDismiss(() => action.onPress(), animateOut);
|
|
97
113
|
}, [alert?.action, animateOut]);
|
|
98
114
|
// The auto-dismiss timer only needs the latest animateOut; wrapping it in an
|
|
99
115
|
// Effect Event keeps it out of the deps so the effect doesn't re-run (and
|
|
@@ -150,8 +166,10 @@ export const Notification = () => {
|
|
|
150
166
|
opacity,
|
|
151
167
|
transform: [{ translateY }],
|
|
152
168
|
};
|
|
153
|
-
|
|
154
|
-
|
|
169
|
+
// 8 pt of air past the inset or a registered bar, whichever reaches further;
|
|
170
|
+
// without either, 20 pt from the edge (as before).
|
|
171
|
+
const topPosition = Math.max(insets.top, spacing.smd, offset.top) + spacing.sm;
|
|
172
|
+
const bottomPosition = Math.max(insets.bottom, spacing.smd, offset.bottom) + spacing.sm;
|
|
155
173
|
const getIconProps = () => {
|
|
156
174
|
switch (alert?.type) {
|
|
157
175
|
case "error":
|
|
@@ -208,7 +226,7 @@ export const Notification = () => {
|
|
|
208
226
|
if (!alert?.show) {
|
|
209
227
|
return null;
|
|
210
228
|
}
|
|
211
|
-
return (_jsx(Animated.View, { accessibilityLiveRegion: "polite", accessibilityRole: "alert", style: [
|
|
229
|
+
return (_jsx(Animated.View, { testID: "ui-notification", accessibilityLiveRegion: "polite", accessibilityRole: "alert", style: [
|
|
212
230
|
styles.container,
|
|
213
231
|
isBottom
|
|
214
232
|
? { bottom: bottomPosition }
|
|
@@ -302,4 +320,4 @@ const createStyles = (theme) => StyleSheet.create({
|
|
|
302
320
|
...(Platform.OS === "web" && { cursor: "pointer" }),
|
|
303
321
|
},
|
|
304
322
|
});
|
|
305
|
-
const themedStyles = createThemedStyles(createStyles);
|
|
323
|
+
const themedStyles = /*#__PURE__*/ createThemedStyles(createStyles);
|
|
@@ -3,22 +3,34 @@ import * as React from "react";
|
|
|
3
3
|
import { ViewProps } from "react-native";
|
|
4
4
|
/**
|
|
5
5
|
* Popover Trigger Component
|
|
6
|
-
* The element that triggers the popover to open/close
|
|
6
|
+
* The element that triggers the popover to open/close. Its ref exposes
|
|
7
|
+
* `open()` / `close()` for driving the popover from outside.
|
|
7
8
|
*/
|
|
8
|
-
type PopoverTriggerProps = PopoverPrimitive.TriggerProps
|
|
9
|
+
type PopoverTriggerProps = PopoverPrimitive.TriggerProps & React.RefAttributes<PopoverPrimitive.TriggerRef>;
|
|
10
|
+
/** What a `PopoverTrigger` ref holds: the pressable plus `open()` / `close()`. */
|
|
11
|
+
type PopoverTriggerRef = PopoverPrimitive.TriggerRef;
|
|
9
12
|
declare function PopoverTrigger({ disabled, ...props }: PopoverTriggerProps): React.JSX.Element;
|
|
10
13
|
interface PopoverContentProps extends PopoverPrimitive.ContentProps {
|
|
11
14
|
/**
|
|
12
15
|
* Optional portal host name for custom portal mounting
|
|
13
16
|
*/
|
|
14
17
|
portalHost?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Scroll the children once they outgrow the room on the popover's side
|
|
20
|
+
* (default). Pass `false` for content that brings its own list: a `FlatList`
|
|
21
|
+
* inside a vertical ScrollView warns. The height cap still applies, but the
|
|
22
|
+
* popover can no longer measure its full height, so it stays on `side`.
|
|
23
|
+
*/
|
|
24
|
+
scrollable?: boolean;
|
|
15
25
|
}
|
|
16
26
|
/**
|
|
17
27
|
* Popover Content Component
|
|
18
|
-
* The content that appears in the popover overlay
|
|
19
|
-
*
|
|
28
|
+
* The content that appears in the popover overlay. `side` is a preference: on
|
|
29
|
+
* native the popover opens on the other side when the content does not fit
|
|
30
|
+
* and there is more room there, caps its height to the room, and scrolls.
|
|
31
|
+
* A `style` merges over the themed surface; `insets` default to the safe area.
|
|
20
32
|
*/
|
|
21
|
-
declare function PopoverContent({
|
|
33
|
+
declare function PopoverContent({ portalHost, insets, ...props }: PopoverContentProps): React.JSX.Element;
|
|
22
34
|
/**
|
|
23
35
|
* Popover Header Component
|
|
24
36
|
* Optional header section for the popover content
|
|
@@ -62,3 +74,4 @@ type PopoverComponent = typeof PopoverRoot & {
|
|
|
62
74
|
*/
|
|
63
75
|
declare const Popover: PopoverComponent;
|
|
64
76
|
export { Popover, PopoverTrigger, PopoverContent, PopoverHeader, PopoverBody, PopoverFooter, };
|
|
77
|
+
export type { PopoverTriggerRef };
|