@xhub-short/ui 0.1.0-beta.8 → 1.0.0-beta.18

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.
Files changed (75) hide show
  1. package/dist/CommentSheet.css-DuBy01rU.d.ts +219 -0
  2. package/dist/VideoSlotPlayIndicator-DPs8Xt5C.d.ts +51 -0
  3. package/dist/chunk-3OB3OVYR.js +349 -0
  4. package/dist/chunk-4RIMQOBR.js +58 -0
  5. package/dist/chunk-5MKYDI4X.js +1 -0
  6. package/dist/chunk-5Y43HNRG.js +296 -0
  7. package/dist/chunk-7WXAQHJI.js +350 -0
  8. package/dist/chunk-BADA7OLG.js +564 -0
  9. package/dist/chunk-BEJAJFV6.js +191 -0
  10. package/dist/chunk-BNI7CYRI.js +334 -0
  11. package/dist/{chunk-UPCVSGWH.js → chunk-CAWE42LH.js} +5 -2
  12. package/dist/chunk-DGKMO3AE.js +717 -0
  13. package/dist/chunk-DR7KR7OT.js +103 -0
  14. package/dist/chunk-GSNIZ6DF.js +605 -0
  15. package/dist/chunk-HXQPEZRG.js +105 -0
  16. package/dist/chunk-IC2KUU4V.js +1264 -0
  17. package/dist/chunk-IWSBYOSS.js +91 -0
  18. package/dist/chunk-MFJS65C5.js +368 -0
  19. package/dist/{chunk-CXPNPSF7.js → chunk-NJXIYSDZ.js} +12 -1
  20. package/dist/{chunk-NRQXKZO3.js → chunk-OM4L7RE5.js} +12 -2
  21. package/dist/chunk-QCRRF76W.js +75 -0
  22. package/dist/chunk-QUEJHA24.js +508 -0
  23. package/dist/chunk-UYBQTE4M.js +337 -0
  24. package/dist/chunk-VJ744W5N.js +603 -0
  25. package/dist/chunk-VXW7AOGM.js +285 -0
  26. package/dist/{chunk-OQ7P5XC7.js → chunk-YB7AXTX7.js} +1 -1
  27. package/dist/components/ActionBar/index.d.ts +7 -2
  28. package/dist/components/ActionBar/index.js +1 -1
  29. package/dist/components/AdvanceMenu/index.d.ts +80 -0
  30. package/dist/components/AdvanceMenu/index.js +1 -0
  31. package/dist/components/ArticleSlot/index.d.ts +213 -0
  32. package/dist/components/ArticleSlot/index.js +1 -0
  33. package/dist/components/AuthorInfo/index.js +1 -1
  34. package/dist/components/BottomSheet/index.d.ts +87 -0
  35. package/dist/components/BottomSheet/index.js +1 -0
  36. package/dist/components/CleanModeOverlay/index.d.ts +60 -0
  37. package/dist/components/CleanModeOverlay/index.js +1 -0
  38. package/dist/components/CommentSheet/index.d.ts +155 -3
  39. package/dist/components/CommentSheet/index.js +1 -1
  40. package/dist/components/DetailView/index.d.ts +314 -0
  41. package/dist/components/DetailView/index.js +1 -0
  42. package/dist/components/ErrorBoundary/index.js +1 -1
  43. package/dist/components/{VideoFeed → Feed}/index.d.ts +64 -45
  44. package/dist/components/Feed/index.js +1 -0
  45. package/dist/components/ImageCarousel/index.d.ts +50 -0
  46. package/dist/components/ImageCarousel/index.js +1 -0
  47. package/dist/components/Playlist/index.d.ts +117 -0
  48. package/dist/components/Playlist/index.js +1 -0
  49. package/dist/components/ProgressBar/index.js +1 -1
  50. package/dist/components/QualityPicker/index.d.ts +35 -0
  51. package/dist/components/QualityPicker/index.js +1 -0
  52. package/dist/components/ReportSheet/index.d.ts +74 -0
  53. package/dist/components/ReportSheet/index.js +1 -0
  54. package/dist/components/Skeleton/index.js +1 -1
  55. package/dist/components/SpeedPicker/index.d.ts +32 -0
  56. package/dist/components/SpeedPicker/index.js +1 -0
  57. package/dist/components/VideoInfo/index.d.ts +7 -3
  58. package/dist/components/VideoInfo/index.js +1 -1
  59. package/dist/components/VideoPlayer/index.js +1 -1
  60. package/dist/components/VideoSlot/index.d.ts +31 -68
  61. package/dist/components/VideoSlot/index.js +2 -1
  62. package/dist/components/VirtualSlider/index.js +1 -1
  63. package/dist/index.d.ts +74 -11
  64. package/dist/index.js +39 -15
  65. package/package.json +8 -4
  66. package/dist/CommentSheet.css-BD6XbpU2.d.ts +0 -207
  67. package/dist/chunk-EBAMBI3O.js +0 -571
  68. package/dist/chunk-K3CETRCY.js +0 -737
  69. package/dist/chunk-PGHLVKXS.js +0 -148
  70. package/dist/chunk-QF7O26KZ.js +0 -357
  71. package/dist/chunk-RKS7YA7Z.js +0 -562
  72. package/dist/chunk-T4RQWGRY.js +0 -1519
  73. package/dist/chunk-UXNIXHII.js +0 -2040
  74. package/dist/chunk-YWAPI7JO.js +0 -204
  75. package/dist/components/VideoFeed/index.js +0 -1
