Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Dec 14, 2024
1 parent 0c8e1c8 commit efb6836
Show file tree
Hide file tree
Showing 14 changed files with 1,277 additions and 3 deletions.
1 change: 1 addition & 0 deletions packages/bits-ui/src/lib/bits/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export { Label } from "./label/index.js";
export { LinkPreview } from "./link-preview/index.js";
export { Menubar } from "./menubar/index.js";
export { NavigationMenu } from "./navigation-menu/index.js";
export { NavigationMenu as NavMenu } from "./navigation-menu-2/index.js";
export { Pagination } from "./pagination/index.js";
export { PinInput } from "./pin-input/index.js";
export { Popover } from "./popover/index.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuContentProps } from "../types.js";
import { useNavigationMenuContent } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
import Portal from "$lib/bits/utilities/portal/portal.svelte";
import PresenceLayer from "$lib/bits/utilities/presence-layer/presence-layer.svelte";
import DismissibleLayer from "$lib/bits/utilities/dismissible-layer/dismissible-layer.svelte";
import EscapeLayer from "$lib/bits/utilities/escape-layer/escape-layer.svelte";
import Mounted from "$lib/bits/utilities/mounted.svelte";
let {
children: contentChildren,
child,
ref = $bindable(null),
id = useId(),
forceMount = false,
onEscapeKeydown,
onInteractOutside,
onFocusOutside,
...restProps
}: NavigationMenuContentProps = $props();
let isMounted = $state(false);
const contentState = useNavigationMenuContent({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => {
ref = v;
}
),
forceMount: box.with(() => forceMount),
isMounted: box.with(() => isMounted),
});
const mergedProps = $derived(mergeProps(restProps, contentState.props));
const portalDisabled = $derived(!contentState.menu.viewportNode);
</script>

