@hjmds/react-native 1.9.0 → 1.11.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 +41 -32
- package/THIRD_PARTY_NOTICES.md +78 -0
- package/dist/actions.d.ts +29 -46
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +16 -58
- package/dist/actions.js.map +1 -1
- package/dist/activity-heatmap.d.ts +9 -0
- package/dist/activity-heatmap.d.ts.map +1 -0
- package/dist/activity-heatmap.js +10 -0
- package/dist/activity-heatmap.js.map +1 -0
- package/dist/auth-screen.d.ts +5 -4
- package/dist/auth-screen.d.ts.map +1 -1
- package/dist/auth-screen.js +14 -4
- package/dist/auth-screen.js.map +1 -1
- package/dist/avatar-blobatar-motion.d.ts +5 -0
- package/dist/avatar-blobatar-motion.d.ts.map +1 -0
- package/dist/avatar-blobatar-motion.js +33 -0
- package/dist/avatar-blobatar-motion.js.map +1 -0
- package/dist/avatar-blobatar.d.ts +6 -0
- package/dist/avatar-blobatar.d.ts.map +1 -0
- package/dist/avatar-blobatar.js +10 -0
- package/dist/avatar-blobatar.js.map +1 -0
- package/dist/carousel-motion.d.ts +4 -1
- package/dist/carousel-motion.d.ts.map +1 -1
- package/dist/carousel-motion.js +16 -7
- package/dist/carousel-motion.js.map +1 -1
- package/dist/code-block.d.ts +8 -0
- package/dist/code-block.d.ts.map +1 -0
- package/dist/code-block.js +17 -0
- package/dist/code-block.js.map +1 -0
- package/dist/content-transition.d.ts +5 -2
- package/dist/content-transition.d.ts.map +1 -1
- package/dist/content-transition.js +14 -8
- package/dist/content-transition.js.map +1 -1
- package/dist/data-display.d.ts +9 -46
- package/dist/data-display.d.ts.map +1 -1
- package/dist/data-display.js +50 -108
- package/dist/data-display.js.map +1 -1
- package/dist/duration-field.d.ts +9 -0
- package/dist/duration-field.d.ts.map +1 -0
- package/dist/duration-field.js +15 -0
- package/dist/duration-field.js.map +1 -0
- package/dist/effect-surface.d.ts +11 -0
- package/dist/effect-surface.d.ts.map +1 -0
- package/dist/effect-surface.js +61 -0
- package/dist/effect-surface.js.map +1 -0
- package/dist/evidence.d.ts +1 -1
- package/dist/evidence.js +1 -1
- package/dist/evidence.js.map +1 -1
- package/dist/feedback.d.ts +1 -5
- package/dist/feedback.d.ts.map +1 -1
- package/dist/feedback.js +63 -22
- package/dist/feedback.js.map +1 -1
- package/dist/floating-action-button.d.ts +2 -2
- package/dist/floating-action-button.d.ts.map +1 -1
- package/dist/floating-action-button.js +7 -7
- package/dist/floating-action-button.js.map +1 -1
- package/dist/folder-preview.d.ts +11 -0
- package/dist/folder-preview.d.ts.map +1 -0
- package/dist/folder-preview.js +10 -0
- package/dist/folder-preview.js.map +1 -0
- package/dist/forms.d.ts +2 -20
- package/dist/forms.d.ts.map +1 -1
- package/dist/forms.js +14 -27
- package/dist/forms.js.map +1 -1
- package/dist/gravity-letters.d.ts +8 -0
- package/dist/gravity-letters.d.ts.map +1 -0
- package/dist/gravity-letters.js +34 -0
- package/dist/gravity-letters.js.map +1 -0
- package/dist/grid-reveal.d.ts +9 -0
- package/dist/grid-reveal.d.ts.map +1 -0
- package/dist/grid-reveal.js +24 -0
- package/dist/grid-reveal.js.map +1 -0
- package/dist/icon-lucide.d.ts +19 -0
- package/dist/icon-lucide.d.ts.map +1 -0
- package/dist/icon-lucide.js +11 -0
- package/dist/icon-lucide.js.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/inline-confirm.d.ts +13 -0
- package/dist/inline-confirm.d.ts.map +1 -0
- package/dist/inline-confirm.js +40 -0
- package/dist/inline-confirm.js.map +1 -0
- package/dist/inputs.d.ts +7 -53
- package/dist/inputs.d.ts.map +1 -1
- package/dist/inputs.js +77 -81
- package/dist/inputs.js.map +1 -1
- package/dist/internal/field-frame.d.ts +16 -0
- package/dist/internal/field-frame.d.ts.map +1 -0
- package/dist/internal/field-frame.js +19 -0
- package/dist/internal/field-frame.js.map +1 -0
- package/dist/internal/recipe-button.d.ts +28 -0
- package/dist/internal/recipe-button.d.ts.map +1 -0
- package/dist/internal/recipe-button.js +64 -0
- package/dist/internal/recipe-button.js.map +1 -0
- package/dist/navigation-bar.d.ts +10 -0
- package/dist/navigation-bar.d.ts.map +1 -0
- package/dist/navigation-bar.js +14 -0
- package/dist/navigation-bar.js.map +1 -0
- package/dist/navigation.d.ts +11 -48
- package/dist/navigation.d.ts.map +1 -1
- package/dist/navigation.js +189 -132
- package/dist/navigation.js.map +1 -1
- package/dist/notification-bell.d.ts +11 -0
- package/dist/notification-bell.d.ts.map +1 -0
- package/dist/notification-bell.js +30 -0
- package/dist/notification-bell.js.map +1 -0
- package/dist/overlays.d.ts.map +1 -1
- package/dist/overlays.js +10 -4
- package/dist/overlays.js.map +1 -1
- package/dist/primitives.d.ts +9 -45
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +11 -24
- package/dist/primitives.js.map +1 -1
- package/dist/reaction-picker.d.ts +10 -0
- package/dist/reaction-picker.d.ts.map +1 -0
- package/dist/reaction-picker.js +11 -0
- package/dist/reaction-picker.js.map +1 -0
- package/dist/scroll-progress.d.ts +9 -0
- package/dist/scroll-progress.d.ts.map +1 -0
- package/dist/scroll-progress.js +6 -0
- package/dist/scroll-progress.js.map +1 -0
- package/dist/sheet-gesture.d.ts.map +1 -1
- package/dist/sheet-gesture.js +17 -3
- package/dist/sheet-gesture.js.map +1 -1
- package/dist/statistic-motion.d.ts +11 -0
- package/dist/statistic-motion.d.ts.map +1 -0
- package/dist/statistic-motion.js +13 -0
- package/dist/statistic-motion.js.map +1 -0
- package/dist/step-player.d.ts +24 -0
- package/dist/step-player.d.ts.map +1 -0
- package/dist/step-player.js +16 -0
- package/dist/step-player.js.map +1 -0
- package/dist/task-list.d.ts +16 -0
- package/dist/task-list.d.ts.map +1 -0
- package/dist/task-list.js +19 -0
- package/dist/task-list.js.map +1 -0
- package/dist/thinking-orb.d.ts +1 -1
- package/dist/thinking-orb.d.ts.map +1 -1
- package/dist/thinking-orb.js +4 -4
- package/dist/thinking-orb.js.map +1 -1
- package/dist/toast-liquid.d.ts.map +1 -1
- package/dist/toast-liquid.js +14 -10
- package/dist/toast-liquid.js.map +1 -1
- package/dist/voice-note.d.ts +24 -0
- package/dist/voice-note.d.ts.map +1 -0
- package/dist/voice-note.js +13 -0
- package/dist/voice-note.js.map +1 -0
- package/docs/liquid-toast.md +48 -2
- package/package.json +136 -7
package/dist/navigation.js
CHANGED
|
@@ -1,29 +1,23 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { getTabNavigationTarget, resolveInitialTabValue, tabsBehaviorDefaults, } from "@hjmds/design-contracts/behaviors";
|
|
3
3
|
import { resolveColorReference } from "@hjmds/design-contracts/color-references";
|
|
4
|
-
import { bottomNavigationRecipe, counterBadgeRecipe, loadMoreRecipe, menuRecipe, spinnerRecipe, tabsRecipe, topBarRecipe, } from "@hjmds/design-contracts/recipes";
|
|
5
|
-
import { createLoadMoreController, validateLoadMoreDescriptor, } from "@hjmds/design-contracts/components/load-more";
|
|
6
4
|
import { resolveBottomNavigationActivation, resolveBottomNavigationConfiguration, resolveBottomNavigationDescriptor, } from "@hjmds/design-contracts/components/bottom-navigation";
|
|
7
|
-
import { getTabNavigationTarget, resolveInitialTabValue, tabsBehaviorDefaults, } from "@hjmds/design-contracts/behaviors";
|
|
8
5
|
import { flattenCollectionItems, validateCollection, } from "@hjmds/design-contracts/components/collection";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
6
|
+
import { createLoadMoreController, validateLoadMoreDescriptor, } from "@hjmds/design-contracts/components/load-more";
|
|
7
|
+
import { backdrop, glyph, radius, spacing, } from "@hjmds/design-contracts/foundations";
|
|
8
|
+
import { resolveGooeyIndicator, } from "@hjmds/design-contracts/gooey-navigation";
|
|
9
|
+
import { bottomNavigationRecipe, counterBadgeRecipe, loadMoreRecipe, menuRecipe, spinnerRecipe, tabsRecipe, topBarRecipe, } from "@hjmds/design-contracts/recipes";
|
|
10
|
+
import { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useRef, useState, } from "react";
|
|
11
|
+
import { AccessibilityInfo, ActivityIndicator, Animated, AppState, Keyboard, Modal, Platform, Pressable, ScrollView, View, findNodeHandle, } from "react-native";
|
|
12
|
+
import { RecipeButton } from "./internal/recipe-button.js";
|
|
11
13
|
import { isWebRenderer, webOnly, webTabProps } from "./internal/web-a11y.js";
|
|
12
14
|
import { Button } from "./actions.js";
|
|
13
|
-
import {
|
|
15
|
+
import { Spinner } from "./feedback.js";
|
|
14
16
|
import { scheduleAfterNativeModalTeardown, shouldAwaitNativeModalDismiss, } from "./internal/modal-lifecycle.js";
|
|
17
|
+
import { useControllableState } from "./internal/state.js";
|
|
15
18
|
import { minimumTargetStyle } from "./internal/styles.js";
|
|
16
19
|
import { Text } from "./primitives.js";
|
|
17
20
|
import { useHjmNativeTheme } from "./provider.js";
|
|
18
|
-
import { Spinner } from "./feedback.js";
|
|
19
|
-
function resolveTabItems(items, options) {
|
|
20
|
-
if ((items === undefined) === (options === undefined)) {
|
|
21
|
-
throw new TypeError("Tabs requires exactly one of items or options");
|
|
22
|
-
}
|
|
23
|
-
if (items !== undefined)
|
|
24
|
-
return items;
|
|
25
|
-
return options.map(({ value, ...option }) => ({ ...option, id: value }));
|
|
26
|
-
}
|
|
27
21
|
function encodedTabId(value) {
|
|
28
22
|
return encodeURIComponent(value);
|
|
29
23
|
}
|
|
@@ -59,13 +53,15 @@ export function TabPanel(props) {
|
|
|
59
53
|
return (_jsx(View, { nativeID: getTabPanelId(tabsId, value, dynamic ? "dynamic" : "keyed"), accessibilityLabel: label, accessibilityLabelledBy: getTabId(tabsId, dynamic ? activeValue : value), accessibilityElementsHidden: !selected, importantForAccessibility: selected ? "auto" : "no-hide-descendants", pointerEvents: selected ? "auto" : "none", role: "tabpanel", style: [style, selected ? null : { display: "none" }], children: children }));
|
|
60
54
|
}
|
|
61
55
|
export function Tabs(props) {
|
|
62
|
-
const { id, label, items,
|
|
56
|
+
const { id, label, items, value: valueProp, defaultValue, onValueChange, activationMode = tabsBehaviorDefaults.activationMode, mountPolicy = tabsBehaviorDefaults.mountPolicy, panelMode = tabsBehaviorDefaults.panelMode, appearance = "standard", orientation = tabsBehaviorDefaults.orientation, direction: directionProp, loop = tabsBehaviorDefaults.loop, size = tabsRecipe.defaults.size, layout = tabsRecipe.defaults.layout, overflow = tabsRecipe.defaults.overflow, renderPanels = true, children, style, tabListStyle, } = props;
|
|
63
57
|
if (!label.trim())
|
|
64
58
|
throw new TypeError("Tabs label must not be empty");
|
|
65
59
|
if (panelMode === "dynamic" && mountPolicy !== "active") {
|
|
66
60
|
throw new TypeError("Tabs dynamic panelMode requires active mountPolicy");
|
|
67
61
|
}
|
|
68
|
-
|
|
62
|
+
if ("options" in props || !Array.isArray(items))
|
|
63
|
+
throw new TypeError("Tabs requires items; options was removed");
|
|
64
|
+
const tabItems = items;
|
|
69
65
|
if (tabItems.length === 0)
|
|
70
66
|
throw new TypeError("Tabs requires at least one option");
|
|
71
67
|
const theme = useHjmNativeTheme();
|
|
@@ -102,6 +98,46 @@ export function Tabs(props) {
|
|
|
102
98
|
const [focusValue, setFocusValue] = useState(selected);
|
|
103
99
|
const [visited, setVisited] = useState(() => new Set([selected]));
|
|
104
100
|
const tabRefs = useRef(new Map());
|
|
101
|
+
const gooey = appearance === "gooey" && orientation === "horizontal";
|
|
102
|
+
const [indicatorRects, setIndicatorRects] = useState({});
|
|
103
|
+
const tabScroll = useRef(null);
|
|
104
|
+
const [tabViewport, setTabViewport] = useState(0);
|
|
105
|
+
const [tabContentWidth, setTabContentWidth] = useState(0);
|
|
106
|
+
const destination = indicatorRects[selected];
|
|
107
|
+
useLayoutEffect(() => {
|
|
108
|
+
if (orientation !== "horizontal" || !scrollable || fitted || !destination || !tabViewport)
|
|
109
|
+
return;
|
|
110
|
+
// Physical RTL offset keeps selected labels visible at 200% scale (iOS showcase).
|
|
111
|
+
const x = Math.max(0, Math.min(destination.x + destination.width / 2 - tabViewport / 2, tabContentWidth - tabViewport));
|
|
112
|
+
tabScroll.current?.scrollTo({ x, animated: false });
|
|
113
|
+
}, [selected, destination, tabViewport, tabContentWidth, direction, orientation, scrollable, fitted]);
|
|
114
|
+
const previousIndicator = useRef(null);
|
|
115
|
+
const progress = useRef(new Animated.Value(1)).current;
|
|
116
|
+
const [indicatorRecipe, setIndicatorRecipe] = useState(null);
|
|
117
|
+
useLayoutEffect(() => {
|
|
118
|
+
progress.stopAnimation();
|
|
119
|
+
progress.setValue(1);
|
|
120
|
+
if (!gooey || !destination) {
|
|
121
|
+
previousIndicator.current = null;
|
|
122
|
+
setIndicatorRecipe(null);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const previous = previousIndicator.current;
|
|
126
|
+
const animate = previous && previous.value !== selected && !environment.reducedMotion && AppState.currentState === "active";
|
|
127
|
+
const recipe = resolveGooeyIndicator(animate ? previous.rect : destination, destination);
|
|
128
|
+
setIndicatorRecipe(recipe);
|
|
129
|
+
previousIndicator.current = { value: selected, rect: destination };
|
|
130
|
+
if (!animate)
|
|
131
|
+
return;
|
|
132
|
+
progress.setValue(0);
|
|
133
|
+
// Width morphing must use the JS driver; transforms alone would distort the pill corners.
|
|
134
|
+
const animation = Animated.timing(progress, { toValue: 1, duration: recipe.duration, useNativeDriver: false });
|
|
135
|
+
animation.start();
|
|
136
|
+
const stop = () => { animation.stop(); progress.setValue(1); };
|
|
137
|
+
const subscription = AppState.addEventListener("change", state => { if (state !== "active")
|
|
138
|
+
stop(); });
|
|
139
|
+
return () => { stop(); subscription.remove(); };
|
|
140
|
+
}, [gooey, selected, destination, environment.reducedMotion, progress]);
|
|
105
141
|
useEffect(() => {
|
|
106
142
|
if (!controlled && !storedValueValid)
|
|
107
143
|
setSelected(collectionFallback);
|
|
@@ -145,6 +181,7 @@ export function Tabs(props) {
|
|
|
145
181
|
if (target !== undefined)
|
|
146
182
|
focusTab(target);
|
|
147
183
|
};
|
|
184
|
+
// Panel flexGrow preserves intrinsic height in auto-sized stacks; flex:1 collapsed it to zero.
|
|
148
185
|
const hasPanels = renderPanels &&
|
|
149
186
|
(children !== undefined || tabItems.some((item) => item.panel !== undefined));
|
|
150
187
|
return (_jsxs(View, { accessibilityLabel: label, style: [
|
|
@@ -154,7 +191,7 @@ export function Tabs(props) {
|
|
|
154
191
|
gap: spacing.md,
|
|
155
192
|
},
|
|
156
193
|
style,
|
|
157
|
-
], children: [
|
|
194
|
+
], children: [_jsxs(ScrollView, { ref: tabScroll, onLayout: event => setTabViewport(event.nativeEvent.layout.width), onContentSizeChange: width => setTabContentWidth(width), nativeID: id, accessibilityLabel: label, accessibilityRole: "tablist", horizontal: orientation === "horizontal", scrollEnabled: scrollable && !fitted, showsHorizontalScrollIndicator: false, showsVerticalScrollIndicator: false, style: tabListStyle, contentContainerStyle: [
|
|
158
195
|
{
|
|
159
196
|
borderBottomColor: orientation === "horizontal"
|
|
160
197
|
? resolveColorReference(tabsRecipe.colors.divider, theme.palette)
|
|
@@ -172,105 +209,114 @@ export function Tabs(props) {
|
|
|
172
209
|
flexGrow: fitted ? 1 : 0,
|
|
173
210
|
flexDirection: orientation === "vertical" ? "column" : "row",
|
|
174
211
|
},
|
|
175
|
-
], children:
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
?
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
212
|
+
], children: [gooey && indicatorRecipe ? _jsx(Animated.View, { pointerEvents: "none", accessible: false, accessibilityElementsHidden: true, importantForAccessibility: "no-hide-descendants", style: {
|
|
213
|
+
position: "absolute", bottom: 0, height: 6, borderRadius: radius.full,
|
|
214
|
+
backgroundColor: resolveColorReference(tabsRecipe.colors.indicator, theme.palette),
|
|
215
|
+
left: progress.interpolate({ inputRange: [...indicatorRecipe.input], outputRange: [...indicatorRecipe.x] }),
|
|
216
|
+
width: progress.interpolate({ inputRange: [...indicatorRecipe.input], outputRange: [...indicatorRecipe.width] }),
|
|
217
|
+
} }) : null, tabItems.map((item) => {
|
|
218
|
+
const active = selected === item.id;
|
|
219
|
+
return (_jsxs(Pressable, { onLayout: orientation === "horizontal" ? event => {
|
|
220
|
+
const { x, width } = event.nativeEvent.layout;
|
|
221
|
+
if (width <= 0)
|
|
222
|
+
return;
|
|
223
|
+
setIndicatorRects(current => current[item.id]?.x === x && current[item.id]?.width === width ? current : { ...current, [item.id]: { x, width } });
|
|
224
|
+
} : undefined, nativeID: id ? getTabId(id, item.id) : undefined, role: Platform.OS === "ios" ? "button" : "tab", ref: (node) => {
|
|
225
|
+
if (node)
|
|
226
|
+
tabRefs.current.set(item.id, node);
|
|
227
|
+
else
|
|
228
|
+
tabRefs.current.delete(item.id);
|
|
229
|
+
}, accessibilityActions: [
|
|
230
|
+
{ name: "activate" },
|
|
231
|
+
{ name: "increment" },
|
|
232
|
+
{ name: "decrement" },
|
|
233
|
+
], accessibilityLabel: item.badge
|
|
234
|
+
? `${item.label}, ${item.badgeAccessibilityLabel ?? item.badge}`
|
|
235
|
+
: item.label, accessibilityRole: "tab", accessibilityState: { disabled: item.disabled === true, selected: active }, ...webOnly(webTabProps({
|
|
236
|
+
selected: active,
|
|
237
|
+
disabled: item.disabled === true,
|
|
238
|
+
// aria-controls must name a rendered panel; a Tabs without
|
|
239
|
+
// panels has nothing for it to point at.
|
|
240
|
+
controls: id && hasPanels ? getTabPanelId(id, item.id, panelMode) : undefined,
|
|
241
|
+
focused: focusValue === item.id,
|
|
242
|
+
orientation,
|
|
243
|
+
direction,
|
|
244
|
+
onActivate: () => {
|
|
245
|
+
setFocusValue(item.id);
|
|
246
|
+
setSelected(item.id);
|
|
247
|
+
},
|
|
248
|
+
onMoveFocus: (intent) => moveFocus(item.id, intent),
|
|
249
|
+
})), disabled: item.disabled, onAccessibilityAction: (event) => {
|
|
250
|
+
const action = event.nativeEvent.actionName;
|
|
251
|
+
if (action === "activate")
|
|
252
|
+
setSelected(item.id);
|
|
253
|
+
else if (action === "increment")
|
|
254
|
+
moveFocus(item.id, "next");
|
|
255
|
+
else if (action === "decrement")
|
|
256
|
+
moveFocus(item.id, "previous");
|
|
257
|
+
}, onFocus: () => {
|
|
258
|
+
setFocusValue(item.id);
|
|
259
|
+
if (activationMode === "automatic")
|
|
260
|
+
setSelected(item.id);
|
|
261
|
+
}, onPress: () => {
|
|
198
262
|
setFocusValue(item.id);
|
|
199
263
|
setSelected(item.id);
|
|
200
|
-
},
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}, children: _jsx(Text, { align: "center", tone: "brand", variant: "caption", children: item.badge }) })) : null, active ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", style: {
|
|
250
|
-
backgroundColor: resolveColorReference(tabsRecipe.colors.indicator, theme.palette),
|
|
251
|
-
...(orientation === "horizontal"
|
|
252
|
-
? {
|
|
253
|
-
bottom: -tabsRecipe.indicatorHeight / 2,
|
|
254
|
-
height: tabsRecipe.indicatorHeight,
|
|
255
|
-
left: 0,
|
|
256
|
-
right: 0,
|
|
257
|
-
}
|
|
258
|
-
: {
|
|
259
|
-
bottom: 0,
|
|
260
|
-
end: -tabsRecipe.indicatorHeight / 2,
|
|
261
|
-
top: 0,
|
|
262
|
-
width: tabsRecipe.indicatorHeight,
|
|
263
|
-
}),
|
|
264
|
-
position: "absolute",
|
|
265
|
-
} })) : null] }, item.id));
|
|
266
|
-
}) }), hasPanels ? panelMode === "dynamic" ? (_jsx(View, { accessibilityLabel: tabItems.find((item) => item.id === selected)?.panelAccessibilityLabel, accessibilityLabelledBy: id ? getTabId(id, selected) : undefined, nativeID: id ? getDynamicTabPanelId(id) : undefined, importantForAccessibility: "yes", role: "tabpanel", style: { flex: 1 }, children: tabItems.find((item) => item.id === selected)?.panel ?? children?.(selected) })) : tabItems.map((item) => {
|
|
264
|
+
}, style: ({ pressed }) => [
|
|
265
|
+
minimumTargetStyle,
|
|
266
|
+
{
|
|
267
|
+
alignItems: "center",
|
|
268
|
+
backgroundColor: pressed
|
|
269
|
+
? resolveColorReference(tabsRecipe.states.pressedBackground, theme.palette)
|
|
270
|
+
: "transparent",
|
|
271
|
+
direction,
|
|
272
|
+
flex: fitted ? 1 : undefined,
|
|
273
|
+
flexDirection: "row",
|
|
274
|
+
gap: tabsRecipe.gap,
|
|
275
|
+
justifyContent: "center",
|
|
276
|
+
minHeight: sizeContract.minHeight,
|
|
277
|
+
opacity: item.disabled ? tabsRecipe.states.disabledOpacity : 1,
|
|
278
|
+
paddingHorizontal: sizeContract.paddingHorizontal,
|
|
279
|
+
position: "relative",
|
|
280
|
+
},
|
|
281
|
+
], children: [item.renderLeading ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", children: item.renderLeading({
|
|
282
|
+
selected: active,
|
|
283
|
+
disabled: item.disabled === true,
|
|
284
|
+
color: resolveColorReference(active ? tabsRecipe.colors.selected : tabsRecipe.colors.idle, theme.palette),
|
|
285
|
+
size: glyph[tabsRecipe.icon.glyph],
|
|
286
|
+
}) })) : null, _jsx(Text, { align: "center", style: {
|
|
287
|
+
color: resolveColorReference(active ? tabsRecipe.colors.selected : tabsRecipe.colors.idle, theme.palette),
|
|
288
|
+
fontWeight: active
|
|
289
|
+
? tabsRecipe.label.selectedFontWeight
|
|
290
|
+
: tabsRecipe.label.fontWeight,
|
|
291
|
+
}, variant: sizeContract.textVariant, children: item.label }), item.badge ? (_jsx(View, { accessible: false, style: {
|
|
292
|
+
backgroundColor: colors.bg,
|
|
293
|
+
borderRadius: radius.full,
|
|
294
|
+
paddingHorizontal: spacing.xs,
|
|
295
|
+
}, children: _jsx(Text, { align: "center", tone: "brand", variant: "caption", children: item.badge }) })) : null, active && !gooey ? (_jsx(View, { accessibilityElementsHidden: true, accessible: false, importantForAccessibility: "no-hide-descendants", style: {
|
|
296
|
+
backgroundColor: resolveColorReference(tabsRecipe.colors.indicator, theme.palette),
|
|
297
|
+
...(orientation === "horizontal"
|
|
298
|
+
? {
|
|
299
|
+
bottom: -tabsRecipe.indicatorHeight / 2,
|
|
300
|
+
height: tabsRecipe.indicatorHeight,
|
|
301
|
+
left: 0,
|
|
302
|
+
right: 0,
|
|
303
|
+
}
|
|
304
|
+
: {
|
|
305
|
+
bottom: 0,
|
|
306
|
+
end: -tabsRecipe.indicatorHeight / 2,
|
|
307
|
+
top: 0,
|
|
308
|
+
width: tabsRecipe.indicatorHeight,
|
|
309
|
+
}),
|
|
310
|
+
position: "absolute",
|
|
311
|
+
} })) : null] }, item.id));
|
|
312
|
+
})] }), hasPanels ? panelMode === "dynamic" ? (_jsx(View, { accessibilityLabel: tabItems.find((item) => item.id === selected)?.panelAccessibilityLabel, accessibilityLabelledBy: id ? getTabId(id, selected) : undefined, nativeID: id ? getDynamicTabPanelId(id) : undefined, importantForAccessibility: "yes", role: "tabpanel", style: { flexGrow: 1, flexShrink: 1 }, children: tabItems.find((item) => item.id === selected)?.panel ?? children?.(selected) })) : tabItems.map((item) => {
|
|
267
313
|
const active = item.id === selected;
|
|
268
314
|
const mounted = mountPolicy === "always" ||
|
|
269
315
|
(mountPolicy === "visited" && (visited.has(item.id) || active)) ||
|
|
270
316
|
(mountPolicy === "active" && active);
|
|
271
317
|
if (!mounted)
|
|
272
318
|
return null;
|
|
273
|
-
return (_jsx(View, { accessibilityLabel: item.panelAccessibilityLabel, accessibilityLabelledBy: id ? getTabId(id, item.id) : undefined, nativeID: id ? getTabPanelId(id, item.id) : undefined, importantForAccessibility: active ? "yes" : "no-hide-descendants", role: "tabpanel", style: { display: active ? "flex" : "none",
|
|
319
|
+
return (_jsx(View, { accessibilityLabel: item.panelAccessibilityLabel, accessibilityLabelledBy: id ? getTabId(id, item.id) : undefined, nativeID: id ? getTabPanelId(id, item.id) : undefined, importantForAccessibility: active ? "yes" : "no-hide-descendants", role: "tabpanel", style: { display: active ? "flex" : "none", flexGrow: 1, flexShrink: 1 }, children: item.panel ?? children?.(item.id) }, item.id));
|
|
274
320
|
}) : null] }));
|
|
275
321
|
}
|
|
276
322
|
function useBottomNavigationKeyboardVisible() {
|
|
@@ -296,6 +342,8 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
296
342
|
...configuration,
|
|
297
343
|
direction: configuration.direction ?? theme.environment.direction,
|
|
298
344
|
}, resolved.items.length);
|
|
345
|
+
const capsule = presentation.presentation === "capsule";
|
|
346
|
+
const expandedLabels = theme.environment.textScale >= 1.5 || resolved.items.length > 4;
|
|
299
347
|
const keyboardVisible = useBottomNavigationKeyboardVisible();
|
|
300
348
|
const density = bottomNavigationRecipe.density[presentation.density];
|
|
301
349
|
const presentationRecipe = bottomNavigationRecipe.presentations[presentation.presentation];
|
|
@@ -326,7 +374,10 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
326
374
|
], children: _jsxs(View, { style: [
|
|
327
375
|
{
|
|
328
376
|
alignSelf: "center",
|
|
329
|
-
|
|
377
|
+
flexDirection: capsule ? "row" : "column",
|
|
378
|
+
alignItems: capsule ? "center" : undefined,
|
|
379
|
+
gap: capsule ? spacing.xs : 0,
|
|
380
|
+
backgroundColor: capsule ? "transparent" : surfaceBackground,
|
|
330
381
|
borderColor: surfaceBorder,
|
|
331
382
|
borderRadius: presentationRecipe.radius
|
|
332
383
|
? radius[presentationRecipe.radius]
|
|
@@ -347,10 +398,17 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
347
398
|
surfaceStyle,
|
|
348
399
|
], children: [_jsx(View, { accessibilityLabel: resolved.accessibilityLabel, accessibilityRole: "tablist", style: [
|
|
349
400
|
{
|
|
350
|
-
alignItems: "flex-start",
|
|
401
|
+
alignItems: capsule ? "stretch" : "flex-start",
|
|
351
402
|
direction: presentation.direction,
|
|
352
403
|
flexDirection: "row",
|
|
353
|
-
width: "100%",
|
|
404
|
+
width: capsule ? undefined : "100%",
|
|
405
|
+
flex: capsule ? 1 : undefined,
|
|
406
|
+
minWidth: 0,
|
|
407
|
+
padding: capsule ? spacing.xxs : 0,
|
|
408
|
+
backgroundColor: capsule ? surfaceBackground : undefined,
|
|
409
|
+
borderRadius: capsule ? radius.full : undefined,
|
|
410
|
+
borderWidth: capsule ? presentationRecipe.borderWidth : 0,
|
|
411
|
+
borderColor: surfaceBorder,
|
|
354
412
|
},
|
|
355
413
|
listStyle,
|
|
356
414
|
], children: resolved.items.map((item, index) => {
|
|
@@ -378,15 +436,16 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
378
436
|
minimumTargetStyle,
|
|
379
437
|
{
|
|
380
438
|
alignItems: "center",
|
|
381
|
-
backgroundColor: pressed ? pressedBackground : "transparent",
|
|
382
|
-
borderRadius: radius.lg,
|
|
383
|
-
|
|
439
|
+
backgroundColor: pressed ? pressedBackground : capsule && selected ? theme.colors.surfaceAccent : "transparent",
|
|
440
|
+
borderRadius: capsule ? radius.full : radius.lg,
|
|
441
|
+
flexDirection: capsule && !expandedLabels ? "row" : "column",
|
|
442
|
+
flex: capsule && selected && !expandedLabels ? 2 : 1,
|
|
384
443
|
flexShrink: 1,
|
|
385
444
|
gap: density.gap,
|
|
386
|
-
justifyContent: "flex-start",
|
|
445
|
+
justifyContent: capsule ? "center" : "flex-start",
|
|
387
446
|
marginEnd: index === middleIndex ? centerGap : 0,
|
|
388
447
|
minHeight: density.itemMinHeight,
|
|
389
|
-
minWidth: density.itemMinWidth,
|
|
448
|
+
minWidth: capsule ? 44 : density.itemMinWidth,
|
|
390
449
|
opacity: item.disabled
|
|
391
450
|
? bottomNavigationRecipe.states.disabledOpacity
|
|
392
451
|
: 1,
|
|
@@ -449,7 +508,7 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
449
508
|
}, children: _jsx(Text, { accessible: false, align: "center", style: {
|
|
450
509
|
color: resolveColorReference(badgeTone.content, theme.palette),
|
|
451
510
|
fontWeight: counterBadgeRecipe.fontWeight,
|
|
452
|
-
}, variant: badgeMetrics.textVariant, children: item.badge.visibleLabel }) })) })) : null] }), _jsx(Text, { align: "center", allowFontScaling: bottomNavigationRecipe.largeText.allowFontScaling, maxFontSizeMultiplier: bottomNavigationRecipe.largeText.maxFontSizeMultiplier, style: {
|
|
511
|
+
}, variant: badgeMetrics.textVariant, children: item.badge.visibleLabel }) })) })) : null] }), (!capsule || expandedLabels || selected) && _jsx(Text, { align: "center", allowFontScaling: bottomNavigationRecipe.largeText.allowFontScaling, maxFontSizeMultiplier: bottomNavigationRecipe.largeText.maxFontSizeMultiplier, style: {
|
|
453
512
|
color: selected ? selectedLabelColor : idleColor,
|
|
454
513
|
flexShrink: 1,
|
|
455
514
|
fontWeight: selected
|
|
@@ -467,6 +526,8 @@ export function BottomNavigation({ descriptor, onActivate, onLongActivate, rende
|
|
|
467
526
|
right: 0,
|
|
468
527
|
top: 0,
|
|
469
528
|
},
|
|
529
|
+
// Keep creation outside route destinations; absolute center placement overlaps capsule labels.
|
|
530
|
+
capsule ? { position: "relative", left: undefined, right: undefined, top: undefined, bottom: undefined, flexShrink: 0 } : undefined,
|
|
470
531
|
primaryActionStyle,
|
|
471
532
|
], children: primaryAction })) : null] }) }));
|
|
472
533
|
}
|
|
@@ -712,7 +773,7 @@ function useMenuAfterDismiss(visible) {
|
|
|
712
773
|
};
|
|
713
774
|
}
|
|
714
775
|
/** Sectioned Native action/selection menu with teardown-safe action callbacks. */
|
|
715
|
-
export function Menu({ triggerLabel, title = triggerLabel, items, sections, source: sourceProp, selection = { mode: "none" },
|
|
776
|
+
export function Menu({ triggerLabel, title = triggerLabel, items, sections, source: sourceProp, selection = { mode: "none" }, onAction, onActionAfterDismiss, onSelectionAfterDismiss, open, defaultOpen = false, onOpenChange, onDismiss, disabled = false, readOnly = false, busy = false, readOnlyLabel, asyncState = { status: "idle" }, onRetry, retryLabel, density = menuRecipe.defaults.density, renderLeading, renderTrailing, dismissLabel, trigger, renderTrigger, style, ...modalProps }) {
|
|
716
777
|
const providedSources = [sourceProp, items, sections].filter((candidate) => candidate !== undefined).length;
|
|
717
778
|
if (providedSources !== 1) {
|
|
718
779
|
throw new TypeError("Menu requires exactly one of source, items, or sections");
|
|
@@ -721,7 +782,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
721
782
|
? { sections }
|
|
722
783
|
: {
|
|
723
784
|
items: (items ?? []).map((item) => ({
|
|
724
|
-
id: item.
|
|
785
|
+
id: item.id,
|
|
725
786
|
label: item.label,
|
|
726
787
|
textValue: item.textValue ?? item.label,
|
|
727
788
|
...(item.description === undefined ? {} : { description: item.description }),
|
|
@@ -734,7 +795,6 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
734
795
|
});
|
|
735
796
|
validateCollection(source);
|
|
736
797
|
const collectionItems = flattenCollectionItems(source);
|
|
737
|
-
const legacyItems = new Map((items ?? []).map((item) => [item.value, item]));
|
|
738
798
|
if (collectionItems.length === 0 && asyncState.status === "idle") {
|
|
739
799
|
throw new Error("Menu requires an item or a non-idle asyncState");
|
|
740
800
|
}
|
|
@@ -818,12 +878,10 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
818
878
|
selection.onSelectionChange?.(next);
|
|
819
879
|
return;
|
|
820
880
|
}
|
|
821
|
-
void onSelect?.(item.id);
|
|
822
881
|
void onAction?.(item.id);
|
|
823
882
|
close("selection", onActionAfterDismiss ? () => onActionAfterDismiss(item.id) : null);
|
|
824
883
|
};
|
|
825
884
|
const renderItem = (item) => {
|
|
826
|
-
const legacyItem = legacyItems.get(item.id);
|
|
827
885
|
const selected = selection.mode === "single"
|
|
828
886
|
? selectedSingle === item.id
|
|
829
887
|
: selection.mode === "multiple"
|
|
@@ -836,14 +894,14 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
836
894
|
disabled: itemDisabled,
|
|
837
895
|
color: resolveColorReference(menuRecipe.leading.color, theme.palette),
|
|
838
896
|
size: glyph[menuRecipe.leading.glyph],
|
|
839
|
-
})
|
|
897
|
+
});
|
|
840
898
|
const trailing = renderTrailing?.(item);
|
|
841
899
|
return (_jsxs(Pressable, { ref: (node) => {
|
|
842
900
|
if (node)
|
|
843
901
|
itemRefs.current.set(item.id, node);
|
|
844
902
|
else
|
|
845
903
|
itemRefs.current.delete(item.id);
|
|
846
|
-
}, accessibilityHint:
|
|
904
|
+
}, accessibilityHint: item.description, accessibilityLabel: item.label, accessibilityRole: "menuitem", accessibilityState: {
|
|
847
905
|
disabled: itemDisabled,
|
|
848
906
|
...(selection.mode === "single" ? { selected } : {}),
|
|
849
907
|
...(selection.mode === "multiple" ? { checked: selected } : {}),
|
|
@@ -895,7 +953,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
895
953
|
position: "absolute",
|
|
896
954
|
right: 0,
|
|
897
955
|
top: 0,
|
|
898
|
-
} }), _jsxs(View, { accessibilityLabel: title, accessibilityRole: "menu", accessibilityViewIsModal: true, style: {
|
|
956
|
+
} }), _jsxs(View, { accessibilityLabel: title.trim() || triggerLabel, accessibilityRole: "menu", accessibilityViewIsModal: true, style: {
|
|
899
957
|
alignSelf: "center",
|
|
900
958
|
backgroundColor: colors.bg,
|
|
901
959
|
borderRadius: radius.lg,
|
|
@@ -904,7 +962,7 @@ export function Menu({ triggerLabel, title = triggerLabel, items, sections, sour
|
|
|
904
962
|
maxWidth: 520,
|
|
905
963
|
padding: spacing.md,
|
|
906
964
|
width: "100%",
|
|
907
|
-
}, children: [_jsx(Text, { tone: "primary", variant: "title", children: title }), _jsxs(ScrollView, { children: [asyncState.status === "loading" || asyncState.status === "error" || asyncState.status === "empty" ? (_jsxs(View, { style: { gap: spacing.sm, minHeight: densityContract.minHeight }, children: [asyncState.status === "loading" ? _jsx(Spinner, { label: asyncState.message }) : (_jsx(Text, { accessibilityLiveRegion: asyncState.status === "error" ? "assertive" : "polite", accessibilityRole: asyncState.status === "error" ? "alert" : undefined, tone: asyncState.status === "error" ? "danger" : "muted", children: asyncState.message })), asyncState.status === "error" && onRetry ? (_jsx(Button, { onPress: onRetry, tone: "secondary", children: retryLabel ?? dismissLabel })) : null] })) : collection, asyncState.status === "loadingMore" ? _jsx(Spinner, { label: asyncState.message }) : null] }), _jsx(Button, { onPress: () => close("programmatic"), tone: "secondary", children: dismissLabel })] })] }) })] }));
|
|
965
|
+
}, children: [title.trim() ? _jsx(Text, { tone: "primary", variant: "title", children: title }) : null, _jsxs(ScrollView, { children: [asyncState.status === "loading" || asyncState.status === "error" || asyncState.status === "empty" ? (_jsxs(View, { style: { gap: spacing.sm, minHeight: densityContract.minHeight }, children: [asyncState.status === "loading" ? _jsx(Spinner, { label: asyncState.message }) : (_jsx(Text, { accessibilityLiveRegion: asyncState.status === "error" ? "assertive" : "polite", accessibilityRole: asyncState.status === "error" ? "alert" : undefined, tone: asyncState.status === "error" ? "danger" : "muted", children: asyncState.message })), asyncState.status === "error" && onRetry ? (_jsx(Button, { onPress: onRetry, tone: "secondary", children: retryLabel ?? dismissLabel })) : null] })) : collection, asyncState.status === "loadingMore" ? _jsx(Spinner, { label: asyncState.message }) : null] }), _jsx(Button, { onPress: () => close("programmatic"), tone: "secondary", children: dismissLabel })] })] }) })] }));
|
|
908
966
|
}
|
|
909
967
|
function createNativeLoadMoreControllerFacade() {
|
|
910
968
|
let active = null;
|
|
@@ -924,7 +982,7 @@ function createNativeLoadMoreControllerFacade() {
|
|
|
924
982
|
};
|
|
925
983
|
}
|
|
926
984
|
/** Collection footer that de-duplicates automatic and manual page requests. */
|
|
927
|
-
export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, mode = loadMoreRecipe.defaults.mode, density = loadMoreRecipe.defaults.density, onRequestOutcome, onRequestError, layoutStyle,
|
|
985
|
+
export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, mode = loadMoreRecipe.defaults.mode, density = loadMoreRecipe.defaults.density, onRequestOutcome, onRequestError, layoutStyle, }, ref) {
|
|
928
986
|
validateLoadMoreDescriptor(descriptor);
|
|
929
987
|
const stateRef = useRef(descriptor.state);
|
|
930
988
|
const handlerRef = useRef(onLoadMore);
|
|
@@ -984,9 +1042,8 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
|
|
|
984
1042
|
gap: densityContract.gap,
|
|
985
1043
|
paddingVertical: densityContract.paddingVertical,
|
|
986
1044
|
},
|
|
987
|
-
style,
|
|
988
1045
|
layoutStyle,
|
|
989
|
-
], children: state.status === "ready" ? (_jsx(
|
|
1046
|
+
], children: state.status === "ready" ? (_jsx(RecipeButton, { onPress: () => {
|
|
990
1047
|
void request("manual").catch(() => undefined);
|
|
991
1048
|
}, labelStyle: {
|
|
992
1049
|
color: resolveColorReference(loadMoreRecipe.trigger.color, theme.palette),
|
|
@@ -1002,7 +1059,7 @@ export const LoadMore = forwardRef(function LoadMore({ descriptor, onLoadMore, m
|
|
|
1002
1059
|
justifyContent: "center",
|
|
1003
1060
|
}, children: [_jsx(ActivityIndicator, { color: resolveColorReference(spinnerRecipe.tones[loadMoreRecipe.spinner.tone], theme.palette), size: loadMoreRecipe.spinner.size }), _jsx(Text, { accessible: false, style: {
|
|
1004
1061
|
color: resolveColorReference(loadMoreRecipe.status.color, theme.palette),
|
|
1005
|
-
}, variant: loadMoreRecipe.status.textVariant, children: labels.loading })] })) : state.status === "error" ? (_jsxs(_Fragment, { children: [_jsx(Text, { accessibilityLiveRegion: "assertive", style: { color: resolveColorReference(loadMoreRecipe.error.color, theme.palette) }, variant: loadMoreRecipe.error.textVariant, children: state.message }), _jsx(
|
|
1062
|
+
}, variant: loadMoreRecipe.status.textVariant, children: labels.loading })] })) : state.status === "error" ? (_jsxs(_Fragment, { children: [_jsx(Text, { accessibilityLiveRegion: "assertive", style: { color: resolveColorReference(loadMoreRecipe.error.color, theme.palette) }, variant: loadMoreRecipe.error.textVariant, children: state.message }), _jsx(RecipeButton, { onPress: () => {
|
|
1006
1063
|
void request("retry").catch(() => undefined);
|
|
1007
1064
|
}, labelStyle: {
|
|
1008
1065
|
color: resolveColorReference(loadMoreRecipe.trigger.color, theme.palette),
|