diff --git a/packages/bits-ui/src/lib/bits/aspect-ratio/aspect-ratio.svelte.ts b/packages/bits-ui/src/lib/bits/aspect-ratio/aspect-ratio.svelte.ts new file mode 100644 index 000000000..0cef786e2 --- /dev/null +++ b/packages/bits-ui/src/lib/bits/aspect-ratio/aspect-ratio.svelte.ts @@ -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>; + +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); +} diff --git a/packages/bits-ui/src/lib/bits/aspect-ratio/components/aspect-ratio.svelte b/packages/bits-ui/src/lib/bits/aspect-ratio/components/aspect-ratio.svelte index 381185b58..8c7178390 100644 --- a/packages/bits-ui/src/lib/bits/aspect-ratio/components/aspect-ratio.svelte +++ b/packages/bits-ui/src/lib/bits/aspect-ratio/components/aspect-ratio.svelte @@ -1,20 +1,37 @@
-
- {@render children?.()} -
+ {#if child} + {@render child({ props: mergedProps })} + {:else} +
+ {@render children?.()} +
+ {/if}
diff --git a/packages/bits-ui/src/lib/bits/aspect-ratio/types.ts b/packages/bits-ui/src/lib/bits/aspect-ratio/types.ts index 714224973..b4d09c34d 100644 --- a/packages/bits-ui/src/lib/bits/aspect-ratio/types.ts +++ b/packages/bits-ui/src/lib/bits/aspect-ratio/types.ts @@ -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; diff --git a/packages/bits-ui/src/lib/internal/types.ts b/packages/bits-ui/src/lib/internal/types.ts index 6287b2d44..165ee8944 100644 --- a/packages/bits-ui/src/lib/internal/types.ts +++ b/packages/bits-ui/src/lib/internal/types.ts @@ -1,5 +1,4 @@ import type { Snippet } from "svelte"; -import type { Action } from "svelte/action"; import type { HTMLAnchorAttributes, HTMLAttributes, @@ -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 extends object ? T : never; -// eslint-disable-next-line ts/no-explicit-any -export type Transition = (node: Element, params?: any) => TransitionConfig; -export type TransitionParams = Parameters[1]; - export type HTMLDivAttributes = HTMLAttributes; export type HTMLSpanAttributes = HTMLAttributes; export type HTMLHeadingAttributes = HTMLAttributes; - -export type OmitOpen = Omit; -export type OmitValue = Omit; -export type OmitChecked = Omit; -export type OmitPressed = Omit; -export type OmitForceVisible = Omit; -export type OmitIds = Omit; -export type OmitDates = Omit< - T, - | "value" - | "defaultValue" - | "placeholder" - | "defaultPlaceholder" - | "onPlaceholderChange" - | "onValueChange" - | "ids" ->; - -export type OmitFloating = OmitOpen< - Omit ->; +export type HTMLUListAttributes = HTMLAttributes; +export type HTMLElementAttributes = HTMLAttributes; +export type HTMLTableSectionAttributes = HTMLAttributes; +export type HTMLTableRowAttributes = HTMLAttributes; +export type SVGElementAttributes = SVGAttributes; export type OnChangeFn = (value: T) => void; -export type Expand = T extends object - ? T extends infer O - ? { [K in keyof O]: O[K] } - : never - : T; - -export type Prettify = { - [K in keyof T]: T[K]; -} & {}; - -export type Builder = { - [x: PropertyKey]: unknown; - // eslint-disable-next-line ts/no-explicit-any - action: Action; -}; - -export type DOMEl = 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; - - /** - * 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; - - /** - * 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; -}>; - type Primitive = Omit & { id?: string }; export type PrimitiveButtonAttributes = Primitive; export type PrimitiveDivAttributes = Primitive; @@ -120,18 +34,18 @@ export type PrimitiveSpanAttributes = Primitive; export type PrimitiveImgAttributes = Primitive; export type PrimitiveHeadingAttributes = Primitive; export type PrimitiveLabelAttributes = Primitive; -export type PrimitiveSVGAttributes = Primitive>; +export type PrimitiveSVGAttributes = Primitive; export type PrimitiveAnchorAttributes = Primitive; export type PrimitiveLiAttributes = Primitive; -export type PrimitiveElementAttributes = Primitive>; -export type PrimitiveUListAttributes = Primitive>; +export type PrimitiveElementAttributes = Primitive; +export type PrimitiveUListAttributes = Primitive; export type PrimitiveTdAttributes = Primitive; export type PrimitiveThAttributes = Primitive; export type PrimitiveTableAttributes = Primitive; -export type PrimitiveTbodyAttributes = Primitive>; -export type PrimitiveTrAttributes = Primitive>; -export type PrimitiveTheadAttrbutes = Primitive>; -export type PrimitiveHeaderAttributes = Primitive>; +export type PrimitiveTbodyAttributes = Primitive; +export type PrimitiveTrAttributes = Primitive; +export type PrimitiveTheadAttrbutes = Primitive; +export type PrimitiveHeaderAttributes = Primitive; export type ElementRef = Box; @@ -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[keyof T]; - export type WithRefProps = T & ReadableBoxedValues<{ id: string }> & WritableBoxedValues<{ ref: HTMLElement | null }>; diff --git a/packages/bits-ui/src/lib/internal/useBodyScrollLock.svelte.ts b/packages/bits-ui/src/lib/internal/useBodyScrollLock.svelte.ts index 2abb70933..13796ede8 100644 --- a/packages/bits-ui/src/lib/internal/useBodyScrollLock.svelte.ts +++ b/packages/bits-ui/src/lib/internal/useBodyScrollLock.svelte.ts @@ -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"; @@ -27,7 +27,7 @@ const useBodyLockStackCount = createSharedHook(() => { let initialBodyStyle: Partial = $state>({}); - let stopTouchMoveListener: Fn | null = null; + let stopTouchMoveListener: NoopFn | null = null; function resetBodyStyle() { if (!isBrowser) return; diff --git a/packages/bits-ui/src/lib/internal/useTimeoutFn.svelte.ts b/packages/bits-ui/src/lib/internal/useTimeoutFn.svelte.ts index 9fe56ecb3..0329500ff 100644 --- a/packages/bits-ui/src/lib/internal/useTimeoutFn.svelte.ts +++ b/packages/bits-ui/src/lib/internal/useTimeoutFn.svelte.ts @@ -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 = { @@ -22,7 +22,7 @@ export type Stoppable = { /** * Stop the effect from executing */ - stop: Fn; + stop: NoopFn; /** * Start the effects diff --git a/packages/bits-ui/src/lib/shared/index.ts b/packages/bits-ui/src/lib/shared/index.ts index a1d9af98b..d758ff138 100644 --- a/packages/bits-ui/src/lib/shared/index.ts +++ b/packages/bits-ui/src/lib/shared/index.ts @@ -41,6 +41,6 @@ export type WithoutChild = T extends { child?: any } ? Omit : T; export type WithElementRef = 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"; diff --git a/sites/docs/content/introduction.md b/sites/docs/content/introduction.md index d0d02ede0..6e5fa4008 100644 --- a/sites/docs/content/introduction.md +++ b/sites/docs/content/introduction.md @@ -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.