Skip to content

Commit

Permalink
Create Meter hooks and components
Browse files Browse the repository at this point in the history
  • Loading branch information
mj12albert committed Oct 17, 2024
1 parent 8c52af4 commit 77b9877
Show file tree
Hide file tree
Showing 28 changed files with 930 additions and 9 deletions.
19 changes: 19 additions & 0 deletions docs/data/api/meter-indicator.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"props": {
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
},
"name": "MeterIndicator",
"imports": [
"import { Meter } from '@base_ui/react/Meter';\nconst MeterIndicator = Meter.Indicator;"
],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "MeterIndicator",
"forwardsRefTo": "HTMLSpanElement",
"filename": "/packages/mui-base/src/Meter/Indicator/MeterIndicator.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/react-meter/\">Meter</a></li></ul>",
"cssComponent": false
}
35 changes: 35 additions & 0 deletions docs/data/api/meter-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"props": {
"value": { "type": { "name": "number" }, "required": true },
"aria-label": { "type": { "name": "string" } },
"aria-labelledby": { "type": { "name": "string" } },
"aria-valuetext": { "type": { "name": "string" } },
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"direction": {
"type": { "name": "enum", "description": "'ltr'<br>&#124;&nbsp;'rtl'" },
"default": "'ltr'"
},
"getAriaLabel": {
"type": { "name": "func" },
"signature": { "type": "function(value: number) => string", "describedArgs": ["value"] }
},
"getAriaValueText": {
"type": { "name": "func" },
"signature": { "type": "function(value: number) => string", "describedArgs": ["value"] }
},
"max": { "type": { "name": "number" }, "default": "100" },
"min": { "type": { "name": "number" }, "default": "0" },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
},
"name": "MeterRoot",
"imports": ["import { Meter } from '@base_ui/react/Meter';\nconst MeterRoot = Meter.Root;"],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "MeterRoot",
"forwardsRefTo": "HTMLDivElement",
"filename": "/packages/mui-base/src/Meter/Root/MeterRoot.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/react-meter/\">Meter</a></li></ul>",
"cssComponent": false
}
17 changes: 17 additions & 0 deletions docs/data/api/meter-track.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"props": {
"className": { "type": { "name": "union", "description": "func<br>&#124;&nbsp;string" } },
"render": { "type": { "name": "union", "description": "element<br>&#124;&nbsp;func" } }
},
"name": "MeterTrack",
"imports": ["import { Meter } from '@base_ui/react/Meter';\nconst MeterTrack = Meter.Track;"],
"classes": [],
"spread": true,
"themeDefaultProps": true,
"muiName": "MeterTrack",
"forwardsRefTo": "HTMLSpanElement",
"filename": "/packages/mui-base/src/Meter/Track/MeterTrack.tsx",
"inheritance": null,
"demos": "<ul><li><a href=\"/components/react-meter/\">Meter</a></li></ul>",
"cssComponent": false
}
19 changes: 19 additions & 0 deletions docs/data/components/meter/MeterIntroduction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';
import * as React from 'react';
import { Meter } from '@base_ui/react/Meter';
import classes from './styles.module.css';

export default function MeterIntroduction() {
return (
<div className={classes.demo}>
<Meter.Root className={classes.meter} value={67} aria-labelledby=":label:">
<span className={classes.label} id=":label:">
Battery Health
</span>
<Meter.Track className={classes.track}>
<Meter.Indicator className={classes.indicator} />
</Meter.Track>
</Meter.Root>
</div>
);
}
19 changes: 19 additions & 0 deletions docs/data/components/meter/MeterIntroduction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client';
import * as React from 'react';
import { Meter } from '@base_ui/react/Meter';
import classes from './styles.module.css';

