@sigmela/router 0.0.13 → 0.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/lib/module/Navigation.js +14 -22
  2. package/lib/module/Router.js +299 -52
  3. package/lib/module/ScreenStack/ScreenStack.native.js +3 -0
  4. package/lib/module/ScreenStack/ScreenStack.web.js +139 -0
  5. package/lib/module/ScreenStack/index.js +3 -0
  6. package/lib/module/{ScreenStackItem.js → ScreenStackItem/ScreenStackItem.js} +7 -13
  7. package/lib/module/ScreenStackItem/ScreenStackItem.types.js +3 -0
  8. package/lib/module/ScreenStackItem/ScreenStackItem.web.js +38 -0
  9. package/lib/module/ScreenStackItem/index.js +3 -0
  10. package/lib/module/StackRenderer.js +8 -8
  11. package/lib/module/TabBar/RenderTabBar.native.js +224 -0
  12. package/lib/module/TabBar/RenderTabBar.web.js +130 -0
  13. package/lib/module/TabBar/TabIcon.web.js +42 -0
  14. package/lib/module/TabBar/index.js +3 -0
  15. package/lib/module/styles.css +139 -0
  16. package/lib/module/web/TransitionStack.js +227 -0
  17. package/lib/typescript/src/Navigation.d.ts +3 -2
  18. package/lib/typescript/src/Router.d.ts +15 -1
  19. package/lib/typescript/src/ScreenStack/ScreenStack.native.d.ts +2 -0
  20. package/lib/typescript/src/ScreenStack/ScreenStack.web.d.ts +11 -0
  21. package/lib/typescript/src/ScreenStack/index.d.ts +2 -0
  22. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.d.ts +3 -0
  23. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.types.d.ts +10 -0
  24. package/lib/typescript/src/ScreenStackItem/ScreenStackItem.web.d.ts +3 -0
  25. package/lib/typescript/src/ScreenStackItem/index.d.ts +3 -0
  26. package/lib/typescript/src/StackRenderer.d.ts +2 -2
  27. package/lib/typescript/src/TabBar/RenderTabBar.native.d.ts +8 -0
  28. package/lib/typescript/src/TabBar/{RenderTabBar.d.ts → RenderTabBar.web.d.ts} +1 -1
  29. package/lib/typescript/src/TabBar/TabBar.d.ts +11 -3
  30. package/lib/typescript/src/TabBar/TabIcon.web.d.ts +7 -0
  31. package/lib/typescript/src/TabBar/index.d.ts +2 -0
  32. package/lib/typescript/src/index.d.ts +1 -1
  33. package/lib/typescript/src/types.d.ts +24 -165
  34. package/lib/typescript/src/web/TransitionStack.d.ts +21 -0
  35. package/package.json +3 -2
  36. package/lib/module/TabBar/RenderTabBar.js +0 -123
  37. package/lib/typescript/src/ScreenStackItem.d.ts +0 -12
@@ -0,0 +1,38 @@
1
+ "use strict";
2
+
3
+ import { RouteLocalContext } from "../RouterContext.js";
4
+ import { memo } from 'react';
5
+ import { StyleSheet, View } from 'react-native';
6
+ import { jsx as _jsx } from "react/jsx-runtime";
7
+ export const ScreenStackItem = /*#__PURE__*/memo(({
8
+ phase = 'active',
9
+ item,
10
+ appearance
11
+ }) => {
12
+ const value = {
13
+ presentation: item.options?.stackPresentation ?? 'push',
14
+ params: item.params,
15
+ query: item.query,
16
+ pattern: item.pattern,
17
+ path: item.path
18
+ };
19
+ return /*#__PURE__*/_jsx("div", {
20
+ "data-presentation": value.presentation,
21
+ className: "screen-stack-item",
22
+ "data-phase": phase,
23
+ children: /*#__PURE__*/_jsx(RouteLocalContext.Provider, {
24
+ value: value,
25
+ children: /*#__PURE__*/_jsx(View, {
26
+ style: [styles.flex, appearance?.screen],
27
+ children: /*#__PURE__*/_jsx(item.component, {
28
+ ...(item.passProps || {})
29
+ })
30
+ })
31
+ })
32
+ });
33
+ });
34
+ const styles = StyleSheet.create({
35
+ flex: {
36
+ flex: 1
37
+ }
38
+ });
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export { ScreenStackItem } from './ScreenStackItem';
@@ -1,14 +1,14 @@
1
1
  "use strict";
