Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(TimePicker): class -> functional component #9921

Merged
merged 27 commits into from
Oct 30, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
c4ab966
feat(TimePicker): class -> functional component
sstrubberg Oct 21, 2021
d06b883
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
0b6d700
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
a5c96fd
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
a2ae515
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
c6331c1
Merge branch 'main' into feat/timepicker-alt
sstrubberg Oct 25, 2021
a93a37d
feat(TimePicker): removed event and actions
sstrubberg Oct 25, 2021
19ffb08
feat(TimePicker): unused var
sstrubberg Oct 25, 2021
c57834a
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
b9c0049
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
8efa319
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
4a089a6
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
1fff39e
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
ac31f33
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
1d3dbec
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
133acc6
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
02153da
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
282a5ed
Update packages/react/src/components/TimePicker/next/TimePicker-test.js
sstrubberg Oct 25, 2021
0559b05
feat(TimePicker): updated removed prop spreading
sstrubberg Oct 25, 2021
d1e1fa3
Merge branch 'main' into feat/timepicker-alt
sstrubberg Oct 25, 2021
4a21f44
feat(TimePicker): removed todos for yarn format
sstrubberg Oct 25, 2021
af1244b
Merge branch 'main' into feat/timepicker-alt
sstrubberg Oct 26, 2021
21c6d1c
Merge branch 'main' into feat/timepicker-alt
tay1orjones Oct 29, 2021
66efb2e
Merge branch 'main' into feat/timepicker-alt
sstrubberg Oct 29, 2021
c954b01
Merge branch 'main' into feat/timepicker-alt
sstrubberg Oct 29, 2021
e359523
Merge branch 'main' into feat/timepicker-alt
sstrubberg Oct 30, 2021
01e8eff
Merge branch 'main' into feat/timepicker-alt
kodiakhq[bot] Oct 30, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion packages/react/src/components/TimePicker/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,14 @@
* LICENSE file in the root directory of this source tree.
*/

export default from './TimePicker';
import * as FeatureFlags from '@carbon/feature-flags';

import { default as TimePickerNext } from './next/TimePicker';

import { default as TimePickerClassic } from './TimePicker';

const TimePicker = FeatureFlags.enabled('enable-v11-release')
? TimePickerNext
: TimePickerClassic;

export default TimePicker;
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from 'react';
import { default as TimePicker } from './TimePicker';

