@xhub-short/ui 0.1.0-beta.0 → 0.1.0-beta.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.
@@ -0,0 +1,738 @@
1
+ import { PlusIcon } from './chunk-4YDIRPIN.js';
2
+ import { injectComponentCSS, removeComponentCSS } from './chunk-UXMA4KJZ.js';
3
+ import { clsx } from 'clsx';
4
+ import { createContext, useInsertionEffect, useMemo, useState, useEffect, useContext } from 'react';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ var AuthorInfoContext = createContext(null);
8
+ AuthorInfoContext.displayName = "AuthorInfoContext";
9
+ function useAuthorInfoContext() {
10
+ const context = useContext(AuthorInfoContext);
11
+ if (!context) {
12
+ throw new Error(
13
+ "[AuthorInfo] useAuthorInfoContext must be used within <AuthorInfoHeadless>. Make sure you wrap your component with <AuthorInfoHeadless>."
14
+ );
15
+ }
16
+ return context;
17
+ }
18
+ function useOptionalAuthorInfoContext() {
19
+ return useContext(AuthorInfoContext);
20
+ }
21
+ function DefaultCheckIcon() {
22
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" }) });
23
+ }
24
+ function DefaultUserIcon() {
25
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z" }) });
26
+ }
27
+ var SIZE_CLASS_MAP = {
28
+ small: "sv-author-info__avatar--small",
29
+ large: "sv-author-info__avatar--large",
30
+ default: ""
31
+ };
32
+ function AuthorAvatar({
33
+ src,
34
+ alt,
35
+ size = "default",
36
+ showVerified = false,
37
+ verifiedIcon,
38
+ onClick,
39
+ className,
40
+ children
41
+ }) {
42
+ const [imgError, setImgError] = useState(false);
43
+ const context = useOptionalAuthorInfoContext();
44
+ const avatarSrc = src ?? context?.author.avatar;
45
+ const avatarAlt = alt ?? context?.author.name ?? "Author avatar";
46
+ const isVerified = context?.author.isVerified ?? false;
47
+ const handleClick = onClick ?? context?.openProfile;
48
+ useEffect(() => {
49
+ setImgError(false);
50
+ }, [avatarSrc]);
51
+ const handleImgError = () => setImgError(true);
52
+ const sizeClass = SIZE_CLASS_MAP[size];
53
+ const showImage = avatarSrc && !imgError;
54
+ const handleKeyDown = (e) => {
55
+ if ((e.key === "Enter" || e.key === " ") && handleClick) {
56
+ e.preventDefault();
57
+ e.stopPropagation();
58
+ handleClick();
59
+ }
60
+ };
61
+ return /* @__PURE__ */ jsxs(
62
+ "div",
63
+ {
64
+ className: clsx("sv-author-info__avatar", sizeClass, className),
65
+ onClick: (e) => {
66
+ e.stopPropagation();
67
+ handleClick?.();
68
+ },
69
+ onKeyDown: handleKeyDown,
70
+ role: handleClick ? "button" : void 0,
71
+ tabIndex: handleClick ? 0 : void 0,
72
+ "aria-label": handleClick ? `View ${avatarAlt}'s profile` : void 0,
73
+ children: [
74
+ children ?? (showImage ? /* @__PURE__ */ jsx(
75
+ "img",
76
+ {
77
+ src: avatarSrc,
78
+ alt: avatarAlt,
79
+ className: "sv-author-info__avatar-img",
80
+ loading: "lazy",
81
+ onError: handleImgError
82
+ }
83
+ ) : /* @__PURE__ */ jsx("div", { className: "sv-author-info__avatar-placeholder", children: /* @__PURE__ */ jsx(DefaultUserIcon, {}) })),
84
+ showVerified && isVerified && /* @__PURE__ */ jsx("div", { className: "sv-author-info__verified-badge", "aria-label": "Verified", children: verifiedIcon ?? /* @__PURE__ */ jsx(DefaultCheckIcon, {}) })
85
+ ]
86
+ }
87
+ );
88
+ }
89
+ function AuthorDescription({
90
+ description,
91
+ singleLine = false,
92
+ className,
93
+ children
94
+ }) {
95
+ const context = useOptionalAuthorInfoContext();
96
+ const displayDescription = description ?? context?.author.description;
97
+ if (!children && !displayDescription) {
98
+ return null;
99
+ }
100
+ return /* @__PURE__ */ jsx(
101
+ "p",
102
+ {
103
+ className: clsx(
104
+ "sv-author-info__description",
105
+ singleLine && "sv-author-info__description--single-line",
106
+ className
107
+ ),
108
+ children: children ?? displayDescription
109
+ }
110
+ );
111
+ }
112
+
113
+ // src/components/AuthorInfo/AuthorInfo.css.ts
114
+ var AUTHOR_INFO_CSS = `
115
+ /* \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
116
+ AUTHOR INFO - Container
117
+ \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 */
118
+
119
+ .sv-author-info {
120
+ display: flex;
121
+ align-items: center;
122
+ gap: var(--sv-author-gap, 12px);
123
+ width: 100%;
124
+ }
125
+
126
+ .sv-author-info--vertical {
127
+ flex-direction: column;
128
+ align-items: flex-start;
129
+ }
130
+
131
+ .sv-author-info--horizontal {
132
+ flex-direction: row;
133
+ align-items: center;
134
+ }
135
+
136
+ .sv-author-info--compact {
137
+ gap: var(--sv-author-gap-compact, 8px);
138
+ }
139
+
140
+ /* \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
141
+ AVATAR
142
+ \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 */
143
+
144
+ .sv-author-info__avatar {
145
+ position: relative;
146
+ flex-shrink: 0;
147
+ width: var(--sv-author-avatar-size, 48px);
148
+ height: var(--sv-author-avatar-size, 48px);
149
+ border-radius: 50%;
150
+ overflow: hidden;
151
+ background: var(--sv-author-avatar-bg, rgba(255, 255, 255, 0.1));
152
+ cursor: pointer;
153
+ transition: transform 0.15s ease;
154
+ }
155
+
156
+ .sv-author-info__avatar:hover {
157
+ transform: scale(1.05);
158
+ }
159
+
160
+ .sv-author-info__avatar:active {
161
+ transform: scale(0.98);
162
+ }
163
+
164
+ .sv-author-info__avatar-img {
165
+ width: 100%;
166
+ height: 100%;
167
+ object-fit: cover;
168
+ border-radius: 50%;
169
+ }
170
+
171
+ .sv-author-info__avatar-placeholder {
172
+ width: 100%;
173
+ height: 100%;
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: center;
177
+ background: var(--sv-author-avatar-placeholder-bg, linear-gradient(135deg, #667eea 0%, #764ba2 100%));
178
+ color: var(--sv-author-avatar-placeholder-color, #fff);
179
+ font-size: calc(var(--sv-author-avatar-size, 48px) * 0.4);
180
+ font-weight: 600;
181
+ text-transform: uppercase;
182
+ }
183
+
184
+ .sv-author-info__avatar-placeholder svg {
185
+ width: 60%;
186
+ height: 60%;
187
+ opacity: 0.9;
188
+ }
189
+
190
+ /* Avatar sizes */
191
+ .sv-author-info__avatar--small {
192
+ width: var(--sv-author-avatar-size-small, 32px);
193
+ height: var(--sv-author-avatar-size-small, 32px);
194
+ }
195
+
196
+ .sv-author-info__avatar--large {
197
+ width: var(--sv-author-avatar-size-large, 64px);
198
+ height: var(--sv-author-avatar-size-large, 64px);
199
+ }
200
+
201
+ /* Verified badge */
202
+ .sv-author-info__verified-badge {
203
+ position: absolute;
204
+ bottom: -2px;
205
+ right: -2px;
206
+ width: calc(var(--sv-author-avatar-size, 48px) * 0.35);
207
+ height: calc(var(--sv-author-avatar-size, 48px) * 0.35);
208
+ min-width: 14px;
209
+ min-height: 14px;
210
+ border-radius: 50%;
211
+ background: var(--sv-verified-badge-bg, #1DA1F2);
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ border: 2px solid var(--sv-verified-badge-border, #000);
216
+ }
217
+
218
+ .sv-author-info__verified-badge svg {
219
+ width: 60%;
220
+ height: 60%;
221
+ color: var(--sv-verified-badge-color, #fff);
222
+ }
223
+
224
+ /* \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
225
+ INFO (Name + Description container)
226
+ \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 */
227
+
228
+ .sv-author-info__content {
229
+ display: flex;
230
+ flex-direction: column;
231
+ gap: var(--sv-author-content-gap, 2px);
232
+ flex: 1;
233
+ min-width: 0; /* Allow text truncation */
234
+ }
235
+
236
+ /* \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
237
+ NAME
238
+ \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 */
239
+
240
+ .sv-author-info__name {
241
+ display: flex;
242
+ align-items: center;
243
+ gap: var(--sv-author-name-gap, 4px);
244
+ font-size: var(--sv-author-name-size, 16px);
245
+ font-weight: var(--sv-author-name-weight, 600);
246
+ color: var(--sv-author-name-color, #fff);
247
+ cursor: pointer;
248
+ transition: opacity 0.15s ease;
249
+ }
250
+
251
+ .sv-author-info__name:hover {
252
+ opacity: 0.8;
253
+ }
254
+
255
+ .sv-author-info__name-text {
256
+ overflow: hidden;
257
+ text-overflow: ellipsis;
258
+ white-space: nowrap;
259
+ }
260
+
261
+ .sv-author-info__name--with-at::before {
262
+ content: '@';
263
+ opacity: 0.8;
264
+ }
265
+
266
+ .sv-author-info__name-verified {
267
+ flex-shrink: 0;
268
+ width: var(--sv-author-name-verified-size, 14px);
269
+ height: var(--sv-author-name-verified-size, 14px);
270
+ color: var(--sv-verified-badge-bg, #1DA1F2);
271
+ }
272
+
273
+ /* \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
274
+ DESCRIPTION / BIO
275
+ \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 */
276
+
277
+ .sv-author-info__description {
278
+ font-size: var(--sv-author-desc-size, 13px);
279
+ color: var(--sv-author-desc-color, rgba(255, 255, 255, 0.7));
280
+ line-height: var(--sv-author-desc-line-height, 1.4);
281
+ overflow: hidden;
282
+ text-overflow: ellipsis;
283
+ display: -webkit-box;
284
+ -webkit-line-clamp: var(--sv-author-desc-lines, 2);
285
+ -webkit-box-orient: vertical;
286
+ }
287
+
288
+ .sv-author-info__description--single-line {
289
+ -webkit-line-clamp: 1;
290
+ white-space: nowrap;
291
+ display: block;
292
+ }
293
+
294
+ /* \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
295
+ FOLLOW BUTTON
296
+ \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 */
297
+
298
+ .sv-author-info__follow-btn {
299
+ display: inline-flex;
300
+ align-items: center;
301
+ justify-content: center;
302
+ gap: var(--sv-follow-btn-gap, 6px);
303
+ padding: var(--sv-follow-btn-padding, 8px 16px);
304
+ min-width: var(--sv-follow-btn-min-width, 88px);
305
+ border: none;
306
+ border-radius: var(--sv-follow-btn-radius, 4px);
307
+ font-size: var(--sv-follow-btn-size, 14px);
308
+ font-weight: var(--sv-follow-btn-weight, 600);
309
+ cursor: pointer;
310
+ transition: all 0.2s ease;
311
+ outline: none;
312
+ user-select: none;
313
+ -webkit-tap-highlight-color: transparent;
314
+ }
315
+
316
+ /* Not following state (primary CTA) */
317
+ .sv-author-info__follow-btn--default {
318
+ background: var(--sv-follow-btn-bg, #fe2c55);
319
+ color: var(--sv-follow-btn-color, #fff);
320
+ }
321
+
322
+ .sv-author-info__follow-btn--default:hover:not(:disabled) {
323
+ background: var(--sv-follow-btn-bg-hover, #ff4466);
324
+ transform: scale(1.02);
325
+ }
326
+
327
+ .sv-author-info__follow-btn--default:active:not(:disabled) {
328
+ transform: scale(0.98);
329
+ }
330
+
331
+ /* Following state (outline) */
332
+ .sv-author-info__follow-btn--following {
333
+ background: var(--sv-follow-btn-following-bg, transparent);
334
+ color: var(--sv-follow-btn-following-color, rgba(255, 255, 255, 0.9));
335
+ border: 1px solid var(--sv-follow-btn-following-border, rgba(255, 255, 255, 0.3));
336
+ }
337
+
338
+ .sv-author-info__follow-btn--following:hover:not(:disabled) {
339
+ background: var(--sv-follow-btn-following-bg-hover, rgba(255, 255, 255, 0.1));
340
+ border-color: var(--sv-follow-btn-following-border-hover, rgba(255, 255, 255, 0.5));
341
+ }
342
+
343
+ /* Pending state */
344
+ .sv-author-info__follow-btn--pending {
345
+ opacity: 0.6;
346
+ pointer-events: none;
347
+ cursor: wait;
348
+ }
349
+
350
+ .sv-author-info__follow-btn:disabled {
351
+ opacity: 0.5;
352
+ cursor: not-allowed;
353
+ }
354
+
355
+ /* Icon in button */
356
+ .sv-author-info__follow-btn-icon {
357
+ width: var(--sv-follow-btn-icon-size, 16px);
358
+ height: var(--sv-follow-btn-icon-size, 16px);
359
+ flex-shrink: 0;
360
+ }
361
+
362
+ /* Loading spinner */
363
+ .sv-author-info__follow-btn-spinner {
364
+ width: var(--sv-follow-btn-icon-size, 16px);
365
+ height: var(--sv-follow-btn-icon-size, 16px);
366
+ border: 2px solid currentColor;
367
+ border-top-color: transparent;
368
+ border-radius: 50%;
369
+ animation: sv-author-info-spin 0.6s linear infinite;
370
+ }
371
+
372
+ @keyframes sv-author-info-spin {
373
+ from {
374
+ transform: rotate(0deg);
375
+ }
376
+ to {
377
+ transform: rotate(360deg);
378
+ }
379
+ }
380
+
381
+ /* Button sizes */
382
+ .sv-author-info__follow-btn--small {
383
+ padding: var(--sv-follow-btn-padding-small, 6px 12px);
384
+ font-size: var(--sv-follow-btn-size-small, 12px);
385
+ min-width: var(--sv-follow-btn-min-width-small, 72px);
386
+ }
387
+
388
+ .sv-author-info__follow-btn--large {
389
+ padding: var(--sv-follow-btn-padding-large, 10px 20px);
390
+ font-size: var(--sv-follow-btn-size-large, 16px);
391
+ min-width: var(--sv-follow-btn-min-width-large, 100px);
392
+ }
393
+
394
+ /* Icon-only button */
395
+ .sv-author-info__follow-btn--icon-only {
396
+ min-width: unset;
397
+ padding: var(--sv-follow-btn-icon-padding, 8px);
398
+ border-radius: 50%;
399
+ }
400
+
401
+ /* \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
402
+ LAYOUT VARIANTS
403
+ \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 */
404
+
405
+ /* Inline variant (avatar + name inline, follow button on right) */
406
+ .sv-author-info--inline {
407
+ justify-content: space-between;
408
+ }
409
+
410
+ .sv-author-info--inline .sv-author-info__content {
411
+ flex: 1;
412
+ }
413
+
414
+ /* Stacked variant (avatar above name) */
415
+ .sv-author-info--stacked {
416
+ flex-direction: column;
417
+ align-items: center;
418
+ text-align: center;
419
+ }
420
+
421
+ .sv-author-info--stacked .sv-author-info__content {
422
+ align-items: center;
423
+ }
424
+
425
+ /* \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
426
+ OVERLAY MODE (for use on video)
427
+ \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 */
428
+
429
+ .sv-author-info--overlay {
430
+ /* Text shadow for readability on video */
431
+ text-shadow: var(--sv-author-overlay-shadow, 0 1px 2px rgba(0, 0, 0, 0.5));
432
+ }
433
+
434
+ .sv-author-info--overlay .sv-author-info__avatar {
435
+ border: 2px solid var(--sv-author-overlay-avatar-border, rgba(255, 255, 255, 0.5));
436
+ }
437
+
438
+ /* \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
439
+ AVATAR-BADGE VARIANT (TikTok-style avatar with follow badge)
440
+ \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 */
441
+
442
+ .sv-author-info--avatar-badge {
443
+ position: relative;
444
+ display: flex;
445
+ flex-direction: column;
446
+ align-items: center;
447
+ width: auto;
448
+ gap: 0;
449
+ }
450
+
451
+ /* Avatar in badge variant */
452
+ .sv-author-info--avatar-badge .sv-author-info__avatar {
453
+ width: var(--sv-avatar-badge-size, 48px);
454
+ height: var(--sv-avatar-badge-size, 48px);
455
+ border: 2px solid var(--sv-avatar-badge-border, #fff);
456
+ }
457
+
458
+ /* Follow button as overlay badge */
459
+ .sv-author-info--avatar-badge .sv-author-info__follow-btn {
460
+ position: absolute;
461
+ bottom: var(--sv-avatar-badge-btn-bottom, -10px);
462
+ left: 50%;
463
+ transform: translateX(-50%);
464
+ min-width: unset;
465
+ width: var(--sv-avatar-badge-btn-size, 24px);
466
+ height: var(--sv-avatar-badge-btn-size, 24px);
467
+ padding: 0;
468
+ border-radius: 50%;
469
+ background: var(--sv-color-primary, #fe2c55);
470
+ border: 2px solid var(--sv-bg-primary, #000);
471
+ z-index: 1;
472
+ }
473
+
474
+ .sv-author-info--avatar-badge .sv-author-info__follow-btn:hover:not(:disabled) {
475
+ transform: translateX(-50%) scale(1.1);
476
+ }
477
+
478
+ .sv-author-info--avatar-badge .sv-author-info__follow-btn:active:not(:disabled) {
479
+ transform: translateX(-50%) scale(0.95);
480
+ }
481
+
482
+ /* Icon size in badge button */
483
+ .sv-author-info--avatar-badge .sv-author-info__follow-btn-icon {
484
+ width: var(--sv-avatar-badge-icon-size, 14px);
485
+ height: var(--sv-avatar-badge-icon-size, 14px);
486
+ }
487
+
488
+ /* Hide button when following (TikTok behavior) */
489
+ .sv-author-info--avatar-badge .sv-author-info__follow-btn--following {
490
+ display: none;
491
+ }
492
+
493
+ /* Hide content section in badge variant */
494
+ .sv-author-info--avatar-badge .sv-author-info__content {
495
+ display: none;
496
+ }
497
+
498
+ /* Add margin bottom for action bar spacing */
499
+ .sv-author-info--avatar-badge {
500
+ margin-bottom: var(--sv-avatar-badge-margin, 16px);
501
+ }
502
+ `;
503
+ function DefaultVerifiedIcon() {
504
+ return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M22.5 12.5c0-1.58-.875-2.95-2.148-3.6.154-.435.238-.905.238-1.4 0-2.21-1.71-3.998-3.818-3.998-.47 0-.92.084-1.336.25C14.818 2.415 13.51 1.5 12 1.5s-2.816.917-3.437 2.25c-.415-.165-.866-.25-1.336-.25-2.11 0-3.818 1.79-3.818 4 0 .494.083.964.237 1.4-1.272.65-2.147 2.018-2.147 3.6 0 1.495.782 2.798 1.942 3.486-.02.17-.032.34-.032.514 0 2.21 1.708 4 3.818 4 .47 0 .92-.086 1.335-.25.62 1.334 1.926 2.25 3.437 2.25 1.512 0 2.818-.916 3.437-2.25.415.163.865.248 1.336.248 2.11 0 3.818-1.79 3.818-4 0-.174-.012-.344-.033-.513 1.158-.687 1.943-1.99 1.943-3.484zm-6.616-3.334l-4.334 6.5c-.145.217-.382.334-.625.334-.143 0-.288-.04-.416-.126l-.115-.094-2.415-2.415c-.293-.293-.293-.768 0-1.06s.768-.294 1.06 0l1.77 1.767 3.825-5.74c.23-.345.696-.436 1.04-.207.346.23.44.696.21 1.04z" }) });
505
+ }
506
+ function AuthorName({
507
+ name,
508
+ showAtPrefix = false,
509
+ showVerified = false,
510
+ verifiedIcon,
511
+ onClick,
512
+ className,
513
+ children
514
+ }) {
515
+ const context = useOptionalAuthorInfoContext();
516
+ const displayName = name ?? context?.author.name ?? "Unknown";
517
+ const isVerified = context?.author.isVerified ?? false;
518
+ const handleClick = onClick ?? context?.openProfile;
519
+ const handleKeyDown = (e) => {
520
+ if ((e.key === "Enter" || e.key === " ") && handleClick) {
521
+ e.preventDefault();
522
+ e.stopPropagation();
523
+ handleClick();
524
+ }
525
+ };
526
+ return /* @__PURE__ */ jsxs(
527
+ "div",
528
+ {
529
+ className: clsx(
530
+ "sv-author-info__name",
531
+ showAtPrefix && "sv-author-info__name--with-at",
532
+ className
533
+ ),
534
+ onClick: (e) => {
535
+ e.stopPropagation();
536
+ handleClick?.();
537
+ },
538
+ onKeyDown: handleKeyDown,
539
+ role: handleClick ? "button" : void 0,
540
+ tabIndex: handleClick ? 0 : void 0,
541
+ "aria-label": handleClick ? `View ${displayName}'s profile` : void 0,
542
+ children: [
543
+ children ?? /* @__PURE__ */ jsx("span", { className: "sv-author-info__name-text", children: displayName }),
544
+ showVerified && isVerified && /* @__PURE__ */ jsx("span", { className: "sv-author-info__name-verified", "aria-label": "Verified", children: verifiedIcon ?? /* @__PURE__ */ jsx(DefaultVerifiedIcon, {}) })
545
+ ]
546
+ }
547
+ );
548
+ }
549
+ function DefaultFollowIcon() {
550
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
551
+ /* @__PURE__ */ jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
552
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "7", r: "4" }),
553
+ /* @__PURE__ */ jsx("line", { x1: "20", y1: "8", x2: "20", y2: "14" }),
554
+ /* @__PURE__ */ jsx("line", { x1: "23", y1: "11", x2: "17", y2: "11" })
555
+ ] });
556
+ }
557
+ function DefaultFollowingIcon() {
558
+ return /* @__PURE__ */ jsxs("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: [
559
+ /* @__PURE__ */ jsx("path", { d: "M16 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" }),
560
+ /* @__PURE__ */ jsx("circle", { cx: "8.5", cy: "7", r: "4" }),
561
+ /* @__PURE__ */ jsx("polyline", { points: "17 11 19 13 23 9" })
562
+ ] });
563
+ }
564
+ var SIZE_CLASS_MAP2 = {
565
+ small: "sv-author-info__follow-btn--small",
566
+ default: "",
567
+ large: "sv-author-info__follow-btn--large"
568
+ };
569
+ function ButtonIcon({
570
+ isPending,
571
+ isFollowing,
572
+ followIcon,
573
+ followingIcon
574
+ }) {
575
+ if (isPending) {
576
+ return /* @__PURE__ */ jsx("span", { className: "sv-author-info__follow-btn-spinner" });
577
+ }
578
+ const icon = isFollowing ? followingIcon ?? /* @__PURE__ */ jsx(DefaultFollowingIcon, {}) : followIcon ?? /* @__PURE__ */ jsx(DefaultFollowIcon, {});
579
+ return /* @__PURE__ */ jsx("span", { className: "sv-author-info__follow-btn-icon", children: icon });
580
+ }
581
+ function FollowButton({
582
+ isFollowing: isFollowingProp,
583
+ isPending: isPendingProp,
584
+ onClick,
585
+ size = "default",
586
+ iconOnly = false,
587
+ followIcon,
588
+ followingIcon,
589
+ followText = "Follow",
590
+ followingText = "Following",
591
+ disabled = false,
592
+ className,
593
+ children
594
+ }) {
595
+ const context = useOptionalAuthorInfoContext();
596
+ const isFollowing = isFollowingProp ?? context?.isFollowing ?? false;
597
+ const isPending = isPendingProp ?? context?.isFollowPending ?? false;
598
+ const handleClick = onClick ?? context?.toggleFollow;
599
+ const isDisabled = disabled || isPending;
600
+ const stateClass = isFollowing ? "sv-author-info__follow-btn--following" : "sv-author-info__follow-btn--default";
601
+ const buttonText = isFollowing ? followingText : followText;
602
+ const handleButtonClick = (e) => {
603
+ e.stopPropagation();
604
+ e.preventDefault();
605
+ if (!isDisabled) {
606
+ handleClick?.();
607
+ }
608
+ };
609
+ const handleKeyDown = (e) => {
610
+ if ((e.key === "Enter" || e.key === " ") && !isDisabled) {
611
+ e.preventDefault();
612
+ e.stopPropagation();
613
+ handleClick?.();
614
+ }
615
+ };
616
+ const stopPropagation = (e) => e.stopPropagation();
617
+ return /* @__PURE__ */ jsx(
618
+ "button",
619
+ {
620
+ type: "button",
621
+ className: clsx(
622
+ "sv-author-info__follow-btn",
623
+ stateClass,
624
+ isPending && "sv-author-info__follow-btn--pending",
625
+ iconOnly && "sv-author-info__follow-btn--icon-only",
626
+ SIZE_CLASS_MAP2[size],
627
+ className
628
+ ),
629
+ onClick: handleButtonClick,
630
+ onKeyDown: handleKeyDown,
631
+ onPointerDown: stopPropagation,
632
+ onPointerUp: stopPropagation,
633
+ onTouchStart: stopPropagation,
634
+ onTouchEnd: stopPropagation,
635
+ disabled: isDisabled,
636
+ "aria-pressed": isFollowing,
637
+ "aria-label": isFollowing ? "Unfollow" : "Follow",
638
+ "aria-busy": isPending,
639
+ children: children ?? /* @__PURE__ */ jsxs(Fragment, { children: [
640
+ /* @__PURE__ */ jsx(
641
+ ButtonIcon,
642
+ {
643
+ isPending,
644
+ isFollowing,
645
+ followIcon: followIcon ?? /* @__PURE__ */ jsx(PlusIcon, { size: 14 }),
646
+ followingIcon
647
+ }
648
+ ),
649
+ !iconOnly && /* @__PURE__ */ jsx("span", { children: buttonText })
650
+ ] })
651
+ }
652
+ );
653
+ }
654
+ var CSS_COMPONENT_ID = "author-info";
655
+ function AuthorInfoContent({
656
+ className,
657
+ children
658
+ }) {
659
+ return /* @__PURE__ */ jsx("div", { className: clsx("sv-author-info__content", className), children });
660
+ }
661
+ function AuthorInfoHeadlessRoot({
662
+ authorState,
663
+ authorActions,
664
+ showFollowButton = true,
665
+ variant = "horizontal",
666
+ overlay = false,
667
+ className,
668
+ children
669
+ }) {
670
+ useInsertionEffect(() => {
671
+ injectComponentCSS(CSS_COMPONENT_ID, AUTHOR_INFO_CSS);
672
+ return () => {
673
+ removeComponentCSS(CSS_COMPONENT_ID);
674
+ };
675
+ }, []);
676
+ const contextValue = useMemo(
677
+ () => ({
678
+ author: authorState.author,
679
+ isFollowing: authorState.isFollowing,
680
+ isFollowPending: authorState.isFollowPending,
681
+ toggleFollow: () => {
682
+ authorActions.toggleFollow();
683
+ },
684
+ openProfile: () => {
685
+ authorActions.openProfile();
686
+ }
687
+ }),
688
+ [authorState, authorActions]
689
+ );
690
+ const variantClass = `sv-author-info--${variant}`;
691
+ const stopPropagation = (e) => {
692
+ e.stopPropagation();
693
+ };
694
+ return /* @__PURE__ */ jsx(AuthorInfoContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsx(
695
+ "div",
696
+ {
697
+ className: clsx(
698
+ "sv-author-info",
699
+ variantClass,
700
+ overlay && "sv-author-info--overlay",
701
+ className
702
+ ),
703
+ onClick: stopPropagation,
704
+ onPointerDown: stopPropagation,
705
+ onPointerUp: stopPropagation,
706
+ onTouchStart: stopPropagation,
707
+ onTouchEnd: stopPropagation,
708
+ children: children ? children : variant === "avatar-badge" ? (
709
+ // Default layout for avatar-badge variant
710
+ /* @__PURE__ */ jsxs(Fragment, { children: [
711
+ /* @__PURE__ */ jsx(AuthorAvatar, {}),
712
+ showFollowButton && /* @__PURE__ */ jsx(FollowButton, { iconOnly: true })
713
+ ] })
714
+ ) : (
715
+ // Default layout when no children provided
716
+ /* @__PURE__ */ jsxs(Fragment, { children: [
717
+ /* @__PURE__ */ jsx(AuthorAvatar, { showVerified: true }),
718
+ /* @__PURE__ */ jsx(AuthorInfoContent, { children: /* @__PURE__ */ jsx(AuthorName, { showAtPrefix: true, showVerified: true }) }),
719
+ showFollowButton && /* @__PURE__ */ jsx(FollowButton, {})
720
+ ] })
721
+ )
722
+ }
723
+ ) });
724
+ }
725
+ var AuthorInfoHeadless = Object.assign(AuthorInfoHeadlessRoot, {
726
+ /** Avatar sub-component */
727
+ Avatar: AuthorAvatar,
728
+ /** Name sub-component */
729
+ Name: AuthorName,
730
+ /** Description/bio sub-component */
731
+ Description: AuthorDescription,
732
+ /** Follow button sub-component */
733
+ FollowButton,
734
+ /** Content wrapper for name + description */
735
+ Content: AuthorInfoContent
736
+ });
737
+
738
+ export { AUTHOR_INFO_CSS, AuthorAvatar, AuthorDescription, AuthorInfoContext, AuthorInfoHeadless, AuthorName, FollowButton, useAuthorInfoContext, useOptionalAuthorInfoContext };