@shortkitsdk/react-native 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,6 +6,7 @@ import type { ShortKitContextValue } from './ShortKitContext';
6
6
  import type {
7
7
  ShortKitProviderProps,
8
8
  ContentItem,
9
+ CustomFeedItem,
9
10
  PlayerTime,
10
11
  PlayerState,
11
12
  CaptionTrack,
@@ -13,6 +14,7 @@ import type {
13
14
  } from './types';
14
15
  import {
15
16
  serializeFeedConfigForModule,
17
+ serializeCustomFeedItems,
16
18
  deserializePlayerState,
17
19
  deserializeContentItem,
18
20
  deserializePlayerTime,
@@ -34,6 +36,7 @@ interface State {
34
36
  activeCaptionTrack: CaptionTrack | null;
35
37
  activeCue: { text: string; startTime: number; endTime: number } | null;
36
38
  prefetchedAheadCount: number;
39
+ remainingContentCount: number;
37
40
  isActive: boolean;
38
41
  isTransitioning: boolean;
39
42
  lastOverlayTap: number;
@@ -51,6 +54,7 @@ const initialState: State = {
51
54
  activeCaptionTrack: null,
52
55
  activeCue: null,
53
56
  prefetchedAheadCount: 0,
57
+ remainingContentCount: 0,
54
58
  isActive: false,
55
59
  isTransitioning: false,
56
60
  lastOverlayTap: 0,
@@ -67,6 +71,7 @@ type Action =
67
71
  | { type: 'CAPTION_TRACK'; payload: CaptionTrack | null }
68
72
  | { type: 'CUE'; payload: { text: string; startTime: number; endTime: number } | null }
69
73
  | { type: 'PREFETCH_COUNT'; payload: number }
74
+ | { type: 'REMAINING_COUNT'; payload: number }
70
75
  | { type: 'OVERLAY_CONFIGURE'; payload: ContentItem }
71
76
  | { type: 'OVERLAY_ACTIVATE'; payload: ContentItem }
72
77
  | { type: 'OVERLAY_RESET' }
@@ -108,6 +113,8 @@ function reducer(state: State, action: Action): State {
108
113
  return { ...state, activeCue: action.payload };
109
114
  case 'PREFETCH_COUNT':
110
115
  return { ...state, prefetchedAheadCount: action.payload };
116
+ case 'REMAINING_COUNT':
117
+ return { ...state, remainingContentCount: action.payload };
111
118
  case 'OVERLAY_CONFIGURE':
112
119
  return { ...state, nextItem: action.payload };
113
120
  case 'OVERLAY_ACTIVATE':
@@ -145,6 +152,7 @@ function reducer(state: State, action: Action): State {
145
152
  export function ShortKitProvider({
146
153
  apiKey,
147
154
  config,
155
+ embedId,
148
156
  userId,
149
157
  clientAppName,
150
158
  clientAppVersion,
@@ -168,6 +176,7 @@ export function ShortKitProvider({
168
176
  NativeShortKitModule.initialize(
169
177
  apiKey,
170
178
  serializedConfig,
179
+ embedId,
171
180
  clientAppName,
172
181
  clientAppVersion,
173
182
  serializedDimensions,
@@ -245,6 +254,7 @@ export function ShortKitProvider({
245
254
  NativeShortKitModule.onCurrentItemChanged((event) => {
246
255
  const item: ContentItem = {
247
256
  id: event.id,
257
+ playbackId: event.playbackId,
248
258
  title: event.title,
249
259
  description: event.description,
250
260
  duration: event.duration,
@@ -327,6 +337,13 @@ export function ShortKitProvider({
327
337
  }),
328
338
  );
329
339
 
340
+ // Remaining content count
341
+ subscriptions.push(
342
+ NativeShortKitModule.onRemainingContentCountChanged((event) => {
343
+ dispatch({ type: 'REMAINING_COUNT', payload: event.count });
344
+ }),
345
+ );
346
+
330
347
  // Overlay lifecycle events
331
348
  subscriptions.push(
332
349
  NativeShortKitModule.onOverlayConfigure((event) => {
@@ -450,6 +467,24 @@ export function ShortKitProvider({
450
467
  NativeShortKitModule?.clearUserId();
451
468
  }, []);
452
469
 
470
+ const setFeedItemsCmd = useCallback((items: CustomFeedItem[]) => {
471
+ NativeShortKitModule?.setFeedItems(serializeCustomFeedItems(items));
472
+ }, []);
473
+
474
+ const appendFeedItemsCmd = useCallback((items: CustomFeedItem[]) => {
475
+ NativeShortKitModule?.appendFeedItems(serializeCustomFeedItems(items));
476
+ }, []);
477
+
478
+ const fetchContentCmd = useCallback(async (limit: number = 10): Promise<ContentItem[]> => {
479
+ if (!NativeShortKitModule) return [];
480
+ const json = await NativeShortKitModule.fetchContent(limit);
481
+ try {
482
+ return JSON.parse(json) as ContentItem[];
483
+ } catch {
484
+ return [];
485
+ }
486
+ }, []);
487
+
453
488
  // -----------------------------------------------------------------------
454
489
  // Context value (memoized to avoid unnecessary re-renders)
455
490
  // -----------------------------------------------------------------------
@@ -466,6 +501,7 @@ export function ShortKitProvider({
466
501
  activeCaptionTrack: state.activeCaptionTrack,
467
502
  activeCue: state.activeCue,
468
503
  prefetchedAheadCount: state.prefetchedAheadCount,
504
+ remainingContentCount: state.remainingContentCount,
469
505
  isActive: state.isActive,
470
506
  isTransitioning: state.isTransitioning,
471
507
  lastOverlayTap: state.lastOverlayTap,
@@ -486,6 +522,9 @@ export function ShortKitProvider({
486
522
  setMaxBitrate,
487
523
  setUserId: setUserIdCmd,
488
524
  clearUserId: clearUserIdCmd,
525
+ setFeedItems: setFeedItemsCmd,
526
+ appendFeedItems: appendFeedItemsCmd,
527
+ fetchContent: fetchContentCmd,
489
528
 
490
529
  // Internal — consumed by ShortKitFeed to pass to OverlayManager
491
530
  _overlayConfig: config.overlay ?? 'none',
@@ -501,6 +540,7 @@ export function ShortKitProvider({
501
540
  state.activeCaptionTrack,
502
541
  state.activeCue,
503
542
  state.prefetchedAheadCount,
543
+ state.remainingContentCount,
504
544
  state.isActive,
505
545
  state.isTransitioning,
506
546
  state.lastOverlayTap,
@@ -519,6 +559,9 @@ export function ShortKitProvider({
519
559
  setMaxBitrate,
520
560
  setUserIdCmd,
521
561
  clearUserIdCmd,
562
+ setFeedItemsCmd,
563
+ appendFeedItemsCmd,
564
+ fetchContentCmd,
522
565
  config.overlay,
523
566
  ],
524
567
  );
@@ -0,0 +1,63 @@
1
+ import React, { useContext, useMemo } from 'react';
2
+ import { View, StyleSheet } from 'react-native';
3
+ import type { ShortKitWidgetProps } from './types';
4
+ import ShortKitWidgetView from './specs/ShortKitWidgetViewNativeComponent';
5
+ import { ShortKitContext } from './ShortKitContext';
6
+
7
+ /**
8
+ * Horizontal carousel widget component. Displays a row of video cards
9
+ * with automatic rotation. Wraps a native Fabric view.
10
+ *
11
+ * Must be rendered inside a `<ShortKitProvider>`.
12
+ */
13
+ export function ShortKitWidget(props: ShortKitWidgetProps) {
14
+ const { config, items, style } = props;
15
+
16
+ const context = useContext(ShortKitContext);
17
+ if (!context) {
18
+ throw new Error('ShortKitWidget must be used within a ShortKitProvider');
19
+ }
20
+
21
+ const serializedConfig = useMemo(() => {
22
+ const cfg = config ?? {};
23
+ return JSON.stringify({
24
+ cardCount: cfg.cardCount ?? 3,
25
+ cardSpacing: cfg.cardSpacing ?? 8,
26
+ cornerRadius: cfg.cornerRadius ?? 12,
27
+ autoplay: cfg.autoplay ?? true,
28
+ muteOnStart: cfg.muteOnStart ?? true,
29
+ loop: cfg.loop ?? true,
30
+ rotationInterval: cfg.rotationInterval ?? 10000,
31
+ clickAction: cfg.clickAction ?? 'feed',
32
+ overlay: cfg.overlay
33
+ ? typeof cfg.overlay === 'string'
34
+ ? cfg.overlay
35
+ : { type: 'custom' }
36
+ : 'none',
37
+ });
38
+ }, [config]);
39
+
40
+ const serializedItems = useMemo(() => {
41
+ if (!items || items.length === 0) return undefined;
42
+ return JSON.stringify(items);
43
+ }, [items]);
44
+
45
+ return (
46
+ <View style={[styles.container, style]}>
47
+ <ShortKitWidgetView
48
+ style={styles.widget}
49
+ config={serializedConfig}
50
+ items={serializedItems}
51
+ />
52
+ </View>
53
+ );
54
+ }
55
+
56
+ const styles = StyleSheet.create({
57
+ container: {
58
+ overflow: 'hidden',
59
+ },
60
+ widget: {
61
+ flex: 1,
62
+ },
63
+ });
package/src/index.ts CHANGED
@@ -1,15 +1,25 @@
1
1
  export { ShortKitProvider } from './ShortKitProvider';
2
2
  export { ShortKitFeed } from './ShortKitFeed';
3
+ export { ShortKitPlayer } from './ShortKitPlayer';
4
+ export { ShortKitWidget } from './ShortKitWidget';
3
5
  export { useShortKitPlayer } from './useShortKitPlayer';
4
6
  export { useShortKit } from './useShortKit';
5
7
  export type {
6
8
  FeedConfig,
7
9
  FeedHeight,
10
+ FeedSource,
8
11
  OverlayConfig,
9
12
  CarouselMode,
10
13
  SurveyMode,
11
14
 
15
+ PlayerConfig,
16
+ PlayerClickAction,
17
+ WidgetConfig,
18
+
12
19
  ContentItem,
20
+ CarouselImage,
21
+ ImageCarouselItem,
22
+ CustomFeedItem,
13
23
  JSONValue,
14
24
  CaptionTrack,
15
25
  PlayerTime,
@@ -22,5 +32,7 @@ export type {
22
32
  ShortKitError,
23
33
  ShortKitProviderProps,
24
34
  ShortKitFeedProps,
35
+ ShortKitPlayerProps,
36
+ ShortKitWidgetProps,
25
37
  ShortKitPlayerState,
26
38
  } from './types';
@@ -1,6 +1,7 @@
1
1
  import type {
2
2
  FeedConfig,
3
3
  ContentItem,
4
+ CustomFeedItem,
4
5
  PlayerState,
5
6
  PlayerTime,
6
7
  } from './types';
@@ -15,6 +16,7 @@ export interface SerializedFeedConfig {
15
16
  carouselMode: string;
16
17
  surveyMode: string;
17
18
  muteOnStart: boolean;
19
+ feedSource: string;
18
20
  }
19
21
 
20
22
  /**
@@ -38,6 +40,7 @@ export function serializeFeedConfig(config: FeedConfig): SerializedFeedConfig {
38
40
  carouselMode: JSON.stringify(config.carouselMode ?? 'none'),
39
41
  surveyMode: JSON.stringify(config.surveyMode ?? 'none'),
40
42
  muteOnStart: config.muteOnStart ?? true,
43
+ feedSource: config.feedSource ?? 'algorithmic',
41
44
  };
42
45
  }
43
46
 
@@ -93,3 +96,10 @@ export function deserializePlayerTime(event: {
93
96
  buffered: event.buffered,
94
97
  };
95
98
  }
99
+
100
+ /**
101
+ * Serialize CustomFeedItem[] to a JSON string for the bridge.
102
+ */
103
+ export function serializeCustomFeedItems(items: CustomFeedItem[]): string {
104
+ return JSON.stringify(items);
105
+ }
@@ -11,6 +11,7 @@ type PlayerStateEvent = Readonly<{
11
11
 
12
12
  type CurrentItemEvent = Readonly<{
13
13
  id: string;
14
+ playbackId?: string;
14
15
  title: string;
15
16
  description?: string;
16
17
  duration: Double;
@@ -77,6 +78,10 @@ type PrefetchedAheadCountEvent = Readonly<{
77
78
  count: Int32;
78
79
  }>;
79
80
 
81
+ type RemainingContentCountEvent = Readonly<{
82
+ count: Int32;
83
+ }>;
84
+
80
85
  type ErrorEvent = Readonly<{
81
86
  code: string;
82
87
  message: string;
@@ -112,6 +117,11 @@ type OverlayRestoreEvent = Readonly<{
112
117
  item: string; // JSON-serialized ContentItem
113
118
  }>;
114
119
 
120
+ type ContentTappedEvent = Readonly<{
121
+ contentId: string;
122
+ index: Int32;
123
+ }>;
124
+
115
125
  type OverlayTapEvent = Readonly<{}>;
116
126
 
117
127
  type OverlayDoubleTapEvent = Readonly<{
@@ -124,6 +134,7 @@ export interface Spec extends TurboModule {
124
134
  initialize(
125
135
  apiKey: string,
126
136
  config: string, // JSON-serialized FeedConfig
137
+ embedId?: string,
127
138
  clientAppName?: string,
128
139
  clientAppVersion?: string,
129
140
  customDimensions?: string, // JSON-serialized Record<string, string>
@@ -148,6 +159,11 @@ export interface Spec extends TurboModule {
148
159
  sendContentSignal(signal: string): void;
149
160
  setMaxBitrate(bitrate: Double): void;
150
161
 
162
+ // --- Custom feed ---
163
+ setFeedItems(items: string): void;
164
+ appendFeedItems(items: string): void;
165
+ fetchContent(limit: Int32): Promise<string>;
166
+
151
167
  // --- Event emitters ---
152
168
  readonly onPlayerStateChanged: EventEmitter<PlayerStateEvent>;
153
169
  readonly onCurrentItemChanged: EventEmitter<CurrentItemEvent>;
@@ -161,6 +177,7 @@ export interface Spec extends TurboModule {
161
177
  readonly onFeedTransition: EventEmitter<FeedTransitionEvent>;
162
178
  readonly onFormatChange: EventEmitter<FormatChangeEvent>;
163
179
  readonly onPrefetchedAheadCountChanged: EventEmitter<PrefetchedAheadCountEvent>;
180
+ readonly onRemainingContentCountChanged: EventEmitter<RemainingContentCountEvent>;
164
181
  readonly onError: EventEmitter<ErrorEvent>;
165
182
  readonly onShareTapped: EventEmitter<ShareTappedEvent>;
166
183
  readonly onSurveyResponse: EventEmitter<SurveyResponseEvent>;
@@ -171,6 +188,7 @@ export interface Spec extends TurboModule {
171
188
  readonly onOverlayRestore: EventEmitter<OverlayRestoreEvent>;
172
189
  readonly onOverlayTap: EventEmitter<OverlayTapEvent>;
173
190
  readonly onOverlayDoubleTap: EventEmitter<OverlayDoubleTapEvent>;
191
+ readonly onContentTapped: EventEmitter<ContentTappedEvent>;
174
192
  }
175
193
 
176
194
  export default TurboModuleRegistry.getEnforcing<Spec>('ShortKitModule');
@@ -0,0 +1,13 @@
1
+ import type { HostComponent, ViewProps } from 'react-native';
2
+ import { codegenNativeComponent } from 'react-native';
3
+
4
+ export interface NativeProps extends ViewProps {
5
+ config: string;
6
+ contentItem?: string;
7
+ active?: boolean;
8
+ }
9
+
10
+ export default codegenNativeComponent<NativeProps>(
11
+ 'ShortKitPlayerView',
12
+ {},
13
+ ) as HostComponent<NativeProps>;
@@ -0,0 +1,12 @@
1
+ import type { HostComponent, ViewProps } from 'react-native';
2
+ import { codegenNativeComponent } from 'react-native';
3
+
4
+ export interface NativeProps extends ViewProps {
5
+ config: string;
6
+ items?: string;
7
+ }
8
+
9
+ export default codegenNativeComponent<NativeProps>(
10
+ 'ShortKitWidgetView',
11
+ {},
12
+ ) as HostComponent<NativeProps>;
package/src/types.ts CHANGED
@@ -2,12 +2,15 @@ import type { ViewStyle } from 'react-native';
2
2
 
3
3
  // --- Configuration ---
4
4
 
5
+ export type FeedSource = 'algorithmic' | 'custom';
6
+
5
7
  export interface FeedConfig {
6
8
  feedHeight?: FeedHeight;
7
9
  overlay?: OverlayConfig;
8
10
  carouselMode?: CarouselMode;
9
11
  surveyMode?: SurveyMode;
10
12
  muteOnStart?: boolean;
13
+ feedSource?: FeedSource;
11
14
  }
12
15
 
13
16
  export type FeedHeight =
@@ -30,6 +33,7 @@ export type SurveyMode =
30
33
 
31
34
  export interface ContentItem {
32
35
  id: string;
36
+ playbackId?: string;
33
37
  title: string;
34
38
  description?: string;
35
39
  duration: number;
@@ -42,6 +46,29 @@ export interface ContentItem {
42
46
  commentCount?: number;
43
47
  }
44
48
 
49
+ // --- Custom Feed Types ---
50
+
51
+ export interface CarouselImage {
52
+ url: string;
53
+ alt?: string;
54
+ }
55
+
56
+ export interface ImageCarouselItem {
57
+ id: string;
58
+ images: CarouselImage[];
59
+ autoScrollInterval?: number;
60
+ caption?: string;
61
+ title?: string;
62
+ description?: string;
63
+ author?: string;
64
+ section?: string;
65
+ articleUrl?: string;
66
+ }
67
+
68
+ export type CustomFeedItem =
69
+ | { type: 'video'; playbackId: string }
70
+ | { type: 'imageCarousel'; item: ImageCarouselItem };
71
+
45
72
  export type JSONValue =
46
73
  | string
47
74
  | number
@@ -109,6 +136,7 @@ export interface ShortKitError {
109
136
  export interface ShortKitProviderProps {
110
137
  apiKey: string;
111
138
  config: FeedConfig;
139
+ embedId?: string;
112
140
  userId?: string;
113
141
 
114
142
  clientAppName?: string;
@@ -127,6 +155,55 @@ export interface ShortKitFeedProps {
127
155
  onLoop?: (event: LoopEvent) => void;
128
156
  onFeedTransition?: (event: FeedTransitionEvent) => void;
129
157
  onFormatChange?: (event: FormatChangeEvent) => void;
158
+ onContentTapped?: (contentId: string, index: number) => void;
159
+ }
160
+
161
+ // --- Single Player Config ---
162
+
163
+ export type PlayerClickAction = 'feed' | 'mute' | 'none';
164
+
165
+ export interface PlayerConfig {
166
+ cornerRadius?: number;
167
+ clickAction?: PlayerClickAction;
168
+ autoplay?: boolean;
169
+ loop?: boolean;
170
+ muteOnStart?: boolean;
171
+ overlay?: OverlayConfig;
172
+ }
173
+
174
+ // --- Widget Config ---
175
+
176
+ export interface WidgetConfig {
177
+ cardCount?: number;
178
+ cardSpacing?: number;
179
+ cornerRadius?: number;
180
+ autoplay?: boolean;
181
+ muteOnStart?: boolean;
182
+ loop?: boolean;
183
+ rotationInterval?: number;
184
+ clickAction?: PlayerClickAction;
185
+ overlay?: OverlayConfig;
186
+ }
187
+
188
+ // --- Single Player Props ---
189
+
190
+ export interface ShortKitPlayerProps {
191
+ config?: PlayerConfig;
192
+ contentItem?: ContentItem;
193
+ /** Controls whether the player is actively playing. When `false` the player
194
+ * returns to thumbnail-only mode without tearing down the view. Defaults to
195
+ * the value of `config.autoplay` (which itself defaults to `true`). */
196
+ active?: boolean;
197
+ style?: ViewStyle;
198
+ onTap?: () => void;
199
+ }
200
+
201
+ // --- Widget Props ---
202
+
203
+ export interface ShortKitWidgetProps {
204
+ config?: WidgetConfig;
205
+ items?: ContentItem[];
206
+ style?: ViewStyle;
130
207
  }
131
208
 
132
209
  // --- Hook Return Types ---
@@ -142,6 +219,7 @@ export interface ShortKitPlayerState {
142
219
  activeCaptionTrack: CaptionTrack | null;
143
220
  activeCue: { text: string; startTime: number; endTime: number } | null;
144
221
  prefetchedAheadCount: number;
222
+ remainingContentCount: number;
145
223
  isActive: boolean;
146
224
  isTransitioning: boolean;
147
225
  lastOverlayTap: number;
@@ -6,7 +6,7 @@ import { ShortKitContext } from './ShortKitContext';
6
6
  *
7
7
  * Must be used within a `<ShortKitProvider>`.
8
8
  *
9
- * @returns SDK operations: `setUserId` and `clearUserId`.
9
+ * @returns SDK operations and state.
10
10
  */
11
11
  export function useShortKit() {
12
12
  const context = useContext(ShortKitContext);
@@ -16,5 +16,9 @@ export function useShortKit() {
16
16
  return {
17
17
  setUserId: context.setUserId,
18
18
  clearUserId: context.clearUserId,
19
+ setFeedItems: context.setFeedItems,
20
+ appendFeedItems: context.appendFeedItems,
21
+ fetchContent: context.fetchContent,
22
+ remainingContentCount: context.remainingContentCount,
19
23
  };
20
24
  }