import { render, cleanup, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import '@testing-library/jest-dom';
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved

describe('TimePicker', () => {
describe('input', () => {
afterEach(cleanup);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved

it('renders as expected', () => {
render(<TimePicker id="time-picker"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.getByRole('textbox')).toBeInTheDocument();
});

it('passes classNames as expected', () => {
render(<TimePicker id="time-picker" className="🚀"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.getByRole('textbox')).toHaveClass('🚀');
});

it('should set type as expected', () => {
render(<TimePicker id="time-picker"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.getByRole('textbox')).toHaveAttribute('type', 'text');
});

it('should set value as expected', () => {
render(<TimePicker id="time-picker" value="🐶"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.getByRole('textbox')).toHaveAttribute('value', '🐶');
});

it('should set disabled as expected', () => {
const onClick = jest.fn();
render(
<TimePicker id="time-picker" onClick={onClick} disabled></TimePicker>
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
);
fireEvent.click(screen.getByRole('textbox'));
expect(onClick).not.toHaveBeenCalled();
});

it('should set placeholder as expected', () => {
render(<TimePicker id="time-picker" placeholder="🧸"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.getByPlaceholderText('🧸')).toBeInTheDocument();
});
});
describe('label', () => {
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
afterEach(cleanup);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved

it('does not render a label by default', () => {
render(<TimePicker id="time-picker"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.queryByLabelText('🐳')).not.toBeInTheDocument();
});

it('renders a label as expected', () => {
render(<TimePicker id="time-picker" labelText="🐳"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
expect(screen.getByLabelText('🐳')).toBeInTheDocument();
});
});
describe('events', () => {
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
it('should write text inside the textbox', () => {
render(<TimePicker id="time-picker"></TimePicker>);
sstrubberg marked this conversation as resolved.
Show resolved Hide resolved
userEvent.type(screen.getByRole('textbox'), '🧛');
expect(screen.getByRole('textbox')).toHaveValue('🧛');
});
});
});

//// Old tests to possibly migrate
//
// Time Picker
//
// label
// - has the expected classes
// - should set label as expected
// events
// - disabled time picker
// - should not invoke onClick
// - should not invoke onChange
// enabled textinput
// - should invoke onBlur when input is clicked
// - should invoke onClick when input is clicked
// - should invoke onChange when input value is changed
// Getting derived state from props
// - should change the value upon change in props
// - should avoid change the value upon setting props, unless there the value actually changes
231 changes: 231 additions & 0 deletions packages/react/src/components/TimePicker/next/TimePicker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
/**
* Copyright IBM Corp. 2016, 2018
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/

import PropTypes from 'prop-types';
import React from 'react';
import cx from 'classnames';
import { usePrefix } from '../../../internal/usePrefix';
import deprecate from '../../../prop-types/deprecate';

const TimePicker = React.forwardRef(function TimePicker(
{
children,
className,
disabled = false,
hideLabel,
id,
invalidText = 'Invalid time format.',
invalid = false,
labelText,
light = false,
maxLength = 5,
onChange = () => {},
onClick = () => {},
onBlur = () => {},
pattern = '(1[012]|[1-9]):[0-5][0-9](\\s)?',
placeholder = 'hh:mm',
size,
type = 'text',
value,
...rest
},
ref
) {
const prefix = usePrefix();

const [isValue, setValue] = React.useState(value);
const [prevValue, setPrevValue] = React.useState(value);

if (value !== prevValue) {
setValue(value);
setPrevValue(value);
}

const timePickerInputProps = {
className: cx(
`${prefix}--time-picker__input-field`,
`${prefix}--text-input`,
[className],
{
[`${prefix}--text-input--light`]: light,
}
),

onChange: (evt) => {
if (!disabled) {
// https://github.com/carbon-design-system/carbon/issues/9535
evt.persist();
setValue(isValue);
onChange(evt);
}
},
onClick: (evt) => {
if (!disabled) {
// https://github.com/carbon-design-system/carbon/issues/9535
evt.persist();
setValue(isValue);
onClick(evt);
}
},
onBlur: (event) => {
if (!disabled) {
// https://github.com/carbon-design-system/carbon/issues/9535
event.persist();
setValue(isValue);
onBlur(event);
}
},
pattern,
placeholder,
maxLength,
id,
type,
disabled,
value,
};

const timePickerClasses = cx({
[`${prefix}--time-picker`]: true,
[`${prefix}--time-picker--light`]: light,
[`${prefix}--time-picker--invalid`]: invalid,
[`${prefix}--time-picker--${size}`]: size,
[className]: className,
});

const labelClasses = cx(`${prefix}--label`, {
[`${prefix}--visually-hidden`]: hideLabel,
[`${prefix}--label--disabled`]: disabled,
});

const label = labelText ? (
<label htmlFor={id} className={labelClasses}>
{labelText}
</label>
) : null;

const error = invalid ? (
<div className={`${prefix}--form-requirement`}>{invalidText}</div>
) : null;

return (
<div className={cx(`${prefix}--form-item`, className)} ref={ref}>
{label}
<div className={timePickerClasses}>
<div className={`${prefix}--time-picker__input`}>
<input
data-invalid={invalid ? invalid : undefined}
{...timePickerInputProps}
{...rest}
/>
</div>
{children}
</div>
{error}
</div>
);
});

TimePicker.propTypes = {
/**
* Pass in the children that will be rendered next to the form control
*/
children: PropTypes.node,

/**
* Specify an optional className to be applied to the container node
*/
className: PropTypes.string,

/**
* Specify whether the `<input>` should be disabled
*/
disabled: PropTypes.bool,

/**
* Specify whether you want the underlying label to be visually hidden
*/
hideLabel: PropTypes.bool,

/**
* Specify a custom `id` for the `<input>`
*/
id: PropTypes.string.isRequired,

/**
* Specify whether the control is currently invalid
*/
invalid: PropTypes.bool,

/**
* Provide the text that is displayed when the control is in an invalid state
*/
invalidText: PropTypes.node,

/**
* Provide the text that will be read by a screen reader when visiting this
* control
*/
labelText: PropTypes.node,

/**
* `true` to use the light version. TODO: V12 remove this.
*/
light: deprecate(
PropTypes.bool,
'The `light` prop for `Tile` is no longer needed and has been deprecated. It will be removed in the next major release. Use the Layer component instead.'
),

/**
* Specify the maximum length of the time string in `<input>`
*/
maxLength: PropTypes.number,

/**
* Optionally provide an `onBlur` handler that is called whenever the
* `<input>` loses focus
*/
onBlur: PropTypes.func,

/**
* Optionally provide an `onChange` handler that is called whenever `<input>`
* is updated
*/
onChange: PropTypes.func,

/**
* Optionally provide an `onClick` handler that is called whenever the
* `<input>` is clicked
*/
onClick: PropTypes.func,

/**
* Specify the regular expression working as the pattern of the time string in `<input>`
*/
pattern: PropTypes.string,

/**
* Specify the placeholder attribute for the `<input>`
*/
placeholder: PropTypes.string,

/**
* Specify the size of the Time Picker. Currently supports either `sm`, 'md' (default) or 'lg` as an option.
*/
size: PropTypes.oneOf(['sm', 'md', 'lg']),

/**
* Specify the type of the `<input>`
*/
type: PropTypes.string,

/**
* Specify the value of the `<input>`
*/
value: PropTypes.string,
};

export default TimePicker;