@dloizides/ui-nav 1.26.1 → 1.27.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 +8 -0
- package/dist/index.d.mts +32 -2
- package/dist/index.d.ts +32 -2
- package/dist/index.js +337 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +337 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.27.0
|
|
4
|
+
|
|
5
|
+
- INVOICE-5 "Edit invoices before sending, shared background and menu hover" (AC-UI-2, AC-UI-6): `NavBar` takes `hoverFeedback` (`NavHoverFeedback`: `default`, `tint-pill`, `underline`, `lift`, `sliding-highlight`); the new phone `BottomTabBar` takes `tabFeedback` (`TabFeedback`: `default`, `indicator-pill`, `dim-spring`, `sliding-bar`, `icon-lift`). Timings come from the `@dloizides/ui-motion` tokens (<= 300 ms); reduced motion keeps every option still. `default` keeps today's look, so existing apps do not change.
|
|
6
|
+
|
|
7
|
+
## 1.26.2
|
|
8
|
+
|
|
9
|
+
- KEFI-LAUNCH-1 "Do every open Kefi task before the public launch": a long account name no longer overlaps the `Topbar` center slot at 360 px. The center slot keeps its content width (`flexShrink: 0`); only the account name gives way. The overlap dates from 1.24.0, when the slot was added.
|
|
10
|
+
|
|
3
11
|
## 1.26.1
|
|
4
12
|
|
|
5
13
|
- KEFI-LAUNCH-1 "Do every open Kefi task before the public launch": the `Topbar` left slot no longer wraps beside a long account name at 360 px. The left slot keeps its width (`flexShrink: 0`) and the account name truncates to one line.
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ViewStyle } from 'react-native';
|
|
2
|
+
import { ViewStyle, StyleProp } from 'react-native';
|
|
3
3
|
import { UiTheme } from '@dloizides/ui-feedback';
|
|
4
4
|
import { DropdownVariant } from '@dloizides/ui-layout';
|
|
5
5
|
import { resolveAccessibleRoutes, RoleRouteTable, RoleRoute } from '@dloizides/auth-web';
|
|
@@ -363,6 +363,14 @@ interface TopbarProps {
|
|
|
363
363
|
}
|
|
364
364
|
declare const Topbar: ({ left, center, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
|
|
365
365
|
|
|
366
|
+
declare enum NavHoverFeedback {
|
|
367
|
+
Default = "default",
|
|
368
|
+
TintPill = "tint-pill",
|
|
369
|
+
Underline = "underline",
|
|
370
|
+
Lift = "lift",
|
|
371
|
+
SlidingHighlight = "sliding-highlight"
|
|
372
|
+
}
|
|
373
|
+
|
|
366
374
|
/**
|
|
367
375
|
* NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar
|
|
368
376
|
* counterpart to `Sidebar`: it renders the SAME caller-supplied `NavItem[]`
|
|
@@ -445,6 +453,7 @@ interface NavBarProps {
|
|
|
445
453
|
* theme. Style-only: every colour still flows through the theme — no per-app fork.
|
|
446
454
|
*/
|
|
447
455
|
barTheme?: UiTheme;
|
|
456
|
+
hoverFeedback?: NavHoverFeedback;
|
|
448
457
|
}
|
|
449
458
|
/**
|
|
450
459
|
* `NavBar` — the horizontal top bar. When `barTheme` is supplied it re-provisions
|
|
@@ -1598,4 +1607,25 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
1598
1607
|
/** Chevron icon size for expandable sections. */
|
|
1599
1608
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
1600
1609
|
|
|
1601
|
-
|
|
1610
|
+
declare enum TabFeedback {
|
|
1611
|
+
Default = "default",
|
|
1612
|
+
IndicatorPill = "indicator-pill",
|
|
1613
|
+
DimSpring = "dim-spring",
|
|
1614
|
+
SlidingBar = "sliding-bar",
|
|
1615
|
+
IconLift = "icon-lift"
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
interface BottomTabBarProps {
|
|
1619
|
+
items: NavItem[];
|
|
1620
|
+
pathname: string;
|
|
1621
|
+
onNavigate: (route: string) => void;
|
|
1622
|
+
tabFeedback?: TabFeedback;
|
|
1623
|
+
navigateHint?: (label: string) => string;
|
|
1624
|
+
regionLabel?: string;
|
|
1625
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
1626
|
+
testID?: string;
|
|
1627
|
+
}
|
|
1628
|
+
/** Phone tab bar; `tabFeedback` picks the press feedback, `Default` adds no motion. */
|
|
1629
|
+
declare const BottomTabBar: ({ items, pathname, onNavigate, tabFeedback, navigateHint, regionLabel, containerStyle, testID, }: BottomTabBarProps) => React.ReactElement;
|
|
1630
|
+
|
|
1631
|
+
export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, BottomTabBar, type BottomTabBarProps, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, NavHoverFeedback, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, PillNavVariant, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, TabFeedback, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolvePillMenuLabel, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { ViewStyle } from 'react-native';
|
|
2
|
+
import { ViewStyle, StyleProp } from 'react-native';
|
|
3
3
|
import { UiTheme } from '@dloizides/ui-feedback';
|
|
4
4
|
import { DropdownVariant } from '@dloizides/ui-layout';
|
|
5
5
|
import { resolveAccessibleRoutes, RoleRouteTable, RoleRoute } from '@dloizides/auth-web';
|
|
@@ -363,6 +363,14 @@ interface TopbarProps {
|
|
|
363
363
|
}
|
|
364
364
|
declare const Topbar: ({ left, center, language, notificationSlot, user, account, logout, containerStyle, }: TopbarProps) => React.ReactElement;
|
|
365
365
|
|
|
366
|
+
declare enum NavHoverFeedback {
|
|
367
|
+
Default = "default",
|
|
368
|
+
TintPill = "tint-pill",
|
|
369
|
+
Underline = "underline",
|
|
370
|
+
Lift = "lift",
|
|
371
|
+
SlidingHighlight = "sliding-highlight"
|
|
372
|
+
}
|
|
373
|
+
|
|
366
374
|
/**
|
|
367
375
|
* NavBar — the config-driven HORIZONTAL top navigation bar. It is the top-bar
|
|
368
376
|
* counterpart to `Sidebar`: it renders the SAME caller-supplied `NavItem[]`
|
|
@@ -445,6 +453,7 @@ interface NavBarProps {
|
|
|
445
453
|
* theme. Style-only: every colour still flows through the theme — no per-app fork.
|
|
446
454
|
*/
|
|
447
455
|
barTheme?: UiTheme;
|
|
456
|
+
hoverFeedback?: NavHoverFeedback;
|
|
448
457
|
}
|
|
449
458
|
/**
|
|
450
459
|
* `NavBar` — the horizontal top bar. When `barTheme` is supplied it re-provisions
|
|
@@ -1598,4 +1607,25 @@ declare const NAV_ICON_SIZE = 14;
|
|
|
1598
1607
|
/** Chevron icon size for expandable sections. */
|
|
1599
1608
|
declare const CHEVRON_ICON_SIZE = 12;
|
|
1600
1609
|
|
|
1601
|
-
|
|
1610
|
+
declare enum TabFeedback {
|
|
1611
|
+
Default = "default",
|
|
1612
|
+
IndicatorPill = "indicator-pill",
|
|
1613
|
+
DimSpring = "dim-spring",
|
|
1614
|
+
SlidingBar = "sliding-bar",
|
|
1615
|
+
IconLift = "icon-lift"
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
interface BottomTabBarProps {
|
|
1619
|
+
items: NavItem[];
|
|
1620
|
+
pathname: string;
|
|
1621
|
+
onNavigate: (route: string) => void;
|
|
1622
|
+
tabFeedback?: TabFeedback;
|
|
1623
|
+
navigateHint?: (label: string) => string;
|
|
1624
|
+
regionLabel?: string;
|
|
1625
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
1626
|
+
testID?: string;
|
|
1627
|
+
}
|
|
1628
|
+
/** Phone tab bar; `tabFeedback` picks the press feedback, `Default` adds no motion. */
|
|
1629
|
+
declare const BottomTabBar: ({ items, pathname, onNavigate, tabFeedback, navigateHint, regionLabel, containerStyle, testID, }: BottomTabBarProps) => React.ReactElement;
|
|
1630
|
+
|
|
1631
|
+
export { ACTIVE_BORDER_RADIUS, ACTIVE_TINT_OPACITY, APP_SHELL_SUFFIX, type AccountPlan, type AccountState, AppShell, type AppShellProps, type AppShellWidth, BASE_INDENT, BottomTabBar, type BottomTabBarProps, CHEVRON_ICON_SIZE, CollapsedRail, type CollapsedRailProps, type CollapsedRailRange, type CommandItem, CommandPalette, type CommandPaletteLabels, type CommandPaletteProps, CommandPaletteTrigger, type CommandPaletteTriggerProps, DEFAULT_COLLAPSED_RAIL_MAX, DEFAULT_SEARCH_BREAKPOINT, DarkModeControl, type DarkModeControlProps, type DarkModeOption, type DarkModeVariant, HOVER_TINT_OPACITY, NAV_ICON_SIZE, NAV_LINK_GAP, NAV_ROW_RADIUS, NAV_TEST_IDS, Nav, NavBar, type NavBarProps, NavExpandableItem, type NavExpandableItemProps, NavHoverFeedback, type NavItem, type NavOrientation, NavOverflowMenu, type NavOverflowMenuProps, type NavProps, NavShell, type NavShellCollapsedRail, type NavShellLayout, type NavShellProps, type NavShellSideRail, type NavShellTopBar, type NavUser, PillNav, type PillNavProps, PillNavVariant, RAIL_FULL_BREAKPOINT, type RailMode, type ResolveRailModeArgs, type SearchAffordance, type ShellMessage, Sidebar, SidebarChevron, type SidebarChevronProps, type SidebarPaletteTriggerConfig, type SidebarProps, SidebarSearch, type SidebarSearchConfig, type SidebarSearchLabels, type SidebarSearchProps, TabFeedback, Topbar, type TopbarAction, type TopbarProps, type TopbarUser, accessibleNavItems, collapsedRailStyles, darkModeStyles, expandableStyles, filterCommands, filterNavItems, isFilterActive, isRouteActive, navStyles, pillNavStyles, resolveActiveRoute, resolveContentMaxWidth, resolvePillMenuLabel, resolveRailMode, resolveSearchAffordance, roleRoutesToNavItems, searchTokens, useCommandPaletteHotkey, useContentMaxWidth };
|
package/dist/index.js
CHANGED
|
@@ -71,7 +71,7 @@ var navStyles = reactNative.StyleSheet.create({
|
|
|
71
71
|
justifyContent: "space-between"
|
|
72
72
|
},
|
|
73
73
|
topbarLeft: { flexGrow: 1, flexShrink: 0 },
|
|
74
|
-
topbarCenter: { flexShrink:
|
|
74
|
+
topbarCenter: { flexShrink: 0, minWidth: 0, flexDirection: "row", alignItems: "center", marginHorizontal: 8 },
|
|
75
75
|
topbarRight: { flexDirection: "row", alignItems: "center", flexShrink: 1, minWidth: 0 },
|
|
76
76
|
topbarRowItem: {
|
|
77
77
|
marginHorizontal: 8,
|
|
@@ -1057,10 +1057,88 @@ var Topbar = ({
|
|
|
1057
1057
|
}
|
|
1058
1058
|
);
|
|
1059
1059
|
};
|
|
1060
|
+
|
|
1061
|
+
// src/NavHoverFeedback.ts
|
|
1062
|
+
var NavHoverFeedback = /* @__PURE__ */ ((NavHoverFeedback2) => {
|
|
1063
|
+
NavHoverFeedback2["Default"] = "default";
|
|
1064
|
+
NavHoverFeedback2["TintPill"] = "tint-pill";
|
|
1065
|
+
NavHoverFeedback2["Underline"] = "underline";
|
|
1066
|
+
NavHoverFeedback2["Lift"] = "lift";
|
|
1067
|
+
NavHoverFeedback2["SlidingHighlight"] = "sliding-highlight";
|
|
1068
|
+
return NavHoverFeedback2;
|
|
1069
|
+
})(NavHoverFeedback || {});
|
|
1070
|
+
|
|
1071
|
+
// src/feedback/navLinkFeedback.ts
|
|
1072
|
+
var DEFAULT_FADE_MS = 150;
|
|
1073
|
+
var PRESS_SCALE = 0.97;
|
|
1074
|
+
var PRESS_NUDGE_PX = 1;
|
|
1075
|
+
var LIFT_PX = -2;
|
|
1076
|
+
var PILL_KINDS = /* @__PURE__ */ new Set([
|
|
1077
|
+
"default" /* Default */,
|
|
1078
|
+
"tint-pill" /* TintPill */,
|
|
1079
|
+
"lift" /* Lift */
|
|
1080
|
+
]);
|
|
1081
|
+
function pillBackground(kind, isHovered, active, colors) {
|
|
1082
|
+
if (!PILL_KINDS.has(kind)) return void 0;
|
|
1083
|
+
if (active) return colors.activeBg;
|
|
1084
|
+
return isHovered ? colors.hoverBg : void 0;
|
|
1085
|
+
}
|
|
1086
|
+
function linkTextColor(kind, isHovered, active, colors) {
|
|
1087
|
+
if (active) return PILL_KINDS.has(kind) ? colors.onPrimary : colors.activeBg;
|
|
1088
|
+
return isHovered ? colors.hoverText : colors.rest;
|
|
1089
|
+
}
|
|
1090
|
+
function pressTransform(kind, pressed, isHovered) {
|
|
1091
|
+
if (kind === "tint-pill" /* TintPill */) return pressed ? [{ scale: PRESS_SCALE }] : void 0;
|
|
1092
|
+
if (kind === "underline" /* Underline */) return pressed ? [{ translateY: PRESS_NUDGE_PX }] : void 0;
|
|
1093
|
+
if (kind !== "lift" /* Lift */) return void 0;
|
|
1094
|
+
if (pressed) return [{ translateY: 0 }];
|
|
1095
|
+
return isHovered ? [{ translateY: LIFT_PX }] : void 0;
|
|
1096
|
+
}
|
|
1097
|
+
function feedbackDuration(kind) {
|
|
1098
|
+
if (kind === "default" /* Default */) return DEFAULT_FADE_MS;
|
|
1099
|
+
return kind === "tint-pill" /* TintPill */ ? uiMotion.motionDurations.fast : uiMotion.motionDurations.base;
|
|
1100
|
+
}
|
|
1101
|
+
function resolveLinkFeedback(kind, state, colors, reducedMotion) {
|
|
1102
|
+
const isHovered = state.hovered && !state.active;
|
|
1103
|
+
const underlined = state.active || isHovered;
|
|
1104
|
+
return {
|
|
1105
|
+
background: pillBackground(kind, isHovered, state.active, colors),
|
|
1106
|
+
textColor: linkTextColor(kind, isHovered, state.active, colors),
|
|
1107
|
+
transform: reducedMotion ? void 0 : pressTransform(kind, state.pressed, isHovered),
|
|
1108
|
+
shadow: kind === "lift" /* Lift */ && isHovered,
|
|
1109
|
+
underline: kind === "underline" /* Underline */ ? { color: colors.activeBg, scale: underlined ? 1 : 0 } : null,
|
|
1110
|
+
durationMs: reducedMotion ? uiMotion.motionDurations.instant : feedbackDuration(kind)
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1060
1113
|
var TEXT_ON_PRIMARY2 = "#ffffff";
|
|
1061
1114
|
function ariaCurrentProps2(isActive) {
|
|
1062
1115
|
return isActive ? { "aria-current": "page" } : {};
|
|
1063
1116
|
}
|
|
1117
|
+
var FEEDBACK_PROPERTIES = "color, background-color, transform, box-shadow";
|
|
1118
|
+
var UNDERLINE_INSET = 14;
|
|
1119
|
+
var UNDERLINE_HEIGHT = 2;
|
|
1120
|
+
var SHADOW_OPACITY = 0.18;
|
|
1121
|
+
var SHADOW_RADIUS = 6;
|
|
1122
|
+
var SHADOW_OFFSET = { width: 0, height: 2 };
|
|
1123
|
+
function feedbackStyle(kind, feedback, shadowColor, reducedMotion) {
|
|
1124
|
+
const background = feedback.background === void 0 ? {} : { backgroundColor: feedback.background };
|
|
1125
|
+
const shadow = feedback.shadow ? { shadowColor, shadowOpacity: SHADOW_OPACITY, shadowRadius: SHADOW_RADIUS, shadowOffset: SHADOW_OFFSET } : {};
|
|
1126
|
+
const transition = kind === "default" /* Default */ ? hoverTransitionStyle(reducedMotion) : webTransition(FEEDBACK_PROPERTIES, feedback.durationMs, reducedMotion);
|
|
1127
|
+
return [background, shadow, { transform: feedback.transform }, transition ?? {}];
|
|
1128
|
+
}
|
|
1129
|
+
function underlineStyle(feedback, reducedMotion) {
|
|
1130
|
+
if (feedback.underline === null) return null;
|
|
1131
|
+
const line = {
|
|
1132
|
+
position: "absolute",
|
|
1133
|
+
left: UNDERLINE_INSET,
|
|
1134
|
+
right: UNDERLINE_INSET,
|
|
1135
|
+
bottom: 0,
|
|
1136
|
+
height: UNDERLINE_HEIGHT,
|
|
1137
|
+
backgroundColor: feedback.underline.color,
|
|
1138
|
+
transform: [{ scaleX: feedback.underline.scale }]
|
|
1139
|
+
};
|
|
1140
|
+
return [line, webTransition("transform", feedback.durationMs, reducedMotion) ?? {}];
|
|
1141
|
+
}
|
|
1064
1142
|
var NavBarLink = ({
|
|
1065
1143
|
item,
|
|
1066
1144
|
isActive,
|
|
@@ -1069,13 +1147,20 @@ var NavBarLink = ({
|
|
|
1069
1147
|
reducedMotion,
|
|
1070
1148
|
colors,
|
|
1071
1149
|
navigateHint,
|
|
1072
|
-
onPress
|
|
1150
|
+
onPress,
|
|
1151
|
+
hoverFeedback = "default" /* Default */,
|
|
1152
|
+
onHoverChange
|
|
1073
1153
|
}) => {
|
|
1074
1154
|
const [hovered, setHovered] = React.useState(false);
|
|
1075
1155
|
const [focused, setFocused] = React.useState(false);
|
|
1076
|
-
const
|
|
1077
|
-
const
|
|
1078
|
-
const
|
|
1156
|
+
const linkColors = { ...colors, onPrimary: TEXT_ON_PRIMARY2 };
|
|
1157
|
+
const resolve = (pressed) => resolveLinkFeedback(hoverFeedback, { hovered, pressed, active: isActive }, linkColors, reducedMotion);
|
|
1158
|
+
const textColor = resolve(false).textColor;
|
|
1159
|
+
const underline = underlineStyle(resolve(false), reducedMotion);
|
|
1160
|
+
const setHover = (next) => {
|
|
1161
|
+
setHovered(next);
|
|
1162
|
+
onHoverChange?.(item.key, next);
|
|
1163
|
+
};
|
|
1079
1164
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1080
1165
|
reactNative.Pressable,
|
|
1081
1166
|
{
|
|
@@ -1083,27 +1168,61 @@ var NavBarLink = ({
|
|
|
1083
1168
|
accessibilityLabel: item.label,
|
|
1084
1169
|
accessibilityRole: "link",
|
|
1085
1170
|
accessibilityState: { selected: isActive },
|
|
1086
|
-
style: [
|
|
1171
|
+
style: ({ pressed }) => [
|
|
1087
1172
|
navBarStyles.link,
|
|
1088
1173
|
collapsed ? navBarStyles.linkStacked : void 0,
|
|
1089
|
-
|
|
1090
|
-
hoverTransitionStyle(reducedMotion),
|
|
1174
|
+
...feedbackStyle(hoverFeedback, resolve(pressed), colors.rest, reducedMotion),
|
|
1091
1175
|
focusRingStyle(focused, colors.ring)
|
|
1092
1176
|
],
|
|
1093
1177
|
testID: item.testID ?? item.key,
|
|
1094
1178
|
onBlur: () => setFocused(false),
|
|
1095
1179
|
onFocus: () => setFocused(true),
|
|
1096
|
-
onHoverIn: () =>
|
|
1097
|
-
onHoverOut: () =>
|
|
1180
|
+
onHoverIn: () => setHover(true),
|
|
1181
|
+
onHoverOut: () => setHover(false),
|
|
1098
1182
|
onPress: () => onPress(item.route),
|
|
1099
1183
|
...ariaCurrentProps2(isActive),
|
|
1100
1184
|
children: [
|
|
1101
1185
|
typeof item.renderIcon === "function" ? /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: navBarStyles.linkIcon, children: item.renderIcon(textColor, iconSize) }) : null,
|
|
1186
|
+
underline === null ? null : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: underline }),
|
|
1102
1187
|
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [navBarStyles.linkText, { color: textColor }], children: item.label })
|
|
1103
1188
|
]
|
|
1104
1189
|
}
|
|
1105
1190
|
);
|
|
1106
1191
|
};
|
|
1192
|
+
var HIGHLIGHT_RADIUS = 8;
|
|
1193
|
+
var SLIDE_PROPERTIES = "left, top, width, height";
|
|
1194
|
+
function slideHighlightRect(layouts, activeKey, hoveredKey) {
|
|
1195
|
+
const hoveredRect = hoveredKey === null ? void 0 : layouts[hoveredKey];
|
|
1196
|
+
const activeRect = activeKey === void 0 ? void 0 : layouts[activeKey];
|
|
1197
|
+
return hoveredRect ?? activeRect ?? null;
|
|
1198
|
+
}
|
|
1199
|
+
function useSlidingHighlight(enabled, activeKey, color, reducedMotion) {
|
|
1200
|
+
const [layouts, setLayouts] = React.useState({});
|
|
1201
|
+
const [hoveredKey, setHoveredKey] = React.useState(null);
|
|
1202
|
+
const measure = React.useCallback(
|
|
1203
|
+
(key, layout) => {
|
|
1204
|
+
if (enabled) setLayouts((prev) => ({ ...prev, [key]: layout }));
|
|
1205
|
+
},
|
|
1206
|
+
[enabled]
|
|
1207
|
+
);
|
|
1208
|
+
const hover = React.useCallback((key, hovered) => {
|
|
1209
|
+
setHoveredKey((prev) => hovered ? key : prev === key ? null : prev);
|
|
1210
|
+
}, []);
|
|
1211
|
+
const rect = enabled ? slideHighlightRect(layouts, activeKey, hoveredKey) : null;
|
|
1212
|
+
const style = rect === null ? null : [
|
|
1213
|
+
{
|
|
1214
|
+
position: "absolute",
|
|
1215
|
+
left: rect.x,
|
|
1216
|
+
top: rect.y,
|
|
1217
|
+
width: rect.width,
|
|
1218
|
+
height: rect.height,
|
|
1219
|
+
borderRadius: HIGHLIGHT_RADIUS,
|
|
1220
|
+
backgroundColor: color
|
|
1221
|
+
},
|
|
1222
|
+
webTransition(SLIDE_PROPERTIES, uiMotion.motionDurations.base, reducedMotion)
|
|
1223
|
+
];
|
|
1224
|
+
return { style, measure, hover };
|
|
1225
|
+
}
|
|
1107
1226
|
var TEXT_ON_PRIMARY3 = "#ffffff";
|
|
1108
1227
|
var NO_ACTIVE_ROUTE = "";
|
|
1109
1228
|
var OVERFLOW_MENU_MIN_WIDTH = 200;
|
|
@@ -1244,7 +1363,8 @@ var NavBarInner = ({
|
|
|
1244
1363
|
overflowLabel = "More",
|
|
1245
1364
|
overflowHint = "",
|
|
1246
1365
|
containerStyle,
|
|
1247
|
-
contentMaxWidth
|
|
1366
|
+
contentMaxWidth,
|
|
1367
|
+
hoverFeedback = "default" /* Default */
|
|
1248
1368
|
}) => {
|
|
1249
1369
|
const { theme } = uiFeedback.useUi();
|
|
1250
1370
|
const colors = theme.colors;
|
|
@@ -1278,6 +1398,12 @@ var NavBarInner = ({
|
|
|
1278
1398
|
[onNavigate]
|
|
1279
1399
|
);
|
|
1280
1400
|
const activeRoute = resolveActiveRoute(items, pathname);
|
|
1401
|
+
const slide = useSlidingHighlight(
|
|
1402
|
+
hoverFeedback === "sliding-highlight" /* SlidingHighlight */,
|
|
1403
|
+
items.find((item) => item.route === activeRoute)?.key,
|
|
1404
|
+
colors.surfaceElevated,
|
|
1405
|
+
reducedMotion
|
|
1406
|
+
);
|
|
1281
1407
|
const linkColors = React.useMemo(
|
|
1282
1408
|
() => ({
|
|
1283
1409
|
rest: colors.textSecondary,
|
|
@@ -1327,9 +1453,11 @@ var NavBarInner = ({
|
|
|
1327
1453
|
colors: linkColors,
|
|
1328
1454
|
iconSize: NAV_ICON_SIZE,
|
|
1329
1455
|
isActive: item.route === activeRoute,
|
|
1456
|
+
hoverFeedback,
|
|
1330
1457
|
item,
|
|
1331
1458
|
navigateHint,
|
|
1332
1459
|
reducedMotion,
|
|
1460
|
+
onHoverChange: slide.hover,
|
|
1333
1461
|
onPress: handlePress
|
|
1334
1462
|
}
|
|
1335
1463
|
);
|
|
@@ -1355,11 +1483,15 @@ var NavBarInner = ({
|
|
|
1355
1483
|
testID: NAV_TEST_IDS.navBarLinks,
|
|
1356
1484
|
onLayout: (event) => setAvailableWidth(event.nativeEvent.layout.width),
|
|
1357
1485
|
children: [
|
|
1486
|
+
slide.style === null ? null : /* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { pointerEvents: "none", style: slide.style }),
|
|
1358
1487
|
visibleItems.map((item, index) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1359
1488
|
reactNative.View,
|
|
1360
1489
|
{
|
|
1361
1490
|
style: navBarStyles.linkCell,
|
|
1362
|
-
onLayout: (event) =>
|
|
1491
|
+
onLayout: (event) => {
|
|
1492
|
+
setItemWidth(index, event.nativeEvent.layout.width);
|
|
1493
|
+
slide.measure(item.key, event.nativeEvent.layout);
|
|
1494
|
+
},
|
|
1363
1495
|
children: renderLink(item)
|
|
1364
1496
|
},
|
|
1365
1497
|
item.key
|
|
@@ -2220,11 +2352,202 @@ function accessibleNavItems(user, table, translate) {
|
|
|
2220
2352
|
return roleRoutesToNavItems(authWeb.resolveAccessibleRoutes(user, table), translate);
|
|
2221
2353
|
}
|
|
2222
2354
|
|
|
2355
|
+
// src/TabFeedback.ts
|
|
2356
|
+
var TabFeedback = /* @__PURE__ */ ((TabFeedback2) => {
|
|
2357
|
+
TabFeedback2["Default"] = "default";
|
|
2358
|
+
TabFeedback2["IndicatorPill"] = "indicator-pill";
|
|
2359
|
+
TabFeedback2["DimSpring"] = "dim-spring";
|
|
2360
|
+
TabFeedback2["SlidingBar"] = "sliding-bar";
|
|
2361
|
+
TabFeedback2["IconLift"] = "icon-lift";
|
|
2362
|
+
return TabFeedback2;
|
|
2363
|
+
})(TabFeedback || {});
|
|
2364
|
+
|
|
2365
|
+
// src/feedback/tabPressFeedback.ts
|
|
2366
|
+
var FULL = 1;
|
|
2367
|
+
var PERCENT = 100;
|
|
2368
|
+
var PRESS_ICON_SCALE = 0.92;
|
|
2369
|
+
var DIM_OPACITY = 0.6;
|
|
2370
|
+
var ICON_LIFT_PX = -3;
|
|
2371
|
+
var SPRING_OVERSHOOT = 1.06;
|
|
2372
|
+
function tabIconTransform(kind, state) {
|
|
2373
|
+
if (kind === "indicator-pill" /* IndicatorPill */ && state.pressed) return [{ scale: PRESS_ICON_SCALE }];
|
|
2374
|
+
const lifted = state.active || state.pressed;
|
|
2375
|
+
return kind === "icon-lift" /* IconLift */ && lifted ? [{ translateY: ICON_LIFT_PX }] : void 0;
|
|
2376
|
+
}
|
|
2377
|
+
function tabDuration(kind) {
|
|
2378
|
+
if (kind === "default" /* Default */) return uiMotion.motionDurations.instant;
|
|
2379
|
+
const slow = kind === "dim-spring" /* DimSpring */ || kind === "sliding-bar" /* SlidingBar */;
|
|
2380
|
+
return slow ? uiMotion.motionDurations.slow : uiMotion.motionDurations.base;
|
|
2381
|
+
}
|
|
2382
|
+
function resolveTabFeedback(kind, state, reducedMotion) {
|
|
2383
|
+
const pillScale = state.active ? FULL : 0;
|
|
2384
|
+
return {
|
|
2385
|
+
opacity: kind === "dim-spring" /* DimSpring */ && state.pressed ? DIM_OPACITY : FULL,
|
|
2386
|
+
iconTransform: reducedMotion ? void 0 : tabIconTransform(kind, state),
|
|
2387
|
+
pillScale: kind === "indicator-pill" /* IndicatorPill */ ? pillScale : null,
|
|
2388
|
+
spring: kind === "dim-spring" /* DimSpring */ && !reducedMotion,
|
|
2389
|
+
durationMs: reducedMotion ? uiMotion.motionDurations.instant : tabDuration(kind)
|
|
2390
|
+
};
|
|
2391
|
+
}
|
|
2392
|
+
function slidingBarPosition(activeIndex, count) {
|
|
2393
|
+
if (activeIndex < 0 || count <= 0) return null;
|
|
2394
|
+
const share = PERCENT / count;
|
|
2395
|
+
return { left: `${activeIndex * share}%`, width: `${share}%` };
|
|
2396
|
+
}
|
|
2397
|
+
function springSteps() {
|
|
2398
|
+
return [
|
|
2399
|
+
{ toValue: SPRING_OVERSHOOT, durationMs: uiMotion.motionDurations.fast },
|
|
2400
|
+
{ toValue: FULL, durationMs: uiMotion.motionDurations.slow - uiMotion.motionDurations.fast }
|
|
2401
|
+
];
|
|
2402
|
+
}
|
|
2403
|
+
function useSettleSpring(active, enabled) {
|
|
2404
|
+
const scale = React.useRef(new reactNative.Animated.Value(FULL)).current;
|
|
2405
|
+
React.useEffect(() => {
|
|
2406
|
+
if (!enabled || !active) return void 0;
|
|
2407
|
+
const steps = springSteps().map(
|
|
2408
|
+
(step) => reactNative.Animated.timing(scale, {
|
|
2409
|
+
toValue: step.toValue,
|
|
2410
|
+
duration: step.durationMs,
|
|
2411
|
+
easing: uiMotion.motionEasings.standard,
|
|
2412
|
+
useNativeDriver: false
|
|
2413
|
+
})
|
|
2414
|
+
);
|
|
2415
|
+
const animation = reactNative.Animated.sequence(steps);
|
|
2416
|
+
animation.start();
|
|
2417
|
+
return () => animation.stop();
|
|
2418
|
+
}, [active, enabled, scale]);
|
|
2419
|
+
return scale;
|
|
2420
|
+
}
|
|
2421
|
+
var ICON_SIZE = 24;
|
|
2422
|
+
var PILL_WIDTH = 56;
|
|
2423
|
+
var PILL_HEIGHT = 32;
|
|
2424
|
+
var PILL_RADIUS = 16;
|
|
2425
|
+
var TAB_MIN_HEIGHT = 56;
|
|
2426
|
+
var LABEL_SIZE = 12;
|
|
2427
|
+
var LABEL_GAP = 2;
|
|
2428
|
+
var TAB_PADDING = 4;
|
|
2429
|
+
var styles7 = reactNative.StyleSheet.create({
|
|
2430
|
+
tab: {
|
|
2431
|
+
flex: 1,
|
|
2432
|
+
minHeight: TAB_MIN_HEIGHT,
|
|
2433
|
+
alignItems: "center",
|
|
2434
|
+
justifyContent: "center",
|
|
2435
|
+
paddingVertical: TAB_PADDING,
|
|
2436
|
+
rowGap: LABEL_GAP
|
|
2437
|
+
},
|
|
2438
|
+
iconWrap: { width: PILL_WIDTH, height: PILL_HEIGHT, alignItems: "center", justifyContent: "center" },
|
|
2439
|
+
pill: { ...reactNative.StyleSheet.absoluteFillObject, borderRadius: PILL_RADIUS },
|
|
2440
|
+
label: { fontSize: LABEL_SIZE, textAlign: "center" }
|
|
2441
|
+
});
|
|
2442
|
+
function ariaCurrentProps3(isActive) {
|
|
2443
|
+
return isActive ? { "aria-current": "page" } : {};
|
|
2444
|
+
}
|
|
2445
|
+
var BottomTab = ({
|
|
2446
|
+
item,
|
|
2447
|
+
isActive,
|
|
2448
|
+
kind,
|
|
2449
|
+
colors,
|
|
2450
|
+
reducedMotion,
|
|
2451
|
+
navigateHint,
|
|
2452
|
+
onPress
|
|
2453
|
+
}) => {
|
|
2454
|
+
const [pressed, setPressed] = React.useState(false);
|
|
2455
|
+
const feedback = resolveTabFeedback(kind, { active: isActive, pressed }, reducedMotion);
|
|
2456
|
+
const scale = useSettleSpring(isActive, feedback.spring);
|
|
2457
|
+
const color = isActive ? colors.active : colors.rest;
|
|
2458
|
+
const motion = webTransition("transform", feedback.durationMs, reducedMotion);
|
|
2459
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2460
|
+
reactNative.Pressable,
|
|
2461
|
+
{
|
|
2462
|
+
accessibilityHint: navigateHint(item.label),
|
|
2463
|
+
accessibilityLabel: item.label,
|
|
2464
|
+
accessibilityRole: "link",
|
|
2465
|
+
accessibilityState: { selected: isActive },
|
|
2466
|
+
style: [styles7.tab, { opacity: feedback.opacity }],
|
|
2467
|
+
testID: item.testID ?? item.key,
|
|
2468
|
+
onPress: () => onPress(item.route),
|
|
2469
|
+
onPressIn: () => setPressed(true),
|
|
2470
|
+
onPressOut: () => setPressed(false),
|
|
2471
|
+
...ariaCurrentProps3(isActive),
|
|
2472
|
+
children: [
|
|
2473
|
+
/* @__PURE__ */ jsxRuntime.jsxs(reactNative.Animated.View, { style: [styles7.iconWrap, { transform: [{ scale }] }], children: [
|
|
2474
|
+
feedback.pillScale === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2475
|
+
reactNative.View,
|
|
2476
|
+
{
|
|
2477
|
+
pointerEvents: "none",
|
|
2478
|
+
style: [styles7.pill, { backgroundColor: colors.pill, transform: [{ scale: feedback.pillScale }] }, motion]
|
|
2479
|
+
}
|
|
2480
|
+
),
|
|
2481
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.View, { style: [{ transform: feedback.iconTransform }, motion], children: item.renderIcon?.(color, ICON_SIZE) })
|
|
2482
|
+
] }),
|
|
2483
|
+
/* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: [styles7.label, { color }], children: item.label })
|
|
2484
|
+
]
|
|
2485
|
+
}
|
|
2486
|
+
);
|
|
2487
|
+
};
|
|
2488
|
+
var DEFAULT_TEST_ID = "bottom-tab-bar";
|
|
2489
|
+
var SLIDER_HEIGHT = 3;
|
|
2490
|
+
var styles8 = reactNative.StyleSheet.create({
|
|
2491
|
+
bar: { flexDirection: "row", borderTopWidth: reactNative.StyleSheet.hairlineWidth },
|
|
2492
|
+
slider: { position: "absolute", top: 0, height: SLIDER_HEIGHT }
|
|
2493
|
+
});
|
|
2494
|
+
var BottomTabBar = ({
|
|
2495
|
+
items,
|
|
2496
|
+
pathname,
|
|
2497
|
+
onNavigate,
|
|
2498
|
+
tabFeedback = "default" /* Default */,
|
|
2499
|
+
navigateHint = (label) => label,
|
|
2500
|
+
regionLabel = "",
|
|
2501
|
+
containerStyle,
|
|
2502
|
+
testID = DEFAULT_TEST_ID
|
|
2503
|
+
}) => {
|
|
2504
|
+
const { theme } = uiFeedback.useUi();
|
|
2505
|
+
const reducedMotion = useReducedMotion();
|
|
2506
|
+
const primary = theme.palette.primary["500"];
|
|
2507
|
+
const activeRoute = resolveActiveRoute(items, pathname);
|
|
2508
|
+
const activeIndex = items.findIndex((item) => item.route === activeRoute);
|
|
2509
|
+
const bar = tabFeedback === "sliding-bar" /* SlidingBar */ ? slidingBarPosition(activeIndex, items.length) : null;
|
|
2510
|
+
const barMs = resolveTabFeedback(tabFeedback, { active: true, pressed: false }, reducedMotion).durationMs;
|
|
2511
|
+
const colors = { active: primary, rest: theme.colors.textSecondary, pill: theme.colors.surfaceElevated };
|
|
2512
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
2513
|
+
reactNative.View,
|
|
2514
|
+
{
|
|
2515
|
+
"aria-label": regionLabel,
|
|
2516
|
+
role: "navigation",
|
|
2517
|
+
style: [styles8.bar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }, containerStyle],
|
|
2518
|
+
testID,
|
|
2519
|
+
children: [
|
|
2520
|
+
bar === null ? null : /* @__PURE__ */ jsxRuntime.jsx(
|
|
2521
|
+
reactNative.View,
|
|
2522
|
+
{
|
|
2523
|
+
pointerEvents: "none",
|
|
2524
|
+
style: [styles8.slider, { left: bar.left, width: bar.width, backgroundColor: primary }, webTransition("left", barMs, reducedMotion)]
|
|
2525
|
+
}
|
|
2526
|
+
),
|
|
2527
|
+
items.map((item) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
2528
|
+
BottomTab,
|
|
2529
|
+
{
|
|
2530
|
+
colors,
|
|
2531
|
+
isActive: item.route === activeRoute,
|
|
2532
|
+
item,
|
|
2533
|
+
kind: tabFeedback,
|
|
2534
|
+
navigateHint,
|
|
2535
|
+
reducedMotion,
|
|
2536
|
+
onPress: onNavigate
|
|
2537
|
+
},
|
|
2538
|
+
item.key
|
|
2539
|
+
))
|
|
2540
|
+
]
|
|
2541
|
+
}
|
|
2542
|
+
);
|
|
2543
|
+
};
|
|
2544
|
+
|
|
2223
2545
|
exports.ACTIVE_BORDER_RADIUS = ACTIVE_BORDER_RADIUS;
|
|
2224
2546
|
exports.ACTIVE_TINT_OPACITY = ACTIVE_TINT_OPACITY;
|
|
2225
2547
|
exports.APP_SHELL_SUFFIX = APP_SHELL_SUFFIX;
|
|
2226
2548
|
exports.AppShell = AppShell;
|
|
2227
2549
|
exports.BASE_INDENT = BASE_INDENT;
|
|
2550
|
+
exports.BottomTabBar = BottomTabBar;
|
|
2228
2551
|
exports.CHEVRON_ICON_SIZE = CHEVRON_ICON_SIZE;
|
|
2229
2552
|
exports.CollapsedRail = CollapsedRail;
|
|
2230
2553
|
exports.CommandPalette = CommandPalette;
|
|
@@ -2240,6 +2563,7 @@ exports.NAV_TEST_IDS = NAV_TEST_IDS;
|
|
|
2240
2563
|
exports.Nav = Nav;
|
|
2241
2564
|
exports.NavBar = NavBar;
|
|
2242
2565
|
exports.NavExpandableItem = NavExpandableItem;
|
|
2566
|
+
exports.NavHoverFeedback = NavHoverFeedback;
|
|
2243
2567
|
exports.NavOverflowMenu = NavOverflowMenu;
|
|
2244
2568
|
exports.NavShell = NavShell;
|
|
2245
2569
|
exports.PillNav = PillNav;
|
|
@@ -2248,6 +2572,7 @@ exports.RAIL_FULL_BREAKPOINT = RAIL_FULL_BREAKPOINT;
|
|
|
2248
2572
|
exports.Sidebar = Sidebar;
|
|
2249
2573
|
exports.SidebarChevron = SidebarChevron;
|
|
2250
2574
|
exports.SidebarSearch = SidebarSearch;
|
|
2575
|
+
exports.TabFeedback = TabFeedback;
|
|
2251
2576
|
exports.Topbar = Topbar;
|
|
2252
2577
|
exports.accessibleNavItems = accessibleNavItems;
|
|
2253
2578
|
exports.collapsedRailStyles = collapsedRailStyles;
|