@netlify/spark-ui 1.5.0-alpha.12 → 1.5.0-alpha.14

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.
@@ -81,9 +81,9 @@ const l = () => /* @__PURE__ */ C(e, { children: [
81
81
  {
82
82
  id: "ask-netlify-eyelid-left",
83
83
  x: "3",
84
- y: "9.5",
84
+ y: "7.3",
85
85
  width: "3",
86
- height: "2.5",
86
+ height: "2",
87
87
  fill: "var(--search-bg-color, currentColor)"
88
88
  }
89
89
  ),
@@ -92,9 +92,9 @@ const l = () => /* @__PURE__ */ C(e, { children: [
92
92
  {
93
93
  id: "ask-netlify-eyelid-right",
94
94
  x: "9.7",
95
- y: "9.5",
95
+ y: "7.3",
96
96
  width: "3",
97
- height: "2.5",
97
+ height: "2",
98
98
  fill: "var(--search-bg-color, currentColor)"
99
99
  }
100
100
  )
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@netlify/spark-ui",
3
3
  "description": "Assets, design tokens, components, and utilities",
4
- "version": "1.5.0-alpha.12",
4
+ "version": "1.5.0-alpha.14",
5
5
  "type": "module",
6
6
  "main": "dist/components/preact/index.js",
7
7
  "types": "dist/components/preact/index.d.ts",
@@ -1,40 +1,78 @@
1
+ /* ----------------------------- */
2
+ /* Layout Containers */
3
+ /* ----------------------------- */
1
4
  .chat-container {
2
5
  opacity: 0;
3
6
  animation: appearContainer 120ms ease-in-out forwards;
4
7
  animation-delay: 140ms;
5
8
  }
6
9
 
7
- @keyframes appearContainer {
8
- 0% {
9
- opacity: 0;
10
- }
11
- 100% {
12
- opacity: 1;
13
- }
14
- }
15
-
16
10
  .conversation {
11
+ display: grid;
12
+ align-items: flex-end;
17
13
  height: calc(var(--n-search-container-height) + var(--n-search-footer-height));
18
14
  padding: var(--n-search-padding-inline);
19
- scroll-behavior: smooth;
20
15
  overflow-y: auto;
21
- display: grid;
22
- align-items: flex-end;
16
+ scroll-behavior: smooth;
23
17
  gap: var(--space-s);
24
18
  }
25
19
 
20
+ .form-wrapper {
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
+ .form-wrapper::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
+ /* ----------------------------- */
26
42
  .question {
27
43
  border: none;
28
- color: var(--color-text-1);
29
- font-weight: var(--font-weight-regular);
30
- font-size: var(--step--1);
31
44
  border-radius: var(--radius-pill);
32
45
  transform: translateX(-0.75rem);
33
- text-wrap: pretty;
46
+ color: var(--color-text-1);
47
+ font-size: var(--step--1);
48
+ font-weight: var(--font-weight-regular);
34
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
+ .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
+ .question:hover {
65
+ background-color: var(--search-back-btn-bg-color);
66
+
67
+ [data-icon='caretRight'] {
68
+ opacity: 1;
69
+ transform: translateX(0px);
70
+ }
35
71
  }
36
72
 
37
73
  .question-list li {
74
+ position: relative;
75
+ width: fit-content;
38
76
  opacity: 0;
39
77
  transform: translateY(10px);
40
78
  animation: fadeInUp 0.5s forwards;
@@ -43,22 +81,13 @@
43
81
  .question-list li:nth-child(1) {
44
82
  animation-delay: 0.5s;
45
83
  }
46
-
47
84
  .question-list li:nth-child(2) {
48
85
  animation-delay: 0.3s;
49
86
  }
50
-
51
87
  .question-list li:nth-child(3) {
52
88
  animation-delay: 0.1s;
53
89
  }
54
90
 
55
- @keyframes fadeInUp {
56
- to {
57
- opacity: 1;
58
- transform: translateY(0);
59
- }
60
- }
61
-
62
91
  .message {
63
92
  display: grid;
64
93
  grid-template-columns: minmax(0, 1fr);
@@ -67,81 +96,22 @@
67
96
  }
68
97
 
69
98
  .message-bubble {
99
+ width: fit-content;
100
+ max-width: 85%;
70
101
  padding: var(--space-xs) var(--space-s);
71
102
  background: var(--search-answer-message-color);
72
103
  border-radius: var(--radius-l);
73
- width: fit-content;
74
- max-width: 85%;
75
104
  font-size: var(--step-0);
76
105
  }
77
106
 
78
- .prose {
79
- font-size: var(--step--0);
80
-
81
- > * + * {
82
- margin-top: var(--flow-space, var(--space-s));
83
- }
84
-
85
- :where(ol, ul, li) > * + * {
86
- margin-top: var(--flow-space, var(--space-3xs));
87
- }
88
-
89
- h1,
90
- h2,
91
- h3 {
92
- font-size: var(--step-1);
93
- }
94
-
95
- h4,
96
- h5,
97
- h6 {
98
- font-size: var(--step-0);
99
- }
100
-
101
- pre {
102
- background: var(--neutral-light-700);
103
- border-radius: var(--radius-m);
104
- max-width: 100%;
105
- box-sizing: border-box;
106
- padding: var(--space-s);
107
- color: var(--neutral-100);
108
- overflow-x: auto;
109
- }
110
-
111
- code {
112
- overflow-x: auto;
113
- }
114
-
115
- code[class*='language-'],
116
- pre[class*='language-'] {
117
- display: block;
118
- font-size: var(--font-size-text-xs);
119
- }
120
- }
121
-
122
107
  .user-message {
123
108
  margin-inline-start: auto;
124
109
  background: var(--search-user-message-color);
125
110
  }
126
111
 
127
- .form-wrapper {
128
- display: flex;
129
- align-items: center;
130
- position: relative;
131
- height: var(--n-search-input-height);
132
- }
133
-
134
- .form-wrapper::after {
135
- content: '';
136
- display: block;
137
- width: 100%;
138
- height: 1px;
139
- background: var(--color-bg-3);
140
- position: absolute;
141
- top: 0;
142
- left: 0;
143
- }
144
-
112
+ /* ----------------------------- */
113
+ /* Form UI */
114
+ /* ----------------------------- */
145
115
  .form-input {
146
116
  border: none;
147
117
  outline: none;
@@ -149,12 +119,11 @@
149
119
  }
150
120
 
151
121
  .form-submit {
152
- border: none;
153
122
  padding: 0.7rem;
154
- border-radius: var(--radius-pill);
155
123
  margin-inline-end: var(--space-2xs);
156
124
  border: none;
157
- background: var(--color-link-1);
125
+ border-radius: var(--radius-pill);
126
+ background: var(--search-submit-btn-bg-color);
158
127
  color: var(--neutral-000);
159
128
  transition: background-color 150ms ease-out;
160
129
  }
@@ -169,53 +138,116 @@
169
138
 
170
139
  .form-submit[disabled] {
171
140
  background: var(--neutral-500);
141
+ }
172
142
 
173
- & > * {
174
- opacity: 0.5;
175
- }
143
+ .form-submit[disabled] > * {
144
+ opacity: 0.5;
176
145
  }
177
146
 
147
+ /* ----------------------------- */
148
+ /* Buttons */
149
+ /* ----------------------------- */
178
150
  .button-back {
179
- width: fit-content;
180
- background: transparent;
181
151
  position: absolute;
182
152
  z-index: 1;
153
+ width: fit-content;
154
+ margin: 1rem;
183
155
  padding: 0.5rem 1rem;
184
156
  background: var(--search-back-btn-bg-color);
185
- margin: 1rem;
186
- border: 2px solid var(--color-bg-3);
157
+ border: 0.5px solid var(--color-bg-3);
187
158
  border-radius: var(--radius-pill);
188
159
  font-size: var(--step--1);
189
- border: 1px solid;
190
- cursor: pointer;
191
- border-color: var(--color-bg-3);
192
160
  color: var(--color-text-1);
193
- border-width: 0.5px;
161
+ cursor: pointer;
194
162
  }
195
163
 
164
+ /* ----------------------------- */
165
+ /* Typography & Prose */
166
+ /* ----------------------------- */
167
+ .prose {
168
+ font-size: var(--step--0);
169
+ }
170
+
171
+ .prose > * + * {
172
+ margin-top: var(--flow-space, var(--space-s));
173
+ }
174
+
175
+ .prose :where(ol, ul, li) > * + * {
176
+ margin-top: var(--flow-space, var(--space-3xs));
177
+ }
178
+
179
+ .prose h1,
180
+ .prose h2,
181
+ .prose h3 {
182
+ font-size: var(--step-1);
183
+ font-weight: var(--font-weight-bold);
184
+ }
185
+
186
+ .prose h4,
187
+ .prose h5,
188
+ .prose h6 {
189
+ font-size: var(--step-0);
190
+ font-weight: var(--font-weight-bold);
191
+ }
192
+
193
+ .prose pre {
194
+ max-width: 100%;
195
+ padding: var(--space-s);
196
+ background: var(--neutral-light-700);
197
+ border-radius: var(--radius-m);
198
+ box-sizing: border-box;
199
+ color: var(--neutral-100);
200
+ overflow-x: auto;
201
+ }
202
+
203
+ .prose code {
204
+ overflow-x: auto;
205
+ }
206
+
207
+ .prose code[class*='language-'],
208
+ .prose pre[class*='language-'] {
209
+ display: block;
210
+ font-size: var(--font-size-text-xs);
211
+ }
212
+
213
+ /* ----------------------------- */
214
+ /* Loaders and SVG Effects */
215
+ /* ----------------------------- */
196
216
  .message-loader {
197
- width: 43px;
217
+ width: 2.5rem;
198
218
  height: auto;
199
219
  display: block;
200
220
  color: var(--search-loading-color);
221
+ }
201
222
 
202
- & :where(#ask-netlify-tick-1, #ask-netlify-tick-2, #ask-netlify-tick-3) {
203
- opacity: 0;
204
- animation: tickFlicker 800ms ease-in-out infinite;
205
- }
223
+ .message-loader :where(#ask-netlify-tick-1, #ask-netlify-tick-2, #ask-netlify-tick-3) {
224
+ opacity: 0;
225
+ animation: fadeIn 800ms ease-in-out infinite;
206
226
  }
207
227
 
208
- #ask-netlify-tick-1 {
228
+ .message-loader #ask-netlify-tick-1 {
209
229
  animation-delay: 100ms;
210
230
  }
211
- #ask-netlify-tick-2 {
231
+
232
+ .message-loader #ask-netlify-tick-2 {
212
233
  animation-delay: 200ms;
213
234
  }
214
- #ask-netlify-tick-3 {
235
+
236
+ .message-loader #ask-netlify-tick-3 {
215
237
  animation-delay: 300ms;
216
238
  }
217
239
 
218
- @keyframes tickFlicker {
240
+ .message-loader :where(#ask-netlify-eyelid-left, #ask-netlify-eyelid-right) {
241
+ transform-origin: top center;
242
+ transform: scaleY(1.035);
243
+ animation: askEyeBlink 4s infinite;
244
+ animation-delay: 1s;
245
+ }
246
+
247
+ /* ----------------------------- */
248
+ /* Animations and Keyframes */
249
+ /* ----------------------------- */
250
+ @keyframes fadeIn {
219
251
  0% {
220
252
  opacity: 0;
221
253
  }
@@ -224,33 +256,32 @@
224
256
  }
225
257
  }
226
258
 
227
- #ask-netlify-eyelid-left,
228
- #ask-netlify-eyelid-right {
229
- transform-origin: top center;
230
- animation: blink 4s infinite;
231
- animation-delay: 1s;
232
- transform: scaleY(0.8);
259
+ @keyframes fadeInUp {
260
+ to {
261
+ opacity: 1;
262
+ transform: translateY(0);
263
+ }
233
264
  }
234
265
 
235
- @keyframes blink {
266
+ @keyframes askEyeBlink {
236
267
  0%,
237
268
  5% {
238
- transform: scaleY(0.8);
269
+ transform: scaleY(1.035);
239
270
  }
240
271
  6%,
241
272
  10% {
242
- transform: scaleY(1);
273
+ transform: scaleY(1.3);
243
274
  }
244
275
  11%,
245
276
  15% {
246
- transform: scaleY(0.8);
277
+ transform: scaleY(1.035);
247
278
  }
248
279
  16%,
249
280
  20% {
250
- transform: scaleY(1);
281
+ transform: scaleY(1.3);
251
282
  }
252
283
  21%,
253
284
  100% {
254
- transform: scaleY(0.8);
285
+ transform: scaleY(1.035);
255
286
  }
256
287
  }
@@ -29,7 +29,9 @@ function ChatInterface({ setModalState }: AskNetlifyProps) {
29
29
  const { conversation, submitQuery, isGeneratingAnswer, isPreparingAnswer, stopGeneration } =
30
30
  useChat();
31
31
 
32
- const chatRef = useRef(null);
32
+ const chatRef = useRef<HTMLDivElement>(null);
33
+ const indicatorRef = useRef<HTMLSpanElement>(null);
34
+ const menuRef = useRef<HTMLUListElement>(null);
33
35
 
34
36
  const questions: Record<string, string> = {
35
37
  'a69747ed-505b-4e98-8535-fd357c327516': 'How do I deploy an app to Netlify using AI?',
@@ -58,11 +60,6 @@ function ChatInterface({ setModalState }: AskNetlifyProps) {
58
60
  }
59
61
  };
60
62
 
61
- useEffect(() => {
62
- const el = chatRef.current as HTMLElement | null;
63
- if (el) el.scrollTop = el.scrollHeight;
64
- }, [conversation]);
65
-
66
63
  return (
67
64
  <div className="chat-container">
68
65
  <button
@@ -81,14 +78,27 @@ function ChatInterface({ setModalState }: AskNetlifyProps) {
81
78
  </button>
82
79
  <div ref={chatRef} className="conversation n-search-window">
83
80
  {!conversation.length && (
84
- <ul class="question-list" role="list">
81
+ <ul ref={menuRef} class="question-list" role="list">
85
82
  {Object.entries(questions).map(([id, question]) => (
86
83
  <li>
87
84
  <button class="question" onClick={handleSubmit} data-id={id}>
88
85
  {question}
86
+ <Icon name="caretRight" />
89
87
  </button>
90
88
  </li>
91
89
  ))}
90
+
91
+ <span
92
+ ref={indicatorRef}
93
+ style={{
94
+ position: 'absolute',
95
+ background: 'rgba(0, 0, 0, 0.1)',
96
+ borderRadius: '4px',
97
+ transition: 'all 200ms ease',
98
+ pointerEvents: 'none',
99
+ zIndex: -1,
100
+ }}
101
+ />
92
102
  </ul>
93
103
  )}
94
104
 
@@ -64,17 +64,17 @@ const AskNetlify: FunctionComponent = () => {
64
64
  <rect
65
65
  id="ask-netlify-eyelid-left"
66
66
  x="3"
67
- y="9.5"
67
+ y="7.3"
68
68
  width="3"
69
- height="2.5"
69
+ height="2"
70
70
  fill="var(--search-bg-color, currentColor)"
71
71
  />
72
72
  <rect
73
73
  id="ask-netlify-eyelid-right"
74
74
  x="9.7"
75
- y="9.5"
75
+ y="7.3"
76
76
  width="3"
77
- height="2.5"
77
+ height="2"
78
78
  fill="var(--search-bg-color, currentColor)"
79
79
  />
80
80
  </>
@@ -1,7 +1,7 @@
1
1
  .n-search-heading {
2
2
  --n-search-heading-weight: var(--font-weight-semibold, 400);
3
3
  --n-search-heading-size: var(--font-size-text-xs, 13px);
4
- --n-search-heading-color: var(--color-text-weaker, #757575);
4
+ --n-search-heading-color: var(--color-text-weak, #757575);
5
5
 
6
6
  font-size: var(--n-search-heading-size);
7
7
  font-weight: var(--n-search-heading-weight);
@@ -31,6 +31,11 @@
31
31
  & .n-search-link-icon [data-icon='caretRight'] {
32
32
  opacity: 1;
33
33
  }
34
+
35
+ & .n-search-link-icon :where(#ask-netlify-eyelid-left, #ask-netlify-eyelid-right) {
36
+ animation: askEyeBlink 4s infinite;
37
+ animation-delay: 500ms;
38
+ }
34
39
  }
35
40
 
36
41
  .n-search-link-title {
@@ -59,7 +64,7 @@
59
64
  display: flex;
60
65
  width: var(--n-search-icon-size);
61
66
  height: var(--n-search-icon-size);
62
- color: var(--n-search-color-text-secondary);
67
+ color: var(--n-search-color-icon);
63
68
  }
64
69
 
65
70
  .n-search-link-icon [data-icon='caretRight'] {
@@ -6,7 +6,8 @@
6
6
  .n-search-modal {
7
7
  --n-search-padding-inline: var(--space-s);
8
8
  --n-search-color-text: var(--color-text-1, currentColor);
9
- --n-search-color-text-secondary: var(--color-text-weaker, currentColor);
9
+ --n-search-color-text-secondary: var(--color-text-weak, currentColor);
10
+ --n-search-color-icon: var(--color-text-weak);
10
11
  --n-search-color-bg: var(--search-bg-color);
11
12
  --n-search-color-bg-2: var(--color-bg-3);
12
13
  --n-search-color-border: var(--color-bg-3);
@@ -15,7 +16,7 @@
15
16
  --n-search-text-highlight: var(--search-highlight-color);
16
17
  --n-search-input-height: 50px;
17
18
  --n-search-footer-height: 40px;
18
- --n-search-container-height: 400px;
19
+ --n-search-container-height: min(50dvh, 26.5rem);
19
20
  }
20
21
 
21
22
  .n-search-modal {
@@ -60,7 +61,7 @@
60
61
  position: relative;
61
62
  display: inline-flex;
62
63
  box-sizing: border-box;
63
- background: var(--n-search-color-bg);
64
+ background: transparent;
64
65
  inline-size: 100%;
65
66
  padding-block: 0.75rem;
66
67
  padding-inline: var(--n-search-padding-inline);
@@ -160,8 +160,10 @@
160
160
  --search-back-btn-bg-color: var(--neutral-100);
161
161
  --search-bg-color: var(--neutral-000);
162
162
  --search-highlight-color: var(--teal-000);
163
- --search-loading-color: var(--blue-700);
164
- --search-user-message-color: var(--blue-100);
163
+ --search-input-bg-color: var(--neutral-100);
164
+ --search-loading-color: var(--teal-700);
165
+ --search-submit-btn-bg-color: var(--teal-200);
166
+ --search-user-message-color: var(--teal-000);
165
167
  --shadow-deep: 0 16px 24px 0 color(srgb 0 0 0 / 0.07), 0 6px 30px 0 color(srgb 0 0 0 / 0.06), 0 8px 10px 0 color(srgb 0 0 0 / 0.1);
166
168
  --shadow-heavy: -10px 10px 80px 0 color(srgb 0 0 0 / 0.1);
167
169
  --shadow-hover: 0 4px 12px 0 color(srgb 0 0 0 / 0.25);
@@ -256,7 +258,9 @@
256
258
  --search-back-btn-bg-color: var(--neutral-500);
257
259
  --search-bg-color: var(--neutral-700);
258
260
  --search-highlight-color: var(--teal-800);
261
+ --search-input-bg-color: var(--neutral-600);
259
262
  --search-loading-color: var(--teal-200);
263
+ --search-submit-btn-bg-color: var(--teal-200);
260
264
  --search-user-message-color: var(--teal-800);
261
265
  --switcher-tab-bg-color: color(srgb 0.047058823529411764 0.16470588235294117 0.16470588235294117 / 0.2);
262
266
  --title-text-color: color(srgb 0.8352941176470589 1 0.996078431372549 / 0.7490196078431373);
@@ -328,7 +332,9 @@
328
332
  --search-back-btn-bg-color: var(--neutral-500);
329
333
  --search-bg-color: var(--neutral-700);
330
334
  --search-highlight-color: var(--teal-800);
335
+ --search-input-bg-color: var(--neutral-600);
331
336
  --search-loading-color: var(--teal-200);
337
+ --search-submit-btn-bg-color: var(--teal-200);
332
338
  --search-user-message-color: var(--teal-800);
333
339
  --switcher-tab-bg-color: color(srgb 0.047058823529411764 0.16470588235294117 0.16470588235294117 / 0.2);
334
340
  --title-text-color: color(srgb 0.8352941176470589 1 0.996078431372549 / 0.7490196078431373);
@@ -399,8 +405,10 @@
399
405
  --search-back-btn-bg-color: var(--neutral-100);
400
406
  --search-bg-color: var(--neutral-000);
401
407
  --search-highlight-color: var(--teal-000);
402
- --search-loading-color: var(--blue-700);
403
- --search-user-message-color: var(--blue-100);
408
+ --search-input-bg-color: var(--neutral-100);
409
+ --search-loading-color: var(--teal-700);
410
+ --search-submit-btn-bg-color: var(--teal-200);
411
+ --search-user-message-color: var(--teal-000);
404
412
  --switcher-tab-bg-color: color(srgb 1 1 1 / 0.047058823529411764);
405
413
  --title-text-color: color(srgb 0.32941176470588235 0.35294117647058826 0.3803921568627451 / 0.7490196078431373);
406
414
  }