@jamwidgets/solid 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 +292 -0
- package/dist/index.d.ts +310 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +409 -0
- package/package.json +51 -0
package/README.md
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# @jamwidgets/solid
|
|
2
|
+
|
|
3
|
+
> **Note:** This repo is a read-only mirror. Source lives in a private monorepo.
|
|
4
|
+
> For issues/PRs, please open them here and we'll sync changes back.
|
|
5
|
+
|
|
6
|
+
SolidJS primitives for [JamWidgets](https://jamwidgets.com) widgets - comments, reactions, forms, subscriptions, and more.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @jamwidgets/solid
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Works with SolidJS 1.8+. Compatible with SolidStart, Astro, and standalone apps.
|
|
15
|
+
|
|
16
|
+
## Primitives
|
|
17
|
+
|
|
18
|
+
### createSubscribe
|
|
19
|
+
|
|
20
|
+
Email subscription form:
|
|
21
|
+
|
|
22
|
+
```tsx
|
|
23
|
+
import { createSubscribe } from "@jamwidgets/solid";
|
|
24
|
+
|
|
25
|
+
function SubscribeForm() {
|
|
26
|
+
const [email, setEmail] = createSignal("");
|
|
27
|
+
const { submit, status, message, error } = createSubscribe({
|
|
28
|
+
siteKey: "your-key",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const handleSubmit = async (e) => {
|
|
32
|
+
e.preventDefault();
|
|
33
|
+
await submit(email());
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<form onSubmit={handleSubmit}>
|
|
38
|
+
<input
|
|
39
|
+
type="email"
|
|
40
|
+
value={email()}
|
|
41
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
42
|
+
placeholder="your@email.com"
|
|
43
|
+
/>
|
|
44
|
+
<button disabled={status() === "loading"}>
|
|
45
|
+
{status() === "loading" ? "Subscribing..." : "Subscribe"}
|
|
46
|
+
</button>
|
|
47
|
+
<Show when={status() === "success"}>
|
|
48
|
+
<p>{message()}</p>
|
|
49
|
+
</Show>
|
|
50
|
+
<Show when={status() === "error"}>
|
|
51
|
+
<p>{error()?.message}</p>
|
|
52
|
+
</Show>
|
|
53
|
+
</form>
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### createReactions
|
|
59
|
+
|
|
60
|
+
Reaction buttons (like, love, clap, etc.):
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
import { createReactions } from "@jamwidgets/solid";
|
|
64
|
+
|
|
65
|
+
function LikeButton() {
|
|
66
|
+
const { counts, userReactions, add, remove, status } = createReactions({
|
|
67
|
+
siteKey: "your-key",
|
|
68
|
+
pageId: "my-page",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const hasLiked = () => userReactions().includes("like");
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
<button onClick={() => (hasLiked() ? remove("like") : add("like"))}>
|
|
75
|
+
{hasLiked() ? "Unlike" : "Like"} ({counts().like || 0})
|
|
76
|
+
</button>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### createComments
|
|
82
|
+
|
|
83
|
+
Threaded comments:
|
|
84
|
+
|
|
85
|
+
```tsx
|
|
86
|
+
import { createComments } from "@jamwidgets/solid";
|
|
87
|
+
|
|
88
|
+
function Comments() {
|
|
89
|
+
const { comments, post, status, error } = createComments({
|
|
90
|
+
siteKey: "your-key",
|
|
91
|
+
pageId: "my-page",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
return (
|
|
95
|
+
<div>
|
|
96
|
+
<For each={comments()}>
|
|
97
|
+
{(comment) => (
|
|
98
|
+
<div>
|
|
99
|
+
<strong>{comment.authorName}</strong>
|
|
100
|
+
<p>{comment.content}</p>
|
|
101
|
+
</div>
|
|
102
|
+
)}
|
|
103
|
+
</For>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### createForm
|
|
110
|
+
|
|
111
|
+
Contact forms with spam protection:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { createForm } from "@jamwidgets/solid";
|
|
115
|
+
|
|
116
|
+
function ContactForm() {
|
|
117
|
+
const { submit, status, message } = createForm({
|
|
118
|
+
siteKey: "your-key",
|
|
119
|
+
formSlug: "contact",
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const handleSubmit = async (e) => {
|
|
123
|
+
e.preventDefault();
|
|
124
|
+
const data = Object.fromEntries(new FormData(e.target));
|
|
125
|
+
await submit(data);
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<form onSubmit={handleSubmit}>
|
|
130
|
+
<input name="email" type="email" required />
|
|
131
|
+
<textarea name="message" required />
|
|
132
|
+
<button type="submit">Send</button>
|
|
133
|
+
<Show when={status() === "success"}>
|
|
134
|
+
<p>{message()}</p>
|
|
135
|
+
</Show>
|
|
136
|
+
</form>
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### createWaitlist
|
|
142
|
+
|
|
143
|
+
Waitlist signups:
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
import { createWaitlist } from "@jamwidgets/solid";
|
|
147
|
+
|
|
148
|
+
function WaitlistForm() {
|
|
149
|
+
const { join, status, message, position } = createWaitlist({
|
|
150
|
+
siteKey: "your-key",
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
const handleSubmit = async (e) => {
|
|
154
|
+
e.preventDefault();
|
|
155
|
+
await join(email(), { name: name(), source: "homepage" });
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<form onSubmit={handleSubmit}>
|
|
160
|
+
<input name="email" type="email" required />
|
|
161
|
+
<button type="submit">Join Waitlist</button>
|
|
162
|
+
<Show when={status() === "success"}>
|
|
163
|
+
<p>{message()}</p>
|
|
164
|
+
</Show>
|
|
165
|
+
</form>
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### createFeedback
|
|
171
|
+
|
|
172
|
+
Feedback forms:
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
import { createFeedback } from "@jamwidgets/solid";
|
|
176
|
+
|
|
177
|
+
function FeedbackWidget() {
|
|
178
|
+
const { submit, status, message } = createFeedback({
|
|
179
|
+
siteKey: "your-key",
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const handleSubmit = async (type, content) => {
|
|
183
|
+
await submit(type, content, { email: email(), pageUrl: window.location.href });
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// ...
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### createPoll
|
|
191
|
+
|
|
192
|
+
Polls and voting:
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import { createPoll } from "@jamwidgets/solid";
|
|
196
|
+
|
|
197
|
+
function Poll() {
|
|
198
|
+
const { poll, vote, hasVoted, status } = createPoll({
|
|
199
|
+
siteKey: "your-key",
|
|
200
|
+
pollId: 123,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
return (
|
|
204
|
+
<Show when={poll()} fallback={<div>Loading...</div>}>
|
|
205
|
+
<div>
|
|
206
|
+
<h3>{poll().question}</h3>
|
|
207
|
+
<For each={poll().options}>
|
|
208
|
+
{(option) => (
|
|
209
|
+
<button onClick={() => vote([option.id])} disabled={hasVoted()}>
|
|
210
|
+
{option.text} ({poll().results?.[option.id] || 0})
|
|
211
|
+
</button>
|
|
212
|
+
)}
|
|
213
|
+
</For>
|
|
214
|
+
</div>
|
|
215
|
+
</Show>
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### createAnnouncements
|
|
221
|
+
|
|
222
|
+
Site announcements:
|
|
223
|
+
|
|
224
|
+
```tsx
|
|
225
|
+
import { createAnnouncements } from "@jamwidgets/solid";
|
|
226
|
+
|
|
227
|
+
function AnnouncementBanner() {
|
|
228
|
+
const { announcements, dismiss, status } = createAnnouncements({
|
|
229
|
+
siteKey: "your-key",
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const visible = () => announcements().filter((a) => !a.dismissed);
|
|
233
|
+
|
|
234
|
+
return (
|
|
235
|
+
<For each={visible()}>
|
|
236
|
+
{(announcement) => (
|
|
237
|
+
<div>
|
|
238
|
+
<p>{announcement.content}</p>
|
|
239
|
+
<Show when={announcement.isDismissible}>
|
|
240
|
+
<button onClick={() => dismiss(announcement.id)}>Dismiss</button>
|
|
241
|
+
</Show>
|
|
242
|
+
</div>
|
|
243
|
+
)}
|
|
244
|
+
</For>
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### createViewCounts
|
|
250
|
+
|
|
251
|
+
Page view tracking:
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import { createViewCounts } from "@jamwidgets/solid";
|
|
255
|
+
import { onMount } from "solid-js";
|
|
256
|
+
|
|
257
|
+
function PageViews() {
|
|
258
|
+
const { views, uniqueVisitors, record, status } = createViewCounts({
|
|
259
|
+
siteKey: "your-key",
|
|
260
|
+
pageId: "my-page",
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// Record view on mount
|
|
264
|
+
onMount(() => {
|
|
265
|
+
record();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<span>
|
|
270
|
+
{views()} views ({uniqueVisitors()} unique)
|
|
271
|
+
</span>
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
## All Primitives
|
|
277
|
+
|
|
278
|
+
| Primitive | Purpose |
|
|
279
|
+
|-----------|---------|
|
|
280
|
+
| `createSubscribe` | Email subscriptions |
|
|
281
|
+
| `createReactions` | Page reactions |
|
|
282
|
+
| `createComments` | Threaded comments |
|
|
283
|
+
| `createForm` | Form submissions |
|
|
284
|
+
| `createWaitlist` | Waitlist signups |
|
|
285
|
+
| `createFeedback` | Feedback forms |
|
|
286
|
+
| `createPoll` | Polls and voting |
|
|
287
|
+
| `createAnnouncements` | Site announcements |
|
|
288
|
+
| `createViewCounts` | Page view tracking |
|
|
289
|
+
|
|
290
|
+
## License
|
|
291
|
+
|
|
292
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jamwidgets/solid - SolidJS primitives for Jamwidgets
|
|
3
|
+
*
|
|
4
|
+
* @example Subscribe form
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { createSubscribe } from '@jamwidgets/solid';
|
|
7
|
+
*
|
|
8
|
+
* function Newsletter() {
|
|
9
|
+
* const { subscribe, status, error } = createSubscribe({
|
|
10
|
+
* siteKey: 'your-site-key',
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const handleSubmit = (e: SubmitEvent) => {
|
|
14
|
+
* e.preventDefault();
|
|
15
|
+
* const email = new FormData(e.currentTarget as HTMLFormElement).get('email') as string;
|
|
16
|
+
* subscribe(email);
|
|
17
|
+
* };
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <form onSubmit={handleSubmit}>
|
|
21
|
+
* <input type="email" name="email" required />
|
|
22
|
+
* <button disabled={status() === 'loading'}>
|
|
23
|
+
* {status() === 'loading' ? 'Subscribing...' : 'Subscribe'}
|
|
24
|
+
* </button>
|
|
25
|
+
* {status() === 'success' && <p>Thanks for subscribing!</p>}
|
|
26
|
+
* {status() === 'error' && <p>Error: {error()?.message}</p>}
|
|
27
|
+
* </form>
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { type Accessor } from "solid-js";
|
|
33
|
+
import { type JamWidgetsConfig, type Comment, type Announcement, type PollWithResults, type FeedbackType, type ControllerStatus } from "@jamwidgets/core";
|
|
34
|
+
export type { JamWidgetsConfig, SubscribeState, FormState, ReactionsState, CommentsState, WaitlistState, ViewCountsState, FeedbackState, PollState, AnnouncementsState, Comment, Announcement, PollWithResults, FeedbackType, ReactionCounts, JamwidgetsPost, SeriphPost, // deprecated alias
|
|
35
|
+
FetchPostsOptions, FetchPostOptions, ControllerStatus, } from "@jamwidgets/core";
|
|
36
|
+
export { fetchPosts, fetchPost, getConfigFromMeta, resolveConfig, DEFAULT_ENDPOINT, API_PATH, } from "@jamwidgets/core";
|
|
37
|
+
type OptionalSiteKey<T extends JamWidgetsConfig> = Omit<T, "siteKey"> & {
|
|
38
|
+
/** Site key - optional if <meta name="jamwidgets-site-key"> is set */
|
|
39
|
+
siteKey?: string;
|
|
40
|
+
};
|
|
41
|
+
export interface CreateSubscribeOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
42
|
+
}
|
|
43
|
+
export interface CreateSubscribeReturn {
|
|
44
|
+
status: Accessor<ControllerStatus>;
|
|
45
|
+
message: Accessor<string | null>;
|
|
46
|
+
error: Accessor<Error | null>;
|
|
47
|
+
subscribe: (email: string) => Promise<void>;
|
|
48
|
+
reset: () => void;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Primitive for handling email subscriptions.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```tsx
|
|
55
|
+
* // With explicit siteKey:
|
|
56
|
+
* const { subscribe, status } = createSubscribe({ siteKey: 'your-key' });
|
|
57
|
+
*
|
|
58
|
+
* // Or with meta tag (add to document head):
|
|
59
|
+
* // <meta name="jamwidgets-site-key" content="your-key" />
|
|
60
|
+
* const { subscribe, status } = createSubscribe({});
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export declare function createSubscribe(options: CreateSubscribeOptions): CreateSubscribeReturn;
|
|
64
|
+
export interface CreateFormOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
65
|
+
/** Form slug/identifier */
|
|
66
|
+
formSlug: string;
|
|
67
|
+
}
|
|
68
|
+
export interface CreateFormReturn {
|
|
69
|
+
status: Accessor<ControllerStatus>;
|
|
70
|
+
message: Accessor<string | null>;
|
|
71
|
+
error: Accessor<Error | null>;
|
|
72
|
+
submit: (data: Record<string, unknown>) => Promise<void>;
|
|
73
|
+
reset: () => void;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Primitive for handling form submissions.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```tsx
|
|
80
|
+
* const { submit, status, error } = createForm({
|
|
81
|
+
* siteKey: 'your-site-key', // optional with meta tag
|
|
82
|
+
* formSlug: 'contact',
|
|
83
|
+
* });
|
|
84
|
+
*
|
|
85
|
+
* const handleSubmit = (e: SubmitEvent) => {
|
|
86
|
+
* e.preventDefault();
|
|
87
|
+
* const formData = new FormData(e.currentTarget as HTMLFormElement);
|
|
88
|
+
* submit(Object.fromEntries(formData));
|
|
89
|
+
* };
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export declare function createForm(options: CreateFormOptions): CreateFormReturn;
|
|
93
|
+
export interface CreateReactionsOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
94
|
+
/** Content identifier (e.g., post slug) */
|
|
95
|
+
contentId: string;
|
|
96
|
+
/** Auto-fetch reactions on mount (default: true) */
|
|
97
|
+
autoFetch?: boolean;
|
|
98
|
+
}
|
|
99
|
+
export interface CreateReactionsReturn {
|
|
100
|
+
counts: Accessor<Record<string, number>>;
|
|
101
|
+
userReactions: Accessor<string[]>;
|
|
102
|
+
status: Accessor<ControllerStatus>;
|
|
103
|
+
error: Accessor<Error | null>;
|
|
104
|
+
addReaction: (type: string) => Promise<void>;
|
|
105
|
+
removeReaction: (type: string) => Promise<void>;
|
|
106
|
+
refresh: () => Promise<void>;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Primitive for handling reactions (likes, claps, etc.).
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
* ```tsx
|
|
113
|
+
* const { counts, userReactions, addReaction, removeReaction } = createReactions({
|
|
114
|
+
* contentId: 'my-post-slug',
|
|
115
|
+
* });
|
|
116
|
+
*
|
|
117
|
+
* <button onClick={() => addReaction('like')}>
|
|
118
|
+
* Like ({counts().like || 0})
|
|
119
|
+
* </button>
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
export declare function createReactions(options: CreateReactionsOptions): CreateReactionsReturn;
|
|
123
|
+
export interface CreateCommentsOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
124
|
+
/** Content identifier (e.g., post slug) */
|
|
125
|
+
contentId: string;
|
|
126
|
+
/** Auto-fetch comments on mount (default: true) */
|
|
127
|
+
autoFetch?: boolean;
|
|
128
|
+
}
|
|
129
|
+
export interface CreateCommentsReturn {
|
|
130
|
+
comments: Accessor<Comment[]>;
|
|
131
|
+
status: Accessor<ControllerStatus>;
|
|
132
|
+
error: Accessor<Error | null>;
|
|
133
|
+
postComment: (author: string, content: string, options?: {
|
|
134
|
+
authorEmail?: string;
|
|
135
|
+
parentId?: string;
|
|
136
|
+
}) => Promise<void>;
|
|
137
|
+
refresh: () => Promise<void>;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Primitive for handling comments.
|
|
141
|
+
*
|
|
142
|
+
* @example
|
|
143
|
+
* ```tsx
|
|
144
|
+
* const { comments, status, postComment } = createComments({
|
|
145
|
+
* contentId: 'my-post-slug',
|
|
146
|
+
* });
|
|
147
|
+
*
|
|
148
|
+
* <For each={comments()}>
|
|
149
|
+
* {(comment) => (
|
|
150
|
+
* <div>
|
|
151
|
+
* <strong>{comment.authorName}</strong>: {comment.content}
|
|
152
|
+
* </div>
|
|
153
|
+
* )}
|
|
154
|
+
* </For>
|
|
155
|
+
*
|
|
156
|
+
* <button onClick={() => postComment('Anonymous', 'Great post!')}>
|
|
157
|
+
* Add Comment
|
|
158
|
+
* </button>
|
|
159
|
+
* ```
|
|
160
|
+
*/
|
|
161
|
+
export declare function createComments(options: CreateCommentsOptions): CreateCommentsReturn;
|
|
162
|
+
export interface CreateWaitlistOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
163
|
+
}
|
|
164
|
+
export interface CreateWaitlistReturn {
|
|
165
|
+
status: Accessor<ControllerStatus>;
|
|
166
|
+
message: Accessor<string | null>;
|
|
167
|
+
position: Accessor<number | null>;
|
|
168
|
+
error: Accessor<Error | null>;
|
|
169
|
+
join: (email: string, options?: {
|
|
170
|
+
name?: string;
|
|
171
|
+
source?: string;
|
|
172
|
+
}) => Promise<void>;
|
|
173
|
+
reset: () => void;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Primitive for handling waitlist signups.
|
|
177
|
+
*
|
|
178
|
+
* @example
|
|
179
|
+
* ```tsx
|
|
180
|
+
* const { join, status, position } = createWaitlist({});
|
|
181
|
+
*
|
|
182
|
+
* <button onClick={() => join('user@example.com')}>Join Waitlist</button>
|
|
183
|
+
* {status() === 'success' && <p>You're #{position()} on the list!</p>}
|
|
184
|
+
* ```
|
|
185
|
+
*/
|
|
186
|
+
export declare function createWaitlist(options: CreateWaitlistOptions): CreateWaitlistReturn;
|
|
187
|
+
export interface CreateViewsOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
188
|
+
/** Page identifier (e.g., slug or URL path) */
|
|
189
|
+
pageId: string;
|
|
190
|
+
/** Auto-record view on mount (default: true) */
|
|
191
|
+
autoRecord?: boolean;
|
|
192
|
+
}
|
|
193
|
+
export interface CreateViewsReturn {
|
|
194
|
+
views: Accessor<number>;
|
|
195
|
+
uniqueVisitors: Accessor<number>;
|
|
196
|
+
status: Accessor<ControllerStatus>;
|
|
197
|
+
error: Accessor<Error | null>;
|
|
198
|
+
record: () => Promise<void>;
|
|
199
|
+
refresh: () => Promise<void>;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Primitive for tracking and displaying page views.
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* ```tsx
|
|
206
|
+
* const { views, uniqueVisitors } = createViews({
|
|
207
|
+
* pageId: '/blog/my-post',
|
|
208
|
+
* });
|
|
209
|
+
*
|
|
210
|
+
* <span>{views()} views ({uniqueVisitors()} unique)</span>
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export declare function createViews(options: CreateViewsOptions): CreateViewsReturn;
|
|
214
|
+
export interface CreateFeedbackOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
215
|
+
}
|
|
216
|
+
export interface CreateFeedbackReturn {
|
|
217
|
+
status: Accessor<ControllerStatus>;
|
|
218
|
+
message: Accessor<string | null>;
|
|
219
|
+
error: Accessor<Error | null>;
|
|
220
|
+
submit: (type: FeedbackType, content: string, options?: {
|
|
221
|
+
email?: string;
|
|
222
|
+
pageUrl?: string;
|
|
223
|
+
}) => Promise<void>;
|
|
224
|
+
reset: () => void;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Primitive for handling feedback submissions.
|
|
228
|
+
*
|
|
229
|
+
* @example
|
|
230
|
+
* ```tsx
|
|
231
|
+
* const { submit, status } = createFeedback({});
|
|
232
|
+
*
|
|
233
|
+
* <button onClick={() => submit('feature', 'Add dark mode!')}>
|
|
234
|
+
* Submit Feedback
|
|
235
|
+
* </button>
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
export declare function createFeedback(options: CreateFeedbackOptions): CreateFeedbackReturn;
|
|
239
|
+
export interface CreatePollOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
240
|
+
/** Poll slug */
|
|
241
|
+
slug: string;
|
|
242
|
+
/** Auto-fetch poll on mount (default: true) */
|
|
243
|
+
autoFetch?: boolean;
|
|
244
|
+
}
|
|
245
|
+
export interface CreatePollReturn {
|
|
246
|
+
poll: Accessor<PollWithResults | null>;
|
|
247
|
+
status: Accessor<ControllerStatus>;
|
|
248
|
+
error: Accessor<Error | null>;
|
|
249
|
+
vote: (selectedOptions: string[]) => Promise<void>;
|
|
250
|
+
hasVoted: Accessor<boolean>;
|
|
251
|
+
refresh: () => Promise<void>;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Primitive for displaying and voting on polls.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* ```tsx
|
|
258
|
+
* // With meta tag: <meta name="jamwidgets-site-key" content="your-key" />
|
|
259
|
+
* const { poll, vote, hasVoted } = createPoll({ slug: 'favorite-framework' });
|
|
260
|
+
*
|
|
261
|
+
* <Show when={poll()}>
|
|
262
|
+
* {(p) => (
|
|
263
|
+
* <div>
|
|
264
|
+
* <h3>{p().question}</h3>
|
|
265
|
+
* <For each={p().options}>
|
|
266
|
+
* {(opt) => (
|
|
267
|
+
* <button onClick={() => vote([opt.id])} disabled={hasVoted()}>
|
|
268
|
+
* {opt.text} ({p().results[opt.id] || 0} votes)
|
|
269
|
+
* </button>
|
|
270
|
+
* )}
|
|
271
|
+
* </For>
|
|
272
|
+
* </div>
|
|
273
|
+
* )}
|
|
274
|
+
* </Show>
|
|
275
|
+
* ```
|
|
276
|
+
*/
|
|
277
|
+
export declare function createPoll(options: CreatePollOptions): CreatePollReturn;
|
|
278
|
+
export interface CreateAnnouncementsOptions extends OptionalSiteKey<JamWidgetsConfig> {
|
|
279
|
+
/** Auto-fetch announcements on mount (default: true) */
|
|
280
|
+
autoFetch?: boolean;
|
|
281
|
+
}
|
|
282
|
+
export interface CreateAnnouncementsReturn {
|
|
283
|
+
announcements: Accessor<Announcement[]>;
|
|
284
|
+
status: Accessor<ControllerStatus>;
|
|
285
|
+
error: Accessor<Error | null>;
|
|
286
|
+
dismiss: (announcementId: number) => Promise<void>;
|
|
287
|
+
refresh: () => Promise<void>;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Primitive for displaying site announcements.
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```tsx
|
|
294
|
+
* // With meta tag: <meta name="jamwidgets-site-key" content="your-key" />
|
|
295
|
+
* const { announcements, dismiss } = createAnnouncements({});
|
|
296
|
+
*
|
|
297
|
+
* <For each={announcements()}>
|
|
298
|
+
* {(ann) => (
|
|
299
|
+
* <div class={`announcement-${ann.announcementType}`}>
|
|
300
|
+
* {ann.content}
|
|
301
|
+
* {ann.isDismissible && (
|
|
302
|
+
* <button onClick={() => dismiss(ann.id)}>Dismiss</button>
|
|
303
|
+
* )}
|
|
304
|
+
* </div>
|
|
305
|
+
* )}
|
|
306
|
+
* </For>
|
|
307
|
+
* ```
|
|
308
|
+
*/
|
|
309
|
+
export declare function createAnnouncements(options: CreateAnnouncementsOptions): CreateAnnouncementsReturn;
|
|
310
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @jamwidgets/solid - SolidJS primitives for Jamwidgets
|
|
3
|
+
*
|
|
4
|
+
* @example Subscribe form
|
|
5
|
+
* ```tsx
|
|
6
|
+
* import { createSubscribe } from '@jamwidgets/solid';
|
|
7
|
+
*
|
|
8
|
+
* function Newsletter() {
|
|
9
|
+
* const { subscribe, status, error } = createSubscribe({
|
|
10
|
+
* siteKey: 'your-site-key',
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const handleSubmit = (e: SubmitEvent) => {
|
|
14
|
+
* e.preventDefault();
|
|
15
|
+
* const email = new FormData(e.currentTarget as HTMLFormElement).get('email') as string;
|
|
16
|
+
* subscribe(email);
|
|
17
|
+
* };
|
|
18
|
+
*
|
|
19
|
+
* return (
|
|
20
|
+
* <form onSubmit={handleSubmit}>
|
|
21
|
+
* <input type="email" name="email" required />
|
|
22
|
+
* <button disabled={status() === 'loading'}>
|
|
23
|
+
* {status() === 'loading' ? 'Subscribing...' : 'Subscribe'}
|
|
24
|
+
* </button>
|
|
25
|
+
* {status() === 'success' && <p>Thanks for subscribing!</p>}
|
|
26
|
+
* {status() === 'error' && <p>Error: {error()?.message}</p>}
|
|
27
|
+
* </form>
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
import { createSignal, createEffect, onCleanup } from "solid-js";
|
|
33
|
+
import { SubscribeController, FormController, ReactionsController, CommentsController, WaitlistController, ViewCountsController, FeedbackController, PollController, AnnouncementsController, resolveConfig, } from "@jamwidgets/core";
|
|
34
|
+
// Re-export API functions and helpers from core
|
|
35
|
+
export { fetchPosts, fetchPost, getConfigFromMeta, resolveConfig, DEFAULT_ENDPOINT, API_PATH, } from "@jamwidgets/core";
|
|
36
|
+
/**
|
|
37
|
+
* Primitive for handling email subscriptions.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```tsx
|
|
41
|
+
* // With explicit siteKey:
|
|
42
|
+
* const { subscribe, status } = createSubscribe({ siteKey: 'your-key' });
|
|
43
|
+
*
|
|
44
|
+
* // Or with meta tag (add to document head):
|
|
45
|
+
* // <meta name="jamwidgets-site-key" content="your-key" />
|
|
46
|
+
* const { subscribe, status } = createSubscribe({});
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export function createSubscribe(options) {
|
|
50
|
+
const [status, setStatus] = createSignal("idle");
|
|
51
|
+
const [message, setMessage] = createSignal(null);
|
|
52
|
+
const [error, setError] = createSignal(null);
|
|
53
|
+
const config = resolveConfig(options);
|
|
54
|
+
const controller = new SubscribeController(config);
|
|
55
|
+
createEffect(() => {
|
|
56
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
57
|
+
setStatus(state.status);
|
|
58
|
+
setMessage(state.message);
|
|
59
|
+
setError(state.error);
|
|
60
|
+
});
|
|
61
|
+
onCleanup(unsubscribe);
|
|
62
|
+
});
|
|
63
|
+
const subscribe = async (email) => {
|
|
64
|
+
await controller.submit(email);
|
|
65
|
+
};
|
|
66
|
+
const reset = () => {
|
|
67
|
+
controller.reset();
|
|
68
|
+
};
|
|
69
|
+
return { status, message, error, subscribe, reset };
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Primitive for handling form submissions.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```tsx
|
|
76
|
+
* const { submit, status, error } = createForm({
|
|
77
|
+
* siteKey: 'your-site-key', // optional with meta tag
|
|
78
|
+
* formSlug: 'contact',
|
|
79
|
+
* });
|
|
80
|
+
*
|
|
81
|
+
* const handleSubmit = (e: SubmitEvent) => {
|
|
82
|
+
* e.preventDefault();
|
|
83
|
+
* const formData = new FormData(e.currentTarget as HTMLFormElement);
|
|
84
|
+
* submit(Object.fromEntries(formData));
|
|
85
|
+
* };
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export function createForm(options) {
|
|
89
|
+
const [status, setStatus] = createSignal("idle");
|
|
90
|
+
const [message, setMessage] = createSignal(null);
|
|
91
|
+
const [error, setError] = createSignal(null);
|
|
92
|
+
const config = resolveConfig(options);
|
|
93
|
+
const controller = new FormController(config, options.formSlug);
|
|
94
|
+
createEffect(() => {
|
|
95
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
96
|
+
setStatus(state.status);
|
|
97
|
+
setMessage(state.message);
|
|
98
|
+
setError(state.error);
|
|
99
|
+
});
|
|
100
|
+
onCleanup(unsubscribe);
|
|
101
|
+
});
|
|
102
|
+
const submit = async (data) => {
|
|
103
|
+
await controller.submit(data);
|
|
104
|
+
};
|
|
105
|
+
const reset = () => {
|
|
106
|
+
controller.reset();
|
|
107
|
+
};
|
|
108
|
+
return { status, message, error, submit, reset };
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Primitive for handling reactions (likes, claps, etc.).
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```tsx
|
|
115
|
+
* const { counts, userReactions, addReaction, removeReaction } = createReactions({
|
|
116
|
+
* contentId: 'my-post-slug',
|
|
117
|
+
* });
|
|
118
|
+
*
|
|
119
|
+
* <button onClick={() => addReaction('like')}>
|
|
120
|
+
* Like ({counts().like || 0})
|
|
121
|
+
* </button>
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function createReactions(options) {
|
|
125
|
+
const [counts, setCounts] = createSignal({});
|
|
126
|
+
const [userReactions, setUserReactions] = createSignal([]);
|
|
127
|
+
const [status, setStatus] = createSignal("idle");
|
|
128
|
+
const [error, setError] = createSignal(null);
|
|
129
|
+
const config = resolveConfig(options);
|
|
130
|
+
const controller = new ReactionsController(config, options.contentId);
|
|
131
|
+
createEffect(() => {
|
|
132
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
133
|
+
setCounts(state.counts);
|
|
134
|
+
setUserReactions(state.userReactions);
|
|
135
|
+
setStatus(state.status);
|
|
136
|
+
setError(state.error);
|
|
137
|
+
});
|
|
138
|
+
// Auto-fetch on mount (default: true)
|
|
139
|
+
if (options.autoFetch !== false) {
|
|
140
|
+
controller.fetch();
|
|
141
|
+
}
|
|
142
|
+
onCleanup(unsubscribe);
|
|
143
|
+
});
|
|
144
|
+
const addReaction = async (type) => {
|
|
145
|
+
await controller.add(type);
|
|
146
|
+
};
|
|
147
|
+
const removeReaction = async (type) => {
|
|
148
|
+
await controller.remove(type);
|
|
149
|
+
};
|
|
150
|
+
const refresh = async () => {
|
|
151
|
+
await controller.fetch();
|
|
152
|
+
};
|
|
153
|
+
return { counts, userReactions, status, error, addReaction, removeReaction, refresh };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Primitive for handling comments.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* ```tsx
|
|
160
|
+
* const { comments, status, postComment } = createComments({
|
|
161
|
+
* contentId: 'my-post-slug',
|
|
162
|
+
* });
|
|
163
|
+
*
|
|
164
|
+
* <For each={comments()}>
|
|
165
|
+
* {(comment) => (
|
|
166
|
+
* <div>
|
|
167
|
+
* <strong>{comment.authorName}</strong>: {comment.content}
|
|
168
|
+
* </div>
|
|
169
|
+
* )}
|
|
170
|
+
* </For>
|
|
171
|
+
*
|
|
172
|
+
* <button onClick={() => postComment('Anonymous', 'Great post!')}>
|
|
173
|
+
* Add Comment
|
|
174
|
+
* </button>
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
export function createComments(options) {
|
|
178
|
+
const [comments, setComments] = createSignal([]);
|
|
179
|
+
const [status, setStatus] = createSignal("idle");
|
|
180
|
+
const [error, setError] = createSignal(null);
|
|
181
|
+
const config = resolveConfig(options);
|
|
182
|
+
const controller = new CommentsController(config, options.contentId);
|
|
183
|
+
createEffect(() => {
|
|
184
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
185
|
+
setComments(state.comments);
|
|
186
|
+
setStatus(state.status);
|
|
187
|
+
setError(state.error);
|
|
188
|
+
});
|
|
189
|
+
// Auto-fetch on mount (default: true)
|
|
190
|
+
if (options.autoFetch !== false) {
|
|
191
|
+
controller.fetch();
|
|
192
|
+
}
|
|
193
|
+
onCleanup(unsubscribe);
|
|
194
|
+
});
|
|
195
|
+
const postComment = async (author, content, options) => {
|
|
196
|
+
await controller.post(author, content, options);
|
|
197
|
+
};
|
|
198
|
+
const refresh = async () => {
|
|
199
|
+
await controller.fetch();
|
|
200
|
+
};
|
|
201
|
+
return { comments, status, error, postComment, refresh };
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Primitive for handling waitlist signups.
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```tsx
|
|
208
|
+
* const { join, status, position } = createWaitlist({});
|
|
209
|
+
*
|
|
210
|
+
* <button onClick={() => join('user@example.com')}>Join Waitlist</button>
|
|
211
|
+
* {status() === 'success' && <p>You're #{position()} on the list!</p>}
|
|
212
|
+
* ```
|
|
213
|
+
*/
|
|
214
|
+
export function createWaitlist(options) {
|
|
215
|
+
const [status, setStatus] = createSignal("idle");
|
|
216
|
+
const [message, setMessage] = createSignal(null);
|
|
217
|
+
const [position, setPosition] = createSignal(null);
|
|
218
|
+
const [error, setError] = createSignal(null);
|
|
219
|
+
const config = resolveConfig(options);
|
|
220
|
+
const controller = new WaitlistController(config);
|
|
221
|
+
createEffect(() => {
|
|
222
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
223
|
+
setStatus(state.status);
|
|
224
|
+
setMessage(state.message);
|
|
225
|
+
setPosition(state.position);
|
|
226
|
+
setError(state.error);
|
|
227
|
+
});
|
|
228
|
+
onCleanup(unsubscribe);
|
|
229
|
+
});
|
|
230
|
+
const join = async (email, opts) => {
|
|
231
|
+
await controller.join(email, opts);
|
|
232
|
+
};
|
|
233
|
+
const reset = () => {
|
|
234
|
+
controller.reset();
|
|
235
|
+
};
|
|
236
|
+
return { status, message, position, error, join, reset };
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Primitive for tracking and displaying page views.
|
|
240
|
+
*
|
|
241
|
+
* @example
|
|
242
|
+
* ```tsx
|
|
243
|
+
* const { views, uniqueVisitors } = createViews({
|
|
244
|
+
* pageId: '/blog/my-post',
|
|
245
|
+
* });
|
|
246
|
+
*
|
|
247
|
+
* <span>{views()} views ({uniqueVisitors()} unique)</span>
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
export function createViews(options) {
|
|
251
|
+
const [views, setViews] = createSignal(0);
|
|
252
|
+
const [uniqueVisitors, setUniqueVisitors] = createSignal(0);
|
|
253
|
+
const [status, setStatus] = createSignal("idle");
|
|
254
|
+
const [error, setError] = createSignal(null);
|
|
255
|
+
const config = resolveConfig(options);
|
|
256
|
+
const controller = new ViewCountsController(config, options.pageId);
|
|
257
|
+
createEffect(() => {
|
|
258
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
259
|
+
setViews(state.views);
|
|
260
|
+
setUniqueVisitors(state.uniqueVisitors);
|
|
261
|
+
setStatus(state.status);
|
|
262
|
+
setError(state.error);
|
|
263
|
+
});
|
|
264
|
+
// Auto-record view on mount (default: true)
|
|
265
|
+
if (options.autoRecord !== false) {
|
|
266
|
+
controller.record();
|
|
267
|
+
}
|
|
268
|
+
onCleanup(unsubscribe);
|
|
269
|
+
});
|
|
270
|
+
const record = async () => {
|
|
271
|
+
await controller.record();
|
|
272
|
+
};
|
|
273
|
+
const refresh = async () => {
|
|
274
|
+
await controller.fetch();
|
|
275
|
+
};
|
|
276
|
+
return { views, uniqueVisitors, status, error, record, refresh };
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Primitive for handling feedback submissions.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* ```tsx
|
|
283
|
+
* const { submit, status } = createFeedback({});
|
|
284
|
+
*
|
|
285
|
+
* <button onClick={() => submit('feature', 'Add dark mode!')}>
|
|
286
|
+
* Submit Feedback
|
|
287
|
+
* </button>
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
export function createFeedback(options) {
|
|
291
|
+
const [status, setStatus] = createSignal("idle");
|
|
292
|
+
const [message, setMessage] = createSignal(null);
|
|
293
|
+
const [error, setError] = createSignal(null);
|
|
294
|
+
const config = resolveConfig(options);
|
|
295
|
+
const controller = new FeedbackController(config);
|
|
296
|
+
createEffect(() => {
|
|
297
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
298
|
+
setStatus(state.status);
|
|
299
|
+
setMessage(state.message);
|
|
300
|
+
setError(state.error);
|
|
301
|
+
});
|
|
302
|
+
onCleanup(unsubscribe);
|
|
303
|
+
});
|
|
304
|
+
const submit = async (type, content, opts) => {
|
|
305
|
+
await controller.submit(type, content, opts);
|
|
306
|
+
};
|
|
307
|
+
const reset = () => {
|
|
308
|
+
controller.reset();
|
|
309
|
+
};
|
|
310
|
+
return { status, message, error, submit, reset };
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Primitive for displaying and voting on polls.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```tsx
|
|
317
|
+
* // With meta tag: <meta name="jamwidgets-site-key" content="your-key" />
|
|
318
|
+
* const { poll, vote, hasVoted } = createPoll({ slug: 'favorite-framework' });
|
|
319
|
+
*
|
|
320
|
+
* <Show when={poll()}>
|
|
321
|
+
* {(p) => (
|
|
322
|
+
* <div>
|
|
323
|
+
* <h3>{p().question}</h3>
|
|
324
|
+
* <For each={p().options}>
|
|
325
|
+
* {(opt) => (
|
|
326
|
+
* <button onClick={() => vote([opt.id])} disabled={hasVoted()}>
|
|
327
|
+
* {opt.text} ({p().results[opt.id] || 0} votes)
|
|
328
|
+
* </button>
|
|
329
|
+
* )}
|
|
330
|
+
* </For>
|
|
331
|
+
* </div>
|
|
332
|
+
* )}
|
|
333
|
+
* </Show>
|
|
334
|
+
* ```
|
|
335
|
+
*/
|
|
336
|
+
export function createPoll(options) {
|
|
337
|
+
const [poll, setPoll] = createSignal(null);
|
|
338
|
+
const [status, setStatus] = createSignal("idle");
|
|
339
|
+
const [error, setError] = createSignal(null);
|
|
340
|
+
const config = resolveConfig(options);
|
|
341
|
+
const controller = new PollController(config, options.slug);
|
|
342
|
+
createEffect(() => {
|
|
343
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
344
|
+
setPoll(state.poll);
|
|
345
|
+
setStatus(state.status);
|
|
346
|
+
setError(state.error);
|
|
347
|
+
});
|
|
348
|
+
// Auto-fetch on mount (default: true)
|
|
349
|
+
if (options.autoFetch !== false) {
|
|
350
|
+
controller.fetch();
|
|
351
|
+
}
|
|
352
|
+
onCleanup(unsubscribe);
|
|
353
|
+
});
|
|
354
|
+
const vote = async (selectedOptions) => {
|
|
355
|
+
await controller.vote(selectedOptions);
|
|
356
|
+
};
|
|
357
|
+
const refresh = async () => {
|
|
358
|
+
await controller.fetch();
|
|
359
|
+
};
|
|
360
|
+
const hasVoted = () => controller.hasVoted();
|
|
361
|
+
return { poll, status, error, vote, hasVoted, refresh };
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Primitive for displaying site announcements.
|
|
365
|
+
*
|
|
366
|
+
* @example
|
|
367
|
+
* ```tsx
|
|
368
|
+
* // With meta tag: <meta name="jamwidgets-site-key" content="your-key" />
|
|
369
|
+
* const { announcements, dismiss } = createAnnouncements({});
|
|
370
|
+
*
|
|
371
|
+
* <For each={announcements()}>
|
|
372
|
+
* {(ann) => (
|
|
373
|
+
* <div class={`announcement-${ann.announcementType}`}>
|
|
374
|
+
* {ann.content}
|
|
375
|
+
* {ann.isDismissible && (
|
|
376
|
+
* <button onClick={() => dismiss(ann.id)}>Dismiss</button>
|
|
377
|
+
* )}
|
|
378
|
+
* </div>
|
|
379
|
+
* )}
|
|
380
|
+
* </For>
|
|
381
|
+
* ```
|
|
382
|
+
*/
|
|
383
|
+
export function createAnnouncements(options) {
|
|
384
|
+
const [announcements, setAnnouncements] = createSignal([]);
|
|
385
|
+
const [status, setStatus] = createSignal("idle");
|
|
386
|
+
const [error, setError] = createSignal(null);
|
|
387
|
+
const config = resolveConfig(options);
|
|
388
|
+
const controller = new AnnouncementsController(config);
|
|
389
|
+
createEffect(() => {
|
|
390
|
+
const unsubscribe = controller.subscribe((state) => {
|
|
391
|
+
// Only show non-dismissed announcements
|
|
392
|
+
setAnnouncements(controller.getVisibleAnnouncements());
|
|
393
|
+
setStatus(state.status);
|
|
394
|
+
setError(state.error);
|
|
395
|
+
});
|
|
396
|
+
// Auto-fetch on mount (default: true)
|
|
397
|
+
if (options.autoFetch !== false) {
|
|
398
|
+
controller.fetch();
|
|
399
|
+
}
|
|
400
|
+
onCleanup(unsubscribe);
|
|
401
|
+
});
|
|
402
|
+
const dismiss = async (announcementId) => {
|
|
403
|
+
await controller.dismiss(announcementId);
|
|
404
|
+
};
|
|
405
|
+
const refresh = async () => {
|
|
406
|
+
await controller.fetch();
|
|
407
|
+
};
|
|
408
|
+
return { announcements, status, error, dismiss, refresh };
|
|
409
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jamwidgets/solid",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "SolidJS primitives for Jamwidgets (forms, comments, reactions, subscriptions)",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/jamwidgets/solid.git"
|
|
8
|
+
},
|
|
9
|
+
"publishConfig": {
|
|
10
|
+
"access": "public"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://jamwidgets.com",
|
|
13
|
+
"author": "Tim Marks <tim@imothee.xyz>",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"main": "./dist/index.js",
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"exports": {
|
|
18
|
+
".": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"import": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"keywords": [
|
|
27
|
+
"solid",
|
|
28
|
+
"solidjs",
|
|
29
|
+
"jamwidgets",
|
|
30
|
+
"forms",
|
|
31
|
+
"comments",
|
|
32
|
+
"reactions",
|
|
33
|
+
"subscribe",
|
|
34
|
+
"primitives"
|
|
35
|
+
],
|
|
36
|
+
"license": "MIT",
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@jamwidgets/core": "0.1.0"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"solid-js": "^1.8.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"solid-js": "^1.9.0",
|
|
45
|
+
"typescript": "^5.7.3"
|
|
46
|
+
},
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc",
|
|
49
|
+
"dev": "tsc --watch"
|
|
50
|
+
}
|
|
51
|
+
}
|