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/README.md +186 -0
- package/dist/index.cjs +551 -0
- package/dist/index.d.cts +39 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.mjs +506 -0
- package/dist/phrases-Dvrk5dm5.d.cts +6 -0
- package/dist/phrases-Dvrk5dm5.d.ts +6 -0
- package/dist/server.cjs +380 -0
- package/dist/server.d.cts +29 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.mjs +353 -0
- package/dist/styles.css +98 -0
- package/package.json +84 -0
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# not-a-spinner
|
|
2
|
+
|
|
3
|
+
> Because modern AI doesn't spin, it thinks.
|
|
4
|
+
|
|
5
|
+
A lightweight React component that replaces loading spinners with rotating AI-style thinking phrases. Ships with 210+ built-in phrases across 7 languages, 4 animation styles, and optional LLM-generated messages.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install not-a-spinner
|
|
11
|
+
# or
|
|
12
|
+
yarn add not-a-spinner
|
|
13
|
+
# or
|
|
14
|
+
pnpm add not-a-spinner
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
**1. Import the CSS once** (in your root layout or entry point):
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import "not-a-spinner/styles.css"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
**2. Use the component:**
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
"use client"
|
|
29
|
+
import { NotASpinner } from "not-a-spinner"
|
|
30
|
+
|
|
31
|
+
export function MyLoader() {
|
|
32
|
+
return <NotASpinner />
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
That's it. You get rotating English phrases with a fade animation and pulsing dots.
|
|
37
|
+
|
|
38
|
+
## Examples
|
|
39
|
+
|
|
40
|
+
```tsx
|
|
41
|
+
// Different animations
|
|
42
|
+
<NotASpinner animation="fade" /> // crossfade (default)
|
|
43
|
+
<NotASpinner animation="typewriter" /> // character-by-character with cursor
|
|
44
|
+
<NotASpinner animation="slide" /> // slide up transition
|
|
45
|
+
<NotASpinner animation="blur" /> // blur in/out
|
|
46
|
+
|
|
47
|
+
// Sizes
|
|
48
|
+
<NotASpinner size="sm" />
|
|
49
|
+
<NotASpinner size="default" />
|
|
50
|
+
<NotASpinner size="lg" />
|
|
51
|
+
|
|
52
|
+
// Languages (30 phrases each)
|
|
53
|
+
<NotASpinner locale="en" /> // English (default)
|
|
54
|
+
<NotASpinner locale="zh" /> // 中文
|
|
55
|
+
<NotASpinner locale="ja" /> // 日本語
|
|
56
|
+
<NotASpinner locale="es" /> // Español
|
|
57
|
+
<NotASpinner locale="fr" /> // Français
|
|
58
|
+
<NotASpinner locale="de" /> // Deutsch
|
|
59
|
+
<NotASpinner locale="ko" /> // 한국어
|
|
60
|
+
|
|
61
|
+
// Custom messages
|
|
62
|
+
<NotASpinner messages={["Crunching numbers", "Consulting the oracle", "Almost there"]} />
|
|
63
|
+
|
|
64
|
+
// Disable dots
|
|
65
|
+
<NotASpinner dots={false} />
|
|
66
|
+
|
|
67
|
+
// Faster rotation (ms)
|
|
68
|
+
<NotASpinner interval={1500} />
|
|
69
|
+
|
|
70
|
+
// Combine options
|
|
71
|
+
<NotASpinner animation="typewriter" locale="ja" size="lg" dots={false} />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Props
|
|
75
|
+
|
|
76
|
+
| Prop | Type | Default | Description |
|
|
77
|
+
|------|------|---------|-------------|
|
|
78
|
+
| `animation` | `"fade" \| "typewriter" \| "slide" \| "blur"` | `"fade"` | Text transition style |
|
|
79
|
+
| `size` | `"sm" \| "default" \| "lg"` | `"default"` | Font size |
|
|
80
|
+
| `locale` | `"en" \| "zh" \| "ja" \| "es" \| "fr" \| "de" \| "ko"` | `"en"` | Built-in phrase language |
|
|
81
|
+
| `messages` | `string[]` | — | Custom phrases (overrides locale) |
|
|
82
|
+
| `fetchPhrase` | `() => Promise<string>` | — | Async phrase fetcher for LLM integration |
|
|
83
|
+
| `interval` | `number` | `3000` | Rotation speed in ms |
|
|
84
|
+
| `dots` | `boolean` | `true` | Show trailing `...` |
|
|
85
|
+
| `className` | `string` | — | Additional CSS classes (merged via `cn()`) |
|
|
86
|
+
|
|
87
|
+
## LLM-Generated Phrases
|
|
88
|
+
|
|
89
|
+
Generate fresh phrases on the fly using OpenAI or Anthropic.
|
|
90
|
+
|
|
91
|
+
**1. Create a server route:**
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
// app/api/thinking/route.ts
|
|
95
|
+
import { createThinkingHandler } from "not-a-spinner/server"
|
|
96
|
+
|
|
97
|
+
const handler = createThinkingHandler({
|
|
98
|
+
provider: "openai", // or "anthropic"
|
|
99
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
100
|
+
model: "gpt-4o-mini", // optional
|
|
101
|
+
locale: "en", // optional
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
export const POST = handler
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**2. Connect to the component:**
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
"use client"
|
|
111
|
+
import { NotASpinner, createOpenAIFetcher } from "not-a-spinner"
|
|
112
|
+
|
|
113
|
+
const fetchPhrase = createOpenAIFetcher("/api/thinking")
|
|
114
|
+
|
|
115
|
+
export function MyLoader() {
|
|
116
|
+
return <NotASpinner fetchPhrase={fetchPhrase} animation="typewriter" />
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The component starts with built-in phrases immediately and mixes in LLM-generated ones as they arrive. If the API fails, it silently falls back to local phrases.
|
|
121
|
+
|
|
122
|
+
### Custom Prompt
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const handler = createThinkingHandler({
|
|
126
|
+
provider: "anthropic",
|
|
127
|
+
apiKey: process.env.ANTHROPIC_API_KEY!,
|
|
128
|
+
systemPrompt: `You generate short, sarcastic loading messages for a developer tool.
|
|
129
|
+
Keep it under 40 characters. Be dry and witty. Output only the phrase.`,
|
|
130
|
+
})
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Built-in Default Prompt
|
|
134
|
+
|
|
135
|
+
The default prompt instructs the LLM to generate short, funny, nerdy loading messages (under 50 characters) matching the tone of phrases like "Reticulating splines" and "Downloading more RAM". It auto-appends locale-specific instructions with example phrases in each language.
|
|
136
|
+
|
|
137
|
+
### Bring Your Own Fetcher
|
|
138
|
+
|
|
139
|
+
Skip the server helper entirely — pass any `() => Promise<string>`:
|
|
140
|
+
|
|
141
|
+
```tsx
|
|
142
|
+
<NotASpinner
|
|
143
|
+
fetchPhrase={async () => {
|
|
144
|
+
const res = await fetch("/your-own-api")
|
|
145
|
+
const data = await res.json()
|
|
146
|
+
return data.text
|
|
147
|
+
}}
|
|
148
|
+
/>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Styling
|
|
152
|
+
|
|
153
|
+
The component ships pre-compiled CSS with `nas-` prefixed classes — **no Tailwind required**. But if you use Tailwind, you can override styles via `className`:
|
|
154
|
+
|
|
155
|
+
```tsx
|
|
156
|
+
<NotASpinner className="text-blue-500 text-xl" />
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
To customize the base color globally, set the CSS variable:
|
|
160
|
+
|
|
161
|
+
```css
|
|
162
|
+
:root {
|
|
163
|
+
--nas-color: #8b5cf6;
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Sample Phrases
|
|
168
|
+
|
|
169
|
+
**English**: "Reticulating splines", "Asking the rubber duck", "Downloading more RAM", "Bribing the cache fairy"
|
|
170
|
+
|
|
171
|
+
**中文**: "让AI再想想", "正在炼丹", "正在解开薛定谔的Bug", "偷偷查看答案中"
|
|
172
|
+
|
|
173
|
+
**日本語**: "AIが悩んでいます", "ピクセルを磨き中", "もうちょっと待ってね"
|
|
174
|
+
|
|
175
|
+
**한국어**: "AI가 고민 중", "바쁜 척하는 중", "거의 거의 다 됐어요"
|
|
176
|
+
|
|
177
|
+
Each locale includes 30 curated phrases.
|
|
178
|
+
|
|
179
|
+
## Requirements
|
|
180
|
+
|
|
181
|
+
- React 18+
|
|
182
|
+
- For LLM features: OpenAI or Anthropic API key
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|