@netlify/spark-ui 1.5.0 → 1.8.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 (62) hide show
  1. package/dist/assets/AskNetlify.css +1 -0
  2. package/dist/assets/SearchHeading.css +1 -1
  3. package/dist/assets/SearchLink.css +1 -1
  4. package/dist/assets/SearchModal.css +1 -1
  5. package/dist/components/preact/AskNetlify/AskNetlify.d.ts +7 -0
  6. package/dist/components/preact/AskNetlify/AskNetlify.js +20 -0
  7. package/dist/components/preact/AskNetlify/components/BackButton.d.ts +5 -0
  8. package/dist/components/preact/AskNetlify/components/BackButton.js +20 -0
  9. package/dist/components/preact/AskNetlify/components/ChatInterface.d.ts +7 -0
  10. package/dist/components/preact/AskNetlify/components/ChatInterface.js +53 -0
  11. package/dist/components/preact/AskNetlify/components/ChatMessages.d.ts +3 -0
  12. package/dist/components/preact/AskNetlify/components/ChatMessages.js +19 -0
  13. package/dist/components/preact/AskNetlify/components/QuestionList/QuestionList.d.ts +4 -0
  14. package/dist/components/preact/AskNetlify/components/QuestionList/QuestionList.js +23 -0
  15. package/dist/components/preact/AskNetlify/components/QuestionList/questions.d.ts +1 -0
  16. package/dist/components/preact/AskNetlify/components/QuestionList/questions.js +10 -0
  17. package/dist/components/preact/Icon/index.d.ts +2 -2
  18. package/dist/components/preact/Icon/index.js +108 -72
  19. package/dist/components/preact/SearchModal/components/SearchInput.d.ts +2 -0
  20. package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.d.ts +1 -1
  21. package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.js +34 -16
  22. package/dist/components/preact/SearchModal/components/SearchModal/SearchModal.js +76 -53
  23. package/dist/components/preact/SearchModal/components/SearchWindow.d.ts +1 -1
  24. package/dist/components/preact/SearchModal/components/SearchWindow.js +49 -29
  25. package/dist/components/preact/SearchModal/hooks/useKeyboardNav.js +6 -6
  26. package/dist/components/preact/SearchModal/hooks/useSearchToggles.d.ts +3 -1
  27. package/dist/components/preact/SearchModal/hooks/useSearchToggles.js +13 -10
  28. package/dist/components/preact/SearchModal/types/search.types.d.ts +6 -1
  29. package/dist/components/preact/SpriteIcon/index.d.ts +7 -0
  30. package/dist/components/preact/SpriteIcon/index.js +7 -0
  31. package/dist/components/preact/index.d.ts +1 -0
  32. package/dist/components/preact/index.js +6 -4
  33. package/dist/utils/theme.d.ts +3 -0
  34. package/package.json +4 -1
  35. package/packages/components/Switchback.astro +129 -0
  36. package/packages/components/Ticker.astro +29 -18
  37. package/packages/components/TrustBarTicker.astro +26 -0
  38. package/packages/components/TrustBarTickerItem.astro +54 -0
  39. package/packages/components/preact/AskNetlify/AskNetlify.css +282 -0
  40. package/packages/components/preact/AskNetlify/AskNetlify.tsx +22 -0
  41. package/packages/components/preact/AskNetlify/components/BackButton.tsx +19 -0
  42. package/packages/components/preact/AskNetlify/components/ChatInterface.tsx +79 -0
  43. package/packages/components/preact/AskNetlify/components/ChatMessages.tsx +33 -0
  44. package/packages/components/preact/AskNetlify/components/QuestionList/QuestionList.tsx +27 -0
  45. package/packages/components/preact/AskNetlify/components/QuestionList/questions.ts +10 -0
  46. package/packages/components/preact/Icon/index.tsx +72 -53
  47. package/packages/components/preact/SearchModal/components/SearchHeading/SearchHeading.css +4 -3
  48. package/packages/components/preact/SearchModal/components/SearchInput.tsx +9 -7
  49. package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.css +20 -10
  50. package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.tsx +27 -7
  51. package/packages/components/preact/SearchModal/components/SearchModal/SearchModal.css +68 -29
  52. package/packages/components/preact/SearchModal/components/SearchModal/SearchModal.tsx +59 -30
  53. package/packages/components/preact/SearchModal/components/SearchWindow.tsx +34 -18
  54. package/packages/components/preact/SearchModal/hooks/useKeyboardNav.ts +2 -2
  55. package/packages/components/preact/SearchModal/hooks/useSearchToggles.ts +11 -3
  56. package/packages/components/preact/SearchModal/types/search.types.tsx +5 -2
  57. package/packages/components/preact/SpriteIcon/index.tsx +13 -0
  58. package/packages/components/preact/index.ts +1 -0
  59. package/packages/styles/fonts.css +25 -1
  60. package/packages/styles/layout.css +6 -6
  61. package/packages/styles/tokens.css +72 -24
  62. package/packages/utils/theme.ts +16 -0
