@retor/react-native 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,11 +6,11 @@ Embed Retor 3D experiences in a React Native / Expo app — with composable bott
6
6
 
7
7
  ```bash
8
8
  # Expo
9
- npx expo install react-native-webview react-native-gesture-handler react-native-reanimated
10
- npm install @gorhom/bottom-sheet @retor/react-native
9
+ npx expo install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg
10
+ npm install @gorhom/bottom-sheet lucide-react-native @retor/react-native
11
11
 
12
12
  # bare React Native
13
- npm install react-native-webview react-native-gesture-handler react-native-reanimated @gorhom/bottom-sheet @retor/react-native
13
+ npm install react-native-webview react-native-gesture-handler react-native-reanimated react-native-svg @gorhom/bottom-sheet lucide-react-native @retor/react-native
14
14
  cd ios && pod install
15
15
  ```
16
16
 
package/dist/index.d.mts CHANGED
@@ -185,71 +185,43 @@ interface HudProps {
185
185
  declare function Hud({ children }: HudProps): React.JSX.Element;
186
186
 
187
187
  interface ProjectSheetProps {
188
- /**
189
- * Snap points for the sheet. Defaults to ["20%", "60%"].
190
- */
188
+ /** Snap points. Defaults to ["35%", "85%"]. */
191
189
  snapPoints?: (string | number)[];
192
- /**
193
- * Override the default header (project name + description).
194
- * Receives the project metadata.
195
- */
190
+ /** Override the default header (project name + description + arrow button). */
196
191
  renderHeader?: (project: {
197
192
  name?: string;
198
193
  description?: string;
199
194
  }) => React.ReactNode;
200
- /**
201
- * Children to render below the header. Typically a `<LinesCarousel>`.
202
- * If omitted, a default LinesCarousel is rendered.
203
- */
195
+ /** Children to render inside. Defaults to a built-in `<LinesCarousel>`. */
204
196
  children?: React.ReactNode;
205
197
  }
206
198
  /**
207
- * The browse-mode bottom sheet. Auto-presents whenever no line is open.
208
- *
209
- * ```tsx
210
- * <ProjectSheet>
211
- * <LinesCarousel>
212
- * {(line) => <MyLineCard line={line} />}
213
- * </LinesCarousel>
214
- * </ProjectSheet>
215
- * ```
199
+ * Browse-mode bottom sheet — auto-presents whenever no line is open.
216
200
  */
217
201
  declare function ProjectSheet({ snapPoints, renderHeader, children }: ProjectSheetProps): React.JSX.Element;
218
202
  interface LinesCarouselProps {
219
203
  children: (line: RetorLine, index: number) => React.ReactNode;
220
- /** Optional gap between items. Defaults to 12. */
221
204
  gap?: number;
222
- /** Optional horizontal padding. Defaults to 16. */
223
205
  paddingHorizontal?: number;
224
206
  }
225
- /**
226
- * Renders a horizontally scrolling list of lines using a render prop.
227
- * Place inside `<ProjectSheet>`.
228
- *
229
- * Pressing an item is up to your render — use `useViewer().openLine(line._id)`.
230
- */
231
207
  declare function LinesCarousel({ children, gap, paddingHorizontal }: LinesCarouselProps): React.JSX.Element | null;
232
208
 
233
209
  interface LineDetailSheetProps {
234
- /** Snap points. Defaults to ["25%", "75%"]. */
210
+ /** Snap points. Defaults to ["35%", "85%"]. */
235
211
  snapPoints?: (string | number)[];
236
- /** Override the header. Receives the active line. */
212
+ /** Override the header. */
237
213
  renderHeader?: (line: RetorLine) => React.ReactNode;
238
- /** Custom content (typically a `<LineTagList>`). Defaults to a built-in tag list. */
214
+ /** Custom content (typically a `<LineTagList>`). */
239
215
  children?: React.ReactNode;
240
216
  }
241
217
  /**
242
- * Sheet that auto-presents when the user enters a line. Shows the line's
243
- * tags and a Done button. Pass `<LineTagList>` as a child to customise items.
218
+ * Sheet shown when a line is open. Includes a default header with the
219
+ * autoplay button + minimize arrow, the tag list, and a Done footer.
244
220
  */
245
221
  declare function LineDetailSheet({ snapPoints, renderHeader, children }: LineDetailSheetProps): React.JSX.Element;
246
222
  interface LineTagListProps {
247
223
  children: (tag: RetorTag, isActive: boolean) => React.ReactNode;
248
224
  }
249
- /**
250
- * Renders the tags of the active line as a vertical list. Re-renders the
251
- * active item when the camera scrolls to it. Empty when no line is open.
252
- */
253
225
  declare function LineTagList({ children }: LineTagListProps): React.JSX.Element | null;
254
226
 
255
227
  interface AddNoteSheetProps {
package/dist/index.d.ts CHANGED
@@ -185,71 +185,43 @@ interface HudProps {
185
185
  declare function Hud({ children }: HudProps): React.JSX.Element;
186
186
 
187
187
  interface ProjectSheetProps {
188
- /**
189
- * Snap points for the sheet. Defaults to ["20%", "60%"].
190
- */
188
+ /** Snap points. Defaults to ["35%", "85%"]. */
191
189
  snapPoints?: (string | number)[];
192
- /**
193
- * Override the default header (project name + description).
194
- * Receives the project metadata.
195
- */
190
+ /** Override the default header (project name + description + arrow button). */
196
191
  renderHeader?: (project: {
197
192
  name?: string;
198
193
  description?: string;
199
194
  }) => React.ReactNode;
200
- /**
201
- * Children to render below the header. Typically a `<LinesCarousel>`.
202
- * If omitted, a default LinesCarousel is rendered.
203
- */
195
+ /** Children to render inside. Defaults to a built-in `<LinesCarousel>`. */
204
196
  children?: React.ReactNode;
205
197
  }
206
198
  /**
207
- * The browse-mode bottom sheet. Auto-presents whenever no line is open.
208
- *
209
- * ```tsx
210
- * <ProjectSheet>
211
- * <LinesCarousel>
212
- * {(line) => <MyLineCard line={line} />}
213
- * </LinesCarousel>
214
- * </ProjectSheet>
215
- * ```
199
+ * Browse-mode bottom sheet — auto-presents whenever no line is open.
216
200
  */
217
201
  declare function ProjectSheet({ snapPoints, renderHeader, children }: ProjectSheetProps): React.JSX.Element;
218
202
  interface LinesCarouselProps {
219
203
  children: (line: RetorLine, index: number) => React.ReactNode;
220
- /** Optional gap between items. Defaults to 12. */
221
204
  gap?: number;
222
- /** Optional horizontal padding. Defaults to 16. */
223
205
  paddingHorizontal?: number;
224
206
  }
225
- /**
226
- * Renders a horizontally scrolling list of lines using a render prop.
227
- * Place inside `<ProjectSheet>`.
228
- *
229
- * Pressing an item is up to your render — use `useViewer().openLine(line._id)`.
230
- */
231
207
  declare function LinesCarousel({ children, gap, paddingHorizontal }: LinesCarouselProps): React.JSX.Element | null;
232
208
 
233
209
  interface LineDetailSheetProps {
234
- /** Snap points. Defaults to ["25%", "75%"]. */
210
+ /** Snap points. Defaults to ["35%", "85%"]. */
235
211
  snapPoints?: (string | number)[];
236
- /** Override the header. Receives the active line. */
212
+ /** Override the header. */
237
213
  renderHeader?: (line: RetorLine) => React.ReactNode;
238
- /** Custom content (typically a `<LineTagList>`). Defaults to a built-in tag list. */
214
+ /** Custom content (typically a `<LineTagList>`). */
239
215
  children?: React.ReactNode;
240
216
  }
241
217
  /**
242
- * Sheet that auto-presents when the user enters a line. Shows the line's
243
- * tags and a Done button. Pass `<LineTagList>` as a child to customise items.
218
+ * Sheet shown when a line is open. Includes a default header with the
219
+ * autoplay button + minimize arrow, the tag list, and a Done footer.
244
220
  */
245
221
  declare function LineDetailSheet({ snapPoints, renderHeader, children }: LineDetailSheetProps): React.JSX.Element;
246
222
  interface LineTagListProps {
247
223
  children: (tag: RetorTag, isActive: boolean) => React.ReactNode;
248
224
  }
249
- /**
250
- * Renders the tags of the active line as a vertical list. Re-renders the
251
- * active item when the camera scrolls to it. Empty when no line is open.
252
- */
253
225
  declare function LineTagList({ children }: LineTagListProps): React.JSX.Element | null;
254
226
 
255
227
  interface AddNoteSheetProps {
package/dist/index.js CHANGED
@@ -359,17 +359,30 @@ function Hud({ children }) {
359
359
  var import_react4 = __toESM(require("react"));
360
360
  var import_react_native3 = require("react-native");
361
361
  var import_bottom_sheet2 = require("@gorhom/bottom-sheet");
362
- function ProjectSheet({ snapPoints = ["20%", "60%"], renderHeader, children }) {
362
+ var import_lucide_react_native = require("lucide-react-native");
363
+ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
363
364
  const { project, activeLineId, isAddNoteOpen } = useRetorBridge();
364
365
  const sheetRef = (0, import_react4.useRef)(null);
365
366
  const snapPointsArr = (0, import_react4.useMemo)(() => snapPoints, [snapPoints]);
367
+ const [minimized, setMinimized] = (0, import_react4.useState)(false);
366
368
  (0, import_react4.useEffect)(() => {
367
369
  if (activeLineId || isAddNoteOpen) {
368
370
  sheetRef.current?.dismiss();
369
371
  } else {
370
372
  sheetRef.current?.present();
373
+ setMinimized(false);
371
374
  }
372
375
  }, [activeLineId, isAddNoteOpen]);
376
+ const handleSheetChange = (index) => {
377
+ setMinimized(index === 0);
378
+ };
379
+ const toggleMinimize = () => {
380
+ if (minimized) {
381
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
382
+ } else {
383
+ sheetRef.current?.snapToIndex(0);
384
+ }
385
+ };
373
386
  return /* @__PURE__ */ import_react4.default.createElement(
374
387
  import_bottom_sheet2.BottomSheetModal,
375
388
  {
@@ -377,6 +390,7 @@ function ProjectSheet({ snapPoints = ["20%", "60%"], renderHeader, children }) {
377
390
  snapPoints: snapPointsArr,
378
391
  enablePanDownToClose: false,
379
392
  enableDismissOnClose: false,
393
+ onChange: handleSheetChange,
380
394
  backdropComponent: (props) => /* @__PURE__ */ import_react4.default.createElement(
381
395
  import_bottom_sheet2.BottomSheetBackdrop,
382
396
  {
@@ -390,7 +404,7 @@ function ProjectSheet({ snapPoints = ["20%", "60%"], renderHeader, children }) {
390
404
  handleIndicatorStyle: styles2.handle,
391
405
  backgroundStyle: styles2.background
392
406
  },
393
- /* @__PURE__ */ import_react4.default.createElement(import_bottom_sheet2.BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: styles2.header }, project?.name && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.title }, project.name), project?.description && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), children ?? /* @__PURE__ */ import_react4.default.createElement(DefaultLinesCarousel, null))
407
+ /* @__PURE__ */ import_react4.default.createElement(import_bottom_sheet2.BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: styles2.header }, /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: { flex: 1, minWidth: 0 } }, project?.name && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.title, numberOfLines: 1 }, project.name), project?.description && /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Pressable, { style: styles2.iconBtn, onPress: toggleMinimize }, minimized ? /* @__PURE__ */ import_react4.default.createElement(import_lucide_react_native.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react4.default.createElement(import_lucide_react_native.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))), children ?? /* @__PURE__ */ import_react4.default.createElement(DefaultLinesCarousel, null))
394
408
  );
395
409
  }
