@xhub-short/ui 0.1.0-beta.1 → 0.1.0-beta.10

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 (45) hide show
  1. package/dist/CommentSheet.css-BeCrEaUG.d.ts +221 -0
  2. package/dist/{chunk-2PTMP65P.js → chunk-2FSDVYER.js} +8 -9
  3. package/dist/{chunk-WKX2WBVO.js → chunk-3XPJHUYL.js} +1 -39
  4. package/dist/{chunk-ANGBSV7L.js → chunk-AC2IFAJR.js} +10 -5
  5. package/dist/{chunk-4YDIRPIN.js → chunk-ANCP53F3.js} +3 -3
  6. package/dist/chunk-AQHD6LPS.js +430 -0
  7. package/dist/{chunk-HW4LXTFT.js → chunk-CL6BS7GB.js} +7 -5
  8. package/dist/{chunk-YW23IBKF.js → chunk-ECR42RKK.js} +46 -5
  9. package/dist/chunk-EDWS2IPH.js +1 -0
  10. package/dist/chunk-FNXTPQ6L.js +2573 -0
  11. package/dist/{chunk-DHQJBXQW.js → chunk-KWHMZ6H5.js} +1 -1
  12. package/dist/{chunk-UXMA4KJZ.js → chunk-RMLTPW5S.js} +3 -2
  13. package/dist/{chunk-SSJDO24Q.js → chunk-SZXFH334.js} +1 -1
  14. package/dist/{chunk-4MN72OZH.js → chunk-UNV3NWN6.js} +4 -4
  15. package/dist/{chunk-ZZDQKP4R.js → chunk-WCRDTBCZ.js} +94 -155
  16. package/dist/{chunk-XAOEHLOX.js → chunk-XDIH66C4.js} +245 -52
  17. package/dist/components/ActionBar/index.js +1 -1
  18. package/dist/components/AuthorInfo/index.d.ts +5 -1
  19. package/dist/components/AuthorInfo/index.js +1 -1
  20. package/dist/components/BlurhashPlaceholder/index.d.ts +67 -0
  21. package/dist/components/BlurhashPlaceholder/index.js +150 -0
  22. package/dist/components/CommentSheet/index.d.ts +164 -0
  23. package/dist/components/CommentSheet/index.js +1 -0
  24. package/dist/components/ErrorBoundary/index.js +1 -1
  25. package/dist/components/OfflineIndicator/index.d.ts +56 -0
  26. package/dist/components/OfflineIndicator/index.js +151 -0
  27. package/dist/components/ProgressBar/index.d.ts +30 -2
  28. package/dist/components/ProgressBar/index.js +1 -1
  29. package/dist/components/Skeleton/index.js +1 -1
  30. package/dist/components/SubtitleDisplay/index.d.ts +94 -0
  31. package/dist/components/SubtitleDisplay/index.js +165 -0
  32. package/dist/components/VideoFeed/index.d.ts +11 -0
  33. package/dist/components/VideoFeed/index.js +1 -1
  34. package/dist/components/VideoInfo/index.js +1 -1
  35. package/dist/components/VideoPlayer/index.d.ts +14 -41
  36. package/dist/components/VideoPlayer/index.js +1 -1
  37. package/dist/components/VideoSlot/index.d.ts +124 -64
  38. package/dist/components/VideoSlot/index.js +1 -1
  39. package/dist/components/VirtualSlider/index.d.ts +339 -0
  40. package/dist/components/VirtualSlider/index.js +1 -0
  41. package/dist/components/icons/index.js +1 -1
  42. package/dist/index.d.ts +76 -93
  43. package/dist/index.js +75 -27
  44. package/package.json +53 -8
  45. package/dist/use-gesture-react.esm-3SV4QLEJ.js +0 -1893
