@netlify/spark-ui 1.4.4 → 1.5.0-alpha.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 (52) hide show
  1. package/dist/assets/AskNetlify.css +1 -0
  2. package/dist/assets/Orb.css +1 -0
  3. package/dist/assets/SearchLink.css +1 -1
  4. package/dist/assets/SearchModal.css +1 -1
  5. package/dist/chunks/compat.module.BNbXPLVY.js +298 -0
  6. package/dist/chunks/hooks.module.DGkdD5n9.js +177 -0
  7. package/dist/chunks/{useTimeout.YR4eKwj7.js → useTimeout.Cq4aqnDe.js} +1 -1
  8. package/dist/components/index.d.ts +1 -0
  9. package/dist/components/preact/AskNetlify/AskNetlify.d.ts +2 -0
  10. package/dist/components/preact/AskNetlify/AskNetlify.js +5653 -0
  11. package/dist/components/preact/Modal/Modal.js +1 -1
  12. package/dist/components/preact/Orb/Orb.d.ts +2 -0
  13. package/dist/components/preact/Orb/Orb.js +5 -0
  14. package/dist/components/preact/SearchModal/components/SearchInput.d.ts +2 -0
  15. package/dist/components/preact/SearchModal/components/SearchInput.js +2 -2
  16. package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.d.ts +1 -1
  17. package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.js +35 -17
  18. package/dist/components/preact/SearchModal/components/SearchModal/SearchModal.js +81 -51
  19. package/dist/components/preact/SearchModal/components/SearchToggle/SearchToggle.js +1 -1
  20. package/dist/components/preact/SearchModal/components/SearchWindow.d.ts +1 -1
  21. package/dist/components/preact/SearchModal/components/SearchWindow.js +46 -30
  22. package/dist/components/preact/SearchModal/hooks/useKeyboardNav.js +6 -6
  23. package/dist/components/preact/SearchModal/hooks/useSearch.js +2 -2
  24. package/dist/components/preact/SearchModal/hooks/useSearchHotkeys.js +1 -1
  25. package/dist/components/preact/SearchModal/hooks/useSearchLogger.js +1 -1
  26. package/dist/components/preact/SearchModal/hooks/useSearchParams.js +1 -1
  27. package/dist/components/preact/SearchModal/hooks/useSearchToggles.js +1 -1
  28. package/dist/components/preact/SearchModal/types/search.types.d.ts +6 -1
  29. package/dist/components/preact/index.d.ts +1 -0
  30. package/dist/components/preact/index.js +7 -5
  31. package/dist/utils/handleThemeChange.d.ts +1 -0
  32. package/package.json +4 -2
  33. package/packages/components/CodeBlock.astro +1 -1
  34. package/packages/components/index.ts +1 -0
  35. package/packages/components/preact/AskNetlify/AskNetlify.css +168 -0
  36. package/packages/components/preact/AskNetlify/AskNetlify.tsx +126 -0
  37. package/packages/components/preact/Orb/Orb.css +94 -0
  38. package/packages/components/preact/Orb/Orb.tsx +7 -0
  39. package/packages/components/preact/SearchModal/components/SearchInput.tsx +9 -7
  40. package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.css +7 -2
  41. package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.tsx +27 -7
  42. package/packages/components/preact/SearchModal/components/SearchModal/SearchModal.css +75 -18
  43. package/packages/components/preact/SearchModal/components/SearchModal/SearchModal.tsx +67 -30
  44. package/packages/components/preact/SearchModal/components/SearchWindow.tsx +31 -16
  45. package/packages/components/preact/SearchModal/hooks/useKeyboardNav.ts +2 -2
  46. package/packages/components/preact/SearchModal/types/search.types.tsx +5 -2
  47. package/packages/components/preact/index.ts +1 -0
  48. package/packages/styles/tokens.css +28 -0
  49. package/packages/utils/handleThemeChange.ts +11 -0
  50. package/dist/chunks/compat.module.DJD78v1z.js +0 -175
  51. package/dist/chunks/hooks.module.D9IrxdEh.js +0 -138
  52. package/packages/themes/poimandres.json +0 -311
