diff --git a/apps/docs/src/examples/meter.module.css b/apps/docs/src/examples/meter.module.css
new file mode 100644
index 00000000..6910f097
--- /dev/null
+++ b/apps/docs/src/examples/meter.module.css
@@ -0,0 +1,38 @@
+.meter {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ width: 300px;
+}
+
+.meter__label-container {
+ display: flex;
+ justify-content: space-between;
+}
+
+.meter__label,
+.meter__value-label {
+ color: hsl(240 4% 16%);
+ font-size: 14px;
+}
+
+.meter__track {
+ height: 10px;
+ background-color: hsl(240 6% 90%);
+}
+
+.meter__fill {
+ background-color: hsl(200 98% 39%);
+ height: 100%;
+ width: var(--kb-meter-fill-width);
+ transition: width 250ms linear;
+}
+
+[data-kb-theme="dark"] .meter__label,
+[data-kb-theme="dark"] .meter__value-label {
+ color: hsl(0 100% 100% / 0.9);
+}
+
+[data-kb-theme="dark"] .meter__track {
+ background-color: hsl(240 5% 26%);
+}
diff --git a/apps/docs/src/examples/meter.tsx b/apps/docs/src/examples/meter.tsx
new file mode 100644
index 00000000..60dd74b9
--- /dev/null
+++ b/apps/docs/src/examples/meter.tsx
@@ -0,0 +1,51 @@
+import { Meter } from "@kobalte/core/meter";
+
+import style from "./meter.module.css";
+
+export function BasicExample() {
+ return (
+
+
+ Batter Level:
+
+
+
+
+
+
+ );
+}
+
+export function CustomValueScaleExample() {
+ return (
+
+
+ Disk Space Usage:
+
+
+
+
+
+
+ );
+}
+
+export function CustomValueLabelExample() {
+ return (
+ `${value} of ${max} tasks completed`}
+ class={style.meter}
+ >
+
+ Processing...
+
+
+
+
+
+
+ );
+}
diff --git a/apps/docs/src/routes/docs/core.tsx b/apps/docs/src/routes/docs/core.tsx
index d4b1efb2..1a30f001 100644
--- a/apps/docs/src/routes/docs/core.tsx
+++ b/apps/docs/src/routes/docs/core.tsx
@@ -97,6 +97,11 @@ const CORE_NAV_SECTIONS: NavSection[] = [
title: "Menubar",
href: "/docs/core/components/menubar",
},
+ {
+ title: "Meter",
+ href: "/docs/core/components/meter",
+ status: "new",
+ },
{
title: "Navigation Menu",
href: "/docs/core/components/navigation-menu",
diff --git a/apps/docs/src/routes/docs/core/components/meter.mdx b/apps/docs/src/routes/docs/core/components/meter.mdx
new file mode 100644
index 00000000..b5e9310a
--- /dev/null
+++ b/apps/docs/src/routes/docs/core/components/meter.mdx
@@ -0,0 +1,197 @@
+import { Preview, TabsSnippets } from "../../../../components";
+import {
+ BasicExample,
+ CustomValueLabelExample,
+ CustomValueScaleExample,
+} from "../../../../examples/meter";
+
+# Meter
+
+Displays numeric value that varies within a defined range
+
+## Import
+
+```ts
+import { Meter } from "@kobalte/core/meter";
+// or
+import { Root, Label, ... } from "@kobalte/core/meter";
+// or (deprecated)
+import { Meter } from "@kobalte/core";
+```
+
+## Features
+
+- Exposed to assistive technology as a meter via ARIA.
+- Labeling support for accessibility.
+- Internationalized number formatting as a percentage or value.
+
+## Anatomy
+
+The meter consists of:
+
+- **Meter:** The root container for a meter.
+- **Meter.Label:** An accessible label that gives the user information on the meter.
+- **Meter.ValueLabel:** The accessible label text representing the current value in a human-readable format.
+- **Meter.Track:** The component that visually represents the meter track.
+- **Meter.Fill:** The component that visually represents the meter value.
+
+```tsx
+
+
+
+
+
+
+
+```
+
+## Example
+
+
+
+
+
+
+
+ index.tsx
+ style.css
+
+ {/* */}
+
+ ```tsx
+ import { Meter } from "@kobalte/core/meter";
+ import "./style.css";
+
+ function App() {
+ return (
+
+
+ Battery Level:
+
+
+
+
+
+
+ );
+ }
+ ```
+
+
+
+ ```css
+ .meter {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ width: 300px;
+ }
+
+ .meter__label-container {
+ display: flex;
+ justify-content: space-between;
+ }
+
+ .meter__label,
+ .meter__value-label {
+ color: hsl(240 4% 16%);
+ font-size: 14px;
+ }
+
+ .meter__track {
+ height: 10px;
+ background-color: hsl(240 6% 90%);
+ }
+
+ .meter__fill {
+ background-color: hsl(200 98% 39%);
+ height: 100%;
+ width: var(--kb-meter-fill-width);
+ transition: width 250ms linear;
+ }
+
+ ```
+
+
+ {/* */}
+
+
+## Usage
+
+### Custom value scale
+
+By default, the `value` prop represents the current value of meter, as the minimum and maximum values default to 0 and 100, respectively. Alternatively, a different scale can be used by setting the `minValue` and `maxValue` props.
+
+
+
+
+
+```tsx {0}
+
+
+ Disk Space Usage:
+
+
+
+
+
+
+```
+
+### Custom value label
+
+The `getValueLabel` prop allows the formatted value used in `Meter.ValueLabel` and ARIA to be replaced with a custom string. It receives the current value, min and max values as parameters.
+
+
+
+
+
+```tsx {4}
+ `${value} of ${max} tasks completed`}
+ class="meter"
+>
+
+ Processing...
+
+
+
+
+
+
+```
+
+### Meter fill width
+
+We expose a CSS custom property `--kb-meter-fill-width` which corresponds to the percentage of meterion (ex: 80%). If you are building a linear meter, you can use it to set the width of the `Meter.Fill` component in CSS.
+
+## API Reference
+
+### Meter
+
+`Meter` is equivalent to the `Root` import from `@kobalte/core/meter` (and deprecated `Meter.Root`).
+
+| Prop | Description |
+| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| value | `number`
The meter value. |
+| minValue | `number`
The minimum meter value. |
+| maxValue | `number`
The maximum meter value. |
+| getValueLabel | `(params: { value: number; min: number; max: number }) => string`
A function to get the accessible label text representing the current value in a human-readable format. If not provided, the value label will be read as a percentage of the max value. |
+
+| Data attribute | Description |
+| :------------- | :---------- |
+
+`Meter.Label`, `Meter.ValueLabel`, `Meter.Track` and `Meter.Fill` shares the same data-attributes.
+
+## Rendered elements
+
+| Component | Default rendered element |
+| :----------------- | :----------------------- |
+| `Meter` | `div` |
+| `Meter.Label` | `span` |
+| `Meter.ValueLabel` | `div` |
+| `Meter.Track` | `div` |
+| `Meter.Fill` | `div` |
diff --git a/packages/core/src/index.tsx b/packages/core/src/index.tsx
index 4a72a633..e3f43f03 100644
--- a/packages/core/src/index.tsx
+++ b/packages/core/src/index.tsx
@@ -28,6 +28,7 @@ export * as Image from "./image";
export * as Link from "./link";
export * as Listbox from "./listbox";
export * as Menubar from "./menubar";
+export * as Meter from "./meter";
export * as NumberField from "./number-field";
export * as Pagination from "./pagination";
export * as Popover from "./popover";
diff --git a/packages/core/src/meter/index.tsx b/packages/core/src/meter/index.tsx
new file mode 100644
index 00000000..64037c15
--- /dev/null
+++ b/packages/core/src/meter/index.tsx
@@ -0,0 +1,66 @@
+import {
+ MeterFill as Fill,
+ type MeterFillCommonProps,
+ type MeterFillOptions,
+ type MeterFillProps,
+ type MeterFillRenderProps,
+} from "./meter-fill";
+import {
+ MeterLabel as Label,
+ type MeterLabelCommonProps,
+ type MeterLabelOptions,
+ type MeterLabelProps,
+ type MeterLabelRenderProps,
+} from "./meter-label";
+import {
+ type MeterRootCommonProps,
+ type MeterRootOptions,
+ type MeterRootProps,
+ type MeterRootRenderProps,
+ MeterRoot as Root,
+} from "./meter-root";
+import {
+ type MeterTrackCommonProps,
+ type MeterTrackOptions,
+ type MeterTrackProps,
+ type MeterTrackRenderProps,
+ MeterTrack as Track,
+} from "./meter-track";
+import {
+ type MeterValueLabelCommonProps,
+ type MeterValueLabelOptions,
+ type MeterValueLabelProps,
+ type MeterValueLabelRenderProps,
+ MeterValueLabel as ValueLabel,
+} from "./meter-value-label";
+
+export type {
+ MeterFillOptions,
+ MeterFillCommonProps,
+ MeterFillRenderProps,
+ MeterFillProps,
+ MeterLabelOptions,
+ MeterLabelCommonProps,
+ MeterLabelRenderProps,
+ MeterLabelProps,
+ MeterRootOptions,
+ MeterRootCommonProps,
+ MeterRootRenderProps,
+ MeterRootProps,
+ MeterTrackOptions,
+ MeterTrackCommonProps,
+ MeterTrackRenderProps,
+ MeterTrackProps,
+ MeterValueLabelOptions,
+ MeterValueLabelCommonProps,
+ MeterValueLabelRenderProps,
+ MeterValueLabelProps,
+};
+export { Fill, Label, Root, Track, ValueLabel };
+
+export const Meter = Object.assign(Root, {
+ Fill,
+ Label,
+ Track,
+ ValueLabel,
+});
diff --git a/packages/core/src/meter/meter-context.tsx b/packages/core/src/meter/meter-context.tsx
new file mode 100644
index 00000000..794eccfe
--- /dev/null
+++ b/packages/core/src/meter/meter-context.tsx
@@ -0,0 +1,28 @@
+import { type Accessor, createContext, useContext } from "solid-js";
+
+export interface MeterDataSet {}
+
+export interface MeterContextValue {
+ dataset: Accessor;
+ value: Accessor;
+ valuePercent: Accessor;
+ valueLabel: Accessor;
+ meterFillWidth: Accessor;
+ labelId: Accessor;
+ generateId: (part: string) => string;
+ registerLabelId: (id: string) => () => void;
+}
+
+export const MeterContext = createContext();
+
+export function useMeterContext() {
+ const context = useContext(MeterContext);
+
+ if (context === undefined) {
+ throw new Error(
+ "[kobalte]: `useMeterContext` must be used within a `Meter.Root` component",
+ );
+ }
+
+ return context;
+}
diff --git a/packages/core/src/meter/meter-fill.tsx b/packages/core/src/meter/meter-fill.tsx
new file mode 100644
index 00000000..e69f74bf
--- /dev/null
+++ b/packages/core/src/meter/meter-fill.tsx
@@ -0,0 +1,49 @@
+import { type JSX, type ValidComponent, splitProps } from "solid-js";
+
+import { combineStyle } from "@solid-primitives/props";
+import {
+ type ElementOf,
+ Polymorphic,
+ type PolymorphicProps,
+} from "../polymorphic";
+import { type MeterDataSet, useMeterContext } from "./meter-context";
+
+export interface MeterFillOptions {}
+
+export interface MeterFillCommonProps {
+ style?: JSX.CSSProperties | string;
+}
+
+export interface MeterFillRenderProps
+ extends MeterFillCommonProps,
+ MeterDataSet {}
+
+export type MeterFillProps<
+ T extends ValidComponent | HTMLElement = HTMLElement,
+> = MeterFillOptions & Partial>>;
+
+/**
+ * The component that visually represents the meter value.
+ * Used to visually show the fill of `Meter.Track`.
+ */
+export function MeterFill(
+ props: PolymorphicProps>,
+) {
+ const context = useMeterContext();
+
+ const [local, others] = splitProps(props as MeterFillProps, ["style"]);
+
+ return (
+
+ as="div"
+ style={combineStyle(
+ {
+ "--kb-meter-fill-width": context.meterFillWidth(),
+ },
+ local.style,
+ )}
+ {...context.dataset()}
+ {...others}
+ />
+ );
+}
diff --git a/packages/core/src/meter/meter-label.tsx b/packages/core/src/meter/meter-label.tsx
new file mode 100644
index 00000000..5cd951ee
--- /dev/null
+++ b/packages/core/src/meter/meter-label.tsx
@@ -0,0 +1,57 @@
+import { mergeDefaultProps } from "@kobalte/utils";
+import {
+ type ValidComponent,
+ createEffect,
+ onCleanup,
+ splitProps,
+} from "solid-js";
+
+import {
+ type ElementOf,
+ Polymorphic,
+ type PolymorphicProps,
+} from "../polymorphic";
+import { type MeterDataSet, useMeterContext } from "./meter-context";
+
+export interface MeterLabelOptions {}
+
+export interface MeterLabelCommonProps {
+ id: string;
+}
+
+export interface MeterLabelRenderProps
+ extends MeterLabelCommonProps,
+ MeterDataSet {}
+
+export type MeterLabelProps<
+ T extends ValidComponent | HTMLElement = HTMLElement,
+> = MeterLabelOptions & Partial>>;
+
+/**
+ * An accessible label that gives the user information on the meter.
+ */
+export function MeterLabel(
+ props: PolymorphicProps>,
+) {
+ const context = useMeterContext();
+
+ const mergedProps = mergeDefaultProps(
+ {
+ id: context.generateId("label"),
+ },
+ props as MeterLabelProps,
+ );
+
+ const [local, others] = splitProps(mergedProps, ["id"]);
+
+ createEffect(() => onCleanup(context.registerLabelId(local.id)));
+
+ return (
+
+ as="span"
+ id={local.id}
+ {...context.dataset()}
+ {...others}
+ />
+ );
+}
diff --git a/packages/core/src/meter/meter-root.tsx b/packages/core/src/meter/meter-root.tsx
new file mode 100644
index 00000000..8b64d7d8
--- /dev/null
+++ b/packages/core/src/meter/meter-root.tsx
@@ -0,0 +1,175 @@
+/*
+ * Portions of this file are based on code from react-spectrum.
+ * Apache License Version 2.0, Copyright 2020 Adobe.
+ *
+ * Credits to the React Spectrum team:
+ * https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/meter/src/useMeter.ts
+ */
+
+import { clamp, createGenerateId, mergeDefaultProps } from "@kobalte/utils";
+import {
+ type Accessor,
+ type ValidComponent,
+ createMemo,
+ createSignal,
+ createUniqueId,
+ splitProps,
+} from "solid-js";
+
+import { createNumberFormatter } from "../i18n";
+import {
+ type ElementOf,
+ Polymorphic,
+ type PolymorphicProps,
+} from "../polymorphic";
+import { createRegisterId } from "../primitives";
+import {
+ MeterContext,
+ type MeterContextValue,
+ type MeterDataSet,
+} from "./meter-context";
+
+interface GetValueLabelParams {
+ value: number;
+ min: number;
+ max: number;
+}
+export interface MeterRootOptions {
+ /**
+ * The meter value.
+ * @default 0
+ */
+ value?: number;
+
+ /**
+ * The minimum meter value.
+ * @default 0
+ */
+ minValue?: number;
+
+ /**
+ * The maximum meter value.
+ * @default 100
+ */
+ maxValue?: number;
+
+ /**
+ * A function to get the accessible label text representing the current value in a human-readable format.
+ * If not provided, the value label will be read as a percentage of the max value.
+ */
+ getValueLabel?: (params: GetValueLabelParams) => string;
+}
+
+export interface MeterRootCommonProps {
+ id: string;
+ role: string;
+ "aria-valuenow": number | undefined;
+ "aria-valuemin": number;
+ "aria-valuemax": number;
+ "aria-valuetext": string | undefined;
+ "aria-labelledby": string | undefined;
+}
+
+export interface MeterRootRenderProps
+ extends MeterRootCommonProps,
+ MeterDataSet {}
+
+export type MeterRootProps<
+ T extends ValidComponent | HTMLElement = HTMLElement,
+> = MeterRootOptions & Partial>>;
+
+/**
+ * Meter displays numeric value that varies within a defined range.
+ */
+export function MeterRoot(
+ props: PolymorphicProps>,
+) {
+ const defaultId = `meter-${createUniqueId()}`;
+
+ const mergedProps = mergeDefaultProps(
+ {
+ id: defaultId,
+ value: 0,
+ minValue: 0,
+ maxValue: 100,
+ role: "meter",
+ indeterminate: false,
+ },
+ props as MeterRootProps,
+ );
+
+ const [local, others] = splitProps(mergedProps, [
+ "value",
+ "minValue",
+ "maxValue",
+ "getValueLabel",
+ "role",
+ "aria-valuetext",
+ "aria-labelledby",
+ "aria-valuemax",
+ "aria-valuemin",
+ "aria-valuenow",
+ "indeterminate",
+ ]);
+
+ const [labelId, setLabelId] = createSignal();
+
+ const defaultFormatter = createNumberFormatter(() => ({ style: "percent" }));
+
+ const value = () => {
+ return clamp(local.value!, local.minValue!, local.maxValue!);
+ };
+
+ const valuePercent = () => {
+ return (value() - local.minValue!) / (local.maxValue! - local.minValue!);
+ };
+
+ const valueLabel = () => {
+ if (local.indeterminate) {
+ return undefined;
+ }
+ if (local.getValueLabel) {
+ return local.getValueLabel({
+ value: value(),
+ min: local.minValue!,
+ max: local.maxValue!,
+ });
+ }
+
+ return defaultFormatter().format(valuePercent());
+ };
+
+ const meterFillWidth = () => {
+ return `${Math.round(valuePercent() * 100)}%`;
+ };
+
+ const dataset: Accessor = createMemo(() => {
+ return {};
+ });
+ const context: MeterContextValue = {
+ dataset,
+ value,
+ valuePercent,
+ valueLabel,
+ labelId,
+ meterFillWidth,
+ generateId: createGenerateId(() => others.id!),
+ registerLabelId: createRegisterId(setLabelId),
+ };
+
+ return (
+
+
+ as="div"
+ role={local.role || "meter"}
+ aria-valuenow={local.indeterminate ? undefined : value()}
+ aria-valuemin={local.minValue}
+ aria-valuemax={local.maxValue}
+ aria-valuetext={valueLabel()}
+ aria-labelledby={labelId()}
+ {...dataset()}
+ {...others}
+ />
+
+ );
+}
diff --git a/packages/core/src/meter/meter-track.tsx b/packages/core/src/meter/meter-track.tsx
new file mode 100644
index 00000000..fd3a4c4a
--- /dev/null
+++ b/packages/core/src/meter/meter-track.tsx
@@ -0,0 +1,37 @@
+import type { ValidComponent } from "solid-js";
+import {
+ type ElementOf,
+ Polymorphic,
+ type PolymorphicProps,
+} from "../polymorphic";
+import { type MeterDataSet, useMeterContext } from "./meter-context";
+
+export interface MeterTrackOptions {}
+
+export interface MeterTrackCommonProps {}
+
+export interface MeterTrackRenderProps
+ extends MeterTrackCommonProps,
+ MeterDataSet {}
+
+export type MeterTrackProps<
+ T extends ValidComponent | HTMLElement = HTMLElement,
+> = MeterTrackOptions & Partial>>;
+
+/**
+ * The component that visually represents the meter track.
+ * Act as a container for `Meter.Fill`.
+ */
+export function MeterTrack(
+ props: PolymorphicProps>,
+) {
+ const context = useMeterContext();
+
+ return (
+
+ as="div"
+ {...context.dataset()}
+ {...(props as MeterTrackProps)}
+ />
+ );
+}
diff --git a/packages/core/src/meter/meter-value-label.tsx b/packages/core/src/meter/meter-value-label.tsx
new file mode 100644
index 00000000..84e639b2
--- /dev/null
+++ b/packages/core/src/meter/meter-value-label.tsx
@@ -0,0 +1,43 @@
+import type { JSX, ValidComponent } from "solid-js";
+
+import {
+ type ElementOf,
+ Polymorphic,
+ type PolymorphicProps,
+} from "../polymorphic";
+import { type MeterDataSet, useMeterContext } from "./meter-context";
+
+export interface MeterValueLabelOptions {}
+
+export interface MeterValueLabelCommonProps<
+ T extends HTMLElement = HTMLElement,
+> {}
+
+export interface MeterValueLabelRenderProps
+ extends MeterValueLabelCommonProps,
+ MeterDataSet {
+ children: JSX.Element;
+}
+
+export type MeterValueLabelProps<
+ T extends ValidComponent | HTMLElement = HTMLElement,
+> = MeterValueLabelOptions & Partial>>;
+
+/**
+ * The accessible label text representing the current value in a human-readable format.
+ */
+export function MeterValueLabel(
+ props: PolymorphicProps>,
+) {
+ const context = useMeterContext();
+
+ return (
+
+ as="div"
+ {...context.dataset()}
+ {...(props as MeterValueLabelProps)}
+ >
+ {context.valueLabel()}
+
+ );
+}
diff --git a/packages/core/src/meter/meter.test.tsx b/packages/core/src/meter/meter.test.tsx
new file mode 100644
index 00000000..ee70896b
--- /dev/null
+++ b/packages/core/src/meter/meter.test.tsx
@@ -0,0 +1,132 @@
+/*
+ * Portions of this file are based on code from react-spectrum.
+ * Apache License Version 2.0, Copyright 2020 Adobe.
+ *
+ * Credits to the React Spectrum team:
+ * https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/meter/src/useMeter.ts
+ * https://github.com/adobe/react-spectrum/blob/main/packages/%40react-spectrum/meter/test/Meter.test.js
+
+*/
+
+import { render } from "@solidjs/testing-library";
+
+import * as Meter from ".";
+
+describe("Meter", () => {
+ it("handles defaults", () => {
+ const { getByRole, getByTestId } = render(() => (
+
+ Meter
+
+
+
+
+
+ ));
+
+ const meter = getByRole("meter");
+ expect(meter).toHaveAttribute("aria-valuemin", "0");
+ expect(meter).toHaveAttribute("aria-valuemax", "100");
+ expect(meter).toHaveAttribute("aria-valuenow", "0");
+ expect(meter).toHaveAttribute("aria-valuetext", "0%");
+
+ const valueLabel = getByTestId("value-label");
+ expect(valueLabel).toHaveTextContent("0%");
+
+ const labelId = meter.getAttribute("aria-labelledby");
+ expect(labelId).toBeDefined();
+
+ const label = document.getElementById(labelId!);
+ expect(label).toHaveTextContent("Meter");
+ });
+
+ it("supports custom value label", () => {
+ const { getByRole, getByTestId } = render(() => (
+ `${value} of ${max} completed`}
+ >
+ Meter
+
+
+
+
+
+ ));
+
+ const meter = getByRole("meter");
+ expect(meter).toHaveAttribute("aria-valuetext", "3 of 10 completed");
+
+ const valueLabel = getByTestId("value-label");
+ expect(valueLabel).toHaveTextContent("3 of 10 completed");
+ });
+
+ it("should update all fields by value", () => {
+ const { getByRole } = render(() => (
+
+ Meter
+
+
+
+
+
+ ));
+
+ const meter = getByRole("meter");
+
+ expect(meter).toHaveAttribute("aria-valuemin", "0");
+ expect(meter).toHaveAttribute("aria-valuemax", "100");
+ expect(meter).toHaveAttribute("aria-valuenow", "30");
+ expect(meter).toHaveAttribute("aria-valuetext", "30%");
+ });
+
+ it("should clamps values to 'minValue'", () => {
+ const { getByRole } = render(() => (
+
+ Meter
+
+
+
+
+
+ ));
+
+ const meter = getByRole("meter");
+ expect(meter).toHaveAttribute("aria-valuenow", "0");
+ expect(meter).toHaveAttribute("aria-valuetext", "0%");
+ });
+
+ it("should clamps values to 'maxValue'", () => {
+ const { getByRole } = render(() => (
+
+ Meter
+
+
+
+
+
+ ));
+
+ const meter = getByRole("meter");
+ expect(meter).toHaveAttribute("aria-valuenow", "100");
+ expect(meter).toHaveAttribute("aria-valuetext", "100%");
+ });
+
+ it("supports negative values", () => {
+ const { getByRole } = render(() => (
+
+ Meter
+
+
+
+
+
+ ));
+
+ const meter = getByRole("meter");
+ expect(meter).toHaveAttribute("aria-valuenow", "0");
+ expect(meter).toHaveAttribute("aria-valuetext", "50%");
+ });
+});
diff --git a/packages/core/src/progress/progress-context.tsx b/packages/core/src/progress/progress-context.tsx
index e9faceda..bbaa168f 100644
--- a/packages/core/src/progress/progress-context.tsx
+++ b/packages/core/src/progress/progress-context.tsx
@@ -1,19 +1,15 @@
import { type Accessor, createContext, useContext } from "solid-js";
+import type { MeterContextValue, MeterDataSet } from "../meter/meter-context";
-export interface ProgressDataSet {
+export interface ProgressDataSet extends MeterDataSet {
"data-progress": "loading" | "complete" | undefined;
"data-indeterminate": string | undefined;
}
-export interface ProgressContextValue {
+export interface ProgressContextValue
+ extends Omit {
dataset: Accessor;
- value: Accessor;
- valuePercent: Accessor;
- valueLabel: Accessor;
progressFillWidth: Accessor;
- labelId: Accessor;
- generateId: (part: string) => string;
- registerLabelId: (id: string) => () => void;
}
export const ProgressContext = createContext();
diff --git a/packages/core/src/progress/progress-fill.tsx b/packages/core/src/progress/progress-fill.tsx
index ca029122..7b0f79d4 100644
--- a/packages/core/src/progress/progress-fill.tsx
+++ b/packages/core/src/progress/progress-fill.tsx
@@ -1,22 +1,24 @@
-import { type JSX, type ValidComponent, splitProps } from "solid-js";
+import { type Component, type ValidComponent, splitProps } from "solid-js";
import { combineStyle } from "@solid-primitives/props";
import {
- type ElementOf,
- Polymorphic,
- type PolymorphicProps,
-} from "../polymorphic";
+ Meter,
+ type MeterFillCommonProps,
+ type MeterFillOptions,
+ type MeterFillRenderProps,
+} from "../meter";
+import type { ElementOf, PolymorphicProps } from "../polymorphic";
import { type ProgressDataSet, useProgressContext } from "./progress-context";
-export interface ProgressFillOptions {}
+export interface ProgressFillOptions extends MeterFillOptions {}
-export interface ProgressFillCommonProps {
- style?: JSX.CSSProperties | string;
-}
+export interface ProgressFillCommonProps
+ extends MeterFillCommonProps {}
export interface ProgressFillRenderProps
extends ProgressFillCommonProps,
- ProgressDataSet {}
+ ProgressDataSet,
+ MeterFillRenderProps {}
export type ProgressFillProps<
T extends ValidComponent | HTMLElement = HTMLElement,
@@ -34,8 +36,9 @@ export function ProgressFill(
const [local, others] = splitProps(props as ProgressFillProps, ["style"]);
return (
-
- as="div"
+ >
+ >
style={combineStyle(
{
"--kb-progress-fill-width": context.progressFillWidth(),
diff --git a/packages/core/src/progress/progress-label.tsx b/packages/core/src/progress/progress-label.tsx
index 541cbdee..e6016cc2 100644
--- a/packages/core/src/progress/progress-label.tsx
+++ b/packages/core/src/progress/progress-label.tsx
@@ -1,5 +1,6 @@
import { mergeDefaultProps } from "@kobalte/utils";
import {
+ type Component,
type ValidComponent,
createEffect,
onCleanup,
@@ -7,20 +8,22 @@ import {
} from "solid-js";
import {
- type ElementOf,
- Polymorphic,
- type PolymorphicProps,
-} from "../polymorphic";
+ Meter,
+ type MeterLabelCommonProps,
+ type MeterLabelOptions,
+ type MeterLabelRenderProps,
+} from "../meter";
+import type { ElementOf, PolymorphicProps } from "../polymorphic";
import { type ProgressDataSet, useProgressContext } from "./progress-context";
-export interface ProgressLabelOptions {}
+export interface ProgressLabelOptions extends MeterLabelOptions {}
-export interface ProgressLabelCommonProps {
- id: string;
-}
+export interface ProgressLabelCommonProps
+ extends MeterLabelCommonProps {}
export interface ProgressLabelRenderProps
- extends ProgressLabelCommonProps,
+ extends MeterLabelRenderProps,
+ ProgressLabelCommonProps,
ProgressDataSet {}
export type ProgressLabelProps<
@@ -41,14 +44,14 @@ export function ProgressLabel(
},
props as ProgressLabelProps,
);
-
const [local, others] = splitProps(mergedProps, ["id"]);
createEffect(() => onCleanup(context.registerLabelId(local.id)));
return (
-
- as="span"
+ >
+ >
id={local.id}
{...context.dataset()}
{...others}
diff --git a/packages/core/src/progress/progress-root.tsx b/packages/core/src/progress/progress-root.tsx
index b7dfaf62..22ae0a5d 100644
--- a/packages/core/src/progress/progress-root.tsx
+++ b/packages/core/src/progress/progress-root.tsx
@@ -9,6 +9,7 @@
import { clamp, createGenerateId, mergeDefaultProps } from "@kobalte/utils";
import {
type Accessor,
+ type Component,
type ValidComponent,
createMemo,
createSignal,
@@ -18,10 +19,12 @@ import {
import { createNumberFormatter } from "../i18n";
import {
- type ElementOf,
- Polymorphic,
- type PolymorphicProps,
-} from "../polymorphic";
+ Meter,
+ type MeterRootCommonProps,
+ type MeterRootOptions,
+ type MeterRootRenderProps,
+} from "../meter";
+import type { ElementOf, PolymorphicProps } from "../polymorphic";
import { createRegisterId } from "../primitives";
import {
ProgressContext,
@@ -29,56 +32,21 @@ import {
type ProgressDataSet,
} from "./progress-context";
-interface GetValueLabelParams {
- value: number;
- min: number;
- max: number;
-}
-
-export interface ProgressRootOptions {
- /**
- * The progress value.
- * @default 0
- */
- value?: number;
-
- /**
- * The minimum progress value.
- * @default 0
- */
- minValue?: number;
-
- /**
- * The maximum progress value.
- * @default 100
- */
- maxValue?: number;
-
+export interface ProgressRootOptions
+ extends Omit {
/** Whether the progress is in an indeterminate state. */
indeterminate?: boolean;
-
- /**
- * A function to get the accessible label text representing the current value in a human-readable format.
- * If not provided, the value label will be read as a percentage of the max value.
- */
- getValueLabel?: (params: GetValueLabelParams) => string;
}
-export interface ProgressRootCommonProps {
- id: string;
-}
+export interface ProgressRootCommonProps
+ extends MeterRootCommonProps {}
export interface ProgressRootRenderProps
- extends ProgressRootCommonProps,
+ extends Omit,
+ ProgressRootCommonProps,
ProgressDataSet {
role: "progressbar";
- "aria-valuenow": number | undefined;
- "aria-valuemin": number;
- "aria-valuemax": number;
- "aria-valuetext": string | undefined;
- "aria-labelledby": string | undefined;
}
-
export type ProgressRootProps<
T extends ValidComponent | HTMLElement = HTMLElement,
> = ProgressRootOptions & Partial>>;
@@ -169,16 +137,13 @@ export function ProgressRoot(
return (
-
- as="div"
+ >
+ >
role="progressbar"
- aria-valuenow={local.indeterminate ? undefined : value()}
- aria-valuemin={local.minValue}
- aria-valuemax={local.maxValue}
- aria-valuetext={valueLabel()}
- aria-labelledby={labelId()}
+ indeterminate={local.indeterminate || false}
{...dataset()}
- {...others}
+ {...mergedProps}
/>
);
diff --git a/packages/core/src/progress/progress-track.tsx b/packages/core/src/progress/progress-track.tsx
index c4cd384f..24ee2f10 100644
--- a/packages/core/src/progress/progress-track.tsx
+++ b/packages/core/src/progress/progress-track.tsx
@@ -1,19 +1,21 @@
-import type { ValidComponent } from "solid-js";
+import type { Component, ValidComponent } from "solid-js";
import {
- type ElementOf,
- Polymorphic,
- type PolymorphicProps,
-} from "../polymorphic";
+ Meter,
+ type MeterTrackCommonProps,
+ type MeterTrackOptions,
+ type MeterTrackRenderProps,
+} from "../meter";
+import type { ElementOf, PolymorphicProps } from "../polymorphic";
import { type ProgressDataSet, useProgressContext } from "./progress-context";
-export interface ProgressTrackOptions {}
+export interface ProgressTrackOptions extends MeterTrackOptions {}
-export interface ProgressTrackCommonProps<
- T extends HTMLElement = HTMLElement,
-> {}
+export interface ProgressTrackCommonProps
+ extends MeterTrackCommonProps {}
export interface ProgressTrackRenderProps
- extends ProgressTrackCommonProps,
+ extends MeterTrackRenderProps,
+ ProgressTrackCommonProps,
ProgressDataSet {}
export type ProgressTrackProps<
@@ -30,8 +32,9 @@ export function ProgressTrack(
const context = useProgressContext();
return (
-
- as="div"
+ >
+ >
{...context.dataset()}
{...(props as ProgressTrackProps)}
/>
diff --git a/packages/core/src/progress/progress-value-label.tsx b/packages/core/src/progress/progress-value-label.tsx
index f8b3a918..b7a1ab3c 100644
--- a/packages/core/src/progress/progress-value-label.tsx
+++ b/packages/core/src/progress/progress-value-label.tsx
@@ -1,23 +1,24 @@
-import type { JSX, ValidComponent } from "solid-js";
+import type { Component, ValidComponent } from "solid-js";
import {
- type ElementOf,
- Polymorphic,
- type PolymorphicProps,
-} from "../polymorphic";
+ Meter,
+ type MeterValueLabelCommonProps,
+ type MeterValueLabelOptions,
+ type MeterValueLabelRenderProps,
+} from "../meter";
+import type { ElementOf, PolymorphicProps } from "../polymorphic";
import { type ProgressDataSet, useProgressContext } from "./progress-context";
-export interface ProgressValueLabelOptions {}
+export interface ProgressValueLabelOptions extends MeterValueLabelOptions {}
export interface ProgressValueLabelCommonProps<
T extends HTMLElement = HTMLElement,
-> {}
+> extends MeterValueLabelCommonProps {}
export interface ProgressValueLabelRenderProps
- extends ProgressValueLabelCommonProps,
- ProgressDataSet {
- children: JSX.Element;
-}
+ extends MeterValueLabelRenderProps,
+ ProgressValueLabelCommonProps,
+ ProgressDataSet {}
export type ProgressValueLabelProps<
T extends ValidComponent | HTMLElement = HTMLElement,
@@ -33,12 +34,13 @@ export function ProgressValueLabel(
const context = useProgressContext();
return (
-
- as="div"
+
+ >
+ >
{...context.dataset()}
{...(props as ProgressValueLabelProps)}
- >
- {context.valueLabel()}
-
+ />
);
}