@@ -0,0 +1,150 @@
1
+ import { injectComponentCSS } from '../../chunk-RMLTPW5S.js';
2
+ import { clsx } from 'clsx';
3
+ import { useRef, useState, useInsertionEffect, useEffect } from 'react';
4
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
+
6
+ // src/components/BlurhashPlaceholder/BlurhashPlaceholder.css.ts
7
+ var BLURHASH_PLACEHOLDER_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
+ * BLURHASH PLACEHOLDER
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-blurhash-placeholder {
13
+ position: absolute;
14
+ inset: 0;
15
+ z-index: var(--sv-z-placeholder, 1);
16
+ background-size: cover;
17
+ background-position: center;
18
+ background-repeat: no-repeat;
19
+ transition: opacity 200ms ease-out;
20
+ }
21
+
22
+ .sv-blurhash-placeholder--hidden {
23
+ opacity: 0;
24
+ pointer-events: none;
25
+ }
26
+
27
+ /* Canvas used for decoding - always hidden */
28
+ .sv-blurhash-placeholder__canvas {
29
+ display: none !important;
30
+ }
31
+
32
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
33
+ * REDUCE MOTION
34
+ * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
35
+
36
+ @media (prefers-reduced-motion: reduce) {
37
+ .sv-blurhash-placeholder {
38
+ transition: none;
39
+ }
40
+ }
41
+ `;
42
+ var imageCache = /* @__PURE__ */ new Map();
43
+ var decodePromise = null;
44
+ function getBlurhashDecoder() {
45
+ if (!decodePromise) {
46
+ decodePromise = import('blurhash').then((m) => ({ decode: m.decode }));
47
+ }
48
+ return decodePromise;
49
+ }
50
+ function getCacheKey(blurhash, width, height, punch) {
51
+ return `${blurhash}:${width}:${height}:${punch}`;
52
+ }
53
+ function BlurhashPlaceholderHeadless({
54
+ blurhash,
55
+ width = 32,
56
+ height = 32,
57
+ punch = 1,
58
+ isHidden = false,
59
+ className,
60
+ testId
61
+ }) {
62
+ const canvasRef = useRef(null);
63
+ const cacheKey = getCacheKey(blurhash, width, height, punch);
64
+ const [dataUrl, setDataUrl] = useState(() => {
65
+ return imageCache.get(cacheKey) ?? null;
66
+ });
67
+ useInsertionEffect(() => {
68
+ return injectComponentCSS("blurhash-placeholder", BLURHASH_PLACEHOLDER_CSS);
69
+ }, []);
70
+ useEffect(() => {
71
+ if (dataUrl) return void 0;
72
+ let cancelled = false;
73
+ async function decode() {
74
+ try {
75
+ const { decode: decodeBlurhash } = await getBlurhashDecoder();
76
+ if (cancelled) return;
77
+ const pixels = decodeBlurhash(blurhash, width, height, punch);
78
+ if (cancelled) return;
79
+ const canvas = canvasRef.current;
80
+ if (!canvas) return;
81
+ canvas.width = width;
82
+ canvas.height = height;
83
+ const ctx = canvas.getContext("2d");
84
+ if (!ctx) return;
85
+ const imageData = ctx.createImageData(width, height);
86
+ imageData.data.set(pixels);
87
+ ctx.putImageData(imageData, 0, 0);
88
+ const url = canvas.toDataURL();
89
+ imageCache.set(cacheKey, url);
90
+ if (!cancelled) {
91
+ setDataUrl(url);
92
+ }
93
+ } catch {
94
+ }
95
+ }
96
+ decode();
97
+ return () => {
98
+ cancelled = true;
99
+ };
100
+ }, [blurhash, width, height, punch, cacheKey, dataUrl]);
101
+ if (!dataUrl && !canvasRef.current) {
102
+ return /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "sv-blurhash-placeholder__canvas" });
103
+ }
104
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
105
+ /* @__PURE__ */ jsx("canvas", { ref: canvasRef, className: "sv-blurhash-placeholder__canvas" }),
106
+ dataUrl && /* @__PURE__ */ jsx(
107
+ "div",
108
+ {
109
+ className: clsx(
110
+ "sv-blurhash-placeholder",
111
+ isHidden && "sv-blurhash-placeholder--hidden",
112
+ className
113
+ ),
114
+ style: { backgroundImage: `url(${dataUrl})` },
115
+ "aria-hidden": "true",
116
+ "data-testid": testId
117
+ }
118
+ )
119
+ ] });
120
+ }
121
+ function clearBlurhashCache() {
122
+ imageCache.clear();
123
+ }
124
+ function getBlurhashCacheSize() {
125
+ return imageCache.size;
126
+ }
127
+ async function preloadBlurhash(blurhash, width = 32, height = 32, punch = 1) {
128
+ const cacheKey = getCacheKey(blurhash, width, height, punch);
129
+ const cached = imageCache.get(cacheKey);
130
+ if (cached) return cached;
131
+ try {
132
+ const { decode } = await getBlurhashDecoder();
133
+ const pixels = decode(blurhash, width, height, punch);
134
+ const canvas = document.createElement("canvas");
135
+ canvas.width = width;
136
+ canvas.height = height;
137
+ const ctx = canvas.getContext("2d");
138
+ if (!ctx) return null;
139
+ const imageData = ctx.createImageData(width, height);
140
+ imageData.data.set(pixels);
141
+ ctx.putImageData(imageData, 0, 0);
142
+ const url = canvas.toDataURL();
143
+ imageCache.set(cacheKey, url);
144
+ return url;
145
+ } catch {
146
+ return null;
147
+ }
148
+ }
149
+
150
+ export { BLURHASH_PLACEHOLDER_CSS, BlurhashPlaceholderHeadless, clearBlurhashCache, getBlurhashCacheSize, preloadBlurhash };
@@ -0,0 +1,164 @@
1
+ export { n as COMMENT_SHEET_CSS, f as CommentInput, g as CommentInputProps, h as CommentItemComponent, i as CommentItemProps, d as CommentList, e as CommentListProps, C as CommentSheet, j as CommentSheetContext, l as CommentSheetContextValue, a as CommentSheetHeadless, b as CommentSheetHeadlessProps, m as CommentSheetI18n, R as ReplyTarget, S as SheetHeader, c as SheetHeaderProps, u as useCommentSheetContext, k as useOptionalCommentSheetContext } from '../../CommentSheet.css-BeCrEaUG.js';
2
+ import * as react from 'react';
3
+ import { ReactNode, ReactElement } from 'react';
4
+ import { CommentItem, ReplyItem } from '@xhub-short/contracts';
5
+
6
+ interface CommentMenuContextValue {
7
+ /** Comment data */
8
+ comment: CommentItem | ReplyItem;
9
+ /** Whether this is a reply */
10
+ isReply: boolean;
11
+ /** Parent comment ID (for replies) */
12
+ parentId?: string;
13
+ /** Whether menu is open */
14
+ isOpen: boolean;
15
+ /** Open menu */
16
+ open: () => void;
17
+ /** Close menu */
18
+ close: () => void;
19
+ /** Toggle menu */
20
+ toggle: () => void;
21
+ /** Trigger button ref */
22
+ triggerRef: React.RefObject<HTMLButtonElement | null>;
23
+ /** Menu container ref */
24
+ menuRef: React.RefObject<HTMLDivElement | null>;
25
+ /** Show confirmation dialog */
26
+ showConfirm: (config: ConfirmDialogConfig) => void;
27
+ /** i18n strings */
28
+ i18n: CommentMenuI18n;
29
+ }
30
+ interface ConfirmDialogConfig {
31
+ title: string;
32
+ message: string;
33
+ confirmText: string;
34
+ cancelText: string;
35
+ danger?: boolean;
36
+ onConfirm: () => Promise<void> | void;
37
+ }
38
+ interface CommentMenuI18n {
39
+ deleteText: string;
40
+ editText: string;
41
+ reportText: string;
42
+ deleteConfirmTitle: string;
43
+ deleteConfirmMessage: string;
44
+ deleteConfirmButton: string;
45
+ cancelButton: string;
46
+ }
47
+ declare function useCommentMenuContext(): CommentMenuContextValue;
48
+ interface DeleteConfirmDialogProps {
49
+ isOpen: boolean;
50
+ onClose: () => void;
51
+ onConfirm: () => void;
52
+ isDeleting?: boolean;
53
+ title?: string;
54
+ message?: string;
55
+ confirmText?: string;
56
+ cancelText?: string;
57
+ danger?: boolean;
58
+ }
59
+ declare const DeleteConfirmDialog: react.NamedExoticComponent<DeleteConfirmDialogProps>;
60
+ interface CommentMenuProps {
61
+ /** Comment or Reply data */
62
+ comment: CommentItem | ReplyItem;
63
+ /** Whether this is a reply */
64
+ isReply?: boolean;
65
+ /** Parent comment ID (required for replies) */
66
+ parentId?: string;
67
+ /** Custom class */
68
+ className?: string;
69
+ /** Children (compound components) */
70
+ children: ReactNode;
71
+ }
72
+ declare function CommentMenuRoot({ comment, isReply, parentId, className, children, }: CommentMenuProps): ReactElement;
73
+ interface CommentMenuTriggerProps {
74
+ /** Custom class */
75
+ className?: string;
76
+ /** Custom icon */
77
+ icon?: ReactNode;
78
+ /** Aria label */
79
+ 'aria-label'?: string;
80
+ }
81
+ declare function Trigger({ className, icon, 'aria-label': ariaLabel, }: CommentMenuTriggerProps): ReactElement;
82
+ interface CommentMenuDropdownProps {
83
+ /** Custom class */
84
+ className?: string;
85
+ /** Children (menu items) */
86
+ children: ReactNode;
87
+ }
88
+ declare function Dropdown({ className, children }: CommentMenuDropdownProps): ReactElement | null;
89
+ interface CommentMenuItemProps {
90
+ /** Icon element */
91
+ icon?: ReactNode;
92
+ /** Danger style (red) */
93
+ danger?: boolean;
94
+ /** Disabled state */
95
+ disabled?: boolean;
96
+ /** Custom class */
97
+ className?: string;
98
+ /** Click handler */
99
+ onClick?: () => void;
100
+ /** Children (label) */
101
+ children: ReactNode;
102
+ }
103
+ declare function Item({ icon, danger, disabled, className, onClick, children, }: CommentMenuItemProps): ReactElement;
104
+ interface DeleteItemProps {
105
+ /** Override confirm behavior (skip dialog if false) */
106
+ requireConfirm?: boolean;
107
+ /**
108
+ * Custom delete handler - overrides default context action
109
+ * @param id - Comment/Reply ID
110
+ * @param isReply - Whether this is a reply
111
+ * @param parentId - Parent comment ID (for replies)
112
+ */
113
+ onDelete?: (id: string, isReply: boolean, parentId?: string) => Promise<void> | void;
114
+ /** Custom label text */
115
+ label?: string;
116
+ /** Custom class */
117
+ className?: string;
118
+ }
119
+ declare function DeleteItem({ requireConfirm, onDelete, label, className }: DeleteItemProps): ReactElement;
120
+ interface EditItemProps {
121
+ /**
122
+ * Edit handler - receives comment ID and current content
123
+ * If not provided, item will be disabled
124
+ */
125
+ onEdit?: (id: string, content: string) => void;
126
+ /** Custom label text */
127
+ label?: string;
128
+ /** Custom class */
129
+ className?: string;
130
+ }
131
+ declare function EditItem({ onEdit, label, className }: EditItemProps): ReactElement;
132
+ interface ReportItemProps {
133
+ /**
134
+ * Custom report handler - overrides default context action
135
+ * @param id - Comment/Reply ID
136
+ */
137
+ onReport?: (id: string) => void;
138
+ /** Default report reason (used when onReport not provided) */
139
+ reason?: string;
140
+ /** Custom label text */
141
+ label?: string;
142
+ /** Custom class */
143
+ className?: string;
144
+ }
145
+ declare function ReportItem({ onReport, reason, label, className, }: ReportItemProps): ReactElement;
146
+ declare const CommentMenu: react.NamedExoticComponent<CommentMenuProps> & {
147
+ readonly type: typeof CommentMenuRoot;
148
+ } & {
149
+ Trigger: react.MemoExoticComponent<typeof Trigger>;
150
+ Dropdown: react.MemoExoticComponent<typeof Dropdown>;
151
+ Item: react.MemoExoticComponent<typeof Item>;
152
+ DeleteItem: react.MemoExoticComponent<typeof DeleteItem>;
153
+ EditItem: react.MemoExoticComponent<typeof EditItem>;
154
+ ReportItem: react.MemoExoticComponent<typeof ReportItem>;
155
+ };
156
+
157
+ declare const CommentItemSkeleton: react.NamedExoticComponent<object>;
158
+ interface CommentListSkeletonProps {
159
+ /** Number of skeleton items to show (default: 5) */
160
+ count?: number;
161
+ }
162
+ declare const CommentListSkeleton: react.NamedExoticComponent<CommentListSkeletonProps>;
163
+
164
+ export { CommentItemSkeleton, CommentListSkeleton, type CommentListSkeletonProps, CommentMenu, type CommentMenuContextValue, type CommentMenuDropdownProps, type CommentMenuI18n, type CommentMenuItemProps, type CommentMenuProps, type CommentMenuTriggerProps, type ConfirmDialogConfig, DeleteConfirmDialog, type DeleteConfirmDialogProps, type DeleteItemProps, type EditItemProps, type ReportItemProps, useCommentMenuContext };
@@ -0,0 +1 @@
1
+ export { COMMENT_SHEET_CSS, CommentInput, CommentItemComponent, CommentItemSkeleton, CommentList, CommentListSkeleton, CommentMenu, CommentSheet, CommentSheetContext, CommentSheetHeadless, DeleteConfirmDialog, SheetHeader, useCommentMenuContext, useCommentSheetContext, useOptionalCommentSheetContext } from '../../chunk-FNXTPQ6L.js';
@@ -1 +1 @@
1
- export { ErrorBoundary } from '../../chunk-SSJDO24Q.js';
1
+ export { ErrorBoundary } from '../../chunk-SZXFH334.js';
@@ -0,0 +1,56 @@
1
+ import { ReactNode } from 'react';
2
+
3
+ /**
4
+ * OfflineIndicatorHeadless - Network status indicator
5
+ *
6
+ * Shows a banner when network is offline or poor.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ /**
12
+ * Indicator status type
13
+ */
14
+ type IndicatorStatus = 'offline' | 'poor';
15
+ /**
16
+ * Props for OfflineIndicatorHeadless
17
+ */
18
+ interface OfflineIndicatorHeadlessProps {
19
+ /** Current connection status */
20
+ status: IndicatorStatus;
21
+ /** Whether to show the indicator */
22
+ isVisible: boolean;
23
+ /** Custom offline message */
24
+ offlineMessage?: string;
25
+ /** Custom poor connection message */
26
+ poorMessage?: string;
27
+ /** Custom icon */
28
+ icon?: ReactNode;
29
+ /** Additional CSS class */
30
+ className?: string;
31
+ /** Test ID */
32
+ testId?: string;
33
+ }
34
+ /**
35
+ * OfflineIndicatorHeadless - Network status indicator
36
+ *
37
+ * @example
38
+ * ```tsx
39
+ * const { isOnline, status } = useOnlineStatus();
40
+ *
41
+ * <OfflineIndicatorHeadless
42
+ * status={status === 'offline' ? 'offline' : 'poor'}
43
+ * isVisible={!isOnline || status === 'poor'}
44
+ * />
45
+ * ```
46
+ */
47
+ declare function OfflineIndicatorHeadless({ status, isVisible, offlineMessage, poorMessage, icon, className, testId, }: OfflineIndicatorHeadlessProps): React.ReactElement | null;
48
+
49
+ /**
50
+ * OfflineIndicator CSS
51
+ *
52
+ * Styles for network status indicator.
53
+ */
54
+ declare const OFFLINE_INDICATOR_CSS = "\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * OFFLINE INDICATOR\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n.sv-offline-indicator {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 8px;\n padding: 8px 16px;\n background: var(--sv-offline-bg, rgba(220, 53, 69, 0.95));\n color: var(--sv-offline-color, #fff);\n font-size: 14px;\n font-weight: 500;\n z-index: var(--sv-z-toast, 1200);\n animation: sv-offline-slide-down 200ms ease-out;\n}\n\n.sv-offline-indicator--poor {\n background: var(--sv-offline-poor-bg, rgba(255, 193, 7, 0.95));\n color: var(--sv-offline-poor-color, #000);\n}\n\n.sv-offline-indicator__icon {\n flex-shrink: 0;\n width: 16px;\n height: 16px;\n}\n\n.sv-offline-indicator__message {\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * ANIMATIONS\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n@keyframes sv-offline-slide-down {\n from {\n transform: translateY(-100%);\n }\n to {\n transform: translateY(0);\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * REDUCE MOTION\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n@media (prefers-reduced-motion: reduce) {\n .sv-offline-indicator {\n animation: none;\n }\n}\n\n/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n * SAFE AREA (Notch devices)\n * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */\n\n@supports (padding-top: env(safe-area-inset-top)) {\n .sv-offline-indicator {\n padding-top: calc(8px + env(safe-area-inset-top));\n }\n}\n";
55
+
56
+ export { type IndicatorStatus, OFFLINE_INDICATOR_CSS, OfflineIndicatorHeadless, type OfflineIndicatorHeadlessProps };
@@ -0,0 +1,151 @@
1
+ import { injectComponentCSS } from '../../chunk-RMLTPW5S.js';
2
+ import { clsx } from 'clsx';
3
+ import { useInsertionEffect } from 'react';
4
+ import { jsxs, jsx } from 'react/jsx-runtime';
5
+
6
+ // src/components/OfflineIndicator/OfflineIndicator.css.ts
7
+ var OFFLINE_INDICATOR_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
+ * OFFLINE INDICATOR
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-offline-indicator {
13
+ position: fixed;
14
+ top: 0;
15
+ left: 0;
16
+ right: 0;
17
+ display: flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ gap: 8px;
21
+ padding: 8px 16px;
22
+ background: var(--sv-offline-bg, rgba(220, 53, 69, 0.95));
23
+ color: var(--sv-offline-color, #fff);
24
+ font-size: 14px;
25
+ font-weight: 500;
26
+ z-index: var(--sv-z-toast, 1200);
27
+ animation: sv-offline-slide-down 200ms ease-out;
28
+ }
29
+
30
+ .sv-offline-indicator--poor {
31
+ background: var(--sv-offline-poor-bg, rgba(255, 193, 7, 0.95));
32
+ color: var(--sv-offline-poor-color, #000);
33
+ }
34
+
35
+ .sv-offline-indicator__icon {
36
+ flex-shrink: 0;
37
+ width: 16px;
38
+ height: 16px;
39
+ }
40
+
41
+ .sv-offline-indicator__message {
42
+ white-space: nowrap;
43
+ overflow: hidden;
44
+ text-overflow: ellipsis;
45
+ }
46
+
47
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
48
+ * ANIMATIONS
49
+ * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
50
+
51
+ @keyframes sv-offline-slide-down {
52
+ from {
53
+ transform: translateY(-100%);
54
+ }
55
+ to {
56
+ transform: translateY(0);
57
+ }
58
+ }
59
+
60
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
61
+ * REDUCE MOTION
62
+ * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
63
+
64
+ @media (prefers-reduced-motion: reduce) {
65
+ .sv-offline-indicator {
66
+ animation: none;
67
+ }
68
+ }
69
+
70
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
71
+ * SAFE AREA (Notch devices)
72
+ * \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 */
73
+
74
+ @supports (padding-top: env(safe-area-inset-top)) {
75
+ .sv-offline-indicator {
76
+ padding-top: calc(8px + env(safe-area-inset-top));
77
+ }
78
+ }
79
+ `;
80
+ var DEFAULT_MESSAGES = {
81
+ offline: "No internet connection",
82
+ poor: "Poor connection"
83
+ };
84
+ function OfflineIcon() {
85
+ return /* @__PURE__ */ jsx(
86
+ "svg",
87
+ {
88
+ className: "sv-offline-indicator__icon",
89
+ width: "16",
90
+ height: "16",
91
+ viewBox: "0 0 24 24",
92
+ fill: "currentColor",
93
+ "aria-hidden": "true",
94
+ children: /* @__PURE__ */ jsx("path", { d: "M23.64 7c-.45-.34-4.93-4-11.64-4-1.5 0-2.89.19-4.15.48L18.18 13.8 23.64 7zM17.04 15.22L3.27 1.44 2 2.72l2.05 2.06C1.91 5.76.59 6.82.36 7L12 21.5l3.07-4.21 4.54 4.54 1.27-1.27-3.84-3.85zM12 4c1.23 0 2.37.14 3.42.36l-3.45 4.74L12 4z" })
95
+ }
96
+ );
97
+ }
98
+ function PoorConnectionIcon() {
99
+ return /* @__PURE__ */ jsxs(
100
+ "svg",
101
+ {
102
+ className: "sv-offline-indicator__icon",
103
+ width: "16",
104
+ height: "16",
105
+ viewBox: "0 0 24 24",
106
+ fill: "currentColor",
107
+ "aria-hidden": "true",
108
+ children: [
109
+ /* @__PURE__ */ jsx(
110
+ "path",
111
+ {
112
+ d: "M12 3C7.46 3 3.34 4.78.29 7.67c-.18.18-.29.43-.29.71 0 .28.11.53.29.71l11 11c.18.18.43.29.71.29s.53-.11.71-.29l11-11c.18-.18.29-.43.29-.71 0-.28-.11-.53-.29-.71C20.66 4.78 16.54 3 12 3zM2.92 8.38C5.43 6.34 8.59 5 12 5s6.57 1.34 9.08 3.38L12 17.5 2.92 8.38z",
113
+ opacity: "0.5"
114
+ }
115
+ ),
116
+ /* @__PURE__ */ jsx("path", { d: "M12 21.5l4.5-6.19c-.93-.76-2.14-1.31-3.5-1.31s-2.57.55-3.5 1.31L12 21.5z" })
117
+ ]
118
+ }
119
+ );
120
+ }
121
+ function OfflineIndicatorHeadless({
122
+ status,
123
+ isVisible,
124
+ offlineMessage,
125
+ poorMessage,
126
+ icon,
127
+ className,
128
+ testId
129
+ }) {
130
+ useInsertionEffect(() => {
131
+ return injectComponentCSS("offline-indicator", OFFLINE_INDICATOR_CSS);
132
+ }, []);
133
+ if (!isVisible) return null;
134
+ const message = status === "offline" ? offlineMessage ?? DEFAULT_MESSAGES.offline : poorMessage ?? DEFAULT_MESSAGES.poor;
135
+ const defaultIcon = status === "offline" ? /* @__PURE__ */ jsx(OfflineIcon, {}) : /* @__PURE__ */ jsx(PoorConnectionIcon, {});
136
+ return /* @__PURE__ */ jsxs(
137
+ "div",
138
+ {
139
+ role: "alert",
140
+ "aria-live": "assertive",
141
+ className: clsx("sv-offline-indicator", `sv-offline-indicator--${status}`, className),
142
+ "data-testid": testId,
143
+ children: [
144
+ icon ?? defaultIcon,
145
+ /* @__PURE__ */ jsx("span", { className: "sv-offline-indicator__message", children: message })
146
+ ]
147
+ }
148
+ );
149
+ }
150
+
151
+ export { OFFLINE_INDICATOR_CSS, OfflineIndicatorHeadless };