Skip to content

Added search to docs #102

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jun 4, 2024
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: 2 additions & 0 deletions .eslintignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ node_modules
.env.*
!.env.example
/coverage
**/pagefind

# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ node_modules
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
/coverage
**/pagefind
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ yarn.lock
/coverage
**/*.mdx
**/*.md
**/pagefind
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"dev": "vite dev",
"preview": "vite preview",
"build": "pnpm build:docs && pnpm build:package",
"build:docs": "svelte-kit sync && vite build",
"build:docs": "svelte-kit sync && vite build && pagefind",
"build:package": "svelte-kit sync && svelte-package && publint",
"ci:publish": "pnpm build:package && changeset publish",
"test": "vitest run --coverage",
Expand Down Expand Up @@ -39,7 +39,7 @@
"@sveltejs/adapter-static": "^3.0.1",
"@sveltejs/kit": "^2.0.0",
"@sveltejs/package": "^2.0.0",
"@sveltejs/vite-plugin-svelte": "4.0.0-next.1",
"@sveltejs/vite-plugin-svelte": "4.0.0-next.2",
"@testing-library/jest-dom": "^6.4.2",
"@testing-library/svelte": "^5.1.0",
"@types/eslint": "^8.56.0",
Expand All @@ -51,8 +51,10 @@
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-svelte": "^2.36.0-next.4",
"focus-trap": "^7.5.4",
"jsdom": "^24.0.0",
"lucide-svelte": "^0.373.0",
"pagefind": "^1.1.0",
"postcss": "^8.4.32",
"postcss-load-config": "^5.0.2",
"prettier": "^3.1.1",
Expand All @@ -65,6 +67,7 @@
"tslib": "^2.4.1",
"typescript": "^5.0.0",
"vite": "^5.0.11",
"vite-plugin-pagefind": "^0.2.8",
"vitest": "^1.2.0"
},
"svelte": "./dist/index.js",
Expand Down
9 changes: 9 additions & 0 deletions pagefind.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"site": "build",
"exclude_selectors": [".expressive-code"],
"vite_plugin_pagefind": {
"assets_dir": "static",
"build_command": "pnpm build:docs",
"dev_strategy": "lazy"
}
}
439 changes: 255 additions & 184 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions src/app.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare global {
// interface Locals {}
interface PageData {
highlighter: import('shiki').Highlighter;
pagefind: import('vite-plugin-pagefind/types').Pagefind;
}
// interface PageState {}
// interface Platform {}
Expand Down
78 changes: 78 additions & 0 deletions src/docs/components/Dialog/Dialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script lang="ts">
import { type Snippet } from 'svelte';
import { createFocusTrap } from 'focus-trap';
import Portal from '../Portal/Portal.svelte';
import { fade, fly } from 'svelte/transition';
import { cubicOut } from 'svelte/easing';
import { beforeNavigate } from '$app/navigation';

interface Props {
children: Snippet;
type?: 'modal' | 'drawer';
open?: boolean;
}

let { children, type = 'modal', open = $bindable(false) }: Props = $props();

$effect(() => {
if (open) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = '';
}

return () => {
document.body.style.overflow = '';
};
});

function focus_trap(node: HTMLElement) {
const trap = createFocusTrap(node, {
returnFocusOnDeactivate: true,
preventScroll: true,
escapeDeactivates: () => {
open = false;
return true;
},
allowOutsideClick: () => {
open = false;
return true;
},
});
trap.activate();
return {
destroy() {
trap.deactivate();
},
};
}

beforeNavigate(() => {
open = false;
});

const commonClasses = 'fixed z-50';

const classes = $derived(
{
drawer: `${commonClasses} top-0 left-0 right-0 bottom-0 h-screen w-fit max-w-[500px]`,
modal: `${commonClasses} top-4 md:top-[15%] left-1/2 -translate-x-1/2 w-[calc(100%-2rem)] max-w-[500px] rounded-md bg-surface-700 max-h-[75vh] max-w-[800px] overflow-auto`,
}[type],
);

const flyParams = $derived(
{
drawer: { x: -500, easing: cubicOut, duration: 250 },
modal: { y: 50, easing: cubicOut, duration: 250 },
}[type],
);
</script>

