@refraction-ui/astro 0.8.0 → 0.9.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/analytics/index.ts +5 -0
- package/dist/astro-accordion/Accordion.stories.ts +12 -0
- package/dist/astro-ai/Ai.stories.ts +12 -0
- package/dist/astro-analytics/Analytics.stories.ts +12 -0
- package/dist/astro-animated-text/AnimatedText.stories.ts +12 -0
- package/dist/astro-app-shell/AppShell.stories.ts +12 -0
- package/dist/astro-auth/Auth.stories.ts +12 -0
- package/dist/astro-avatar/Avatar.stories.ts +12 -0
- package/dist/astro-avatar-group/AvatarGroup.stories.ts +12 -0
- package/dist/astro-badge/Badge.stories.ts +16 -0
- package/dist/astro-bottom-nav/BottomNav.stories.ts +12 -0
- package/dist/astro-breadcrumbs/Breadcrumbs.stories.ts +12 -0
- package/dist/astro-button/Button.stories.ts +12 -0
- package/dist/astro-calendar/Calendar.stories.ts +12 -0
- package/dist/astro-callout/Callout.stories.ts +12 -0
- package/dist/astro-card/Card.stories.ts +12 -0
- package/dist/astro-card-grid/CardGrid.stories.ts +12 -0
- package/dist/astro-carousel/Carousel.stories.ts +12 -0
- package/dist/astro-charts/Charts.stories.ts +12 -0
- package/dist/astro-checkbox/Checkbox.stories.ts +12 -0
- package/dist/astro-code-block/CodeBlock.stories.ts +12 -0
- package/dist/astro-code-editor/CodeEditor.stories.ts +12 -0
- package/dist/astro-collapsible/Collapsible.stories.ts +12 -0
- package/dist/astro-command/Command.stories.ts +12 -0
- package/dist/astro-command-input/CommandInput.stories.ts +12 -0
- package/dist/astro-content-protection/ContentProtection.stories.ts +12 -0
- package/dist/astro-conversation/Chat.astro +208 -0
- package/dist/astro-conversation/Conversation.stories.ts +12 -0
- package/dist/astro-conversation/index.ts +29 -0
- package/dist/astro-cookie-consent/CookieConsent.astro +141 -0
- package/dist/astro-cookie-consent/index.ts +12 -0
- package/dist/astro-data-table/DataTable.stories.ts +12 -0
- package/dist/astro-date-picker/DatePicker.stories.ts +12 -0
- package/dist/astro-device-frame/DeviceFrame.stories.ts +12 -0
- package/dist/astro-dialog/Dialog.stories.ts +12 -0
- package/dist/astro-diff-viewer/DiffViewer.stories.ts +12 -0
- package/dist/astro-dropdown-menu/DropdownMenu.stories.ts +12 -0
- package/dist/astro-emoji-picker/EmojiPicker.stories.ts +12 -0
- package/dist/astro-feedback-dialog/FeedbackDialog.stories.ts +12 -0
- package/dist/astro-file-tree/FileTree.stories.ts +12 -0
- package/dist/astro-file-upload/FileUpload.stories.ts +12 -0
- package/dist/astro-footer/Footer.stories.ts +12 -0
- package/dist/astro-icon-system/IconSystem.stories.ts +12 -0
- package/dist/astro-inline-editor/InlineEditor.stories.ts +12 -0
- package/dist/astro-input/Input.stories.ts +12 -0
- package/dist/astro-input-group/InputGroup.stories.ts +12 -0
- package/dist/astro-install-prompt/InstallPrompt.stories.ts +12 -0
- package/dist/astro-keyboard-shortcut/KeyboardShortcut.stories.ts +12 -0
- package/dist/astro-language-selector/LanguageSelector.stories.ts +12 -0
- package/dist/astro-link-card/LinkCard.stories.ts +12 -0
- package/dist/astro-logger/Logger.stories.ts +12 -0
- package/dist/astro-markdown-renderer/MarkdownRenderer.stories.ts +12 -0
- package/dist/astro-mobile-nav/MobileNav.stories.ts +12 -0
- package/dist/astro-navbar/Navbar.stories.ts +12 -0
- package/dist/astro-otp-input/OtpInput.stories.ts +12 -0
- package/dist/astro-pagination/Pagination.stories.ts +12 -0
- package/dist/astro-payment/Payment.stories.ts +12 -0
- package/dist/astro-popover/Popover.stories.ts +12 -0
- package/dist/astro-presence-indicator/PresenceIndicator.stories.ts +12 -0
- package/dist/astro-progress-display/ProgressDisplay.stories.ts +12 -0
- package/dist/astro-radio/Radio.stories.ts +12 -0
- package/dist/astro-reaction-bar/ReactionBar.stories.ts +12 -0
- package/dist/astro-resizable-layout/ResizableLayout.stories.ts +12 -0
- package/dist/astro-rich-editor/RichEditor.stories.ts +12 -0
- package/dist/astro-search-bar/SearchBar.stories.ts +12 -0
- package/dist/astro-select/Select.stories.ts +12 -0
- package/dist/astro-sidebar/Sidebar.stories.ts +12 -0
- package/dist/astro-skeleton/Skeleton.stories.ts +12 -0
- package/dist/astro-skip-to-content/SkipToContent.stories.ts +12 -0
- package/dist/astro-slide-viewer/SlideViewer.stories.ts +12 -0
- package/dist/astro-status-indicator/StatusIndicator.stories.ts +12 -0
- package/dist/astro-steps/Steps.stories.ts +12 -0
- package/dist/astro-switch/Switch.stories.ts +12 -0
- package/dist/astro-table-of-contents/TableOfContents.stories.ts +12 -0
- package/dist/astro-tabs/Tabs.stories.ts +12 -0
- package/dist/astro-textarea/Textarea.stories.ts +12 -0
- package/dist/astro-theme/Theme.stories.ts +12 -0
- package/dist/astro-thread-view/ThreadView.stories.ts +12 -0
- package/dist/astro-toast/Toast.stories.ts +12 -0
- package/dist/astro-tooltip/Tooltip.stories.ts +12 -0
- package/dist/astro-version-selector/VersionSelector.stories.ts +12 -0
- package/dist/astro-video-player/VideoPlayer.stories.ts +12 -0
- package/dist/astro-voice-pill/VoicePill.stories.ts +12 -0
- package/dist/astro-waveform/Waveform.stories.ts +12 -0
- package/dist/conversation/conversation.ts +379 -0
- package/dist/conversation/index.ts +30 -0
- package/dist/conversation/selectors.ts +58 -0
- package/dist/conversation/types.ts +222 -0
- package/dist/cookie-consent/cookie-consent.ts +132 -0
- package/dist/cookie-consent/index.ts +10 -0
- package/dist/cookie-consent/types.ts +72 -0
- package/dist/index.ts +2 -0
- package/dist/shared/library-origin-error.ts +1 -2
- package/dist/shared/types.ts +1 -1
- package/dist/theme/theme-script.ts +1 -1
- package/package.json +3 -1
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @refraction-ui/conversation — headless chat store.
|
|
3
|
+
*
|
|
4
|
+
* Owns the *data format* and *behavior* of a multi-conversation chat with
|
|
5
|
+
* reply-threads, reactions, edit/delete, and streaming. It has NO UI opinion
|
|
6
|
+
* and NO backend opinion: the wire is supplied by a {@link ChatTransport}
|
|
7
|
+
* adapter, exactly as {@link AIProvider} is supplied to `@refraction-ui/ai`.
|
|
8
|
+
*
|
|
9
|
+
* Two threading modes (how replies relate to the main timeline):
|
|
10
|
+
* - `'inline'` (default): every message — replies included — appears in the
|
|
11
|
+
* main timeline; a reply shows its parent quoted, and opening it reveals the
|
|
12
|
+
* focused thread in a side panel.
|
|
13
|
+
* - `'panel'`: only root messages appear in the main timeline (each showing a
|
|
14
|
+
* reply count); replies live solely in the thread panel (Slack-style).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** Author display info — structurally compatible with thread-view's MessageData. */
|
|
18
|
+
export interface ChatAuthor {
|
|
19
|
+
id: string
|
|
20
|
+
name: string
|
|
21
|
+
avatarUrl?: string
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Conversational role. Generic; the transport decides what each means. */
|
|
25
|
+
export type ChatRole = 'user' | 'assistant' | 'system'
|
|
26
|
+
|
|
27
|
+
/** Lifecycle of a single message. */
|
|
28
|
+
export type MessageStatus = 'pending' | 'streaming' | 'sent' | 'error'
|
|
29
|
+
|
|
30
|
+
/** How replies relate to the main timeline. */
|
|
31
|
+
export type ThreadingMode = 'inline' | 'panel'
|
|
32
|
+
|
|
33
|
+
/** An emoji reaction aggregate on a message. */
|
|
34
|
+
export interface MessageReaction {
|
|
35
|
+
emoji: string
|
|
36
|
+
count: number
|
|
37
|
+
/** Whether the local user has reacted with this emoji */
|
|
38
|
+
userReacted: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** A file/media attachment (images, gifs, docs). */
|
|
42
|
+
export interface MessageAttachment {
|
|
43
|
+
id: string
|
|
44
|
+
name: string
|
|
45
|
+
url: string
|
|
46
|
+
/** MIME type, e.g. 'image/gif', 'image/png', 'application/pdf' */
|
|
47
|
+
type: string
|
|
48
|
+
size?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** A single message. */
|
|
52
|
+
export interface ChatMessage {
|
|
53
|
+
/** Unique message ID */
|
|
54
|
+
id: string
|
|
55
|
+
/** Conversation this message belongs to */
|
|
56
|
+
conversationId: string
|
|
57
|
+
/** Who/what produced it */
|
|
58
|
+
role: ChatRole
|
|
59
|
+
/** Author display info */
|
|
60
|
+
author: ChatAuthor
|
|
61
|
+
/** Content (markdown — may contain code fences, images/gifs; grows while streaming) */
|
|
62
|
+
content: string
|
|
63
|
+
/** Creation time */
|
|
64
|
+
timestamp: Date
|
|
65
|
+
/** Lifecycle status */
|
|
66
|
+
status: MessageStatus
|
|
67
|
+
/** Failure reason when `status === 'error'` */
|
|
68
|
+
error?: string
|
|
69
|
+
/**
|
|
70
|
+
* Root message id of the reply-thread this message belongs to. Absent on
|
|
71
|
+
* root/main-timeline messages. Threads are one level deep.
|
|
72
|
+
*/
|
|
73
|
+
parentId?: string
|
|
74
|
+
/** Emoji reactions */
|
|
75
|
+
reactions?: MessageReaction[]
|
|
76
|
+
/** Attachments (images/gifs/files) */
|
|
77
|
+
attachments?: MessageAttachment[]
|
|
78
|
+
/** Whether this message was edited */
|
|
79
|
+
edited?: boolean
|
|
80
|
+
/** Arbitrary consumer metadata — never inspected by the store */
|
|
81
|
+
metadata?: Record<string, unknown>
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** A conversation/session (an entry in the conversation list). */
|
|
85
|
+
export interface Conversation {
|
|
86
|
+
id: string
|
|
87
|
+
/** Display title (auto-derived from the first message unless set) */
|
|
88
|
+
title: string
|
|
89
|
+
createdAt: Date
|
|
90
|
+
/** Last-activity time (bumped on every message) */
|
|
91
|
+
updatedAt: Date
|
|
92
|
+
metadata?: Record<string, unknown>
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Context handed to the transport for one send. */
|
|
96
|
+
export interface SendContext {
|
|
97
|
+
/** Active conversation ID */
|
|
98
|
+
conversationId: string
|
|
99
|
+
/** The user message being sent */
|
|
100
|
+
message: ChatMessage
|
|
101
|
+
/** Prior messages in scope (the thread when replying, else the main timeline) */
|
|
102
|
+
history: ChatMessage[]
|
|
103
|
+
/** Root message id when this send is a reply within a thread */
|
|
104
|
+
parentId?: string
|
|
105
|
+
/** Aborted when the consumer calls `stop()` */
|
|
106
|
+
signal: AbortSignal
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** A streamed piece of an assistant reply. */
|
|
110
|
+
export interface TransportChunk {
|
|
111
|
+
/** Token/delta to append to the assistant reply */
|
|
112
|
+
delta?: string
|
|
113
|
+
/** Replace the whole reply content (for non-streaming transports) */
|
|
114
|
+
content?: string
|
|
115
|
+
/** Arbitrary metadata merged onto the assistant message */
|
|
116
|
+
metadata?: Record<string, unknown>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* The backend wire. Implemented by adapter code the consumer owns — never by
|
|
121
|
+
* this package. A non-streaming backend yields one chunk with `content` then
|
|
122
|
+
* returns; a streaming backend yields many `delta` chunks.
|
|
123
|
+
*/
|
|
124
|
+
export interface ChatTransport {
|
|
125
|
+
name: string
|
|
126
|
+
send(ctx: SendContext): AsyncIterable<TransportChunk>
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Snapshot of the store. Returned by `getState()`; treat as immutable. */
|
|
130
|
+
export interface ConversationState {
|
|
131
|
+
/** All conversations, most-recently-updated first */
|
|
132
|
+
conversations: Conversation[]
|
|
133
|
+
/** Active conversation ID, or null when none selected */
|
|
134
|
+
activeConversationId: string | null
|
|
135
|
+
/** Flat messages of the active conversation (roots + replies) */
|
|
136
|
+
messages: ChatMessage[]
|
|
137
|
+
/** Root id of the thread shown in the side panel, or null when closed */
|
|
138
|
+
openThreadRootId: string | null
|
|
139
|
+
/** Current threading mode */
|
|
140
|
+
threadingMode: ThreadingMode
|
|
141
|
+
/** Coarse store status */
|
|
142
|
+
status: 'idle' | 'sending' | 'streaming' | 'error'
|
|
143
|
+
/** Last error, if any */
|
|
144
|
+
error: string | null
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Options for `createConversation`. */
|
|
148
|
+
export interface ConversationConfig {
|
|
149
|
+
/** Backend wire. Without it the store is a local message log. */
|
|
150
|
+
transport?: ChatTransport
|
|
151
|
+
/** Seed conversations */
|
|
152
|
+
conversations?: Conversation[]
|
|
153
|
+
/** Seed messages keyed by conversationId */
|
|
154
|
+
messages?: Record<string, ChatMessage[]>
|
|
155
|
+
/** Initially active conversation */
|
|
156
|
+
activeConversationId?: string
|
|
157
|
+
/** The local user — author of outgoing messages */
|
|
158
|
+
currentUser?: ChatAuthor
|
|
159
|
+
/** Author of assistant replies (default: { id: 'assistant', name: 'Assistant' }) */
|
|
160
|
+
assistant?: ChatAuthor
|
|
161
|
+
/** Derive a conversation title from its first message (default: first ~48 chars) */
|
|
162
|
+
generateTitle?: (firstMessage: string) => string
|
|
163
|
+
/** Threading mode (default 'inline') */
|
|
164
|
+
threadingMode?: ThreadingMode
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** Options for a single `sendMessage`. */
|
|
168
|
+
export interface SendOptions {
|
|
169
|
+
/** Send into a specific conversation instead of the active one */
|
|
170
|
+
conversationId?: string
|
|
171
|
+
/** Reply within the thread of this message id */
|
|
172
|
+
replyTo?: string
|
|
173
|
+
/** Attachments on the outgoing message */
|
|
174
|
+
attachments?: MessageAttachment[]
|
|
175
|
+
/** Metadata attached to the outgoing user message */
|
|
176
|
+
metadata?: Record<string, unknown>
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** The framework-agnostic store. React/Astro adapters wrap this. */
|
|
180
|
+
export interface ConversationAPI {
|
|
181
|
+
/** Current immutable snapshot */
|
|
182
|
+
getState(): ConversationState
|
|
183
|
+
/** Subscribe to changes; returns an unsubscribe fn (suits useSyncExternalStore) */
|
|
184
|
+
subscribe(listener: () => void): () => void
|
|
185
|
+
|
|
186
|
+
// — conversation management —
|
|
187
|
+
/** Create a new conversation and make it active */
|
|
188
|
+
newConversation(opts?: { title?: string; metadata?: Record<string, unknown> }): Conversation
|
|
189
|
+
/** Make a conversation active */
|
|
190
|
+
selectConversation(conversationId: string): void
|
|
191
|
+
/** Delete a conversation and its messages */
|
|
192
|
+
deleteConversation(conversationId: string): void
|
|
193
|
+
/** Rename a conversation */
|
|
194
|
+
renameConversation(conversationId: string, title: string): void
|
|
195
|
+
|
|
196
|
+
// — messaging —
|
|
197
|
+
/** Optimistically append the user message, then stream the reply via transport */
|
|
198
|
+
sendMessage(content: string, opts?: SendOptions): Promise<void>
|
|
199
|
+
/** Append a message directly (no transport round-trip) */
|
|
200
|
+
appendMessage(message: ChatMessage): void
|
|
201
|
+
/** Edit a message's content (marks it edited) */
|
|
202
|
+
editMessage(messageId: string, content: string): void
|
|
203
|
+
/** Delete a message (and, for a thread root, its replies) */
|
|
204
|
+
deleteMessage(messageId: string): void
|
|
205
|
+
/** Toggle the local user's emoji reaction on a message */
|
|
206
|
+
react(messageId: string, emoji: string): void
|
|
207
|
+
/** Retry the last failed turn in the active conversation */
|
|
208
|
+
retryLast(): Promise<void>
|
|
209
|
+
/** Abort the in-flight stream, keeping any partial reply */
|
|
210
|
+
stop(): void
|
|
211
|
+
|
|
212
|
+
// — threads / view —
|
|
213
|
+
/** Open the side panel focused on a message's thread */
|
|
214
|
+
openThread(rootId: string): void
|
|
215
|
+
/** Close the thread side panel */
|
|
216
|
+
closeThread(): void
|
|
217
|
+
/** Switch threading mode */
|
|
218
|
+
setThreadingMode(mode: ThreadingMode): void
|
|
219
|
+
|
|
220
|
+
/** Swap the transport at runtime */
|
|
221
|
+
setTransport(transport: ChatTransport): void
|
|
222
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CookieCategory,
|
|
3
|
+
CookieConsentAPI,
|
|
4
|
+
CookieConsentConfig,
|
|
5
|
+
CookieConsentState,
|
|
6
|
+
ConsentPreferences,
|
|
7
|
+
} from './types.js'
|
|
8
|
+
|
|
9
|
+
const DEFAULT_KEY = 'rfr-cookie-consent'
|
|
10
|
+
|
|
11
|
+
/** Popular default GDPR-style categories. */
|
|
12
|
+
export const DEFAULT_CATEGORIES: CookieCategory[] = [
|
|
13
|
+
{ id: 'necessary', label: 'Strictly necessary', description: 'Required for the site to function. Always on.', required: true },
|
|
14
|
+
{ id: 'preferences', label: 'Preferences', description: 'Remembers your settings and choices.' },
|
|
15
|
+
{ id: 'analytics', label: 'Analytics', description: 'Helps us understand how the site is used.' },
|
|
16
|
+
{ id: 'marketing', label: 'Marketing', description: 'Used to personalize ads and measure campaigns.' },
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
interface Persisted {
|
|
20
|
+
version?: string
|
|
21
|
+
preferences: ConsentPreferences
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Preferences with only required categories granted. */
|
|
25
|
+
function baseline(categories: CookieCategory[]): ConsentPreferences {
|
|
26
|
+
const p: ConsentPreferences = {}
|
|
27
|
+
for (const c of categories) p[c.id] = !!c.required
|
|
28
|
+
return p
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* createCookieConsent — headless consent store. Reads any persisted choice on
|
|
33
|
+
* init (re-prompting if the `version` changed), and exposes accept/reject/save
|
|
34
|
+
* plus banner visibility. Persistence is via the optional storage adapter.
|
|
35
|
+
*/
|
|
36
|
+
export function createCookieConsent(config: CookieConsentConfig = {}): CookieConsentAPI {
|
|
37
|
+
const categories = config.categories ?? DEFAULT_CATEGORIES
|
|
38
|
+
const storage = config.storage
|
|
39
|
+
const key = config.storageKey ?? DEFAULT_KEY
|
|
40
|
+
const version = config.version
|
|
41
|
+
|
|
42
|
+
let preferences = baseline(categories)
|
|
43
|
+
let consented = false
|
|
44
|
+
|
|
45
|
+
// Hydrate from storage.
|
|
46
|
+
const raw = storage?.get(key) ?? null
|
|
47
|
+
if (raw) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(raw) as Persisted
|
|
50
|
+
if ((parsed.version ?? undefined) === version) {
|
|
51
|
+
preferences = { ...baseline(categories), ...parsed.preferences }
|
|
52
|
+
// required stay on regardless of stored value
|
|
53
|
+
for (const c of categories) if (c.required) preferences[c.id] = true
|
|
54
|
+
consented = true
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// ignore corrupt storage
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let open = !consented
|
|
62
|
+
|
|
63
|
+
const listeners = new Set<() => void>()
|
|
64
|
+
let snapshot = build()
|
|
65
|
+
|
|
66
|
+
function build(): CookieConsentState {
|
|
67
|
+
return { consented, preferences: { ...preferences }, open, categories }
|
|
68
|
+
}
|
|
69
|
+
function emit(): void {
|
|
70
|
+
snapshot = build()
|
|
71
|
+
for (const l of listeners) l()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function persist(): void {
|
|
75
|
+
storage?.set(key, JSON.stringify({ version, preferences } satisfies Persisted))
|
|
76
|
+
config.onChange?.({ ...preferences })
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function save(next: ConsentPreferences): void {
|
|
80
|
+
const merged = { ...baseline(categories), ...next }
|
|
81
|
+
for (const c of categories) if (c.required) merged[c.id] = true
|
|
82
|
+
preferences = merged
|
|
83
|
+
consented = true
|
|
84
|
+
open = false
|
|
85
|
+
persist()
|
|
86
|
+
emit()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
getState() {
|
|
91
|
+
return snapshot
|
|
92
|
+
},
|
|
93
|
+
subscribe(listener) {
|
|
94
|
+
listeners.add(listener)
|
|
95
|
+
return () => listeners.delete(listener)
|
|
96
|
+
},
|
|
97
|
+
acceptAll() {
|
|
98
|
+
const all: ConsentPreferences = {}
|
|
99
|
+
for (const c of categories) all[c.id] = true
|
|
100
|
+
save(all)
|
|
101
|
+
},
|
|
102
|
+
rejectAll() {
|
|
103
|
+
save(baseline(categories))
|
|
104
|
+
},
|
|
105
|
+
savePreferences(prefs) {
|
|
106
|
+
save(prefs)
|
|
107
|
+
},
|
|
108
|
+
setPreference(id, value) {
|
|
109
|
+
const cat = categories.find((c) => c.id === id)
|
|
110
|
+
if (!cat || cat.required) return
|
|
111
|
+
preferences = { ...preferences, [id]: value }
|
|
112
|
+
emit()
|
|
113
|
+
},
|
|
114
|
+
reset() {
|
|
115
|
+
storage?.remove(key)
|
|
116
|
+
preferences = baseline(categories)
|
|
117
|
+
consented = false
|
|
118
|
+
open = true
|
|
119
|
+
emit()
|
|
120
|
+
},
|
|
121
|
+
openSettings() {
|
|
122
|
+
if (open) return
|
|
123
|
+
open = true
|
|
124
|
+
emit()
|
|
125
|
+
},
|
|
126
|
+
close() {
|
|
127
|
+
if (!open) return
|
|
128
|
+
open = false
|
|
129
|
+
emit()
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @refraction-ui/cookie-consent — headless cookie-consent store.
|
|
3
|
+
*
|
|
4
|
+
* Owns consent categories + the user's per-category opt-in and banner state.
|
|
5
|
+
* Persistence is backend-agnostic via a {@link CookieStorage} adapter (the React
|
|
6
|
+
* adapter defaults to localStorage); no DOM/cookie access in the core.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** A consent category the user can opt into. */
|
|
10
|
+
export interface CookieCategory {
|
|
11
|
+
id: string
|
|
12
|
+
label: string
|
|
13
|
+
description?: string
|
|
14
|
+
/** Required categories are always on and cannot be toggled off. */
|
|
15
|
+
required?: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Per-category opt-in map (categoryId → granted). */
|
|
19
|
+
export type ConsentPreferences = Record<string, boolean>
|
|
20
|
+
|
|
21
|
+
/** Persistence adapter — implement over localStorage, cookies, a backend, etc. */
|
|
22
|
+
export interface CookieStorage {
|
|
23
|
+
get(key: string): string | null
|
|
24
|
+
set(key: string, value: string): void
|
|
25
|
+
remove(key: string): void
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Options for `createCookieConsent`. */
|
|
29
|
+
export interface CookieConsentConfig {
|
|
30
|
+
/** Consent categories (default: necessary*, analytics, marketing, preferences) */
|
|
31
|
+
categories?: CookieCategory[]
|
|
32
|
+
/** Persistence adapter; omit for in-memory (non-persistent) */
|
|
33
|
+
storage?: CookieStorage
|
|
34
|
+
/** Storage key (default 'rfr-cookie-consent') */
|
|
35
|
+
storageKey?: string
|
|
36
|
+
/** Consent version — bump to re-prompt users after a policy change */
|
|
37
|
+
version?: string
|
|
38
|
+
/** Called whenever consent is saved, with the resolved preferences */
|
|
39
|
+
onChange?: (preferences: ConsentPreferences) => void
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Immutable store snapshot. */
|
|
43
|
+
export interface CookieConsentState {
|
|
44
|
+
/** Whether the user has made a choice (false → show the banner) */
|
|
45
|
+
consented: boolean
|
|
46
|
+
/** Per-category opt-in */
|
|
47
|
+
preferences: ConsentPreferences
|
|
48
|
+
/** Banner/dialog visibility */
|
|
49
|
+
open: boolean
|
|
50
|
+
/** The configured categories */
|
|
51
|
+
categories: CookieCategory[]
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The framework-agnostic store. */
|
|
55
|
+
export interface CookieConsentAPI {
|
|
56
|
+
getState(): CookieConsentState
|
|
57
|
+
subscribe(listener: () => void): () => void
|
|
58
|
+
/** Grant every category and persist */
|
|
59
|
+
acceptAll(): void
|
|
60
|
+
/** Grant only required categories and persist */
|
|
61
|
+
rejectAll(): void
|
|
62
|
+
/** Persist the given preferences (required categories are forced on) */
|
|
63
|
+
savePreferences(preferences: ConsentPreferences): void
|
|
64
|
+
/** Toggle a single (non-required) category in the working preferences */
|
|
65
|
+
setPreference(id: string, value: boolean): void
|
|
66
|
+
/** Clear stored consent and re-open the banner */
|
|
67
|
+
reset(): void
|
|
68
|
+
/** Open the banner/settings */
|
|
69
|
+
openSettings(): void
|
|
70
|
+
/** Close the banner without changing stored consent */
|
|
71
|
+
close(): void
|
|
72
|
+
}
|
package/dist/index.ts
CHANGED
|
@@ -18,7 +18,9 @@ export * from './astro-checkbox/index.ts';
|
|
|
18
18
|
export * from './astro-code-editor/index.ts';
|
|
19
19
|
export * from './astro-collapsible/index.ts';
|
|
20
20
|
export * from './astro-command/index.ts';
|
|
21
|
+
export * from './astro-conversation/index.ts';
|
|
21
22
|
export * from './astro-content-protection/index.ts';
|
|
23
|
+
export * from './astro-cookie-consent/index.ts';
|
|
22
24
|
export * from './astro-data-table/index.ts';
|
|
23
25
|
export * from './astro-date-picker/index.ts';
|
|
24
26
|
export * from './astro-device-frame/index.ts';
|
|
@@ -22,7 +22,6 @@ const REFRACTION_FRAME = /@refraction-ui\//
|
|
|
22
22
|
/** Frameworks a refraction-ui adapter can run under. */
|
|
23
23
|
export type LibraryFramework =
|
|
24
24
|
| 'react'
|
|
25
|
-
| 'angular'
|
|
26
25
|
| 'astro'
|
|
27
26
|
| 'vue'
|
|
28
27
|
| 'svelte'
|
|
@@ -203,7 +202,7 @@ export type LibraryOriginIdentity = Pick<
|
|
|
203
202
|
|
|
204
203
|
/**
|
|
205
204
|
* The single capture primitive shared by every per-framework seam (React error
|
|
206
|
-
* boundary,
|
|
205
|
+
* boundary, Astro middleware). It performs the entire
|
|
207
206
|
* guarded flow in one place so the seams stay thin and identical in behavior:
|
|
208
207
|
*
|
|
209
208
|
* 1. **Library-origin filter** — if the error's stack has no
|
package/dist/shared/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Framework-agnostic base prop types for all refraction-ui components.
|
|
3
|
-
* These are headless core types — no React
|
|
3
|
+
* These are headless core types — no React or Astro imports.
|
|
4
4
|
* Framework wrappers extend these with framework-specific types.
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Inline script for preventing theme flash on page load.
|
|
3
3
|
* Inject this as a <script> tag in the <head> before any CSS.
|
|
4
|
-
* Works with any framework (React,
|
|
4
|
+
* Works with any framework (React, Astro, plain HTML).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
export function getThemeScript(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@refraction-ui/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "All Refraction UI Astro components in one package",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
"@refraction-ui/astro-code-editor": "workspace:*",
|
|
39
39
|
"@refraction-ui/astro-collapsible": "workspace:*",
|
|
40
40
|
"@refraction-ui/astro-command": "workspace:*",
|
|
41
|
+
"@refraction-ui/astro-conversation": "workspace:*",
|
|
41
42
|
"@refraction-ui/astro-content-protection": "workspace:*",
|
|
43
|
+
"@refraction-ui/astro-cookie-consent": "workspace:*",
|
|
42
44
|
"@refraction-ui/astro-data-table": "workspace:*",
|
|
43
45
|
"@refraction-ui/astro-date-picker": "workspace:*",
|
|
44
46
|
"@refraction-ui/astro-device-frame": "workspace:*",
|