not-a-spinner 0.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,506 @@
1
+ // src/utils.ts
2
+ import { clsx } from "clsx";
3
+ import { twMerge } from "tailwind-merge";
4
+ function cn(...inputs) {
5
+ return twMerge(clsx(inputs));
6
+ }
7
+
8
+ // src/not-a-spinner.tsx
9
+ import * as React from "react";
10
+ import { cva } from "class-variance-authority";
11
+
12
+ // src/use-text-rotation.ts
13
+ import { useState, useEffect, useCallback, useRef } from "react";
14
+
15
+ // src/phrases.ts
16
+ var phrases = {
17
+ en: [
18
+ "Contemplating the meaning of null",
19
+ "Asking the rubber duck",
20
+ "Reticulating splines",
21
+ "Consulting the ancient scrolls",
22
+ "Negotiating with the API gods",
23
+ "Untangling spaghetti code",
24
+ "Feeding the hamsters that power the server",
25
+ "Converting caffeine to code",
26
+ "Aligning the cosmic bits",
27
+ "Performing mass speculation",
28
+ "Warming up the neural pathways",
29
+ "Bribing the cache fairy",
30
+ "Dividing by almost zero",
31
+ "Searching for the missing semicolon",
32
+ "Teaching electrons to think",
33
+ "Compiling excuses",
34
+ "Reversing entropy locally",
35
+ "Summoning the async wizard",
36
+ "Polishing the pixels",
37
+ "Calculating the meaning of life",
38
+ "Downloading more RAM",
39
+ "Defragmenting the thought process",
40
+ "Poking the language model",
41
+ "Waiting for the stars to align",
42
+ "Generating plausible nonsense",
43
+ "Consulting the magic 8-ball API",
44
+ "Herding digital cats",
45
+ "Overthinking at machine speed",
46
+ "Pretending to be busy",
47
+ "Running on vibes and vectors"
48
+ ],
49
+ zh: [
50
+ "\u8BA9AI\u518D\u60F3\u60F3",
51
+ "\u6B63\u5728\u6478\u9C7C\u5F0F\u601D\u8003",
52
+ "\u6B63\u5728\u7FFB\u9605\u7956\u4F20\u4EE3\u7801",
53
+ "\u6B63\u5728\u548Cbug\u8C08\u5224",
54
+ "\u75AF\u72C2\u6697\u793A\u670D\u52A1\u5668\u52A0\u901F",
55
+ "\u6B63\u5728\u8585\u7F8A\u6BDB\u5F0F\u8BA1\u7B97",
56
+ "\u5047\u88C5\u5F88\u5FD9\u7684\u6837\u5B50",
57
+ "\u6B63\u5728\u91CF\u5B50\u7EA0\u7F20\u4E2D",
58
+ "\u8BA9\u5B50\u5F39\u98DE\u4E00\u4F1A\u513F",
59
+ "\u6B63\u5728\u52A0\u8F7D\u4EBA\u751F\u7684\u610F\u4E49",
60
+ "\u6B63\u5728\u6478\u7D22\u5B87\u5B99\u7684\u89C4\u5F8B",
61
+ "\u8DDF\u7F13\u5B58\u7CBE\u7075\u8BA8\u4EF7\u8FD8\u4EF7",
62
+ "\u6B63\u5728\u70BC\u4E39",
63
+ "\u8BF7\u6C42\u795E\u7ECF\u7F51\u7EDC\u5F00\u4E2A\u540E\u95E8",
64
+ "\u6B63\u5728\u6D88\u5316\u4F60\u7684\u8BF7\u6C42",
65
+ "\u6316\u77FF\u4E2D\u8BF7\u7A0D\u5019",
66
+ "\u6B63\u5728\u7FFB\u8BD1\u673A\u5668\u8BED\u8A00",
67
+ "\u5927\u8111\u6B63\u5728\u8D85\u9891\u8FD0\u884C",
68
+ "\u6B63\u5728\u627E\u7075\u611F",
69
+ "\u5411\u670D\u52A1\u5668\u53D1\u9001\u8D3F\u8D42",
70
+ "\u6B63\u5728\u8FDB\u884C\u610F\u5FF5\u52A0\u901F",
71
+ "\u8BA9\u4EE3\u7801\u5148\u8DD1\u4E00\u4F1A\u513F",
72
+ "\u6B63\u5728\u65BD\u5C55\u6570\u636E\u9B54\u6CD5",
73
+ "\u5077\u5077\u67E5\u770B\u7B54\u6848\u4E2D",
74
+ "\u6B63\u5728\u89E3\u5F00\u859B\u5B9A\u8C14\u7684Bug",
75
+ "\u62FC\u547D\u601D\u8003\u4E0D\u8981\u50AC",
76
+ "\u6B63\u5728\u548CAI\u540C\u4E8B\u5F00\u4F1A",
77
+ "\u7A7F\u8D8A\u6570\u636E\u7684\u661F\u8FB0\u5927\u6D77",
78
+ "\u6B63\u5728\u8FDB\u884C\u7075\u9B42\u62F7\u95EE",
79
+ "\u7B49\u7B49\u9A6C\u4E0A\u5C31\u597D"
80
+ ],
81
+ ja: [
82
+ "AI\u304C\u60A9\u3093\u3067\u3044\u307E\u3059",
83
+ "\u30B9\u30D1\u30B2\u30C3\u30C6\u30A3\u30B3\u30FC\u30C9\u3092\u89E3\u8AAD\u4E2D",
84
+ "\u30B4\u30E0\u306E\u30A2\u30D2\u30EB\u306B\u76F8\u8AC7\u4E2D",
85
+ "\u30B5\u30FC\u30D0\u30FC\u306E\u30CF\u30E0\u30B9\u30BF\u30FC\u3092\u8D77\u52D5\u4E2D",
86
+ "\u30AB\u30D5\u30A7\u30A4\u30F3\u3092\u30B3\u30FC\u30C9\u306B\u5909\u63DB\u4E2D",
87
+ "\u5B87\u5B99\u306E\u30D3\u30C3\u30C8\u3092\u6574\u5217\u4E2D",
88
+ "\u30BB\u30DF\u30B3\u30ED\u30F3\u3092\u63A2\u7D22\u4E2D",
89
+ "\u96FB\u5B50\u306B\u8003\u3048\u308B\u3053\u3068\u3092\u6559\u80B2\u4E2D",
90
+ "\u8A00\u3044\u8A33\u3092\u30B3\u30F3\u30D1\u30A4\u30EB\u4E2D",
91
+ "\u975E\u540C\u671F\u306E\u9B54\u6CD5\u4F7F\u3044\u3092\u53EC\u559A\u4E2D",
92
+ "\u30D4\u30AF\u30BB\u30EB\u3092\u78E8\u304D\u4E2D",
93
+ "\u4EBA\u751F\u306E\u610F\u5473\u3092\u8A08\u7B97\u4E2D",
94
+ "RAM\u3092\u30C0\u30A6\u30F3\u30ED\u30FC\u30C9\u4E2D",
95
+ "\u601D\u8003\u30D7\u30ED\u30BB\u30B9\u3092\u30C7\u30D5\u30E9\u30B0\u4E2D",
96
+ "\u30DE\u30B7\u30F3\u901F\u5EA6\u3067\u8003\u3048\u3059\u304E\u4E2D",
97
+ "\u91CF\u5B50\u306E\u3082\u3064\u308C\u3092\u89E3\u6D88\u4E2D",
98
+ "API\u306E\u795E\u69D8\u3068\u4EA4\u6E09\u4E2D",
99
+ "\u30C7\u30B8\u30BF\u30EB\u732B\u3092\u8FFD\u3044\u304B\u3051\u4E2D",
100
+ "\u30CB\u30E5\u30FC\u30E9\u30EB\u30D1\u30B9\u3092\u6696\u3081\u4E2D",
101
+ "\u30C7\u30FC\u30BF\u306E\u6D77\u3092\u6CF3\u3044\u3067\u3044\u307E\u3059",
102
+ "\u30D0\u30B0\u3068\u548C\u89E3\u4EA4\u6E09\u4E2D",
103
+ "\u5FD9\u3057\u3044\u3075\u308A\u3092\u3057\u3066\u3044\u307E\u3059",
104
+ "\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u304C\u7791\u60F3\u4E2D",
105
+ "\u9B54\u6CD5\u306E8\u30DC\u30FC\u30EB\u306B\u805E\u3044\u3066\u307E\u3059",
106
+ "\u3082\u3046\u3061\u3087\u3063\u3068\u5F85\u3063\u3066\u306D",
107
+ "\u5168\u529B\u3067\u51E6\u7406\u3057\u3066\u3044\u307E\u3059",
108
+ "\u7B54\u3048\u306E\u8FD1\u304F\u306B\u3044\u307E\u3059",
109
+ "\u5929\u624D\u7684\u306A\u3072\u3089\u3081\u304D\u3092\u5F85\u6A5F\u4E2D",
110
+ "\u30B3\u30FC\u30D2\u30FC\u30D6\u30EC\u30A4\u30AF\u4E2D\u3067\u3059",
111
+ "\u9811\u5F35\u3063\u3066\u8003\u3048\u4E2D"
112
+ ],
113
+ es: [
114
+ "Consultando al or\xE1culo digital",
115
+ "Pensando intensamente",
116
+ "Negociando con los dioses del API",
117
+ "Desenredando c\xF3digo espagueti",
118
+ "Alimentando los h\xE1msters del servidor",
119
+ "Convirtiendo cafe\xEDna en c\xF3digo",
120
+ "Alineando los bits c\xF3smicos",
121
+ "Buscando el punto y coma perdido",
122
+ "Ense\xF1ando a pensar a los electrones",
123
+ "Compilando excusas creativas",
124
+ "Invocando al mago as\xEDncrono",
125
+ "Puliendo los p\xEDxeles",
126
+ "Calculando el sentido de la vida",
127
+ "Descargando m\xE1s RAM",
128
+ "Preguntando al pato de goma",
129
+ "Pastoreando gatos digitales",
130
+ "Sobrepensando a velocidad m\xE1quina",
131
+ "Fingiendo estar ocupado",
132
+ "Sobornando al hada del cach\xE9",
133
+ "Dividiendo por casi cero",
134
+ "Generando tonter\xEDas plausibles",
135
+ "Calentando las rutas neuronales",
136
+ "Esperando que se alineen las estrellas",
137
+ "Haciendo magia con los datos",
138
+ "Meditando algor\xEDtmicamente",
139
+ "Revolviendo la sopa cu\xE1ntica",
140
+ "Susurrando al servidor",
141
+ "Recalibrando la realidad",
142
+ "Casi casi ya casi",
143
+ "Un momento de genialidad en curso"
144
+ ],
145
+ fr: [
146
+ "Consultation de l'oracle num\xE9rique",
147
+ "R\xE9flexion intense en cours",
148
+ "N\xE9gociation avec les dieux de l'API",
149
+ "D\xE9m\xEAlage de code spaghetti",
150
+ "Conversion de caf\xE9ine en code",
151
+ "Alignement des bits cosmiques",
152
+ "Recherche du point-virgule perdu",
153
+ "Compilation d'excuses cr\xE9atives",
154
+ "Invocation du sorcier asynchrone",
155
+ "Polissage des pixels",
156
+ "Calcul du sens de la vie",
157
+ "T\xE9l\xE9chargement de RAM suppl\xE9mentaire",
158
+ "Consultation du canard en caoutchouc",
159
+ "Rassemblement de chats num\xE9riques",
160
+ "Surr\xE9flexion \xE0 vitesse machine",
161
+ "Simulation d'activit\xE9 intense",
162
+ "Corruption de la f\xE9e du cache",
163
+ "R\xE9chauffement des voies neuronales",
164
+ "G\xE9n\xE9ration de b\xEAtises plausibles",
165
+ "M\xE9ditation algorithmique",
166
+ "Persuasion quantique en cours",
167
+ "Interrogation de la boule magique",
168
+ "D\xE9fragmentation de la pens\xE9e",
169
+ "Dressage des \xE9lectrons pensants",
170
+ "Attente de l'alignement stellaire",
171
+ "Magie des donn\xE9es en cours",
172
+ "Murmures au serveur",
173
+ "Recalibrage de la r\xE9alit\xE9",
174
+ "Presque presque bient\xF4t",
175
+ "Un \xE9clair de g\xE9nie arrive"
176
+ ],
177
+ de: [
178
+ "Algorithmus gr\xFCbelt nach",
179
+ "Daten werden meditiert",
180
+ "Verhandlung mit den API-G\xF6ttern",
181
+ "Spaghetti-Code wird entwirrt",
182
+ "Server-Hamster werden gef\xFCttert",
183
+ "Koffein wird in Code umgewandelt",
184
+ "Kosmische Bits werden ausgerichtet",
185
+ "Das fehlende Semikolon wird gesucht",
186
+ "Elektronen lernen das Denken",
187
+ "Ausreden werden kompiliert",
188
+ "Der asynchrone Zauberer wird beschworen",
189
+ "Pixel werden poliert",
190
+ "Der Sinn des Lebens wird berechnet",
191
+ "Mehr RAM wird heruntergeladen",
192
+ "Die Gummiente wird befragt",
193
+ "Digitale Katzen werden geh\xFCtet",
194
+ "Maschinengeschwindigkeit-\xDCberdenken",
195
+ "Besch\xE4ftigt-Sein wird simuliert",
196
+ "Die Cache-Fee wird bestochen",
197
+ "Beinahe durch Null geteilt",
198
+ "Plausibeler Unsinn wird generiert",
199
+ "Neuronale Pfade werden aufgew\xE4rmt",
200
+ "Quantensuppe wird umger\xFChrt",
201
+ "Sternenausrichtung wird abgewartet",
202
+ "Datenmagie wird ausgef\xFChrt",
203
+ "Dem Server wird zugefl\xFCstert",
204
+ "Realit\xE4t wird neu kalibriert",
205
+ "Gleich gleich fast fertig",
206
+ "Ein Geistesblitz ist unterwegs",
207
+ "Intensives Nachdenken l\xE4uft"
208
+ ],
209
+ ko: [
210
+ "AI\uAC00 \uACE0\uBBFC \uC911",
211
+ "\uB370\uC774\uD130\uB97C \uBA85\uC0C1 \uC911",
212
+ "\uACE0\uBB34 \uC624\uB9AC\uC5D0\uAC8C \uC0C1\uB2F4 \uC911",
213
+ "\uC11C\uBC84 \uD584\uC2A4\uD130\uC5D0\uAC8C \uBC25 \uC8FC\uB294 \uC911",
214
+ "\uCE74\uD398\uC778\uC744 \uCF54\uB4DC\uB85C \uBCC0\uD658 \uC911",
215
+ "\uC6B0\uC8FC \uBE44\uD2B8\uB97C \uC815\uB82C \uC911",
216
+ "\uC783\uC5B4\uBC84\uB9B0 \uC138\uBBF8\uCF5C\uB860 \uCC3E\uB294 \uC911",
217
+ "\uC804\uC790\uC5D0\uAC8C \uC0DD\uAC01\uD558\uB294 \uBC95 \uAC00\uB974\uCE58\uB294 \uC911",
218
+ "\uBCC0\uBA85\uC744 \uCEF4\uD30C\uC77C \uC911",
219
+ "\uBE44\uB3D9\uAE30 \uB9C8\uBC95\uC0AC \uC18C\uD658 \uC911",
220
+ "\uD53D\uC140\uC744 \uB2E6\uB294 \uC911",
221
+ "\uC778\uC0DD\uC758 \uC758\uBBF8 \uACC4\uC0B0 \uC911",
222
+ "RAM \uB2E4\uC6B4\uB85C\uB4DC \uC911",
223
+ "\uB514\uC9C0\uD138 \uACE0\uC591\uC774 \uBAB0\uC774 \uC911",
224
+ "\uBA38\uC2E0 \uC18D\uB3C4\uB85C \uACFC\uB3C4\uD558\uAC8C \uC0DD\uAC01 \uC911",
225
+ "\uBC14\uC05C \uCC99\uD558\uB294 \uC911",
226
+ "\uCE90\uC2DC \uC694\uC815\uC5D0\uAC8C \uB1CC\uBB3C \uC8FC\uB294 \uC911",
227
+ "\uAC70\uC758 0\uC73C\uB85C \uB098\uB204\uB294 \uC911",
228
+ "\uADF8\uB7F4\uB4EF\uD55C \uD5DB\uC18C\uB9AC \uC0DD\uC131 \uC911",
229
+ "\uC2E0\uACBD \uACBD\uB85C \uC6CC\uBC0D\uC5C5 \uC911",
230
+ "\uC591\uC790 \uC218\uD504 \uC813\uB294 \uC911",
231
+ "\uBCC4\uC774 \uC815\uB82C\uB418\uAE38 \uAE30\uB2E4\uB9AC\uB294 \uC911",
232
+ "\uB370\uC774\uD130 \uB9C8\uBC95 \uC2DC\uC804 \uC911",
233
+ "\uC11C\uBC84\uC5D0\uAC8C \uC18D\uC0AD\uC774\uB294 \uC911",
234
+ "\uD604\uC2E4 \uC7AC\uBCF4\uC815 \uC911",
235
+ "\uAC70\uC758 \uAC70\uC758 \uB2E4 \uB410\uC5B4\uC694",
236
+ "\uCC9C\uC7AC\uC801 \uC601\uAC10 \uB300\uAE30 \uC911",
237
+ "\uC2A4\uD30C\uAC8C\uD2F0 \uCF54\uB4DC \uD574\uB3C5 \uC911",
238
+ "API \uC2E0\uACFC \uD611\uC0C1 \uC911",
239
+ "\uC5F4\uC2EC\uD788 \uC0DD\uAC01\uD558\uB294 \uC911"
240
+ ]
241
+ };
242
+ function shufflePhrases(input) {
243
+ const arr = [...input];
244
+ for (let i = arr.length - 1; i > 0; i--) {
245
+ const j = Math.floor(Math.random() * (i + 1));
246
+ [arr[i], arr[j]] = [arr[j], arr[i]];
247
+ }
248
+ return arr;
249
+ }
250
+ function getPhrasesForConfig(messages, locale) {
251
+ if (messages && messages.length > 0) return messages;
252
+ if (locale && phrases[locale]) return phrases[locale];
253
+ return phrases.en;
254
+ }
255
+
256
+ // src/use-text-rotation.ts
257
+ function useTextRotation({
258
+ phrases: inputPhrases,
259
+ interval = 3e3,
260
+ animationDuration = 400
261
+ }) {
262
+ const [currentIndex, setCurrentIndex] = useState(0);
263
+ const [isExiting, setIsExiting] = useState(false);
264
+ const [isEntering, setIsEntering] = useState(false);
265
+ const poolRef = useRef(inputPhrases);
266
+ const initializedRef = useRef(false);
267
+ const timerRef = useRef(null);
268
+ const mountedRef = useRef(true);
269
+ useEffect(() => {
270
+ if (!initializedRef.current) {
271
+ poolRef.current = shufflePhrases(inputPhrases);
272
+ initializedRef.current = true;
273
+ setCurrentIndex(0);
274
+ }
275
+ }, []);
276
+ useEffect(() => {
277
+ poolRef.current = shufflePhrases(inputPhrases);
278
+ setCurrentIndex(0);
279
+ }, [inputPhrases]);
280
+ const appendPhrases = useCallback((newPhrases) => {
281
+ const current = poolRef.current;
282
+ const combined = [...current, ...newPhrases];
283
+ if (combined.length > 20) {
284
+ poolRef.current = combined.slice(combined.length - 20);
285
+ } else {
286
+ poolRef.current = combined;
287
+ }
288
+ }, []);
289
+ useEffect(() => {
290
+ mountedRef.current = true;
291
+ if (poolRef.current.length <= 1) return;
292
+ timerRef.current = setInterval(() => {
293
+ if (!mountedRef.current) return;
294
+ setIsExiting(true);
295
+ setTimeout(() => {
296
+ if (!mountedRef.current) return;
297
+ setIsExiting(false);
298
+ setCurrentIndex((prev) => {
299
+ const next = prev + 1;
300
+ if (next >= poolRef.current.length) {
301
+ poolRef.current = shufflePhrases(poolRef.current);
302
+ return 0;
303
+ }
304
+ return next;
305
+ });
306
+ setIsEntering(true);
307
+ setTimeout(() => {
308
+ if (!mountedRef.current) return;
309
+ setIsEntering(false);
310
+ }, animationDuration);
311
+ }, animationDuration);
312
+ }, interval);
313
+ return () => {
314
+ mountedRef.current = false;
315
+ if (timerRef.current) {
316
+ clearInterval(timerRef.current);
317
+ timerRef.current = null;
318
+ }
319
+ };
320
+ }, [interval, animationDuration]);
321
+ const pool = poolRef.current;
322
+ const phrase = pool.length > 0 ? pool[currentIndex % pool.length] : "";
323
+ return {
324
+ currentPhrase: phrase,
325
+ isExiting,
326
+ isEntering,
327
+ appendPhrases
328
+ };
329
+ }
330
+
331
+ // src/not-a-spinner.tsx
332
+ import { jsx, jsxs } from "react/jsx-runtime";
333
+ var notASpinnerVariants = cva("nas-base", {
334
+ variants: {
335
+ size: {
336
+ sm: "nas-sm",
337
+ default: "nas-default",
338
+ lg: "nas-lg"
339
+ },
340
+ animation: {
341
+ fade: "nas-animation-fade",
342
+ typewriter: "nas-animation-typewriter",
343
+ slide: "nas-animation-slide",
344
+ blur: "nas-animation-blur"
345
+ }
346
+ },
347
+ defaultVariants: {
348
+ size: "default",
349
+ animation: "fade"
350
+ }
351
+ });
352
+ var NotASpinner = React.forwardRef(
353
+ ({
354
+ className,
355
+ size,
356
+ animation = "fade",
357
+ messages,
358
+ locale,
359
+ fetchPhrase,
360
+ interval = 3e3,
361
+ dots = true,
362
+ ...props
363
+ }, ref) => {
364
+ const resolvedPhrases = React.useMemo(
365
+ () => getPhrasesForConfig(messages, locale),
366
+ [messages, locale]
367
+ );
368
+ const { currentPhrase, isExiting, isEntering, appendPhrases } = useTextRotation({
369
+ phrases: resolvedPhrases,
370
+ interval
371
+ });
372
+ React.useEffect(() => {
373
+ if (!fetchPhrase) return;
374
+ let cancelled = false;
375
+ const fetchInterval = setInterval(async () => {
376
+ if (cancelled) return;
377
+ try {
378
+ const phrase = await fetchPhrase();
379
+ if (!cancelled && phrase) {
380
+ appendPhrases([phrase]);
381
+ }
382
+ } catch {
383
+ }
384
+ }, interval * 2);
385
+ return () => {
386
+ cancelled = true;
387
+ clearInterval(fetchInterval);
388
+ };
389
+ }, [fetchPhrase, interval, appendPhrases]);
390
+ const getAnimationClass = () => {
391
+ if (animation === "typewriter") return "";
392
+ const prefix = animation === "blur" ? "nas-blur" : animation === "slide" ? "nas-slide" : "nas-fade";
393
+ if (isExiting) return `${prefix}-exit`;
394
+ if (isEntering) return `${prefix}-enter`;
395
+ return "";
396
+ };
397
+ if (animation === "typewriter") {
398
+ return /* @__PURE__ */ jsx(
399
+ TypewriterRenderer,
400
+ {
401
+ ref,
402
+ className: cn(notASpinnerVariants({ size, animation }), className),
403
+ currentPhrase,
404
+ interval,
405
+ dots,
406
+ ...props
407
+ }
408
+ );
409
+ }
410
+ return /* @__PURE__ */ jsxs(
411
+ "div",
412
+ {
413
+ ref,
414
+ className: cn(notASpinnerVariants({ size, animation }), className),
415
+ ...props,
416
+ children: [
417
+ /* @__PURE__ */ jsx("div", { className: "nas-text-wrapper", children: /* @__PURE__ */ jsx("span", { className: getAnimationClass(), children: currentPhrase }) }),
418
+ dots && /* @__PURE__ */ jsx("span", { className: "nas-dots", children: "..." })
419
+ ]
420
+ }
421
+ );
422
+ }
423
+ );
424
+ NotASpinner.displayName = "NotASpinner";
425
+ var TypewriterRenderer = React.forwardRef(({ className, currentPhrase, interval, dots, ...props }, ref) => {
426
+ const [displayedChars, setDisplayedChars] = React.useState(0);
427
+ const [phase, setPhase] = React.useState(
428
+ "typing"
429
+ );
430
+ const phraseRef = React.useRef(currentPhrase);
431
+ React.useEffect(() => {
432
+ phraseRef.current = currentPhrase;
433
+ setDisplayedChars(0);
434
+ setPhase("typing");
435
+ }, [currentPhrase]);
436
+ React.useEffect(() => {
437
+ const totalChars = phraseRef.current.length;
438
+ if (totalChars === 0) return;
439
+ const typingSpeed = 50;
440
+ const erasingSpeed = 30;
441
+ const typingDuration = totalChars * typingSpeed;
442
+ const erasingDuration = totalChars * erasingSpeed;
443
+ const holdDuration = Math.max(interval - typingDuration - erasingDuration, 500);
444
+ let timer;
445
+ if (phase === "typing") {
446
+ if (displayedChars < totalChars) {
447
+ timer = setTimeout(() => setDisplayedChars((c) => c + 1), typingSpeed);
448
+ } else {
449
+ timer = setTimeout(() => setPhase("holding"), 0);
450
+ }
451
+ } else if (phase === "holding") {
452
+ timer = setTimeout(() => setPhase("erasing"), holdDuration);
453
+ } else if (phase === "erasing") {
454
+ if (displayedChars > 0) {
455
+ timer = setTimeout(() => setDisplayedChars((c) => c - 1), erasingSpeed);
456
+ }
457
+ }
458
+ return () => clearTimeout(timer);
459
+ }, [phase, displayedChars, interval]);
460
+ const displayedText = phraseRef.current.slice(0, displayedChars);
461
+ return /* @__PURE__ */ jsxs("div", { ref, className, ...props, children: [
462
+ /* @__PURE__ */ jsx("div", { className: "nas-text-wrapper", children: /* @__PURE__ */ jsxs("span", { children: [
463
+ displayedText,
464
+ /* @__PURE__ */ jsx("span", { className: "nas-cursor" })
465
+ ] }) }),
466
+ dots && displayedChars > 0 && /* @__PURE__ */ jsx("span", { className: "nas-dots", children: "..." })
467
+ ] });
468
+ });
469
+ TypewriterRenderer.displayName = "TypewriterRenderer";
470
+
471
+ // src/providers.ts
472
+ function createAnthropicFetcher(endpoint, locale = "en") {
473
+ return async () => {
474
+ const res = await fetch(endpoint, {
475
+ method: "POST",
476
+ headers: { "Content-Type": "application/json" },
477
+ body: JSON.stringify({ provider: "anthropic", locale })
478
+ });
479
+ if (!res.ok) throw new Error(`Failed to fetch phrase: ${res.status}`);
480
+ const data = await res.json();
481
+ return data.phrase;
482
+ };
483
+ }
484
+ function createOpenAIFetcher(endpoint, locale = "en") {
485
+ return async () => {
486
+ const res = await fetch(endpoint, {
487
+ method: "POST",
488
+ headers: { "Content-Type": "application/json" },
489
+ body: JSON.stringify({ provider: "openai", locale })
490
+ });
491
+ if (!res.ok) throw new Error(`Failed to fetch phrase: ${res.status}`);
492
+ const data = await res.json();
493
+ return data.phrase;
494
+ };
495
+ }
496
+ export {
497
+ NotASpinner,
498
+ cn,
499
+ createAnthropicFetcher,
500
+ createOpenAIFetcher,
501
+ getPhrasesForConfig,
502
+ notASpinnerVariants,
503
+ phrases,
504
+ shufflePhrases,
505
+ useTextRotation
506
+ };
@@ -0,0 +1,6 @@
1
+ type Locale = "en" | "zh" | "ja" | "es" | "fr" | "de" | "ko";
2
+ declare const phrases: Record<Locale, string[]>;
3
+ declare function shufflePhrases(input: string[]): string[];
4
+ declare function getPhrasesForConfig(messages?: string[], locale?: Locale): string[];
5
+
6
+ export { type Locale as L, getPhrasesForConfig as g, phrases as p, shufflePhrases as s };
@@ -0,0 +1,6 @@
1
+ type Locale = "en" | "zh" | "ja" | "es" | "fr" | "de" | "ko";
2
+ declare const phrases: Record<Locale, string[]>;
3
+ declare function shufflePhrases(input: string[]): string[];
4
+ declare function getPhrasesForConfig(messages?: string[], locale?: Locale): string[];
5
+
6
+ export { type Locale as L, getPhrasesForConfig as g, phrases as p, shufflePhrases as s };