Skip to content

Commit

Permalink
feature(FDS-342): Input time field (#267)
Browse files Browse the repository at this point in the history
* Input time

* Input time

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
  • Loading branch information
rociobaena and github-actions[bot] authored Sep 20, 2021
1 parent a95b3d6 commit e3cb177
Show file tree
Hide file tree
Showing 10 changed files with 282 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/cascara/src/modules/DataModule.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ $module-background: #f0f0f0;
.Password,
.Text,
.TextArea,
.Time,
.Select {
@extend %ModuleContainer;
}
Expand Down
24 changes: 24 additions & 0 deletions packages/cascara/src/modules/DataTime/DataTime.doc.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
title: DataTime
propTable: DataTime.js
type: 'module'
public: false
---

A data module to display **string** values, it renders a input of type **time**.

### Sandbox

Feel free to play around with the following code:

```jsx
<ModuleSandbox
isEditing={true}
record={{
id: 1,
time: '13:30',
}}
>
<DataTime attribute='time' isEditable={true} label='Schedule' value='13:30' />
</ModuleSandbox>
```
32 changes: 32 additions & 0 deletions packages/cascara/src/modules/DataTime/DataTime.fixture.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import DataTime from './DataTime';
import ModuleSandbox from '../ModuleSandbox';

const DataTimeSandbox = ({ isEditing, ...rest }) => (
<ModuleSandbox isEditing={isEditing}>
<DataTime {...rest} />
</ModuleSandbox>
);

const timeValue = '13:30';

const displayProps = {
label: 'Display',
value: timeValue,
};

const editingProps = {
isEditing: true,
label: 'Editing',
value: timeValue,
};

// These can be used in tests
export { displayProps, editingProps };

export default {
display: <DataTimeSandbox {...displayProps} />,
displayNoLabel: <DataTimeSandbox {...displayProps} isLabeled={false} />,
editing: <DataTimeSandbox {...editingProps} />,
editingNoLabel: <DataTimeSandbox {...editingProps} isLabeled={false} />,
};
74 changes: 74 additions & 0 deletions packages/cascara/src/modules/DataTime/DataTime.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import React, { useContext } from 'react';
import { Input } from 'reakit/Input';
import pt from 'prop-types';
import { ModuleContext } from '../context';
import styles from '../DataModule.module.scss';

import ModuleErrorBoundary from '../ModuleErrorBoundary';
import getAccessibleLabelSetters from '../helpers';

const propTypes = {
/** A module can have an Attribute, which will be used as form field name */
attribute: pt.string,
/** A Module can be defined to not present an editing state */
isEditable: pt.bool,
/** Presents the input without a label. NOT USER CONFIGURABLE */
isLabeled: pt.bool,
/** A Module needs to have a unique label relative to its context */
label: pt.string,
/** A Module can have a value */
value: pt.string,
};

const DataTime = ({
attribute,
isEditable = true,
isLabeled = true,
label,
value,
...rest
}) => {
const { isEditing, formMethods } = useContext(ModuleContext);
const { setAriaLabel, setHtmlFor } = getAccessibleLabelSetters(
isLabeled,
label
);

const renderEditing = (
<label htmlFor={setHtmlFor}>
{label && isLabeled && <span className={styles.LabelText}>{label}</span>}
<Input
{...rest}
aria-label={setAriaLabel}
className={styles.Time}
defaultValue={value}
id={label}
name={attribute || label}
ref={formMethods?.register}
type='time'
/>
</label>
);

const renderDisplay = (
<span>
{label && isLabeled && <span className={styles.LabelText}>{label}</span>}
<span aria-label={label} className={styles.Time} {...rest}>
{value}
</span>
</span>
);

// Do not render an editable input if the module is not editable
return (
<ModuleErrorBoundary>
<div className={styles.Time}>
{isEditing && isEditable ? renderEditing : renderDisplay}
</div>
</ModuleErrorBoundary>
);
};

DataTime.propTypes = propTypes;

export default DataTime;
98 changes: 98 additions & 0 deletions packages/cascara/src/modules/DataTime/DataTime.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { fireEvent, render, screen } from '@testing-library/react';

import cosmosFixtures, { displayProps, editingProps } from './DataTime.fixture';

const { display, editing, displayNoLabel, editingNoLabel } = cosmosFixtures;

describe('DataTime', () => {
// without ModuleSandbox will render the property information into a span

describe('display', () => {
// We need a place to store the view for snapshot testing. This is not required when we are using `screen` directly from RTL.
let view;

beforeEach(() => {
// Set the render container to our `view` so it is in scope for the snapshot test
view = render(display).container;
});

test('snapshot', () => {
expect(view).toMatchSnapshot();
});

test('renders a <span> by default', () => {
const input = screen.getByLabelText(displayProps.label);
// Make sure the actual DOM element is not render an input
expect(input.tagName).toMatch('SPAN');
// Make sure the dom element that has our aria-label is the input
expect(input.classList.contains('Time')).toBe(true);
});
});

describe('editing', () => {
// We need a place to store the view for snapshot testing. This is not required when we are using `screen` directly from RTL.
let view;

beforeEach(() => {
// Set the render container to our `view` so it is in scope for the snapshot test
view = render(editing).container;
});

test('snapshot', () => {
expect(view).toMatchSnapshot();
});

test('renders a <input email> by default', () => {
const input = screen.getByLabelText(editingProps.label);
// Check that we also use the correct type
expect(input).toHaveAttribute('type', 'time');
});

test('change value', () => {
const newTime = '14:50';
const input = screen.getByLabelText(editingProps.label);
fireEvent.change(input, { target: { value: newTime } });
expect(input).toHaveValue(newTime);
});
});

describe('accessibility', () => {
test('editing', () => {
render(editing);

const input = screen.getByLabelText(editingProps.label);
// The label tag is the parent wrapper
const label = input.closest('label');

// Test is written this way to make sure we know that both values need to be the same.
const linkedLabelValue = editingProps.label;

// Verify label for attribute has linked value
expect(label).toHaveAttribute(
'for',
expect.stringContaining(linkedLabelValue)
);
// Verify input id attribute has linked value
expect(input).toHaveAttribute(
'id',
expect.stringContaining(linkedLabelValue)
);
// Check that the input does NOT have an aria-label defined because there is a label tag
expect(input).not.toHaveAttribute('aria-label');
});

test('display no label', () => {
// Make sure that the input is still accessible with label text even when we are not showing a label tag in tables
render(displayNoLabel);
const input = screen.getByLabelText(displayProps.label);
expect(input).toBeDefined();
});

test('editing no label', () => {
// Make sure that the input is still accessible with label text even when we are not showing a label tag in tables
render(editingNoLabel);
const input = screen.getByLabelText(editingProps.label);
expect(input).toBeDefined();
});
});
});
48 changes: 48 additions & 0 deletions packages/cascara/src/modules/DataTime/DataTime.test.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`DataTime display snapshot 1`] = `
<div>
<div
class="Time"
>
<span>
<span
class="LabelText"
>
Display
</span>
<span
aria-label="Display"
class="Time"
>
13:30
</span>
</span>
</div>
</div>
`;

