AnnexUI
AnnexUI is a MIT licensed UI library for Typesense.
It's designed to be a clean, UX focused library that gets developers up and
running in a few minutes. Include a link to the CDN-hosted JavaScript bundle,
set your Typesense cluster settings, and then press ⌘k to see Annex up and
running.
It's currently in early-development.
Quick links
- Quick demos
- Quick preview
- Quick intro
- Quick start
- Config
- Attribute-based events
- Events
- Methods
- Templates
- Additional notes
Quick demos
- Inline layout: https://annexsearch.com/#inline-demo-01
- Modal layout: https://annexsearch.com/#modal-demo-01
- Left panel layout: https://annexsearch.com/#panel-left-demo-01
- Right panel layout: https://annexsearch.com/#panel-right-demo-01
- Inline layout (dark mode): https://annexsearch.com/#inline-demo-02
- Multiple modal layouts: https://annexsearch.com/#modal-demo-03
- Inline layout (using
sku-v0.1.0templates): https://annexsearch.com/#inline-demo-05
Quick preview
Quick intro
Annex uses Web Components to encapsulate and restrict JavaScript and CSS scope. The fundamental concepts to understand are:
- The UI will be encapsulated within a
<annex-search-widget>HTML tag- In docs, this will often be referenced as the
$annexSearchWidgetvariable
- In docs, this will often be referenced as the
- This web component exposes a number of methods that developers can use to
interact with it (e.g.
show,hide,focus, etc)
💪 Out the box, Annex supports the following:
- Four (4) different layouts (
'inline','modal','panel-left'and'panel-right') - Dark mode
- Responsive layouts
- Event handling
🎨 Style considerations:
- There are currently 65+ CSS variables that can be overriden
- These control layouts, dimensions, colors, fonts and more
🔒 Privacy and data considerations:
- No data is stored in cookies or localStorage
- Queries are only sent to your Typesense cluster; no middleware is present
- Images are base64-encoded and not requested remotely
- The remote CDN used is jsDelivr
⚡️ Performance considerations:
- Annex currently employs a "Last in, first out" approach to queries
- This means that if users type quickly, previous searches will be aborted to lower server and bandwidth burden
- Analytics can be facilitate through Events
Quick start
Below you'll find a few lines to get up and running quickly (remember to swap in your Typesense cluster settings).
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/AnnexLabs/AnnexUI@0.1.14-stable/dist/bundle.min.js" defer></script> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { let $annexSearchWidget = document.createElement('annex-search-widget'); $annexSearchWidget.setMutator('typesenseSearchResponse', function(typesenseSearchRequest) { let hits = typesenseSearchRequest.getResponse().hits; for (let hit of hits) { hit.document.uri = 'https://' + (hit.document.hostname) + (hit.document.relativeURL); hit.document.imageUrl = hit.document.thumbnailURL; } }); $annexSearchWidget.setConfig({ chips: { idle: ['aws', 'fotos', 'layers', 'google', 'figma'] }, cluster: { apiKey: '606o4DjqwBhFNZ2NKgSiqFsqdMNCbcKx', collectionName: 'prod:::tpclwpqz62hq:::crawlerResourceSearch:::v0.1.0', hostname: 'b3487cx0hrdu1y6kp-1.a1.typesense.net', presetName: 'prod:::tcprkee8nnvp:::crawlerResourceSearch:::v0.1.0', }, keyboardShortcut: '⌘k', layout: 'modal', }); $annexSearchWidget.mount(); }); </script>
Config
Below you'll find a high-level breakdown of configuration options that can be changed. See Config overriding for details on how to do so.
| Key | Type | Required | Default value | Description |
|---|---|---|---|---|
| $container | null || EventTarget |
❌ | null |
The EventTarget that the $annexSearchWidget element should be appended to. |
| autoFocusOnScroll | Boolean |
❌ | true |
Whether the web component should receive focus when it's scrolled into the viewport. |
| callbacks | Object |
❌ | (see Events) | Map of callback functions that will be triggered upon certain events. |
| chips | Object |
❌ | (n/a) | Map of arrays of "chip" strings. |
| chips.idle | Array |
❌ | [] |
Array of strings which are shown as the default chips when the web component is shown. |
| cluster | Object |
✅ | (n/a) | Map of Typesense related cluster auth properties. |
| cluster.apiKey | String |
✅ | null |
Typesense cluster search API key. |
| cluster.collectionName | String |
✅ | null |
Typesense cluster collection name. |
| cluster.hostname | String |
✅ | null |
Typesense cluster hostname. |
| cluster.presetName | null || String |
❌ | null |
Typesense cluster search preset name. |
| colorScheme | String |
❌ | 'auto' |
The color scheme for Annex. Can be: 'auto', 'light' or 'dark'. |
| copy | Object |
❌ | Object |
Map of copy used in different templates. |
| debug | Boolean |
❌ | false |
Whether debugging information should be logged to console. |
| env | String |
❌ | 'prod' |
String which can be set to provide insights on the env you're operating in. |
| highlightTagName | String |
❌ | 'MARK' |
The EventTarget that should be rendered around query matches. |
| id | String |
❌ | null |
The id of the instance. Useful for differentiating between multiple $annexSearchWidget instances. |
| keyboardShortcut | null || String |
❌ | '⌘k' |
The keyboard shortcut that should be used to toggle Annex (does not apply to inline instances). |
| layout | String |
❌ | 'modal' |
The layout for Annex. Can be: 'inline', 'modal', 'panel-left' or 'panel-right'. |
| modalAlignment | String |
❌ | 'top' |
Whether a modal instance of Annex should be fixed to the top, or float in the middle of the viewport. |
| resources | Object |
❌ | Object |
Map of css URLs that are loaded for an $annexSearchWidget. |
| searchOptions | Object |
✅ | Object |
Map of search options that are passed in a Typesense search query. |
| searchRequestMethod | String |
❌ | 'lifo' |
The type of search handling. Currently limited to just lifo (last in first out) |
| showOverlay | Boolean |
❌ | true |
Whether the overlay EventTarget should be rendered. |
| templates | Object |
❌ | (see Templates) | Map of templates that should be used in Annex rendering. |
Config overriding
Below are examples showing how configuration options can be set. Worth noting is
the flexibility around the key (e.g. can contain a . as a delimter).
$('annex-search-widget').setConfig('$container', document.body); $('annex-search-widget').setConfig('searchOptions.snippet_threshold', 20); $('annex-search-widget').setConfig('searchOptions', { snippet_threshold: 20 });
Attribute-based events
Below is a list of supported attributes, which when found on an element, will trigger behaviour against the related web component.
Notes:
- When there is a single web component on the page, the
iddoes not need to be specified in the attribute value - When there are multiple web components on the page, the
idmust be specified in the attribute value - Not all layouts support all interactions. For example, an
inline$annexSearchWidgetcannot be "shown" or "hidden"
Behaviour
| Behaviour name | Example | Layout Support | Description |
|---|---|---|---|
| clear | <a data-annex-search="clear">test</a> |
All | Clears the search query input value from the $annexSearchWidget. |
| disable | <a data-annex-search="disable">test</a> |
All | Disables the $annexSearchWidget if it's currently enabled. |
| enable | <a data-annex-search="enable">test</a> |
All | Enables the $annexSearchWidget if it's currently disabled. |
| focus | <a data-annex-search="focus">test</a> |
All | Focuses on $annexSearchWidget if it's currently showing. |
| hide | <a data-annex-search="hide">test</a> |
All (except 'inline') |
Hides the $annexSearchWidget if it's not currently hidden. |
| query | <a data-annex-search-query="apple">test</a> |
All | Performs a query based on the attribute value. |
| show | <a data-annex-search="show">test</a> |
All (except 'inline') |
Shows the $annexSearchWidget if it's currently hidden. |
| toggle | <a data-annex-search="toggle">test</a> |
All (except 'inline') |
Shows or hides the $annexSearchWidget depending on it's current state. |
Examples (without id)
<a href="#test" data-annex-search="clear">clear the $annexSearchWidget $input</a> <a href="#test" data-annex-search="disable">disable the $annexSearchWidget</a> <a href="#test" data-annex-search="enable">enable the $annexSearchWidget</a> <a href="#test" data-annex-search="focus">focus on the $annexSearchWidget</a> <a href="#test" data-annex-search="hide">hide the $annexSearchWidget</a> <a href="#test" data-annex-search-query="search query">show the $annexSearchWidget, insert query and search</a> <a href="#test" data-annex-search="show">show the $annexSearchWidget</a> <a href="#test" data-annex-search="toggle">toggle the $annexSearchWidget</a>
Examples (with id)
<a href="#test" data-annex-search="{id}:clear">clear the $annexSearchWidget $input</a> <a href="#test" data-annex-search="{id}:disable">disable on the $annexSearchWidget</a> <a href="#test" data-annex-search="{id}:enable">enable on the $annexSearchWidget</a> <a href="#test" data-annex-search="{id}:focus">focus on the $annexSearchWidget</a> <a href="#test" data-annex-search="{id}:hide">hide the $annexSearchWidget</a> <a href="#test" data-annex-search-query="{id}:search query">show the $annexSearchWidget, insert query and search</a> <a href="#test" data-annex-search="{id}:show">show the $annexSearchWidget</a> <a href="#test" data-annex-search="{id}:toggle">toggle the $annexSearchWidget</a>
Events
Events can be processed either through the config options set via setConfig or
via native event listeners (e.g.
$annexSearchWidget.addEventListener('result.click', handler)). Below you'll
find a list of supported events.
When events are processed through native event listeners, arguments are
accessible via the event.detail property.
| Event Name | Description |
|---|---|
result.click |
Dispatched when a result is clicked. |
result.copy |
Dispatched when the user attempts to copy to their clipboard (e.g. via Command + C). |
result.focus |
Dispatched when a result is focused. |
results.empty |
Dispatched when a search results in an empy state (no results found). |
results.error |
Dispatched when a search results in an error. |
results.idle |
Dispatched when the idle state for results is shown (e.g. the user deletes a previously searched query from the input). |
results.loaded |
Dispatched when a search results in an results being shown. |
root.hide |
Dispatched when the $annexSearchWidget is hidden. |
root.show |
Dispatched when the $annexSearchWidget is shown. |
root.toggle |
Dispatched when the $annexSearchWidget is toggled. |
Example "native" event handling
$('annex-search-widget').addEventListener('root.show', function(customEvent) { console.log(customEvent); console.log(customEvent.detail); });
Methods
Below you'll find methods that can be called against an $annexSearchWidget
reference. While you'll find other public events available when inspecting the
element, only the ones below are currently supported.
| Method name | Return value | Description |
|---|---|---|
disable |
Promise |
Disables the $annexSearchWidget (if enabled). |
enable |
Promise |
Enables the $annexSearchWidget (if disabled). |
focus |
Promise |
Focuses on the $annexSearchWidget search query input. |
getConfig |
Boolean |
Returns an object representing the config options for the $annexSearchWidget. |
hide |
Promise |
Hides the $annexSearchWidget if it's currently showing. |
mount |
Promise |
Mounts the $annexSearchWidget to the $container config option. |
query |
Boolean |
Shows the $annexSearchWidget if it's currently hidden, and performs a query (based on the passed in value). |
ready |
Promise |
Returns a promise when the $annexSearchWidget is ready for interaction. |
setConfig |
Boolean |
Sets $annexSearchWidget config options. |
setMutator |
Boolean |
Sets a mutator Function which is used to modify data. Useful for Typesense response normalization. |
show |
Promise |
Shows the $annexSearchWidget if it's currently hidden. |
showing |
Boolean |
Returns whether or not the $annexSearchWidget is currently showing. |
showToast |
BaseView |
Shows a toast in the search UI, accepting title, message and hideTimeoutDuration params. |
toggle |
Promise |
Shows or hides the $annexSearchWidget depending on it's currently state. |
Templates
While Annex does it's best to work (to some degree) "out of the box", any meaningful implementation will require developers to define their own templates.
In practice, this means passing in String values for the different web
component states (along with defining your own styles).
This part of Annex is a work-in-progress, and more documentation will be added soon, however until then use the table below as a guide to the available template keys that can be overridden.
| Config property key | Description | data properties |
|---|---|---|
root |
The "root" of the search UI. Broadly, a container for other containers. | config |
body |
Contains the body of search results, including errors, idle states and empty result states. | config |
chip |
An anchor element that when clicked performs a query. | config, chip |
errorBody |
Element that communicates an error took place during search. | config |
idleBody |
Element that communicates that no search has taken place yet. | config |
emptyResultsBody |
Element that communicates no matching search results were found. | config |
foundResultsBody |
Element that contains a list of results. | config |
resultFoundResultsBody |
The result element itself. This is likely what you want to start with. | config, hit |
resultsBody |
A container for the broad concept of results. | config |
toast |
An element to communicate an alert. | config, title, message |
brandingBarFooter |
Element that communicates the Annex brand. | config |
footer |
Container element for footer elements. | config |
statusBarFooter |
Element that communicates some status around the search UX. | config |
fieldHeader |
Element that contains the keyboard shortcut, input field, hide button and spinner. | config |
timer |
An element that counts down from a certain number of seconds. | config, remaining |
header |
Element that contains the $fieldHeader element. |
config |
metaBarHeader |
Element that communicates meta data about a search (if any). | config, typesenseSearchResponse |
Additional notes
Below are some additional notes that may prove useful to developers. Feel free
to open a PR on this README.md file with anything else you think would be
useful to others.
Styling (using vars)
While docs related to styling will come soon, below you'll see a quick example of how styling can be executed against an Annex instance.
<style type="text/css"> annex-search-widget { --annex-search-show-panel-duration: 2000ms; } annex-search-widget::part(result-content-price) { background-color: rgba(0, 0, 0, 0.60); color: #ffffff; } @media (prefers-color-scheme: dark) { annex-search-widget::part(result-content-price) { background-color: #ffffff; color: rgba(0, 0, 0, 0.60); } } </style>
Vendors / Dependencies
Currently, the only 3rd party vendor included in Annex is Lodash v4.17.21. Lodash is used to facilitate more robust templating logic.
This minification of vendors / dependencies is by design, in large part to lower the likelihood of supply chain attacks.
Preset without config options
The code below shows the simplest example of Annex. By specifying a presetName
value, your Typesense cluster knows how to perform the query. Along with that,
the default config options (e.g. '⌘k' for the keyboard shortcut, layout being
'modal', etc.) are good enough to get going.
This does assume your Typesense collection schema is "simple enough" that Annex can determine what the title, body and URL of the result ought to be (during rendering).
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/AnnexLabs/AnnexUI@0.1.14-stable/dist/bundle.min.js" defer></script> <script type="text/javascript"> document.addEventListener('DOMContentLoaded', function() { let $annexSearchWidget = document.createElement('annex-search-widget'); $annexSearchWidget.setConfig({ cluster: { apiKey: '606o4DjqwBhFNZ2NKgSiqFsqdMNCbcKx', collectionName: 'prod:::tpclwpqz62hq:::crawlerResourceSearch:::v0.1.0', hostname: 'b3487cx0hrdu1y6kp-1.a1.typesense.net', presetName: 'prod:::tcprkee8nnvp:::crawlerResourceSearch:::v0.1.0', } }); $annexSearchWidget.mount(); }); </script>
Edge cases considered
- High-resolution monitors that immediately ought to trigger a loadMore flow (because there's not scrollbar)
- Aborting Typesense cluster queries when the search input value changes
- Prevention of mobile zooming when the search input is focused on
Releases
Below are a command-notes that are helpful to the author during version-bumping.
Namely, to cd into the directory (after version strings have been changed),
building the dist-files, commit, tagging and pushing the tag.
Thereafter a release can be created via /releases.
cd TurtlePHP/application/vendors/submodules/AnnexUI/./scripts/dist.shgit add . && git commit -m "Version bump" && git pushgit tag -a 0.1.14-stable -m "Release based tag"git push origin 0.1.14-stable