@@ -0,0 +1,168 @@
1
+ .chat-container {
2
+ opacity: 0;
3
+ animation: appearContainer 120ms ease-in-out forwards;
4
+ animation-delay: 140ms;
5
+ }
6
+
7
+ @keyframes appearContainer {
8
+ 0% {
9
+ opacity: 0;
10
+ }
11
+ 100% {
12
+ opacity: 1;
13
+ }
14
+ }
15
+
16
+ .conversation {
17
+ height: calc(var(--n-search-container-height) + var(--n-search-footer-height));
18
+ padding: var(--n-search-padding-inline);
19
+ scroll-behavior: smooth;
20
+ overflow-y: auto;
21
+ display: grid;
22
+ align-items: flex-end;
23
+ gap: var(--space-s);
24
+ }
25
+
26
+ .message {
27
+ display: grid;
28
+ gap: var(--space-s);
29
+ }
30
+
31
+ .message-bubble {
32
+ padding: var(--space-xs) var(--space-s);
33
+ background: var(--search-answer-message-color);
34
+ border-radius: var(--radius-l);
35
+ width: fit-content;
36
+ max-width: 85%;
37
+ font-size: var(--step-0);
38
+ }
39
+
40
+ .prose {
41
+ font-size: var(--step--0);
42
+
43
+ > * + * {
44
+ margin-top: var(--flow-space, var(--space-xs));
45
+ }
46
+
47
+ h1,
48
+ h2,
49
+ h3 {
50
+ font-size: var(--step-1);
51
+ }
52
+
53
+ h4,
54
+ h5,
55
+ h6 {
56
+ font-size: var(--step-0);
57
+ }
58
+
59
+ pre {
60
+ background: var(--neutral-light-700);
61
+ border-radius: var(--radius-m);
62
+ max-width: 100%;
63
+ box-sizing: border-box;
64
+ }
65
+
66
+ code {
67
+ overflow-x: auto;
68
+ }
69
+
70
+ code[class*='language-'],
71
+ pre[class*='language-'] {
72
+ display: block;
73
+ padding: var(--space-s);
74
+ }
75
+ }
76
+
77
+ .user-message {
78
+ margin-inline-start: auto;
79
+ background: var(--search-user-message-color);
80
+ }
81
+
82
+ .form-wrapper {
83
+ display: flex;
84
+ align-items: center;
85
+ position: relative;
86
+ height: var(--n-search-input-height);
87
+ }
88
+
89
+ .form-wrapper::after {
90
+ content: '';
91
+ display: block;
92
+ width: 100%;
93
+ height: 1px;
94
+ background: var(--color-bg-3);
95
+ position: absolute;
96
+ top: 0;
97
+ left: 0;
98
+ }
99
+
100
+ .form-input {
101
+ border: none;
102
+ outline: none;
103
+ padding-inline: var(--n-search-padding-inline);
104
+ }
105
+
106
+ .form-submit {
107
+ border: none;
108
+ padding: 0.7rem;
109
+ border-radius: var(--radius-pill);
110
+ margin-inline-end: var(--space-2xs);
111
+ border: none;
112
+ background: var(--color-link);
113
+ color: var(--neutral-000);
114
+ transition: background-color 150ms ease-out;
115
+ }
116
+
117
+ .form-submit > svg {
118
+ width: 1rem;
119
+ height: auto;
120
+ aspect-ratio: 1 / 1;
121
+ border-radius: 1.5px;
122
+ transition: opacity 150ms ease-out;
123
+ }
124
+
125
+ .form-submit[disabled] {
126
+ background: var(--neutral-500);
127
+
128
+ & > * {
129
+ opacity: 0.5;
130
+ }
131
+ }
132
+
133
+ .button-back {
134
+ width: fit-content;
135
+ background: transparent;
136
+ position: absolute;
137
+ z-index: 1;
138
+ padding: 0.5rem 1rem;
139
+ background: var(--search-back-btn-bg-color);
140
+ margin: 1rem;
141
+ border: 2px solid var(--color-bg-3);
142
+ border-radius: var(--radius-pill);
143
+ font-size: var(--step--1);
144
+ border: 1px solid;
145
+ cursor: pointer;
146
+ border-color: var(--color-bg-3);
147
+ color: var(--color-text-1);
148
+ border-width: 0.5px;
149
+ }
150
+
151
+ .pulsing {
152
+ display: block;
153
+ width: 1rem;
154
+ height: 1rem;
155
+ clip-path: circle(40%);
156
+ background: var(--search-loading-color);
157
+ animation: pulsing 2.5s infinite ease-in-out;
158
+ }
159
+
160
+ @keyframes pulsing {
161
+ 0%,
162
+ 100% {
163
+ transform: scale(0.9);
164
+ }
165
+ 50% {
166
+ transform: scale(1.05);
167
+ }
168
+ }
@@ -0,0 +1,126 @@
1
+ import { useState, useRef, useEffect } from 'preact/hooks';
2
+ import { marked } from 'marked';
3
+ import { KapaProvider, useChat } from '@kapaai/react-sdk';
4
+
5
+ import './AskNetlify.css';
6
+
7
+ function AskNetlify(props) {
8
+ return (
9
+ <KapaProvider
10
+ integrationId="3c0789a6-c6a8-4fbc-b0cc-235f9e4a6351"
11
+ callbacks={{
12
+ askAI: {
13
+ onQuerySubmit: data => console.log('Question asked:', data.question),
14
+ },
15
+ }}
16
+ >
17
+ <ChatInterface {...props} />
18
+ </KapaProvider>
19
+ );
20
+ }
21
+
22
+ function ChatInterface({ setModalState }) {
23
+ const [message, setMessage] = useState('');
24
+ const { conversation, submitQuery, isGeneratingAnswer, isPreparingAnswer, stopGeneration } =
25
+ useChat();
26
+
27
+ const chatRef = useRef(null);
28
+
29
+ const handleSubmit = e => {
30
+ e.preventDefault();
31
+ if (message.trim()) {
32
+ submitQuery(message);
33
+ setMessage('');
34
+ }
35
+ };
36
+
37
+ useEffect(() => {
38
+ const el = chatRef.current as HTMLElement | null;
39
+ if (el) el.scrollTop = el.scrollHeight;
40
+ }, [conversation]);
41
+
42
+ return (
43
+ <div className="chat-container">
44
+ <button
45
+ class="button button-back"
46
+ data-button-variant="outline"
47
+ onClick={() => setModalState({ current: 'search' })}
48
+ aria-label={'Return to search'}
49
+ >
50
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
51
+ <path
52
+ d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"
53
+ fill="currentColor"
54
+ />
55
+ </svg>
56
+ Search
57
+ </button>
58
+ <div ref={chatRef} className="conversation n-search-window">
59
+ {conversation.map((qa, index) => (
60
+ <div key={qa.id || `qa-${index}`} className="message">
61
+ <div className="message-right">
62
+ <div className="message-bubble user-message">
63
+ <p>{qa.question}</p>
64
+ </div>
65
+ </div>
66
+
67
+ <div className="message-left">
68
+ {qa.answer ? (
69
+ <div className="message-bubble bot-message">
70
+ <div
71
+ className="message-answer prose flow"
72
+ dangerouslySetInnerHTML={{
73
+ __html: marked.parse(qa.answer),
74
+ }}
75
+ />
76
+ </div>
77
+ ) : (
78
+ <span class="pulsing"></span>
79
+ )}
80
+ </div>
81
+ </div>
82
+ ))}
83
+ </div>
84
+
85
+ <form onSubmit={handleSubmit} className="form-wrapper n-search-input-wrapper">
86
+ <input
87
+ type="text"
88
+ className="form-input n-search-input"
89
+ value={message}
90
+ onChange={e => setMessage(e.target.value)}
91
+ placeholder="Ask a question..."
92
+ disabled={isGeneratingAnswer}
93
+ />
94
+ {/* <button type="submit" className="form-submit" disabled={isGeneratingAnswer}>
95
+ Send
96
+ </button> */}
97
+
98
+ {isGeneratingAnswer ? (
99
+ <button
100
+ type="button"
101
+ className="form-submit"
102
+ onClick={stopGeneration}
103
+ aria-label="Stop generation"
104
+ >
105
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="currentColor">
106
+ <rect x="0" y="0" width="16" height="16" />
107
+ </svg>
108
+ </button>
109
+ ) : (
110
+ <button
111
+ type="submit"
112
+ className="form-submit"
113
+ disabled={!message.trim() || isPreparingAnswer}
114
+ aria-label="Send message"
115
+ >
116
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" fill="currentColor">
117
+ <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" />
118
+ </svg>
119
+ </button>
120
+ )}
121
+ </form>
122
+ </div>
123
+ );
124
+ }
125
+
126
+ export default AskNetlify;
@@ -0,0 +1,94 @@
1
+ @property --angle {
2
+ syntax: '<angle>';
3
+ inherits: false;
4
+ initial-value: 0deg;
5
+ }
6
+
7
+ :root {
8
+ --bg: oklch(12.9% 0.042 264.695);
9
+ --c1: oklch(44.4% 0.177 26.899);
10
+ --c2: oklch(85.2% 0.199 91.936);
11
+ --c3: oklch(58.8% 0.158 241.966);
12
+ }
13
+
14
+ .orb {
15
+ display: grid;
16
+ grid-template-areas: 'stack';
17
+ inline-size: min(80vmin, 100%);
18
+ aspect-ratio: 1;
19
+ overflow: hidden;
20
+ border-radius: 50%;
21
+
22
+ &::before,
23
+ &::after {
24
+ content: '';
25
+ display: block;
26
+ grid-area: stack;
27
+ width: 100%;
28
+ height: 100%;
29
+ border-radius: 50%;
30
+ transform: translateZ(0);
31
+ }
32
+
33
+ &::before {
34
+ background:
35
+ conic-gradient(
36
+ from calc(var(--angle) * 2) at 25% 70%,
37
+ var(--c3),
38
+ transparent 20% 80%,
39
+ var(--c3)
40
+ ),
41
+ conic-gradient(
42
+ from calc(var(--angle) * 2) at 45% 75%,
43
+ var(--c2),
44
+ transparent 30% 60%,
45
+ var(--c2)
46
+ ),
47
+ conic-gradient(
48
+ from calc(var(--angle) * -3) at 80% 20%,
49
+ var(--c1),
50
+ transparent 40% 60%,
51
+ var(--c1)
52
+ ),
53
+ conic-gradient(
54
+ from calc(var(--angle) * 2) at 15% 5%,
55
+ var(--c2),
56
+ transparent 10% 90%,
57
+ var(--c2)
58
+ ),
59
+ conic-gradient(
60
+ from calc(var(--angle) * 1) at 20% 80%,
61
+ var(--c1),
62
+ transparent 10% 90%,
63
+ var(--c1)
64
+ ),
65
+ conic-gradient(
66
+ from calc(var(--angle) * -2) at 85% 10%,
67
+ var(--c3),
68
+ transparent 20% 80%,
69
+ var(--c3)
70
+ );
71
+ box-shadow: inset var(--bg) 0 0 5vmin 1vmin;
72
+ filter: blur(3vmin) contrast(5);
73
+ animation: rotate 20s linear infinite;
74
+ }
75
+
76
+ &::after {
77
+ --dot: 1.5px;
78
+ background-image: radial-gradient(
79
+ circle at center,
80
+ var(--bg) var(--dot),
81
+ transparent var(--dot)
82
+ );
83
+ background-size: calc(var(--dot) * 2) calc(var(--dot) * 2);
84
+ mask-image: radial-gradient(black 25%, transparent 75%);
85
+ backdrop-filter: blur(8vmin) contrast(10);
86
+ mix-blend-mode: overlay;
87
+ }
88
+ }
89
+
90
+ @keyframes rotate {
91
+ to {
92
+ --angle: 360deg;
93
+ }
94
+ }
@@ -0,0 +1,7 @@
1
+ import './Orb.css';
2
+
3
+ const Orb = () => {
4
+ return <div class="orb"></div>;
5
+ };
6
+
7
+ export default Orb;
@@ -6,20 +6,22 @@ interface Props {
6
6
  setSearchTerm: (term: string) => void;
7
7
  placeholder?: string;
8
8
  initialValue?: string;
9
+ isGeneratingAnswer?: boolean;
10
+ isPreparingAnswer?: boolean;
9
11
  }