export default function MeterIntroduction() {
return (
<div className={classes.demo}>
<Meter.Root className={classes.meter} value={67} aria-labelledby=":label:">
<span className={classes.label} id=":label:">
Battery Health
</span>
<Meter.Track className={classes.track}>
<Meter.Indicator className={classes.indicator} />
</Meter.Track>
</Meter.Root>
</div>
);
}
8 changes: 8 additions & 0 deletions docs/data/components/meter/MeterIntroduction.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<Meter.Root className={classes.meter} value={67} aria-labelledby=":label:">
<span className={classes.label} id=":label:">
Battery Health
</span>
<Meter.Track className={classes.track}>
<Meter.Indicator className={classes.indicator} />
</Meter.Track>
</Meter.Root>
38 changes: 38 additions & 0 deletions docs/data/components/meter/meter.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
productId: base-ui
title: React Meter components
description: The Meter component provides a graphical display of a numeric value within a defined range
components: MeterRoot, MeterTrack, MeterIndicator
hooks: useMeterRoot, useMeterIndicator
githubLabel: 'component: meter'
waiAria: https://www.w3.org/WAI/ARIA/apg/patterns/meter/
packageName: '@base_ui/react'
---

# Meter

<Description />

<ComponentLinkHeader design={false} />

<Demo demo="MeterIntroduction" defaultCodeOpen="false" bg="gradient" />

## Installation

<InstallationInstructions componentName="Meter" />

### Anatomy

Meter

- `<Meter.Root />` is a top-level component that wraps the other components.
- `<Meter.Track />` renders the rail that represents the full range of possible values.
- `<Meter.Indicator />` renders the filled portion of the track.

```tsx
<Meter.Root>
<Meter.Track>
<Meter.Indicator />
</Meter.Track>
</Meter.Root>
```
33 changes: 33 additions & 0 deletions docs/data/components/meter/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.demo {
font-family: system-ui, sans-serif;
width: 20rem;
padding: 1rem;
}

.meter {
display: flex;
flex-flow: column nowrap;
gap: 1rem;
}

.track {
position: relative;
width: 100%;
height: 18px;
border-radius: 5px;
border: 2px solid #222;
padding: 2px;
background-color: var(--gray-container-1);
display: flex;
overflow: hidden;
}

.indicator {
background-color: rgb(40, 205, 65);
border-radius: 3px;
}

.label {
cursor: unset;
font-weight: bold;
}
1 change: 1 addition & 0 deletions docs/data/pages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const pages: readonly RouteMetadata[] = [
{ pathname: '/components/react-fieldset', title: 'Fieldset' },
{ pathname: '/components/react-form', title: 'Form' },
{ pathname: '/components/react-menu', title: 'Menu' },
{ pathname: '/components/react-meter', title: 'Meter' },
{ pathname: '/components/react-number-field', title: 'Number Field' },
{ pathname: '/components/react-popover', title: 'Popover' },
{ pathname: '/components/react-preview-card', title: 'Preview Card' },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"componentDescription": "",
"propDescriptions": {
"className": {
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"render": { "description": "A function to customize rendering of the component." }
},
"classDescriptions": {}
}
29 changes: 29 additions & 0 deletions docs/data/translations/api-docs/meter-root/meter-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"componentDescription": "",
"propDescriptions": {
"aria-label": { "description": "The label for the Indicator component." },
"aria-labelledby": {
"description": "An id or space-separated list of ids of elements that label the Indicator component."
},
"aria-valuetext": {
"description": "A string value that provides a human-readable text alternative for the current value of the meter indicator."
},
"className": {
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"direction": { "description": "The direction that the meter fills towards" },
"getAriaLabel": {
"description": "Accepts a function which returns a string value that provides an accessible name for the Indicator component",
"typeDescriptions": { "value": "The component&#39;s value" }
},
"getAriaValueText": {
"description": "Accepts a function which returns a string value that provides a human-readable text alternative for the current value of the meter indicator.",
"typeDescriptions": { "value": "The component&#39;s value to format" }
},
"max": { "description": "The maximum value" },
"min": { "description": "The minimum value" },
"render": { "description": "A function to customize rendering of the component." },
"value": { "description": "The current value." }
},
"classDescriptions": {}
}
10 changes: 10 additions & 0 deletions docs/data/translations/api-docs/meter-track/meter-track.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"componentDescription": "",
"propDescriptions": {
"className": {
"description": "Class names applied to the element or a function that returns them based on the component&#39;s state."
},
"render": { "description": "A function to customize rendering of the component." }
},
"classDescriptions": {}
}
53 changes: 53 additions & 0 deletions packages/mui-base/src/Meter/Indicator/MeterIndicator.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import * as React from 'react';
import { expect } from 'chai';
import { Meter } from '@base_ui/react/Meter';
import { createRenderer, describeConformance } from '#test-utils';
import { MeterRootContext } from '../Root/MeterRootContext';

