@ably/ui 7.10.0-dev.45e131a → 7.10.0-dev.caff705
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/core/Meganav/component.js +2 -1
- package/core/Meganav/component.js.LICENSE.txt +7 -0
- package/core/Meganav.jsx +8066 -4938
- package/core/MeganavContentWhyAbly.jsx +1 -1
- package/core/MeganavControl/component.js +1 -1
- package/core/MeganavControlMobileDropdown/component.js +1 -1
- package/core/MeganavItemsMobile.jsx +52 -11
- package/core/MeganavItemsSignedIn.jsx +40 -10
- package/core/MeganavSearch.jsx +40 -10
- package/core/MeganavSearchAutocomplete/component.js +2 -0
- package/core/MeganavSearchAutocomplete/component.js.LICENSE.txt +7 -0
- package/core/MeganavSearchAutocomplete.jsx +117 -0
- package/core/MeganavSearchPanel.jsx +37 -8
- package/core/MeganavSearchSuggestions/component.js +1 -1
- package/core/MeganavSearchSuggestions.jsx +6 -6
- package/core/scripts.js +1 -1
- package/package.json +2 -1
- package/src/core/Meganav/component.js +8 -0
- package/src/core/MeganavControl/component.js +37 -12
- package/src/core/MeganavControlMobileDropdown/component.js +0 -22
- package/src/core/MeganavItemsMobile/component.html.erb +20 -3
- package/src/core/MeganavItemsMobile/component.jsx +21 -3
- package/src/core/MeganavSearch/component.html.erb +3 -2
- package/src/core/MeganavSearch/component.jsx +3 -2
- package/src/core/MeganavSearchAutocomplete/component.html.erb +6 -0
- package/src/core/MeganavSearchAutocomplete/component.js +170 -0
- package/src/core/MeganavSearchAutocomplete/component.jsx +14 -0
- package/src/core/MeganavSearchAutocomplete/component.rb +6 -0
- package/src/core/MeganavSearchPanel/component.html.erb +3 -1
- package/src/core/MeganavSearchPanel/component.jsx +4 -1
- package/src/core/MeganavSearchSuggestions/component.html.erb +5 -5
- package/src/core/MeganavSearchSuggestions/component.js +123 -0
- package/src/core/MeganavSearchSuggestions/component.jsx +6 -6
- package/src/core/remote-blogs-posts.js +1 -1
- package/src/core/remote-session-data.js +1 -1
|
@@ -10,12 +10,13 @@ const MeganavSearch = ({ absUrl }) => {
|
|
|
10
10
|
<button
|
|
11
11
|
type="button"
|
|
12
12
|
data-id="meganav-control"
|
|
13
|
-
|
|
13
|
+
data-control="search"
|
|
14
|
+
className="h-64 w-24 px-24 pr-48 py-20 group focus:outline-none"
|
|
14
15
|
aria-expanded="false"
|
|
15
16
|
aria-controls="panel-search"
|
|
16
17
|
aria-label={`Show Search Panel`}
|
|
17
18
|
>
|
|
18
|
-
<Icon name="icon-gui-search" color="text-cool-black" size="1.5rem" additionalCSS="" />
|
|
19
|
+
<Icon name="icon-gui-search" color="text-cool-black" size="1.5rem" additionalCSS="group-hover:text-gui-hover group-focus:text-gui-focus" />
|
|
19
20
|
</button>
|
|
20
21
|
|
|
21
22
|
<div className="ui-meganav-panel invisible" id="panel-search" data-id="meganav-panel">
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { queryId } from "../dom-query";
|
|
2
|
+
import AddSearchClient from "addsearch-js-client";
|
|
3
|
+
|
|
4
|
+
const init = ({ input, container, listContainer, clear, client }) => {
|
|
5
|
+
client.setAnalyticsTag("Meganav autocomplete");
|
|
6
|
+
client.setThrottleTime(400);
|
|
7
|
+
|
|
8
|
+
const clearResults = () => {
|
|
9
|
+
container.classList.add("hidden");
|
|
10
|
+
listContainer.innerHTML = "";
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const toggleClearBtn = (query) => {
|
|
14
|
+
if ((query || "").length > 0 && clear) {
|
|
15
|
+
clear.classList.remove("invisible");
|
|
16
|
+
} else if (clear) {
|
|
17
|
+
clear.classList.add("invisible");
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const markQueryInSuggestion = (suggestionValue, query) => {
|
|
22
|
+
return suggestionValue.replace(
|
|
23
|
+
query.toLowerCase(),
|
|
24
|
+
`<span class="font-light">${query}</span>`
|
|
25
|
+
);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const navigateToUrl = (q) => (window.location = `/search?q=${q}`);
|
|
29
|
+
|
|
30
|
+
const focusNext = (index) => {
|
|
31
|
+
const nextSuggestion = listContainer.querySelector(
|
|
32
|
+
`[data-suggestion-index="${index + 1}"]`
|
|
33
|
+
);
|
|
34
|
+
if (!nextSuggestion) return;
|
|
35
|
+
nextSuggestion.focus();
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const focusPrevious = (index) => {
|
|
39
|
+
const previousIndex = index - 1;
|
|
40
|
+
|
|
41
|
+
const previousSuggestion = listContainer.querySelector(
|
|
42
|
+
`[data-suggestion-index="${previousIndex}"]`
|
|
43
|
+
);
|
|
44
|
+
if (!previousSuggestion) return;
|
|
45
|
+
previousSuggestion.focus();
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const renderResults =
|
|
49
|
+
(query) =>
|
|
50
|
+
(results = { suggestions: [] }) => {
|
|
51
|
+
toggleClearBtn(query);
|
|
52
|
+
|
|
53
|
+
if (results.suggestions.length === 0) {
|
|
54
|
+
clearResults();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const items = results.suggestions.map((suggestion, index) => {
|
|
59
|
+
const li = document.createElement("li");
|
|
60
|
+
const button = document.createElement("button");
|
|
61
|
+
button.type = "button";
|
|
62
|
+
|
|
63
|
+
button.classList.add(
|
|
64
|
+
"ui-text-menu2",
|
|
65
|
+
"font-medium",
|
|
66
|
+
"p-8",
|
|
67
|
+
"w-full",
|
|
68
|
+
"text-left",
|
|
69
|
+
"rounded",
|
|
70
|
+
"hover:text-gui-hover",
|
|
71
|
+
"focus:outline-gui-focus",
|
|
72
|
+
"hover:bg-light-grey"
|
|
73
|
+
);
|
|
74
|
+
|
|
75
|
+
button.innerHTML = markQueryInSuggestion(suggestion.value, query);
|
|
76
|
+
|
|
77
|
+
button.dataset.suggestionIndex = index;
|
|
78
|
+
|
|
79
|
+
button.addEventListener("click", () => {
|
|
80
|
+
navigateToUrl(suggestion.value);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
button.addEventListener("keydown", (e) => {
|
|
84
|
+
const key = e.key;
|
|
85
|
+
|
|
86
|
+
if (key === "ArrowDown") {
|
|
87
|
+
focusNext(index);
|
|
88
|
+
} else if (key === "ArrowUp" && index - 1 < 0) {
|
|
89
|
+
input.focus();
|
|
90
|
+
} else if (key === "ArrowUp" && index - 1 >= 0) {
|
|
91
|
+
focusPrevious(index);
|
|
92
|
+
} else if (key === "Enter" || key === "Space") {
|
|
93
|
+
navigateToUrl(suggestion.value);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
li.appendChild(button);
|
|
98
|
+
return li;
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
listContainer.innerHTML = "";
|
|
102
|
+
items.forEach((item) => listContainer.appendChild(item));
|
|
103
|
+
container.classList.remove("hidden");
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const keyupHandler = (e) => {
|
|
107
|
+
const query = e.target.value;
|
|
108
|
+
const key = e.key;
|
|
109
|
+
|
|
110
|
+
if (key === "ArrowDown") {
|
|
111
|
+
focusNext(0);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!query) {
|
|
116
|
+
clearResults();
|
|
117
|
+
} else {
|
|
118
|
+
client.suggestions(query, renderResults(query));
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
let clearHandler;
|
|
123
|
+
if (clear) {
|
|
124
|
+
clearHandler = () => {
|
|
125
|
+
input.value = "";
|
|
126
|
+
clear.classList.add("invisible");
|
|
127
|
+
clearResults();
|
|
128
|
+
};
|
|
129
|
+
clear.addEventListener("click", clearHandler);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
input.addEventListener("keyup", keyupHandler);
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
teardown: () => {
|
|
136
|
+
input.removeEventListener("keyup", keyupHandler);
|
|
137
|
+
if (clear) clear.removeEventListener("click", clearHandler);
|
|
138
|
+
},
|
|
139
|
+
clear: () => {
|
|
140
|
+
input.value = "";
|
|
141
|
+
clearResults();
|
|
142
|
+
},
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export default () => {
|
|
147
|
+
const apiKey = document.body.dataset.addSearchApiKey;
|
|
148
|
+
if (!apiKey) {
|
|
149
|
+
console.log(`No AddSearch API key provided, skipping search suggestions.`);
|
|
150
|
+
return [];
|
|
151
|
+
}
|
|
152
|
+
const client = new AddSearchClient(apiKey);
|
|
153
|
+
|
|
154
|
+
return [
|
|
155
|
+
queryId("meganav-search-input"),
|
|
156
|
+
queryId("meganav-mobile-search-input"),
|
|
157
|
+
]
|
|
158
|
+
.filter((i) => i)
|
|
159
|
+
.map((input) => {
|
|
160
|
+
const parent = input.parentNode;
|
|
161
|
+
const container = queryId(
|
|
162
|
+
"meganav-search-autocomplete-container",
|
|
163
|
+
parent
|
|
164
|
+
);
|
|
165
|
+
const listContainer = queryId("meganav-search-autocomplete-list", parent);
|
|
166
|
+
const clear = queryId("meganav-search-input-clear", parent);
|
|
167
|
+
|
|
168
|
+
return init({ input, container, listContainer, client, clear });
|
|
169
|
+
});
|
|
170
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
|
|
3
|
+
const MeganavSearchAutocomplete = () => {
|
|
4
|
+
return (
|
|
5
|
+
<div
|
|
6
|
+
className="absolute w-full mt-8 z-10 hidden shadow-container rounded-lg bg-white border border-mid-grey"
|
|
7
|
+
data-id="meganav-search-autocomplete-container"
|
|
8
|
+
>
|
|
9
|
+
<ol className="m-16" data-id="meganav-search-autocomplete-list"></ol>
|
|
10
|
+
</div>
|
|
11
|
+
);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default MeganavSearchAutocomplete;
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
<form class="flex items-start" action={absUrl("/search")} method="get">
|
|
5
5
|
<div class="relative w-full">
|
|
6
6
|
<%= render(AblyUi::Core::Icon.new(name: "icon-gui-search", size: "1.5rem", color: "text-cool-black", additional_css:"absolute top-12 left-16")) %>
|
|
7
|
-
<input type="search" name="q" class="ui-input pl-48 h-48" placeholder="Search" />
|
|
7
|
+
<input type="search" name="q" class="ui-input pl-48 h-48" placeholder="Search" autocomplete="off" data-id="meganav-search-input" />
|
|
8
|
+
|
|
9
|
+
<%= render(AblyUi::Core::MeganavSearchAutocomplete.new) %>
|
|
8
10
|
</div>
|
|
9
11
|
|
|
10
12
|
<button type="submit" class="ui-btn-secondary ml-8 sm:ml-16 md:ml-24 xl:ml-32">
|
|
@@ -3,6 +3,7 @@ import T from "prop-types";
|
|
|
3
3
|
|
|
4
4
|
import Icon from "../Icon/component.jsx";
|
|
5
5
|
import MeganavSearchSuggestions from "../MeganavSearchSuggestions/component.jsx";
|
|
6
|
+
import MeganavSearchAutocomplete from "../MeganavSearchAutocomplete/component.jsx";
|
|
6
7
|
|
|
7
8
|
const MeganavSearchPanel = ({ absUrl }) => {
|
|
8
9
|
return (
|
|
@@ -12,7 +13,9 @@ const MeganavSearchPanel = ({ absUrl }) => {
|
|
|
12
13
|
<form className="flex items-start" action={absUrl("/search")} method="get">
|
|
13
14
|
<div className="relative w-full">
|
|
14
15
|
<Icon name="icon-gui-search" color="text-cool-black" size="1.5rem" additionalCSS="absolute top-12 left-16" />
|
|
15
|
-
<input type="search" name="q" className="ui-input pl-48 h-48" placeholder="Search" />
|
|
16
|
+
<input type="search" name="q" className="ui-input pl-48 h-48" placeholder="Search" autoComplete="off" data-id="meganav-search-input" />
|
|
17
|
+
|
|
18
|
+
<MeganavSearchAutocomplete />
|
|
16
19
|
</div>
|
|
17
20
|
|
|
18
21
|
<button type="submit" className="ui-btn-secondary ml-8 sm:ml-16 md:ml-24 xl:ml-32">
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
<p class="ui-text-overline2 text-cool-black py-12">Popular pages</p>
|
|
2
2
|
|
|
3
|
-
<div class="flex justify-between items-center overflow-x-scroll">
|
|
3
|
+
<div class="flex justify-between items-center overflow-x-scroll md:overflow-auto">
|
|
4
4
|
<ul class="flex">
|
|
5
5
|
<li class="py-12 pr-8 flex-shrink-0">
|
|
6
|
-
<%= link_to 'How does Ably work?', abs_url("/docs/how-ably-works"), class: "ui-text-p2
|
|
6
|
+
<%= link_to 'How does Ably work?', abs_url("/docs/how-ably-works"), class: "ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus" %>
|
|
7
7
|
</li>
|
|
8
8
|
<li class="py-12 px-8 flex-shrink-0">
|
|
9
|
-
<%= link_to 'Quickstart guide', abs_url("/docs/quick-start-guide"), class: "ui-text-p2
|
|
9
|
+
<%= link_to 'Quickstart guide', abs_url("/docs/quick-start-guide"), class: "ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus" %>
|
|
10
10
|
</li>
|
|
11
11
|
<li class="py-12 px-8 flex-shrink-0">
|
|
12
|
-
<%= link_to 'Publish/Subscribe Messaging', abs_url("/docs/core-features/pubsub"), class: "ui-text-p2
|
|
12
|
+
<%= link_to 'Publish/Subscribe Messaging', abs_url("/docs/core-features/pubsub"), class: "ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus" %>
|
|
13
13
|
</li>
|
|
14
14
|
<li class="py-12 pl-8 flex-shrink-0">
|
|
15
|
-
<%= link_to 'Platform', abs_url("/docs/how-ably-works"), class: "ui-text-p2
|
|
15
|
+
<%= link_to 'Platform', abs_url("/docs/how-ably-works"), class: "ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus" %>
|
|
16
16
|
</li>
|
|
17
17
|
</ul>
|
|
18
18
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { queryId } from "../dom-query";
|
|
2
|
+
|
|
3
|
+
const DRAG_BUFFER = 5;
|
|
4
|
+
|
|
5
|
+
const getTranslateX = (node) =>
|
|
6
|
+
new DOMMatrix(window.getComputedStyle(node).transform).e;
|
|
7
|
+
|
|
8
|
+
const updateTranslateX = (node, value) =>
|
|
9
|
+
(node.style.transform = `translateX(${value}px)`);
|
|
10
|
+
|
|
11
|
+
const dragLeftBoundary = (translateX, threshold) => translateX >= threshold;
|
|
12
|
+
|
|
13
|
+
const dragRightBoundary = (translateX, itemsWidth, windowWidth, threshold) =>
|
|
14
|
+
Math.abs(translateX - windowWidth + threshold) > itemsWidth;
|
|
15
|
+
|
|
16
|
+
const getDistance = (e, touchStartX) =>
|
|
17
|
+
e.changedTouches[0]?.clientX - touchStartX;
|
|
18
|
+
|
|
19
|
+
const withinBuffer = (distance) => Math.abs(distance) < DRAG_BUFFER;
|
|
20
|
+
|
|
21
|
+
const MeganavSearchSuggestions = () => {
|
|
22
|
+
const suggestionsToggle = queryId("meganav-mobile-search-input");
|
|
23
|
+
const suggestions = queryId("meganav-mobile-search-suggestions");
|
|
24
|
+
const list = suggestions.querySelector("ul");
|
|
25
|
+
const listItems = list.querySelectorAll("li");
|
|
26
|
+
|
|
27
|
+
const itemsTotalWidth = Array.from(listItems)
|
|
28
|
+
.map((item) => item.getBoundingClientRect().width)
|
|
29
|
+
.reduce((acc, val) => acc + val, 0);
|
|
30
|
+
|
|
31
|
+
const dragLeft = (distance, threshold) => {
|
|
32
|
+
const currentTranslateX = getTranslateX(list);
|
|
33
|
+
const translateX = Math.round(currentTranslateX + distance);
|
|
34
|
+
if (dragLeftBoundary(translateX, threshold)) return;
|
|
35
|
+
updateTranslateX(list, translateX);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const dragLeftEnd = (distance, threshold) => {
|
|
39
|
+
const currentTranslateX = getTranslateX(list);
|
|
40
|
+
let translateX = Math.round(currentTranslateX + distance);
|
|
41
|
+
|
|
42
|
+
if (dragLeftBoundary(translateX, threshold)) {
|
|
43
|
+
translateX = 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
updateTranslateX(list, translateX);
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const dragRight = (distance, threshold) => {
|
|
50
|
+
const listWidth = list.getBoundingClientRect().width;
|
|
51
|
+
const currentTranslateX = getTranslateX(list);
|
|
52
|
+
const translateX = Math.round(currentTranslateX + distance);
|
|
53
|
+
|
|
54
|
+
if (dragRightBoundary(translateX, itemsTotalWidth, listWidth, threshold)) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
updateTranslateX(list, translateX);
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const dragRightEnd = (distance, threshold) => {
|
|
62
|
+
const listWidth = list.getBoundingClientRect().width;
|
|
63
|
+
const currentTranslateX = getTranslateX(list);
|
|
64
|
+
let translateX = Math.round(currentTranslateX + distance);
|
|
65
|
+
|
|
66
|
+
if (dragRightBoundary(translateX, itemsTotalWidth, listWidth, threshold)) {
|
|
67
|
+
translateX = -(itemsTotalWidth - listWidth + threshold);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
updateTranslateX(list, translateX);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
let touchStartX;
|
|
74
|
+
|
|
75
|
+
const touchstartHandler = (e) => {
|
|
76
|
+
touchStartX = e.touches[0]?.clientX;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const touchmoveHandler = (e) => {
|
|
80
|
+
const distance = getDistance(e, touchStartX);
|
|
81
|
+
if (withinBuffer(distance)) return;
|
|
82
|
+
distance > 0 ? dragLeft(distance, 24) : dragRight(distance, 96);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const touchendHandler = (e) => {
|
|
86
|
+
const distance = getDistance(e, touchStartX);
|
|
87
|
+
if (withinBuffer(distance)) return;
|
|
88
|
+
distance > 0 ? dragLeftEnd(distance, 24) : dragRightEnd(distance, 48);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const focusSuggestionsHandler = () => {
|
|
92
|
+
suggestions.classList.add("max-h-96");
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const blurSuggestionsHandler = (e) => {
|
|
96
|
+
if (e.relatedTarget === suggestions.querySelectorAll("a")[0]) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
suggestions.classList.remove("max-h-96");
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
suggestionsToggle.addEventListener("focus", focusSuggestionsHandler);
|
|
103
|
+
suggestionsToggle.addEventListener("blur", blurSuggestionsHandler);
|
|
104
|
+
suggestions.addEventListener("touchstart", touchstartHandler);
|
|
105
|
+
suggestions.addEventListener("touchmove", touchmoveHandler);
|
|
106
|
+
suggestions.addEventListener("touchend", touchendHandler);
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
teardown: () => {
|
|
110
|
+
suggestionsToggle.removeEventListener("focus", focusSuggestionsHandler);
|
|
111
|
+
suggestionsToggle.removeEventListener("blur", blurSuggestionsHandler);
|
|
112
|
+
suggestions.removeEventListener("touchstart", touchstartHandler);
|
|
113
|
+
suggestions.removeEventListener("touchmove", touchmoveHandler);
|
|
114
|
+
suggestions.removeEventListener("touchend", touchendHandler);
|
|
115
|
+
},
|
|
116
|
+
clear: () => {
|
|
117
|
+
suggestions.classList.remove("max-h-96");
|
|
118
|
+
list.style.transform = `translateX(0px)`;
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
export default MeganavSearchSuggestions;
|
|
@@ -8,25 +8,25 @@ const MeganavSearchSuggestions = ({ absUrl, displaySupportLink }) => {
|
|
|
8
8
|
<>
|
|
9
9
|
<p className="ui-text-overline2 text-cool-black py-12">Popular pages</p>
|
|
10
10
|
|
|
11
|
-
<div className="flex justify-between items-center overflow-
|
|
12
|
-
<ul className="flex">
|
|
11
|
+
<div className="flex justify-between items-center overflow-hidden">
|
|
12
|
+
<ul className="flex transition-transform">
|
|
13
13
|
<li className="py-12 pr-8 flex-shrink-0">
|
|
14
|
-
<a href={absUrl("/docs/how-ably-works")} className="ui-text-p2
|
|
14
|
+
<a href={absUrl("/docs/how-ably-works")} className="ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus">
|
|
15
15
|
How does Ably work?
|
|
16
16
|
</a>
|
|
17
17
|
</li>
|
|
18
18
|
<li className="py-12 px-8 flex-shrink-0">
|
|
19
|
-
<a href={absUrl("/docs/quick-start-guide")} className="ui-text-p2
|
|
19
|
+
<a href={absUrl("/docs/quick-start-guide")} className="ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus">
|
|
20
20
|
Quickstart guide
|
|
21
21
|
</a>
|
|
22
22
|
</li>
|
|
23
23
|
<li className="py-12 px-8 flex-shrink-0">
|
|
24
|
-
<a href={absUrl("/docs/core-features/pubsub")} className="ui-text-p2
|
|
24
|
+
<a href={absUrl("/docs/core-features/pubsub")} className="ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus">
|
|
25
25
|
Publish/Subscribe Messaging
|
|
26
26
|
</a>
|
|
27
27
|
</li>
|
|
28
28
|
<li className="py-12 pl-8 flex-shrink-0">
|
|
29
|
-
<a href={absUrl("/platform")} className="ui-text-p2
|
|
29
|
+
<a href={absUrl("/platform")} className="ui-text-p2 hover:text-gui-hover active:text-gui-active focus:text-gui-focus">
|
|
30
30
|
Platform
|
|
31
31
|
</a>
|
|
32
32
|
</li>
|