@netlify/spark-ui 1.12.0-alpha.0 → 1.12.0-alpha.2
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/assets/AskNetlify.css +1 -1
- package/dist/assets/SearchLink.css +1 -1
- package/dist/assets/SearchModal.css +1 -1
- package/dist/chunks/{ChatInterface.EQAPs01R.js → AskNetlify.lrswYyKn.js} +119 -110
- package/dist/components/preact/AskNetlify/AskNetlify.d.ts +3 -3
- package/dist/components/preact/AskNetlify/AskNetlify.js +8 -34
- package/dist/components/preact/AskNetlify/components/ChatMessages.d.ts +2 -1
- package/dist/components/preact/AskNetlify/components/ChatMessages.js +25 -21
- package/dist/components/preact/AskNetlify/components/SourcesList.js +17 -13
- package/dist/components/preact/AskNetlify/hooks/useAskAnalytics.d.ts +23 -0
- package/dist/components/preact/AskNetlify/hooks/useAskAnalytics.js +22 -0
- package/dist/components/preact/Icon/index.js +6 -6
- package/dist/components/preact/SearchModal/components/SearchInput.js +18 -8
- package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.d.ts +1 -1
- package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.js +23 -22
- package/dist/components/preact/SearchModal/components/SearchLinkThemeToggle.d.ts +1 -0
- package/dist/components/preact/SearchModal/components/SearchLinkThemeToggle.js +26 -0
- package/dist/components/preact/SearchModal/components/SearchModal/SearchModal.js +89 -84
- package/dist/components/preact/SearchModal/components/SearchWindow.d.ts +1 -1
- package/dist/components/preact/SearchModal/components/SearchWindow.js +55 -48
- package/dist/components/preact/SearchModal/hooks/index.d.ts +1 -1
- package/dist/components/preact/SearchModal/hooks/index.js +8 -8
- package/dist/components/preact/SearchModal/hooks/useKeyboardNav.d.ts +1 -1
- package/dist/components/preact/SearchModal/hooks/useKeyboardNav.js +11 -12
- package/dist/components/preact/SearchModal/hooks/useSearch.js +19 -19
- package/dist/components/preact/SearchModal/hooks/{useSearchLogger.d.ts → useSearchAnalytics.d.ts} +2 -2
- package/dist/components/preact/SearchModal/hooks/{useSearchLogger.js → useSearchAnalytics.js} +5 -5
- package/dist/components/preact/SearchModal/hooks/useSearchHotkeys.d.ts +1 -1
- package/dist/components/preact/SearchModal/hooks/useSearchHotkeys.js +8 -9
- package/dist/components/preact/SearchModal/hooks/useSearchParams.d.ts +1 -1
- package/dist/components/preact/SearchModal/hooks/useSearchParams.js +14 -7
- package/dist/components/preact/SearchModal/hooks/useSearchToggles.d.ts +1 -1
- package/dist/components/preact/SearchModal/hooks/useSearchToggles.js +7 -8
- package/dist/components/preact/SearchModal/types/search.types.d.ts +11 -4
- package/dist/components/preact/index.js +3 -3
- package/package.json +1 -1
- package/packages/components/preact/AskNetlify/AskNetlify.css +41 -1
- package/packages/components/preact/AskNetlify/AskNetlify.tsx +98 -34
- package/packages/components/preact/AskNetlify/components/ChatMessages.tsx +9 -7
- package/packages/components/preact/AskNetlify/components/SourcesList.tsx +24 -16
- package/packages/components/preact/AskNetlify/hooks/useAskAnalytics.tsx +50 -0
- package/packages/components/preact/Icon/index.tsx +2 -2
- package/packages/components/preact/SearchModal/components/SearchInput.tsx +10 -0
- package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.css +5 -0
- package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.tsx +6 -4
- package/packages/components/preact/SearchModal/components/SearchLinkThemeToggle.tsx +19 -0
- package/packages/components/preact/SearchModal/components/SearchModal/SearchModal.css +33 -3
- package/packages/components/preact/SearchModal/components/SearchModal/SearchModal.tsx +73 -89
- package/packages/components/preact/SearchModal/components/SearchWindow.tsx +64 -46
- package/packages/components/preact/SearchModal/hooks/index.ts +1 -1
- package/packages/components/preact/SearchModal/hooks/useKeyboardNav.ts +1 -3
- package/packages/components/preact/SearchModal/hooks/useSearch.ts +20 -16
- package/packages/components/preact/SearchModal/hooks/{useSearchLogger.ts → useSearchAnalytics.ts} +9 -10
- package/packages/components/preact/SearchModal/hooks/useSearchHotkeys.ts +2 -4
- package/packages/components/preact/SearchModal/hooks/useSearchParams.ts +24 -2
- package/packages/components/preact/SearchModal/hooks/useSearchToggles.ts +1 -3
- package/packages/components/preact/SearchModal/types/search.types.tsx +7 -4
- package/packages/styles/loaders.css +18 -0
- package/packages/styles/tokens.css +4 -0
- package/dist/components/preact/AskNetlify/components/ChatInterface.d.ts +0 -7
- package/dist/components/preact/AskNetlify/components/ChatInterface.js +0 -10
- package/packages/components/preact/AskNetlify/components/ChatInterface.tsx +0 -94
|
@@ -1,44 +1,108 @@
|
|
|
1
|
-
import { useRef } from 'preact/hooks';
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
1
|
+
import { useState, useRef, useEffect } from 'preact/hooks';
|
|
2
|
+
import { useChat } from '@kapaai/react-sdk';
|
|
3
|
+
import { questions } from './components/QuestionList/questions';
|
|
4
|
+
import { QuestionList } from './components/QuestionList/QuestionList';
|
|
5
|
+
import { BackButton } from './components/BackButton';
|
|
6
|
+
import { ChatMessages } from './components/ChatMessages';
|
|
7
|
+
|
|
5
8
|
import './AskNetlify.css';
|
|
6
9
|
|
|
7
|
-
interface
|
|
8
|
-
kapaIntegrationId?: string;
|
|
10
|
+
interface Props {
|
|
9
11
|
setModalState?: (e: { current: string }) => void;
|
|
12
|
+
initialQuery?: string;
|
|
10
13
|
}
|
|
11
14
|
|
|
12
|
-
export default function AskNetlify(
|
|
13
|
-
const
|
|
15
|
+
export default function AskNetlify({ setModalState, initialQuery }: Props) {
|
|
16
|
+
const [message, setMessage] = useState('');
|
|
17
|
+
const [newMessageReady, setNewMessageReady] = useState(false);
|
|
18
|
+
const chatRef = useRef<HTMLDivElement>(null);
|
|
19
|
+
const menuRef = useRef<HTMLUListElement>(null);
|
|
20
|
+
const lastMessageRef = useRef<HTMLDivElement | null>(null);
|
|
21
|
+
|
|
22
|
+
const { conversation, submitQuery, isGeneratingAnswer, isPreparingAnswer, stopGeneration } =
|
|
23
|
+
useChat();
|
|
24
|
+
|
|
25
|
+
const handleSubmit = (e: Event | preact.JSX.TargetedMouseEvent<HTMLButtonElement>) => {
|
|
26
|
+
e.preventDefault();
|
|
27
|
+
const target = e.target as HTMLButtonElement;
|
|
28
|
+
const id = target?.getAttribute('data-question-id');
|
|
29
|
+
const text = id ? questions[id] : message;
|
|
30
|
+
|
|
31
|
+
if (text.trim()) {
|
|
32
|
+
submitQuery(text.trim());
|
|
33
|
+
}
|
|
34
|
+
if (!id) setMessage('');
|
|
14
35
|
|
|
15
|
-
|
|
36
|
+
setNewMessageReady(true);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/*
|
|
40
|
+
* When a new user message is ready, scroll it into view and reset the flag.
|
|
41
|
+
* This ensures the most recent message is visible to the user immediately after rendering.
|
|
42
|
+
*/
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
if (newMessageReady && lastMessageRef.current) {
|
|
45
|
+
lastMessageRef.current.scrollIntoView({ behavior: 'smooth' });
|
|
46
|
+
setNewMessageReady(false);
|
|
47
|
+
}
|
|
48
|
+
}, [newMessageReady]);
|
|
49
|
+
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
if (initialQuery) {
|
|
52
|
+
submitQuery(initialQuery.trim());
|
|
53
|
+
setNewMessageReady(true);
|
|
54
|
+
}
|
|
55
|
+
}, [initialQuery]);
|
|
16
56
|
|
|
17
57
|
return (
|
|
18
|
-
<
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
<div className="n-ask-chat-container">
|
|
59
|
+
<BackButton onClick={() => setModalState?.({ current: 'search' })} />
|
|
60
|
+
<div ref={chatRef} className="n-ask-conversation n-search-window">
|
|
61
|
+
{!conversation.length ? (
|
|
62
|
+
<QuestionList onClick={handleSubmit} menuRef={menuRef} />
|
|
63
|
+
) : (
|
|
64
|
+
<ChatMessages
|
|
65
|
+
isPreparingAnswer={isPreparingAnswer}
|
|
66
|
+
conversation={conversation}
|
|
67
|
+
lastMessageRef={lastMessageRef}
|
|
68
|
+
/>
|
|
69
|
+
)}
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
<form onSubmit={handleSubmit} className="n-ask-form n-search-input-wrapper">
|
|
73
|
+
<input
|
|
74
|
+
className="n-ask-input n-search-input"
|
|
75
|
+
type="text"
|
|
76
|
+
value={message}
|
|
77
|
+
onInput={e => setMessage(e.currentTarget.value)}
|
|
78
|
+
placeholder="Ask a question..."
|
|
79
|
+
disabled={isPreparingAnswer || isGeneratingAnswer}
|
|
80
|
+
/>
|
|
81
|
+
|
|
82
|
+
{isGeneratingAnswer ? (
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
className="n-ask-form-submit"
|
|
86
|
+
onClick={stopGeneration}
|
|
87
|
+
aria-label="Stop generation"
|
|
88
|
+
>
|
|
89
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" fill="var(--neutral-100)">
|
|
90
|
+
<rect x="0" y="0" width="16" height="16" />
|
|
91
|
+
</svg>
|
|
92
|
+
</button>
|
|
93
|
+
) : (
|
|
94
|
+
<button
|
|
95
|
+
type="submit"
|
|
96
|
+
className="n-ask-form-submit"
|
|
97
|
+
disabled={!message.trim() || isPreparingAnswer}
|
|
98
|
+
aria-label="Send message"
|
|
99
|
+
>
|
|
100
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" fill="var(--neutral-100)">
|
|
101
|
+
<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" />
|
|
102
|
+
</svg>
|
|
103
|
+
</button>
|
|
104
|
+
)}
|
|
105
|
+
</form>
|
|
106
|
+
</div>
|
|
43
107
|
);
|
|
44
108
|
}
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import type { Ref } from 'preact';
|
|
2
1
|
import { marked } from 'marked';
|
|
3
|
-
|
|
4
2
|
import Icon from '../../Icon';
|
|
3
|
+
import { Ref } from 'preact';
|
|
5
4
|
import { SourcesList } from './SourcesList';
|
|
6
5
|
|
|
7
6
|
export function ChatMessages({
|
|
8
7
|
conversation,
|
|
9
8
|
lastMessageRef,
|
|
9
|
+
isPreparingAnswer,
|
|
10
10
|
}: {
|
|
11
11
|
conversation: any[];
|
|
12
12
|
lastMessageRef: Ref<HTMLDivElement>;
|
|
13
|
+
isPreparingAnswer: boolean;
|
|
13
14
|
}) {
|
|
14
15
|
return (
|
|
15
16
|
<>
|
|
@@ -27,7 +28,7 @@ export function ChatMessages({
|
|
|
27
28
|
</div>
|
|
28
29
|
|
|
29
30
|
<div class="n-ask-message-left">
|
|
30
|
-
{qa.answer
|
|
31
|
+
{qa.answer && (
|
|
31
32
|
<div class="n-ask-message-bubble n-ask-bot-message">
|
|
32
33
|
<div
|
|
33
34
|
class="n-ask-message-answer n-ask-prose prose flow"
|
|
@@ -35,15 +36,16 @@ export function ChatMessages({
|
|
|
35
36
|
/>
|
|
36
37
|
{qa.id && <SourcesList sources={qa.sources} />}
|
|
37
38
|
</div>
|
|
38
|
-
) : (
|
|
39
|
-
<div class="n-ask-message-loader">
|
|
40
|
-
<Icon name="askNetlify" />
|
|
41
|
-
</div>
|
|
42
39
|
)}
|
|
43
40
|
</div>
|
|
44
41
|
</div>
|
|
45
42
|
);
|
|
46
43
|
})}
|
|
44
|
+
{isPreparingAnswer && (
|
|
45
|
+
<div class="n-ask-message-loader">
|
|
46
|
+
<Icon name="askNetlify" />
|
|
47
|
+
</div>
|
|
48
|
+
)}
|
|
47
49
|
</>
|
|
48
50
|
);
|
|
49
51
|
}
|
|
@@ -1,25 +1,33 @@
|
|
|
1
|
+
import Icon from '../../Icon';
|
|
2
|
+
|
|
1
3
|
export function SourcesList({ sources }) {
|
|
2
4
|
if (!sources?.length) return null;
|
|
3
5
|
|
|
4
6
|
return (
|
|
5
7
|
<div className="n-ask-sources">
|
|
6
|
-
<
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
<details>
|
|
9
|
+
<summary>
|
|
10
|
+
<h4 class="n-ask-sources-title">Sources:</h4>
|
|
11
|
+
<Icon name="caretRight" />
|
|
12
|
+
</summary>
|
|
13
|
+
|
|
14
|
+
<ul>
|
|
15
|
+
{sources.map((source, index) => {
|
|
16
|
+
if (!source.title) return;
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
return (
|
|
19
|
+
<li key={index}>
|
|
20
|
+
<a href={source.source_url} target="_blank" rel="noopener noreferrer">
|
|
21
|
+
{source.title}
|
|
22
|
+
{source.subtitle && source.subtitle !== source.title && (
|
|
23
|
+
<span> - {source.subtitle}</span>
|
|
24
|
+
)}
|
|
25
|
+
</a>
|
|
26
|
+
</li>
|
|
27
|
+
);
|
|
28
|
+
})}
|
|
29
|
+
</ul>
|
|
30
|
+
</details>
|
|
23
31
|
</div>
|
|
24
32
|
);
|
|
25
33
|
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { QuerySubmitPayload, AnswerGenerationCompletedPayload } from '@kapaai/react-sdk';
|
|
2
|
+
import { useRef, useCallback } from 'preact/hooks';
|
|
3
|
+
import Analytics from '../../../../analytics/analytics';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Custom hook that tracks analytics events related to Ask Netlify and pushes them to the Google Analytics 4 (GA4) data layer.
|
|
7
|
+
*
|
|
8
|
+
* This hook captures two key interaction events and pushes their payloads to the `dataLayer`, enabling tracking in GA4:
|
|
9
|
+
* - `ask:new_query`: Triggered when a user submits a new question. The payload includes the question text and a thread ID.
|
|
10
|
+
* - `ask:answer_generated`: Triggered when a generated answer is completed. The payload includes the question ID and the length of the answer.
|
|
11
|
+
*
|
|
12
|
+
* Internally, the hook uses an instance of the `Analytics` class to structure and dispatch events. All event payloads are pushed to `window.dataLayer` to be consumed by Google Tag Manager and GA4.
|
|
13
|
+
*
|
|
14
|
+
* @returns {Object} - An object with the following functions:
|
|
15
|
+
* - `handleNewQuery`: Callback to be invoked when a user submits a new question. Sends a `ask:new_query` event with:
|
|
16
|
+
* - `question` (string): The user-submitted question.
|
|
17
|
+
* - `thread_id` (string): A unique identifier for the conversation thread.
|
|
18
|
+
* - `handleAnswer`: Callback to be invoked when a answer is returned. Sends a `ask:answer_generated` event with:
|
|
19
|
+
* - `question_id` (string): The ID linking the question to its answer.
|
|
20
|
+
* - `answer_length` (number): The character length of the generated answer.
|
|
21
|
+
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export function useAskAnalytics() {
|
|
25
|
+
const ref = useRef<Analytics>(new Analytics());
|
|
26
|
+
const analytics = ref.current;
|
|
27
|
+
|
|
28
|
+
const handleNewQuery = useCallback((data: QuerySubmitPayload) => {
|
|
29
|
+
if (!data.question || !data.threadId) return;
|
|
30
|
+
|
|
31
|
+
analytics?.track('ask:new_query', {
|
|
32
|
+
question: data.question,
|
|
33
|
+
thread_id: data.threadId,
|
|
34
|
+
});
|
|
35
|
+
}, []);
|
|
36
|
+
|
|
37
|
+
const handleAnswer = useCallback((data: AnswerGenerationCompletedPayload) => {
|
|
38
|
+
if (!data.questionAnswerId || !data.answer) return;
|
|
39
|
+
|
|
40
|
+
analytics?.track('ask:answer_generated', {
|
|
41
|
+
question_id: data.questionAnswerId,
|
|
42
|
+
answer_length: data.answer.length,
|
|
43
|
+
});
|
|
44
|
+
}, []);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
handleNewQuery,
|
|
48
|
+
handleAnswer,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
@@ -67,7 +67,7 @@ const AskNetlify: FunctionComponent = () => {
|
|
|
67
67
|
y="7.3"
|
|
68
68
|
width="3"
|
|
69
69
|
height="2"
|
|
70
|
-
fill="var(--
|
|
70
|
+
fill="var(--n-ask-netlify-eyelid-color, currentColor)"
|
|
71
71
|
/>
|
|
72
72
|
<rect
|
|
73
73
|
id="ask-netlify-eyelid-right"
|
|
@@ -75,7 +75,7 @@ const AskNetlify: FunctionComponent = () => {
|
|
|
75
75
|
y="7.3"
|
|
76
76
|
width="3"
|
|
77
77
|
height="2"
|
|
78
|
-
fill="var(--
|
|
78
|
+
fill="var(--n-ask-netlify-eyelid-color, currentColor)"
|
|
79
79
|
/>
|
|
80
80
|
</>
|
|
81
81
|
);
|
|
@@ -45,6 +45,16 @@ const SearchInput = (props: Props) => {
|
|
|
45
45
|
placeholder={props.placeholder || 'Search...'}
|
|
46
46
|
onInput={handleInput}
|
|
47
47
|
/>
|
|
48
|
+
<button
|
|
49
|
+
class="n-search-input-clear"
|
|
50
|
+
onClick={() => {
|
|
51
|
+
setValue('');
|
|
52
|
+
reset();
|
|
53
|
+
run('');
|
|
54
|
+
}}
|
|
55
|
+
>
|
|
56
|
+
Clear
|
|
57
|
+
</button>
|
|
48
58
|
</div>
|
|
49
59
|
);
|
|
50
60
|
};
|
|
@@ -33,11 +33,16 @@
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
& .n-search-link-icon :where(#ask-netlify-eyelid-left, #ask-netlify-eyelid-right) {
|
|
36
|
+
--n-ask-netlify-eyelid-color: var(--n-search-color-bg-2);
|
|
36
37
|
animation: askEyeBlink 4s infinite;
|
|
37
38
|
animation-delay: 200ms;
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
41
|
|
|
42
|
+
.n-search-link-icon :where(#ask-netlify-eyelid-left, #ask-netlify-eyelid-right) {
|
|
43
|
+
--n-ask-netlify-eyelid-color: var(--search-bg-color);
|
|
44
|
+
}
|
|
45
|
+
|
|
41
46
|
.n-search-link-title {
|
|
42
47
|
font-weight: var(--font-weight-medium);
|
|
43
48
|
color: var(--color-text-1);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { SearchLinkProps } from '../../types/search.types';
|
|
2
|
-
import {
|
|
2
|
+
import { useSearchAnalytics } from '../../hooks';
|
|
3
3
|
import './SearchLink.css';
|
|
4
4
|
|
|
5
5
|
import Icon from '../../../Icon';
|
|
@@ -12,8 +12,9 @@ const SearchLink = ({
|
|
|
12
12
|
excerpt,
|
|
13
13
|
resource,
|
|
14
14
|
onClick,
|
|
15
|
+
children,
|
|
15
16
|
}: SearchLinkProps) => {
|
|
16
|
-
const { handleClick:
|
|
17
|
+
const { handleClick: trackClick } = useSearchAnalytics();
|
|
17
18
|
|
|
18
19
|
return (
|
|
19
20
|
<Component
|
|
@@ -21,7 +22,7 @@ const SearchLink = ({
|
|
|
21
22
|
href={url}
|
|
22
23
|
onClick={(e: Event) => {
|
|
23
24
|
onClick && onClick();
|
|
24
|
-
|
|
25
|
+
trackClick(e);
|
|
25
26
|
}}
|
|
26
27
|
>
|
|
27
28
|
{icon && (
|
|
@@ -31,7 +32,8 @@ const SearchLink = ({
|
|
|
31
32
|
)}
|
|
32
33
|
<div className="n-search-link-body">
|
|
33
34
|
<p className="n-search-link-title">
|
|
34
|
-
<span className="n-search-link-label">{label}</span>
|
|
35
|
+
{label && <span className="n-search-link-label">{label}</span>}
|
|
36
|
+
{children && <span className="n-search-link-label">{children}</span>}
|
|
35
37
|
{resource && (
|
|
36
38
|
<span className="n-search-link-badge" data-resource={resource}>
|
|
37
39
|
{resource}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import SearchLink from '../components/SearchLink/SearchLink';
|
|
2
|
+
import { THEMES } from '../../../../utils/types';
|
|
3
|
+
import { handleThemeChange, ThemeMap } from '../../../../utils/theme';
|
|
4
|
+
|
|
5
|
+
export default function SearchLinkThemeToggle() {
|
|
6
|
+
return (
|
|
7
|
+
<SearchLink
|
|
8
|
+
as="button"
|
|
9
|
+
icon="lightbulb"
|
|
10
|
+
label="Toggle theme"
|
|
11
|
+
onClick={() => {
|
|
12
|
+
const isDarkTheme = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
13
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
14
|
+
const theme = isDarkTheme ? 'dark' : currentTheme ? currentTheme : 'light';
|
|
15
|
+
theme && handleThemeChange(ThemeMap[theme as THEMES]);
|
|
16
|
+
}}
|
|
17
|
+
/>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
@import '../../../KeyboardKey/KeyboardKey.css';
|
|
2
2
|
@import '../SearchHeading/SearchHeading.css';
|
|
3
3
|
@import '../SearchLink/SearchLink.css';
|
|
4
|
+
@import '../../../../../styles/loaders.css';
|
|
4
5
|
|
|
5
6
|
/* Root Variables */
|
|
6
7
|
.n-search-modal {
|
|
@@ -26,9 +27,10 @@
|
|
|
26
27
|
width: min(var(--n-search-max-w), 100%);
|
|
27
28
|
margin: auto;
|
|
28
29
|
background: var(--n-search-color-bg);
|
|
30
|
+
transition-delay: 200ms;
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
.n-search-modal-ask {
|
|
33
|
+
.n-search-modal[data-state='ask'] {
|
|
32
34
|
--n-search-max-w: 800px;
|
|
33
35
|
}
|
|
34
36
|
|
|
@@ -55,6 +57,7 @@
|
|
|
55
57
|
display: flex;
|
|
56
58
|
width: 100%;
|
|
57
59
|
height: var(--n-search-input-height);
|
|
60
|
+
align-items: center;
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
.n-search-input {
|
|
@@ -72,6 +75,16 @@
|
|
|
72
75
|
color: var(--color-text-1);
|
|
73
76
|
}
|
|
74
77
|
|
|
78
|
+
.n-search-input-clear {
|
|
79
|
+
border: none;
|
|
80
|
+
color: var(--n-search-color-text-secondary);
|
|
81
|
+
font-size: var(--font-size-text-sm);
|
|
82
|
+
margin-inline: var(--space-m);
|
|
83
|
+
height: min-content;
|
|
84
|
+
padding: 0;
|
|
85
|
+
outline-offset: 0.5rem;
|
|
86
|
+
}
|
|
87
|
+
|
|
75
88
|
.n-search-input-wrapper::after,
|
|
76
89
|
.n-search-footer::after {
|
|
77
90
|
content: '';
|
|
@@ -90,6 +103,7 @@
|
|
|
90
103
|
|
|
91
104
|
.n-search-links {
|
|
92
105
|
padding: var(--space-2xs);
|
|
106
|
+
height: 100%;
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
.n-search-label {
|
|
@@ -98,6 +112,16 @@
|
|
|
98
112
|
align-items: center;
|
|
99
113
|
}
|
|
100
114
|
|
|
115
|
+
.n-results-list {
|
|
116
|
+
padding-block-end: var(--space-2xs);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.n-results-info {
|
|
120
|
+
font-family: var(--font-family-display);
|
|
121
|
+
font-size: var(--step--1);
|
|
122
|
+
padding-inline: var(--space-xs);
|
|
123
|
+
}
|
|
124
|
+
|
|
101
125
|
.n-search-label > * {
|
|
102
126
|
display: inline-block;
|
|
103
127
|
margin: 0;
|
|
@@ -111,8 +135,8 @@
|
|
|
111
135
|
}
|
|
112
136
|
|
|
113
137
|
.n-search-term {
|
|
114
|
-
font-weight: var(--font-
|
|
115
|
-
|
|
138
|
+
font-weight: var(--font-bold);
|
|
139
|
+
color: var(--n-search-color-text-secondary);
|
|
116
140
|
}
|
|
117
141
|
|
|
118
142
|
/* Search Results */
|
|
@@ -126,6 +150,12 @@
|
|
|
126
150
|
background: var(--n-search-color-bg);
|
|
127
151
|
}
|
|
128
152
|
|
|
153
|
+
.n-search-feedback {
|
|
154
|
+
height: calc(100% - 70px);
|
|
155
|
+
display: grid;
|
|
156
|
+
place-content: center;
|
|
157
|
+
}
|
|
158
|
+
|
|
129
159
|
/* Footer */
|
|
130
160
|
.n-search-footer {
|
|
131
161
|
position: relative;
|