Skip to content

Commit

Permalink
next: cleanup menubar (#579)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Jun 17, 2024
1 parent 6c38794 commit 18ebc51
Show file tree
Hide file tree
Showing 20 changed files with 335 additions and 231 deletions.
4 changes: 3 additions & 1 deletion packages/bits-ui/src/lib/bits/aspect-ratio/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ import type { DOMEl, HTMLDivAttributes } from "$lib/internal/index.js";
export type AspectRatioPropsWithoutHTML = Expand<
{
ratio?: number;
} & DOMEl
} & {
ref?: HTMLElement | null;
}
>;

export type AspectRatioProps = AspectRatioPropsWithoutHTML & HTMLDivAttributes;
98 changes: 68 additions & 30 deletions packages/bits-ui/src/lib/bits/avatar/avatar.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { ReadableBox, WritableBox } from "svelte-toolbelt";
import type { AvatarImageLoadingStatus } from "./types.js";
import { createContext } from "$lib/internal/createContext.js";
import type { ReadableBoxedValues } from "$lib/internal/box.svelte.js";
import type { WithRefProps } from "$lib/internal/types.js";
import { useRefById } from "$lib/internal/useRefById.svelte.js";

const ROOT_ATTR = "data-avatar-root";
const IMAGE_ATTR = "data-avatar-image";
Expand All @@ -11,27 +13,29 @@ const FALLBACK_ATTR = "data-avatar-fallback";
/**
* ROOT
*/
type AvatarRootStateProps = {
type AvatarRootStateProps = WithRefProps<{
delayMs: ReadableBox<number>;
loadingStatus: WritableBox<AvatarImageLoadingStatus>;
};
}>;

type AvatarImageSrc = string | null | undefined;

