Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

next: Label component #468

Merged
merged 1 commit into from
Apr 17, 2024
Merged
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
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
Loading