@netlify/spark-ui 0.0.44 → 0.0.46-alpha
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/README.md +6 -0
- package/dist/analytics/analytics.d.ts +8 -0
- package/dist/analytics/index.d.ts +4 -0
- package/dist/components/index.d.ts +3 -1
- package/dist/components/preact/SearchModal/hooks/useSearchLogger.d.ts +19 -10
- package/dist/components/preact/SearchModal/hooks/useSearchLogger.js +19 -29
- package/package.json +4 -2
- package/packages/analytics/GoogleAnalytics.astro +9 -0
- package/packages/analytics/GoogleTagManager.astro +14 -0
- package/packages/analytics/analytics.ts +10 -0
- package/packages/analytics/index.ts +5 -0
- package/packages/components/Button.astro +22 -0
- package/packages/components/DeployToNetlifyButton.astro +36 -0
- package/packages/components/Tabs.astro +2 -0
- package/packages/components/Ticker.astro +112 -0
- package/packages/components/index.ts +6 -2
- package/packages/components/preact/SearchModal/hooks/useSearchLogger.ts +27 -18
- package/packages/styles/base.css +0 -1
- package/packages/styles/buttons.css +5 -3
- package/packages/styles/tabs.css +4 -4
- package/packages/styles/tokens.css +10 -0
- package/dist/analytics/logger.d.ts +0 -13
- package/packages/analytics/logger.ts +0 -26
package/README.md
CHANGED
|
@@ -34,3 +34,9 @@ Tokens are converted into CSS variables using the [terrazzo](https://terrazzo.ap
|
|
|
34
34
|
### Rebuild CSS token variables
|
|
35
35
|
|
|
36
36
|
When any files in this tokens directory are updated and merged into the `main` branch, the [build-tokens-css](https://github.com/netlify/netlify-bits/blob/main/.github/workflows/build-tokens-css.yml) GitHub Action is executed. This runs the `build:tokens` script, recreating the [tokens.css](https://github.com/netlify/netlify-bits/blob/main/packages/styles/tokens.css) file with updated CSS variables.
|
|
37
|
+
|
|
38
|
+
The tokens file can also be rebuilt manually with the following command:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
npm run build:tokens
|
|
42
|
+
```
|
|
@@ -5,5 +5,7 @@ export { default as SearchLink } from './preact/SearchModal/components/SearchLin
|
|
|
5
5
|
export { default as SearchHeading } from './preact/SearchModal/components/SearchHeading/SearchHeading';
|
|
6
6
|
export { default as SearchToggle } from './preact/SearchModal/components/SearchToggle/SearchToggle';
|
|
7
7
|
export { default as SearchModal } from './preact/SearchModal/components/SearchModal/SearchModal';
|
|
8
|
-
export { default as Tabs } from './Tabs.astro';
|
|
9
8
|
export { type IconName } from './preact/Icon';
|
|
9
|
+
export { default as Button } from './Button.astro';
|
|
10
|
+
export { default as DeployToNetlifyButton } from './DeployToNetlifyButton.astro';
|
|
11
|
+
export { default as Tabs } from './Tabs.astro';
|
|
@@ -1,17 +1,26 @@
|
|
|
1
1
|
import { SearchState } from '../types/search.types';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Custom hook that logs search-related events for analytics.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
* -
|
|
8
|
-
* -
|
|
5
|
+
* This hook tracks the following search events:
|
|
6
|
+
* - `spark_search:query_changed`: Logs when the search query changes.
|
|
7
|
+
* - `spark_search:no_results`: Logs when no results are found for a search.
|
|
8
|
+
* - `spark_search:result_clicked`: Logs when a search result is clicked.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* It uses an instance of `Analytics` to track these events. The hook listens to changes in the `searchTerm` and `results` properties from the provided `SearchState`.
|
|
11
|
+
*
|
|
12
|
+
* @param {SearchState} [state] - The search state, which includes the current search term and the results of the search.
|
|
13
|
+
* - `state.searchTerm`: The current search query string.
|
|
14
|
+
* - `state.results`: The search results array. If it's empty, the hook will log a no-results event.
|
|
15
|
+
*
|
|
16
|
+
* @returns {Object} - An object with the following function:
|
|
17
|
+
* - `handleClick`: A callback function that should be attached to search result click events. It tracks `spark_search:result_clicked` events with the clicked result's title and URL.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* const { handleClick } = useSearchLogger(searchState);
|
|
21
|
+
*
|
|
22
|
+
* // In your component, attach handleClick to the search result links
|
|
23
|
+
* <a href="result-url" onClick={handleClick}>Search Result</a>
|
|
15
24
|
*/
|
|
16
25
|
export declare function useSearchLogger(state?: SearchState): {
|
|
17
26
|
handleClick: (e: Event) => void;
|
|
@@ -1,43 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
var d = (r, e, c) => e in r ? a(r, e, { enumerable: !0, configurable: !0, writable: !0, value: c }) : r[e] = c;
|
|
3
|
-
var h = (r, e, c) => d(r, typeof e != "symbol" ? e + "" : e, c);
|
|
4
|
-
import { A as L, y as l, q as s } from "../../../../chunks/hooks.module.CNV9mxng.js";
|
|
1
|
+
import { A as k, y as u, q as f } from "../../../../chunks/hooks.module.CNV9mxng.js";
|
|
5
2
|
class g {
|
|
6
|
-
/**
|
|
7
|
-
* @param dl Optional injected dataLayer array. Defaults to window.dataLayer.
|
|
8
|
-
*/
|
|
9
|
-
constructor(e) {
|
|
10
|
-
h(this, "dataLayer");
|
|
11
|
-
this.dataLayer = Array.isArray(e) ? e : window.dataLayer, Array.isArray(this.dataLayer) || (window.dataLayer = [], this.dataLayer = window.dataLayer);
|
|
12
|
-
}
|
|
13
3
|
/**
|
|
14
4
|
* Pushes an event payload into the dataLayer.
|
|
15
|
-
* @param eventName e.g. '
|
|
5
|
+
* @param eventName e.g. 'www:search_query_changed'
|
|
16
6
|
* @param payload additional key/value data
|
|
17
7
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
8
|
+
track(n, c = {}) {
|
|
9
|
+
gtag("event", n, c);
|
|
20
10
|
}
|
|
21
11
|
}
|
|
22
|
-
function
|
|
23
|
-
const
|
|
24
|
-
return
|
|
25
|
-
r != null && r.searchTerm &&
|
|
26
|
-
}, [r == null ? void 0 : r.searchTerm]),
|
|
27
|
-
r != null && r.results && r != null && r.searchTerm && r.results.length === 0 &&
|
|
12
|
+
function T(r) {
|
|
13
|
+
const c = k(new g()).current;
|
|
14
|
+
return u(() => {
|
|
15
|
+
r != null && r.searchTerm && c.track("spark_search:query_changed", { query: r == null ? void 0 : r.searchTerm });
|
|
16
|
+
}, [r == null ? void 0 : r.searchTerm]), u(() => {
|
|
17
|
+
r != null && r.results && r != null && r.searchTerm && r.results.length === 0 && c.track("spark_search:no_results", { query: r == null ? void 0 : r.searchTerm });
|
|
28
18
|
}, [r == null ? void 0 : r.results]), {
|
|
29
|
-
handleClick:
|
|
30
|
-
var
|
|
31
|
-
const
|
|
32
|
-
if (!
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
handleClick: f((h) => {
|
|
20
|
+
var e;
|
|
21
|
+
const l = h.target.closest("a");
|
|
22
|
+
if (!l) return;
|
|
23
|
+
const i = (e = l.querySelector(".n-search-link-label")) == null ? void 0 : e.innerHTML, o = l.getAttribute("href");
|
|
24
|
+
c.track("spark_search:result_clicked", {
|
|
25
|
+
resultTitle: i,
|
|
26
|
+
resultUrl: o
|
|
37
27
|
});
|
|
38
28
|
}, [])
|
|
39
29
|
};
|
|
40
30
|
}
|
|
41
31
|
export {
|
|
42
|
-
|
|
32
|
+
T as useSearchLogger
|
|
43
33
|
};
|
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": "0.0.
|
|
4
|
+
"version": "0.0.46-alpha",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/components/preact/index.js",
|
|
7
7
|
"types": "dist/components/preact/index.d.ts",
|
|
@@ -28,7 +28,8 @@
|
|
|
28
28
|
"./logos/*.svg": "./packages/assets/logos/*.svg",
|
|
29
29
|
"./styles/*.css": "./packages/styles/*.css",
|
|
30
30
|
"./components": "./packages/components/index.ts",
|
|
31
|
-
"./utils": "./packages/utils/index.ts"
|
|
31
|
+
"./utils": "./packages/utils/index.ts",
|
|
32
|
+
"./analytics": "./packages/analytics/index.ts"
|
|
32
33
|
},
|
|
33
34
|
"dependencies": {
|
|
34
35
|
"@preact/preset-vite": "^2.10.1",
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@terrazzo/cli": "^0.6.3",
|
|
44
45
|
"@terrazzo/plugin-css": "^0.6.0",
|
|
46
|
+
"@types/gtag.js": "^0.0.20",
|
|
45
47
|
"tslib": "^2.8.1",
|
|
46
48
|
"typescript": "^5.8.2"
|
|
47
49
|
},
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
<!-- Google tag (gtag.js) -->
|
|
2
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-X2FMMZSSS9"></script>
|
|
3
|
+
<script>
|
|
4
|
+
window.dataLayer = window.dataLayer || [];
|
|
5
|
+
function gtag(){dataLayer.push(arguments);}
|
|
6
|
+
gtag('js', new Date());
|
|
7
|
+
|
|
8
|
+
gtag('config', 'G-X2FMMZSSS9');
|
|
9
|
+
</script>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<!-- Google Tag Manager -->
|
|
2
|
+
<script>
|
|
3
|
+
(function (w, d, s, l, i) {
|
|
4
|
+
w[l] = w[l] || [];
|
|
5
|
+
w[l].push({ 'gtm.start': new Date().getTime(), event: 'gtm.js' });
|
|
6
|
+
var f = d.getElementsByTagName(s)[0],
|
|
7
|
+
j = d.createElement(s),
|
|
8
|
+
dl = l != 'dataLayer' ? '&l=' + l : '';
|
|
9
|
+
j.async = true;
|
|
10
|
+
j.src = 'https://www.googletagmanager.com/gtm.js?id=' + i + dl;
|
|
11
|
+
f.parentNode.insertBefore(j, f);
|
|
12
|
+
})(window, document, 'script', 'dataLayer', 'GTM-T7WNFLD');
|
|
13
|
+
</script>
|
|
14
|
+
<!-- End Google Tag Manager -->
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export default class Analytics {
|
|
2
|
+
/**
|
|
3
|
+
* Pushes an event payload into the dataLayer.
|
|
4
|
+
* @param eventName e.g. 'www:search_query_changed'
|
|
5
|
+
* @param payload additional key/value data
|
|
6
|
+
*/
|
|
7
|
+
track(eventName: string, payload: Record<string, any> = {}): void {
|
|
8
|
+
gtag('event', eventName, payload);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
import '../styles/buttons.css';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
variant?: 'primary' | 'secondary' | 'outline' | 'ghost';
|
|
6
|
+
href?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const {
|
|
10
|
+
class: className,
|
|
11
|
+
variant = 'primary',
|
|
12
|
+
href,
|
|
13
|
+
target = !href?.includes(Astro.url.origin) ? '_blank' : undefined,
|
|
14
|
+
...rest
|
|
15
|
+
} = Astro.props;
|
|
16
|
+
|
|
17
|
+
const Element = href ? 'a' : 'button';
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<Element class:list={['button', className]} data-button-variant={variant} {href} {target} {...rest}>
|
|
21
|
+
<slot />
|
|
22
|
+
</Element>
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
repo: string;
|
|
4
|
+
branch?: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
utm?: string;
|
|
7
|
+
}
|
|
8
|
+
const { repo, branch, path, utm } = Astro.props;
|
|
9
|
+
|
|
10
|
+
const url = new URL('https://app.netlify.com/start/deploy');
|
|
11
|
+
|
|
12
|
+
url.search = new URLSearchParams({
|
|
13
|
+
repository: repo,
|
|
14
|
+
...(branch ? { branch } : { branch: 'main' }),
|
|
15
|
+
...(path ? { create_from_path: path } : {}),
|
|
16
|
+
...(utm ? { utm } : {}),
|
|
17
|
+
}).toString();
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
<a href={url.toString()} rel="nofollow">
|
|
21
|
+
<img
|
|
22
|
+
src="https://www.netlify.com/img/deploy/button.svg"
|
|
23
|
+
alt="Deploy to Netlify"
|
|
24
|
+
width="179"
|
|
25
|
+
height="32"
|
|
26
|
+
/>
|
|
27
|
+
</a>
|
|
28
|
+
|
|
29
|
+
<style>
|
|
30
|
+
a {
|
|
31
|
+
display: inline-grid;
|
|
32
|
+
text-decoration: none;
|
|
33
|
+
border-radius: 0.35em;
|
|
34
|
+
outline-offset: 2px;
|
|
35
|
+
}
|
|
36
|
+
</style>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
---
|
|
2
|
+
interface Props {
|
|
3
|
+
id?: string;
|
|
4
|
+
class?: string;
|
|
5
|
+
gap?: string;
|
|
6
|
+
pauseOnHover?: boolean;
|
|
7
|
+
reverse?: boolean;
|
|
8
|
+
scrollY?: boolean;
|
|
9
|
+
speed?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
id,
|
|
14
|
+
class: className,
|
|
15
|
+
gap,
|
|
16
|
+
pauseOnHover,
|
|
17
|
+
reverse,
|
|
18
|
+
scrollY,
|
|
19
|
+
speed = 30,
|
|
20
|
+
...rest
|
|
21
|
+
} = Astro.props;
|
|
22
|
+
|
|
23
|
+
const css = {
|
|
24
|
+
'n-ticker-animation': scrollY ? 'scroll-y' : 'scroll-x',
|
|
25
|
+
'n-ticker-direction': scrollY ? 'column' : 'row',
|
|
26
|
+
'n-ticker-duration': speed + 's',
|
|
27
|
+
'n-ticker-gap': gap || undefined,
|
|
28
|
+
'n-ticker-hover-play-state': pauseOnHover ? 'paused' : 'running',
|
|
29
|
+
'n-ticker-mask-direction': scrollY ? 'to bottom' : 'to right',
|
|
30
|
+
};
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
<div id={id} class:list={['ticker', reverse && 'ticker-reverse', className]} {...rest}>
|
|
34
|
+
{
|
|
35
|
+
Array.from({ length: 2 }, (_, index) => (
|
|
36
|
+
<div class="ticker-content" aria-hidden={index !== 0 ? 'true' : undefined}>
|
|
37
|
+
<slot />
|
|
38
|
+
</div>
|
|
39
|
+
))
|
|
40
|
+
}
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<style define:vars={css}>
|
|
44
|
+
.ticker {
|
|
45
|
+
--_gap: var(--n-ticker-gap, 1rem);
|
|
46
|
+
display: flex;
|
|
47
|
+
flex-direction: var(--n-ticker-direction, row);
|
|
48
|
+
overflow: hidden;
|
|
49
|
+
user-select: none;
|
|
50
|
+
gap: var(--_gap);
|
|
51
|
+
|
|
52
|
+
/** Fade out along edges */
|
|
53
|
+
mask-image: linear-gradient(
|
|
54
|
+
var(--n-ticker-mask-direction, to right),
|
|
55
|
+
rgba(0, 0, 0, 0) 0%,
|
|
56
|
+
rgba(0, 0, 0, 1) 15%,
|
|
57
|
+
rgba(0, 0, 0, 1) 85%,
|
|
58
|
+
rgba(0, 0, 0, 0) 100%
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.ticker-content {
|
|
63
|
+
flex-shrink: 0;
|
|
64
|
+
display: flex;
|
|
65
|
+
flex-direction: var(--n-ticker-direction, row);
|
|
66
|
+
justify-content: space-around;
|
|
67
|
+
min-width: 100%;
|
|
68
|
+
gap: var(--_gap);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Animation */
|
|
72
|
+
.ticker-content {
|
|
73
|
+
animation-name: var(--n-ticker-animation, scroll-x);
|
|
74
|
+
animation-duration: var(--n-ticker-duration, 30s);
|
|
75
|
+
animation-play-state: var(--n-ticker-play-state, running);
|
|
76
|
+
animation-timing-function: linear;
|
|
77
|
+
animation-iteration-count: infinite;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.ticker:hover .ticker-content {
|
|
81
|
+
animation-play-state: var(--n-ticker-hover-play-state, running);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ticker-reverse .ticker-content {
|
|
85
|
+
animation-direction: reverse;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@keyframes scroll-x {
|
|
89
|
+
from {
|
|
90
|
+
transform: translateX(0);
|
|
91
|
+
}
|
|
92
|
+
to {
|
|
93
|
+
transform: translateX(calc(-100% - var(--_gap)));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@keyframes scroll-y {
|
|
98
|
+
from {
|
|
99
|
+
transform: translateY(0);
|
|
100
|
+
}
|
|
101
|
+
to {
|
|
102
|
+
transform: translateY(calc(-100% - var(--_gap)));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* Pause animation when reduced-motion is set */
|
|
107
|
+
@media (prefers-reduced-motion: reduce) {
|
|
108
|
+
.ticker-content {
|
|
109
|
+
animation-play-state: paused !important;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
</style>
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Preact components
|
|
1
2
|
export { default as KeyboardKey } from './preact/KeyboardKey/KeyboardKey';
|
|
2
3
|
export { default as Modal } from './preact/Modal/Modal';
|
|
3
4
|
export { default as Icon } from './preact/Icon';
|
|
@@ -5,6 +6,9 @@ export { default as SearchLink } from './preact/SearchModal/components/SearchLin
|
|
|
5
6
|
export { default as SearchHeading } from './preact/SearchModal/components/SearchHeading/SearchHeading';
|
|
6
7
|
export { default as SearchToggle } from './preact/SearchModal/components/SearchToggle/SearchToggle';
|
|
7
8
|
export { default as SearchModal } from './preact/SearchModal/components/SearchModal/SearchModal';
|
|
8
|
-
export { default as Tabs } from './Tabs.astro';
|
|
9
|
-
|
|
10
9
|
export { type IconName } from './preact/Icon';
|
|
10
|
+
|
|
11
|
+
// Astro components
|
|
12
|
+
export { default as Button } from './Button.astro';
|
|
13
|
+
export { default as DeployToNetlifyButton } from './DeployToNetlifyButton.astro';
|
|
14
|
+
export { default as Tabs } from './Tabs.astro';
|
|
@@ -1,30 +1,39 @@
|
|
|
1
1
|
import type { SearchState } from '../types/search.types';
|
|
2
2
|
import { useEffect, useRef, useCallback } from 'preact/hooks';
|
|
3
|
-
import
|
|
3
|
+
import Analytics from '../../../../analytics/analytics';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Custom hook that logs search-related events for analytics.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* -
|
|
10
|
-
* -
|
|
11
|
-
* -
|
|
8
|
+
* This hook tracks the following search events:
|
|
9
|
+
* - `spark_search:query_changed`: Logs when the search query changes.
|
|
10
|
+
* - `spark_search:no_results`: Logs when no results are found for a search.
|
|
11
|
+
* - `spark_search:result_clicked`: Logs when a search result is clicked.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* It uses an instance of `Analytics` to track these events. The hook listens to changes in the `searchTerm` and `results` properties from the provided `SearchState`.
|
|
14
|
+
*
|
|
15
|
+
* @param {SearchState} [state] - The search state, which includes the current search term and the results of the search.
|
|
16
|
+
* - `state.searchTerm`: The current search query string.
|
|
17
|
+
* - `state.results`: The search results array. If it's empty, the hook will log a no-results event.
|
|
18
|
+
*
|
|
19
|
+
* @returns {Object} - An object with the following function:
|
|
20
|
+
* - `handleClick`: A callback function that should be attached to search result click events. It tracks `spark_search:result_clicked` events with the clicked result's title and URL.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* const { handleClick } = useSearchLogger(searchState);
|
|
24
|
+
*
|
|
25
|
+
* // In your component, attach handleClick to the search result links
|
|
26
|
+
* <a href="result-url" onClick={handleClick}>Search Result</a>
|
|
18
27
|
*/
|
|
19
28
|
|
|
20
|
-
|
|
21
29
|
export function useSearchLogger(state?: SearchState) {
|
|
22
|
-
const
|
|
30
|
+
const ref = useRef<Analytics>(new Analytics());
|
|
31
|
+
const analytics = ref.current;
|
|
23
32
|
|
|
24
33
|
// Log search query
|
|
25
34
|
useEffect(() => {
|
|
26
35
|
if (!state?.searchTerm) return
|
|
27
|
-
|
|
36
|
+
analytics.track('spark_search:query_changed', { query: state?.searchTerm });
|
|
28
37
|
}, [state?.searchTerm]);
|
|
29
38
|
|
|
30
39
|
// Log zero‑hit searches
|
|
@@ -32,7 +41,7 @@ export function useSearchLogger(state?: SearchState) {
|
|
|
32
41
|
if (!state?.results) return
|
|
33
42
|
|
|
34
43
|
if (state?.searchTerm && state.results.length === 0) {
|
|
35
|
-
|
|
44
|
+
analytics.track('spark_search:no_results', { query: state?.searchTerm });
|
|
36
45
|
}
|
|
37
46
|
}, [state?.results]);
|
|
38
47
|
|
|
@@ -44,9 +53,9 @@ export function useSearchLogger(state?: SearchState) {
|
|
|
44
53
|
const label = anchor.querySelector('.n-search-link-label')?.innerHTML
|
|
45
54
|
const url = anchor.getAttribute('href')
|
|
46
55
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
analytics.track('spark_search:result_clicked', {
|
|
57
|
+
resultTitle: label,
|
|
58
|
+
resultUrl: url,
|
|
50
59
|
});
|
|
51
60
|
}, []);
|
|
52
61
|
|
package/packages/styles/base.css
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
button,
|
|
2
2
|
.button {
|
|
3
3
|
appearance: none;
|
|
4
|
+
position: relative;
|
|
4
5
|
cursor: pointer;
|
|
5
6
|
display: inline-flex;
|
|
6
7
|
align-items: center;
|
|
7
8
|
gap: var(--space-xs);
|
|
8
|
-
padding: var(--space-
|
|
9
|
+
padding: var(--space-xs) var(--space-m);
|
|
9
10
|
font-size: var(--font-size-0);
|
|
10
11
|
font-family: var(--font-body);
|
|
11
12
|
font-weight: var(--font-semibold);
|
|
@@ -21,6 +22,7 @@ button,
|
|
|
21
22
|
flex: none;
|
|
22
23
|
height: var(--button-icon-size, 1.2cap);
|
|
23
24
|
width: auto;
|
|
25
|
+
fill: currentColor;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/* Button states */
|
|
@@ -46,13 +48,13 @@ button,
|
|
|
46
48
|
border-color: var(--color-brand-alt);
|
|
47
49
|
}
|
|
48
50
|
|
|
49
|
-
&[data-button-variant='
|
|
51
|
+
&[data-button-variant='ghost'] {
|
|
50
52
|
color: currentColor;
|
|
51
53
|
background-color: transparent;
|
|
52
54
|
border-color: transparent;
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
&[data-button-variant='
|
|
57
|
+
&[data-button-variant='outline'] {
|
|
56
58
|
color: currentColor;
|
|
57
59
|
background-color: transparent;
|
|
58
60
|
border-color: currentColor;
|
package/packages/styles/tabs.css
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
1
|
+
[role='tablist'] {
|
|
2
2
|
display: flex;
|
|
3
3
|
isolation: isolate;
|
|
4
4
|
border: var(--_border);
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
[role='tab'] {
|
|
8
8
|
position: relative;
|
|
9
9
|
border-color: transparent;
|
|
10
10
|
|
|
@@ -14,12 +14,12 @@
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
[role='tab'][aria-selected='true'] {
|
|
18
18
|
/* Bring selected tab to layer above others to avoid clipping outline styles */
|
|
19
19
|
z-index: 1;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
[role='tabpanel'] {
|
|
23
23
|
border: var(--_border);
|
|
24
24
|
border-block-start: unset;
|
|
25
25
|
padding: var(--_padding);
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
--color-bg-3: var(--neutral-200);
|
|
19
19
|
--color-border: var(--neutral-200);
|
|
20
20
|
--color-brand: var(--blue-700);
|
|
21
|
+
--color-brand-alt: var(--teal-200);
|
|
21
22
|
--color-disabled-bg: var(--neutral-200);
|
|
22
23
|
--color-disabled-border: var(--neutral-400);
|
|
23
24
|
--color-disabled-text: var(--neutral-400);
|
|
@@ -32,6 +33,7 @@
|
|
|
32
33
|
--color-text-2: var(--neutral-600);
|
|
33
34
|
--color-text-3: var(--neutral-400);
|
|
34
35
|
--color-text-accent: var(--teal-000);
|
|
36
|
+
--color-text-inverse: var(--neutral-100);
|
|
35
37
|
--color-warning-bg: var(--gold-100);
|
|
36
38
|
--color-warning-border: var(--gold-300);
|
|
37
39
|
--color-warning-text: var(--gold-800);
|
|
@@ -149,6 +151,7 @@
|
|
|
149
151
|
--color-bg-3: var(--neutral-200);
|
|
150
152
|
--color-border: var(--neutral-200);
|
|
151
153
|
--color-brand: var(--blue-700);
|
|
154
|
+
--color-brand-alt: var(--teal-200);
|
|
152
155
|
--color-disabled-bg: var(--neutral-200);
|
|
153
156
|
--color-disabled-border: var(--neutral-400);
|
|
154
157
|
--color-disabled-text: var(--neutral-400);
|
|
@@ -163,6 +166,7 @@
|
|
|
163
166
|
--color-text-2: var(--neutral-600);
|
|
164
167
|
--color-text-3: var(--neutral-400);
|
|
165
168
|
--color-text-accent: var(--teal-000);
|
|
169
|
+
--color-text-inverse: var(--neutral-100);
|
|
166
170
|
--color-warning-bg: var(--gold-100);
|
|
167
171
|
--color-warning-border: var(--gold-300);
|
|
168
172
|
--color-warning-text: var(--gold-800);
|
|
@@ -184,6 +188,7 @@
|
|
|
184
188
|
--color-bg-3: var(--neutral-200);
|
|
185
189
|
--color-border: var(--neutral-200);
|
|
186
190
|
--color-brand: var(--blue-700);
|
|
191
|
+
--color-brand-alt: var(--teal-200);
|
|
187
192
|
--color-disabled-bg: var(--neutral-200);
|
|
188
193
|
--color-disabled-border: var(--neutral-400);
|
|
189
194
|
--color-disabled-text: var(--neutral-400);
|
|
@@ -198,6 +203,7 @@
|
|
|
198
203
|
--color-text-2: var(--neutral-600);
|
|
199
204
|
--color-text-3: var(--neutral-400);
|
|
200
205
|
--color-text-accent: var(--teal-000);
|
|
206
|
+
--color-text-inverse: var(--neutral-100);
|
|
201
207
|
--color-warning-bg: var(--gold-100);
|
|
202
208
|
--color-warning-border: var(--gold-300);
|
|
203
209
|
--color-warning-text: var(--gold-800);
|
|
@@ -219,6 +225,7 @@
|
|
|
219
225
|
--color-bg-3: var(--neutral-600);
|
|
220
226
|
--color-border: var(--neutral-200);
|
|
221
227
|
--color-brand: var(--teal-200);
|
|
228
|
+
--color-brand-alt: var(--blue-700);
|
|
222
229
|
--color-disabled-bg: var(--neutral-600);
|
|
223
230
|
--color-disabled-border: var(--neutral-400);
|
|
224
231
|
--color-disabled-text: var(--neutral-400);
|
|
@@ -233,6 +240,7 @@
|
|
|
233
240
|
--color-text-2: var(--neutral-300);
|
|
234
241
|
--color-text-3: var(--neutral-500);
|
|
235
242
|
--color-text-accent: var(--teal-600);
|
|
243
|
+
--color-text-inverse: var(--teal-800);
|
|
236
244
|
--color-warning-bg: var(--gold-200);
|
|
237
245
|
--color-warning-border: var(--gold-400);
|
|
238
246
|
--color-warning-text: var(--gold-900);
|
|
@@ -254,6 +262,7 @@
|
|
|
254
262
|
--color-bg-3: var(--neutral-600);
|
|
255
263
|
--color-border: var(--neutral-200);
|
|
256
264
|
--color-brand: var(--teal-200);
|
|
265
|
+
--color-brand-alt: var(--blue-700);
|
|
257
266
|
--color-disabled-bg: var(--neutral-600);
|
|
258
267
|
--color-disabled-border: var(--neutral-400);
|
|
259
268
|
--color-disabled-text: var(--neutral-400);
|
|
@@ -268,6 +277,7 @@
|
|
|
268
277
|
--color-text-2: var(--neutral-300);
|
|
269
278
|
--color-text-3: var(--neutral-500);
|
|
270
279
|
--color-text-accent: var(--teal-600);
|
|
280
|
+
--color-text-inverse: var(--teal-800);
|
|
271
281
|
--color-warning-bg: var(--gold-200);
|
|
272
282
|
--color-warning-border: var(--gold-400);
|
|
273
283
|
--color-warning-text: var(--gold-900);
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export declare class Logger {
|
|
2
|
-
private dataLayer;
|
|
3
|
-
/**
|
|
4
|
-
* @param dl Optional injected dataLayer array. Defaults to window.dataLayer.
|
|
5
|
-
*/
|
|
6
|
-
constructor(dl?: any[]);
|
|
7
|
-
/**
|
|
8
|
-
* Pushes an event payload into the dataLayer.
|
|
9
|
-
* @param eventName e.g. 'searchResultClick'
|
|
10
|
-
* @param payload additional key/value data
|
|
11
|
-
*/
|
|
12
|
-
push(eventName: string, payload?: Record<string, any>): void;
|
|
13
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
export class Logger {
|
|
2
|
-
private dataLayer: any[];
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @param dl Optional injected dataLayer array. Defaults to window.dataLayer.
|
|
6
|
-
*/
|
|
7
|
-
constructor(dl?: any[]) {
|
|
8
|
-
// Use injected or global dataLayer
|
|
9
|
-
this.dataLayer = Array.isArray(dl) ? dl : (window as any).dataLayer;
|
|
10
|
-
|
|
11
|
-
// If for some reason it's not an array, initialize it
|
|
12
|
-
if (!Array.isArray(this.dataLayer)) {
|
|
13
|
-
(window as any).dataLayer = [];
|
|
14
|
-
this.dataLayer = (window as any).dataLayer;
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Pushes an event payload into the dataLayer.
|
|
20
|
-
* @param eventName e.g. 'searchResultClick'
|
|
21
|
-
* @param payload additional key/value data
|
|
22
|
-
*/
|
|
23
|
-
push(eventName: string, payload: Record<string, any> = {}): void {
|
|
24
|
-
this.dataLayer.push({ event: eventName, ...payload });
|
|
25
|
-
}
|
|
26
|
-
}
|