Skip to content

Commit

Permalink
checkbox stuff
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Apr 13, 2024
1 parent e91c40a commit ff12454
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 62 deletions.
10 changes: 7 additions & 3 deletions packages/bits-ui/src/lib/bits/checkbox/checkbox.svelte.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,12 @@ class CheckboxRootState {
#attrs = $derived({
"data-disabled": getDataDisabled(this.disabled.value),
"data-state": getCheckboxDataState(this.checked.value),
type: "button",
role: "checkbox",
type: "button",
"aria-checked": getAriaChecked(this.checked.value),
"aria-required": getAriaRequired(this.required.value),
"data-checkbox-root": "",
disabled: this.disabled.value,
} as const);

constructor(props: CheckboxRootStateProps) {
Expand All @@ -59,8 +60,11 @@ class CheckboxRootState {

onclick = composeHandlers(this.onclickProp.value, () => {
if (this.disabled.value) return;
if (this.checked.value === "indeterminate") return true;
return !this.checked.value;
if (this.checked.value === "indeterminate") {
this.checked.value = true;
return;
}
this.checked.value = !this.checked.value;
});

createIndicator() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
const indicatorState = getCheckboxIndicatorState();
const mergedProps = $derived({
...indicatorState.attrs,
...indicatorState.props,
...restProps,
});
</script>
Expand All @@ -16,6 +16,6 @@
{@render child?.(mergedProps)}
{:else}
<div bind:this={el} {...mergedProps}>
{@render children?.()}
{@render children?.({ checked: indicatorState.root.checked })}
</div>
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
</script>

{#if inputState.shouldRender}
<input {...inputState.props} style="display: none !important;" />
<input {...inputState.props} type="checkbox" style="display: none !important;" />
{/if}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import type { RootProps } from "../index.js";
import { setCheckboxRootState } from "../checkbox.svelte.js";
import CheckboxInput from "./checkbox-input.svelte";
import { box } from "$lib/internal/box.svelte.js";
let {
Expand All @@ -14,8 +15,8 @@
onclick: onclickProp,
onkeydown: onkeydownProp,
asChild,
children,
child,
indicator,
...restProps
}: RootProps = $props();
Expand Down Expand Up @@ -50,9 +51,11 @@
</script>

{#if asChild}
{@render child?.(mergedProps)}
{@render child?.({ props: mergedProps, checked: checkboxState.checked.value })}
{:else}
<button bind:this={el} {...mergedProps}>
{@render children?.()}
{@render indicator?.({ checked: checkboxState.checked.value })}
</button>
{/if}

<CheckboxInput />
98 changes: 52 additions & 46 deletions packages/bits-ui/src/lib/bits/checkbox/types.ts
Original file line number Diff line number Diff line change
@@ -1,63 +1,69 @@
import type { HTMLButtonAttributes } from "svelte/elements";
import type { Snippet } from "svelte";
import type {
EventCallback,
HTMLDivAttributes,
OnChangeFn,
WithAsChild,
} from "$lib/internal/index.js";

export type CheckboxRootPropsWithoutHTML = WithAsChild<{
/**
* Whether the checkbox is disabled.
*
* @defaultValue false
*/
disabled?: boolean;

/**
* Whether the checkbox is required (for form validation).
*
* @defaultValue false
*/
required?: boolean;

/**
* The name of the checkbox used in form submission.
* If not provided, the hidden input will not be rendered.
*
* @defaultValue undefined
*/
name?: string;

/**
* The value of the checkbox used in form submission.
*
* @defaultValue undefined
*/
value?: string;

/**
* The checked state of the checkbox. It can be one of:
* - `true` for checked
* - `false` for unchecked
* - `"indeterminate"` for indeterminate
*
* @defaultValue false
*/
checked?: boolean | "indeterminate";

/**
* A callback function called when the checked state changes.
*/
onCheckedChange?: OnChangeFn<boolean | "indeterminate">;
}>;
export type CheckboxRootPropsWithoutHTML = WithAsChild<
{
/**
* Whether the checkbox is disabled.
*
* @defaultValue false
*/
disabled?: boolean;

/**
* Whether the checkbox is required (for form validation).
*
* @defaultValue false
*/
required?: boolean;

/**
* The name of the checkbox used in form submission.
* If not provided, the hidden input will not be rendered.
*
* @defaultValue undefined
*/
name?: string;

/**
* The value of the checkbox used in form submission.
*
* @defaultValue undefined
*/
value?: string;

/**
* The checked state of the checkbox. It can be one of:
* - `true` for checked
* - `false` for unchecked
* - `"indeterminate"` for indeterminate
*
* @defaultValue false
*/
checked?: boolean | "indeterminate";

/**
* A callback function called when the checked state changes.
*/
onCheckedChange?: OnChangeFn<boolean | "indeterminate">;

indicator?: Snippet<[{ checked: boolean | "indeterminate" }]>;
},
{ props: Record<PropertyKey, unknown>; checked: boolean | "indeterminate" }
>;

export type CheckboxRootProps = CheckboxRootPropsWithoutHTML &
Omit<HTMLButtonAttributes, "value" | "disabled" | "name" | "onclick" | "onkeydown"> & {
onclick?: EventCallback<MouseEvent>;
onkeydown?: EventCallback<KeyboardEvent>;
};

export type CheckboxIndicatorPropsWithoutHTML = WithAsChild<object>;
export type CheckboxIndicatorPropsWithoutHTML = WithAsChild<{ checked: boolean | "indeterminate" }>;

export type CheckboxIndicatorProps = CheckboxIndicatorPropsWithoutHTML & HTMLDivAttributes;
6 changes: 3 additions & 3 deletions packages/bits-ui/src/lib/internal/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,16 +135,16 @@ export type AsChildProps<T, U> = {
el?: HTMLElement;
} & Omit<T, "children" | "asChild">;

export type DefaultProps<T> = {
export type DefaultProps<T, U> = {
asChild?: never;
child?: never;
children?: Snippet;
children?: Snippet<[U]>;
el?: HTMLElement;
} & Omit<T, "child" | "asChild">;

// eslint-disable-next-line ts/ban-types
export type WithAsChild<T, U extends Record<PropertyKey, unknown> = {}> =
| DefaultProps<T>
| DefaultProps<T, U>
| AsChildProps<T, U>;

/**
Expand Down
15 changes: 11 additions & 4 deletions sites/docs/src/lib/components/demos/checkbox-demo.svelte
Original file line number Diff line number Diff line change
@@ -1,17 +1,24 @@
<script lang="ts">
import { Checkbox, Label } from "bits-ui";
import { Check, Minus } from "$icons/index.js";
</script>

<div class="flex items-center space-x-3">
<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 active:scale-98 data-[state=unchecked]:border-border-input data-[state=unchecked]:bg-background data-[state=unchecked]:hover:border-dark-40"
checked="indeterminate"
name="hello"
>
<Checkbox.Indicator class="inline-flex items-center justify-center text-background">
check
</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 ff12454

Please sign in to comment.