Skip to content

Commit

Permalink
some more
Browse files Browse the repository at this point in the history
  • Loading branch information
huntabyte committed Jul 22, 2024
1 parent 81ab5f7 commit 18a2383
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 134 deletions.
49 changes: 49 additions & 0 deletions packages/bits-ui/src/lib/bits/aspect-ratio/aspect-ratio.svelte.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { useRefById, type ReadableBoxedValues, type WithRefProps } from "$lib/internal/index.js";

const ASPECT_RATIO_ROOT_ATTR = "data-aspect-ratio-root";

type AspectRatioRootStateProps = WithRefProps<ReadableBoxedValues<{ ratio: number }>>;

export class AspectRatioRootState {
#ref: AspectRatioRootStateProps["ref"];
#id: AspectRatioRootStateProps["id"];
#ratio: AspectRatioRootStateProps["ratio"];

constructor(props: AspectRatioRootStateProps) {
this.#ref = props.ref;
this.#id = props.id;
this.#ratio = props.ratio;

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

wrapperProps = $derived.by(() => ({
style: {
position: "relative",
width: "100%",
paddingBottom: `${this.#ratio.current ? 100 / this.#ratio.current : 0}%}`,
},
}));

props = $derived.by(
() =>
({
id: this.#id.current,
style: {
position: "absolute",
top: 0,
right: 0,
bottom: 0,
left: 0,
},
[ASPECT_RATIO_ROOT_ATTR]: "",
}) as const
);
}

export function useAspectRatioRoot(props: AspectRatioRootStateProps) {
return new AspectRatioRootState(props);
}
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
<script lang="ts">
import { box } from "svelte-toolbelt";
import type { RootProps } from "../index.js";
import { useAspectRatioRoot } from "../aspect-ratio.svelte.js";
import { mergeProps } from "$lib/internal/mergeProps.js";
import { useId } from "$lib/internal/useId.js";
let { ratio, children, ref = $bindable(), ...restProps }: RootProps = $props();
let {
ref = $bindable(null),
id = useId(),
ratio = 1,
children,
child,
...restProps
}: RootProps = $props();
const rootState = useAspectRatioRoot({
id: box.with(() => id),
ref: box.with(
() => ref,
(v) => (ref = v)
),
ratio: box.with(() => ratio),
});
const mergedProps = $derived(mergeProps(restProps, rootState.props));
</script>

<div style:position="relative" style:width="100%" style:padding-bottom="{ratio ? 100 / ratio : 0}%">
<div
bind:this={ref}
style:position="absolute"
style:top="0"
style:right="0"
style:bottom="0"
style:left="0"
{...restProps}
data-aspect-ratio-root=""
>
{@render children?.()}
</div>
{#if child}
{@render child({ props: mergedProps })}
{:else}
<div {...mergedProps}>
{@render children?.()}
</div>
{/if}
</div>
9 changes: 4 additions & 5 deletions packages/bits-ui/src/lib/bits/aspect-ratio/types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import type { HTMLDivAttributes } from "$lib/internal/index.js";
import type { Expand } from "$lib/internal/index.js";
import type { PrimitiveDivAttributes, WithChild, Without } from "$lib/internal/index.js";

export type AspectRatioPropsWithoutHTML = Expand<{
export type AspectRatioPropsWithoutHTML = WithChild<{
ratio?: number;
ref?: HTMLElement | null;
}>;

export type AspectRatioProps = AspectRatioPropsWithoutHTML & HTMLDivAttributes;
export type AspectRatioProps = AspectRatioPropsWithoutHTML &
Without<PrimitiveDivAttributes, AspectRatioPropsWithoutHTML>;
112 changes: 12 additions & 100 deletions packages/bits-ui/src/lib/internal/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type { Snippet } from "svelte";
import type { Action } from "svelte/action";
import type {
HTMLAnchorAttributes,
HTMLAttributes,
Expand All @@ -13,105 +12,20 @@ import type {
HTMLThAttributes,
SVGAttributes,
} from "svelte/elements";
import type { TransitionConfig } from "svelte/transition";
import type { Box, ReadableBoxedValues, WritableBoxedValues } from "./box.svelte.js";
import type { StyleProperties } from "$lib/shared/index.js";

export type ObjectVariation<T> = T extends object ? T : never;
// eslint-disable-next-line ts/no-explicit-any
export type Transition = (node: Element, params?: any) => TransitionConfig;
export type TransitionParams<T extends Transition> = Parameters<T>[1];

export type HTMLDivAttributes = HTMLAttributes<HTMLDivElement>;
export type HTMLSpanAttributes = HTMLAttributes<HTMLSpanElement>;
export type HTMLHeadingAttributes = HTMLAttributes<HTMLHeadingElement>;

export type OmitOpen<T> = Omit<T, "open" | "defaultOpen" | "onOpenChange">;
export type OmitValue<T> = Omit<T, "value" | "defaultValue" | "onValueChange">;
export type OmitChecked<T> = Omit<T, "checked" | "defaultChecked" | "onCheckedChange">;
export type OmitPressed<T> = Omit<T, "pressed" | "defaultPressed" | "onPressedChange">;
export type OmitForceVisible<T> = Omit<T, "forceVisible">;
export type OmitIds<T> = Omit<T, "ids">;
export type OmitDates<T> = Omit<
T,
| "value"
| "defaultValue"
| "placeholder"
| "defaultPlaceholder"
| "onPlaceholderChange"
| "onValueChange"
| "ids"
>;

export type OmitFloating<T> = OmitOpen<
Omit<T, "forceVisible" | "ids" | "arrowSize" | "positioning">
>;
export type HTMLUListAttributes = HTMLAttributes<HTMLUListElement>;
export type HTMLElementAttributes = HTMLAttributes<HTMLElement>;
export type HTMLTableSectionAttributes = HTMLAttributes<HTMLTableSectionElement>;
export type HTMLTableRowAttributes = HTMLAttributes<HTMLTableRowElement>;
export type SVGElementAttributes = SVGAttributes<SVGElement>;

export type OnChangeFn<T> = (value: T) => void;

export type Expand<T> = T extends object
? T extends infer O
? { [K in keyof O]: O[K] }
: never
: T;

export type Prettify<T> = {
[K in keyof T]: T[K];
} & {};

export type Builder = {
[x: PropertyKey]: unknown;
// eslint-disable-next-line ts/no-explicit-any
action: Action<HTMLElement, any, any>;
};

export type DOMEl<T extends Element = HTMLDivElement> = Expand<{
/**
* Wheter to expose the underlying DOM element.
*/
el?: T;
}>;

export type TransitionProps<
T extends Transition = Transition,
In extends Transition = Transition,
Out extends Transition = Transition,
> = Expand<{
/**
* A transition function to use during both the in and out transitions.
*/
transition?: T;

/**
* The configuration to pass to the `transition` function.
*/
transitionConfig?: TransitionParams<T>;

/**
* A transition function to use during the in transition.
*
* If provided, this will override the `transition` function.
*/
inTransition?: In;

/**
* The configuration to pass to the `inTransition` function.
*/
inTransitionConfig?: TransitionParams<In>;

/**
* A transition function to use during the out transition.
*
* If provided, this will override the `transition` function.
*/
outTransition?: Out;

/**
* The configuration to pass to the `outTransition` function.
*/
outTransitionConfig?: TransitionParams<Out>;
}>;

type Primitive<T> = Omit<T, "style" | "id" | "children"> & { id?: string };
export type PrimitiveButtonAttributes = Primitive<HTMLButtonAttributes>;
export type PrimitiveDivAttributes = Primitive<HTMLDivAttributes>;
Expand All @@ -120,18 +34,18 @@ export type PrimitiveSpanAttributes = Primitive<HTMLSpanAttributes>;
export type PrimitiveImgAttributes = Primitive<HTMLImgAttributes>;
export type PrimitiveHeadingAttributes = Primitive<HTMLHeadingAttributes>;
export type PrimitiveLabelAttributes = Primitive<HTMLLabelAttributes>;
export type PrimitiveSVGAttributes = Primitive<SVGAttributes<SVGElement>>;
export type PrimitiveSVGAttributes = Primitive<SVGElementAttributes>;
export type PrimitiveAnchorAttributes = Primitive<HTMLAnchorAttributes>;
export type PrimitiveLiAttributes = Primitive<HTMLLiAttributes>;
export type PrimitiveElementAttributes = Primitive<HTMLAttributes<HTMLElement>>;
export type PrimitiveUListAttributes = Primitive<HTMLAttributes<HTMLUListElement>>;
export type PrimitiveElementAttributes = Primitive<HTMLElementAttributes>;
export type PrimitiveUListAttributes = Primitive<HTMLUListAttributes>;
export type PrimitiveTdAttributes = Primitive<HTMLTdAttributes>;
export type PrimitiveThAttributes = Primitive<HTMLThAttributes>;
export type PrimitiveTableAttributes = Primitive<HTMLTableAttributes>;
export type PrimitiveTbodyAttributes = Primitive<HTMLAttributes<HTMLTableSectionElement>>;
export type PrimitiveTrAttributes = Primitive<HTMLAttributes<HTMLTableRowElement>>;
export type PrimitiveTheadAttrbutes = Primitive<HTMLAttributes<HTMLTableSectionElement>>;
export type PrimitiveHeaderAttributes = Primitive<HTMLAttributes<HTMLElement>>;
export type PrimitiveTbodyAttributes = Primitive<HTMLTableSectionAttributes>;
export type PrimitiveTrAttributes = Primitive<HTMLTableRowAttributes>;
export type PrimitiveTheadAttrbutes = Primitive<HTMLTableSectionAttributes>;
export type PrimitiveHeaderAttributes = Primitive<HTMLElementAttributes>;

export type ElementRef = Box<HTMLElement | null>;

Expand Down Expand Up @@ -181,8 +95,6 @@ export type Fn = () => void;
// eslint-disable-next-line ts/no-explicit-any
export type AnyFn = (...args: any[]) => any;

export type ValueOf<T> = T[keyof T];

export type WithRefProps<T = {}> = T &
ReadableBoxedValues<{ id: string }> &
WritableBoxedValues<{ ref: HTMLElement | null }>;
4 changes: 2 additions & 2 deletions packages/bits-ui/src/lib/internal/useBodyScrollLock.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { SvelteMap } from "svelte/reactivity";
import { box } from "svelte-toolbelt";
import type { Fn } from "./types.js";
import type { NoopFn } from "./types.js";
import { isBrowser, isIOS } from "./is.js";
import { addEventListener } from "./events.js";
import { afterTick } from "./afterTick.js";
Expand All @@ -27,7 +27,7 @@ const useBodyLockStackCount = createSharedHook(() => {

let initialBodyStyle: Partial<CSSStyleDeclaration> = $state<Partial<CSSStyleDeclaration>>({});

let stopTouchMoveListener: Fn | null = null;
let stopTouchMoveListener: NoopFn | null = null;

function resetBodyStyle() {
if (!isBrowser) return;
Expand Down
4 changes: 2 additions & 2 deletions packages/bits-ui/src/lib/internal/useTimeoutFn.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { box } from "svelte-toolbelt";
import { onDestroy } from "svelte";
import type { Box } from "./box.svelte.js";
import type { AnyFn, Fn } from "./types.js";
import type { AnyFn, NoopFn } from "./types.js";
import { isBrowser } from "./is.js";

export type UseTimeoutFnOptions = {
Expand All @@ -22,7 +22,7 @@ export type Stoppable<StartFnArgs extends unknown[] = unknown[]> = {
/**
* Stop the effect from executing
*/
stop: Fn;
stop: NoopFn;

/**
* Start the effects
Expand Down
2 changes: 1 addition & 1 deletion packages/bits-ui/src/lib/shared/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,6 @@ export type WithoutChild<T> = T extends { child?: any } ? Omit<T, "child"> : T;
export type WithElementRef<T, U extends HTMLElement = HTMLElement> = T & { ref?: U | null };
export type { EditableSegmentPart } from "./date/field/types.js";
export type { Month } from "./date/types.js";
export type { WithChild, Expand, Without } from "$lib/internal/types.js";
export type { WithChild, Without } from "$lib/internal/types.js";
export { mergeProps } from "$lib/internal/mergeProps.js";
export { useId } from "$lib/internal/useId.js";
23 changes: 12 additions & 11 deletions sites/docs/content/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,30 @@ title: Introduction
description: The headless components for Svelte.
---

Bits UI is a collection of headless component primitives that enable you to build your own custom components. They have been thoughtfully designed to prioritize simplicity without compromising customizability.

Under the hood, most of these components are powered by [Melt UI](https://melt-ui.com), which provides an even lower-level builder API for creating headless components. Bits takes that API and wraps it in a more familiar component interface, allowing us to handle some quality of life improvements for you.
Bits UI is a collection of headless component primitives that enable you to build your own custom components. They are designed to prioritize accessibility and flexibility, enabling you to add your own styles and behaviors to the components.

## Unstyled

Bits UI components are unstyled by default. This means that they don't come with any styles out of the box. This is intentional, as it allows you to style them however you want. You can use the `class` prop to apply your own styles, or use the applied data attributes to target the components across your entire application. Check out the [styling](/docs/styling) section for more information.
Most Bits UI components are unstyled by default, it's up to you to style them however you please. You can use the `class` prop to apply your own styles, or use the applied data attributes to target the components across your entire application. Check out the [styling](/docs/styling) section for more information.

## Customizable

Each component offers a wide range of props for customizing behavior to fit your needs. Events are also dispatched for each interaction, allowing you to override the default functionality of the component.
Each component offers a wide range of props for customizing behavior to fit your needs. Events and callbacks are chainable, allowing you to override the default functionality of the component by simply cancelling the event.

## Accessible

A ton of effort has been invested in making sure that the components are accessible by default. They've been designed following the best practices for accessibility with the goal of making them usable by as many people as possible. Keyboard navigation, screen reader support, and focus management are all built-in.
Bits UI components have been designed following the [W3C ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.2/) with the goal of making them usable by as many people as possible. Keyboard navigation, screen reader support, and focus management are all built-in. If you notice an accessibility issue, please [raise an issue](https://github.com/huntabyte/bits-ui/issues/new) and we'll address it as soon as possible.

## Consistent

The API of each component has been designed with consistency in mind. This means that once you learn how to use one component, you should be able to use similar components with ease. While the docs are always there to help, the goal is to make the components as intuitive as possible.
Although the components do entirely different things, Bits UI has tried to align the APIs of each component with the same concepts and patterns. This means that once you learn how to use one component, learning similar components should be a quick and easy process. While the docs are always there to help, the goal is to make the components as intuitive as possible.

## Credits

- [Bitworks](https://bitworks.cz) - The design team behind the Bits UI documentation and example components.
- [Melt UI](https://melt-ui.com) - The underlying builder API that powers Bits.
- [Radix UI](https://radix-ui.com) - The incredible headless component APIs that we've taken heavy inspiration from.
- [React Spectrum](https://react-spectrum.adobe.com) - An incredible collection of headless components we've taken inspiration from.
Built by [@huntabyte](https://x.com/huntabyte). Documentation and example components designed by [@pavel_stianko](https://x.com/pavel_stianko) and [Bitworks](https://bitworks.cz).

### Inspiration & Code References

- [Melt UI](https://melt-ui.com) - The powerful builder API that inspired a lot of the internals of Bits UI.
- [Radix UI](https://radix-ui.com) - The incredible headless component APIs that we've taken heavy inspiration and code references from to build Bits UI.
- [React Spectrum](https://react-spectrum.adobe.com) - A world-class library of headless components, hooks, and utilities that we've taken inspiration from to build the various Date and Time components in Bits UI.

0 comments on commit 18a2383

Please sign in to comment.