2
2
 
3
3
  import { memo, useCallback, useSyncExternalStore } from 'react';
4
- import { ScreenStackItem } from "./ScreenStackItem.js";
5
- import { ScreenStack } from 'react-native-screens';
4
+ import { ScreenStackItem } from "./ScreenStackItem/index.js";
5
+ import { ScreenStack } from "./ScreenStack/index.js";
6
6
  import { useRouter } from "./RouterContext.js";
7
7
  import { StyleSheet } from 'react-native';
8
8
  import { jsx as _jsx } from "react/jsx-runtime";
9
9
  export const StackRenderer = /*#__PURE__*/memo(({
10
10
  stack,
11
- screenStyle
11
+ appearance
12
12
  }) => {
13
13
  const router = useRouter();
14
14
  const stackId = stack.getId();
@@ -16,13 +16,13 @@ export const StackRenderer = /*#__PURE__*/memo(({
16
16
  const get = useCallback(() => router.getStackHistory(stackId), [router, stackId]);
17
17
  const historyForThisStack = useSyncExternalStore(subscribe, get, get);
18
18
  return /*#__PURE__*/_jsx(ScreenStack, {
19
- style: styles.flex,
19
+ style: [styles.flex, appearance?.screen],
20
20
  children: historyForThisStack.map(item => /*#__PURE__*/_jsx(ScreenStackItem, {
21
- item: item,
21
+ appearance: appearance,
22
22
  stackId: stackId,
23
- screenStyle: screenStyle
24
- }, item.key))
25
- });
23
+ item: item
24
+ }, `stack-renderer-${item.key}`))
25
+ }, `stack-${stackId}`);
26
26
  });