396
410
  function DefaultLinesCarousel() {
@@ -399,16 +413,15 @@ function DefaultLinesCarousel() {
399
413
  function LinesCarousel({ children, gap = 12, paddingHorizontal = 16 }) {
400
414
  const { lines } = useRetorBridge();
401
415
  if (lines.length === 0) return null;
402
- return /* @__PURE__ */ import_react4.default.createElement(
416
+ return /* @__PURE__ */ import_react4.default.createElement(import_react_native3.View, { style: styles2.carouselWrap }, /* @__PURE__ */ import_react4.default.createElement(import_react_native3.Text, { style: styles2.carouselLabel }, "Routes"), /* @__PURE__ */ import_react4.default.createElement(
403
417
  import_react_native3.ScrollView,
404
418
  {
405
419
  horizontal: true,
406
420
  showsHorizontalScrollIndicator: false,
407
- contentContainerStyle: { paddingHorizontal, gap },
408
- style: styles2.carousel
421
+ contentContainerStyle: { paddingHorizontal, gap }
409
422
  },
410
423
  lines.map((line, idx) => /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, { key: line._id }, children(line, idx)))
411
- );
424
+ ));
412
425
  }
413
426
  function DefaultLineCard({ line }) {
414
427
  const { controls } = useRetorBridge();
@@ -426,10 +439,33 @@ var styles2 = import_react_native3.StyleSheet.create({
426
439
  background: { backgroundColor: "rgba(20,20,20,0.95)" },
427
440
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
428
441
  content: { flex: 1, paddingTop: 12, paddingBottom: 24 },
429
- header: { paddingHorizontal: 24, paddingBottom: 16 },
442
+ header: {
443
+ flexDirection: "row",
444
+ alignItems: "flex-start",
445
+ paddingHorizontal: 24,
446
+ paddingBottom: 16,
447
+ gap: 8
448
+ },
430
449
  title: { color: "white", fontSize: 18, fontWeight: "600", lineHeight: 22 },
431
450
  subtitle: { color: "rgba(255,255,255,0.6)", fontSize: 13, marginTop: 4, lineHeight: 18 },
432
- carousel: { marginTop: 8 },
451
+ iconBtn: {
452
+ width: 28,
453
+ height: 28,
454
+ borderRadius: 14,
455
+ backgroundColor: "rgba(255,255,255,0.1)",
456
+ alignItems: "center",
457
+ justifyContent: "center"
458
+ },
459
+ carouselWrap: { marginTop: 8 },
460
+ carouselLabel: {
461
+ color: "rgba(255,255,255,0.4)",
462
+ fontSize: 10,
463
+ textTransform: "uppercase",
464
+ letterSpacing: 1,
465
+ fontWeight: "500",
466
+ paddingHorizontal: 24,
467
+ marginBottom: 8
468
+ },
433
469
  lineCard: {
434
470
  width: 220,
435
471
  backgroundColor: "rgba(255,255,255,0.06)",
@@ -444,17 +480,31 @@ var styles2 = import_react_native3.StyleSheet.create({
444
480
  var import_react5 = __toESM(require("react"));
445
481
  var import_react_native4 = require("react-native");
446
482
  var import_bottom_sheet3 = require("@gorhom/bottom-sheet");
447
- function LineDetailSheet({ snapPoints = ["25%", "75%"], renderHeader, children }) {
483
+ var import_react_native_svg = __toESM(require("react-native-svg"));
484
+ var import_lucide_react_native2 = require("lucide-react-native");
485
+ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
448
486
  const { activeLine, isAddNoteOpen, controls } = useRetorBridge();
449
487
  const sheetRef = (0, import_react5.useRef)(null);
450
488
  const snapPointsArr = (0, import_react5.useMemo)(() => snapPoints, [snapPoints]);
489
+ const [minimized, setMinimized] = (0, import_react5.useState)(false);
451
490
  (0, import_react5.useEffect)(() => {
452
491
  if (activeLine && !isAddNoteOpen) {
453
492
  sheetRef.current?.present();
493
+ setMinimized(false);
454
494
  } else {
455
495
  sheetRef.current?.dismiss();
456
496
  }
457
497
  }, [activeLine, isAddNoteOpen]);
498
+ const handleSheetChange = (index) => {
499
+ setMinimized(index === 0);
500
+ };
501
+ const toggleMinimize = () => {
502
+ if (minimized) {
503
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
504
+ } else {
505
+ sheetRef.current?.snapToIndex(0);
506
+ }
507
+ };
458
508
  return /* @__PURE__ */ import_react5.default.createElement(
459
509
  import_bottom_sheet3.BottomSheetModal,
460
510
  {
@@ -462,6 +512,7 @@ function LineDetailSheet({ snapPoints = ["25%", "75%"], renderHeader, children }
462
512
  snapPoints: snapPointsArr,
463
513
  enablePanDownToClose: false,
464
514
  enableDismissOnClose: false,
515
+ onChange: handleSheetChange,
465
516
  backdropComponent: (props) => /* @__PURE__ */ import_react5.default.createElement(
466
517
  import_bottom_sheet3.BottomSheetBackdrop,
467
518
  {
@@ -475,60 +526,153 @@ function LineDetailSheet({ snapPoints = ["25%", "75%"], renderHeader, children }
475
526
  handleIndicatorStyle: styles3.handle,
476
527
  backgroundStyle: styles3.background
477
528
  },
478
- /* @__PURE__ */ import_react5.default.createElement(import_bottom_sheet3.BottomSheetView, { style: styles3.content }, activeLine && (renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.header }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.title }, activeLine.name), activeLine.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.subtitle }, activeLine.subtitle), activeLine.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.description, numberOfLines: 4 }, activeLine.description))), children ?? /* @__PURE__ */ import_react5.default.createElement(DefaultLineTagList, null), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.footer }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.doneText }, "Done"))))
529
+ activeLine && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.root }, renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ import_react5.default.createElement(
530
+ DefaultHeader,
531
+ {
532
+ line: activeLine,
533
+ minimized,
534
+ onToggleMinimize: toggleMinimize
535
+ }
536
+ ), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.listContainer }, children ?? /* @__PURE__ */ import_react5.default.createElement(DefaultLineTagList, null)), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.footer }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.doneText }, "Done"))))
479
537
  );
