Skip to content

Commit

Permalink
next: Label component (#468)
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte authored Apr 17, 2024
1 parent 2375dfd commit 1bcc2d3
Show file tree
Hide file tree
Showing 7 changed files with 76 additions and 57 deletions.
46 changes: 25 additions & 21 deletions packages/bits-ui/src/lib/bits/label/components/label.svelte
Original file line number Diff line number Diff line change
@@ -1,31 +1,35 @@
<script lang="ts">
import { createLabel, melt } from "@melt-ui/svelte";
import { getLabelData } from "../ctx.js";
import type { Events, Props } from "../index.js";
import { createDispatcher } from "$lib/internal/events.js";
import type { RootProps } from "../index.js";
import { setLabelRootState } from "../label.svelte.js";
import { readonlyBox } from "$lib/internal/box.svelte.js";
import { styleToString } from "$lib/internal/style.js";
type $$Props = Props;
type $$Events = Events;
let {
onmousedown: onmousedownProp = () => {},
asChild,
children,
child,
el = $bindable(),
style,
for: forProp,
...restProps
}: RootProps = $props();
export let asChild: $$Props["asChild"] = false;
export let el: $$Props["el"] = undefined;
const onmousedown = readonlyBox(() => onmousedownProp);
const {
elements: { root },
} = createLabel();
const dispatch = createDispatcher();
const { getAttrs } = getLabelData();
const attrs = getAttrs("root");
$: builder = $root;
$: Object.assign(builder, attrs);
const rootState = setLabelRootState({ onmousedown });
const mergedProps = $derived({
...restProps,
...rootState.props,
style: styleToString(style),
for: forProp,
} as const);
</script>

{#if asChild}
<slot {builder} />
{@render child?.({ props: mergedProps })}
{:else}
<label bind:this={el} use:melt={builder} {...$$restProps} on:m-mousedown={dispatch}>
<slot {builder} />
<label bind:this={el} {...mergedProps} for={forProp}>
{@render children?.()}
</label>
{/if}
13 changes: 0 additions & 13 deletions packages/bits-ui/src/lib/bits/label/ctx.ts

This file was deleted.

2 changes: 1 addition & 1 deletion packages/bits-ui/src/lib/bits/label/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
export { default as Root } from "./components/label.svelte";

export type { LabelProps as Props, LabelEvents as Events } from "./types.js";
export type { LabelRootProps as RootProps } from "./types.js";
28 changes: 28 additions & 0 deletions packages/bits-ui/src/lib/bits/label/label.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { type ReadonlyBoxedValues, boxedState, readonlyBox } from "$lib/internal/box.svelte.js";
import { type EventCallback, composeHandlers } from "$lib/internal/events.js";

type LabelRootStateProps = ReadonlyBoxedValues<{
onmousedown: EventCallback<MouseEvent>;
}>;

class LabelRootState {
#onmousedownProp = boxedState<LabelRootStateProps["onmousedown"]>(readonlyBox(() => () => {}));

constructor(props: LabelRootStateProps) {
this.#onmousedownProp.value = props.onmousedown;
}

#onmousedown = composeHandlers(this.#onmousedownProp, (e) => {
if (e.detail > 1) e.preventDefault();
});

get props() {
return {
onmousedown: this.#onmousedown,
};
}
}

export function setLabelRootState(props: LabelRootStateProps) {
return new LabelRootState(props);
}
18 changes: 9 additions & 9 deletions packages/bits-ui/src/lib/bits/label/types.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { HTMLLabelAttributes } from "svelte/elements";
import type { CustomEventHandler } from "$lib/index.js";
import type { DOMElement } from "$lib/internal/types.js";
import type { PrimitiveLabelAttributes, WithAsChild } from "$lib/internal/types.js";
import type { EventCallback } from "$lib/internal/events.js";

export type LabelPropsWithoutHTML = DOMElement<HTMLLabelElement>;

export type LabelProps = LabelPropsWithoutHTML & HTMLLabelAttributes;

export type LabelEvents<T extends Element = HTMLLabelElement> = {
mousedown: CustomEventHandler<MouseEvent, T>;
export type LabelRootPropsWithoutHTML = WithAsChild<{
for: string;
}> & {
onmousedown?: EventCallback<MouseEvent>;
};

export type LabelRootProps = LabelRootPropsWithoutHTML &
Omit<PrimitiveLabelAttributes, "onmousedown" | "for">;
2 changes: 2 additions & 0 deletions packages/bits-ui/src/lib/internal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type {
HTMLButtonAttributes,
HTMLImgAttributes,
HTMLInputAttributes,
HTMLLabelAttributes,
} from "svelte/elements";
import type { TransitionConfig } from "svelte/transition";
import type { StyleProperties } from "$lib/shared/index.js";
Expand Down Expand Up @@ -128,6 +129,7 @@ export type PrimitiveInputAttributes = Primitive<HTMLInputAttributes>;
export type PrimitiveSpanAttributes = Primitive<HTMLSpanAttributes>;
export type PrimitiveImgAttributes = Primitive<HTMLImgAttributes>;
export type PrimitiveHeadingAttributes = Primitive<HTMLHeadingAttributes>;
export type PrimitiveLabelAttributes = Primitive<HTMLLabelAttributes>;

export type AsChildProps<T, U> = {
child: Snippet<[U & { props: Record<string, unknown> }]>;
Expand Down
24 changes: 11 additions & 13 deletions sites/docs/src/lib/components/demos/label-demo.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,18 @@
<Checkbox.Root
id="terms"
aria-labelledby="terms-label"
class="peer inline-flex size-[25px] items-center justify-center rounded-md border border-muted bg-foreground transition-all duration-150 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-foreground focus-visible:ring-offset-2 focus-visible:ring-offset-background active:scale-98 data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40"
checked="indeterminate"
class="peer inline-flex size-[25px] items-center justify-center rounded-md border border-muted bg-foreground transition-all duration-150 ease-in-out active:scale-98 data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40"
name="hello"
>
<Checkbox.Indicator
let:isChecked
let:isIndeterminate
class="inline-flex items-center justify-center text-background"
>
{#if isChecked}
<Check class="size-[15px]" weight="bold" />
{:else if isIndeterminate}
<Minus class="size-[15px]" weight="bold" />
{/if}
</Checkbox.Indicator>
{#snippet indicator({ checked })}
<div class="inline-flex items-center justify-center text-background">
{#if checked === true}
<Check class="size-[15px]" weight="bold" />
{:else if checked === "indeterminate"}
<Minus class="size-[15px]" weight="bold" />
{/if}
</div>
{/snippet}
</Checkbox.Root>
<Label.Root
id="terms-label"
Expand Down

0 comments on commit 1bcc2d3

Please sign in to comment.