<Portal to={contentState.menu.viewportNode ?? undefined} disabled={portalDisabled}>
<PresenceLayer {id} present={contentState.isPresent}>
{#snippet presence()}
<EscapeLayer
enabled={contentState.isPresent}
onEscapeKeydown={(e) => {
onEscapeKeydown?.(e);
if (e.defaultPrevented) return;
contentState.onEscapeKeydown(e);
}}
>
<DismissibleLayer
enabled={contentState.isPresent}
{id}
onInteractOutside={(e) => {
onInteractOutside?.(e);
if (e.defaultPrevented) return;
contentState.onInteractOutside(e);
}}
onFocusOutside={(e) => {
onFocusOutside?.(e);
if (e.defaultPrevented) return;
contentState.onFocusOutside(e);
}}
>
{#snippet children({ props: dismissibleProps })}
{#if child}
<Mounted bind:isMounted />
{@render child({ props: mergeProps(dismissibleProps, mergedProps) })}
{:else}
<Mounted bind:isMounted />
<div {...mergeProps(dismissibleProps, mergedProps)}>
{@render contentChildren?.()}
</div>
{/if}
{/snippet}
</DismissibleLayer>
</EscapeLayer>
{/snippet}
</PresenceLayer>
</Portal>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuIndicatorProps } from "../types.js";
import { useNavigationMenuIndicator } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
import PresenceLayer from "$lib/bits/utilities/presence-layer/presence-layer.svelte";
import Portal from "$lib/bits/utilities/portal/portal.svelte";
let {
id = useId(),
ref = $bindable(null),
children,
child,
forceMount = false,
...restProps
}: NavigationMenuIndicatorProps = $props();
const indicatorState = useNavigationMenuIndicator({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, indicatorState.props));
</script>

{#if indicatorState.menu.indicatorTrackNode}
<Portal to={indicatorState.menu.indicatorTrackNode}>
<PresenceLayer {id} present={forceMount || indicatorState.isVisible}>
{#snippet presence()}
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PresenceLayer>
</Portal>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuItemProps } from "../types.js";
import { useNavigationMenuItem } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
let {
id = useId(),
value = useId(),
ref = $bindable(null),
child,
children,
...restProps
}: NavigationMenuItemProps = $props();
const itemState = useNavigationMenuItem({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
value: box.with(() => value),
});
const mergedProps = $derived(mergeProps(restProps, itemState.props));
</script>

{#if child}
{@render child({ props: mergedProps })}
{:else}
<li {...mergedProps}>
{@render children?.()}
</li>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuLinkProps } from "../types.js";
import { useNavigationMenuLink } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
import { noop } from "$lib/internal/noop.js";
let {
id = useId(),
ref = $bindable(null),
child,
children,
active = false,
onSelect = noop,
...restProps
}: NavigationMenuLinkProps = $props();
const linkState = useNavigationMenuLink({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
active: box.with(() => active),
onSelect: box.with(() => onSelect),
});
const mergedProps = $derived(mergeProps(restProps, linkState.props));
</script>

{#if child}
{@render child({ props: mergedProps })}
{:else}
<a {...mergedProps}>
{@render children?.()}
</a>
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuListProps } from "../types.js";
import { useNavigationMenuList } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
let {
id = useId(),
children,
child,
ref = $bindable(null),
...restProps
}: NavigationMenuListProps = $props();
const listState = useNavigationMenuList({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
indicatorTrackRef: box(null),
});
const mergedProps = $derived(mergeProps(restProps, listState.props));
const indicatorTrackProps = $derived(mergeProps(listState.indicatorTrackProps, {}));
</script>

<div {...indicatorTrackProps}>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<ul {...mergedProps}>
{@render children?.()}
</ul>
{/if}
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuTriggerProps } from "../types.js";
import { useNavigationMenuTrigger } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
import VisuallyHidden from "$lib/bits/utilities/visually-hidden/visually-hidden.svelte";
import Mounted from "$lib/bits/utilities/mounted.svelte";
let {
id = useId(),
disabled = false,
children,
child,
ref = $bindable(null),
...restProps
}: NavigationMenuTriggerProps = $props();
let focusProxyMounted = $state(false);
const triggerState = useNavigationMenuTrigger({
id: box.with(() => id),
disabled: box.with(() => disabled ?? false),
ref: box.with(
() => ref,
(v) => (ref = v)
),
focusProxyMounted: box.with(() => focusProxyMounted),
});
const mergedProps = $derived(mergeProps(restProps, triggerState.props));
</script>

{#if child}
{@render child({ props: mergedProps })}
{:else}
<button {...mergedProps}>
{@render children?.()}
</button>
{/if}

{#if triggerState.open}
<Mounted bind:isMounted={focusProxyMounted} />
<VisuallyHidden {...triggerState.visuallyHiddenProps} />
{#if triggerState.menu.viewportNode}
<span aria-owns={triggerState.item.contentNode?.id ?? undefined}></span>
{/if}
{/if}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuViewportProps } from "../types.js";
import { useNavigationMenuViewport } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
import PresenceLayer from "$lib/bits/utilities/presence-layer/presence-layer.svelte";
let {
id = useId(),
ref = $bindable(null),
children,
child,
forceMount = false,
...restProps
}: NavigationMenuViewportProps = $props();
const viewportState = useNavigationMenuViewport({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, viewportState.props));
</script>

<PresenceLayer {id} present={forceMount || viewportState.open}>
{#snippet presence()}
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
{/snippet}
</PresenceLayer>
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<script lang="ts">
import { box, mergeProps } from "svelte-toolbelt";
import type { NavigationMenuRootProps } from "../types.js";
import { useNavigationMenuRoot } from "../navigation-menu.svelte.js";
import { useId } from "$lib/internal/use-id.js";
import { noop } from "$lib/internal/noop.js";
let {
child,
children,
id = useId(),
ref = $bindable(null),
value = $bindable(""),
onValueChange = noop,
delayDuration = 200,
skipDelayDuration = 300,
dir = "ltr",
orientation = "horizontal",
controlledValue = false,
...restProps
}: NavigationMenuRootProps = $props();
const rootState = useNavigationMenuRoot({
id: box.with(() => id),
value: box.with(
() => value,
(v) => {
if (controlledValue) {
onValueChange(v);
} else {
value = v;
onValueChange(v);
}
}
),
delayDuration: box.with(() => delayDuration),
skipDelayDuration: box.with(() => skipDelayDuration),
dir: box.with(() => dir),
orientation: box.with(() => orientation),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps({ "aria-label": "main" }, restProps, rootState.props));
</script>

{#if child}
{@render child({ props: mergedProps })}
{:else}
<nav {...mergedProps}>
{@render children?.()}
</nav>
{/if}
Loading

0 comments on commit efb6836

Please sign in to comment.