480
538
  }
481
- function DefaultLineTagList() {
482
- return /* @__PURE__ */ import_react5.default.createElement(LineTagList, null, (tag, isActive) => /* @__PURE__ */ import_react5.default.createElement(DefaultTagItem, { tag, isActive }));
539
+ function DefaultHeader({
540
+ line,
541
+ minimized,
542
+ onToggleMinimize
543
+ }) {
544
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.header }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: styles3.headerActions }, /* @__PURE__ */ import_react5.default.createElement(AutoplayButton, null), /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))));
545
+ }
546
+ function AutoplayButton() {
547
+ const { isPlaying, progress, controls } = useRetorBridge();
548
+ const r = 12.5;
549
+ const c = 2 * Math.PI * r;
550
+ return /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Pressable, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ import_react5.default.createElement(import_react_native_svg.default, { width: 28, height: 28, style: import_react_native4.StyleSheet.absoluteFill }, /* @__PURE__ */ import_react5.default.createElement(
551
+ import_react_native_svg.Circle,
552
+ {
553
+ cx: 14,
554
+ cy: 14,
555
+ r,
556
+ fill: "none",
557
+ stroke: "white",
558
+ strokeWidth: 2,
559
+ strokeDasharray: `${c}`,
560
+ strokeDashoffset: c * (1 - progress),
561
+ strokeLinecap: "round",
562
+ transform: "rotate(-90, 14, 14)"
563
+ }
564
+ )), isPlaying ? /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
483
565
  }
484
566
  function LineTagList({ children }) {
485
567
  const { activeLine, closestTagId } = useRetorBridge();
568
+ const listRef = (0, import_react5.useRef)(null);
486
569
  const tags = (0, import_react5.useMemo)(
487
570
  () => (activeLine?.tags ?? []).filter((t) => t.name && t.name.trim().length > 0),
488
571
  [activeLine]
489
572
  );
573
+ (0, import_react5.useEffect)(() => {
574
+ if (!closestTagId) return;
575
+ const index = tags.findIndex((t) => t._id === closestTagId);
576
+ if (index < 0) return;
577
+ requestAnimationFrame(() => {
578
+ try {
579
+ listRef.current?.scrollToIndex({ index, animated: true, viewPosition: 0 });
580
+ } catch {
581
+ }
582
+ });
583
+ }, [closestTagId, tags]);
490
584
  if (!activeLine) return null;
491
585
  return /* @__PURE__ */ import_react5.default.createElement(
492
586
  import_bottom_sheet3.BottomSheetFlatList,
493
587
  {
588
+ ref: listRef,
494
589
  data: tags,
495
590
  keyExtractor: (t) => t._id,
496
591
  renderItem: ({ item }) => /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, null, children(item, item._id === closestTagId)),
497
- contentContainerStyle: styles3.list
592
+ contentContainerStyle: styles3.list,
593
+ onScrollToIndexFailed: (info) => {
594
+ setTimeout(() => {
595
+ listRef.current?.scrollToOffset({ offset: info.averageItemLength * info.index, animated: true });
596
+ }, 100);
597
+ }
498
598
  }
499
599
  );
