# AnnexUI AnnexUI is a MIT licensed UI library for [Typesense](https://typesense.org/). 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 1. [Quick demos](#quick-demos) 2. [Quick preview](#quick-preview) 3. [Quick intro](#quick-intro) 4. [Quick start](#quick-start) 5. [Config](#config) 6. [Attribute-based events](#attribute-based-events) 7. [Events](#events) 8. [Methods](#methods) 9. [Templates](#templates) 9. [Additional notes](#additional-notes)
### Quick demos 1. Inline layout: https://annexsearch.com/#inline-demo-01 2. Modal layout: https://annexsearch.com/#modal-demo-01 3. Left panel layout: https://annexsearch.com/#panel-left-demo-01 4. Right panel layout: https://annexsearch.com/#panel-right-demo-01 5. Inline layout (dark mode): https://annexsearch.com/#inline-demo-02 6. Multiple modal layouts: https://annexsearch.com/#modal-demo-03 7. Inline layout (using `sku-v0.1.0` templates): https://annexsearch.com/#inline-demo-05
### Quick preview ![](https://416.io/ss/f/x43xuv/r)
### Quick intro Annex uses [Web Components](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) to encapsulate and restrict JavaScript and CSS scope. The fundamental concepts to understand are: - The UI will be encapsulated within a `` HTML tag - In docs, this will often be referenced as the `$annexSearchWidget` variable - 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](https://www.jsdelivr.com/) ⚡️ 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). ``` html ```
### Config Below you'll find a high-level breakdown of configuration options that can be changed. See [Config overriding](#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](#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](#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). ``` javascript $('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**: 1. When there is a single web component on the page, the `id` does not need to be specified in the attribute value 2. When there are multiple web components on the page, the `id` must be specified in the attribute value 3. Not all layouts support all interactions. For example, an `inline` `$annexSearchWidget` cannot be "shown" or "hidden" #### Behaviour | Behaviour name | Example | Layout Support | Description | | ----------------------|-----------------------------------------------|---------------------------|-------------------------------------------------------------------------------| | clear | `test` | All | Clears the search query input value from the `$annexSearchWidget`. | | disable | `test` | All | Disables the `$annexSearchWidget` if it's currently enabled. | | enable | `test` | All | Enables the `$annexSearchWidget` if it's currently disabled. | | focus | `test` | All | Focuses on `$annexSearchWidget` if it's currently showing. | | hide | `test` | All (except `'inline'`) | Hides the `$annexSearchWidget` if it's not currently hidden. | | query | `test` | All | Performs a query based on the attribute value. | | show | `test` | All (except `'inline'`) | Shows the `$annexSearchWidget` if it's currently hidden. | | toggle | `test` | All (except `'inline'`) | Shows or hides the `$annexSearchWidget` depending on it's current state. | #### Examples (without `id`) ``` html clear the $annexSearchWidget $input disable the $annexSearchWidget enable the $annexSearchWidget focus on the $annexSearchWidget hide the $annexSearchWidget show the $annexSearchWidget, insert query and search show the $annexSearchWidget toggle the $annexSearchWidget ```` #### Examples (with `id`) ``` html clear the $annexSearchWidget $input disable on the $annexSearchWidget enable on the $annexSearchWidget focus on the $annexSearchWidget hide the $annexSearchWidget show the $annexSearchWidget, insert query and search show the $annexSearchWidget toggle the $annexSearchWidget ````
### 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 ``` javascript $('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. ```html ``` #### Vendors / Dependencies Currently, the only 3rd party vendor included in Annex is [Lodash v4.17.21](https://github.com/lodash/lodash). 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). ``` html ``` #### 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](https://github.com/onassar/AnnexUI/releases/new). - `cd TurtlePHP/application/vendors/submodules/AnnexUI/` - `./scripts/dist.sh` - `git add . && git commit -m "Version bump" && git push` - `git tag -a 0.1.17-stable -m "Release based tag"` - `git push origin 0.1.17-stable`