Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
15 changes: 9 additions & 6 deletions client/src/components/UIPatterns/UIPatterns.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import UIPatternsMessage from './UIPatternsMessage';
import UIPatternsDescriptionList from './UIPatternsDescriptionList';
import UIPatternsDropdownSelector from './UIPatternsDropdownSelector';
import './UIPatterns.module.scss';

Expand All @@ -11,18 +12,20 @@ function UIPatterns() {
</div>
<div styleName="items">
<div styleName="grid-item">
<div styleName="item-header">
<h6>Message</h6>
</div>
<h6>Message</h6>
<UIPatternsMessage />
</div>
<div styleName="grid-item">
<div styleName="item-header">
<h6>DropdownSelector</h6>
</div>
<h6>DropdownSelector</h6>
<UIPatternsDropdownSelector />
</div>
</div>
<div styleName="items">
<div styleName="grid-item">
<h6>DescriptionList</h6>
<UIPatternsDescriptionList />
</div>
</div>
</div>
);
}
Expand Down
30 changes: 6 additions & 24 deletions client/src/components/UIPatterns/UIPatterns.module.scss
Original file line number Diff line number Diff line change
@@ -1,31 +1,13 @@
/* This is a CSS Modules, and minimal, version of the Dashboard stylesheet */
/* This is a version of the Dashboard stylesheet with these changes:
- CSS Modules
- minimal
- no flexbox
*/

.container {
display: flex;
flex-direction: column;

// FAQ: Extra padding is added to bottom only for this section
padding: 20px 40px 40px 25px;
}
.items {
/* As a flex item */
flex-grow: 1;

/* As a flex container */
display: flex;
flex-direction: column;
}
/* Make bottom-most item fill up remaining vertical space */
.items > :last-child {
flex-grow: 1;
}

.header,
.item-header {
display: flex;
justify-content: space-between;
align-items: baseline;
}

.header {
border-bottom: 1px solid #707070;
Expand All @@ -35,7 +17,7 @@
font-weight: 400;
}