27
27
  const styles = StyleSheet.create({
28
28
  flex: {
@@ -0,0 +1,224 @@
1
+ "use strict";
2
+
3
+ import { StackRenderer } from "../StackRenderer.js";
4
+ import { TabBarContext } from "./TabBarContext.js";
5
+ import { useRouter } from "../RouterContext.js";
6
+ import { BottomTabsScreen, BottomTabs, ScreenStackItem } from 'react-native-screens';
7
+ import { Platform, StyleSheet, View } from 'react-native';
8
+ import { useCallback, useSyncExternalStore, memo, useEffect, useState } from 'react';
9
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
10
+ // Helpers outside render to avoid re-creation
11
+ const isImageSource = value => {
12
+ if (value == null) return false;
13
+ const valueType = typeof value;
14
+ if (valueType === 'number') return true;
15
+ if (Array.isArray(value)) return value.length > 0;
16
+ if (valueType === 'object') {
17
+ const v = value;
18
+ if ('uri' in v || 'width' in v || 'height' in v) return true;
19
+ if ('sfSymbolName' in v || 'imageSource' in v || 'templateSource' in v) return false;
20
+ }
21
+ return false;
22
+ };
23
+ const isRNSIcon = value => {
24
+ if (value == null || typeof value !== 'object') return false;
25
+ const v = value;
26
+ return 'sfSymbolName' in v || 'imageSource' in v || 'templateSource' in v;
27
+ };
28
+ const buildIOSIcon = value => {
29
+ if (!value) return undefined;
30
+ if (isRNSIcon(value)) return value;
31
+ return {
32
+ templateSource: value
33
+ };
34
+ };
35
+
36
+ // Map unified tab icon props to RNS BottomTabsScreen platform-specific props
37
+ const getTabIcon = tab => {
38
+ const {
39
+ icon,
40
+ selectedIcon
41
+ } = tab;
42
+ if (icon || selectedIcon) {
43
+ if (Platform.OS === 'android' && isImageSource(icon)) {
44
+ return {
45
+ iconResource: icon
46
+ };
47
+ }
48
+ return {
49
+ selectedIcon: buildIOSIcon(selectedIcon),
50
+ icon: buildIOSIcon(icon)
51
+ };
52
+ }
53
+ return undefined;
54
+ };
55
+ export const RenderTabBar = /*#__PURE__*/memo(({
56
+ tabBar,
57
+ appearance = {}
58
+ }) => {
59
+ const router = useRouter();
60
+ const subscribe = useCallback(cb => tabBar.subscribe(cb), [tabBar]);
61
+ const snapshot = useSyncExternalStore(subscribe, tabBar.getState, tabBar.getState);
62
+ const {
63
+ tabs,
64
+ index,
65
+ config
66
+ } = snapshot;
67
+ const {
68
+ iconColor,
69
+ iconColorActive,
70
+ androidActiveIndicatorEnabled,
71
+ androidActiveIndicatorColor,
72
+ androidRippleColor,
73
+ labelVisibilityMode,
74
+ title,
75
+ backgroundColor,
76
+ badgeBackgroundColor,
77
+ iOSShadowColor
78
+ } = appearance?.tabBar ?? {};
79
+ useEffect(() => {
80
+ router.ensureTabSeed(index);
81
+ }, [index, router]);
82
+ const onNativeFocusChange = useCallback(event => {
83
+ const tabKey = event.nativeEvent.tabKey;
84
+ const tabIndex = tabs.findIndex(route => route.tabKey === tabKey);
85
+ router.onTabIndexChange(tabIndex);
86
+ }, [tabs, router]);
87
+ const onTabPress = useCallback(nextIndex => {
88
+ router.onTabIndexChange(nextIndex);
89
+ }, [router]);
90
+ const containerProps = {
91
+ tabBarBackgroundColor: backgroundColor,
92
+ tabBarItemTitleFontFamily: title?.fontFamily,
93
+ tabBarItemTitleFontSize: title?.fontSize,
94
+ tabBarItemTitleFontWeight: title?.fontWeight,
95
+ tabBarItemTitleFontStyle: title?.fontStyle,
96
+ tabBarItemTitleFontColor: title?.color,
97
+ tabBarItemTitleFontColorActive: title?.activeColor,
98
+ tabBarItemIconColor: iconColor,
99
+ tabBarItemIconColorActive: iconColorActive,
100
+ tabBarItemActiveIndicatorColor: androidActiveIndicatorColor,
101
+ tabBarItemActiveIndicatorEnabled: androidActiveIndicatorEnabled,
102
+ tabBarItemRippleColor: androidRippleColor,
103
+ tabBarItemLabelVisibilityMode: labelVisibilityMode
104
+ };
105
+ const iosState = {
106
+ tabBarItemTitleFontFamily: title?.fontFamily,
107
+ tabBarItemTitleFontSize: title?.fontSize,
108
+ tabBarItemTitleFontWeight: title?.fontWeight,
109
+ tabBarItemTitleFontStyle: title?.fontStyle,
110
+ tabBarItemTitleFontColor: title?.color,
111
+ tabBarItemBadgeBackgroundColor: badgeBackgroundColor,
112
+ tabBarItemTitleFontColorActive: title?.color,
113
+ tabBarItemIconColorActive: iconColorActive,
114
+ tabBarItemIconColor: iconColor
115
+ };
116
+ const iosAppearance = Platform.select({
117
+ default: undefined,
118
+ ios: {
119
+ tabBarBackgroundColor: backgroundColor,
120
+ tabBarShadowColor: iOSShadowColor,
121
+ compactInline: {
122
+ normal: iosState
123
+ },
124
+ stacked: {
125
+ normal: iosState
126
+ },
127
+ inline: {
128
+ normal: iosState
129
+ }
130
+ }
131
+ });
132
+
133
+ // If a custom component is provided, render it instead of default native BottomTabs
134
+ const CustomTabBar = config.component;
135
+
136
+ // Track visited tabs to lazily mount on first visit and keep mounted afterwards
137
+ const [visited, setVisited] = useState({});
138
+ useEffect(() => {
139
+ const key = tabs[index]?.tabKey;
140
+ if (key) {
141
+ setVisited(prev => prev[key] ? prev : {
142
+ ...prev,
143
+ [key]: true
144
+ });
145
+ }
146
+ }, [tabs, index]);
147
+ if (CustomTabBar) {
148
+ return /*#__PURE__*/_jsx(ScreenStackItem, {
149
+ screenId: "root-tabbar",
150
+ headerConfig: {
151
+ hidden: true
152
+ },
153
+ style: StyleSheet.absoluteFill,
154
+ stackAnimation: "slide_from_right",
155
+ children: /*#__PURE__*/_jsxs(TabBarContext.Provider, {
156
+ value: tabBar,
157
+ children: [/*#__PURE__*/_jsx(View, {
158
+ style: styles.flex,
159
+ children: tabs.filter(t => visited[t.tabKey]).map(tab => {
160
+ const isActive = tab.tabKey === tabs[index]?.tabKey;
161
+ const stackForTab = tabBar.stacks[tab.tabKey];
162
+ const ScreenForTab = tabBar.screens[tab.tabKey];
163
+ return /*#__PURE__*/_jsx(View, {
164
+ style: [styles.flex, !isActive && styles.hidden],
165
+ children: stackForTab ? /*#__PURE__*/_jsx(StackRenderer, {
166
+ appearance: appearance,
167
+ stack: stackForTab
168
+ }) : ScreenForTab ? /*#__PURE__*/_jsx(ScreenForTab, {}) : null
169
+ }, `tab-content-${tab.tabKey}`);
170
+ })
171
+ }), /*#__PURE__*/_jsx(CustomTabBar, {
172
+ onTabPress: onTabPress,
173
+ activeIndex: index,
174
+ tabs: tabs
175
+ })]
176
+ })
177
+ });
178
+ }
179
+ return /*#__PURE__*/_jsx(ScreenStackItem, {
180
+ screenId: "root-tabbar",
181
+ headerConfig: {
182
+ hidden: true
183
+ },
184
+ style: StyleSheet.absoluteFill,
185
+ stackAnimation: "slide_from_right",
186
+ children: /*#__PURE__*/_jsx(TabBarContext.Provider, {
187
+ value: tabBar,
188
+ children: /*#__PURE__*/_jsx(BottomTabs, {
189
+ onNativeFocusChange: onNativeFocusChange,
190
+ ...containerProps,
191
+ children: tabs.map(tab => {
192
+ const isFocused = tab.tabKey === tabs[index]?.tabKey;
193
+ const stack = tabBar.stacks[tab.tabKey];
194
+ const Screen = tabBar.screens[tab.tabKey];
195
+ const icon = getTabIcon(tab);
196
+ return /*#__PURE__*/_jsx(BottomTabsScreen, {
197
+ scrollEdgeAppearance: iosAppearance,
198
+ standardAppearance: iosAppearance,
199
+ isFocused: isFocused,
200
+ tabKey: tab.tabKey,
201
+ title: tab.title,
202
+ badgeValue: tab.badgeValue,
203
+ specialEffects: tab.specialEffects,
204
+ selectedIcon: icon?.selectedIcon,
205
+ iconResource: icon?.iconResource,
206
+ icon: icon?.icon,
207
+ children: stack ? /*#__PURE__*/_jsx(StackRenderer, {
208
+ appearance: appearance,
209
+ stack: stack
210
+ }) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null
211
+ }, tab.tabKey);
212
+ })
213
+ })
214
+ })
215
+ });
216
+ });
217
+ const styles = StyleSheet.create({
218
+ flex: {
219
+ flex: 1
220
+ },
221
+ hidden: {
222
+ display: 'none'
223
+ }
224
+ });
@@ -0,0 +1,130 @@
1
+ "use strict";
2
+
3
+ import { StackRenderer } from "../StackRenderer.js";
4
+ import { TabBarContext } from "./TabBarContext.js";
5
+ import { useRouter } from "../RouterContext.js";
6
+ import { TabIcon } from './TabIcon';
7
+ import { useCallback, useSyncExternalStore, memo, useEffect, useMemo } from 'react';
8
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
+ const isImageSource = value => {
10
+ if (value == null) return false;
11
+ const valueType = typeof value;
12
+ if (valueType === 'number') return true;
13
+ if (Array.isArray(value)) return value.length > 0;
14
+ if (valueType === 'object') {
15
+ const v = value;
16
+ if ('uri' in v || 'width' in v || 'height' in v) return true;
17
+ }
18
+ return false;
19
+ };
20
+ const toColorString = c => typeof c === 'string' ? c : undefined;
21
+
22
+ //
23
+
24
+ export const RenderTabBar = /*#__PURE__*/memo(({
25
+ tabBar,
26
+ appearance
27
+ }) => {
28
+ const router = useRouter();
29
+ const subscribe = useCallback(cb => tabBar.subscribe(cb), [tabBar]);
30
+ const snapshot = useSyncExternalStore(subscribe, tabBar.getState, tabBar.getState);
31
+ const {
32
+ tabs,
33
+ index,
34
+ config
35
+ } = snapshot;
36
+ useEffect(() => {
37
+ router.ensureTabSeed(index);
38
+ }, [index, router]);
39
+ const focusedTab = tabs[index];
40
+ const stack = focusedTab ? tabBar.stacks[focusedTab.tabKey] : undefined;
41
+ const Screen = focusedTab ? tabBar.screens[focusedTab.tabKey] : undefined;
42
+ const onTabClick = useCallback(nextIndex => {
43
+ const targetTab = tabs[nextIndex];
44
+ if (!targetTab) return;
45
+ const targetStack = tabBar.stacks[targetTab.tabKey];
46
+ if (targetStack) {
47
+ // Prefer last visited route in the target stack; otherwise first route
48
+ const stackId = targetStack.getId();
49
+ const history = router.getStackHistory(stackId);
50
+ const last = history.length ? history[history.length - 1] : undefined;
51
+ const toPath = last?.path ?? targetStack.getFirstRoute()?.path;
52
+ if (toPath) {
53
+ const currentPath = router.getVisibleRoute()?.path;
54
+ if (nextIndex === index && toPath === currentPath) return;
55
+ // Use replace to avoid duplicating history entries when switching tabs
56
+ router.replace(toPath, true);
57
+ return;
58
+ }
59
+ }
60
+
61
+ // Fallback: just switch tab index (no history push if there is no path)
62
+ router.onTabIndexChange(nextIndex);
63
+ }, [router, tabBar, tabs, index]);
64
+ const tabBarStyle = useMemo(() => {
65
+ const tabBarBg = toColorString(appearance?.tabBar?.backgroundColor);
66
+ return tabBarBg ? {
67
+ backgroundColor: tabBarBg
68
+ } : undefined;
69
+ }, [appearance?.tabBar?.backgroundColor]);
70
+ const titleBaseStyle = useMemo(() => ({
71
+ fontFamily: appearance?.tabBar?.title?.fontFamily,
72
+ fontSize: appearance?.tabBar?.title?.fontSize,
73
+ fontWeight: appearance?.tabBar?.title?.fontWeight,
74
+ fontStyle: appearance?.tabBar?.title?.fontStyle
75
+ }), [appearance?.tabBar?.title?.fontFamily, appearance?.tabBar?.title?.fontSize, appearance?.tabBar?.title?.fontWeight, appearance?.tabBar?.title?.fontStyle]);
76
+
77
+ // If a custom component is provided, render it instead of the default web tab bar
78
+ const CustomTabBar = config.component;
79
+ return /*#__PURE__*/_jsx("div", {
80
+ className: "screen-stack-item",
81
+ "data-presentation": "push",
82
+ "data-phase": "active",
83
+ children: /*#__PURE__*/_jsx(TabBarContext.Provider, {
84
+ value: tabBar,
85
+ children: /*#__PURE__*/_jsxs("div", {
86
+ className: "tab-stacks-container",
87
+ children: [stack ? /*#__PURE__*/_jsx(StackRenderer, {
88
+ appearance: appearance,
89
+ stack: stack
90
+ }) : Screen ? /*#__PURE__*/_jsx(Screen, {}) : null, CustomTabBar ? /*#__PURE__*/_jsx(CustomTabBar, {
91
+ onTabPress: onTabClick,
92
+ activeIndex: index,
93
+ tabs: tabs
94
+ }) : /*#__PURE__*/_jsx("div", {
95
+ className: "tab-bar",
96
+ style: tabBarStyle,
97
+ children: tabs.map((tab, i) => {
98
+ const isActive = i === index;
99
+ const iconTint = toColorString(isActive ? appearance?.tabBar?.iconColorActive : appearance?.tabBar?.iconColor);
100
+ const title = appearance?.tabBar?.title;
101
+ const labelColor = isActive ? toColorString(title?.activeColor) ?? toColorString(title?.color) : toColorString(title?.color);
102
+ const labelStyle = {
103
+ ...titleBaseStyle,
104
+ color: labelColor
105
+ };
106
+ return /*#__PURE__*/_jsxs("button", {
107
+ "data-index": i,
108
+ className: `tab-item${isActive ? ' active' : ''}`,
109
+ onClick: () => onTabClick(i),
110
+ children: [/*#__PURE__*/_jsx("div", {
111
+ className: "tab-item-icon",
112
+ children: isImageSource(tab.icon) ? /*#__PURE__*/_jsx(TabIcon, {
113
+ source: tab.icon,
114
+ tintColor: iconTint
115
+ }) : null
116
+ }), tab.badgeValue ? /*#__PURE__*/_jsx("span", {
117
+ className: "tab-item-label-badge",
118
+ children: tab.badgeValue
119
+ }) : null, /*#__PURE__*/_jsx("div", {
120
+ className: "tab-item-label",
121
+ style: labelStyle,
122
+ children: tab.title
123
+ })]
124
+ }, tab.tabKey);
125
+ })
126
+ })]
127
+ })
128
+ })
129
+ });
130
+ });
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+
3
+ import { memo } from 'react';
4
+ import { Image } from 'react-native';
5
+ import { jsx as _jsx } from "react/jsx-runtime";
6
+ const resolveImageUri = source => {
7
+ if (!source) return undefined;
8
+ const resolved = typeof Image.resolveAssetSource === 'function' ? Image.resolveAssetSource(source) : undefined;
9
+ if (resolved?.uri) return resolved.uri;
10
+ if (Array.isArray(source)) {
11
+ const first = source[0];
12
+ if (first && typeof first === 'object' && 'uri' in first) return first.uri;
13
+ }
14
+ if (typeof source === 'object' && source && 'uri' in source) return source.uri;
15
+ return undefined;
16
+ };
17
+ export const TabIcon = /*#__PURE__*/memo(({
18
+ source,
19
+ tintColor
20
+ }) => {
21
+ const iconUri = resolveImageUri(source);
22
+ const useMask = Boolean(tintColor && iconUri);
23
+ if (useMask && iconUri) {
24
+ const maskStyle = {
25
+ backgroundColor: tintColor,
26
+ WebkitMaskImage: `url(${iconUri})`,
27
+ maskImage: `url(${iconUri})`,
28
+ WebkitMaskSize: 'contain',
29
+ maskSize: 'contain',
30
+ WebkitMaskRepeat: 'no-repeat',
31
+ maskRepeat: 'no-repeat',
32
+ WebkitMaskPosition: 'center',
33
+ maskPosition: 'center'
34
+ };
35
+ return /*#__PURE__*/_jsx("div", {
36
+ style: maskStyle
37
+ });
38
+ }
39
+ return /*#__PURE__*/_jsx(Image, {
40
+ source: source
41
+ });
42
+ });
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+
3
+ export { RenderTabBar } from './RenderTabBar';
@@ -0,0 +1,139 @@
1
+
2
+
3
+ :root {
4
+ --tabs-transition: .2s ease-in-out;
5
+ --transition-standard-easing: cubic-bezier(.4, .0, .2, 1);
6
+ --transition-standard-in-time: .3s;
7
+ --transition-standard-out-time: .25s;
8
+
9
+ --transition-standard-in: var(--transition-standard-in-time) var(--transition-standard-easing);
10
+ --transition-standard-out: var(--transition-standard-out-time) var(--transition-standard-easing);
11
+
12
+ --background-color-true: #181818;
13
+
14
+ --background-color: var(--background-color-true);
15
+ }
16
+
17
+
18
+ body {
19
+ margin: 0;
20
+ padding: 0;
21
+ }
22
+
23
+ .screen-stack {
24
+ min-width: 100%;
25
+ width: 100%;
26
+ display: flex;
27
+ position: relative;
28
+ height: 100dvh;
29
+ overflow: hidden;
30
+ }
31
+
32
+ .screen-stack > .screen-stack-item {
33
+ position: absolute;
34
+ inset: 0;
35
+ display: flex;
36
+ font-size: 24px;
37
+ display: none;
38
+ overflow: hidden;
39
+ }
40
+
41
+ .screen-stack > .screen-stack-item.active {
42
+ display: flex;
43
+ }
44
+
45
+ .screen-stack[data-animation="navigation"].animating > .screen-stack-item {
46
+ transition: transform var(--transition-standard-in), filter var(--transition-standard-in);
47
+ }
48
+
49
+ .screen-stack[data-animation="navigation"].animating.backwards > .screen-stack-item {
50
+ transition: transform var(--transition-standard-out), filter var(--transition-standard-out);
51
+ }
52
+
53
+ .screen-stack[data-animation="modal"].animating > .screen-stack-item {
54
+ transition: transform var(--transition-standard-in), filter var(--transition-standard-in);
55
+ }
56
+
57
+ .screen-stack[data-animation="modal"].animating.backwards > .screen-stack-item {
58
+ transition: transform var(--transition-standard-out), filter var(--transition-standard-out);
59
+ }
60
+
61
+ .tab-stacks-container {
62
+ display: flex;
63
+ flex-direction: column;
64
+ flex: 1 1 auto;
65
+ min-height: 100vhd;
66
+ }
67
+
68
+ .tab-bar {
69
+ display: flex;
70
+ flex-direction: row;
71
+ align-items: center;
72
+ justify-content: center;
73
+ flex-shrink: 0;
74
+ position: relative;
75
+ height: 49px;
76
+ }
77
+
78
+ .tab-item {
79
+ appearance: none;
80
+ background: transparent;
81
+ border: 0;
82
+ margin: 0;
83
+ padding: 4px 0 2px;
84
+ height: 100%;
85
+ color: inherit;
86
+ display: flex;
87
+ flex: 1 1 0%;
88
+ flex-direction: column;
89
+ align-items: center;
90
+ justify-content: center;
91
+ position: relative;
92
+ cursor: pointer;
93
+ }
94
+
95
+ .tab-item:focus {
96
+ outline: none;
97
+ }
98
+
99
+ .tab-item-icon {
100
+ width: 24px;
101
+ height: 24px;
102
+ display: flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ }
106
+
107
+ .tab-item-icon > * {
108
+ width: 24px;
109
+ height: 24px;
110
+ display: block;
111
+ object-fit: contain;
112
+ }
113
+
114
+ .tab-item-label {
115
+ font-size: 11px;
116
+ line-height: 12px;
117
+ text-align: center;
118
+ white-space: nowrap;
119
+ pointer-events: none;
120
+ margin-top: 2px;
121
+ }
122
+
123
+ .tab-item-label-badge {
124
+ position: absolute;
125
+ top: 4px;
126
+ left: 50%;
127
+ transform: translateX(10px);
128
+ min-width: 16px;
129
+ height: 16px;
130
+ padding: 0 5px;
131
+ border-radius: 9999px;
132
+ background-color: #dc3545;
133
+ color: #fff;
134
+ font-size: 10px;
135
+ line-height: 16px;
136
+ display: inline-flex;
137
+ align-items: center;
138
+ justify-content: center;
139
+ }