Skip to content
Open
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
8 changes: 8 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"permissions": {
"allow": [
"Skill(playwright-cli)",
"Skill(playwright-cli:*)"
]
}
}
57 changes: 57 additions & 0 deletions docs/src/lib/attachments/hideybar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { on } from 'svelte/events';

/** Hides a sticky bar on scroll down, reveals on scroll up. */
export function hideybar({ offsetTop = '0px', mx = '0px', threshold = 0 } = {}) {
return (node: HTMLElement) => {
const parent = node.parentElement;
if (!parent) return;

const spacer = document.createElement('div');
node.insertAdjacentElement('afterend', spacer);

node.style.position = 'fixed';
node.style.top = offsetTop;
node.style.transform = 'translateY(0)';
node.style.transition = 'transform 200ms ease-in-out';
node.style.overflow = 'hidden';

const syncSize = () => {
const parentRect = parent.getBoundingClientRect();
node.style.left = `calc(${parentRect.left}px + ${mx})`;
node.style.width = `calc(${parentRect.width}px - ${mx} * 2)`;
const h = node.getBoundingClientRect().height;
if (h > 0) spacer.style.height = `${h}px`;
};
syncSize();

const ro = new ResizeObserver(syncSize);
ro.observe(parent);
ro.observe(node);

let lastScrollY = window.scrollY;
let isHidden = false;
const cleanupScroll = on(
window,
'scroll',
() => {
const currentScrollY = window.scrollY;
const goingUp = currentScrollY < lastScrollY;
const shouldHide = !goingUp && currentScrollY > threshold;
if (shouldHide !== isHidden) {
node.style.transform = shouldHide
? `translateY(calc(-100% - ${offsetTop}))`
: 'translateY(0)';
isHidden = shouldHide;
}
lastScrollY = currentScrollY;
},
{ passive: true }
);

return () => {
cleanupScroll();
ro.disconnect();
spacer.remove();
};
};
}
12 changes: 10 additions & 2 deletions docs/src/lib/components/OpenWithButton.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,18 @@
}
</script>

