react-native-magic-tab-bar 1.0.1 → 2.0.1
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 +245 -46
- package/lib/module/MagicTabBar.js +50 -7
- package/lib/module/MagicTabBar.js.map +1 -1
- package/lib/module/MagicTabItem.js +256 -23
- package/lib/module/MagicTabItem.js.map +1 -1
- package/lib/module/MagicTabs.js +107 -15
- package/lib/module/MagicTabs.js.map +1 -1
- package/lib/module/defaultTabs.js +5 -4
- package/lib/module/defaultTabs.js.map +1 -1
- package/lib/module/index.js +6 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/theme.js +10 -1
- package/lib/module/theme.js.map +1 -1
- package/lib/typescript/MagicTabBar.d.ts +18 -1
- package/lib/typescript/MagicTabBar.d.ts.map +1 -1
- package/lib/typescript/MagicTabItem.d.ts +30 -4
- package/lib/typescript/MagicTabItem.d.ts.map +1 -1
- package/lib/typescript/MagicTabs.d.ts +43 -6
- package/lib/typescript/MagicTabs.d.ts.map +1 -1
- package/lib/typescript/defaultTabs.d.ts +5 -4
- package/lib/typescript/defaultTabs.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +1 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/theme.d.ts.map +1 -1
- package/lib/typescript/types.d.ts +60 -0
- package/lib/typescript/types.d.ts.map +1 -1
- package/package.json +14 -4
- package/src/MagicTabBar.tsx +81 -7
- package/src/MagicTabItem.tsx +338 -19
- package/src/MagicTabs.tsx +185 -13
- package/src/defaultTabs.tsx +5 -4
- package/src/index.tsx +10 -1
- package/src/theme.ts +5 -0
- package/src/types.ts +64 -0
package/README.md
CHANGED
|
@@ -1,25 +1,53 @@
|
|
|
1
1
|
# react-native-magic-tab-bar
|
|
2
2
|
|
|
3
|
-
A customizable, animated floating tab bar for
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
A customizable, animated **floating tab bar for [Expo Router](https://docs.expo.dev/router/introduction/)** (SDK 56+). You bring your own icons and labels — the package handles the layout, the active-pill animation, and the navigation wiring.
|
|
4
|
+
|
|
5
|
+
> Built on Expo Router's headless tabs (`expo-router/ui`). Works in any Expo Router project on **iOS and Android**. (Bare React Native / React Navigation is not supported yet — see [Roadmap](#roadmap).)
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- 🎯 **Drop-in** — replaces your Expo Router tabs layout; bring your own icons.
|
|
10
|
+
- ✨ **Animated active pill** with a spring you can tune.
|
|
11
|
+
- 🏷️ **Flexible labels** — beside or below the icon; show on the active tab, always, or never.
|
|
12
|
+
- 🔴 **Badges** — dots or counts on any tab.
|
|
13
|
+
- 🧊 **Glass / blur / transparent** backgrounds (native iOS Liquid Glass supported).
|
|
14
|
+
- 📳 **Haptics** and **press callbacks** (great for "scroll to top" on re-press).
|
|
15
|
+
- ➕ **Action (FAB) tab** for a raised center button.
|
|
16
|
+
- 🪶 **Light mode** — a compact, icon-only bar you can switch on **per tab** (e.g. only on an immersive full-screen feed), with a smooth transition.
|
|
17
|
+
- 🎨 **Fully themeable** via a single `theme` prop.
|
|
18
|
+
|
|
19
|
+
## Table of contents
|
|
20
|
+
|
|
21
|
+
- [Installation](#installation)
|
|
22
|
+
- [Quick start](#quick-start)
|
|
23
|
+
- [Recipes](#recipes)
|
|
24
|
+
- [Labels](#labels)
|
|
25
|
+
- [Badges](#badges)
|
|
26
|
+
- [Haptics and press callbacks](#haptics-and-press-callbacks)
|
|
27
|
+
- [Disabled tabs](#disabled-tabs)
|
|
28
|
+
- [Action (FAB) tab](#action-fab-tab)
|
|
29
|
+
- [Glass, blur and transparency](#glass-blur-and-transparency)
|
|
30
|
+
- [Light mode](#light-mode-compact-bar)
|
|
31
|
+
- [Theming](#theming)
|
|
32
|
+
- [API](#api)
|
|
33
|
+
- [Development](#development)
|
|
34
|
+
- [Roadmap](#roadmap)
|
|
35
|
+
- [License](#license)
|
|
10
36
|
|
|
11
37
|
## Screenshots
|
|
12
38
|
|
|
13
39
|
<p align="center">
|
|
14
|
-
<img src="https://raw.githubusercontent.com/Bhavinpethani04/react-native-magic-tab-bar/main/assets/
|
|
40
|
+
<img src="https://raw.githubusercontent.com/Bhavinpethani04/react-native-magic-tab-bar/main/assets/gifs/ios.gif" alt="MagicTabs on iOS" width="280" />
|
|
15
41
|
|
|
16
|
-
<img src="https://raw.githubusercontent.com/Bhavinpethani04/react-native-magic-tab-bar/main/assets/
|
|
42
|
+
<img src="https://raw.githubusercontent.com/Bhavinpethani04/react-native-magic-tab-bar/main/assets/gifs/android.gif" alt="MagicTabs on Android" width="280" />
|
|
17
43
|
</p>
|
|
18
44
|
|
|
19
45
|
<p align="center"><sub>iOS (left) · Android (right)</sub></p>
|
|
20
46
|
|
|
21
47
|
## Installation
|
|
22
48
|
|
|
49
|
+
> **Requirements:** an [Expo Router](https://docs.expo.dev/router/introduction/) project (Expo SDK 56+) with an `app/` directory. Works on iOS & Android.
|
|
50
|
+
|
|
23
51
|
```bash
|
|
24
52
|
npm install react-native-magic-tab-bar
|
|
25
53
|
```
|
|
@@ -32,79 +60,248 @@ These are normally already present in an Expo Router app:
|
|
|
32
60
|
npx expo install expo-router react-native-reanimated react-native-safe-area-context react-native-worklets
|
|
33
61
|
```
|
|
34
62
|
|
|
35
|
-
Make sure the Reanimated/Worklets Babel plugin is enabled (Expo SDK 56's
|
|
36
|
-
|
|
63
|
+
Make sure the Reanimated/Worklets Babel plugin is enabled (Expo SDK 56's `babel-preset-expo` configures this automatically).
|
|
64
|
+
|
|
65
|
+
### Optional dependencies
|
|
66
|
+
|
|
67
|
+
Install these only if you use the matching feature — the library works without them:
|
|
37
68
|
|
|
38
|
-
|
|
69
|
+
```bash
|
|
70
|
+
npx expo install expo-glass-effect # native iOS Liquid Glass (glass prop)
|
|
71
|
+
npx expo install expo-haptics # selection haptics (haptics prop)
|
|
72
|
+
npx expo install @expo/vector-icons # only for the /default-tabs demo set
|
|
73
|
+
```
|
|
39
74
|
|
|
40
|
-
|
|
41
|
-
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
Use `MagicTabs` as your tab navigator in `app/_layout.tsx`. Each entry in `tabs` maps a route to an icon and label:
|
|
42
78
|
|
|
43
79
|
```tsx
|
|
44
80
|
// app/_layout.tsx
|
|
45
|
-
import { MagicTabs
|
|
46
|
-
import {
|
|
81
|
+
import { MagicTabs } from "react-native-magic-tab-bar";
|
|
82
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
47
83
|
|
|
48
84
|
export default function Layout() {
|
|
49
85
|
return (
|
|
50
86
|
<MagicTabs
|
|
51
87
|
tabs={[
|
|
52
|
-
{ name: "index", href: "/", label: "Home", icon: ({ color, size }) => <
|
|
53
|
-
{ name: "search", href: "/search", label: "Search", icon: ({ color, size }) => <
|
|
54
|
-
{ name: "profile", href: "/profile", label: "Profile", icon: ({ color, size }) => <
|
|
88
|
+
{ name: "index", href: "/", label: "Home", icon: ({ color, size }) => <Ionicons name="home" color={color} size={size} /> },
|
|
89
|
+
{ name: "search", href: "/search", label: "Search", icon: ({ color, size }) => <Ionicons name="search" color={color} size={size} /> },
|
|
90
|
+
{ name: "profile", href: "/profile", label: "Profile", icon: ({ color, size }) => <Ionicons name="person" color={color} size={size} /> },
|
|
55
91
|
]}
|
|
56
92
|
/>
|
|
57
93
|
);
|
|
58
94
|
}
|
|
59
95
|
```
|
|
60
96
|
|
|
61
|
-
|
|
62
|
-
|
|
97
|
+
- `name` must match the route file in your `app/` directory (e.g. `index`, `search`, `profile`).
|
|
98
|
+
- `href` is where the tab navigates.
|
|
99
|
+
- `icon` receives `{ focused, color, size }` so you can swap glyphs/colors for the active state.
|
|
100
|
+
|
|
101
|
+
> `MagicTabs` renders Expo Router's `<Tabs>` for you — put it straight in `app/_layout.tsx`. There's no separate `<Tabs>` wrapper to set up.
|
|
102
|
+
|
|
103
|
+
**Tip — filled vs. outline icons:** use `focused` to swap the glyph on the active tab:
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
icon: ({ focused, color, size }) => (
|
|
107
|
+
<Ionicons name={focused ? "home" : "home-outline"} color={color} size={size} />
|
|
108
|
+
);
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
> **Want a ready-made set for a quick look?** Import the demo tabs from the subpath (this is the only thing that pulls in `@expo/vector-icons`, so the core stays dependency-free):
|
|
112
|
+
>
|
|
113
|
+
> ```tsx
|
|
114
|
+
> import { defaultTabs } from "react-native-magic-tab-bar/default-tabs";
|
|
115
|
+
>
|
|
116
|
+
> <MagicTabs tabs={defaultTabs} />;
|
|
117
|
+
> ```
|
|
118
|
+
|
|
119
|
+
## Recipes
|
|
63
120
|
|
|
64
|
-
###
|
|
121
|
+
### Labels
|
|
65
122
|
|
|
66
|
-
|
|
123
|
+
Labels are **beside** the icon by default (`labelPosition="right"`), shown only on the **active** tab.
|
|
67
124
|
|
|
68
125
|
```tsx
|
|
69
|
-
|
|
126
|
+
<MagicTabs tabs={tabs} labelPosition="bottom" showLabels="always" /> // Material-style bar
|
|
127
|
+
<MagicTabs tabs={tabs} showLabels={false} /> // icon-only bar
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
| `showLabels` | Effect |
|
|
131
|
+
| --- | --- |
|
|
132
|
+
| `true` / `"active"` | Label on the focused tab only *(default)* |
|
|
133
|
+
| `"always"` | Label on every tab *(requires `labelPosition="bottom"`)* |
|
|
134
|
+
| `false` / `"never"` | Icon-only |
|
|
70
135
|
|
|
136
|
+
`labelPosition` is `"right"` (default) or `"bottom"`. You can also override visibility per tab with `showLabel` on the tab config.
|
|
137
|
+
|
|
138
|
+
### Badges
|
|
139
|
+
|
|
140
|
+
```tsx
|
|
141
|
+
{ name: "inbox", href: "/inbox", label: "Inbox", badge: 5, icon }, // count bubble
|
|
142
|
+
{ name: "alerts", href: "/alerts", label: "Alerts", badge: true, icon }, // dot
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`badge` accepts a `number`, `string`, or `boolean` (`true` = dot). Numbers above 99 show as `99+`. Colors come from `theme.badgeColor` / `theme.badgeTextColor`.
|
|
146
|
+
|
|
147
|
+
### Haptics and press callbacks
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
71
150
|
<MagicTabs
|
|
72
151
|
tabs={tabs}
|
|
73
|
-
|
|
74
|
-
|
|
152
|
+
haptics // selection haptic on tap (needs expo-haptics)
|
|
153
|
+
onTabPress={(name, focused) => {
|
|
154
|
+
if (focused) scrollToTop(name); // re-pressing the active tab
|
|
155
|
+
}}
|
|
156
|
+
/>
|
|
75
157
|
```
|
|
76
158
|
|
|
77
|
-
|
|
159
|
+
`onTabPress` / `onTabLongPress` receive `(name, focused)`, where `focused` tells you the tab was **already** active — perfect for scroll-to-top or reset-stack behavior.
|
|
160
|
+
|
|
161
|
+
### Disabled tabs
|
|
162
|
+
|
|
163
|
+
```tsx
|
|
164
|
+
{ name: "soon", href: "/soon", label: "Soon", disabled: true, icon }
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Dims the tab and blocks navigation to it.
|
|
168
|
+
|
|
169
|
+
### Action (FAB) tab
|
|
170
|
+
|
|
171
|
+
Render a raised, circular center button:
|
|
172
|
+
|
|
173
|
+
```tsx
|
|
174
|
+
{ name: "create", href: "/create", variant: "action", icon }
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Uses `theme.actionColor` / `theme.actionIconColor`.
|
|
178
|
+
|
|
179
|
+
### Glass, blur and transparency
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
<MagicTabs tabs={tabs} glass /> // native iOS Liquid Glass (iOS 26+)
|
|
183
|
+
<MagicTabs tabs={tabs} isTransparent transparency={0.4} />// translucent bar
|
|
184
|
+
<MagicTabs tabs={tabs} renderBackground={() => <MyBlurView />} /> // any custom background
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- `glass` uses `expo-glass-effect` on iOS 26+ and falls back to the translucent `barColor` elsewhere.
|
|
188
|
+
- `renderBackground` renders any view behind the bar; when omitted, a solid `barColor` is used.
|
|
189
|
+
|
|
190
|
+
### Light mode (compact bar)
|
|
191
|
+
|
|
192
|
+
A shorter, **icon-only** bar (65% width, floating higher). Turn it on for the whole bar, or **per tab** so it only appears on an immersive screen:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
// Whole bar
|
|
196
|
+
<MagicTabs tabs={tabs} isLight lightBottomMargin={40} />
|
|
197
|
+
|
|
198
|
+
// Per tab — the bar morphs to light only while "explore" is the active route
|
|
199
|
+
<MagicTabs
|
|
200
|
+
tabs={[
|
|
201
|
+
{ name: "index", href: "/", label: "Home", icon },
|
|
202
|
+
{ name: "explore", href: "/explore", label: "Explore", icon, isLight: true },
|
|
203
|
+
{ name: "profile", href: "/profile", label: "Profile", icon },
|
|
204
|
+
]}
|
|
205
|
+
/>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
The transition between the normal and light bar is animated. `lightBottomMargin` (default `14`) controls the extra gap above the screen edge in light mode.
|
|
209
|
+
|
|
210
|
+
### Theming
|
|
211
|
+
|
|
212
|
+
Everything visual — colors, sizes, corner radius, the animation spring — lives in one `theme` prop. Override any subset; the rest falls back to `defaultTheme`.
|
|
213
|
+
|
|
214
|
+
**The colors that matter most:**
|
|
215
|
+
|
|
216
|
+
| Token | Controls |
|
|
217
|
+
| --- | --- |
|
|
218
|
+
| `barColor` | the bar's background |
|
|
219
|
+
| `activePillColor` | the pill highlight behind the **active** tab |
|
|
220
|
+
| `activeColor` | the active tab's icon + label |
|
|
221
|
+
| `inactiveColor` | the icons of **inactive** tabs |
|
|
222
|
+
| `badgeColor` / `badgeTextColor` | the badge bubble + its text |
|
|
223
|
+
| `actionColor` / `actionIconColor` | the [action (FAB) tab](#action-fab-tab) |
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
<MagicTabs
|
|
227
|
+
tabs={tabs}
|
|
228
|
+
theme={{
|
|
229
|
+
barColor: "#17171C", // dark bar
|
|
230
|
+
activePillColor: "#2563EB", // blue highlight behind the active tab
|
|
231
|
+
activeColor: "#FFFFFF", // active icon/label
|
|
232
|
+
inactiveColor: "#9BA0AA", // inactive icons
|
|
233
|
+
radius: 24, // rounder bar & pill
|
|
234
|
+
spring: { mass: 0.6, damping: 16, stiffness: 200 }, // snappier animation
|
|
235
|
+
}}
|
|
236
|
+
/>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
See the [full token list with defaults](#magictabbartheme) for sizes (`iconSize`, `height`, `fontSize`), spacing (`horizontalMargin`, `bottomInset`) and more.
|
|
78
240
|
|
|
79
241
|
## API
|
|
80
242
|
|
|
81
243
|
### `<MagicTabs />`
|
|
82
244
|
|
|
83
|
-
| Prop
|
|
84
|
-
|
|
|
85
|
-
| `tabs`
|
|
86
|
-
| `theme`
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
245
|
+
| Prop | Type | Default | Description |
|
|
246
|
+
| --- | --- | --- | --- |
|
|
247
|
+
| `tabs` | `MagicTabConfig[]` | **required** | The tabs, in order. |
|
|
248
|
+
| `theme` | `Partial<MagicTabBarTheme>` | `defaultTheme` | Override any visual token. |
|
|
249
|
+
| `showLabels` | `boolean \| 'active' \| 'always' \| 'never'` | `'active'` | When labels are shown. `'always'` needs `labelPosition="bottom"`. |
|
|
250
|
+
| `labelPosition` | `'right' \| 'bottom'` | `'right'` | Label beside or below the icon. |
|
|
251
|
+
| `variant` | `'floating' \| 'docked'` | `'floating'` | Float over content, or dock in flow. |
|
|
252
|
+
| `isLight` | `boolean` | `false` | Force the compact light bar for all tabs. |
|
|
253
|
+
| `lightBottomMargin` | `number` | `14` | Extra bottom gap in light mode only. |
|
|
254
|
+
| `isTransparent` | `boolean` | `false` | Make the bar background see-through. |
|
|
255
|
+
| `transparency` | `number` | `0.6` | Bar opacity `0–1` while `isTransparent`. |
|
|
256
|
+
| `glass` | `boolean` | `false` | Native iOS Liquid Glass (needs `expo-glass-effect`). |
|
|
257
|
+
| `renderBackground` | `() => ReactNode` | — | Custom background (blur/glass) behind the bar. |
|
|
258
|
+
| `haptics` | `boolean` | `false` | Selection haptic on press (needs `expo-haptics`). |
|
|
259
|
+
| `onTabPress` | `(name, focused) => void` | — | Fired on tab press. |
|
|
260
|
+
| `onTabLongPress` | `(name, focused) => void` | — | Fired on tab long-press. |
|
|
89
261
|
|
|
90
262
|
### `MagicTabConfig`
|
|
91
263
|
|
|
92
|
-
| Field
|
|
93
|
-
|
|
|
94
|
-
| `name`
|
|
95
|
-
| `href`
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
264
|
+
| Field | Type | Description |
|
|
265
|
+
| --- | --- | --- |
|
|
266
|
+
| `name` | `string` | Route name (matches the file in `app/`). |
|
|
267
|
+
| `href` | `Href` | Destination, e.g. `/` or `/search`. |
|
|
268
|
+
| `icon` | `(p: MagicTabIconProps) => ReactNode` | Renders the icon. Gets `{ focused, color, size }`. |
|
|
269
|
+
| `label` | `string?` | Text label. |
|
|
270
|
+
| `showLabel` | `boolean?` | Per-tab override of `showLabels`. |
|
|
271
|
+
| `badge` | `number \| string \| boolean?` | Dot (`true`) or count bubble. |
|
|
272
|
+
| `disabled` | `boolean?` | Dim the tab and block navigation. |
|
|
273
|
+
| `variant` | `'action'?` | Render as a raised FAB button. |
|
|
274
|
+
| `isLight` | `boolean?` | Switch the whole bar to light mode while this tab is active. |
|
|
275
|
+
|
|
276
|
+
### `MagicTabBarTheme`
|
|
277
|
+
|
|
278
|
+
| Token | Default | |
|
|
279
|
+
| --- | --- | --- |
|
|
280
|
+
| `barColor` | `rgba(38,38,40,0.94)` | Bar background |
|
|
281
|
+
| `activePillColor` | `rgba(120,120,124,0.55)` | Active-tab pill |
|
|
282
|
+
| `activeColor` | `#FFFFFF` | Active icon/label color |
|
|
283
|
+
| `inactiveColor` | `#FFFFFF` | Inactive icon color |
|
|
284
|
+
| `iconSize` | `22` | Icon size |
|
|
285
|
+
| `fontSize` | `12` | Active label size |
|
|
286
|
+
| `height` | `56` | Bar height |
|
|
287
|
+
| `radius` | `28` | Bar & pill corner radius |
|
|
288
|
+
| `badgeColor` | `#FF3B30` | Badge background |
|
|
289
|
+
| `badgeTextColor` | `#FFFFFF` | Badge text |
|
|
290
|
+
| `actionColor` | `#0A84FF` | Action (FAB) background |
|
|
291
|
+
| `actionIconColor` | `#FFFFFF` | Action (FAB) icon |
|
|
292
|
+
| `horizontalMargin` | `14` | Side margin from screen edges |
|
|
293
|
+
| `bottomInset` | `10` | Extra space below the bar |
|
|
294
|
+
| `spring` | `{ mass: 0.6, damping: 18, stiffness: 180 }` | Pill/label animation |
|
|
295
|
+
|
|
296
|
+
### Exported types
|
|
297
|
+
|
|
298
|
+
`MagicTabConfig`, `MagicTabIconProps`, `MagicTabBarTheme`, `MagicTabBarVariant`, `MagicTabPressHandler`, `MagicLabelMode`, `MagicLabelPosition`, `MagicSpringConfig`, plus the `defaultTheme` value.
|
|
299
|
+
|
|
300
|
+
> `defaultTabs` is exported from the `react-native-magic-tab-bar/default-tabs` subpath (not the main entry), so the core adds **zero runtime dependencies**.
|
|
103
301
|
|
|
104
302
|
## Development
|
|
105
303
|
|
|
106
|
-
This repo is a monorepo: the library lives at the root (`src/`) and `example/`
|
|
107
|
-
is a runnable Expo app that imports the library straight from source.
|
|
304
|
+
This repo is a monorepo: the library lives at the root (`src/`) and `example/` is a runnable Expo app that imports the library straight from source.
|
|
108
305
|
|
|
109
306
|
```bash
|
|
110
307
|
# from the repo root
|
|
@@ -116,6 +313,8 @@ Editing files in `src/` hot-reloads in the example app.
|
|
|
116
313
|
|
|
117
314
|
### Building / publishing
|
|
118
315
|
|
|
316
|
+
> Publishing is restricted to package maintainers.
|
|
317
|
+
|
|
119
318
|
```bash
|
|
120
319
|
npm run build # react-native-builder-bob -> lib/ (ESM + d.ts)
|
|
121
320
|
npm publish
|
|
@@ -2,8 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
import { forwardRef } from "react";
|
|
4
4
|
import { StyleSheet, View } from "react-native";
|
|
5
|
+
import Animated, { LinearTransition } from "react-native-reanimated";
|
|
5
6
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
6
7
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
/**
|
|
9
|
+
* Layout transition used to morph the bar between its normal and compact
|
|
10
|
+
* "light" shapes (width, height and bottom margin) when the active tab changes.
|
|
11
|
+
*/
|
|
12
|
+
const barTransition = LinearTransition.springify().mass(0.5).damping(16).stiffness(160);
|
|
13
|
+
|
|
14
|
+
/** Extra bar height when labels sit below icons, so they have room to breathe. */
|
|
15
|
+
const BOTTOM_LABEL_EXTRA_HEIGHT = 6;
|
|
16
|
+
|
|
17
|
+
/** Fixed height of the compact "light" bar. */
|
|
18
|
+
const LIGHT_BAR_HEIGHT = 46;
|
|
19
|
+
|
|
20
|
+
/** Default extra bottom margin added below the bar in "light" mode. */
|
|
21
|
+
const LIGHT_EXTRA_BOTTOM_MARGIN = 14;
|
|
7
22
|
/**
|
|
8
23
|
* `expo-glass-effect` is an optional peer dependency. We load it through a
|
|
9
24
|
* guarded `require` so the library still installs and runs for consumers who
|
|
@@ -28,16 +43,25 @@ export const MIN_BAR_OPACITY = 0.1;
|
|
|
28
43
|
export const MagicTabBar = /*#__PURE__*/forwardRef(function MagicTabBar({
|
|
29
44
|
theme,
|
|
30
45
|
variant = "floating",
|
|
46
|
+
labelPosition = "right",
|
|
31
47
|
isTransparent = false,
|
|
32
48
|
transparency = 0.6,
|
|
33
49
|
glass = false,
|
|
34
50
|
renderBackground,
|
|
51
|
+
isLight = false,
|
|
52
|
+
lightBottomMargin = LIGHT_EXTRA_BOTTOM_MARGIN,
|
|
35
53
|
children,
|
|
36
54
|
style,
|
|
37
55
|
...rest
|
|
38
56
|
}, ref) {
|
|
39
57
|
const insets = useSafeAreaInsets();
|
|
40
58
|
const floating = variant !== "docked";
|
|
59
|
+
// "Light" mode is a fixed, compact height. Otherwise stacked (bottom) labels
|
|
60
|
+
// need more vertical room than the icon-only / side-by-side layouts, so the
|
|
61
|
+
// bar grows a little in that mode.
|
|
62
|
+
const barHeight = isLight ? LIGHT_BAR_HEIGHT : labelPosition === "bottom" ? theme.height + BOTTOM_LABEL_EXTRA_HEIGHT : theme.height;
|
|
63
|
+
// "Light" mode floats a little higher off the bottom edge.
|
|
64
|
+
const extraBottomMargin = isLight ? lightBottomMargin : 0;
|
|
41
65
|
// Native Liquid Glass needs the optional `expo-glass-effect` dep and iOS
|
|
42
66
|
// 26+; everywhere else we fall back to the translucent color background.
|
|
43
67
|
const useGlass = glass && !!glassEffect?.isLiquidGlassAvailable();
|
|
@@ -50,13 +74,17 @@ export const MagicTabBar = /*#__PURE__*/forwardRef(function MagicTabBar({
|
|
|
50
74
|
return /*#__PURE__*/_jsx(View, {
|
|
51
75
|
ref: ref,
|
|
52
76
|
pointerEvents: "box-none",
|
|
53
|
-
style: [floating ? styles.floatingWrapper : styles.dockedWrapper,
|
|
54
|
-
|
|
55
|
-
|
|
77
|
+
style: [floating ? styles.floatingWrapper : styles.dockedWrapper,
|
|
78
|
+
// Light mode centers a narrower bar; drop the side padding so the
|
|
79
|
+
// bar's width is measured against the full screen width.
|
|
80
|
+
isLight && styles.lightWrapper, {
|
|
81
|
+
paddingHorizontal: isLight ? 0 : theme.horizontalMargin,
|
|
82
|
+
paddingBottom: (floating ? insets.bottom : 0) + theme.bottomInset + extraBottomMargin
|
|
56
83
|
}],
|
|
57
|
-
children: /*#__PURE__*/_jsxs(View, {
|
|
58
|
-
|
|
59
|
-
|
|
84
|
+
children: /*#__PURE__*/_jsxs(Animated.View, {
|
|
85
|
+
layout: barTransition,
|
|
86
|
+
style: [styles.bar, !seeThrough && styles.barShadow, isLight && styles.lightBar, {
|
|
87
|
+
height: barHeight,
|
|
60
88
|
borderRadius: theme.radius
|
|
61
89
|
}],
|
|
62
90
|
children: [renderBackground ? /*#__PURE__*/_jsx(View, {
|
|
@@ -89,7 +117,7 @@ export const MagicTabBar = /*#__PURE__*/forwardRef(function MagicTabBar({
|
|
|
89
117
|
opacity: barOpacity
|
|
90
118
|
}]
|
|
91
119
|
}), /*#__PURE__*/_jsx(View, {
|
|
92
|
-
style: [styles.row, style],
|
|
120
|
+
style: [isLight ? styles.lightRow : styles.row, style],
|
|
93
121
|
...rest,
|
|
94
122
|
children: children
|
|
95
123
|
})]
|
|
@@ -109,6 +137,13 @@ const styles = StyleSheet.create({
|
|
|
109
137
|
bar: {
|
|
110
138
|
flexDirection: "row"
|
|
111
139
|
},
|
|
140
|
+
// Light mode: a narrower bar (65% of screen width) centered by its wrapper.
|
|
141
|
+
lightWrapper: {
|
|
142
|
+
alignItems: "center"
|
|
143
|
+
},
|
|
144
|
+
lightBar: {
|
|
145
|
+
width: "65%"
|
|
146
|
+
},
|
|
112
147
|
barShadow: {
|
|
113
148
|
shadowColor: "#000",
|
|
114
149
|
shadowOpacity: 0.25,
|
|
@@ -125,6 +160,14 @@ const styles = StyleSheet.create({
|
|
|
125
160
|
alignItems: "center",
|
|
126
161
|
justifyContent: "space-around",
|
|
127
162
|
paddingHorizontal: 6
|
|
163
|
+
},
|
|
164
|
+
// Compact "light" row: tighter horizontal padding around the small icons.
|
|
165
|
+
lightRow: {
|
|
166
|
+
flex: 1,
|
|
167
|
+
flexDirection: "row",
|
|
168
|
+
alignItems: "center",
|
|
169
|
+
justifyContent: "space-around",
|
|
170
|
+
paddingHorizontal: 10
|
|
128
171
|
}
|
|
129
172
|
});
|
|
130
173
|
//# sourceMappingURL=MagicTabBar.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["forwardRef","StyleSheet","View","useSafeAreaInsets","jsx","_jsx","jsxs","_jsxs","glassEffect","require","MIN_BAR_OPACITY","MagicTabBar","theme","variant","isTransparent","transparency","glass","renderBackground","children","style","rest","ref","insets","floating","useGlass","isLiquidGlassAvailable","barOpacity","Math","min","max","seeThrough","pointerEvents","styles","floatingWrapper","dockedWrapper","paddingHorizontal","horizontalMargin","paddingBottom","bottom","bottomInset","bar","barShadow","
|
|
1
|
+
{"version":3,"names":["forwardRef","StyleSheet","View","Animated","LinearTransition","useSafeAreaInsets","jsx","_jsx","jsxs","_jsxs","barTransition","springify","mass","damping","stiffness","BOTTOM_LABEL_EXTRA_HEIGHT","LIGHT_BAR_HEIGHT","LIGHT_EXTRA_BOTTOM_MARGIN","glassEffect","require","MIN_BAR_OPACITY","MagicTabBar","theme","variant","labelPosition","isTransparent","transparency","glass","renderBackground","isLight","lightBottomMargin","children","style","rest","ref","insets","floating","barHeight","height","extraBottomMargin","useGlass","isLiquidGlassAvailable","barOpacity","Math","min","max","seeThrough","pointerEvents","styles","floatingWrapper","dockedWrapper","lightWrapper","paddingHorizontal","horizontalMargin","paddingBottom","bottom","bottomInset","layout","bar","barShadow","lightBar","borderRadius","radius","absoluteFill","overflow","GlassView","glassEffectStyle","tintColor","barColor","backgroundColor","opacity","lightRow","row","create","position","left","right","width","flexDirection","alignItems","shadowColor","shadowOpacity","shadowRadius","shadowOffset","elevation","flex","justifyContent"],"sourceRoot":"../../src","sources":["MagicTabBar.tsx"],"mappings":"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","ignoreList":[]}
|