500
600
  }
601
+ function DefaultLineTagList() {
602
+ return /* @__PURE__ */ import_react5.default.createElement(LineTagList, null, (tag, isActive) => /* @__PURE__ */ import_react5.default.createElement(DefaultTagItem, { tag, isActive }));
603
+ }
501
604
  function DefaultTagItem({ tag, isActive }) {
502
- const { controls } = useRetorBridge();
605
+ const { controls, openAddNote, activeLine } = useRetorBridge();
606
+ const showPlus = isActive && (activeLine?.notesSupported ?? false);
503
607
  return /* @__PURE__ */ import_react5.default.createElement(
504
608
  import_react_native4.Pressable,
505
609
  {
506
610
  onPress: () => controls.scrollToTag(tag._id),
507
611
  style: [styles3.tagItem, isActive && styles3.tagItemActive]
508
612
  },
509
- /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name),
510
- tag.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle)
613
+ /* @__PURE__ */ import_react5.default.createElement(import_react_native4.View, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
614
+ tag.subtitle && /* @__PURE__ */ import_react5.default.createElement(import_react_native4.Text, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
615
+ showPlus && /* @__PURE__ */ import_react5.default.createElement(
616
+ import_react_native4.Pressable,
617
+ {
618
+ onPress: (e) => {
619
+ e.stopPropagation();
620
+ openAddNote(tag._id);
621
+ },
622
+ style: styles3.plusBtn
623
+ },
624
+ /* @__PURE__ */ import_react5.default.createElement(import_lucide_react_native2.Plus, { size: 14, color: "white" })
625
+ )
511
626
  );
512
627
  }
513
628
  var styles3 = import_react_native4.StyleSheet.create({
514
629
  background: { backgroundColor: "rgba(20,20,20,0.95)" },
515
630
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
516
- content: { flex: 1 },
517
- header: { paddingHorizontal: 24, paddingTop: 8, paddingBottom: 12 },
518
- title: { color: "white", fontSize: 20, fontWeight: "700" },
519
- subtitle: { color: "rgba(255,255,255,0.5)", fontSize: 12, marginTop: 4 },
520
- description: { color: "rgba(255,255,255,0.7)", fontSize: 13, marginTop: 8, lineHeight: 18 },
521
- list: { paddingHorizontal: 16, paddingBottom: 8 },
631
+ root: { flex: 1 },
632
+ header: {
633
+ flexDirection: "row",
634
+ alignItems: "flex-start",
635
+ paddingHorizontal: 24,
636
+ paddingTop: 8,
637
+ paddingBottom: 12,
638
+ gap: 8
639
+ },
640
+ headerActions: { flexDirection: "row", alignItems: "center", gap: 6 },
641
+ title: { color: "white", fontSize: 18, fontWeight: "700" },
642
+ subtitle: { color: "rgba(255,255,255,0.5)", fontSize: 12, marginTop: 2 },
643
+ description: { color: "rgba(255,255,255,0.6)", fontSize: 12, marginTop: 6, lineHeight: 18 },
644
+ iconBtn: {
645
+ width: 28,
646
+ height: 28,
647
+ borderRadius: 14,
648
+ backgroundColor: "rgba(255,255,255,0.1)",
649
+ alignItems: "center",
650
+ justifyContent: "center",
651
+ position: "relative"
652
+ },
653
+ listContainer: { flex: 1 },
654
+ list: { paddingHorizontal: 16, paddingBottom: 8, gap: 4 },
522
655
  tagItem: {
656
+ flexDirection: "row",
657
+ alignItems: "center",
523
658
  paddingHorizontal: 12,
524
- paddingVertical: 10,
659
+ paddingVertical: 12,
525
660
  borderRadius: 12,
526
- marginBottom: 4
661
+ gap: 8
527
662
  },
528
663
  tagItemActive: { backgroundColor: "rgba(255,255,255,0.1)" },
529
- tagText: { color: "rgba(255,255,255,0.6)", fontSize: 14 },
664
+ tagText: { color: "rgba(255,255,255,0.6)", fontSize: 13 },
530
665
  tagTextActive: { color: "white", fontWeight: "600" },
531
- tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10, marginTop: 2 },
666
+ tagDescription: { color: "rgba(255,255,255,0.4)", fontSize: 11, marginTop: 2, lineHeight: 14 },
667
+ tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10 },
668
+ plusBtn: {
669
+ width: 22,
670
+ height: 22,
671
+ borderRadius: 11,
672
+ backgroundColor: "rgba(255,255,255,0.15)",
673
+ alignItems: "center",
674
+ justifyContent: "center"
675
+ },
532
676
  footer: { paddingHorizontal: 16, paddingBottom: 16, paddingTop: 8 },
533
677
  doneButton: {
534
678
  backgroundColor: "white",
@@ -543,6 +687,7 @@ var styles3 = import_react_native4.StyleSheet.create({
543
687
  var import_react6 = __toESM(require("react"));
544
688
  var import_react_native5 = require("react-native");
545
689
  var import_bottom_sheet4 = require("@gorhom/bottom-sheet");
690
+ var import_lucide_react_native3 = require("lucide-react-native");
546
691
  function AddNoteSheet({
547
692
  snapPoints = ["50%"],
548
693
  maxLength = 280,
@@ -602,7 +747,7 @@ function AddNoteSheet({
602
747
  handleIndicatorStyle: styles4.handle,
603
748
  backgroundStyle: styles4.background
604
749
  },
605
- /* @__PURE__ */ import_react6.default.createElement(import_bottom_sheet4.BottomSheetView, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.form }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.headerRow }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.closeBtnText }, "\u2715"))), /* @__PURE__ */ import_react6.default.createElement(
750
+ /* @__PURE__ */ import_react6.default.createElement(import_bottom_sheet4.BottomSheetView, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.form }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: styles4.headerRow }, /* @__PURE__ */ import_react6.default.createElement(import_react_native5.View, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Pressable, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native3.X, { size: 14, color: "rgba(255,255,255,0.6)" }))), /* @__PURE__ */ import_react6.default.createElement(
606
751
  import_bottom_sheet4.BottomSheetTextInput,
607
752
  {
608
753
  value: text,
@@ -629,7 +774,7 @@ function AddNoteSheet({
629
774
  disabled: !text.trim(),
630
775
  style: [styles4.submit, !text.trim() && styles4.submitDisabled]
631
776
  },
632
- /* @__PURE__ */ import_react6.default.createElement(import_react_native5.Text, { style: styles4.submitArrow }, "\u2191")
777
+ /* @__PURE__ */ import_react6.default.createElement(import_lucide_react_native3.ArrowUp, { size: 16, color: "black", strokeWidth: 2.5 })
633
778
  ))))
634
779
  );
635
780
  }
