@xhub-short/ui 0.1.0-beta.0

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,204 @@
1
+ import { injectComponentCSS } from './chunk-UXMA4KJZ.js';
2
+ import { useInsertionEffect, Component } from 'react';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
+
5
+ // src/components/ErrorBoundary/ErrorBoundary.css.ts
6
+ var ERROR_BOUNDARY_CSS = `
7
+ /* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550
8
+ * ErrorBoundary Styles
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
+
11
+ .sv-error-boundary {
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ width: 100%;
16
+ height: 100%;
17
+ background: var(--sv-bg-primary, #000);
18
+ color: var(--sv-text-primary, #fff);
19
+ font-family: var(--sv-font-family, 'Urbanist', sans-serif);
20
+ }
21
+
22
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
23
+ * Error State
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
+
26
+ .sv-error-boundary--error .sv-error-boundary__content {
27
+ display: flex;
28
+ flex-direction: column;
29
+ align-items: center;
30
+ gap: var(--sv-spacing-md, 16px);
31
+ padding: var(--sv-spacing-lg, 24px);
32
+ text-align: center;
33
+ }
34
+
35
+ .sv-error-boundary__icon {
36
+ font-size: 48px;
37
+ }
38
+
39
+ .sv-error-boundary__message {
40
+ font-size: var(--sv-font-size-md, 14px);
41
+ color: var(--sv-text-secondary, #999);
42
+ }
43
+
44
+ .sv-error-boundary__retry {
45
+ padding: var(--sv-spacing-sm, 8px) var(--sv-spacing-lg, 24px);
46
+ border: none;
47
+ border-radius: var(--sv-border-radius-md, 8px);
48
+ background: var(--sv-color-primary, #fe2c55);
49
+ color: #fff;
50
+ font-size: var(--sv-font-size-sm, 13px);
51
+ font-weight: var(--sv-font-weight-medium, 500);
52
+ cursor: pointer;
53
+ transition: opacity 0.2s ease;
54
+ }
55
+
56
+ .sv-error-boundary__retry:hover {
57
+ opacity: 0.9;
58
+ }
59
+
60
+ .sv-error-boundary__retry:active {
61
+ opacity: 0.8;
62
+ }
63
+
64
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
65
+ * Toast State (auto-skip with notification)
66
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
67
+
68
+ .sv-error-boundary--toast {
69
+ position: relative;
70
+ }
71
+
72
+ .sv-error-boundary__toast {
73
+ position: absolute;
74
+ bottom: var(--sv-spacing-xl, 32px);
75
+ left: 50%;
76
+ transform: translateX(-50%);
77
+ padding: var(--sv-spacing-sm, 8px) var(--sv-spacing-md, 16px);
78
+ border-radius: var(--sv-border-radius-md, 8px);
79
+ background: rgba(0, 0, 0, 0.8);
80
+ color: #fff;
81
+ font-size: var(--sv-font-size-sm, 13px);
82
+ white-space: nowrap;
83
+ animation: sv-toast-fade-in 0.3s ease;
84
+ }
85
+
86
+ @keyframes sv-toast-fade-in {
87
+ from {
88
+ opacity: 0;
89
+ transform: translateX(-50%) translateY(10px);
90
+ }
91
+ to {
92
+ opacity: 1;
93
+ transform: translateX(-50%) translateY(0);
94
+ }
95
+ }
96
+
97
+ /* \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
98
+ * Loading State (silent auto-skip)
99
+ * \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
100
+
101
+ .sv-error-boundary--loading {
102
+ background: var(--sv-bg-primary, #000);
103
+ }
104
+
105
+ .sv-error-boundary__spinner {
106
+ width: 32px;
107
+ height: 32px;
108
+ border: 3px solid rgba(255, 255, 255, 0.2);
109
+ border-top-color: var(--sv-color-primary, #fe2c55);
110
+ border-radius: 50%;
111
+ animation: sv-spinner 0.8s linear infinite;
112
+ }
113
+
114
+ @keyframes sv-spinner {
115
+ to {
116
+ transform: rotate(360deg);
117
+ }
118
+ }
119
+ `;
120
+ var ErrorBoundaryInternal = class extends Component {
121
+ constructor(props) {
122
+ super(props);
123
+ this.skipTimeoutId = null;
124
+ this.reset = () => {
125
+ const { onReset } = this.props;
126
+ this.clearSkipTimeout();
127
+ this.setState({ hasError: false, error: null, errorInfo: null });
128
+ onReset?.();
129
+ };
130
+ this.state = { hasError: false, error: null, errorInfo: null };
131
+ }
132
+ static getDerivedStateFromError(error) {
133
+ return { hasError: true, error };
134
+ }
135
+ componentDidCatch(error, errorInfo) {
136
+ const { onError, mode = "show-error", skipDelay = 1e3 } = this.props;
137
+ this.setState({ errorInfo });
138
+ onError?.(error, errorInfo);
139
+ if (!this.skipTimeoutId && (mode === "auto-skip" || mode === "auto-skip-with-toast")) {
140
+ this.skipTimeoutId = setTimeout(() => {
141
+ this.handleSkip();
142
+ }, skipDelay);
143
+ }
144
+ }
145
+ componentWillUnmount() {
146
+ this.clearSkipTimeout();
147
+ }
148
+ /**
149
+ * Handle skip action with error safeguard
150
+ * Wraps onSkip in try/finally to ensure reset() is always called
151
+ */
152
+ handleSkip() {
153
+ const { onSkip } = this.props;
154
+ try {
155
+ onSkip?.();
156
+ } finally {
157
+ this.reset();
158
+ }
159
+ }
160
+ clearSkipTimeout() {
161
+ if (this.skipTimeoutId) {
162
+ clearTimeout(this.skipTimeoutId);
163
+ this.skipTimeoutId = null;
164
+ }
165
+ }
166
+ render() {
167
+ const {
168
+ children,
169
+ fallback,
170
+ mode = "show-error",
171
+ toastMessage = "Video unavailable, skipping..."
172
+ } = this.props;
173
+ const { hasError, error, errorInfo } = this.state;
174
+ if (!hasError) {
175
+ return children;
176
+ }
177
+ if (fallback) {
178
+ if (typeof fallback === "function") {
179
+ return fallback(error ?? new Error("Unknown error"), errorInfo, this.reset);
180
+ }
181
+ return fallback;
182
+ }
183
+ if (mode === "auto-skip-with-toast") {
184
+ return /* @__PURE__ */ jsx("div", { className: "sv-error-boundary sv-error-boundary--toast", children: /* @__PURE__ */ jsx("div", { className: "sv-error-boundary__toast", children: toastMessage }) });
185
+ }
186
+ if (mode === "auto-skip") {
187
+ return /* @__PURE__ */ jsx("div", { className: "sv-error-boundary sv-error-boundary--loading" });
188
+ }
189
+ return /* @__PURE__ */ jsx("div", { className: "sv-error-boundary sv-error-boundary--error", children: /* @__PURE__ */ jsxs("div", { className: "sv-error-boundary__content", children: [
190
+ /* @__PURE__ */ jsx("div", { className: "sv-error-boundary__icon", children: "\u26A0\uFE0F" }),
191
+ /* @__PURE__ */ jsx("div", { className: "sv-error-boundary__message", children: "Something went wrong" }),
192
+ /* @__PURE__ */ jsx("button", { type: "button", className: "sv-error-boundary__retry", onClick: this.reset, children: "Try Again" })
193
+ ] }) });
194
+ }
195
+ };
196
+ function ErrorBoundary(props) {
197
+ const { cssKey = "sv-error-boundary" } = props;
198
+ useInsertionEffect(() => {
199
+ return injectComponentCSS(cssKey, ERROR_BOUNDARY_CSS);
200
+ }, [cssKey]);
201
+ return /* @__PURE__ */ jsx(ErrorBoundaryInternal, { ...props });
202
+ }
203
+
204
+ export { ErrorBoundary };