@peersahab/side-island 0.1.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/LICENSE +23 -0
- package/README.md +403 -0
- package/dist/index.d.mts +146 -0
- package/dist/index.d.ts +146 -0
- package/dist/index.js +536 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +527 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Peersahab
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|
|
23
|
+
|
package/README.md
ADDED
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
# @peersahab/side-island
|
|
2
|
+
|
|
3
|
+
A Skia-powered side island overlay for React Native, with an internal virtualized list (FlatList) and an optional Provider + hooks control layer. Perfect for quick access menus, color pickers, and other side-mounted UI components.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm i @peersahab/side-island
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
### Peer dependencies (required)
|
|
12
|
+
|
|
13
|
+
This library expects these to be installed in your app:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @shopify/react-native-skia react-native-reanimated
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Appearance
|
|
20
|
+
|
|
21
|
+
The `SideIsland` component can be positioned on either side of the screen and displays a smooth, wave-shaped overlay with a virtualized list of items.
|
|
22
|
+
|
|
23
|
+
### Right Position
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
### Left Position
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+
|
|
31
|
+
## Quick Start
|
|
32
|
+
|
|
33
|
+
### Basic Usage
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import React from "react";
|
|
37
|
+
import { View } from "react-native";
|
|
38
|
+
import { SideIsland } from "@peersahab/side-island";
|
|
39
|
+
|
|
40
|
+
export function Example() {
|
|
41
|
+
const items = Array.from({ length: 40 }).map((_, i) => ({ id: String(i) }));
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<View style={{ flex: 1 }}>
|
|
45
|
+
<SideIsland
|
|
46
|
+
items={items}
|
|
47
|
+
keyExtractor={(item) => item.id}
|
|
48
|
+
renderItem={({ item }) => (
|
|
49
|
+
<View style={{ width: 32, height: 32, borderRadius: 16, backgroundColor: "#00000022" }} />
|
|
50
|
+
)}
|
|
51
|
+
/>
|
|
52
|
+
</View>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### With Haptics
|
|
58
|
+
|
|
59
|
+
```tsx
|
|
60
|
+
import React from "react";
|
|
61
|
+
import * as Haptics from "expo-haptics";
|
|
62
|
+
import { SideIsland } from "@peersahab/side-island";
|
|
63
|
+
|
|
64
|
+
export function Example() {
|
|
65
|
+
return (
|
|
66
|
+
<SideIsland
|
|
67
|
+
items={items}
|
|
68
|
+
renderItem={({ item }) => <YourItem item={item} />}
|
|
69
|
+
haptics={{
|
|
70
|
+
onOpen: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
|
|
71
|
+
onClose: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
|
|
72
|
+
onFocusChange: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Rigid),
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### With Backdrop and Focused Item Detail
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import React from "react";
|
|
83
|
+
import { BlurView } from "expo-blur";
|
|
84
|
+
import { useWindowDimensions } from "react-native";
|
|
85
|
+
import { SideIsland } from "@peersahab/side-island";
|
|
86
|
+
|
|
87
|
+
export function Example() {
|
|
88
|
+
const { width, height } = useWindowDimensions();
|
|
89
|
+
const [expanded, setExpanded] = useState(false);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<SideIsland
|
|
93
|
+
expanded={expanded}
|
|
94
|
+
onToggleExpanded={setExpanded}
|
|
95
|
+
items={people}
|
|
96
|
+
renderItem={({ item }) => <AvatarItem person={item} />}
|
|
97
|
+
backdropComponent={
|
|
98
|
+
<BlurView intensity={20} style={{ width, height }} tint="dark" />
|
|
99
|
+
}
|
|
100
|
+
renderFocusedItemDetail={({ item }) => (
|
|
101
|
+
<View>
|
|
102
|
+
<Text>{item.name}</Text>
|
|
103
|
+
<Text>{item.email}</Text>
|
|
104
|
+
</View>
|
|
105
|
+
)}
|
|
106
|
+
/>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Provider + Hook Pattern
|
|
112
|
+
|
|
113
|
+
### Setup Provider
|
|
114
|
+
|
|
115
|
+
Wrap your app once with the provider:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import React from "react";
|
|
119
|
+
import { SideIslandProvider } from "@peersahab/side-island";
|
|
120
|
+
|
|
121
|
+
export function AppRoot() {
|
|
122
|
+
return (
|
|
123
|
+
<SideIslandProvider
|
|
124
|
+
defaultExpanded={false}
|
|
125
|
+
config={{
|
|
126
|
+
position: "right",
|
|
127
|
+
width: 40,
|
|
128
|
+
height: 300,
|
|
129
|
+
}}
|
|
130
|
+
>
|
|
131
|
+
{/* your navigation tree */}
|
|
132
|
+
</SideIslandProvider>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Control from Anywhere
|
|
138
|
+
|
|
139
|
+
Use the hook to control the island from any component:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
import React from "react";
|
|
143
|
+
import { Button } from "react-native";
|
|
144
|
+
import { useSideIsland } from "@peersahab/side-island";
|
|
145
|
+
|
|
146
|
+
export function ToggleIslandButton() {
|
|
147
|
+
const island = useSideIsland();
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
<Button
|
|
151
|
+
title={island.expanded ? "Close" : "Open"}
|
|
152
|
+
onPress={island.toggle}
|
|
153
|
+
/>
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Render Island (uses Provider state)
|
|
159
|
+
|
|
160
|
+
When using the provider, the island automatically uses the provider's state:
|
|
161
|
+
|
|
162
|
+
```tsx
|
|
163
|
+
import React from "react";
|
|
164
|
+
import { SideIsland } from "@peersahab/side-island";
|
|
165
|
+
|
|
166
|
+
export function IslandOverlay() {
|
|
167
|
+
return (
|
|
168
|
+
<SideIsland
|
|
169
|
+
items={items}
|
|
170
|
+
renderItem={({ item }) => <YourItem item={item} />}
|
|
171
|
+
// No need to pass expanded/onToggleExpanded - uses provider state
|
|
172
|
+
/>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## API Reference
|
|
178
|
+
|
|
179
|
+
### `SideIsland<ItemT>`
|
|
180
|
+
|
|
181
|
+
The main component that renders the side island overlay.
|
|
182
|
+
|
|
183
|
+
#### Required Props
|
|
184
|
+
|
|
185
|
+
| Prop | Type | Description |
|
|
186
|
+
|------|------|-------------|
|
|
187
|
+
| `items` | `readonly ItemT[]` | Array of items to display in the island |
|
|
188
|
+
| `renderItem` | `(info: { item: ItemT; index: number }) => React.ReactElement \| null` | Function to render each item |
|
|
189
|
+
|
|
190
|
+
#### Optional Props
|
|
191
|
+
|
|
192
|
+
##### List Configuration
|
|
193
|
+
|
|
194
|
+
| Prop | Type | Default | Description |
|
|
195
|
+
|------|------|---------|-------------|
|
|
196
|
+
| `keyExtractor` | `(item: ItemT, index: number) => string` | `(_, index) => String(index)` | Function to extract unique keys for items |
|
|
197
|
+
| `listProps` | `Omit<FlatListProps<ItemT>, "data" \| "renderItem" \| "keyExtractor">` | - | Additional props to pass to the internal FlatList |
|
|
198
|
+
| `onFocusedItemChange` | `(info: { item: ItemT; index: number } \| null) => void` | - | Called when the focused item changes (item closest to center) |
|
|
199
|
+
|
|
200
|
+
##### Position & Layout
|
|
201
|
+
|
|
202
|
+
| Prop | Type | Default | Description |
|
|
203
|
+
|------|------|---------|-------------|
|
|
204
|
+
| `position` | `"left" \| "right"` | `"right"` | Which side of the screen the island is pinned to |
|
|
205
|
+
| `width` | `number` | `40` | Width of the island in pixels |
|
|
206
|
+
| `height` | `number` | `250` | Height of the island in pixels |
|
|
207
|
+
| `topOffset` | `number` | `0` | Vertical offset from center in pixels (positive = down, negative = up) |
|
|
208
|
+
| `style` | `ViewStyle` | - | Additional styles for the island container |
|
|
209
|
+
|
|
210
|
+
##### Wave Shape (Advanced)
|
|
211
|
+
|
|
212
|
+
| Prop | Type | Default | Description |
|
|
213
|
+
|------|------|---------|-------------|
|
|
214
|
+
| `waveAmplitude` | `number` | `18` | Amplitude of the wave bulge (controls how much it extends) |
|
|
215
|
+
| `waveY1` | `number` | `0.1` | Top wave peak position (0-1, relative to height) |
|
|
216
|
+
| `waveY2` | `number` | `0.9` | Bottom wave peak position (0-1, relative to height) |
|
|
217
|
+
|
|
218
|
+
##### Appearance
|
|
219
|
+
|
|
220
|
+
| Prop | Type | Default | Description |
|
|
221
|
+
|------|------|---------|-------------|
|
|
222
|
+
| `backgroundColor` | `string` | `"#000000"` | Background color of the island |
|
|
223
|
+
| `handleWidth` | `number` | `16` | Width of the touchable handle area (0 to disable) |
|
|
224
|
+
|
|
225
|
+
##### State Management
|
|
226
|
+
|
|
227
|
+
| Prop | Type | Default | Description |
|
|
228
|
+
|------|------|---------|-------------|
|
|
229
|
+
| `expanded` | `boolean` | - | Controlled expanded state (if provided, component is controlled) |
|
|
230
|
+
| `onToggleExpanded` | `(next: boolean) => void` | - | Callback when expanded state should change (required if `expanded` is provided) |
|
|
231
|
+
| `defaultExpanded` | `boolean` | `false` | Initial expanded state (only used if uncontrolled) |
|
|
232
|
+
|
|
233
|
+
##### Interaction
|
|
234
|
+
|
|
235
|
+
| Prop | Type | Default | Description |
|
|
236
|
+
|------|------|---------|-------------|
|
|
237
|
+
| `onPress` | `() => void` | - | Fired when the handle area is pressed (island still toggles automatically) |
|
|
238
|
+
| `haptics` | `SideIslandHaptics` | - | Haptics adapter for feedback (see Haptics section below) |
|
|
239
|
+
|
|
240
|
+
##### Advanced Features
|
|
241
|
+
|
|
242
|
+
| Prop | Type | Default | Description |
|
|
243
|
+
|------|------|---------|-------------|
|
|
244
|
+
| `backdropComponent` | `React.ReactElement` | - | Component to render as backdrop (fades in when expanded) |
|
|
245
|
+
| `renderFocusedItemDetail` | `(info: { item: ItemT; index: number; expanded: boolean; setExpanded: (next: boolean) => void }) => React.ReactElement \| null` | - | Component to render details of the focused item (displayed opposite the island) |
|
|
246
|
+
| `focusedItemDetailGap` | `number` | `16` | Horizontal gap between focused item detail and island |
|
|
247
|
+
|
|
248
|
+
### `SideIslandProvider`
|
|
249
|
+
|
|
250
|
+
Provider component for managing island state globally.
|
|
251
|
+
|
|
252
|
+
#### Props
|
|
253
|
+
|
|
254
|
+
| Prop | Type | Default | Description |
|
|
255
|
+
|------|------|---------|-------------|
|
|
256
|
+
| `children` | `React.ReactNode` | - | Child components |
|
|
257
|
+
| `defaultExpanded` | `boolean` | `false` | Initial expanded state |
|
|
258
|
+
| `onExpandedChange` | `(next: boolean) => void` | - | Callback when expanded state changes |
|
|
259
|
+
| `config` | `SideIslandConfig` | - | Default configuration for all islands (see `SideIslandConfig` below) |
|
|
260
|
+
| `value` | `{ expanded: boolean; setExpanded: (next: boolean) => void; config?: SideIslandConfig }` | - | External state control (for advanced use cases) |
|
|
261
|
+
|
|
262
|
+
### `useSideIsland()`
|
|
263
|
+
|
|
264
|
+
Hook to access and control the island state from the provider.
|
|
265
|
+
|
|
266
|
+
#### Returns
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
{
|
|
270
|
+
expanded: boolean;
|
|
271
|
+
setExpanded: (next: boolean) => void;
|
|
272
|
+
open: () => void;
|
|
273
|
+
close: () => void;
|
|
274
|
+
toggle: () => void;
|
|
275
|
+
config: SideIslandConfig;
|
|
276
|
+
}
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### `SideIslandConfig`
|
|
280
|
+
|
|
281
|
+
Configuration object for default island settings (used in Provider or individual islands).
|
|
282
|
+
|
|
283
|
+
| Property | Type | Default | Description |
|
|
284
|
+
|----------|------|---------|-------------|
|
|
285
|
+
| `position` | `"left" \| "right"` | `"right"` | Default position |
|
|
286
|
+
| `width` | `number` | `40` | Default width |
|
|
287
|
+
| `height` | `number` | `250` | Default height |
|
|
288
|
+
| `waveAmplitude` | `number` | `18` | Default wave amplitude |
|
|
289
|
+
| `waveY1` | `number` | `0.1` | Default top wave position |
|
|
290
|
+
| `waveY2` | `number` | `0.9` | Default bottom wave position |
|
|
291
|
+
| `backgroundColor` | `string` | `"#000000"` | Default background color |
|
|
292
|
+
| `handleWidth` | `number` | `16` | Default handle width |
|
|
293
|
+
| `topOffset` | `number` | `0` | Default top offset |
|
|
294
|
+
| `haptics` | `SideIslandHaptics` | - | Default haptics adapter |
|
|
295
|
+
|
|
296
|
+
### `SideIslandHaptics`
|
|
297
|
+
|
|
298
|
+
Haptics adapter interface for providing haptic feedback.
|
|
299
|
+
|
|
300
|
+
| Property | Type | Description |
|
|
301
|
+
|----------|------|-------------|
|
|
302
|
+
| `onOpen` | `() => void \| Promise<void>` | Called when island opens |
|
|
303
|
+
| `onClose` | `() => void \| Promise<void>` | Called when island closes |
|
|
304
|
+
| `onFocusChange` | `(info: { index: number } \| null) => void \| Promise<void>` | Called when focused item changes |
|
|
305
|
+
|
|
306
|
+
**Example with expo-haptics:**
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
import * as Haptics from "expo-haptics";
|
|
310
|
+
|
|
311
|
+
<SideIsland
|
|
312
|
+
haptics={{
|
|
313
|
+
onOpen: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
|
|
314
|
+
onClose: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light),
|
|
315
|
+
onFocusChange: () => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Rigid),
|
|
316
|
+
}}
|
|
317
|
+
// ... other props
|
|
318
|
+
/>
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Running the Example App
|
|
322
|
+
|
|
323
|
+
The repository includes an example Expo app demonstrating various use cases.
|
|
324
|
+
|
|
325
|
+
### Prerequisites
|
|
326
|
+
|
|
327
|
+
- Node.js (v18 or later)
|
|
328
|
+
- npm or yarn
|
|
329
|
+
- Expo CLI (optional, but recommended): `npm install -g expo-cli`
|
|
330
|
+
|
|
331
|
+
### Setup
|
|
332
|
+
|
|
333
|
+
1. **Install dependencies for the main package:**
|
|
334
|
+
```bash
|
|
335
|
+
npm install
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
2. **Build the package:**
|
|
339
|
+
```bash
|
|
340
|
+
npm run build
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
3. **Navigate to the example app:**
|
|
344
|
+
```bash
|
|
345
|
+
cd apps/example
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
4. **Install example app dependencies:**
|
|
349
|
+
```bash
|
|
350
|
+
npm install
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
### Running
|
|
354
|
+
|
|
355
|
+
**Start the Expo development server:**
|
|
356
|
+
```bash
|
|
357
|
+
npm start
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
This will open the Expo DevTools. From there you can:
|
|
361
|
+
|
|
362
|
+
- Press `i` to open iOS simulator (requires Xcode on macOS)
|
|
363
|
+
- Press `a` to open Android emulator (requires Android Studio)
|
|
364
|
+
- Scan the QR code with Expo Go app on your physical device
|
|
365
|
+
- Press `w` to open in web browser
|
|
366
|
+
|
|
367
|
+
**Or use the specific platform commands:**
|
|
368
|
+
```bash
|
|
369
|
+
npm run ios # iOS simulator
|
|
370
|
+
npm run android # Android emulator
|
|
371
|
+
npm run web # Web browser
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Example App Features
|
|
375
|
+
|
|
376
|
+
The example app demonstrates:
|
|
377
|
+
- People picker with avatar items
|
|
378
|
+
- Color picker with label colors
|
|
379
|
+
- Focused item detail views
|
|
380
|
+
- Backdrop blur effects
|
|
381
|
+
- Haptic feedback integration
|
|
382
|
+
- Multiple islands (left and right positioned)
|
|
383
|
+
|
|
384
|
+
## TypeScript
|
|
385
|
+
|
|
386
|
+
The library is written in TypeScript and includes full type definitions. All exports are properly typed:
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
import {
|
|
390
|
+
SideIsland,
|
|
391
|
+
SideIslandProvider,
|
|
392
|
+
useSideIsland,
|
|
393
|
+
type SideIslandProps,
|
|
394
|
+
type SideIslandConfig,
|
|
395
|
+
type SideIslandController,
|
|
396
|
+
type SideIslandPosition,
|
|
397
|
+
type SideIslandHaptics,
|
|
398
|
+
} from "@peersahab/side-island";
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
## License
|
|
402
|
+
|
|
403
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { FlatListProps, ViewStyle } from 'react-native';
|
|
3
|
+
|
|
4
|
+
type TeamMember = {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
avatar?: string;
|
|
8
|
+
role: string;
|
|
9
|
+
status?: "active" | "inactive";
|
|
10
|
+
};
|
|
11
|
+
type SideIslandHaptics = {
|
|
12
|
+
/**
|
|
13
|
+
* Called when the island opens (expanded becomes true).
|
|
14
|
+
* Implement this using your own haptics library (e.g. expo-haptics).
|
|
15
|
+
*/
|
|
16
|
+
onOpen?: () => void | Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Called when the island closes (expanded becomes false).
|
|
19
|
+
* Implement this using your own haptics library (e.g. expo-haptics).
|
|
20
|
+
*/
|
|
21
|
+
onClose?: () => void | Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Called when the focused item changes while scrolling.
|
|
24
|
+
* Use this to trigger a "rigid" (or other) haptic in your app.
|
|
25
|
+
*/
|
|
26
|
+
onFocusChange?: (info: {
|
|
27
|
+
index: number;
|
|
28
|
+
} | null) => void | Promise<void>;
|
|
29
|
+
};
|
|
30
|
+
type SideIslandPosition = "left" | "right";
|
|
31
|
+
type SideIslandConfig = {
|
|
32
|
+
/**
|
|
33
|
+
* Which side of the screen the island is pinned to.
|
|
34
|
+
* Default: "right"
|
|
35
|
+
*/
|
|
36
|
+
position?: SideIslandPosition;
|
|
37
|
+
width?: number;
|
|
38
|
+
height?: number;
|
|
39
|
+
waveAmplitude?: number;
|
|
40
|
+
waveY1?: number;
|
|
41
|
+
waveY2?: number;
|
|
42
|
+
backgroundColor?: string;
|
|
43
|
+
handleWidth?: number;
|
|
44
|
+
topOffset?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Optional haptics adapter. If provided, it will be used to trigger haptic feedback
|
|
47
|
+
* on island open/close without adding a hard dependency to any haptics library.
|
|
48
|
+
*/
|
|
49
|
+
haptics?: SideIslandHaptics;
|
|
50
|
+
};
|
|
51
|
+
type SideIslandController = {
|
|
52
|
+
expanded: boolean;
|
|
53
|
+
setExpanded: (next: boolean) => void;
|
|
54
|
+
open: () => void;
|
|
55
|
+
close: () => void;
|
|
56
|
+
toggle: () => void;
|
|
57
|
+
config: SideIslandConfig;
|
|
58
|
+
};
|
|
59
|
+
type SideIslandProviderProps = {
|
|
60
|
+
children: React.ReactNode;
|
|
61
|
+
defaultExpanded?: boolean;
|
|
62
|
+
onExpandedChange?: (next: boolean) => void;
|
|
63
|
+
config?: SideIslandConfig;
|
|
64
|
+
value?: {
|
|
65
|
+
expanded: boolean;
|
|
66
|
+
setExpanded: (next: boolean) => void;
|
|
67
|
+
config?: SideIslandConfig;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
70
|
+
type SideIslandProps<ItemT> = {
|
|
71
|
+
items: readonly ItemT[];
|
|
72
|
+
renderItem: (info: {
|
|
73
|
+
item: ItemT;
|
|
74
|
+
index: number;
|
|
75
|
+
}) => React.ReactElement | null;
|
|
76
|
+
keyExtractor?: (item: ItemT, index: number) => string;
|
|
77
|
+
listProps?: Omit<FlatListProps<ItemT>, "data" | "renderItem" | "keyExtractor">;
|
|
78
|
+
/**
|
|
79
|
+
* Called whenever the "focused" item changes as the user scrolls.
|
|
80
|
+
* Focus is determined by the item closest to the vertical center of the island.
|
|
81
|
+
* On first open, the island scrolls to focus the first item (index 0) centered.
|
|
82
|
+
* On subsequent opens, the island scrolls back to the last focused item.
|
|
83
|
+
*/
|
|
84
|
+
onFocusedItemChange?: (info: {
|
|
85
|
+
item: ItemT;
|
|
86
|
+
index: number;
|
|
87
|
+
} | null) => void;
|
|
88
|
+
/**
|
|
89
|
+
* Which side of the screen the island is pinned to.
|
|
90
|
+
* Default: "right"
|
|
91
|
+
*/
|
|
92
|
+
position?: SideIslandPosition;
|
|
93
|
+
width?: number;
|
|
94
|
+
height?: number;
|
|
95
|
+
waveAmplitude?: number;
|
|
96
|
+
waveY1?: number;
|
|
97
|
+
waveY2?: number;
|
|
98
|
+
backgroundColor?: string;
|
|
99
|
+
handleWidth?: number;
|
|
100
|
+
topOffset?: number;
|
|
101
|
+
/**
|
|
102
|
+
* Optional haptics adapter. If provided, it will be used to trigger haptic feedback
|
|
103
|
+
* on island open/close without adding a hard dependency to any haptics library.
|
|
104
|
+
*/
|
|
105
|
+
haptics?: SideIslandHaptics;
|
|
106
|
+
style?: ViewStyle;
|
|
107
|
+
expanded?: boolean;
|
|
108
|
+
onToggleExpanded?: (next: boolean) => void;
|
|
109
|
+
defaultExpanded?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Fired when the handle area is pressed.
|
|
112
|
+
* The island will still toggle expansion via controlled/provider/internal state.
|
|
113
|
+
*/
|
|
114
|
+
onPress?: () => void;
|
|
115
|
+
/**
|
|
116
|
+
* Optional backdrop component that will fade into view when the island expands.
|
|
117
|
+
* Should cover the full screen and be positioned behind the island.
|
|
118
|
+
*/
|
|
119
|
+
backdropComponent?: React.ReactElement;
|
|
120
|
+
/**
|
|
121
|
+
* Optional component to render details of the currently focused item.
|
|
122
|
+
* Displayed on top of the backdrop, opposite of the island:
|
|
123
|
+
* - position="right" => detail is to the left of the island
|
|
124
|
+
* - position="left" => detail is to the right of the island
|
|
125
|
+
* Receives the focused item info and can interact with the island.
|
|
126
|
+
*/
|
|
127
|
+
renderFocusedItemDetail?: (info: {
|
|
128
|
+
item: ItemT;
|
|
129
|
+
index: number;
|
|
130
|
+
expanded: boolean;
|
|
131
|
+
setExpanded: (next: boolean) => void;
|
|
132
|
+
}) => React.ReactElement | null;
|
|
133
|
+
/**
|
|
134
|
+
* Horizontal gap between the focused item detail component and the island.
|
|
135
|
+
* Default: 16
|
|
136
|
+
*/
|
|
137
|
+
focusedItemDetailGap?: number;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
declare function SideIsland<ItemT>({ items, renderItem, keyExtractor, listProps, onFocusedItemChange, position, width, height, waveAmplitude, waveY1, waveY2, backgroundColor, handleWidth, topOffset, haptics, style, expanded, onToggleExpanded, defaultExpanded, onPress, backdropComponent, renderFocusedItemDetail, focusedItemDetailGap, }: SideIslandProps<ItemT>): React.JSX.Element;
|
|
141
|
+
|
|
142
|
+
declare function SideIslandProvider({ children, defaultExpanded, onExpandedChange, config, value, }: SideIslandProviderProps): React.JSX.Element;
|
|
143
|
+
|
|
144
|
+
declare function useSideIsland(): SideIslandController;
|
|
145
|
+
|
|
146
|
+
export { SideIsland, type SideIslandConfig, type SideIslandController, type SideIslandPosition, type SideIslandProps, SideIslandProvider, type SideIslandProviderProps, type TeamMember, useSideIsland };
|