@ai-matrx/capture 0.1.0 → 0.1.2

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/dist/react.d.cts CHANGED
@@ -96,8 +96,11 @@ interface CaptureCameraSlots {
96
96
  topBarCenter?: React.ReactNode;
97
97
  /** Honesty chips under the top bar (QR confirmation, etc.). */
98
98
  statusChips?: React.ReactNode;
99
+ /** Rendered OVER THE FEED just above the bottom bar (with the zoom
100
+ * pills) — costs the bar no height. The filmstrip belongs here. */
101
+ aboveBar?: React.ReactNode;
99
102
  /** Rows rendered inside the bottom bar ABOVE the mode selector
100
- * (filmstrip, notes/voice row, process button…). */
103
+ * (keep to ONE compact row bar height is stolen from the feed). */
101
104
  aboveModeSelector?: React.ReactNode;
102
105
  /** A compact action pinned right of the mode selector (Next/Break). */
103
106
  modeRowTrailing?: React.ReactNode;
@@ -238,11 +241,16 @@ interface ImageEditSheetProps {
238
241
  declare function ImageEditSheet({ open, src, onClose, onSave, }: ImageEditSheetProps): React__default.JSX.Element | null;
239
242
 
240
243
  /**
241
- * ModeSelector — the iPhone mode row: uppercase, letter-spaced labels in a
242
- * horizontal band; the ACTIVE mode sits in a dark pill with the iOS camera
243
- * yellow. VIDEO and PHOTO are persistent modes; UPLOAD is an immediate
244
- * action (opens the host's picker) and never takes the active state it is
245
- * our third lane in the slot the iPhone layout leaves open next to PHOTO.
244
+ * ModeSelector — the CONNECTED capture-mode bar: one compact rounded track
245
+ * holding VIDEO · PHOTO · UPLOAD (+ injected extras) with a spring-sliding
246
+ * thumb under the active mode (the proven CaptureModeBar interaction, in
247
+ * iPhone dress: uppercase letter-spaced labels, active in iOS camera
248
+ * yellow). Deliberately narrow the row keeps usable space on BOTH sides
249
+ * for host affordances; screen height is the scarcest resource on a phone
250
+ * browser, so the bar is one short row, never a spread of pills.
251
+ *
252
+ * VIDEO and PHOTO are persistent modes; UPLOAD and extras are immediate
253
+ * actions and never take the thumb.
246
254
  *
247
255
  * Package source (`@ai-matrx/capture`) — presentational only.
248
256
  */
package/dist/react.d.ts CHANGED
@@ -96,8 +96,11 @@ interface CaptureCameraSlots {
96
96
  topBarCenter?: React.ReactNode;
97
97
  /** Honesty chips under the top bar (QR confirmation, etc.). */
98
98
  statusChips?: React.ReactNode;
99
+ /** Rendered OVER THE FEED just above the bottom bar (with the zoom
100
+ * pills) — costs the bar no height. The filmstrip belongs here. */
101
+ aboveBar?: React.ReactNode;
99
102
  /** Rows rendered inside the bottom bar ABOVE the mode selector
100
- * (filmstrip, notes/voice row, process button…). */
103
+ * (keep to ONE compact row bar height is stolen from the feed). */
101
104
  aboveModeSelector?: React.ReactNode;
102
105
  /** A compact action pinned right of the mode selector (Next/Break). */
103
106
  modeRowTrailing?: React.ReactNode;
@@ -238,11 +241,16 @@ interface ImageEditSheetProps {
238
241
  declare function ImageEditSheet({ open, src, onClose, onSave, }: ImageEditSheetProps): React__default.JSX.Element | null;
239
242
 
240
243
  /**
241
- * ModeSelector — the iPhone mode row: uppercase, letter-spaced labels in a
242
- * horizontal band; the ACTIVE mode sits in a dark pill with the iOS camera
243
- * yellow. VIDEO and PHOTO are persistent modes; UPLOAD is an immediate
244
- * action (opens the host's picker) and never takes the active state it is
245
- * our third lane in the slot the iPhone layout leaves open next to PHOTO.
244
+ * ModeSelector — the CONNECTED capture-mode bar: one compact rounded track
245
+ * holding VIDEO · PHOTO · UPLOAD (+ injected extras) with a spring-sliding
246
+ * thumb under the active mode (the proven CaptureModeBar interaction, in
247
+ * iPhone dress: uppercase letter-spaced labels, active in iOS camera
248
+ * yellow). Deliberately narrow the row keeps usable space on BOTH sides
249
+ * for host affordances; screen height is the scarcest resource on a phone
250
+ * browser, so the bar is one short row, never a spread of pills.
251
+ *
252
+ * VIDEO and PHOTO are persistent modes; UPLOAD and extras are immediate
253
+ * actions and never take the thumb.
246
254
  *
247
255
  * Package source (`@ai-matrx/capture`) — presentational only.
248
256
  */
package/dist/react.js CHANGED
@@ -154,7 +154,8 @@ function ShutterButton({
154
154
 
155
155
  // src/components/ModeSelector.tsx
156
156
  import { jsx as jsx2, jsxs } from "react/jsx-runtime";
157
- var LABEL_BASE = "touch-manipulation rounded-full px-4 py-1.5 text-[13px] font-semibold uppercase tracking-[0.12em] transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 disabled:opacity-40";
157
+ var SPRING_EASING = "linear(0, 0.0047 0.71%, 0.0189 1.44%, 0.0755 2.93%, 0.1692 4.49%, 0.3921 7.55%, 0.8121 12.94%, 0.9804 15.49%, 1.0946 18.14%, 1.1423 20.16%, 1.1568 21.62%, 1.1541 23.03%, 1.1113 26.4%, 1.0322 31.83%, 0.9902 36.25%, 0.9769 40.24%, 0.9844 45.87%, 1.0028 55.35%, 1.0075 63.42%, 1.0006 85.48%, 1)";
158
+ var LABEL_BASE = "relative z-10 flex h-8 min-w-0 touch-manipulation items-center justify-center rounded-full px-3 text-[11px] font-semibold uppercase tracking-[0.12em] transition-colors duration-200 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 disabled:opacity-40";
158
159
  function ModeSelector({
159
160
  mode,
160
161
  onModeChange,
@@ -163,13 +164,28 @@ function ModeSelector({
163
164
  uploadDisabled = false,
164
165
  extraModes = []
165
166
  }) {
167
+ const segments = 3 + extraModes.length;
168
+ const activeIndex = mode === "video" ? 0 : 1;
166
169
  return /* @__PURE__ */ jsxs(
167
170
  "div",
168
171
  {
169
172
  role: "tablist",
170
173
  "aria-label": "Capture mode",
171
- className: "flex items-center justify-center gap-1",
174
+ className: "relative inline-grid rounded-full bg-white/10 p-1",
175
+ style: { gridTemplateColumns: `repeat(${segments}, minmax(0, 1fr))` },
172
176
  children: [
177
+ /* @__PURE__ */ jsx2(
178
+ "div",
179
+ {
180
+ "aria-hidden": true,
181
+ className: "pointer-events-none absolute bottom-1 top-1 left-1 rounded-full bg-white/20 transition-transform duration-500 ease-[cubic-bezier(0.34,1.56,0.64,1)] will-change-transform motion-reduce:transition-none",
182
+ style: {
183
+ width: `calc((100% - 0.5rem) / ${segments})`,
184
+ transform: `translateX(${activeIndex * 100}%)`,
185
+ transitionTimingFunction: SPRING_EASING
186
+ }
187
+ }
188
+ ),
173
189
  /* @__PURE__ */ jsx2(
174
190
  "button",
175
191
  {
@@ -180,7 +196,7 @@ function ModeSelector({
180
196
  onClick: () => onModeChange("video"),
181
197
  className: cn(
182
198
  LABEL_BASE,
183
- mode === "video" ? "bg-white/15 text-[#FFCC00]" : "text-white"
199
+ mode === "video" ? "text-[#FFCC00]" : "text-white"
184
200
  ),
185
201
  children: "Video"
186
202
  }
@@ -195,7 +211,7 @@ function ModeSelector({
195
211
  onClick: () => onModeChange("photo"),
196
212
  className: cn(
197
213
  LABEL_BASE,
198
- mode === "photo" ? "bg-white/15 text-[#FFCC00]" : "text-white"
214
+ mode === "photo" ? "text-[#FFCC00]" : "text-white"
199
215
  ),
200
216
  children: "Photo"
201
217
  }
@@ -524,17 +540,17 @@ function CameraCapture({
524
540
  }
525
541
  ),
526
542
  /* @__PURE__ */ jsx8(CountdownOverlay, { seconds: countdown }),
527
- !controlsHidden && /* @__PURE__ */ jsx8("div", { className: "absolute inset-x-0 top-0 z-20 bg-black/65 pt-safe backdrop-blur-[2px]", children: /* @__PURE__ */ jsxs6("div", { className: "flex h-16 items-center gap-1 px-3", children: [
543
+ !controlsHidden && /* @__PURE__ */ jsx8("div", { className: "absolute inset-x-0 top-0 z-20 bg-black/65 pt-safe backdrop-blur-[2px]", children: /* @__PURE__ */ jsxs6("div", { className: "flex h-11 items-center gap-0.5 px-1.5", children: [
528
544
  onClose ? /* @__PURE__ */ jsx8(
529
545
  "button",
530
546
  {
531
547
  type: "button",
532
548
  onClick: onClose,
533
549
  "aria-label": "Close camera",
534
- className: "flex h-11 w-11 shrink-0 touch-manipulation items-center justify-center rounded-full text-white transition-colors hover:bg-white/10",
535
- children: /* @__PURE__ */ jsx8(X2, { className: "h-6 w-6" })
550
+ className: "flex h-10 w-10 shrink-0 touch-manipulation items-center justify-center rounded-full text-white transition-colors hover:bg-white/10",
551
+ children: /* @__PURE__ */ jsx8(X2, { className: "h-5 w-5" })
536
552
  }
537
- ) : /* @__PURE__ */ jsx8("span", { className: "w-11 shrink-0" }),
553
+ ) : /* @__PURE__ */ jsx8("span", { className: "w-10 shrink-0" }),
538
554
  /* @__PURE__ */ jsx8("div", { className: "min-w-0 flex-1", children: slots.topBarCenter }),
539
555
  controls.torchSupported && /* @__PURE__ */ jsx8(
540
556
  "button",
@@ -544,13 +560,13 @@ function CameraCapture({
544
560
  "aria-label": controls.torchOn ? "Turn flash off" : "Turn flash on",
545
561
  "aria-pressed": controls.torchOn,
546
562
  className: cn(
547
- "flex h-11 w-11 shrink-0 touch-manipulation items-center justify-center rounded-full transition-colors",
563
+ "flex h-10 w-10 shrink-0 touch-manipulation items-center justify-center rounded-full transition-colors",
548
564
  controls.torchOn ? "text-[#FFCC00]" : "text-white hover:bg-white/10"
549
565
  ),
550
566
  children: /* @__PURE__ */ jsx8(
551
567
  Zap,
552
568
  {
553
- className: "h-[22px] w-[22px]",
569
+ className: "h-5 w-5",
554
570
  fill: controls.torchOn ? "currentColor" : "none"
555
571
  }
556
572
  )
@@ -565,14 +581,14 @@ function CameraCapture({
565
581
  "aria-label": "More camera options",
566
582
  "aria-expanded": optionsOpen,
567
583
  className: cn(
568
- "flex h-11 w-11 shrink-0 touch-manipulation items-center justify-center rounded-full transition-colors",
584
+ "flex h-10 w-10 shrink-0 touch-manipulation items-center justify-center rounded-full transition-colors",
569
585
  optionsOpen ? "bg-white/20 text-white" : "text-white hover:bg-white/10"
570
586
  ),
571
- children: /* @__PURE__ */ jsx8(Grip, { className: "h-[22px] w-[22px]" })
587
+ children: /* @__PURE__ */ jsx8(Grip, { className: "h-5 w-5" })
572
588
  }
573
589
  )
574
590
  ] }) }),
575
- /* @__PURE__ */ jsxs6("div", { className: "pointer-events-none absolute inset-x-0 top-20 z-20 mt-safe flex flex-col items-center gap-2", children: [
591
+ /* @__PURE__ */ jsxs6("div", { className: "pointer-events-none absolute inset-x-0 top-[50px] z-20 mt-safe flex flex-col items-center gap-1.5", children: [
576
592
  engine.recording && /* @__PURE__ */ jsxs6("span", { className: "flex items-center gap-2 rounded-full bg-black/60 px-3 py-1.5 text-sm font-medium text-white", children: [
577
593
  /* @__PURE__ */ jsx8("span", { className: "h-2.5 w-2.5 animate-pulse rounded-full bg-[#FF3B30]" }),
578
594
  formatElapsed(engine.recordElapsedSeconds)
@@ -580,7 +596,7 @@ function CameraCapture({
580
596
  slots.statusChips
581
597
  ] }),
582
598
  !controlsHidden && /* @__PURE__ */ jsxs6("div", { className: "absolute inset-x-0 bottom-0 z-20", children: [
583
- !blocked && controls.zoomOptions.length >= 2 && /* @__PURE__ */ jsx8("div", { className: "mb-3", children: /* @__PURE__ */ jsx8(
599
+ !blocked && controls.zoomOptions.length >= 2 && /* @__PURE__ */ jsx8("div", { className: "mb-2", children: /* @__PURE__ */ jsx8(
584
600
  ZoomRow,
585
601
  {
586
602
  options: controls.zoomOptions,
@@ -588,6 +604,7 @@ function CameraCapture({
588
604
  onSelect: controls.setZoom
589
605
  }
590
606
  ) }),
607
+ slots.aboveBar && /* @__PURE__ */ jsx8("div", { className: "mb-1.5 px-2", children: slots.aboveBar }),
591
608
  exposureOpen && controls.exposureSupported && controls.exposureRange && /* @__PURE__ */ jsxs6("div", { className: "mx-auto mb-3 flex w-64 items-center gap-3 rounded-full bg-black/55 px-4 py-2", children: [
592
609
  /* @__PURE__ */ jsx8(SunMedium, { className: "h-4 w-4 shrink-0 text-[#FFCC00]" }),
593
610
  /* @__PURE__ */ jsx8(
@@ -610,26 +627,28 @@ function CameraCapture({
610
627
  ] }),
611
628
  /* @__PURE__ */ jsxs6("div", { className: "bg-black/65 px-3 pb-safe backdrop-blur-[2px]", children: [
612
629
  slots.aboveModeSelector,
613
- slots.modeRowTrailing && /* @__PURE__ */ jsx8("div", { className: "flex justify-end pb-1 pt-1.5", children: slots.modeRowTrailing }),
614
- /* @__PURE__ */ jsx8("div", { className: "flex items-center justify-center py-2.5", children: /* @__PURE__ */ jsx8(
615
- ModeSelector,
616
- {
617
- mode,
618
- onModeChange,
619
- onUpload: engine.onUpload,
620
- modeDisabled: engine.recording,
621
- uploadDisabled: shutterDisabled && !blocked,
622
- extraModes: slots.extraModes
623
- }
624
- ) }),
625
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-2 pb-4 pt-1", children: [
626
- /* @__PURE__ */ jsx8("div", { className: "flex w-16 justify-start", children: /* @__PURE__ */ jsx8(
630
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-center gap-2 py-1.5", children: [
631
+ /* @__PURE__ */ jsx8(
632
+ ModeSelector,
633
+ {
634
+ mode,
635
+ onModeChange,
636
+ onUpload: engine.onUpload,
637
+ modeDisabled: engine.recording,
638
+ uploadDisabled: shutterDisabled && !blocked,
639
+ extraModes: slots.extraModes
640
+ }
641
+ ),
642
+ slots.modeRowTrailing && /* @__PURE__ */ jsx8("div", { className: "shrink-0", children: slots.modeRowTrailing })
643
+ ] }),
644
+ /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between px-2 pb-1.5 pt-0.5", children: [
645
+ /* @__PURE__ */ jsx8("div", { className: "flex w-14 justify-start", children: /* @__PURE__ */ jsx8(
627
646
  "button",
628
647
  {
629
648
  type: "button",
630
649
  onClick: cloud.onOpenLibrary,
631
650
  "aria-label": "Open your media library",
632
- className: "h-12 w-12 touch-manipulation overflow-hidden rounded-xl bg-white/10 ring-1 ring-white/25 transition-transform active:scale-95",
651
+ className: "h-11 w-11 touch-manipulation overflow-hidden rounded-xl bg-white/10 ring-1 ring-white/25 transition-transform active:scale-95",
633
652
  children: cloud.recentsThumb ?? /* @__PURE__ */ jsx8("span", { className: "block h-full w-full bg-white/5" })
634
653
  }
635
654
  ) }),
@@ -642,16 +661,16 @@ function CameraCapture({
642
661
  onPress: onShutter
643
662
  }
644
663
  ),
645
- /* @__PURE__ */ jsx8("div", { className: "flex w-16 justify-end", children: engine.onFlipCamera ? /* @__PURE__ */ jsx8(
664
+ /* @__PURE__ */ jsx8("div", { className: "flex w-14 justify-end", children: engine.onFlipCamera ? /* @__PURE__ */ jsx8(
646
665
  "button",
647
666
  {
648
667
  type: "button",
649
668
  onClick: engine.onFlipCamera,
650
669
  "aria-label": "Switch camera",
651
- className: "flex h-12 w-12 touch-manipulation items-center justify-center rounded-full bg-white/15 text-white transition-transform active:rotate-180 active:scale-95 duration-300",
670
+ className: "flex h-11 w-11 touch-manipulation items-center justify-center rounded-full bg-white/15 text-white transition-transform active:rotate-180 active:scale-95 duration-300",
652
671
  children: /* @__PURE__ */ jsx8(RefreshCw, { className: "h-5 w-5" })
653
672
  }
654
- ) : /* @__PURE__ */ jsx8("span", { className: "h-12 w-12" }) })
673
+ ) : /* @__PURE__ */ jsx8("span", { className: "h-11 w-11" }) })
655
674
  ] })
656
675
  ] })
657
676
  ] }),