/* FAQ: Indirect child element of `.dashboard-items` */
/* FAQ: Ancestor of `.dashboard-items` */
/* RFC: Class name `.dashboard-item` (to coincide with `.dashboard-items`) */
.grid-item {
margin-top: 20px;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import { DescriptionList, Icon } from '_common';

import './UIPatternsDescriptionList.module.css';

const DATA = {
Username: 'bobward500',
Prefix: 'Mr.',
Name: 'Bob Ward',
Suffix: 'The 5th',
'Favorite Numeric Value': 5,
Icon: <Icon name="dashboard" />
};

function UIPatternsDropdownSelector() {
return (
<>
<div styleName="list-cols">
<dl>
<dt>Vertical Layout & Default Density</dt>
<dd>
<DescriptionList data={DATA} />
</dd>
</dl>
<dl>
<dt>Vertical Layout & Compact Density</dt>
<dd>
<DescriptionList data={DATA} density="compact" />
</dd>
</dl>
<dl>
<dt>Vertical Layout & Compact Density - Narrow Container</dt>
<dd>
<DescriptionList
data={DATA}
density="compact"
styleName="item-x-narrow"
/>
</dd>
</dl>
</div>
<div styleName="list-rows">
<dl>
<dt>Horizontal Layout & Default Density</dt>
<dd>
<DescriptionList data={DATA} direction="horizontal" />
</dd>
<dt>Horizontal Layout & Compact Density</dt>
<dd>
<DescriptionList
data={DATA}
density="compact"
direction="horizontal"
/>
</dd>
<dt>Horizontal Layout & Compact Density - Narrow Container</dt>
<dd>
<DescriptionList
data={DATA}
density="compact"
direction="horizontal"
styleName="item-narrow"
/>
</dd>
</dl>
</div>
</>
);
}

export default UIPatternsDropdownSelector;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/* Force narrow widths to trigger density features */
.item-narrow { width: 500px; }
.item-x-narrow { width: 7ch; }

/* Align vertical lists left-to-right, or top-to-bottom */
.list-cols,
.list-rows { display: flex; }
.list-cols > * { flex-grow: 1; }
.list-cols { flex-direction: row; }
.list-rows { flex-direction: column; }
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './UIPatternsDescriptionList';
63 changes: 63 additions & 0 deletions client/src/components/_common/DescriptionList/DescriptionList.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';
import PropTypes from 'prop-types';

import './DescriptionList.module.css';

export const DIRECTION_CLASS_MAP = {
vertical: 'is-vert',
horizontal: 'is-horz'
};
export const DEFAULT_DIRECTION = 'vertical';
export const DIRECTIONS = ['', ...Object.keys(DIRECTION_CLASS_MAP)];

export const DENSITY_CLASS_MAP = {
compact: 'is-narrow',
default: 'is-wide'
};
export const DEFAULT_DENSITY = 'default';
export const DENSITIES = ['', ...Object.keys(DENSITY_CLASS_MAP)];

const DescriptionList = ({ className, data, density, direction }) => {
const modifierClasses = [];
modifierClasses.push(DENSITY_CLASS_MAP[density || DEFAULT_DENSITY]);
modifierClasses.push(DIRECTION_CLASS_MAP[direction || DEFAULT_DIRECTION]);
const containerStyleNames = ['container', ...modifierClasses].join(' ');

return (
<dl
styleName={containerStyleNames}
className={className}
data-testid="list"
>
{Object.keys(data).map(key => (
<React.Fragment key={key}>
<dt styleName="key" data-testid="key">
{key}
</dt>
<dd styleName="value" data-testid="value">
{data[key]}
</dd>
</React.Fragment>
))}
</dl>
);
};
DescriptionList.propTypes = {
/** Additional className for the root element */
className: PropTypes.string,
/** Selector type */
/* FAQ: We can support any values, even a component */
// eslint-disable-next-line react/forbid-prop-types
data: PropTypes.object.isRequired,
/** Layout density */
density: PropTypes.oneOf(DENSITIES),
/** Layout direction */
direction: PropTypes.oneOf(DIRECTIONS)
};
DescriptionList.defaultProps = {
className: '',
density: DEFAULT_DENSITY,
direction: DEFAULT_DIRECTION
};

export default DescriptionList;
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
.container {
/* … */
}

/* Children */

.key {
composes: u-ellipsis from '../../../styles/trumps/_u-ellipsis.scss';

font-weight: 500;
text-overflow: ':';
}
.key::after {
content: ':';
display: inline;
margin-right: 0.25em;
}
.is-horz .value {
white-space: nowrap;
}

/* Types */

.is-vert {
/* … */
}
.is-horz {
display: flex;
flex-direction: row;
}
.is-horz .key ~ .key::before {
content: '|';
display: inline-block;
}
.is-horz.is-narrow .key ~ .key::before {
margin-left: 0.5em;
margin-right: 0.5em;
}
.is-horz.is-wide .key ~ .key::before {
margin-left: 1em;
margin-right: 1em;
}

.is-vert.is-narrow .value {
/* TODO: Support mixins, but prevent SCSS pollution in `src/styles/` */
/* HACK: CSS Modules will NOT compose with nested classes */
/* composes: u-ellipsis from '../../../styles/trumps/_u-ellipsis.scss'; */
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

/* Overwrite Bootstrap `_reboot.scss` */
.is-vert.is-narrow .value { margin-left: 0; }
.is-vert.is-wide .value { margin-left: 2.5rem; } /* 40px Firefox default */
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';
import { render } from '@testing-library/react';
import DescriptionList, * as DL from './DescriptionList';

const DATA = {
Username: 'bobward500',
Prefix: 'Mr.',
Name: 'Bob Ward',
Suffix: 'The 5th'
};

describe('Description List', () => {
it('has accurate tags', async () => {
const { getByTestId, findAllByTestId } = render(<DescriptionList data={DATA} />);
const list = getByTestId('list');
const keys = await findAllByTestId('key');
const values = await findAllByTestId('value');
expect(list).toBeDefined();
expect(list.tagName).toEqual('DL');
keys.forEach( key => {
expect(key.tagName).toEqual('DT');
});
values.forEach( value => {
expect(value.tagName).toEqual('DD');
});
});
it.each(DL.DIRECTIONS)('has accurate className when direction is "%s"', direction => {
const { getByTestId, findAllByTestId } = render(<DescriptionList data={DATA} direction={direction} />);
const list = getByTestId('list');
const className = DL.DIRECTION_CLASS_MAP[direction || DL.DEFAULT_DIRECTION];
expect(list).toBeDefined();
expect(list.className).toMatch(className);
});
it.each(DL.DENSITIES)('has accurate className when density is "%s"', density => {
const { getByTestId, findAllByTestId } = render(<DescriptionList data={DATA} density={density} />);
const list = getByTestId('list');
const className = DL.DENSITY_CLASS_MAP[density || DL.DEFAULT_DENSITY];
expect(list).toBeDefined();
expect(list.className).toMatch(className);
});
});
3 changes: 3 additions & 0 deletions client/src/components/_common/DescriptionList/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import DescriptionList from './DescriptionList';

export default DescriptionList;
1 change: 1 addition & 0 deletions client/src/components/_common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export { default as InfiniteScrollTable } from './InfiniteScrollTable';
export { default as AppIcon } from './AppIcon';
export { default as Icon } from './Icon';
export { default as Message } from './Message';
export { default as DescriptionList } from './DescriptionList';
export { default as DropdownSelector } from './DropdownSelector';