@@ -1,148 +0,0 @@
1
- import { clsx2 } from './chunk-EDWS2IPH.js';
2
- import { injectComponentCSS } from './chunk-UPCVSGWH.js';
3
- import { useInsertionEffect } from 'react';
4
- import { jsx } from 'react/jsx-runtime';
5
-
6
- // src/components/Skeleton/Skeleton.css.ts
7
- var SKELETON_CSS = `
8
- /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
9
- * Skeleton Styles
10
- * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
11
-
12
- .sv-skeleton {
13
- display: block;
14
- background: var(--sv-skeleton-bg, rgba(255, 255, 255, 0.1));
15
- border-radius: var(--sv-border-radius-sm, 4px);
16
- }
17
-
18
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
19
- * Pulse Animation
20
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
21
-
22
- .sv-skeleton--pulse {
23
- animation: sv-skeleton-pulse 1.5s ease-in-out infinite;
24
- }
25
-
26
- @keyframes sv-skeleton-pulse {
27
- 0%, 100% {
28
- opacity: 1;
29
- }
30
- 50% {
31
- opacity: 0.4;
32
- }
33
- }
34
-
35
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
36
- * Wave Animation
37
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
38
-
39
- .sv-skeleton--wave {
40
- position: relative;
41
- overflow: hidden;
42
- }
43
-
44
- .sv-skeleton--wave::after {
45
- content: '';
46
- position: absolute;
47
- top: 0;
48
- left: 0;
49
- right: 0;
50
- bottom: 0;
51
- background: linear-gradient(
52
- 90deg,
53
- transparent,
54
- rgba(255, 255, 255, 0.1),
55
- transparent
56
- );
57
- animation: sv-skeleton-wave 1.5s ease-in-out infinite;
58
- }
59
-
60
- @keyframes sv-skeleton-wave {
61
- 0% {
62
- transform: translateX(-100%);
63
- }
64
- 100% {
65
- transform: translateX(100%);
66
- }
67
- }
68
-
69
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
70
- * Preset: Video
71
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
72
-
73
- .sv-skeleton-video {
74
- width: 100%;
75
- height: 100%;
76
- background: var(--sv-bg-primary, #000);
77
- }
78
-
79
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
80
- * Dark Mode Support
81
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
82
-
83
- :root[data-theme="light"] .sv-skeleton,
84
- .light .sv-skeleton {
85
- background: var(--sv-skeleton-bg-light, rgba(0, 0, 0, 0.1));
86
- }
87
-
88
- :root[data-theme="light"] .sv-skeleton--wave::after,
89
- .light .sv-skeleton--wave::after {
90
- background: linear-gradient(
91
- 90deg,
92
- transparent,
93
- rgba(0, 0, 0, 0.05),
94
- transparent
95
- );
96
- }
97
- `;
98
- function Skeleton({
99
- width,
100
- height,
101
- borderRadius,
102
- animation = "pulse",
103
- circle = false,
104
- className
105
- }) {
106
- useInsertionEffect(() => {
107
- return injectComponentCSS("skeleton", SKELETON_CSS);
108
- }, []);
109
- const style = {};
110
- if (width !== void 0) {
111
- style.width = typeof width === "number" ? `${width}px` : width;
112
- }
113
- if (height !== void 0) {
114
- style.height = typeof height === "number" ? `${height}px` : height;
115
- }
116
- if (borderRadius !== void 0) {
117
- style.borderRadius = typeof borderRadius === "number" ? `${borderRadius}px` : borderRadius;
118
- } else if (circle) {
119
- style.borderRadius = "50%";
120
- }
121
- return /* @__PURE__ */ jsx(
122
- "div",
123
- {
124
- className: clsx2("sv-skeleton", animation !== "none" && `sv-skeleton--${animation}`, className),
125
- style,
126
- "aria-hidden": "true"
127
- }
128
- );
129
- }
130
- function SkeletonVideo({
131
- className
132
- }) {
133
- return /* @__PURE__ */ jsx("div", { className: clsx2("sv-skeleton-video", className), children: /* @__PURE__ */ jsx(Skeleton, { width: "100%", height: "100%", animation: "wave" }) });
134
- }
135
- function SkeletonAvatar({
136
- size = 40,
137
- className
138
- }) {
139
- return /* @__PURE__ */ jsx(Skeleton, { width: size, height: size, circle: true, className });
140
- }
141
- function SkeletonText({
142
- width = "100%",
143
- className
144
- }) {
145
- return /* @__PURE__ */ jsx(Skeleton, { width, height: 14, borderRadius: 4, className });
146
- }
147
-
148
- export { Skeleton, SkeletonAvatar, SkeletonText, SkeletonVideo };
@@ -1,357 +0,0 @@
1
- import { VirtualSlider } from './chunk-OQ7P5XC7.js';
2
- import { clsx2 } from './chunk-EDWS2IPH.js';
3
- import { injectComponentCSS } from './chunk-UPCVSGWH.js';
4
- import { createContext, useContext, useInsertionEffect, useRef, useMemo, useCallback } from 'react';
5
- import { jsx, jsxs } from 'react/jsx-runtime';
6
-
7
- // src/components/VideoFeed/VideoFeed.css.ts
8
- var VIDEO_FEED_CSS = `
9
- /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
10
- * VideoFeed Styles
11
- * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
12
-
13
- .sv-video-feed {
14
- position: relative;
15
- width: 100%;
16
- height: 100%;
17
- overflow: hidden;
18
- background: var(--sv-bg-primary, #000);
19
- touch-action: pan-y;
20
- user-select: none;
21
- -webkit-user-select: none;
22
- }
23
-
24
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
25
- * Feed Container (holds all slots)
26
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
27
-
28
- .sv-video-feed__container {
29
- position: relative;
30
- width: 100%;
31
- height: 100%;
32
- will-change: transform;
33
- }
34
-
35
- /* Smooth transition when not swiping */
36
- .sv-video-feed__container--snapping {
37
- transition: transform 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94);
38
- }
39
-
40
- /* No transition during swipe for immediate feedback */
41
- .sv-video-feed__container--swiping {
42
- transition: none;
43
- }
44
-
45
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
46
- * Video Slot Wrapper
47
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
48
-
49
- .sv-video-feed__slot {
50
- position: absolute;
51
- top: 0;
52
- left: 0;
53
- width: 100%;
54
- height: 100%;
55
- will-change: transform, opacity;
56
- backface-visibility: hidden;
57
- -webkit-backface-visibility: hidden;
58
- }
59
-
60
- /* Active slot */
61
- .sv-video-feed__slot--active {
62
- z-index: 2;
63
- }
64
-
65
- /* Adjacent slots (for preview) */
66
- .sv-video-feed__slot--adjacent {
67
- z-index: 1;
68
- pointer-events: none;
69
- }
70
-
71
- /* Transition for smooth snap animations (when not swiping) */
72
- .sv-video-feed__slot--transitioning {
73
- transition: transform 0.175s cubic-bezier(0.25, 0.46, 0.45, 0.94),
74
- opacity 0.2s ease;
75
- }
76
-
77
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
78
- * Loading State
79
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
80
-
81
- .sv-video-feed__loading {
82
- position: absolute;
83
- top: 50%;
84
- left: 50%;
85
- transform: translate(-50%, -50%);
86
- display: flex;
87
- flex-direction: column;
88
- align-items: center;
89
- gap: var(--sv-spacing-md, 16px);
90
- color: var(--sv-text-secondary, #999);
91
- }
92
-
93
- .sv-video-feed__loading-spinner {
94
- width: 32px;
95
- height: 32px;
96
- border: 3px solid rgba(255, 255, 255, 0.2);
97
- border-top-color: var(--sv-color-primary, #fe2c55);
98
- border-radius: 50%;
99
- animation: sv-feed-spinner 0.8s linear infinite;
100
- }
101
-
102
- @keyframes sv-feed-spinner {
103
- to {
104
- transform: rotate(360deg);
105
- }
106
- }
107
-
108
- .sv-video-feed__loading-text {
109
- font-size: var(--sv-font-size-sm, 13px);
110
- font-family: var(--sv-font-family, 'Urbanist', sans-serif);
111
- }
112
-
113
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
114
- * Empty State
115
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
116
-
117
- .sv-video-feed__empty {
118
- position: absolute;
119
- top: 50%;
120
- left: 50%;
121
- transform: translate(-50%, -50%);
122
- text-align: center;
123
- color: var(--sv-text-secondary, #999);
124
- font-family: var(--sv-font-family, 'Urbanist', sans-serif);
125
- }
126
-
127
- .sv-video-feed__empty-icon {
128
- font-size: 48px;
129
- margin-bottom: var(--sv-spacing-md, 16px);
130
- }
131
-
132
- .sv-video-feed__empty-text {
133
- font-size: var(--sv-font-size-md, 14px);
134
- }
135
-
136
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
137
- * End of Feed
138
- * Note: Positioned outside container but above slots. Uses safe-area-inset
139
- * to avoid being covered by device notch/home indicator.
140
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
141
-
142
- .sv-video-feed__end {
143
- position: absolute;
144
- /* Safe bottom positioning - accounts for device UI */
145
- bottom: calc(var(--sv-spacing-xl, 32px) + env(safe-area-inset-bottom, 0px));
146
- left: 50%;
147
- transform: translateX(-50%);
148
- z-index: 10; /* Above slots (z-index 1-2) */
149
- padding: var(--sv-spacing-sm, 8px) var(--sv-spacing-md, 16px);
150
- background: rgba(0, 0, 0, 0.7);
151
- backdrop-filter: blur(8px);
152
- -webkit-backdrop-filter: blur(8px);
153
- border-radius: var(--sv-border-radius-md, 8px);
154
- color: var(--sv-text-secondary, #999);
155
- font-size: var(--sv-font-size-sm, 13px);
156
- font-family: var(--sv-font-family, 'Urbanist', sans-serif);
157
- pointer-events: none; /* Don't block interactions */
158
- animation: sv-feed-end-fade-in 0.3s ease-out;
159
- }
160
-
161
- @keyframes sv-feed-end-fade-in {
162
- from {
163
- opacity: 0;
164
- transform: translateX(-50%) translateY(10px);
165
- }
166
- to {
167
- opacity: 1;
168
- transform: translateX(-50%) translateY(0);
169
- }
170
- }
171
-
172
- /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
173
- * Reduced Motion
174
- * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
175
-
176
- @media (prefers-reduced-motion: reduce) {
177
- .sv-video-feed__container--snapping {
178
- transition: none;
179
- }
180
-
181
- .sv-video-feed__loading-spinner {
182
- animation: none;
183
- }
184
- }
185
- `;
186
- var VideoFeedContext = createContext(null);
187
- function useVideoFeedContext() {
188
- const context = useContext(VideoFeedContext);
189
- if (!context) {
190
- throw new Error("useVideoFeedContext must be used within a VideoFeed");
191
- }
192
- return context;
193
- }
194
- function useOptionalVideoFeedContext() {
195
- return useContext(VideoFeedContext);
196
- }
197
- function VideoFeedLoading({ text = "Loading..." }) {
198
- return /* @__PURE__ */ jsxs("div", { className: "sv-video-feed__loading", children: [
199
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__loading-spinner" }),
200
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__loading-text", children: text })
201
- ] });
202
- }
203
- function VideoFeedEmpty({ text = "No videos" }) {
204
- return /* @__PURE__ */ jsxs("div", { className: "sv-video-feed__empty", children: [
205
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__empty-icon", children: "\u{1F4ED}" }),
206
- /* @__PURE__ */ jsx("div", { className: "sv-video-feed__empty-text", children: text })
207
- ] });
208
- }
209
- function VideoFeedEnd({ text = "You've reached the end" }) {
210
- return /* @__PURE__ */ jsx("div", { className: "sv-video-feed__end", "aria-live": "polite", children: text });
211
- }
212
- function VideoFeedHeadless({
213
- feedState,
214
- swipeState,
215
- height,
216
- className,
217
- renderSlot,
218
- onIndexChange,
219
- onEndReached,
220
- endReachedThreshold = 2,
221
- bufferSize = 1,
222
- loadingText = "Loading...",
223
- emptyText = "No videos",
224
- endText = "You've reached the end",
225
- disableInlineTransforms = false
226
- }) {
227
- useInsertionEffect(() => {
228
- return injectComponentCSS("sv-video-feed", VIDEO_FEED_CSS);
229
- }, []);
230
- const { videos, isLoading, hasMore } = feedState;
231
- const { activeIndex, isSwiping, dragOffset, goToIndex } = swipeState;
232
- const containerHeightRef = useRef(
233
- typeof window !== "undefined" ? window.innerHeight : 800
234
- );
235
- const contextValue = useMemo(
236
- () => ({
237
- videos,
238
- activeIndex: Math.max(0, Math.min(videos.length - 1, activeIndex)),
239
- isSwiping,
240
- dragOffset,
241
- containerHeight: containerHeightRef.current,
242
- goToIndex,
243
- totalCount: videos.length
244
- }),
245
- [videos, activeIndex, isSwiping, dragOffset, goToIndex]
246
- );
247
- const keyExtractor = useCallback((video) => video.id, []);
248
- const renderItem = useCallback(
249
- (video, index, _isActive) => {
250
- return renderSlot(video, index);
251
- },
252
- [renderSlot]
253
- );
254
- const loadingComponent = useMemo(() => /* @__PURE__ */ jsx(VideoFeedLoading, { text: loadingText }), [loadingText]);
255
- const emptyComponent = useMemo(() => /* @__PURE__ */ jsx(VideoFeedEmpty, { text: emptyText }), [emptyText]);
256
- const endComponent = useMemo(() => /* @__PURE__ */ jsx(VideoFeedEnd, { text: endText }), [endText]);
257
- const sliderProps = {
258
- // Required
259
- items: videos,
260
- keyExtractor,
261
- renderItem,
262
- // Swipe state
263
- activeIndex,
264
- isSwiping,
265
- dragOffset,
266
- goToIndex,
267
- // Callbacks
268
- onIndexChange,
269
- onEndReached,
270
- endReachedThreshold,
271
- bufferSize,
272
- // Loading states
273
- isLoading,
274
- hasMore,
275
- // Custom UI (video-specific styling)
276
- loadingComponent,
277
- emptyComponent,
278
- endComponent,
279
- // Styling - use sv-video-feed classes instead of sv-slider
280
- height,
281
- className: clsx2("sv-video-feed", className),
282
- disableInlineTransforms
283
- };
284
- return /* @__PURE__ */ jsx(VideoFeedContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(VirtualSlider, { ...sliderProps }) });
285
- }
286
- VideoFeedHeadless.displayName = "VideoFeedHeadless";
287
- function useVideoFeedPosition(options) {
288
- const {
289
- totalCount,
290
- activeIndex,
291
- containerHeight,
292
- dragOffset,
293
- isSwiping,
294
- isResizing = false,
295
- bufferSize = 1
296
- } = options;
297
- return useMemo(() => {
298
- if (totalCount === 0) {
299
- return {
300
- slots: [],
301
- scrollOffset: 0,
302
- isAtStart: true,
303
- isAtLastIndex: true,
304
- clampedActiveIndex: 0
305
- };
306
- }
307
- const clampedActiveIndex = Math.max(0, Math.min(totalCount - 1, activeIndex));
308
- const slots = [];
309
- const startIndex = Math.max(0, clampedActiveIndex - bufferSize);
310
- const endIndex = Math.min(totalCount - 1, clampedActiveIndex + bufferSize);
311
- const baseOffset = -clampedActiveIndex * containerHeight;
312
- const scrollOffset = baseOffset + dragOffset;
313
- for (let i = startIndex; i <= endIndex; i++) {
314
- const signedDistance = i - clampedActiveIndex;
315
- const distance = Math.abs(signedDistance);
316
- const isActive = i === clampedActiveIndex;
317
- const top = i * containerHeight;
318
- const transform = `translateY(${top + scrollOffset}px)`;
319
- let opacity = 1;
320
- if (!isActive && !isResizing) {
321
- opacity = isSwiping ? 0.7 : 0.3;
322
- }
323
- slots.push({
324
- index: i,
325
- top,
326
- isActive,
327
- distance,
328
- signedDistance,
329
- transform,
330
- opacity
331
- });
332
- }
333
- return {
334
- slots,
335
- scrollOffset,
336
- isAtStart: clampedActiveIndex === 0,
337
- isAtLastIndex: clampedActiveIndex >= totalCount - 1,
338
- clampedActiveIndex
339
- };
340
- }, [totalCount, activeIndex, containerHeight, dragOffset, isSwiping, isResizing, bufferSize]);
341
- }
342
- function getSlotIndexFromPosition(yPosition, containerHeight, totalCount) {
343
- if (totalCount === 0 || containerHeight === 0) {
344
- return 0;
345
- }
346
- const rawIndex = Math.round(-yPosition / containerHeight);
347
- return Math.max(0, Math.min(totalCount - 1, rawIndex));
348
- }
349
-
350
- // src/components/VideoFeed/constants.ts
351
- var SLOT_INDEX_ATTR = "data-slot-index";
352
- var SLOT_ACTIVE_ATTR = "data-slot-active";
353
- var SLOT_INDEX_DATASET_KEY = "slotIndex";
354
- var SLOT_ACTIVE_DATASET_KEY = "slotActive";
355
- var VIDEO_FEED_CLASS_PREFIX = "sv-video-feed";
356
-
357
- export { SLOT_ACTIVE_ATTR, SLOT_ACTIVE_DATASET_KEY, SLOT_INDEX_ATTR, SLOT_INDEX_DATASET_KEY, VIDEO_FEED_CLASS_PREFIX, VIDEO_FEED_CSS, VideoFeedContext, VideoFeedHeadless, getSlotIndexFromPosition, useOptionalVideoFeedContext, useVideoFeedContext, useVideoFeedPosition };