@@ -649,7 +794,6 @@ var styles4 = import_react_native5.StyleSheet.create({
649
794
  alignItems: "center",
650
795
  justifyContent: "center"
651
796
  },
652
- closeBtnText: { color: "rgba(255,255,255,0.6)", fontSize: 14 },
653
797
  input: {
654
798
  flex: 1,
655
799
  color: "white",
@@ -683,8 +827,7 @@ var styles4 = import_react_native5.StyleSheet.create({
683
827
  alignItems: "center",
684
828
  justifyContent: "center"
685
829
  },
686
- submitDisabled: { opacity: 0.3 },
687
- submitArrow: { color: "black", fontSize: 16, fontWeight: "700" }
830
+ submitDisabled: { opacity: 0.3 }
688
831
  });
689
832
 
690
833
  // src/CoverPhoto.tsx
package/dist/index.mjs CHANGED
@@ -313,20 +313,33 @@ function Hud({ children }) {
313
313
  }
314
314
 
315
315
  // src/ProjectSheet.tsx
316
- import React4, { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2 } from "react";
316
+ import React4, { useEffect as useEffect2, useMemo as useMemo3, useRef as useRef2, useState as useState2 } from "react";
317
317
  import { Pressable, ScrollView, StyleSheet as StyleSheet3, Text, View as View3 } from "react-native";
318
318
  import { BottomSheetBackdrop, BottomSheetModal, BottomSheetView } from "@gorhom/bottom-sheet";
319
- function ProjectSheet({ snapPoints = ["20%", "60%"], renderHeader, children }) {
319
+ import { ArrowDown, ArrowUp } from "lucide-react-native";
320
+ function ProjectSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
320
321
  const { project, activeLineId, isAddNoteOpen } = useRetorBridge();
321
322
  const sheetRef = useRef2(null);
322
323
  const snapPointsArr = useMemo3(() => snapPoints, [snapPoints]);
324
+ const [minimized, setMinimized] = useState2(false);
323
325
  useEffect2(() => {
324
326
  if (activeLineId || isAddNoteOpen) {
325
327
  sheetRef.current?.dismiss();
326
328
  } else {
327
329
  sheetRef.current?.present();
330
+ setMinimized(false);
328
331
  }
329
332
  }, [activeLineId, isAddNoteOpen]);
333
+ const handleSheetChange = (index) => {
334
+ setMinimized(index === 0);
335
+ };
336
+ const toggleMinimize = () => {
337
+ if (minimized) {
338
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
339
+ } else {
340
+ sheetRef.current?.snapToIndex(0);
341
+ }
342
+ };
330
343
  return /* @__PURE__ */ React4.createElement(
331
344
  BottomSheetModal,
332
345
  {
@@ -334,6 +347,7 @@ function ProjectSheet({ snapPoints = ["20%", "60%"], renderHeader, children }) {
334
347
  snapPoints: snapPointsArr,
335
348
  enablePanDownToClose: false,
336
349
  enableDismissOnClose: false,
350
+ onChange: handleSheetChange,
337
351
  backdropComponent: (props) => /* @__PURE__ */ React4.createElement(
338
352
  BottomSheetBackdrop,
339
353
  {
@@ -347,7 +361,7 @@ function ProjectSheet({ snapPoints = ["20%", "60%"], renderHeader, children }) {
347
361
  handleIndicatorStyle: styles2.handle,
348
362
  backgroundStyle: styles2.background
349
363
  },
350
- /* @__PURE__ */ React4.createElement(BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ React4.createElement(View3, { style: styles2.header }, project?.name && /* @__PURE__ */ React4.createElement(Text, { style: styles2.title }, project.name), project?.description && /* @__PURE__ */ React4.createElement(Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), children ?? /* @__PURE__ */ React4.createElement(DefaultLinesCarousel, null))
364
+ /* @__PURE__ */ React4.createElement(BottomSheetView, { style: styles2.content }, renderHeader ? renderHeader({ name: project?.name, description: project?.description }) : /* @__PURE__ */ React4.createElement(View3, { style: styles2.header }, /* @__PURE__ */ React4.createElement(View3, { style: { flex: 1, minWidth: 0 } }, project?.name && /* @__PURE__ */ React4.createElement(Text, { style: styles2.title, numberOfLines: 1 }, project.name), project?.description && /* @__PURE__ */ React4.createElement(Text, { style: styles2.subtitle, numberOfLines: 4 }, project.description)), /* @__PURE__ */ React4.createElement(Pressable, { style: styles2.iconBtn, onPress: toggleMinimize }, minimized ? /* @__PURE__ */ React4.createElement(ArrowUp, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React4.createElement(ArrowDown, { size: 14, color: "rgba(255,255,255,0.6)" }))), children ?? /* @__PURE__ */ React4.createElement(DefaultLinesCarousel, null))
351
365
  );
352
366
  }
353
367
  function DefaultLinesCarousel() {
@@ -356,16 +370,15 @@ function DefaultLinesCarousel() {
356
370
  function LinesCarousel({ children, gap = 12, paddingHorizontal = 16 }) {
357
371
  const { lines } = useRetorBridge();
358
372
  if (lines.length === 0) return null;
359
- return /* @__PURE__ */ React4.createElement(
373
+ return /* @__PURE__ */ React4.createElement(View3, { style: styles2.carouselWrap }, /* @__PURE__ */ React4.createElement(Text, { style: styles2.carouselLabel }, "Routes"), /* @__PURE__ */ React4.createElement(
360
374
  ScrollView,
361
375
  {
362
376
  horizontal: true,
363
377
  showsHorizontalScrollIndicator: false,
364
- contentContainerStyle: { paddingHorizontal, gap },
365
- style: styles2.carousel
378
+ contentContainerStyle: { paddingHorizontal, gap }
366
379
  },
367
380
  lines.map((line, idx) => /* @__PURE__ */ React4.createElement(React4.Fragment, { key: line._id }, children(line, idx)))
368
- );
381
+ ));
369
382
  }
370
383
  function DefaultLineCard({ line }) {
371
384
  const { controls } = useRetorBridge();
@@ -383,10 +396,33 @@ var styles2 = StyleSheet3.create({
383
396
  background: { backgroundColor: "rgba(20,20,20,0.95)" },
384
397
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
385
398
  content: { flex: 1, paddingTop: 12, paddingBottom: 24 },
386
- header: { paddingHorizontal: 24, paddingBottom: 16 },
399
+ header: {
400
+ flexDirection: "row",
401
+ alignItems: "flex-start",
402
+ paddingHorizontal: 24,
403
+ paddingBottom: 16,
404
+ gap: 8
405
+ },
387
406
  title: { color: "white", fontSize: 18, fontWeight: "600", lineHeight: 22 },
388
407
  subtitle: { color: "rgba(255,255,255,0.6)", fontSize: 13, marginTop: 4, lineHeight: 18 },
389
- carousel: { marginTop: 8 },
408
+ iconBtn: {
409
+ width: 28,
410
+ height: 28,
411
+ borderRadius: 14,
412
+ backgroundColor: "rgba(255,255,255,0.1)",
413
+ alignItems: "center",
414
+ justifyContent: "center"
415
+ },
416
+ carouselWrap: { marginTop: 8 },
417
+ carouselLabel: {
418
+ color: "rgba(255,255,255,0.4)",
419
+ fontSize: 10,
420
+ textTransform: "uppercase",
421
+ letterSpacing: 1,
422
+ fontWeight: "500",
423
+ paddingHorizontal: 24,
424
+ marginBottom: 8
425
+ },
390
426
  lineCard: {
391
427
  width: 220,
392
428
  backgroundColor: "rgba(255,255,255,0.06)",
@@ -398,20 +434,34 @@ var styles2 = StyleSheet3.create({
398
434
  });
399
435
 
400
436
  // src/LineDetailSheet.tsx
401
- import React5, { useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3 } from "react";
437
+ import React5, { useEffect as useEffect3, useMemo as useMemo4, useRef as useRef3, useState as useState3 } from "react";
402
438
  import { Pressable as Pressable2, StyleSheet as StyleSheet4, Text as Text2, View as View4 } from "react-native";
403
- import { BottomSheetBackdrop as BottomSheetBackdrop2, BottomSheetFlatList, BottomSheetModal as BottomSheetModal2, BottomSheetView as BottomSheetView2 } from "@gorhom/bottom-sheet";
404
- function LineDetailSheet({ snapPoints = ["25%", "75%"], renderHeader, children }) {
439
+ import { BottomSheetBackdrop as BottomSheetBackdrop2, BottomSheetFlatList, BottomSheetModal as BottomSheetModal2 } from "@gorhom/bottom-sheet";
440
+ import Svg, { Circle } from "react-native-svg";
441
+ import { ArrowDown as ArrowDown2, ArrowUp as ArrowUp2, Pause, Play, Plus } from "lucide-react-native";
442
+ function LineDetailSheet({ snapPoints = ["35%", "85%"], renderHeader, children }) {
405
443
  const { activeLine, isAddNoteOpen, controls } = useRetorBridge();
406
444
  const sheetRef = useRef3(null);
407
445
  const snapPointsArr = useMemo4(() => snapPoints, [snapPoints]);
446
+ const [minimized, setMinimized] = useState3(false);
408
447
  useEffect3(() => {
409
448
  if (activeLine && !isAddNoteOpen) {
410
449
  sheetRef.current?.present();
450
+ setMinimized(false);
411
451
  } else {
412
452
  sheetRef.current?.dismiss();
413
453
  }
414
454
  }, [activeLine, isAddNoteOpen]);
455
+ const handleSheetChange = (index) => {
456
+ setMinimized(index === 0);
457
+ };
458
+ const toggleMinimize = () => {
459
+ if (minimized) {
460
+ sheetRef.current?.snapToIndex(snapPointsArr.length - 1);
461
+ } else {
462
+ sheetRef.current?.snapToIndex(0);
463
+ }
464
+ };
415
465
  return /* @__PURE__ */ React5.createElement(
416
466
  BottomSheetModal2,
417
467
  {
@@ -419,6 +469,7 @@ function LineDetailSheet({ snapPoints = ["25%", "75%"], renderHeader, children }
419
469
  snapPoints: snapPointsArr,
420
470
  enablePanDownToClose: false,
421
471
  enableDismissOnClose: false,
472
+ onChange: handleSheetChange,
422
473
  backdropComponent: (props) => /* @__PURE__ */ React5.createElement(
423
474
  BottomSheetBackdrop2,
424
475
  {
@@ -432,60 +483,153 @@ function LineDetailSheet({ snapPoints = ["25%", "75%"], renderHeader, children }
432
483
  handleIndicatorStyle: styles3.handle,
433
484
  backgroundStyle: styles3.background
434
485
  },
435
- /* @__PURE__ */ React5.createElement(BottomSheetView2, { style: styles3.content }, activeLine && (renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ React5.createElement(View4, { style: styles3.header }, /* @__PURE__ */ React5.createElement(Text2, { style: styles3.title }, activeLine.name), activeLine.subtitle && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.subtitle }, activeLine.subtitle), activeLine.description && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.description, numberOfLines: 4 }, activeLine.description))), children ?? /* @__PURE__ */ React5.createElement(DefaultLineTagList, null), /* @__PURE__ */ React5.createElement(View4, { style: styles3.footer }, /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ React5.createElement(Text2, { style: styles3.doneText }, "Done"))))
486
+ activeLine && /* @__PURE__ */ React5.createElement(View4, { style: styles3.root }, renderHeader ? renderHeader(activeLine) : /* @__PURE__ */ React5.createElement(
487
+ DefaultHeader,
488
+ {
489
+ line: activeLine,
490
+ minimized,
491
+ onToggleMinimize: toggleMinimize
492
+ }
493
+ ), /* @__PURE__ */ React5.createElement(View4, { style: styles3.listContainer }, children ?? /* @__PURE__ */ React5.createElement(DefaultLineTagList, null)), /* @__PURE__ */ React5.createElement(View4, { style: styles3.footer }, /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.doneButton, onPress: () => controls.exitLine() }, /* @__PURE__ */ React5.createElement(Text2, { style: styles3.doneText }, "Done"))))
436
494
  );
437
495
  }
438
- function DefaultLineTagList() {
439
- return /* @__PURE__ */ React5.createElement(LineTagList, null, (tag, isActive) => /* @__PURE__ */ React5.createElement(DefaultTagItem, { tag, isActive }));
496
+ function DefaultHeader({
497
+ line,
498
+ minimized,
499
+ onToggleMinimize
500
+ }) {
501
+ return /* @__PURE__ */ React5.createElement(View4, { style: styles3.header }, /* @__PURE__ */ React5.createElement(View4, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React5.createElement(Text2, { style: styles3.title, numberOfLines: 1 }, line.name), line.subtitle && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.subtitle, numberOfLines: 1 }, line.subtitle), line.description && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.description, numberOfLines: minimized ? 2 : 4 }, line.description)), /* @__PURE__ */ React5.createElement(View4, { style: styles3.headerActions }, /* @__PURE__ */ React5.createElement(AutoplayButton, null), /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.iconBtn, onPress: onToggleMinimize }, minimized ? /* @__PURE__ */ React5.createElement(ArrowUp2, { size: 14, color: "rgba(255,255,255,0.6)" }) : /* @__PURE__ */ React5.createElement(ArrowDown2, { size: 14, color: "rgba(255,255,255,0.6)" }))));
502
+ }
503
+ function AutoplayButton() {
504
+ const { isPlaying, progress, controls } = useRetorBridge();
505
+ const r = 12.5;
506
+ const c = 2 * Math.PI * r;
507
+ return /* @__PURE__ */ React5.createElement(Pressable2, { style: styles3.iconBtn, onPress: () => controls.toggleAutoplay() }, /* @__PURE__ */ React5.createElement(Svg, { width: 28, height: 28, style: StyleSheet4.absoluteFill }, /* @__PURE__ */ React5.createElement(
508
+ Circle,
509
+ {
510
+ cx: 14,
511
+ cy: 14,
512
+ r,
513
+ fill: "none",
514
+ stroke: "white",
515
+ strokeWidth: 2,
516
+ strokeDasharray: `${c}`,
517
+ strokeDashoffset: c * (1 - progress),
518
+ strokeLinecap: "round",
519
+ transform: "rotate(-90, 14, 14)"
520
+ }
521
+ )), isPlaying ? /* @__PURE__ */ React5.createElement(Pause, { size: 11, color: "white", fill: "white" }) : /* @__PURE__ */ React5.createElement(Play, { size: 11, color: "white", fill: "white", style: { marginLeft: 1 } }));
440
522
  }
441
523
  function LineTagList({ children }) {
442
524
  const { activeLine, closestTagId } = useRetorBridge();
525
+ const listRef = useRef3(null);
443
526
  const tags = useMemo4(
444
527
  () => (activeLine?.tags ?? []).filter((t) => t.name && t.name.trim().length > 0),
445
528
  [activeLine]
446
529
  );
530
+ useEffect3(() => {
531
+ if (!closestTagId) return;
532
+ const index = tags.findIndex((t) => t._id === closestTagId);
533
+ if (index < 0) return;
534
+ requestAnimationFrame(() => {
535
+ try {
536
+ listRef.current?.scrollToIndex({ index, animated: true, viewPosition: 0 });
537
+ } catch {
538
+ }
539
+ });
540
+ }, [closestTagId, tags]);
447
541
  if (!activeLine) return null;
448
542
  return /* @__PURE__ */ React5.createElement(
449
543
  BottomSheetFlatList,
450
544
  {
545
+ ref: listRef,
451
546
  data: tags,
452
547
  keyExtractor: (t) => t._id,
453
548
  renderItem: ({ item }) => /* @__PURE__ */ React5.createElement(View4, null, children(item, item._id === closestTagId)),
454
- contentContainerStyle: styles3.list
549
+ contentContainerStyle: styles3.list,
550
+ onScrollToIndexFailed: (info) => {
551
+ setTimeout(() => {
552
+ listRef.current?.scrollToOffset({ offset: info.averageItemLength * info.index, animated: true });
553
+ }, 100);
554
+ }
455
555
  }
456
556
  );
457
557
  }
558
+ function DefaultLineTagList() {
559
+ return /* @__PURE__ */ React5.createElement(LineTagList, null, (tag, isActive) => /* @__PURE__ */ React5.createElement(DefaultTagItem, { tag, isActive }));
560
+ }
458
561
  function DefaultTagItem({ tag, isActive }) {
459
- const { controls } = useRetorBridge();
562
+ const { controls, openAddNote, activeLine } = useRetorBridge();
563
+ const showPlus = isActive && (activeLine?.notesSupported ?? false);
460
564
  return /* @__PURE__ */ React5.createElement(
461
565
  Pressable2,
462
566
  {
463
567
  onPress: () => controls.scrollToTag(tag._id),
464
568
  style: [styles3.tagItem, isActive && styles3.tagItemActive]
465
569
  },
466
- /* @__PURE__ */ React5.createElement(Text2, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name),
467
- tag.subtitle && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle)
570
+ /* @__PURE__ */ React5.createElement(View4, { style: { flex: 1, minWidth: 0 } }, /* @__PURE__ */ React5.createElement(Text2, { style: [styles3.tagText, isActive && styles3.tagTextActive], numberOfLines: 1 }, tag.name), isActive && tag.description && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.tagDescription, numberOfLines: 2 }, tag.description)),
571
+ tag.subtitle && /* @__PURE__ */ React5.createElement(Text2, { style: styles3.tagSubtitle, numberOfLines: 1 }, tag.subtitle),
572
+ showPlus && /* @__PURE__ */ React5.createElement(
573
+ Pressable2,
574
+ {
575
+ onPress: (e) => {
576
+ e.stopPropagation();
577
+ openAddNote(tag._id);
578
+ },
579
+ style: styles3.plusBtn
580
+ },
581
+ /* @__PURE__ */ React5.createElement(Plus, { size: 14, color: "white" })
582
+ )
468
583
  );
469
584
  }
470
585
  var styles3 = StyleSheet4.create({
471
586
  background: { backgroundColor: "rgba(20,20,20,0.95)" },
472
587
  handle: { backgroundColor: "rgba(255,255,255,0.3)" },
473
- content: { flex: 1 },
474
- header: { paddingHorizontal: 24, paddingTop: 8, paddingBottom: 12 },
475
- title: { color: "white", fontSize: 20, fontWeight: "700" },
476
- subtitle: { color: "rgba(255,255,255,0.5)", fontSize: 12, marginTop: 4 },
477
- description: { color: "rgba(255,255,255,0.7)", fontSize: 13, marginTop: 8, lineHeight: 18 },
478
- list: { paddingHorizontal: 16, paddingBottom: 8 },
588
+ root: { flex: 1 },
589
+ header: {
590
+ flexDirection: "row",
591
+ alignItems: "flex-start",
592
+ paddingHorizontal: 24,
593
+ paddingTop: 8,
594
+ paddingBottom: 12,
595
+ gap: 8
596
+ },
597
+ headerActions: { flexDirection: "row", alignItems: "center", gap: 6 },
598
+ title: { color: "white", fontSize: 18, fontWeight: "700" },
599
+ subtitle: { color: "rgba(255,255,255,0.5)", fontSize: 12, marginTop: 2 },
600
+ description: { color: "rgba(255,255,255,0.6)", fontSize: 12, marginTop: 6, lineHeight: 18 },
601
+ iconBtn: {
602
+ width: 28,
603
+ height: 28,
604
+ borderRadius: 14,
605
+ backgroundColor: "rgba(255,255,255,0.1)",
606
+ alignItems: "center",
607
+ justifyContent: "center",
608
+ position: "relative"
609
+ },
610
+ listContainer: { flex: 1 },
611
+ list: { paddingHorizontal: 16, paddingBottom: 8, gap: 4 },
479
612
  tagItem: {
613
+ flexDirection: "row",
614
+ alignItems: "center",
480
615
  paddingHorizontal: 12,
481
- paddingVertical: 10,
616
+ paddingVertical: 12,
482
617
  borderRadius: 12,
483
- marginBottom: 4
618
+ gap: 8
484
619
  },
485
620
  tagItemActive: { backgroundColor: "rgba(255,255,255,0.1)" },
486
- tagText: { color: "rgba(255,255,255,0.6)", fontSize: 14 },
621
+ tagText: { color: "rgba(255,255,255,0.6)", fontSize: 13 },
487
622
  tagTextActive: { color: "white", fontWeight: "600" },
488
- tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10, marginTop: 2 },
623
+ tagDescription: { color: "rgba(255,255,255,0.4)", fontSize: 11, marginTop: 2, lineHeight: 14 },
624
+ tagSubtitle: { color: "rgba(255,255,255,0.4)", fontSize: 10 },
625
+ plusBtn: {
626
+ width: 22,
627
+ height: 22,
628
+ borderRadius: 11,
629
+ backgroundColor: "rgba(255,255,255,0.15)",
630
+ alignItems: "center",
631
+ justifyContent: "center"
632
+ },
489
633
  footer: { paddingHorizontal: 16, paddingBottom: 16, paddingTop: 8 },
490
634
  doneButton: {
491
635
  backgroundColor: "white",
@@ -497,14 +641,15 @@ var styles3 = StyleSheet4.create({
497
641
  });
498
642
 
499
643
  // src/AddNoteSheet.tsx
500
- import React6, { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState2 } from "react";
644
+ import React6, { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef4, useState as useState4 } from "react";
501
645
  import { Pressable as Pressable3, StyleSheet as StyleSheet5, Text as Text3, View as View5 } from "react-native";
502
646
  import {
503
647
  BottomSheetBackdrop as BottomSheetBackdrop3,
504
648
  BottomSheetModal as BottomSheetModal3,
505
649
  BottomSheetTextInput,
506
- BottomSheetView as BottomSheetView3
650
+ BottomSheetView as BottomSheetView2
507
651
  } from "@gorhom/bottom-sheet";
652
+ import { ArrowUp as ArrowUp3, X } from "lucide-react-native";
508
653
  function AddNoteSheet({
509
654
  snapPoints = ["50%"],
510
655
  maxLength = 280,
@@ -514,8 +659,8 @@ function AddNoteSheet({
514
659
  const { isAddNoteOpen, addNoteTagId, activeLine, closeAddNote, submitNote } = useRetorBridge();
515
660
  const sheetRef = useRef4(null);
516
661
  const snapPointsArr = useMemo5(() => snapPoints, [snapPoints]);
517
- const [text, setText] = useState2("");
518
- const [isPrivate, setPrivate] = useState2(true);
662
+ const [text, setText] = useState4("");
663
+ const [isPrivate, setPrivate] = useState4(true);
519
664
  useEffect4(() => {
520
665
  if (isAddNoteOpen) {
521
666
  sheetRef.current?.present();
@@ -564,7 +709,7 @@ function AddNoteSheet({
564
709
  handleIndicatorStyle: styles4.handle,
565
710
  backgroundStyle: styles4.background
566
711
  },
567
- /* @__PURE__ */ React6.createElement(BottomSheetView3, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ React6.createElement(View5, { style: styles4.form }, /* @__PURE__ */ React6.createElement(View5, { style: styles4.headerRow }, /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ React6.createElement(Text3, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ React6.createElement(Text3, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ React6.createElement(Pressable3, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ React6.createElement(Text3, { style: styles4.closeBtnText }, "\u2715"))), /* @__PURE__ */ React6.createElement(
712
+ /* @__PURE__ */ React6.createElement(BottomSheetView2, { style: styles4.content }, renderForm ? renderForm(formApi) : /* @__PURE__ */ React6.createElement(View5, { style: styles4.form }, /* @__PURE__ */ React6.createElement(View5, { style: styles4.headerRow }, /* @__PURE__ */ React6.createElement(View5, { style: { flex: 1 } }, activeLine && /* @__PURE__ */ React6.createElement(Text3, { style: styles4.title, numberOfLines: 1 }, activeLine.name), tag && /* @__PURE__ */ React6.createElement(Text3, { style: styles4.subtitle, numberOfLines: 1 }, tag.name)), /* @__PURE__ */ React6.createElement(Pressable3, { onPress: closeAddNote, style: styles4.closeBtn }, /* @__PURE__ */ React6.createElement(X, { size: 14, color: "rgba(255,255,255,0.6)" }))), /* @__PURE__ */ React6.createElement(
568
713
  BottomSheetTextInput,
569
714
  {
570
715
  value: text,
@@ -591,7 +736,7 @@ function AddNoteSheet({
591
736
  disabled: !text.trim(),
592
737
  style: [styles4.submit, !text.trim() && styles4.submitDisabled]
593
738
  },
594
- /* @__PURE__ */ React6.createElement(Text3, { style: styles4.submitArrow }, "\u2191")
739
+ /* @__PURE__ */ React6.createElement(ArrowUp3, { size: 16, color: "black", strokeWidth: 2.5 })
595
740
  ))))
596
741
  );
597
742
  }
@@ -611,7 +756,6 @@ var styles4 = StyleSheet5.create({
611
756
  alignItems: "center",
612
757
  justifyContent: "center"
613
758
  },
614
- closeBtnText: { color: "rgba(255,255,255,0.6)", fontSize: 14 },
615
759
  input: {
616
760
  flex: 1,
617
761
  color: "white",
@@ -645,8 +789,7 @@ var styles4 = StyleSheet5.create({
645
789
  alignItems: "center",
646
790
  justifyContent: "center"
647
791
  },
648
- submitDisabled: { opacity: 0.3 },
649
- submitArrow: { color: "black", fontSize: 16, fontWeight: "700" }
792
+ submitDisabled: { opacity: 0.3 }
650
793
  });
651
794
 
652
795
  // src/CoverPhoto.tsx
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retor/react-native",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "React Native SDK for embedding Retor 3D experiences",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -27,26 +27,30 @@
27
27
  "license": "MIT",
28
28
  "peerDependencies": {
29
29
  "@gorhom/bottom-sheet": ">=5",
30
+ "lucide-react-native": ">=1",
30
31
  "react": ">=18",
31
32
  "react-native": ">=0.72",
32
33
  "react-native-gesture-handler": ">=2.12",
33
34
  "react-native-reanimated": ">=3.5",
35
+ "react-native-svg": ">=14",
34
36
  "react-native-webview": ">=13"
35
37
  },
36
38
  "devDependencies": {
37
39
  "@gorhom/bottom-sheet": "^5.1.0",
38
40
  "@types/react": "^19.0.0",
41
+ "lucide-react-native": "^1.8.0",
39
42
  "react": "19.1.0",
40
43
  "react-native": "0.81.4",
41
44
  "react-native-gesture-handler": "^2.20.0",
42
45
  "react-native-reanimated": "^3.16.0",
46
+ "react-native-svg": "^15.8.0",
43
47
  "react-native-webview": "^13.13.0",
44
48
  "tsup": "^8.0.0",
45
49
  "typescript": "^5.4.0"
46
50
  },
47
51
  "scripts": {
48
- "build": "tsup src/index.tsx --format cjs,esm --dts --clean --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated",
49
- "dev": "tsup src/index.tsx --format cjs,esm --dts --watch --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated",
52
+ "build": "tsup src/index.tsx --format cjs,esm --dts --clean --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated --external react-native-svg --external lucide-react-native",
53
+ "dev": "tsup src/index.tsx --format cjs,esm --dts --watch --external react --external react-native --external react-native-webview --external @gorhom/bottom-sheet --external react-native-gesture-handler --external react-native-reanimated --external react-native-svg --external lucide-react-native",
50
54
  "typecheck": "tsc --noEmit"
51
55
  }
52
56
  }