<ButtonGroup variant="fill-light" size="sm" color="primary" class={example ? 'mb-40 mt-4' : ''}>
<ButtonGroup
variant="fill-light"
size="sm"
color="primary"
class={example
? 'mb-40 mt-4 bg-surface-100 rounded-full border p-0.5'
: 'bg-surface-100 rounded-full border p-0.5'}
>
<Button
icon={LucideCopyIcon}
variant="fill-light"
rounded="full"
size="sm"
color="primary"
onclick={async () => {
Expand All @@ -114,7 +122,7 @@
</span>
</Button>
<Toggle bind:on={isOpen} let:on={open} let:toggle let:toggleOff>
<Button on:click={toggle}>
<Button rounded="full" size="sm" on:click={toggle}>
<span style="transition: transform 300ms ease; transform: rotate({open ? -180 : 0}deg);">
<ChevronDownIcon />
</span>
Expand Down
8 changes: 4 additions & 4 deletions docs/src/routes/docs/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

<header
class={cls(
'sticky top-0 z-30 flex h-16 items-center border-b border-primary/10 px-4 py-2',
'banner sticky top-0 z-30 flex h-header items-center border-b border-primary/10 px-4 py-2',
// dot background
'bg-radial from-black/0 from-[1px] to-surface-100/90 to-[1px] bg-size-[6px_6px] backdrop-blur-lg'
)}
Expand Down Expand Up @@ -142,10 +142,10 @@
</div>
</header>

<div class="bg-surface-200 flex min-h-[calc(100vh-64px)]">
<div class="bg-surface-200 flex min-h-[calc(100vh-var(--spacing-header))]">
<aside
class={cls(
'bg-surface-300/30 sticky top-16 hidden max-h-[calc(100dvh-64px)] border-r border-primary/10 transition-[width]',
'bg-surface-300/30 sticky top-header hidden max-h-[calc(100dvh-var(--spacing-header))] border-r border-primary/10 transition-[width]',
'lg:grid lg:grid-rows-[1fr_56px]',
showSidebar ? 'w-62' : 'w-0'
)}
Expand Down Expand Up @@ -248,7 +248,7 @@
<!-- Table of Contents -->
{#if page.data.metadata?.toc?.length}
<div
class="sticky top-16 hidden max-h-[calc(100dvh-64px)] w-70 overflow-auto py-5 pr-6 xl:block"
class="sticky top-header hidden max-h-[calc(100dvh-var(--spacing-header))] w-70 overflow-auto py-5 pr-6 xl:block"
>
<div
class="text-surface-content/50 flex items-center gap-2 pb-3 text-xs font-medium uppercase tracking-widest"
Expand Down
190 changes: 121 additions & 69 deletions docs/src/routes/docs/components/[name]/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,23 +1,52 @@
<script lang="ts">
import { dev } from '$app/environment';
import { onMount } from 'svelte';
import { fly } from 'svelte/transition';
import { getSettings } from 'layerchart';
import { Button, Menu, Switch, Toggle, ToggleGroup, ToggleOption, Tooltip } from 'svelte-ux';
import {
Button,
Menu,
Switch,
Toggle,
ToggleGroup,
ToggleOption,
Tooltip,
Breadcrumb
} from 'svelte-ux';
import { toTitleCase } from '@layerstack/utils';
import LoadingPlaceholder from '$lib/components/LoadingPlaceholder.svelte';
import OpenWithButton from '$lib/components/OpenWithButton.svelte';

import ScrollToTop from '~icons/lucide/arrow-up-to-line';
import { hideybar } from '$lib/attachments/hideybar.js';
import { examples } from '$lib/context.js';
import { intersectExampleLayers } from '$lib/utils/layers.js';
import { page } from '$app/state';

import LucideSettings from '~icons/lucide/settings';
import LucideChevronLeft from '~icons/lucide/chevron-left';
import LucideChevronRight from '~icons/lucide/chevron-right';
import { cls } from '@layerstack/tailwind';

// TODO: `setSettings({...})` or just use default?
const settings = getSettings();

const origin = $derived(dev ? 'http://next.layerchart.com' : page.url.origin);
const url = $derived(`${origin}${page.url.pathname})`);
let { data, children } = $props();

let loaded = $state(false);
let showScrollToTop = $state(false);
onMount(() => {
loaded = true;
const updateScrollToTop = () => {
showScrollToTop = window.scrollY > 240;
};

updateScrollToTop();
window.addEventListener('scroll', updateScrollToTop, { passive: true });

return () => {
window.removeEventListener('scroll', updateScrollToTop);
};
});

const { metadata } = $derived(data);

// Derive examples reactively so changes propagate to child components
Expand Down Expand Up @@ -78,37 +107,46 @@
let layers = $derived(
pageExample?.module?.layers ?? computedExampleLayers ?? metadata.layers ?? []
);
</script>

<div class="mb-4">
<!-- Show back if viewing individual example or all component examples -->
{#if page.params.example || page.route.id == '/docs/components/[name]/examples'}
<Button
size="sm"
icon={LucideChevronLeft}
href="/docs/components/{page.params.name}"
class="mb-4 border"
>
Back to {page.params.name}
</Button>
{/if}

<div class="flex items-center gap-2 text-xs font-bold">
<div class="text-surface-content/50 capitalize">
{metadata.category}
</div>
const title = $derived(
pageExample?.module?.title ??
toTitleCase(page.params.example?.replaceAll('-', ' ') ?? toTitleCase(metadata.name))
);

{#if page.params.example}
<LucideChevronRight class="text-sm opacity-25" />
<a href="/docs/components/{page.params.name}" class="text-primary">{metadata.name}</a>
{/if}
</div>
const breadcrumbs = $derived([
{ label: metadata.category },
...(page.params.example
? [{ label: metadata.name, href: `/docs/components/${page.params.name}` }]
: []),
{
label: title,
href: `/docs/components/${metadata.name}/${title.toLowerCase().replaceAll(' ', '-')}`
}
]);
</script>

<div class="flex items-center gap-4">
<h1 class="text-3xl font-bold first-letter:capitalize">
{pageExample?.module?.title ?? page.params.example?.replaceAll('-', ' ') ?? metadata.name}
</h1>
<span class="flex items-center gap-1">
<div
{@attach hideybar({ offsetTop: '57px', mx: '20px' })}
class={cls(
loaded ? 'visible' : 'invisible',
'flex flex-col z-29 w-full rounded-xl rounded-t-none border-x border-b border-primary/10 shadow-lg px-2 py-1 overflow-hidden',
'bg-radial from-black/0 from-[1px] to-surface-100 to-[1px] bg-size-[6px_6px] backdrop-blur-lg'
)}
>
<div class="flex items-center gap-4 px-3 overflow-hidden h-full">
<Breadcrumb
items={breadcrumbs}
class="text-surface-content/50 font-bold capitalize text-xs min-w-0 overflow-hidden [&_.divider]:text-primary-500 [&_.divider]:opacity-70"
>
{#snippet item({ item }: { item: (typeof breadcrumbs)[number] })}
{#if item.href}
<a href={item.href} class="text-primary truncate">{item.label}</a>
{:else}
<span class="truncate">{item.label}</span>
{/if}
{/snippet}
</Breadcrumb>
<span class="flex items-center gap-2 ml-auto shrink-0">
{#if layers?.length}
<ToggleGroup
bind:value={settings.layer}
Expand All @@ -117,13 +155,14 @@
inset
rounded="full"
size="sm"
class="bg-surface-100 rounded-full"
>
{#each layers as layer}
<ToggleOption value={layer}>{toTitleCase(layer)}</ToggleOption>
{/each}
</ToggleGroup>
{/if}

<OpenWithButton {metadata} />
<Toggle let:on={open} let:toggle let:toggleOff>
<Tooltip title="Settings">
<Button iconOnly on:click={toggle}>
Expand All @@ -139,43 +178,56 @@
</Toggle>
</span>
</div>

{#if pageExample?.module?.description}
<div class="text-sm text-surface-content/70">{pageExample.module.description}</div>
{/if}

{#if page.params.example == null}
<div class="text-sm text-surface-content/70">{metadata.description}</div>

<div class="flex gap-2 mt-3">
<OpenWithButton {metadata} />

<!-- <ViewSourceButton
label="Page source"
source={pageSource}
href={pageUrl
? `https://github.com/techniq/layerchart/blob/next/packages/layerchart/${pageUrl}`
: ''}
icon={LucideFilePenLine}
/> -->

<!-- {#if !hideTableOfContents}
<Button
icon={LucideChevronDown}
on:click={() => {
showTableOfContents = !showTableOfContents;
}}
variant="fill-light"
color="primary"
size="sm"
>
On this page
</Button>
{/if} -->
</div>
{/if}
</div>

{#if showScrollToTop}
<div
class="fixed bottom-4 right-4 z-40 md:bottom-6 md:right-6"
transition:fly={{ y: 12, duration: 180 }}
>
<Tooltip title="Scroll to top" placement="top" offset={4}>
<Button
iconOnly
icon={ScrollToTop}
class="size-10 text-surface-content bg-surface-100/90 hover:bg-surface-content/10 border border-primary/10 shadow-lg backdrop-blur-lg"
onclick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
></Button>
</Tooltip>
</div>
{/if}

<h1
class="text-4xl font-bold select-none pb-4"
ondblclick={() => {
navigator.clipboard.writeText(`[${title}](${url})`);
}}
>
{title}
</h1>
{#if pageExample?.module?.description}
<div class="text-sm text-surface-content/70">{pageExample.module.description}</div>
{/if}

{#if page.params.example == null}
<div class="text-sm text-surface-content/70">{metadata.description}</div>

<!-- <div class="flex gap-2 mt-3">
{#if !hideTableOfContents}
<Button
icon={LucideChevronDown}
on:click={() => {
showTableOfContents = !showTableOfContents;
}}
variant="fill-light"
color="primary"
size="sm"
>
On this page
</Button>
{/if}
</div> -->
{/if}

<svelte:boundary>
{#snippet pending()}
<LoadingPlaceholder />
Expand Down
5 changes: 0 additions & 5 deletions docs/src/routes/docs/components/[name]/[example]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import ComponentLink from '$lib/components/ComponentLink.svelte';
import ExampleListing from '$lib/components/ExampleListing.svelte';
import OpenWithButton from '$lib/components/OpenWithButton.svelte';

let { data } = $props();

Expand All @@ -15,10 +14,6 @@
const exampleInfo = $derived(data.catalog?.examples.find((e) => e.name === example));
</script>

<div class="mb-4">
<OpenWithButton />
</div>

<Example name={example} {component} showCode />

<H2>Components</H2>
Expand Down
2 changes: 1 addition & 1 deletion docs/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export default defineConfig({
}) /*, devtoolsJson()*/
],
server: {
// allowedHosts: ['.trycloudflare.com'],
allowedHosts: ['.ngrok-free.app', '.trycloudflare.com'],
fs: {
allow: ['.live-code']
},
Expand Down
Loading