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 24, 2024
1 parent b37dfc9 commit 395484a
Show file tree
Hide file tree
Showing 29 changed files with 1,073 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
}
38 changes: 38 additions & 0 deletions docs/data/api/meter-root.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"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"] }
},
"high": { "type": { "name": "number" }, "default": "100" },
"low": { "type": { "name": "number" }, "default": "0" },
"max": { "type": { "name": "number" }, "default": "100" },
"min": { "type": { "name": "number" }, "default": "0" },
"optimum": { "type": { "name": "number" }, "default": "50" },
"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
}
35 changes: 35 additions & 0 deletions docs/data/components/meter/MeterIntroduction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'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={60} aria-label="Battery Life">
<Meter.Track className={classes.track}>
<BoltIcon className={classes.icon} />
<Meter.Indicator className={classes.indicator} />
</Meter.Track>
</Meter.Root>
</div>
);
}

function BoltIcon(props) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
{...props}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M10.67 21c-.35 0-.62-.31-.57-.66L11 14H7.5c-.88 0-.33-.75-.31-.78 1.26-2.23 3.15-5.53 5.65-9.93.1-.18.3-.29.5-.29.35 0 .62.31.57.66l-.9 6.34h3.51c.4 0 .62.19.4.66-3.29 5.74-5.2 9.09-5.75 10.05-.1.18-.29.29-.5.29z"
fill="currentColor"
/>
</svg>
);
}
35 changes: 35 additions & 0 deletions docs/data/components/meter/MeterIntroduction.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'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={60} aria-label="Battery Life">
<Meter.Track className={classes.track}>
<BoltIcon className={classes.icon} />
<Meter.Indicator className={classes.indicator} />
</Meter.Track>
</Meter.Root>
</div>
);
}

function BoltIcon(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
{...props}
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
>
<path
d="M10.67 21c-.35 0-.62-.31-.57-.66L11 14H7.5c-.88 0-.33-.75-.31-.78 1.26-2.23 3.15-5.53 5.65-9.93.1-.18.3-.29.5-.29.35 0 .62.31.57.66l-.9 6.34h3.51c.4 0 .62.19.4.66-3.29 5.74-5.2 9.09-5.75 10.05-.1.18-.29.29-.5.29z"
fill="currentColor"
/>
</svg>
);
}
6 changes: 6 additions & 0 deletions docs/data/components/meter/MeterIntroduction.tsx.preview
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<Meter.Root className={classes.meter} value={60} aria-label="Battery Life">
<Meter.Track className={classes.track}>
<BoltIcon className={classes.icon} />
<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>
```
49 changes: 49 additions & 0 deletions docs/data/components/meter/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
.demo {
--icon-size: 46px;
display: flex;
flex-flow: row nowrap;
justify-content: center;
align-items: center;
padding: 1rem;
}

.meter {
display: flex;
flex-flow: column nowrap;
gap: 1rem;
color: var(--gray-text-2);
}

.track {
position: relative;
width: 38px;
height: 16px;
border-radius: 5px;
border: 3px solid currentColor;
padding: 2px;
display: flex;
}

.track:after {
content: '';
background-color: currentColor;
position: absolute;
z-index: 1;
top: 3px;
right: -6px;
width: 3px;
height: 14px;
border-radius: 0 6px 6px 0;
}

.icon {
position: absolute;
width: var(--icon-size);
height: var(--icon-size);
transform: translate(-3px, -14px);
}

.indicator {
background-color: rgb(40, 205, 65);
border-radius: 3px;
}
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": {}
}
38 changes: 38 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,38 @@
{
"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" }
},
"high": {
"description": "Sets the lower boundary of the high end of the numeric range represented by the component. If unspecified, or greater than <code>max</code>, it will fall back to <code>max</code>."
},
"low": {
"description": "Sets the upper boundary of the low end of the numeric range represented by the component. If unspecified, or less than <code>min</code>, it will fall back to <code>min</code>."
},
"max": { "description": "The maximum value" },
"min": { "description": "The minimum value" },
"optimum": {
"description": "Indicates the optimal point in the numeric range represented by the component. If unspecified, it will fall back to the midpoint between <code>min</code> and <code>max</code>."
},
"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": {}
}
58 changes: 58 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,58 @@
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,
percentageValue: 30,
segment: 'low',
isOptimal: false,
ownerState: {
direction: 'ltr',
max: 100,
min: 0,
segment: 'low',
isOptimal: false,
},
};

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%',
});
});
});
});
Loading

0 comments on commit 395484a

Please sign in to comment.