class AvatarRootState {
#id: AvatarRootStateProps["id"];
#ref: AvatarRootStateProps["ref"];
delayMs: AvatarRootStateProps["delayMs"];
loadingStatus: AvatarRootStateProps["loadingStatus"];
props = $derived.by(
() =>
({
[ROOT_ATTR]: "",
"data-status": this.loadingStatus.value,
}) as const
);

constructor(props: AvatarRootStateProps) {
this.delayMs = props.delayMs;
this.loadingStatus = props.loadingStatus;
this.#ref = props.ref;
this.#id = props.id;

useRefById({
id: this.#id,
ref: this.#ref,
});
}

loadImage(src: string) {
Expand All @@ -52,54 +56,92 @@ class AvatarRootState {
};
}

props = $derived.by(
() =>
({
id: this.#id.value,
[ROOT_ATTR]: "",
"data-status": this.loadingStatus.value,
}) as const
);

createImage(props: AvatarImageStateProps) {
return new AvatarImageState(props, this);
}

createFallback() {
return new AvatarFallbackState(this);
createFallback(props: AvatarFallbackStateProps) {
return new AvatarFallbackState(props, this);
}
}

/**
* IMAGE
*/

type AvatarImageStateProps = ReadableBoxedValues<{
src: AvatarImageSrc;
}>;
type AvatarImageStateProps = WithRefProps<
ReadableBoxedValues<{
src: AvatarImageSrc;
}>
>;

class AvatarImageState {
#id: AvatarImageStateProps["id"];
#ref: AvatarImageStateProps["ref"];
src: AvatarImageStateProps["src"];
root: AvatarRootState;
props = $derived.by(
() =>
({
style: {
display: this.root.loadingStatus.value === "loaded" ? "block" : "none",
},
[IMAGE_ATTR]: "",
src: this.src.value,
}) as const
);

constructor(props: AvatarImageStateProps, root: AvatarRootState) {
this.root = root;
this.src = props.src;
this.#id = props.id;
this.#ref = props.ref;

useRefById({
id: this.#id,
ref: this.#ref,
});

$effect.pre(() => {
if (!this.src.value) return;
untrack(() => this.root.loadImage(this.src.value ?? ""));
});
}

props = $derived.by(
() =>
({
id: this.#id.value,
style: {
display: this.root.loadingStatus.value === "loaded" ? "block" : "none",
},
[IMAGE_ATTR]: "",
src: this.src.value,
}) as const
);
}

/**
* FALLBACK
*/

type AvatarFallbackStateProps = WithRefProps;

class AvatarFallbackState {
#id: AvatarFallbackStateProps["id"];
#ref: AvatarFallbackStateProps["ref"];
root: AvatarRootState;

constructor(props: AvatarFallbackStateProps, root: AvatarRootState) {
this.root = root;
this.#id = props.id;
this.#ref = props.ref;

useRefById({
id: this.#id,
ref: this.#ref,
});
}

props = $derived.by(
() =>
({
Expand All @@ -109,10 +151,6 @@ class AvatarFallbackState {
[FALLBACK_ATTR]: "",
}) as const
);

constructor(root: AvatarRootState) {
this.root = root;
}
}

/**
Expand All @@ -129,6 +167,6 @@ export function useAvatarImage(props: AvatarImageStateProps) {
return getAvatarRootContext().createImage(props);
}

export function useAvatarFallback() {
return getAvatarRootContext().createFallback();
export function useAvatarFallback(props: AvatarFallbackStateProps) {
return getAvatarRootContext().createFallback(props);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,33 @@
import type { FallbackProps } from "../index.js";
import { useAvatarFallback } from "../avatar.svelte.js";
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.svelte.js";
import { box } from "svelte-toolbelt";
let { asChild, children, child, ref = $bindable(), ...restProps }: FallbackProps = $props();
let {
asChild,
children,
child,
id = useId(),
ref = $bindable(null),
...restProps
}: FallbackProps = $props();
const fallbackState = useAvatarFallback();
const fallbackState = useAvatarFallback({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, fallbackState.props));
</script>

{#if asChild}
{@render child?.({ props: mergedProps })}
{:else}
<span bind:this={ref} {...mergedProps}>
<span {...mergedProps}>
{@render children?.()}
</span>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,24 @@
import type { ImageProps } from "../index.js";
import { useAvatarImage } from "../avatar.svelte.js";
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.svelte.js";
let { src, asChild, child, ref = $bindable(), ...restProps }: ImageProps = $props();
let {
src,
asChild,
child,
id = useId(),
ref = $bindable(null),
...restProps
}: ImageProps = $props();
const imageState = useAvatarImage({
src: box.with(() => src),
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, imageState.props));
Expand All @@ -16,5 +29,5 @@
{#if asChild}
{@render child?.({ props: mergedProps })}
{:else}
<img bind:this={ref} {...mergedProps} {src} />
<img {...mergedProps} {src} />
{/if}
11 changes: 9 additions & 2 deletions packages/bits-ui/src/lib/bits/avatar/components/avatar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import type { RootProps } from "../index.js";
import { useAvatarRoot } from "../avatar.svelte.js";
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.svelte.js";
let {
delayMs = 0,
Expand All @@ -11,7 +12,8 @@
asChild,
child,
children,
ref = $bindable(),
id = useId(),
ref = $bindable(null),
...restProps
}: RootProps = $props();
Expand All @@ -26,6 +28,11 @@
}
}
),
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps(restProps, rootState.props));
Expand All @@ -34,7 +41,7 @@
{#if asChild}
{@render child?.({ props: mergedProps })}
{:else}
<div bind:this={ref} {...mergedProps}>
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
32 changes: 23 additions & 9 deletions packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import {
type ReadableBoxedValues,
type WithRefProps,
type WritableBoxedValues,
getAriaChecked,
getAriaRequired,
getDataDisabled,
kbd,
useRefById,
} from "$lib/internal/index.js";
import { createContext } from "$lib/internal/createContext.js";

const ROOT_ATTR = "data-checkbox-root";

type CheckboxRootStateProps = ReadableBoxedValues<{
disabled: boolean;
required: boolean;
name: string | undefined;
value: string | undefined;
}> &
WritableBoxedValues<{
checked: boolean | "indeterminate";
}>;
type CheckboxRootStateProps = WithRefProps<
ReadableBoxedValues<{
disabled: boolean;
required: boolean;
name: string | undefined;
value: string | undefined;
}> &
WritableBoxedValues<{
checked: boolean | "indeterminate";
}>
>;

class CheckboxRootState {
#id: CheckboxRootStateProps["id"];
#ref: CheckboxRootStateProps["ref"];
checked: CheckboxRootStateProps["checked"];
disabled: CheckboxRootStateProps["disabled"];
required: CheckboxRootStateProps["required"];
Expand All @@ -33,6 +39,13 @@ class CheckboxRootState {
this.required = props.required;
this.name = props.name;
this.value = props.value;
this.#ref = props.ref;
this.#id = props.id;

useRefById({
id: this.#id,
ref: this.#ref,
});
}

#onkeydown = (e: KeyboardEvent) => {
Expand All @@ -55,6 +68,7 @@ class CheckboxRootState {
props = $derived.by(
() =>
({
id: this.#id.value,
"data-disabled": getDataDisabled(this.disabled.value),
"data-state": getCheckboxDataState(this.checked.value),
role: "checkbox",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import { useCheckboxRoot } from "../checkbox.svelte.js";
import CheckboxInput from "./checkbox-input.svelte";
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.svelte.js";
let {
checked = $bindable(false),
Expand All @@ -13,7 +14,8 @@
required = false,
name = undefined,
value = "on",
ref = $bindable(),
id = useId(),
ref = $bindable(null),
asChild,
child,
...restProps
Expand All @@ -33,6 +35,11 @@
required: box.with(() => required),
name: box.with(() => name),
value: box.with(() => value),
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
});
const mergedProps = $derived(mergeProps({ ...restProps }, rootState.props));
Expand All @@ -41,7 +48,7 @@
{#if asChild}
{@render child?.({ props: mergedProps, checked: rootState.checked.value })}
{:else}
<button bind:this={ref} {...mergedProps}>
<button {...mergedProps}>
{@render children?.({
checked: rootState.checked.value,
})}
Expand Down
Loading

0 comments on commit 18ebc51

Please sign in to comment.