Skip to content

Commit

Permalink
docs: Context documentation (#182)
Browse files Browse the repository at this point in the history
* docs: `Context` documentation

* reorganize nav

* tidy up
  • Loading branch information
huntabyte authored Dec 19, 2024
1 parent f6455d9 commit bba5162
Show file tree
Hide file tree
Showing 12 changed files with 192 additions and 26 deletions.
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/animation-frames.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: AnimationFrames
description: A wrapper for requestAnimationFrame with FPS control and frame metrics
category: Browser
category: Animation
---

<script>
Expand Down
135 changes: 135 additions & 0 deletions sites/docs/src/content/utilities/context.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
---
title: Context
description:
A wrapper around Svelte's Context API that provides type safety and improved ergonomics for
sharing data between components.
category: State
---

<script>
import { Steps, Step, Callout } from '@svecodocs/kit';
</script>

Context allows you to pass data through the component tree without explicitly passing props through
every level. It's useful for sharing data that many components need, like themes, authentication
state, or localization preferences.

The `Context` class provides a type-safe way to define, set, and retrieve context values.

## Usage

<Steps>

<Step>Creating a Context</Step>

First, create a `Context` instance with the type of value it will hold:

```ts title="context.ts"
import { Context } from "runed";

export const myTheme = new Context<"light" | "dark">("theme");
```

Creating a `Context` instance only defines the context - it doesn't actually set any value. The
value passed to the constructor (`"theme"` in this example) is just an identifier used for debugging
and error messages.

Think of this step as creating a "container" that will later hold your context value. The container
is typed (in this case to only accept `"light"` or `"dark"` as values) but remains empty until you
explicitly call `myTheme.set()` during component initialization.

This separation between defining and setting context allows you to:

- Keep context definitions in separate files
- Reuse the same context definition across different parts of your app
- Maintain type safety throughout your application
- Set different values for the same context in different component trees

<Step>Setting Context Values</Step>

Set the context value in a parent component during initialization.

```svelte title="+layout.svelte"
<script lang="ts">
import { myTheme } from "./context";
let { data, children } = $props();
myTheme.set(data.theme);
</script>
{@render children?.()}
```

<Callout>

Context must be set during component initialization, similar to lifecycle functions like `onMount`.
You cannot set context inside event handlers or callbacks.

</Callout>

<Step>Reading Context Values</Step>

Child components can access the context using `get()` or `getOr()`

```svelte title="+page.svelte"
<script lang="ts">
import { myTheme } from "./context";
const theme = myTheme.get();
// or with a fallback value if the context is not set
const theme = myTheme.getOr("light");
</script>
```

</Steps>

## Type Definition

```ts
class Context<TContext> {
/**
* @param name The name of the context.
* This is used for generating the context key and error messages.
*/
constructor(name: string) {}

/**
* The key used to get and set the context.
*
* It is not recommended to use this value directly.
* Instead, use the methods provided by this class.
*/
get key(): symbol;

/**
* Checks whether this has been set in the context of a parent component.
*
* Must be called during component initialization.
*/
exists(): boolean;

/**
* Retrieves the context that belongs to the closest parent component.
*
* Must be called during component initialization.
*
* @throws An error if the context does not exist.
*/
get(): TContext;

/**
* Retrieves the context that belongs to the closest parent component,
* or the given fallback value if the context does not exist.
*
* Must be called during component initialization.
*/
getOr<TFallback>(fallback: TFallback): TContext | TFallback;

/**
* Associates the given value with the current component and returns it.
*
* Must be called during component initialization.
*/
set(context: TContext): TContext;
}
```
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/is-focus-within.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ title: IsFocusWithin
description:
A utility that tracks whether any descendant element has focus within a specified container
element.
category: Utilities
category: Elements
---

<script>
Expand Down
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/is-idle.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: IsIdle
description: Track if a user is idle and the last time they were active.
category: Utilities
category: Sensors
---

<script>
Expand Down
4 changes: 2 additions & 2 deletions sites/docs/src/content/utilities/is-in-viewport.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: IsInViewport
description: N/A
category: Utilities
description: Track if an element is visible within the current viewport.
category: Elements
---

<script>
Expand Down
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/pressed-keys.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: PressedKeys
description: Tracks which keys are currently pressed
category: Browser
category: Sensors
---

<script>
Expand Down
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/use-geolocation.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: useGeolocation
description: Reactive access to the browser's Geolocation API.
category: Browser
category: Sensors
---

<script>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: useIntersectionObserver
description: Watch for intersection changes of a target element.
category: Browser
category: Elements
---

<script>
Expand Down
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/use-mutation-observer.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: useMutationObserver
description: Observe changes in an element
category: Browser
category: Elements
---

<script>
Expand Down
2 changes: 1 addition & 1 deletion sites/docs/src/content/utilities/use-resize-observer.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: useResizeObserver
description: Detects changes in the size of an element
category: Browser
category: Elements
---

<script>
Expand Down
61 changes: 45 additions & 16 deletions sites/docs/src/lib/config/navigation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,41 @@ import Notebook from "phosphor-svelte/lib/Notebook";
import RocketLaunch from "phosphor-svelte/lib/RocketLaunch";
import Tag from "phosphor-svelte/lib/Tag";

function docToNavItem(doc: (typeof docs)[number]) {
type NavItem = {
title: string;
href: string;
};

function docToNavItem(doc: (typeof docs)[number]): NavItem {
return {
title: doc.title,
href: `/docs/${doc.slug}`,
};
}

const newSection = docs.filter((doc) => doc.category === "New").map(docToNavItem);
const reactivitySection = docs.filter((doc) => doc.category === "Reactivity").map(docToNavItem);
const stateSection = docs.filter((doc) => doc.category === "State").map(docToNavItem);
const elementsSection = docs.filter((doc) => doc.category === "Elements").map(docToNavItem);
const browserSection = docs.filter((doc) => doc.category === "Browser").map(docToNavItem);
const componentSection = docs.filter((doc) => doc.category === "Component").map(docToNavItem);
const utilitiesSection = docs.filter((doc) => doc.category === "Utilities").map(docToNavItem);
function buildSections() {
const sections: Record<(typeof docs)[0]["category"], NavItem[]> = {
New: [],
Reactivity: [],
State: [],
Elements: [],
Browser: [],
Component: [],
Utilities: [],
Animation: [],
Sensors: [],
Anchor: [],
};

for (const doc of docs) {
sections[doc.category].push(docToNavItem(doc));
}

return sections;
}

const { New, Reactivity, State, Elements, Browser, Component, Utilities, Animation, Sensors } =
buildSections();

export const navigation = defineNavigation({
anchors: [
Expand All @@ -40,31 +61,39 @@ export const navigation = defineNavigation({
sections: [
{
title: "New",
items: newSection,
items: New,
},
{
title: "Reactivity",
items: reactivitySection,
items: Reactivity,
},
{
title: "State",
items: stateSection,
items: State,
},
{
title: "Elements",
items: elementsSection,
items: Elements,
},
{
title: "Browser",
items: browserSection,
items: Browser,
},
{
title: "Component",
items: componentSection,
title: "Sensors",
items: Sensors,
},
{
title: "Animation",
items: Animation,
},
{
title: "Utilities",
items: utilitiesSection,
items: Utilities,
},
{
title: "Component",
items: Component,
},
].filter((item) => item.items.length),
});
2 changes: 2 additions & 0 deletions sites/docs/velite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const baseSchema = s.object({
"Component",
"Utilities",
"Anchor",
"Animation",
"Sensors",
]),
});

Expand Down

0 comments on commit bba5162

Please sign in to comment.