From fe332ed5bcaad9d4fc6407596360574a772821bc Mon Sep 17 00:00:00 2001 From: Shubhdeep Chhabra Date: Wed, 16 Oct 2024 19:15:58 +0530 Subject: [PATCH 1/5] added meter component --- apps/docs/src/examples/meter.module.css | 38 ++++ apps/docs/src/examples/meter.tsx | 51 +++++ apps/docs/src/routes/docs/core.tsx | 4 + .../src/routes/docs/core/components/meter.mdx | 197 ++++++++++++++++++ packages/core/src/index.tsx | 1 + packages/core/src/meter/index.tsx | 66 ++++++ packages/core/src/meter/meter-context.tsx | 28 +++ packages/core/src/meter/meter-fill.tsx | 49 +++++ packages/core/src/meter/meter-label.tsx | 57 +++++ packages/core/src/meter/meter-root.tsx | 136 ++++++++++++ packages/core/src/meter/meter-track.tsx | 37 ++++ packages/core/src/meter/meter-value-label.tsx | 43 ++++ packages/core/src/meter/meter.test.tsx | 132 ++++++++++++ 13 files changed, 839 insertions(+) create mode 100644 apps/docs/src/examples/meter.module.css create mode 100644 apps/docs/src/examples/meter.tsx create mode 100644 apps/docs/src/routes/docs/core/components/meter.mdx create mode 100644 packages/core/src/meter/index.tsx create mode 100644 packages/core/src/meter/meter-context.tsx create mode 100644 packages/core/src/meter/meter-fill.tsx create mode 100644 packages/core/src/meter/meter-label.tsx create mode 100644 packages/core/src/meter/meter-root.tsx create mode 100644 packages/core/src/meter/meter-track.tsx create mode 100644 packages/core/src/meter/meter-value-label.tsx create mode 100644 packages/core/src/meter/meter.test.tsx diff --git a/apps/docs/src/examples/meter.module.css b/apps/docs/src/examples/meter.module.css new file mode 100644 index 000000000..6910f0974 --- /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 000000000..60dd74b94 --- /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 d4b1efb21..c5b18d196 100644 --- a/apps/docs/src/routes/docs/core.tsx +++ b/apps/docs/src/routes/docs/core.tsx @@ -97,6 +97,10 @@ const CORE_NAV_SECTIONS: NavSection[] = [ title: "Menubar", href: "/docs/core/components/menubar", }, + { + title: "Meter", + href: "/docs/core/components/meter", + }, { 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 000000000..b5e9310a5 --- /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 4a72a633e..e3f43f031 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 000000000..64037c15c --- /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 000000000..794eccfef --- /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 000000000..e69f74bf2 --- /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 000000000..5cd951ee3 --- /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 000000000..fe52fa745 --- /dev/null +++ b/packages/core/src/meter/meter-root.tsx @@ -0,0 +1,136 @@ +/* + * 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 type { ProgressRootOptions } from "../progress"; +import { + MeterContext, + type MeterContextValue, + type MeterDataSet, +} from "./meter-context"; + +export interface MeterRootOptions + extends Omit {} + +export interface MeterRootCommonProps { + id: string; +} + +export interface MeterRootRenderProps + extends MeterRootCommonProps, + MeterDataSet { + role: "meter"; + "aria-valuenow": number | undefined; + "aria-valuemin": number; + "aria-valuemax": number; + "aria-valuetext": string | undefined; + "aria-labelledby": string | undefined; +} + +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, + }, + props as MeterRootProps, + ); + + const [local, others] = splitProps(mergedProps, [ + "value", + "minValue", + "maxValue", + "getValueLabel", + ]); + + 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.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="meter" + aria-valuenow={value()} + aria-valuemin={local.minValue} + aria-valuemax={local.maxValue} + aria-valuetext={valueLabel()} + aria-labelledby={labelId()} + {...others} + /> + + ); +} diff --git a/packages/core/src/meter/meter-track.tsx b/packages/core/src/meter/meter-track.tsx new file mode 100644 index 000000000..fd3a4c4a7 --- /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 000000000..84e639b24 --- /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 000000000..ee70896b7 --- /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%"); + }); +}); From e71c9dd5ca556dc6ba22879f2e04edf21a3c6d1d Mon Sep 17 00:00:00 2001 From: Shubhdeep Chhabra Date: Thu, 17 Oct 2024 19:16:25 +0530 Subject: [PATCH 2/5] Made meter generic component and Progress component as an extended Meter component --- packages/core/src/meter/meter-root.tsx | 33 ++++++++++-- .../core/src/progress/progress-context.tsx | 12 ++--- packages/core/src/progress/progress-fill.tsx | 15 ++++-- packages/core/src/progress/progress-label.tsx | 15 ++++-- packages/core/src/progress/progress-root.tsx | 50 ++++--------------- packages/core/src/progress/progress-track.tsx | 15 ++++-- .../src/progress/progress-value-label.tsx | 16 +++--- 7 files changed, 84 insertions(+), 72 deletions(-) diff --git a/packages/core/src/meter/meter-root.tsx b/packages/core/src/meter/meter-root.tsx index fe52fa745..b06a2f250 100644 --- a/packages/core/src/meter/meter-root.tsx +++ b/packages/core/src/meter/meter-root.tsx @@ -23,15 +23,42 @@ import { type PolymorphicProps, } from "../polymorphic"; import { createRegisterId } from "../primitives"; -import type { ProgressRootOptions } from "../progress"; import { MeterContext, type MeterContextValue, type MeterDataSet, } from "./meter-context"; -export interface MeterRootOptions - extends Omit {} +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; diff --git a/packages/core/src/progress/progress-context.tsx b/packages/core/src/progress/progress-context.tsx index e9facedaa..bbaa168fa 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 ca0291228..b7e0da981 100644 --- a/packages/core/src/progress/progress-fill.tsx +++ b/packages/core/src/progress/progress-fill.tsx @@ -1,6 +1,11 @@ import { type JSX, type ValidComponent, splitProps } from "solid-js"; import { combineStyle } from "@solid-primitives/props"; +import type { + MeterFillCommonProps, + MeterFillOptions, + MeterFillRenderProps, +} from "../meter"; import { type ElementOf, Polymorphic, @@ -8,15 +13,15 @@ import { } 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, diff --git a/packages/core/src/progress/progress-label.tsx b/packages/core/src/progress/progress-label.tsx index 541cbdeec..11ced2952 100644 --- a/packages/core/src/progress/progress-label.tsx +++ b/packages/core/src/progress/progress-label.tsx @@ -6,6 +6,11 @@ import { splitProps, } from "solid-js"; +import type { + MeterLabelCommonProps, + MeterLabelOptions, + MeterLabelRenderProps, +} from "../meter"; import { type ElementOf, Polymorphic, @@ -13,14 +18,14 @@ import { } 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< diff --git a/packages/core/src/progress/progress-root.tsx b/packages/core/src/progress/progress-root.tsx index b7dfaf622..127e79940 100644 --- a/packages/core/src/progress/progress-root.tsx +++ b/packages/core/src/progress/progress-root.tsx @@ -17,6 +17,11 @@ import { } from "solid-js"; import { createNumberFormatter } from "../i18n"; +import type { + MeterRootCommonProps, + MeterRootOptions, + MeterRootRenderProps, +} from "../meter"; import { type ElementOf, Polymorphic, @@ -29,54 +34,19 @@ 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 MeterRootOptions { /** 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, - ProgressDataSet { + ProgressDataSet, + Omit { role: "progressbar"; - "aria-valuenow": number | undefined; - "aria-valuemin": number; - "aria-valuemax": number; - "aria-valuetext": string | undefined; - "aria-labelledby": string | undefined; } export type ProgressRootProps< diff --git a/packages/core/src/progress/progress-track.tsx b/packages/core/src/progress/progress-track.tsx index c4cd384f6..21e9bce15 100644 --- a/packages/core/src/progress/progress-track.tsx +++ b/packages/core/src/progress/progress-track.tsx @@ -1,4 +1,9 @@ import type { ValidComponent } from "solid-js"; +import type { + MeterTrackCommonProps, + MeterTrackOptions, + MeterTrackRenderProps, +} from "../meter"; import { type ElementOf, Polymorphic, @@ -6,14 +11,14 @@ import { } 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< diff --git a/packages/core/src/progress/progress-value-label.tsx b/packages/core/src/progress/progress-value-label.tsx index f8b3a9188..1304086bb 100644 --- a/packages/core/src/progress/progress-value-label.tsx +++ b/packages/core/src/progress/progress-value-label.tsx @@ -1,5 +1,10 @@ import type { JSX, ValidComponent } from "solid-js"; +import type { + MeterValueLabelCommonProps, + MeterValueLabelOptions, + MeterValueLabelRenderProps, +} from "../meter"; import { type ElementOf, Polymorphic, @@ -7,17 +12,16 @@ import { } 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, From e60d61c80a981a3e824be50a2c9ad795e5d77c9a Mon Sep 17 00:00:00 2001 From: Shubhdeep Chhabra Date: Thu, 17 Oct 2024 19:19:08 +0530 Subject: [PATCH 3/5] Added new status for Meter in Sidebar --- apps/docs/src/routes/docs/core.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/src/routes/docs/core.tsx b/apps/docs/src/routes/docs/core.tsx index c5b18d196..1a30f0012 100644 --- a/apps/docs/src/routes/docs/core.tsx +++ b/apps/docs/src/routes/docs/core.tsx @@ -100,6 +100,7 @@ const CORE_NAV_SECTIONS: NavSection[] = [ { title: "Meter", href: "/docs/core/components/meter", + status: "new", }, { title: "Navigation Menu", From 5f51307057ddd8b13335b97b4da3634ca1eb149a Mon Sep 17 00:00:00 2001 From: Shubhdeep Chhabra Date: Sun, 20 Oct 2024 15:46:34 +0530 Subject: [PATCH 4/5] fixed re-exports of Meter in Progress --- packages/core/src/meter/meter-root.tsx | 28 ++++++++++----- packages/core/src/progress/progress-fill.tsx | 21 +++++++----- packages/core/src/progress/progress-label.tsx | 17 +++++----- packages/core/src/progress/progress-root.tsx | 34 +++++++++---------- packages/core/src/progress/progress-track.tsx | 17 +++++----- .../src/progress/progress-value-label.tsx | 23 +++++++------ 6 files changed, 79 insertions(+), 61 deletions(-) diff --git a/packages/core/src/meter/meter-root.tsx b/packages/core/src/meter/meter-root.tsx index b06a2f250..8b64d7d8e 100644 --- a/packages/core/src/meter/meter-root.tsx +++ b/packages/core/src/meter/meter-root.tsx @@ -62,12 +62,7 @@ export interface MeterRootOptions { export interface MeterRootCommonProps { id: string; -} - -export interface MeterRootRenderProps - extends MeterRootCommonProps, - MeterDataSet { - role: "meter"; + role: string; "aria-valuenow": number | undefined; "aria-valuemin": number; "aria-valuemax": number; @@ -75,6 +70,10 @@ export interface MeterRootRenderProps "aria-labelledby": string | undefined; } +export interface MeterRootRenderProps + extends MeterRootCommonProps, + MeterDataSet {} + export type MeterRootProps< T extends ValidComponent | HTMLElement = HTMLElement, > = MeterRootOptions & Partial>>; @@ -93,6 +92,8 @@ export function MeterRoot( value: 0, minValue: 0, maxValue: 100, + role: "meter", + indeterminate: false, }, props as MeterRootProps, ); @@ -102,6 +103,13 @@ export function MeterRoot( "minValue", "maxValue", "getValueLabel", + "role", + "aria-valuetext", + "aria-labelledby", + "aria-valuemax", + "aria-valuemin", + "aria-valuenow", + "indeterminate", ]); const [labelId, setLabelId] = createSignal(); @@ -117,6 +125,9 @@ export function MeterRoot( }; const valueLabel = () => { + if (local.indeterminate) { + return undefined; + } if (local.getValueLabel) { return local.getValueLabel({ value: value(), @@ -150,12 +161,13 @@ export function MeterRoot( as="div" - role="meter" - aria-valuenow={value()} + 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/progress/progress-fill.tsx b/packages/core/src/progress/progress-fill.tsx index b7e0da981..a7be4a2bb 100644 --- a/packages/core/src/progress/progress-fill.tsx +++ b/packages/core/src/progress/progress-fill.tsx @@ -1,14 +1,18 @@ -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 { - MeterFillCommonProps, - MeterFillOptions, - MeterFillRenderProps, +import { + Meter, + type MeterFillCommonProps, + type MeterFillOptions, + type MeterFillRenderProps, } from "../meter"; import { type ElementOf, - Polymorphic, type PolymorphicProps, } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; @@ -39,8 +43,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 11ced2952..22ed6925d 100644 --- a/packages/core/src/progress/progress-label.tsx +++ b/packages/core/src/progress/progress-label.tsx @@ -1,19 +1,20 @@ import { mergeDefaultProps } from "@kobalte/utils"; import { + type Component, type ValidComponent, createEffect, onCleanup, splitProps, } from "solid-js"; -import type { - MeterLabelCommonProps, - MeterLabelOptions, - MeterLabelRenderProps, +import { + Meter, + type MeterLabelCommonProps, + type MeterLabelOptions, + type MeterLabelRenderProps, } from "../meter"; import { type ElementOf, - Polymorphic, type PolymorphicProps, } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; @@ -46,14 +47,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 127e79940..dabae8335 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, @@ -17,14 +18,14 @@ import { } from "solid-js"; import { createNumberFormatter } from "../i18n"; -import type { - MeterRootCommonProps, - MeterRootOptions, - MeterRootRenderProps, +import { + Meter, + type MeterRootCommonProps, + type MeterRootOptions, + type MeterRootRenderProps, } from "../meter"; import { type ElementOf, - Polymorphic, type PolymorphicProps, } from "../polymorphic"; import { createRegisterId } from "../primitives"; @@ -34,7 +35,8 @@ import { type ProgressDataSet, } from "./progress-context"; -export interface ProgressRootOptions extends MeterRootOptions { +export interface ProgressRootOptions + extends Omit { /** Whether the progress is in an indeterminate state. */ indeterminate?: boolean; } @@ -43,12 +45,11 @@ export interface ProgressRootCommonProps extends MeterRootCommonProps {} export interface ProgressRootRenderProps - extends ProgressRootCommonProps, - ProgressDataSet, - Omit { + extends Omit, + ProgressRootCommonProps, + ProgressDataSet { role: "progressbar"; } - export type ProgressRootProps< T extends ValidComponent | HTMLElement = HTMLElement, > = ProgressRootOptions & Partial>>; @@ -139,16 +140,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 21e9bce15..bc3b2f6a5 100644 --- a/packages/core/src/progress/progress-track.tsx +++ b/packages/core/src/progress/progress-track.tsx @@ -1,12 +1,12 @@ -import type { ValidComponent } from "solid-js"; -import type { - MeterTrackCommonProps, - MeterTrackOptions, - MeterTrackRenderProps, +import type { Component, ValidComponent } from "solid-js"; +import { + Meter, + type MeterTrackCommonProps, + type MeterTrackOptions, + type MeterTrackRenderProps, } from "../meter"; import { type ElementOf, - Polymorphic, type PolymorphicProps, } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; @@ -35,8 +35,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 1304086bb..e4b436134 100644 --- a/packages/core/src/progress/progress-value-label.tsx +++ b/packages/core/src/progress/progress-value-label.tsx @@ -1,13 +1,13 @@ -import type { JSX, ValidComponent } from "solid-js"; +import type { Component, ValidComponent } from "solid-js"; -import type { - MeterValueLabelCommonProps, - MeterValueLabelOptions, - MeterValueLabelRenderProps, +import { + Meter, + type MeterValueLabelCommonProps, + type MeterValueLabelOptions, + type MeterValueLabelRenderProps, } from "../meter"; import { type ElementOf, - Polymorphic, type PolymorphicProps, } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; @@ -37,12 +37,13 @@ export function ProgressValueLabel( const context = useProgressContext(); return ( - - as="div" + + > + > {...context.dataset()} {...(props as ProgressValueLabelProps)} - > - {context.valueLabel()} - + /> ); } From ac42c9931d5434852eac23756f2e59392b3e9bd7 Mon Sep 17 00:00:00 2001 From: Shubhdeep Chhabra Date: Tue, 22 Oct 2024 10:52:31 +0530 Subject: [PATCH 5/5] formatting fixed --- packages/core/src/progress/progress-fill.tsx | 11 ++--------- packages/core/src/progress/progress-label.tsx | 5 +---- packages/core/src/progress/progress-root.tsx | 5 +---- packages/core/src/progress/progress-track.tsx | 5 +---- packages/core/src/progress/progress-value-label.tsx | 5 +---- 5 files changed, 6 insertions(+), 25 deletions(-) diff --git a/packages/core/src/progress/progress-fill.tsx b/packages/core/src/progress/progress-fill.tsx index a7be4a2bb..7b0f79d4e 100644 --- a/packages/core/src/progress/progress-fill.tsx +++ b/packages/core/src/progress/progress-fill.tsx @@ -1,8 +1,4 @@ -import { - type Component, - type ValidComponent, - splitProps, -} from "solid-js"; +import { type Component, type ValidComponent, splitProps } from "solid-js"; import { combineStyle } from "@solid-primitives/props"; import { @@ -11,10 +7,7 @@ import { type MeterFillOptions, type MeterFillRenderProps, } from "../meter"; -import { - type ElementOf, - type PolymorphicProps, -} from "../polymorphic"; +import type { ElementOf, PolymorphicProps } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; export interface ProgressFillOptions extends MeterFillOptions {} diff --git a/packages/core/src/progress/progress-label.tsx b/packages/core/src/progress/progress-label.tsx index 22ed6925d..e6016cc2a 100644 --- a/packages/core/src/progress/progress-label.tsx +++ b/packages/core/src/progress/progress-label.tsx @@ -13,10 +13,7 @@ import { type MeterLabelOptions, type MeterLabelRenderProps, } from "../meter"; -import { - type ElementOf, - type PolymorphicProps, -} from "../polymorphic"; +import type { ElementOf, PolymorphicProps } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; export interface ProgressLabelOptions extends MeterLabelOptions {} diff --git a/packages/core/src/progress/progress-root.tsx b/packages/core/src/progress/progress-root.tsx index dabae8335..22ae0a5d9 100644 --- a/packages/core/src/progress/progress-root.tsx +++ b/packages/core/src/progress/progress-root.tsx @@ -24,10 +24,7 @@ import { type MeterRootOptions, type MeterRootRenderProps, } from "../meter"; -import { - type ElementOf, - type PolymorphicProps, -} from "../polymorphic"; +import type { ElementOf, PolymorphicProps } from "../polymorphic"; import { createRegisterId } from "../primitives"; import { ProgressContext, diff --git a/packages/core/src/progress/progress-track.tsx b/packages/core/src/progress/progress-track.tsx index bc3b2f6a5..24ee2f108 100644 --- a/packages/core/src/progress/progress-track.tsx +++ b/packages/core/src/progress/progress-track.tsx @@ -5,10 +5,7 @@ import { type MeterTrackOptions, type MeterTrackRenderProps, } from "../meter"; -import { - type ElementOf, - type PolymorphicProps, -} from "../polymorphic"; +import type { ElementOf, PolymorphicProps } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; export interface ProgressTrackOptions extends MeterTrackOptions {} diff --git a/packages/core/src/progress/progress-value-label.tsx b/packages/core/src/progress/progress-value-label.tsx index e4b436134..b7a1ab3c5 100644 --- a/packages/core/src/progress/progress-value-label.tsx +++ b/packages/core/src/progress/progress-value-label.tsx @@ -6,10 +6,7 @@ import { type MeterValueLabelOptions, type MeterValueLabelRenderProps, } from "../meter"; -import { - type ElementOf, - type PolymorphicProps, -} from "../polymorphic"; +import type { ElementOf, PolymorphicProps } from "../polymorphic"; import { type ProgressDataSet, useProgressContext } from "./progress-context"; export interface ProgressValueLabelOptions extends MeterValueLabelOptions {}