const contextValue: MeterRootContext = {
direction: 'ltr',
max: 100,
min: 0,
value: 30,
ownerState: {
direction: 'ltr',
max: 100,
min: 0,
},
};

describe('<Meter.Indicator />', () => {
const { render } = createRenderer();

describeConformance(<Meter.Indicator />, () => ({
render: (node) => {
return render(
<MeterRootContext.Provider value={contextValue}>{node}</MeterRootContext.Provider>,
);
},
refInstanceof: window.HTMLSpanElement,
}));

describe('internal styles', () => {
it('determinate', async function test() {
if (/jsdom/.test(window.navigator.userAgent)) {
this.skip();
}

const { getByTestId } = await render(
<Meter.Root value={33}>
<Meter.Track>
<Meter.Indicator data-testid="indicator" />
</Meter.Track>
</Meter.Root>,
);

const indicator = getByTestId('indicator');

expect(indicator).toHaveComputedStyle({
left: '0px',
width: '33%',
});
});
});
});
76 changes: 76 additions & 0 deletions packages/mui-base/src/Meter/Indicator/MeterIndicator.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
'use client';
import * as React from 'react';
import PropTypes from 'prop-types';
import { useComponentRenderer } from '../../utils/useComponentRenderer';
import { useMeterIndicator } from './useMeterIndicator';
import { MeterRoot } from '../Root/MeterRoot';
import { useMeterRootContext } from '../Root/MeterRootContext';
import { BaseUIComponentProps } from '../../utils/types';
/**
*
* Demos:
*
* - [Meter](https://base-ui.netlify.app/components/react-meter/)
*
* API:
*
* - [MeterIndicator API](https://base-ui.netlify.app/components/react-meter/#api-reference-MeterIndicator)
*/
const MeterIndicator = React.forwardRef(function MeterIndicator(
props: MeterIndicator.Props,
forwardedRef: React.ForwardedRef<HTMLSpanElement>,
) {
const { render, className, ...otherProps } = props;

const { direction, max, min, value, ownerState } = useMeterRootContext();

const { getRootProps } = useMeterIndicator({
direction,
max,
min,
value,
});

const { renderElement } = useComponentRenderer({
propGetter: getRootProps,
render: render ?? 'span',
ownerState,
className,
ref: forwardedRef,
extraProps: otherProps,
customStyleHookMapping: {
direction: () => null,
max: () => null,
min: () => null,
},
});

return renderElement();
});

MeterIndicator.propTypes /* remove-proptypes */ = {
// ┌────────────────────────────── Warning ──────────────────────────────┐
// │ These PropTypes are generated from the TypeScript type definitions. │
// │ To update them, edit the TypeScript types and run `pnpm proptypes`. │
// └─────────────────────────────────────────────────────────────────────┘
/**
* @ignore
*/
children: PropTypes.node,
/**
* Class names applied to the element or a function that returns them based on the component's state.
*/
className: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/**
* A function to customize rendering of the component.
*/
render: PropTypes.oneOfType([PropTypes.element, PropTypes.func]),
} as any;

namespace MeterIndicator {
export interface OwnerState extends MeterRoot.OwnerState {}

export interface Props extends BaseUIComponentProps<'span', OwnerState> {}
}

export { MeterIndicator };
Loading

0 comments on commit 77b9877

Please sign in to comment.