Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
"ember-eslint": "^0.7.0",
"ember-modifier": "^4.3.0",
"ember-page-title": "^9.0.3",
"ember-primitives": "^0.61.1",
"ember-primitives": "^0.62.0",
"ember-qunit": "^9.1.0",
"ember-resolver": "^13.2.0",
"ember-resources": "^7.1.0",
Expand Down
54 changes: 54 additions & 0 deletions docs-app/src/styles/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,60 @@ a[data-search-result]:is(:hover, :focus, :active) {
text-decoration: none;
}

/*
* <Search /> in pico's colours. The component brings its own layout; these
* are the only values it asks for.
*/
:root {
--kolay-search-bg: var(--pico-background-color);
--kolay-search-fg: var(--pico-color);
--kolay-search-muted: var(--pico-muted-color);
--kolay-search-border: var(--pico-muted-border-color);
--kolay-search-accent: var(--pico-primary);
--kolay-search-active: color-mix(in oklab, var(--pico-primary), transparent 88%);
}

/* the palette's results are anchors too, and pico reaches them the same way */
a.kolay__search__result,
a.kolay__search__result:is(:hover, :focus, :active) {
--pico-color: inherit;
--pico-background-color: inherit;
color: inherit;
text-decoration: none;
}

/*
* The palette's input. Pico reaches every text input through a selector that
* carries an attribute, which outranks the component's single class, and
* gives it a box and a bottom margin the palette does not want.
*/
input.kolay__search__input {
height: auto;
margin: 0;
padding: 0;
border: none;
background: none;
box-shadow: none;
}

/* the header's middle column */
header .kolay__search__trigger {
flex: 1;
max-width: 24rem;
}

@media (width < 1000px) {
header .kolay__search__trigger {
max-width: 18rem;
}
}

@media (width < 600px) {
header .kolay__search__trigger {
max-width: none;
}
}

/*
* The shine's colours. Kept as their own names so the sweep can be retuned in
* one place: the site's primary at the leading edge, running through violet
Expand Down
86 changes: 5 additions & 81 deletions docs-app/src/templates/application.gts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,8 @@ import { pascalCase, sentenceCase } from 'change-case';
// @ts-expect-error no types for the mobile-menu
import MenuWrapper from 'ember-mobile-menu/components/mobile-menu-wrapper';
import { pageTitle } from 'ember-page-title';
import { Form } from 'ember-primitives/components/form';
import Route from 'ember-route-template';
import { GroupNav, PageNav } from 'kolay/components';
import { GroupNav, PageNav, Search } from 'kolay/components';
import rememberDocumentScroll from 'memory-scroll/modifiers/remember-document-scroll';
import { ExternalLink } from 'nvp.ui';

Expand Down Expand Up @@ -73,83 +72,6 @@ const Menu: TOC<{ Element: SVGElement }> = <template>
></path></svg>
</template>;

class HeaderSearch extends Component<{ Args: Record<string, never> }> {
@service declare router: RouterService;

get isSearchRoute() {
return this.router.currentRouteName === 'search';
}

submit = (data: { q?: string }) => {
const q = data.q ?? '';

this.router.transitionTo('search', { queryParams: { q } });
};

<template>
{{#unless this.isSearchRoute}}
<Form data-search @onChange={{this.submit}}>
<input aria-label="Search documentation" placeholder="Search docs" name="q" />
<button type="submit" aria-label="Search">
<svg aria-hidden="true" viewBox="0 0 24 24"><circle cx="11" cy="11" r="6.5"></circle><path
d="m16 16 5 5"
></path></svg>
</button>
</Form>
{{/unless}}

<style scoped>
[data-search] {
flex: 1;
display: flex;
max-width: 24rem;
margin: 0;
input {
min-width: 0;
width: 100%;
margin: 0;
padding: 0.5rem 1rem;
line-height: 1rem;
height: auto;
border-radius: 0.35rem 0 0 0.35rem;
}

button {
display: inline-flex;
width: auto;
align-items: center;
justify-content: center;
margin: 0;
padding: 0.4rem 0.65rem;
border-radius: 0 0.35rem 0.35rem 0;
}

svg {
width: 1rem;
height: 1rem;
fill: none;
stroke: currentColor;
stroke-linecap: round;
stroke-linejoin: round;
stroke-width: 2;
}
}

@media (width < 1000px) {
[data-search] {
max-width: 18rem;
}
}

@media (width < 600px) {
[data-search] {
max-width: none;
}
}
</style>
</template>
}

const SideNav: TOC<{ Element: HTMLElement }> = <template>
<aside>
<PageNav ...attributes>
Expand Down Expand Up @@ -202,7 +124,7 @@ export default Route(
<mmw.Toggle><Menu /></mmw.Toggle>
<GroupNav />
</div>
<HeaderSearch />
<Search />
<div>
<ExternalLink href="https://github.com/universal-ember/kolay">GitHub</ExternalLink>
</div>
Expand Down Expand Up @@ -232,7 +154,9 @@ export default Route(
justify-content: space-between;
gap: 1rem;

div:first-child {
/* the nav group, not every div under the header: <Search /> puts its
<dialog> here too, and a descendant rule reaches inside it */
> div:first-child {
display: flex; gap: 1rem;
align-items: baseline;
}
Expand Down
2 changes: 1 addition & 1 deletion docs/navigation/meta.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"order": ["is-active", "group-nav", "page-nav", "handle-potential-index-visit"]
"order": ["is-active", "group-nav", "page-nav", "search", "handle-potential-index-visit"]
}
101 changes: 101 additions & 0 deletions docs/navigation/search.gjs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# `<Search />`