10
12
 
11
13
  const SearchInput = (props: Props) => {
12
14
  const [value, setValue] = useState<string>('');
13
15
 
14
- const { reset, run } = useTimeout((query: string) => {
15
- props.setSearchTerm(query);
16
- }, 300);
17
-
18
- const handleInput = (e: InputEvent) => {
16
+ const { reset, run } = useTimeout((query: string) => {
17
+ props.setSearchTerm(query);
18
+ }, 300);
19
+
20
+ const handleInput = (e: InputEvent) => {
19
21
  const target = e.target as HTMLInputElement;
20
22
  setValue(target.value);
21
- reset()
22
- run(target.value)
23
+ reset();
24
+ run(target.value);
23
25
  };
24
26
 
25
27
  useEffect(() => {
@@ -1,6 +1,7 @@
1
1
  .n-search-link {
2
2
  --n-search-icon-size: 1em;
3
- all: unset;
3
+ /* all: unset; */
4
+ box-sizing: border-box;
4
5
  display: flex;
5
6
  justify-content: space-between;
6
7
  flex-wrap: wrap;
@@ -11,6 +12,10 @@
11
12
  font-weight: var(--font-weight-regular);
12
13
  text-decoration: none;
13
14
  cursor: pointer;
15
+ width: 100%;
16
+ border: none;
17
+ text-align: left;
18
+ outline: none;
14
19
  }
15
20
 
16
21
  .n-search-link-body {
@@ -30,7 +35,7 @@
30
35
 
31
36
  .n-search-link-title {
32
37
  font-weight: var(--font-weight-medium);
33
- color: var(--color-text);
38
+ color: var(--color-text-1);
34
39
  line-height: 1.5;
35
40
  }
36
41
 
@@ -4,20 +4,40 @@ import './SearchLink.css';
4
4
 
5
5
  import Icon from '../../../Icon';
6
6
 
7
- const SearchLink = ({ icon, label, url, excerpt, resource }: SearchLinkProps) => {
8
- const { handleClick } = useSearchLogger()
9
-
7
+ const SearchLink = ({
8
+ as: Component = 'a',
9
+ icon,
10
+ label,
11
+ url,
12
+ excerpt,
13
+ resource,
14
+ onClick,
15
+ }: SearchLinkProps) => {
16
+ const { handleClick: log } = useSearchLogger();
17
+
10
18
  return (
11
- <a className="n-search-link" href={url} onClick={handleClick}>
19
+ <Component
20
+ className="n-search-link"
21
+ href={url}
22
+ onClick={(e: Event) => {
23
+ onClick && onClick();
24
+ log(e);
25
+ }}
26
+ >
12
27
  {icon && (
13
28
  <span className="n-search-link-icon">
14
29
  <Icon name={icon} />
15
30
  </span>
16
31
  )}
17
32
  <div className="n-search-link-body">
18
-
19
33
  <p className="n-search-link-title">
20
- <span className="n-search-link-label">{label}</span> {resource && <span className="n-search-link-badge" data-resource={resource}>{resource}</span>}</p>
34
+ <span className="n-search-link-label">{label}</span>{' '}
35
+ {resource && (
36
+ <span className="n-search-link-badge" data-resource={resource}>
37
+ {resource}
38
+ </span>
39
+ )}
40
+ </p>
21
41
  {excerpt && (
22
42
  <p className="n-search-link-excerpt" dangerouslySetInnerHTML={{ __html: excerpt }} />
23
43
  )}
@@ -25,7 +45,7 @@ const SearchLink = ({ icon, label, url, excerpt, resource }: SearchLinkProps) =>
25
45
  <div className="n-search-link-icon">
26
46
  <Icon name="caretRight" />
27
47
  </div>
28
- </a>
48
+ </Component>
29
49
  );
30
50
  };
31
51
 
@@ -4,15 +4,18 @@
4
4
 
5
5
  /* Root Variables */
6
6
  .n-search-modal {
7
- --n-search-padding-inline: 0.5em;
8
- --n-search-color-text: var(--color-text, currentColor);
7
+ --n-search-padding-inline: var(--space-s);
8
+ --n-search-color-text: var(--color-text-1, currentColor);
9
9
  --n-search-color-text-secondary: var(--color-text-weak, currentColor);
10
10
  --n-search-color-bg: var(--search-bg-color);
11
- --n-search-color-bg-2: var(--color-bg-2);
11
+ --n-search-color-bg-2: var(--color-bg-3);
12
12
  --n-search-color-border: var(--color-bg-3);
13
- --n-search-border-radius: 8px;
13
+ --n-search-border-radius: 1.3rem;
14
14
  --n-search-max-w: 600px;
15
15
  --n-search-text-highlight: var(--search-highlight-color);
16
+ --n-search-input-height: 50px;
17
+ --n-search-footer-height: 40px;
18
+ --n-search-container-height: 400px;
16
19
  }
17
20
 
18
21
  .n-search-modal {
@@ -21,13 +24,33 @@
21
24
  border-radius: var(--n-search-border-radius);
22
25
  width: min(var(--n-search-max-w), 100%);
23
26
  margin: auto;
27
+ background: var(--n-search-color-bg);
28
+ }
29
+
30
+ .n-search-modal::after {
31
+ content: '';
32
+ display: block;
33
+ width: 100%;
34
+ height: 100%;
35
+ background: blue;
36
+ position: absolute;
37
+ z-index: 0;
38
+ inset: 0;
39
+ transform-origin: center;
40
+ transform: scale(0);
41
+ border-radius: var(--radius-m);
42
+ transition: transform 300ms ease-in-out;
43
+ }
44
+
45
+ .n-search-modal-ask {
46
+ --n-search-max-w: 800px;
24
47
  }
25
48
 
26
49
  .n-search-container {
27
50
  --mark-bg: var(--n-search-text-highlight);
28
51
  display: grid;
29
52
  width: min(var(--n-search-max-w), 100%);
30
- min-height: 470px;
53
+ /* min-height: 530px; */
31
54
  background-color: var(--n-search-color-bg);
32
55
  border-radius: var(--n-search-border-radius);
33
56
  overflow: hidden;
@@ -43,13 +66,15 @@
43
66
  }
44
67
 
45
68
  .n-search-input-wrapper {
46
- all: unset;
69
+ /* all: unset; */
70
+ position: relative;
47
71
  display: flex;
48
72
  width: 100%;
49
- border-bottom: 1px solid var(--n-search-color-border);
73
+ height: var(--n-search-input-height);
50
74
  }
51
75
 
52
76
  .n-search-input {
77
+ position: relative;
53
78
  display: inline-flex;
54
79
  box-sizing: border-box;
55
80
  background: var(--n-search-color-bg);
@@ -60,7 +85,19 @@
60
85
  border: none;
61
86
  outline: none;
62
87
  font-size: 1rem;
63
- color: var(--color-text);
88
+ color: var(--color-text-1);
89
+ }
90
+
91
+ .n-search-input-wrapper::after,
92
+ .n-search-footer::after {
93
+ content: '';
94
+ display: block;
95
+ width: 200%;
96
+ height: 1px;
97
+ background: var(--n-search-color-border);
98
+ position: absolute;
99
+ bottom: 0;
100
+ left: 0;
64
101
  }
65
102
 
66
103
  .n-search-input::placeholder {
@@ -69,7 +106,7 @@
69
106
 
70
107
  .n-search-label {
71
108
  display: flex;
72
- padding-inline: var(--n-search-padding-inline);
109
+ padding-inline-start: var(--n-search-padding-inline);
73
110
  align-items: center;
74
111
  }
75
112
 
@@ -92,7 +129,7 @@
92
129
 
93
130
  /* Search Results */
94
131
  .n-search-window {
95
- height: 400px;
132
+ height: var(--n-search-container-height);
96
133
  overflow-x: hidden;
97
134
  overflow-y: auto;
98
135
  overscroll-behavior: contain;
@@ -115,11 +152,15 @@ ul[role='list'] {
115
152
 
116
153
  /* Footer */
117
154
  .n-search-footer {
118
- border-top: 1px solid var(--n-search-color-border);
119
- padding: var(--space-2xs) var(--space-s);
120
- background: var(--color-bg-2);
155
+ position: relative;
121
156
  display: flex;
122
157
  gap: var(--space-m);
158
+ padding: var(--space-2xs) var(--space-s);
159
+ height: var(--n-search-footer-height);
160
+ }
161
+
162
+ .n-search-footer::after {
163
+ top: 0;
123
164
  }
124
165
 
125
166
  .n-search-footer-tip {
@@ -134,7 +175,8 @@ ul[role='list'] {
134
175
  .n-search-modal {
135
176
  transition:
136
177
  display 0.2s allow-discrete,
137
- overlay 0.2s allow-discrete;
178
+ overlay 0.2s allow-discrete,
179
+ width 0.15s ease-in-out;
138
180
  animation: closeSearch 0.2s forwards;
139
181
  }
140
182
 
@@ -142,28 +184,43 @@ ul[role='list'] {
142
184
  animation: openSearch 0.2s forwards;
143
185
  }
144
186
 
187
+ .n-search-modal[data-state='search'] > * {
188
+ opacity: 0;
189
+ animation: appearContainer 120ms ease-in-out forwards;
190
+ animation-delay: 140ms;
191
+ }
192
+
193
+ @keyframes appearContainer {
194
+ 0% {
195
+ opacity: 0;
196
+ }
197
+ 100% {
198
+ opacity: 1;
199
+ }
200
+ }
201
+
145
202
  @keyframes openSearch {
146
203
  from {
147
204
  opacity: 0;
148
- transform: translateY(20px) scale(0.9);
205
+ transform: scale(0.9);
149
206
  }
150
207
  to {
151
208
  opacity: 1;
152
- transform: translateY(0px) scale(1);
209
+ transform: scale(1);
153
210
  }
154
211
  }
155
212
 
156
213
  @keyframes closeSearch {
157
214
  from {
158
215
  opacity: 1;
159
- transform: translateY(0px) scale(1);
216
+ transform: scale(1);
160
217
  }
161
218
  50% {
162
219
  opacity: 0.3;
163
220
  }
164
221
  to {
165
222
  opacity: 0;
166
- transform: translateY(20px) scale(0.9);
223
+ transform: scale(0.9);
167
224
  }
168
225
  }
169
226