Skip to content

Commit

Permalink
chore: merging updates and resolving conflicts
Browse files Browse the repository at this point in the history
  • Loading branch information
amit-y committed Dec 19, 2023
2 parents b29598e + 3632e3c commit ef9e9a4
Show file tree
Hide file tree
Showing 13 changed files with 378 additions and 18 deletions.
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- TimePicker component by [@amit-y](https://github.com/amit-y)
- DateTimePicker component by [@amit-y](https://github.com/amit-y)
- TimeRangePicker component by [@amit-y](https://github.com/amit-y)

## [1.20.0] - 2023-12-18

### Added

- DateTimePicker component by [@amit-y](https://github.com/amit-y)

## [1.19.0] - 2023-12-18

### Added

- TimePicker component by [@amit-y](https://github.com/amit-y)

## [1.18.0] - 2023-12-18

### Changed

- SimpleBillboard - add option to enable title's tooltip + change up/down trend arrow colors to grey by [@shahramk](https://github.com/shahramk)

## [1.17.0] - 2023-12-14

### Added
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@newrelic/nr-labs-components",
"version": "1.17.0",
"version": "1.20.0",
"description": "New Relic Labs components",
"main": "dist/index.js",
"module": "dist/index.es.js",
Expand Down
28 changes: 28 additions & 0 deletions src/components/date-time-picker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# DateTimePicker

The `DateTimePicker` component is used to select a date/time value.

## Usage

To use the component, simply import and use:

```jsx
import React, { useState } from 'react';
import { DateTimePicker } from '@newrelic/nr-labs-components';

function MyComponent() {
const [selectedDate, setSelectedDate] = useState(new Date());

return (
<div>
<DateTimePicker date={selectedDate} onChange={setSelectedDate} />
</div>
);
}
```
### Props

- date (date): The default date
- onChange (function): A function that is called when the user selects a date
- validFrom (date): only allow date/times starting from the provided value
- validTill (date): only allow date/times ending till the provided value
28 changes: 28 additions & 0 deletions src/components/date-time-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import PropTypes from 'prop-types';

import DatePicker from '../date-picker';
import TimePicker from '../time-picker';

import styles from './styles.scss';

const DateTimePicker = ({ datetime, validFrom, validTill, onChange }) => (
<div className={styles['date-time-picker']}>
<DatePicker date={datetime} onChange={onChange} validFrom={validFrom} />
<TimePicker
time={datetime}
validFrom={validFrom}
validTill={validTill}
onChange={onChange}
/>
</div>
);

DateTimePicker.propTypes = {
datetime: PropTypes.instanceOf(Date),
validFrom: PropTypes.instanceOf(Date),
validTill: PropTypes.instanceOf(Date),
onChange: PropTypes.func,
};

export default DateTimePicker;
5 changes: 5 additions & 0 deletions src/components/date-time-picker/styles.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.date-time-picker {
display: grid;
grid-template-columns: 96px 76px;
gap: 4px;
}
1 change: 1 addition & 0 deletions src/components/simple-billboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ and the code snippet to use the component:
name: PropTypes.string, // required - metric name
style: PropTypes.object, // optional - SCSS class name for metric name
className: PropTypes.string, // optional - SCSS style for metric name
toolTip: PropTypes.bool // optional - enable tool tip for metric name (default: false)
}),
};
```
Expand Down
33 changes: 20 additions & 13 deletions src/components/simple-billboard/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import styles from './styles.scss';
/**
* @param {Object} metric - metric value, previousValue, optional: prefix, suffix, className, style
* @param {Object} statusTrend - optional: className, style
* @param {Object} title - metric name, optional: className, style
* @param {Object} title - metric name, optional: className, style, toolTip
* @return {JSX Object} - RENDERING name, value, up/down trend when previousValue present
*/
const SimpleBillboard = ({ metric, statusTrend = {}, title }) => {
Expand Down Expand Up @@ -58,20 +58,15 @@ const SimpleBillboard = ({ metric, statusTrend = {}, title }) => {
};
const icon =
difference > 0
? {
type: 'uparrow',
fill: '#02865B',
}
: {
type: 'downarrow',
fill: '#DF2D24',
};
? { type: 'uparrow', }
: { type: 'downarrow', };

return (
<svg
className={`${styles['metric-status']} ${statusTrend.className || ''}`}
style={{ ...statusTrend.style } || {}}
viewBox="0 0 16 16"
fill={icon.fill}
fill="#9EA5A9"
xmlns="http://www.w3.org/2000/svg"
focusable="false"
role="img"
Expand All @@ -83,7 +78,7 @@ const SimpleBillboard = ({ metric, statusTrend = {}, title }) => {
}, [difference]);

return (
<div className="simple-billboard">
<div className="simple-billboard">
<div
className={`${styles['metric-color']} ${styles['metric-value']} ${
metric.className || ''
Expand All @@ -94,7 +89,18 @@ const SimpleBillboard = ({ metric, statusTrend = {}, title }) => {
<span>{changeStatus}</span>
</div>

<Tooltip text={title.name}>
{title.toolTip ? (
<Tooltip text={title.name}>
<div
className={`${styles['metric-color']} ${styles['metric-name']} ${
title.className || ''
}`}
style={{ ...title.style } || {}}
>
{title.name}
</div>
</Tooltip>
) : (
<div
className={`${styles['metric-color']} ${styles['metric-name']} ${
title.className || ''
Expand All @@ -103,7 +109,7 @@ const SimpleBillboard = ({ metric, statusTrend = {}, title }) => {
>
{title.name}
</div>
</Tooltip>
)}
</div>
);
};
Expand All @@ -125,6 +131,7 @@ SimpleBillboard.propTypes = {
name: PropTypes.string,
style: PropTypes.object,
className: PropTypes.string,
toolTip: PropTypes.bool,
}),
};

Expand Down
Binary file modified src/components/simple-billboard/simple-billboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions src/components/time-picker/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# TimePicker

The `TimePicker` component is used to select a time value from a list of valid times.

## Usage

To use the component, simply import and use:

```jsx
import React, { useState } from 'react';
import { TimePicker } from '@newrelic/nr-labs-components';

function MyComponent() {
const [selectedTime, setSelectedTime] = useState(new Date());

return (
<div>
<TimePicker time={selectedTime} onChange={setSelectedTime} />
</div>
);
}
```
### Props

- time (date): The default time
- onChange (function): A function that is called when the user selects a time
- validFrom (date): only allow times starting from the provided value
- validTill (date): only allow times ending till the provided value
162 changes: 162 additions & 0 deletions src/components/time-picker/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import React, { useCallback, useEffect, useState } from 'react';
import PropTypes from 'prop-types';

import {
Popover,
PopoverTrigger,
PopoverBody,
TextField,
List,
ListItem,
Button,
} from 'nr1';

import {
getHourMinuteFromTimeString,
isValidTime,
normalizedDateTime,
isHourMinuteNumbers,
} from './utils';

import styles from './styles.scss';

const TIME_FORMATTER = new Intl.DateTimeFormat('default', {
hour12: true,
hour: 'numeric',
minute: 'numeric',
});

const TIMES_LIST = Array.from({ length: 48 }).map((_, i) => ({
key: `time${i}`,
value:
`${Math.floor(i / 2) % 12 || 12}`.padStart(2, '0') +
`:${i % 2 ? '30' : '00'} ${i > 23 ? 'pm' : 'am'}`,
}));

const TimePicker = ({ time, validFrom, validTill, onChange }) => {
const [opened, setOpened] = useState(false);
const [filter, setFilter] = useState('');
const [times, setTimes] = useState([]);

useEffect(() => {
if (!filter) {
setTimes(
TIMES_LIST.filter(({ value }) =>
isValidTime(value, validFrom, validTill, time)
)
);
return;
}
const re = /^(1[0-2]|0?[1-9]):?([0-5]?[0-9])? ?([AaPp][Mm]?)?/;
const [, hr, mi, me] = filter.match(re) || [];

if (hr) {
let reStr = hr;
if (reStr.length === 1 && Number(hr) < 3) reStr += '[0-9]?';
reStr += mi ? `:${mi}` : ':[0-9][0-9]';
if (reStr && me && /^[Aa]/.test(me)) reStr += ' am';
if (reStr && me && /^[Pp]/.test(me)) reStr += ' pm';

const re2 = new RegExp(reStr);
const matches = TIMES_LIST.filter(
({ value }) =>
value.match(re2) && isValidTime(value, validFrom, validTill, time)
);
if (!matches.length && hr && mi && mi.length === 2) {
if (me && /^[AaPp]/.test(me)) {
const value = `${hr}:${mi} ${/^[Aa]/.test(me) ? 'am' : 'pm'}`;
if (isValidTime(value, validFrom, validTill, time))
matches.push({
key: 'time48',
value,
});
} else {
['am', 'pm'].forEach((m) => {
const value = `${hr}:${mi} ${m}`;
if (isValidTime(value, validFrom, validTill, time))
matches.push({
key: `time48${m}`,
value,
});
});
}
}
setTimes(matches);
}
}, [filter]);

const clickHandler = useCallback((e, t) => {
e.stopPropagation();
const { hr, mi } = getHourMinuteFromTimeString(t);
if (!isHourMinuteNumbers(hr, mi)) return;
if (onChange) onChange(normalizedDateTime(time, hr, mi));
setOpened(false);
setFilter('');
}, []);

const changeHandler = useCallback((_, o) => {
if (!o) setFilter('');
setOpened(o);
});

const filterChangeHandler = useCallback(
({ target: { value = '' } } = {}) => setFilter(value),
[]
);

const keyDownHandler = useCallback((e) => {
const re = /[0-9APMapm: ]+/g;
if (!re.test(e.key)) e.preventDefault();
}, []);

return (
<Popover opened={opened} onChange={changeHandler}>
<PopoverTrigger>
<TextField
className={styles['time-picker-text-field']}
value={
time instanceof Date
? TIME_FORMATTER.format(time).toLocaleLowerCase()
: ''
}
placeholder="Select time"
readOnly
/>
</PopoverTrigger>
<PopoverBody>
<div className={styles['time-picker']}>
<TextField
className={styles['time-list-search']}
type={TextField.TYPE.SEARCH}
placeholder="filter..."
value={filter}
onKeyDown={keyDownHandler}
onChange={filterChangeHandler}
/>
<List className={styles['time-list-items']}>
{times.map(({ key, value }) => (
<ListItem key={key}>
<Button
className={styles['time-list-item']}
type={Button.TYPE.PLAIN}
onClick={(e) => clickHandler(e, value)}
>
{value}
</Button>
</ListItem>
))}
</List>
</div>
</PopoverBody>
</Popover>
);
};

TimePicker.propTypes = {
time: PropTypes.instanceOf(Date),
validFrom: PropTypes.instanceOf(Date),
validTill: PropTypes.instanceOf(Date),
onChange: PropTypes.func,
};

export default TimePicker;
Loading

0 comments on commit ef9e9a4

Please sign in to comment.