@refraction-ui/astro 0.8.1 → 0.10.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.
Files changed (93) hide show
  1. package/dist/analytics/index.ts +5 -0
  2. package/dist/astro-accordion/Accordion.stories.ts +12 -0
  3. package/dist/astro-ai/Ai.stories.ts +12 -0
  4. package/dist/astro-analytics/Analytics.stories.ts +12 -0
  5. package/dist/astro-animated-text/AnimatedText.stories.ts +12 -0
  6. package/dist/astro-app-shell/AppShell.stories.ts +12 -0
  7. package/dist/astro-auth/Auth.stories.ts +12 -0
  8. package/dist/astro-avatar/Avatar.stories.ts +12 -0
  9. package/dist/astro-avatar-group/AvatarGroup.stories.ts +12 -0
  10. package/dist/astro-badge/Badge.stories.ts +16 -0
  11. package/dist/astro-bottom-nav/BottomNav.stories.ts +12 -0
  12. package/dist/astro-breadcrumbs/Breadcrumbs.stories.ts +12 -0
  13. package/dist/astro-button/Button.stories.ts +12 -0
  14. package/dist/astro-calendar/Calendar.stories.ts +12 -0
  15. package/dist/astro-callout/Callout.stories.ts +12 -0
  16. package/dist/astro-card/Card.stories.ts +12 -0
  17. package/dist/astro-card-grid/CardGrid.stories.ts +12 -0
  18. package/dist/astro-carousel/Carousel.stories.ts +12 -0
  19. package/dist/astro-charts/Charts.stories.ts +12 -0
  20. package/dist/astro-checkbox/Checkbox.stories.ts +12 -0
  21. package/dist/astro-code-block/CodeBlock.stories.ts +12 -0
  22. package/dist/astro-code-editor/CodeEditor.stories.ts +12 -0
  23. package/dist/astro-collapsible/Collapsible.stories.ts +12 -0
  24. package/dist/astro-command/Command.stories.ts +12 -0
  25. package/dist/astro-command-input/CommandInput.stories.ts +12 -0
  26. package/dist/astro-content-protection/ContentProtection.stories.ts +12 -0
  27. package/dist/astro-conversation/Chat.astro +208 -0
  28. package/dist/astro-conversation/Conversation.stories.ts +12 -0
  29. package/dist/astro-conversation/index.ts +29 -0
  30. package/dist/astro-cookie-consent/CookieConsent.astro +141 -0
  31. package/dist/astro-cookie-consent/index.ts +12 -0
  32. package/dist/astro-data-table/DataTable.stories.ts +12 -0
  33. package/dist/astro-date-picker/DatePicker.stories.ts +12 -0
  34. package/dist/astro-device-frame/DeviceFrame.stories.ts +12 -0
  35. package/dist/astro-dialog/Dialog.stories.ts +12 -0
  36. package/dist/astro-diff-viewer/DiffViewer.stories.ts +12 -0
  37. package/dist/astro-dropdown-menu/DropdownMenu.stories.ts +12 -0
  38. package/dist/astro-emoji-picker/EmojiPicker.stories.ts +12 -0
  39. package/dist/astro-feedback-dialog/FeedbackDialog.stories.ts +12 -0
  40. package/dist/astro-file-tree/FileTree.stories.ts +12 -0
  41. package/dist/astro-file-upload/FileUpload.stories.ts +12 -0
  42. package/dist/astro-footer/Footer.stories.ts +12 -0
  43. package/dist/astro-icon-system/IconSystem.stories.ts +12 -0
  44. package/dist/astro-inline-editor/InlineEditor.stories.ts +12 -0
  45. package/dist/astro-input/Input.stories.ts +12 -0
  46. package/dist/astro-input-group/InputGroup.stories.ts +12 -0
  47. package/dist/astro-install-prompt/InstallPrompt.stories.ts +12 -0
  48. package/dist/astro-keyboard-shortcut/KeyboardShortcut.stories.ts +12 -0
  49. package/dist/astro-language-selector/LanguageSelector.stories.ts +12 -0
  50. package/dist/astro-link-card/LinkCard.stories.ts +12 -0
  51. package/dist/astro-logger/Logger.stories.ts +12 -0
  52. package/dist/astro-markdown-renderer/MarkdownRenderer.stories.ts +12 -0
  53. package/dist/astro-mobile-nav/MobileNav.stories.ts +12 -0
  54. package/dist/astro-navbar/Navbar.stories.ts +12 -0
  55. package/dist/astro-otp-input/OtpInput.stories.ts +12 -0
  56. package/dist/astro-pagination/Pagination.stories.ts +12 -0
  57. package/dist/astro-payment/Payment.stories.ts +12 -0
  58. package/dist/astro-popover/Popover.stories.ts +12 -0
  59. package/dist/astro-presence-indicator/PresenceIndicator.stories.ts +12 -0
  60. package/dist/astro-progress-display/ProgressDisplay.stories.ts +12 -0
  61. package/dist/astro-radio/Radio.stories.ts +12 -0
  62. package/dist/astro-reaction-bar/ReactionBar.stories.ts +12 -0
  63. package/dist/astro-resizable-layout/ResizableLayout.stories.ts +12 -0
  64. package/dist/astro-rich-editor/RichEditor.stories.ts +12 -0
  65. package/dist/astro-search-bar/SearchBar.stories.ts +12 -0
  66. package/dist/astro-select/Select.stories.ts +12 -0
  67. package/dist/astro-sidebar/Sidebar.stories.ts +12 -0
  68. package/dist/astro-skeleton/Skeleton.stories.ts +12 -0
  69. package/dist/astro-skip-to-content/SkipToContent.stories.ts +12 -0
  70. package/dist/astro-slide-viewer/SlideViewer.stories.ts +12 -0
  71. package/dist/astro-status-indicator/StatusIndicator.stories.ts +12 -0
  72. package/dist/astro-steps/Steps.stories.ts +12 -0
  73. package/dist/astro-switch/Switch.stories.ts +12 -0
  74. package/dist/astro-table-of-contents/TableOfContents.stories.ts +12 -0
  75. package/dist/astro-tabs/Tabs.stories.ts +12 -0
  76. package/dist/astro-textarea/Textarea.stories.ts +12 -0
  77. package/dist/astro-theme/Theme.stories.ts +12 -0
  78. package/dist/astro-thread-view/ThreadView.stories.ts +12 -0
  79. package/dist/astro-toast/Toast.stories.ts +12 -0
  80. package/dist/astro-tooltip/Tooltip.stories.ts +12 -0
  81. package/dist/astro-version-selector/VersionSelector.stories.ts +12 -0
  82. package/dist/astro-video-player/VideoPlayer.stories.ts +12 -0
  83. package/dist/astro-voice-pill/VoicePill.stories.ts +12 -0
  84. package/dist/astro-waveform/Waveform.stories.ts +12 -0
  85. package/dist/conversation/conversation.ts +399 -0
  86. package/dist/conversation/index.ts +30 -0
  87. package/dist/conversation/selectors.ts +58 -0
  88. package/dist/conversation/types.ts +232 -0
  89. package/dist/cookie-consent/cookie-consent.ts +132 -0
  90. package/dist/cookie-consent/index.ts +10 -0
  91. package/dist/cookie-consent/types.ts +72 -0
  92. package/dist/index.ts +2 -0
  93. package/package.json +3 -1