Site-wide search, as a command palette. The <kbd>⌘</kbd><kbd>K</kbd> in this site's header is this component.

```gjs
import { Search } from 'kolay/components';

<template>
<Search />
</template>
```

There is nothing to index and nothing to configure. The `docs()` plugin already wrote every page's title, headings, and prose into the compiled docs; this renders what [`searcher`](/Runtime/utilities/search.md) ranks.

Put it in your application template, next to your nav. It renders a button; the palette itself is a `<dialog>`, so it does not matter where in the layout it sits.

## What it is

`<CommandPalette>` from [ember-primitives](https://ember-primitives.pages.dev/5-floaty-bits/command-palette), filled in with kolay's index:

- The `<dialog>` handles the layer, the focus trap, and returning focus to whatever opened it.
- `aria-activedescendant` handles the keyboard, so <kbd>ArrowUp</kbd> and <kbd>ArrowDown</kbd> move the selection while the caret stays where the reader is typing.
- <kbd>Enter</kbd> clicks the active result, which is a real link, so the router navigates and <kbd>⌘</kbd>-click still opens a new tab.
- <kbd>Esc</kbd>, a click outside, and the footer's Close button all close it.

Excerpts come from `stripFormatting`, and the query is marked inside them with `highlightSearch`. Both are documented under [`searcher`](/Runtime/utilities/search.md).

## Your own trigger

The `:trigger` block is yielded `open`, and the modifier that returns focus to your button when the palette closes.

```gjs
import { on } from '@ember/modifier';
import { Search } from 'kolay/components';

<template>
<Search>
<:trigger as |open focusOnClose|>
<button type="button" class="my-search-button" {{focusOnClose}} {{on "click" open}}>
🔍
</button>
</:trigger>
</Search>
</template>
```

Leave the block empty to render no trigger at all. <kbd>⌘</kbd><kbd>K</kbd> still opens the palette.

## Your own result

The `:result` block replaces the group, title, and excerpt, and is yielded the result and the query.

```gjs
import { Search } from 'kolay/components';
import { stripFormatting } from 'kolay';

<template>
<Search>
<:result as |result|>
<strong>{{result.title}}</strong>
<small>{{result.score}}</small>
</:result>
</Search>
</template>
```

A result carries everything `searcher` produces: `path`, `title`, `groupName`, `headings`, `score`, `text`, and `excerptRange`.

## Styling

Default styles ship in `kolay.css`, which you already import. The colours are variables. Set them on `:root` and the palette follows your site rather than the other way around.

```css
:root {
--kolay-search-bg: var(--my-surface);
--kolay-search-fg: var(--my-text);
--kolay-search-muted: var(--my-muted);
--kolay-search-border: var(--my-border);
--kolay-search-accent: var(--my-link);
--kolay-search-active: color-mix(in oklab, var(--my-link), transparent 88%);
--kolay-search-backdrop: rgb(0 0 0 / 0.45);
--kolay-search-width: 40rem;
--kolay-search-radius: 0.75rem;
}
```

Every rule is a single class (`.kolay__search`, `.kolay__search__result`, and friends), so replacing one takes one class of your own.

The active result is `[data-active="true"]`, set by the pointer and the keyboard alike. Style that, not `:hover`. One selector covers both inputs, and the two can never disagree about what <kbd>Enter</kbd> will do.

## A search page as well

`<Search />` is for looking something up and leaving. For a page a reader can link to and scroll through, keep the query in the URL and render the results yourself with [`searcher`](/Runtime/utilities/search.md). This site has both.

## API Reference

<ComponentSignature
@package='kolay'
@name='Search'
@module='declarations/browser/components'
/>
1 change: 1 addition & 0 deletions docs/navigation/search.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "title": "<Search />" }
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@
"content-tag": "^4.2.0",
"decorator-transforms": "^2.4.0",
"ember-modifier": "^4.3.0",
"ember-primitives": "^0.61.1",
"ember-primitives": "^0.62.0",
"ember-repl": "^8.2.3",
"ember-resources": "^7.1.0",
"globby": "^16.2.3",
Expand Down
55 changes: 49 additions & 6 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src/browser/components.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export { GroupNav } from './components/group-nav.gts';
export { Logs } from './components/logs.gts';
export { Page } from './components/page.gts';
export { PageNav } from './components/page-nav.gts';
export type { SearchSignature } from './components/search.gts';
export { Search } from './components/search.gts';
Loading
Loading