@@ -0,0 +1,282 @@
1
+ /* ----------------------------- */
2
+ /* Layout Containers */
3
+ /* ----------------------------- */
4
+ .n-ask-chat-container {
5
+ opacity: 0;
6
+ animation: appearContainer 120ms ease-in-out forwards;
7
+ animation-delay: 140ms;
8
+ }
9
+
10
+ .n-ask-conversation {
11
+ display: grid;
12
+ align-items: flex-end;
13
+ height: calc(var(--n-search-container-height) + var(--n-search-footer-height));
14
+ padding: var(--n-search-padding-inline);
15
+ overflow-y: auto;
16
+ scroll-behavior: smooth;
17
+ gap: var(--space-s);
18
+ }
19
+
20
+ .n-ask-form {
21
+ display: flex;
22
+ align-items: center;
23
+ position: relative;
24
+ height: var(--n-search-input-height);
25
+ background-color: var(--search-input-bg-color);
26
+ }
27
+
28
+ .n-ask-form::after {
29
+ content: '';
30
+ display: block;
31
+ position: absolute;
32
+ top: 0;
33
+ left: 0;
34
+ width: 100%;
35
+ height: 1px;
36
+ background: var(--color-bg-3);
37
+ }
38
+
39
+ /* ----------------------------- */
40
+ /* Conversation and Message UI */
41
+ /* ----------------------------- */
42
+ .n-ask-question {
43
+ border: none;
44
+ border-radius: var(--radius-pill);
45
+ transform: translateX(-0.75rem);
46
+ color: var(--color-text-1);
47
+ font-size: var(--step--1);
48
+ font-weight: var(--font-weight-regular);
49
+ text-align: left;
50
+ text-wrap: pretty;
51
+ display: flex;
52
+ align-items: center;
53
+ gap: var(--space-2xs);
54
+ transition: background-color 200ms ease-out;
55
+ }
56
+
57
+ .n-ask-question [data-icon='caretRight'] {
58
+ opacity: 0;
59
+ color: var(--color-text-weaker);
60
+ transform: translateX(-3px);
61
+ transition: transform 200ms ease-out;
62
+ }
63
+
64
+ .n-ask-question:hover {
65
+ background-color: var(--n-search-color-bg-2);
66
+
67
+ [data-icon='caretRight'] {
68
+ opacity: 1;
69
+ transform: translateX(0px);
70
+ }
71
+ }
72
+
73
+ .n-ask-question-list .n-ask-question-list-item {
74
+ position: relative;
75
+ width: fit-content;
76
+ opacity: 0;
77
+ transform: translateY(10px);
78
+ animation: fadeInUp 0.5s forwards;
79
+ }
80
+
81
+ .n-ask-message {
82
+ display: grid;
83
+ grid-template-columns: minmax(0, 1fr);
84
+ gap: var(--space-s);
85
+ margin-block-start: var(--space-2xl);
86
+ }
87
+
88
+ .n-ask-message-bubble {
89
+ width: fit-content;
90
+ max-width: 85%;
91
+ padding: var(--space-xs) var(--space-s);
92
+ background: var(--search-answer-message-color);
93
+ border-radius: var(--radius-l);
94
+ font-size: var(--step-0);
95
+ }
96
+
97
+ .n-ask-user-message {
98
+ margin-inline-start: auto;
99
+ background: var(--search-user-message-color);
100
+ }
101
+
102
+ /* ----------------------------- */
103
+ /* Form UI */
104
+ /* ----------------------------- */
105
+ .n-ask-input {
106
+ border: none;
107
+ outline: none;
108
+ padding-inline: var(--n-search-padding-inline);
109
+ }
110
+
111
+ .n-ask-form-submit {
112
+ padding: 0.7rem;
113
+ margin-inline-end: var(--space-2xs);
114
+ border: none;
115
+ border-radius: var(--radius-pill);
116
+ background: var(--search-submit-btn-bg-color);
117
+ color: var(--neutral-000);
118
+ transition: background-color 150ms ease-out;
119
+ }
120
+
121
+ .n-ask-form-submit > svg {
122
+ width: 1rem;
123
+ height: auto;
124
+ aspect-ratio: 1 / 1;
125
+ border-radius: 1.5px;
126
+ transition: opacity 150ms ease-out;
127
+ }
128
+
129
+ .n-ask-form-submit[disabled] {
130
+ background: var(--neutral-500);
131
+ }
132
+
133
+ .n-ask-form-submit[disabled] > * {
134
+ opacity: 0.5;
135
+ }
136
+
137
+ /* ----------------------------- */
138
+ /* Buttons */
139
+ /* ----------------------------- */
140
+ .n-ask-button-back {
141
+ position: absolute;
142
+ z-index: 1;
143
+ width: fit-content;
144
+ margin: 1rem;
145
+ padding: 0.5rem 1rem;
146
+ background: var(--search-back-btn-bg-color);
147
+ border: 0.5px solid var(--color-bg-3);
148
+ border-radius: var(--radius-pill);
149
+ font-size: var(--step--1);
150
+ color: var(--color-text-1);
151
+ cursor: pointer;
152
+ }
153
+
154
+ .n-ask-button-back [data-icon='caretRight'] {
155
+ color: var(--color-text-weaker);
156
+ transform: rotate(180deg);
157
+ }
158
+
159
+ /* ----------------------------- */
160
+ /* Typography & Prose */
161
+ /* ----------------------------- */
162
+ .prose {
163
+ font-size: var(--step--0);
164
+ }
165
+
166
+ .prose > * + * {
167
+ margin-top: var(--flow-space, var(--space-s));
168
+ }
169
+
170
+ .prose :where(ol, ul, li) > * + * {
171
+ margin-top: var(--flow-space, var(--space-3xs));
172
+ }
173
+
174
+ .prose h1,
175
+ .prose h2,
176
+ .prose h3 {
177
+ font-size: var(--step-1);
178
+ font-weight: var(--font-weight-bold);
179
+ }
180
+
181
+ .prose h4,
182
+ .prose h5,
183
+ .prose h6 {
184
+ font-size: var(--step-0);
185
+ font-weight: var(--font-weight-bold);
186
+ }
187
+
188
+ .prose pre {
189
+ max-width: 100%;
190
+ padding: var(--space-s);
191
+ background: var(--neutral-light-700);
192
+ border-radius: var(--radius-m);
193
+ box-sizing: border-box;
194
+ color: var(--neutral-100);
195
+ overflow-x: auto;
196
+ }
197
+
198
+ .prose code {
199
+ overflow-x: auto;
200
+ }
201
+
202
+ .prose code[class*='language-'],
203
+ .prose pre[class*='language-'] {
204
+ display: block;
205
+ font-size: var(--font-size-text-xs);
206
+ }
207
+
208
+ /* ----------------------------- */
209
+ /* Loading Animation */
210
+ /* ----------------------------- */
211
+ .n-ask-message-loader {
212
+ width: 2.5rem;
213
+ height: auto;
214
+ display: block;
215
+ color: var(--search-loading-color);
216
+ }
217
+
218
+ .n-ask-message-loader :where(#ask-netlify-tick-1, #ask-netlify-tick-2, #ask-netlify-tick-3) {
219
+ opacity: 0;
220
+ animation: fadeIn 800ms ease-in-out infinite;
221
+ }
222
+
223
+ .n-ask-message-loader #ask-netlify-tick-1 {
224
+ animation-delay: 100ms;
225
+ }
226
+
227
+ .n-ask-message-loader #ask-netlify-tick-2 {
228
+ animation-delay: 200ms;
229
+ }
230
+
231
+ .n-ask-message-loader #ask-netlify-tick-3 {
232
+ animation-delay: 300ms;
233
+ }
234
+
235
+ .n-ask-message-loader :where(#ask-netlify-eyelid-left, #ask-netlify-eyelid-right) {
236
+ transform-origin: top center;
237
+ transform: scaleY(1.035);
238
+ animation: askEyeBlink 4s infinite;
239
+ animation-delay: 1s;
240
+ }
241
+
242
+ /* ----------------------------- */
243
+ /* Animations and Keyframes */
244
+ /* ----------------------------- */
245
+ @keyframes fadeIn {
246
+ 0% {
247
+ opacity: 0;
248
+ }
249
+ 100% {
250
+ opacity: 1;
251
+ }
252
+ }
253
+
254
+ @keyframes fadeInUp {
255
+ to {
256
+ opacity: 1;
257
+ transform: translateY(0);
258
+ }
259
+ }
260
+
261
+ @keyframes askEyeBlink {
262
+ 0%,
263
+ 5% {
264
+ transform: scaleY(1.035);
265
+ }
266
+ 6%,
267
+ 10% {
268
+ transform: scaleY(1.3);
269
+ }
270
+ 11%,
271
+ 15% {
272
+ transform: scaleY(1.035);
273
+ }
274
+ 16%,
275
+ 20% {
276
+ transform: scaleY(1.3);
277
+ }
278
+ 21%,
279
+ 100% {
280
+ transform: scaleY(1.035);
281
+ }
282
+ }
@@ -0,0 +1,22 @@
1
+ import { KapaProvider } from '@kapaai/react-sdk';
2
+ import ChatInterface from './components/ChatInterface';
3
+ import './AskNetlify.css';
4
+
5
+ interface AskNetlifyProps {
6
+ setModalState?: (state?: { current: string }) => void;
7
+ }
8
+
9
+ export default function AskNetlify(props: AskNetlifyProps) {
10
+ return (
11
+ <KapaProvider
12
+ integrationId="3c0789a6-c6a8-4fbc-b0cc-235f9e4a6351"
13
+ callbacks={{
14
+ askAI: {
15
+ onQuerySubmit: data => console.log('Question asked:', data.question),
16
+ },
17
+ }}
18
+ >
19
+ <ChatInterface {...props} />
20
+ </KapaProvider>
21
+ );
22
+ }
@@ -0,0 +1,19 @@
1
+ import Icon from '../../Icon';
2
+
3
+ interface BackButtonProps {
4
+ onClick: () => void;
5
+ }
6
+
7
+ export function BackButton({ onClick }: BackButtonProps) {
8
+ return (
9
+ <button
10
+ class="n-ask-button-back button"
11
+ data-button-variant="outline"
12
+ onClick={onClick}
13
+ aria-label="Return to search"
14
+ >
15
+ <Icon name="caretRight" />
16
+ Search
17
+ </button>
18
+ );
19
+ }
@@ -0,0 +1,79 @@
1
+ import { useState, useRef } from 'preact/hooks';
2
+ import { useChat } from '@kapaai/react-sdk';
3
+ import { questions } from './QuestionList/questions';
4
+ import { QuestionList } from './QuestionList/QuestionList';
5
+ import { BackButton } from './BackButton';
6
+ import { ChatMessages } from './ChatMessages';
7
+
8
+ interface Props {
9
+ setModalState?: (state?: { current: string }) => void;
10
+ }
11
+
12
+ export default function ChatInterface({ setModalState }: Props) {
13
+ const [message, setMessage] = useState('');
14
+ const chatRef = useRef<HTMLDivElement>(null);
15
+ const menuRef = useRef<HTMLUListElement>(null);
16
+
17
+ const { conversation, submitQuery, isGeneratingAnswer, isPreparingAnswer, stopGeneration } =
18
+ useChat();
19
+
20
+ const handleSubmit = (e: Event | preact.JSX.TargetedMouseEvent<HTMLButtonElement>) => {
21
+ e.preventDefault();
22
+ const target = e.target as HTMLButtonElement;
23
+ const id = target?.getAttribute('data-question-id');
24
+ const text = id ? questions[id] : message;
25
+
26
+ if (text.trim()) {
27
+ submitQuery(text.trim());
28
+ }
29
+ if (!id) setMessage('');
30
+ };
31
+
32
+ return (
33
+ <div className="n-ask-chat-container">
34
+ <BackButton onClick={() => setModalState?.({ current: 'search' })} />
35
+ <div ref={chatRef} className="n-ask-conversation n-search-window">
36
+ {!conversation.length ? (
37
+ <QuestionList onClick={handleSubmit} menuRef={menuRef} />
38
+ ) : (
39
+ <ChatMessages conversation={conversation} />
40
+ )}
41
+ </div>
42
+
43
+ <form onSubmit={handleSubmit} className="n-ask-form n-search-input-wrapper">
44
+ <input
45
+ className="n-ask-input n-search-input"
46
+ type="text"
47
+ value={message}
48
+ onInput={e => setMessage(e.currentTarget.value)}
49
+ placeholder="Ask a question..."
50
+ disabled={isGeneratingAnswer}
51
+ />
52
+
53
+ {isGeneratingAnswer ? (
54
+ <button
55
+ type="button"
56
+ className="n-ask-form-submit"
57
+ onClick={stopGeneration}
58
+ aria-label="Stop generation"
59
+ >
60
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
61
+ <rect x="0" y="0" width="16" height="16" />
62
+ </svg>
63
+ </button>
64
+ ) : (
65
+ <button
66
+ type="submit"
67
+ className="n-ask-form-submit"
68
+ disabled={!message.trim() || isPreparingAnswer}
69
+ aria-label="Send message"
70
+ >
71
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" fill="currentColor">
72
+ <path d="M438.6 278.6c12.5-12.5 12.5-32.8 0-45.3l-160-160c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L338.8 224 32 224c-17.7 0-32 14.3-32 32s14.3 32 32 32l306.7 0L233.4 393.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l160-160z" />
73
+ </svg>
74
+ </button>
75
+ )}
76
+ </form>
77
+ </div>
78
+ );
79
+ }
@@ -0,0 +1,33 @@
1
+ import { marked } from 'marked';
2
+ import Icon from '../../Icon';
3
+
4
+ export function ChatMessages({ conversation }: { conversation: any[] }) {
5
+ return (
6
+ <>
7
+ {conversation.map((qa, index) => (
8
+ <div key={qa.id || `qa-${index}`} class="n-ask-message">
9
+ <div class="n-ask-message-right">
10
+ <div class="n-ask-message-bubble n-ask-user-message">
11
+ <p>{qa.question}</p>
12
+ </div>
13
+ </div>
14
+
15
+ <div class="n-ask-message-left">
16
+ {qa.answer ? (
17
+ <div class="n-ask-message-bubble n-ask-bot-message">
18
+ <div
19
+ class="n-ask-message-answer prose flow"
20
+ dangerouslySetInnerHTML={{ __html: marked.parse(qa.answer) }}
21
+ />
22
+ </div>
23
+ ) : (
24
+ <div class="n-ask-message-loader">
25
+ <Icon name="askNetlify" />
26
+ </div>
27
+ )}
28
+ </div>
29
+ </div>
30
+ ))}
31
+ </>
32
+ );
33
+ }
@@ -0,0 +1,27 @@
1
+ import { questions } from './questions';
2
+ import Icon from '../../../Icon';
3
+
4
+ export function QuestionList({
5
+ onClick,
6
+ menuRef,
7
+ }: {
8
+ onClick: (e: Event) => void;
9
+ menuRef: preact.RefObject<HTMLUListElement>;
10
+ }) {
11
+ return (
12
+ <ul ref={menuRef} class="n-ask-question-list" role="list">
13
+ {Object.entries(questions).map(([id, question], index) => (
14
+ <li
15
+ key={id}
16
+ class="n-ask-question-list-item"
17
+ style={{ animationDelay: `${0.1 * (index + 1)}s` }}
18
+ >
19
+ <button class="n-ask-question" onClick={onClick} data-question-id={id}>
20
+ {question}
21
+ <Icon name="caretRight" />
22
+ </button>
23
+ </li>
24
+ ))}
25
+ </ul>
26
+ );
27
+ }
@@ -0,0 +1,10 @@
1
+ export const questions: Record<string, string> = {
2
+ 'a69747ed-505b-4e98-8535-fd357c327516': 'How do I deploy an app to Netlify using AI?',
3
+ 'e477f4dd-7159-4c31-8a18-4dfc7e84d927': 'How does Netlify simplify my CI/CD pipeline?',
4
+ '39229ddb-9000-4e83-bbf8-54f1c1e55ed4':
5
+ 'How does Netlify help improve my site’s Core Web Vitals?',
6
+ '5c41b3c2-4c90-4cf7-9f24-f0d0b8f0f759':
7
+ 'What are the benefits of using Netlify with a headless CMS?',
8
+ '0270de6c-387e-401f-b24c-6ae1166db477':
9
+ 'What are the benefits of hosting on Netlify’s edge network?',
10
+ };