<Portal>
{#if open}
<div in:fade={{ duration: 250 }} class="fixed inset-0 z-50 bg-black bg-opacity-50"></div>
<div in:fly={flyParams} class={classes} role="dialog" aria-modal="true" use:focus_trap>
{@render children()}
</div>
{/if}
</Portal>
100 changes: 100 additions & 0 deletions src/docs/components/Dialog/SearchDialog.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<script lang="ts">
import Dialog from './Dialog.svelte';
import SearchIcon from 'lucide-svelte/icons/search';
import LoaderIcon from 'lucide-svelte/icons/loader';
import { page } from '$app/stores';

let open = $state(false);
let query = $state('');

const searchPromise = $derived.by(async () => {
if (query === '') {
return [];
}

// FIXME: https://github.com/sveltejs/eslint-plugin-svelte/issues/652
// eslint-disable-next-line svelte/valid-compile
const result = await $page.data.pagefind.debouncedSearch(query, {}, 250);

if (result === null) {
return [];
}

return await Promise.all(result.results.map((result) => result.data()));
});

$effect(() => {
function onKeydown(event: KeyboardEvent) {
if (event.key === 'k' && (event.ctrlKey || event.metaKey)) {
event.preventDefault();
open = true;
}
}

document.addEventListener('keydown', onKeydown);

return () => {
document.removeEventListener('keydown', onKeydown);
};
});
</script>

<button
class="flex gap-4 items-center border border-surface-600 px-4 py-1 rounded-md"
onclick={() => (open = true)}
>
<p class="hidden md:block text-lg">Search</p>
<SearchIcon size={20} />
</button>

<Dialog bind:open>
<div class="divide-y-4 divide-surface-600">
<div class="relative p-4">
<input
class="bg-transparent text-2xl outline-none pl-10 w-full"
placeholder="Search the docs..."
bind:value={query}
/>
<SearchIcon class="absolute left-4 top-1/2 -translate-y-1/2" />
</div>
<div class="flex flex-col gap-1 p-4" tabindex="-1">
{#if query === ''}
<p class="text-lg text-center py-16">What can we help you find?</p>
{:else}
{#await searchPromise}
<p class="text-lg text-center py-16">
<LoaderIcon class="inline animate-spin" size={16} />
</p>
{:then results}
{#if results.length > 0}
<nav>
<ul class="space-y-4">
{#each results as result}
<li>
<a
href={result.url.replace('.html', '')}
class="block text-xl font-semibold hover:bg-surface-500 focus:bg-surface-500 transition p-1 rounded-md"
>
{result.meta.title}

<!-- eslint-disable-next-line svelte/no-at-html-tags-->
<p class="text-sm line-clamp-2">{@html result.excerpt}</p>
</a>
</li>
{/each}
</ul>
</nav>
{:else if query !== ''}
<p class="text-lg text-center py-16">No results found for "{query}"</p>
{/if}
{/await}
{/if}
</div>
</div>
</Dialog>

<style lang="postcss">
:global(mark) {
@apply bg-rose-400 rounded-md px-0.5;
}
</style>
14 changes: 12 additions & 2 deletions src/docs/components/Navigation/Navigation.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script lang="ts">
import { page } from '$app/stores';
import { drawer } from '$docs/stores.svelte';
import { getDrawer } from '$docs/stores.svelte';
// Icons (Docs)
import IconGetStarted from 'lucide-svelte/icons/rocket';
// Icons (Examples)
Expand Down Expand Up @@ -53,9 +53,19 @@
},
];

const drawer = getDrawer();

// FIXME: Remove when Svelte 5 supports $page, see: https://github.com/sveltejs/eslint-plugin-svelte/issues/652
// eslint-disable-next-line svelte/valid-compile
const navActive = (href: string) => $page.route.id?.replace('/(inner)', '') == href;

$effect(() => {
const close = () => (drawer.open = false);
window.addEventListener('resize', close);
return () => {
window.removeEventListener('resize', close);
};
});
</script>

<div
Expand All @@ -81,7 +91,7 @@
href={link.href}
class="grid grid-cols-[24px_1fr] items-center gap-4 rounded-tr-xl rounded-br-xl px-4 py-3 text-left hover:bg-surface-500/20"
class:nav-active={navActive(link.href)}
onclick={() => drawer.close()}
onclick={() => (drawer.open = false)}
>
<svelte:component this={link.icon} size={24} />
<span>{link.label}</span>
Expand Down
15 changes: 15 additions & 0 deletions src/docs/components/Overlay/Overlay.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<script lang="ts">
import type { Snippet } from 'svelte';

interface Props {
children: Snippet;
}

let { children }: Props = $props();
</script>

<div data-portal-target></div>

<div data-body>
{@render children()}
</div>
12 changes: 10 additions & 2 deletions src/docs/components/PageHeader/PageHeader.svelte
Original file line number Diff line number Diff line change
@@ -1,21 +1,29 @@
<script lang="ts">
import { version } from '$app/environment';
import { drawer } from '$docs/stores.svelte';

// Icons
import IconMenu from 'lucide-svelte/icons/menu';
import SearchDialog from '../Dialog/SearchDialog.svelte';
import { getDrawer } from '$docs/stores.svelte.js';

const drawer = getDrawer();
</script>

<header
class="sticky top-0 z-10 bg-surface-200/75 dark:bg-surface-800/75 backdrop-blur border-b border-surface-500/20"
>
<div class="container mx-auto flex justify-between gap-4 p-4 lg:px-32">
<div class="flex items-center gap-4">
<button type="button" class="inline-block lg:hidden" onclick={() => drawer.toggle()}>
<button
type="button"
class="inline-block lg:hidden"
onclick={() => (drawer.open = !drawer.open)}
>
<IconMenu />
</button>
<a href="/" class="font-bold">Floating UI Svelte</a>
</div>
<SearchDialog />
<div class="flex items-center gap-4">
<a
href="https://github.com/skeletonlabs/floating-ui-svelte"
Expand Down
31 changes: 31 additions & 0 deletions src/docs/components/Portal/Portal.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import type { Snippet } from 'svelte';

interface Props {
children: Snippet;
}

let { children }: Props = $props();

let element: HTMLElement | null = $state(null);

$effect.pre(() => {
const target = document.querySelector('[data-portal-target]');
if (element === null || target === null) {
return;
}

target.appendChild(element);

return () => {
if (element === null) {
return;
}
element.remove();
};
});
</script>

<div class="contents" bind:this={element}>
{@render children()}
</div>
17 changes: 6 additions & 11 deletions src/docs/stores.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { getContext, setContext } from 'svelte';

// Reusable State Stores

// Navigation Drawer ---
type Drawer = { open: boolean };

function createDrawer() {
let value = $state(false);
return {
get value() {
return value;
},
toggle: () => (value = !value),
close: () => (value = false),
};
}
const drawerKey = Symbol('drawer');

export const drawer = createDrawer();
export const getDrawer = () => getContext<Drawer>(drawerKey);
export const setDrawer = (drawer: Drawer) => setContext(drawerKey, drawer);
Loading