@@ -0,0 +1,58 @@
1
+ import type { ChatMessage, ConversationState, ThreadingMode } from './types.js'
2
+
3
+ /** Sort by timestamp ascending (oldest first). */
4
+ function byTime(a: ChatMessage, b: ChatMessage): number {
5
+ return a.timestamp.getTime() - b.timestamp.getTime()
6
+ }
7
+
8
+ /** Find a message by id. */
9
+ export function findMessage(messages: ChatMessage[], id: string): ChatMessage | undefined {
10
+ return messages.find((m) => m.id === id)
11
+ }
12
+
13
+ /** Resolve a message id to its thread root (itself if it's already a root). */
14
+ export function rootIdOf(messages: ChatMessage[], id: string): string {
15
+ const m = findMessage(messages, id)
16
+ if (!m) return id
17
+ return m.parentId ?? m.id
18
+ }
19
+
20
+ /** Root (main-timeline) messages — those not belonging to a reply-thread. */
21
+ export function selectRoots(messages: ChatMessage[]): ChatMessage[] {
22
+ return messages.filter((m) => !m.parentId).sort(byTime)
23
+ }
24
+
25
+ /** Replies belonging to a thread root, oldest first (excludes the root itself). */
26
+ export function selectReplies(messages: ChatMessage[], rootId: string): ChatMessage[] {
27
+ return messages.filter((m) => m.parentId === rootId).sort(byTime)
28
+ }
29
+
30
+ /** A full thread: root message followed by its replies. */
31
+ export function selectThreadMessages(messages: ChatMessage[], rootId: string): ChatMessage[] {
32
+ const root = findMessage(messages, rootId)
33
+ const replies = selectReplies(messages, rootId)
34
+ return root ? [root, ...replies] : replies
35
+ }
36
+
37
+ /** Number of replies in a thread. */
38
+ export function getReplyCount(messages: ChatMessage[], rootId: string): number {
39
+ return messages.reduce((n, m) => (m.parentId === rootId ? n + 1 : n), 0)
40
+ }
41
+
42
+ /**
43
+ * The main timeline for the active conversation, honoring the threading mode:
44
+ * - `'inline'`: every message in chronological order (replies included).
45
+ * - `'panel'`: only root messages (replies hidden behind their thread).
46
+ */
47
+ export function selectMainTimeline(
48
+ messages: ChatMessage[],
49
+ mode: ThreadingMode,
50
+ ): ChatMessage[] {
51
+ if (mode === 'panel') return selectRoots(messages)
52
+ return [...messages].sort(byTime)
53
+ }
54
+
55
+ /** Convenience: main timeline from a store snapshot. */
56
+ export function selectTimelineFromState(state: ConversationState): ChatMessage[] {
57
+ return selectMainTimeline(state.messages, state.threadingMode)
58
+ }
@@ -0,0 +1,232 @@
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 — a reply to any
72
+ * message in a thread groups under the same originating root.
73
+ */
74
+ parentId?: string
75
+ /**
76
+ * The specific message this is "in reply to" (for the quote), which may be a
77
+ * mid-thread reply even though `parentId` is always the originating root.
78
+ */
79
+ replyToId?: string
80
+ /** Emoji reactions */
81
+ reactions?: MessageReaction[]
82
+ /** Attachments (images/gifs/files) */
83
+ attachments?: MessageAttachment[]
84
+ /** Whether this message was edited */
85
+ edited?: boolean
86
+ /** Arbitrary consumer metadata — never inspected by the store */
87
+ metadata?: Record<string, unknown>
88
+ }
89
+
90
+ /** A conversation/session (an entry in the conversation list). */
91
+ export interface Conversation {
92
+ id: string
93
+ /** Display title (auto-derived from the first message unless set) */
94
+ title: string
95
+ createdAt: Date
96
+ /** Last-activity time (bumped on every message) */
97
+ updatedAt: Date
98
+ metadata?: Record<string, unknown>
99
+ }
100
+
101
+ /** Context handed to the transport for one send. */
102
+ export interface SendContext {
103
+ /** Active conversation ID */
104
+ conversationId: string
105
+ /** The user message being sent */
106
+ message: ChatMessage
107
+ /** Prior messages in scope (the thread when replying, else the main timeline) */
108
+ history: ChatMessage[]
109
+ /** Root message id when this send is a reply within a thread */
110
+ parentId?: string
111
+ /** Aborted when the consumer calls `stop()` */
112
+ signal: AbortSignal
113
+ }
114
+
115
+ /** A streamed piece of an assistant reply. */
116
+ export interface TransportChunk {
117
+ /** Token/delta to append to the assistant reply */
118
+ delta?: string
119
+ /** Replace the whole reply content (for non-streaming transports) */
120
+ content?: string
121
+ /** Arbitrary metadata merged onto the assistant message */
122
+ metadata?: Record<string, unknown>
123
+ }
124
+
125
+ /**
126
+ * The backend wire. Implemented by adapter code the consumer owns — never by
127
+ * this package. A non-streaming backend yields one chunk with `content` then
128
+ * returns; a streaming backend yields many `delta` chunks.
129
+ */
130
+ export interface ChatTransport {
131
+ name: string
132
+ send(ctx: SendContext): AsyncIterable<TransportChunk>
133
+ }
134
+
135
+ /** Snapshot of the store. Returned by `getState()`; treat as immutable. */
136
+ export interface ConversationState {
137
+ /** All conversations, most-recently-updated first */
138
+ conversations: Conversation[]
139
+ /** Active conversation ID, or null when none selected */
140
+ activeConversationId: string | null
141
+ /** Flat messages of the active conversation (roots + replies) */
142
+ messages: ChatMessage[]
143
+ /** Root id of the thread shown in the side panel, or null when closed */
144
+ openThreadRootId: string | null
145
+ /** The specific message a reply in the open thread will target (default: the root) */
146
+ replyTarget: string | null
147
+ /** Current threading mode */
148
+ threadingMode: ThreadingMode
149
+ /** Coarse store status */
150
+ status: 'idle' | 'sending' | 'streaming' | 'error'
151
+ /** Last error, if any */
152
+ error: string | null
153
+ }
154
+
155
+ /** Options for `createConversation`. */
156
+ export interface ConversationConfig {
157
+ /** Backend wire. Without it the store is a local message log. */
158
+ transport?: ChatTransport
159
+ /** Seed conversations */
160
+ conversations?: Conversation[]
161
+ /** Seed messages keyed by conversationId */
162
+ messages?: Record<string, ChatMessage[]>
163
+ /** Initially active conversation */
164
+ activeConversationId?: string
165
+ /** The local user — author of outgoing messages */
166
+ currentUser?: ChatAuthor
167
+ /** Author of assistant replies (default: { id: 'assistant', name: 'Assistant' }) */
168
+ assistant?: ChatAuthor
169
+ /** Derive a conversation title from its first message (default: first ~48 chars) */
170
+ generateTitle?: (firstMessage: string) => string
171
+ /** Threading mode (default 'inline') */
172
+ threadingMode?: ThreadingMode
173
+ }
174
+
175
+ /** Options for a single `sendMessage`. */
176
+ export interface SendOptions {
177
+ /** Send into a specific conversation instead of the active one */
178
+ conversationId?: string
179
+ /** Reply within the thread of this message id */
180
+ replyTo?: string
181
+ /** Attachments on the outgoing message */
182
+ attachments?: MessageAttachment[]
183
+ /** Metadata attached to the outgoing user message */
184
+ metadata?: Record<string, unknown>
185
+ }
186
+
187
+ /** The framework-agnostic store. React/Astro adapters wrap this. */
188
+ export interface ConversationAPI {
189
+ /** Current immutable snapshot */
190
+ getState(): ConversationState
191
+ /** Subscribe to changes; returns an unsubscribe fn (suits useSyncExternalStore) */
192
+ subscribe(listener: () => void): () => void
193
+
194
+ // — conversation management —
195
+ /** Create a new conversation and make it active */
196
+ newConversation(opts?: { title?: string; metadata?: Record<string, unknown> }): Conversation
197
+ /** Make a conversation active */
198
+ selectConversation(conversationId: string): void
199
+ /** Delete a conversation and its messages */
200
+ deleteConversation(conversationId: string): void
201
+ /** Rename a conversation */
202
+ renameConversation(conversationId: string, title: string): void
203
+
204
+ // — messaging —
205
+ /** Optimistically append the user message, then stream the reply via transport */
206
+ sendMessage(content: string, opts?: SendOptions): Promise<void>
207
+ /** Append a message directly (no transport round-trip) */
208
+ appendMessage(message: ChatMessage): void
209
+ /** Edit a message's content (marks it edited) */
210
+ editMessage(messageId: string, content: string): void
211
+ /** Delete a message (and, for a thread root, its replies) */
212
+ deleteMessage(messageId: string): void
213
+ /** Toggle the local user's emoji reaction on a message */
214
+ react(messageId: string, emoji: string): void
215
+ /** Retry the last failed turn in the active conversation */
216
+ retryLast(): Promise<void>
217
+ /** Abort the in-flight stream, keeping any partial reply */
218
+ stop(): void
219
+
220
+ // — threads / view —
221
+ /** Open the side panel focused on a message's thread (reply target = root) */
222
+ openThread(rootId: string): void
223
+ /** Open the originating thread and target a specific message for the next reply */
224
+ replyTo(messageId: string): void
225
+ /** Close the thread side panel */
226
+ closeThread(): void
227
+ /** Switch threading mode */
228
+ setThreadingMode(mode: ThreadingMode): void
229
+
230
+ /** Swap the transport at runtime */
231
+ setTransport(transport: ChatTransport): void
232
+ }
@@ -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,10 @@
1
+ export { createCookieConsent, DEFAULT_CATEGORIES } from './cookie-consent.js'
2
+
3
+ export type {
4
+ CookieCategory,
5
+ ConsentPreferences,
6
+ CookieStorage,
7
+ CookieConsentConfig,
8
+ CookieConsentState,
9
+ CookieConsentAPI,
10
+ } from './types.js'
@@ -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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@refraction-ui/astro",
3
- "version": "0.8.1",
3
+ "version": "0.10.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:*",