exports[`DataTime editing snapshot 1`] = `
<div>
<div
class="Time"
>
<label
for="Editing"
>
<span
class="LabelText"
>
Editing
</span>
<input
class="Time"
id="Editing"
name="Editing"
type="time"
value="13:30"
/>
</label>
</div>
</div>
`;
1 change: 1 addition & 0 deletions packages/cascara/src/modules/DataTime/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './DataTime';
2 changes: 2 additions & 0 deletions packages/cascara/src/modules/ModuleKeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import DataImage from './DataImage';
import DataFile from './DataFile';
import DataDate from './DataDate';
import DataDateTime from './DataDateTime';
import DataTime from './DataTime';
import DataMonth from './DataMonth';
import DataPassword from './DataPassword';
import DataTel from './DataTel';
Expand All @@ -41,6 +42,7 @@ const dataModules = {
tel: DataTel,
text: DataText,
textarea: DataTextArea,
time: DataTime,
};

export { actionModules, dataModules };
1 change: 1 addition & 0 deletions packages/cascara/src/modules/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export { default as ModuleSandbox } from './ModuleSandbox';
export { default as DataImage } from './DataImage';
export { default as DataFile } from './DataFile';
export { default as DataDate } from './DataDate';
export { default as DataTime } from './DataTime';
export { default as DataDateTime } from './DataDateTime';
export { default as DataMonth } from './DataMonth';
export { default as DataPassword } from './DataPassword';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ exports[`Table snapshot tests dataDisplay with non-existent module 1`] = `
class="Error"
data-testid="module-error"
>
unknownModule is not a valid value for module. Try using one of [checkbox, date, datetime, email, file, image, json, month, number, passord, radio, select, tel, text, textarea]
unknownModule is not a valid value for module. Try using one of [checkbox, date, datetime, email, file, image, json, month, number, passord, radio, select, tel, text, textarea, time]
</div>
</td>
<td
Expand Down

0 comments on commit e3cb177

Please sign in to comment.