Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.

Commit b2c657c

Browse files
chrismohrswese44
authored andcommitted
Add HorizontalActionList Component (#511)
* add post action link list * refactoring * use correct icon color * menuButton styling * update snapshots * add unit tests * rename to MessageActionsList * update reference images * fix reference image * fix vertical alignment * update interface to support both links and clickable * use a more generic name * remove stale reference images * add references with new name * add action states for open overflow menu * fix description * revert menuButton color override * update snapshot
1 parent d81ffbe commit b2c657c

19 files changed

+622
-6
lines changed
Loading
Loading
Loading
Loading
Loading
Loading
Loading

src/components/ActionLink/ActionLink.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,13 @@ const text = 'SharePoint Document Library website';
4242
<ActionLink text={text} icon={Icon} ariaLabel={text} href="https://www.yammer.com" newWindow={true} />
4343
</div>
4444
```
45+
46+
Compact:
47+
48+
```js { "props": { "data-description": "with compact" } }
49+
const Like = require('../Icon/icons/Like').default;
50+
51+
<div>
52+
<ActionLink text="Like" icon={Like} ariaLabel="Like this Post" compact={true} />
53+
</div>
54+
```

src/components/ActionLink/ActionLink.test.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,23 @@ describe('<ActionLink />', () => {
8282
expect(component).toMatchSnapshot();
8383
});
8484
});
85+
86+
describe('with compact set to true', () => {
87+
beforeEach(() => {
88+
component = shallow(
89+
<ActionLink
90+
className="TEST_CLASSNAME"
91+
text="Download"
92+
icon={Down}
93+
ariaLabel="Download the file"
94+
href="https://yammer.com"
95+
compact={true}
96+
/>,
97+
);
98+
});
99+
100+
it('matches its snapshot', () => {
101+
expect(component).toMatchSnapshot();
102+
});
103+
});
85104
});

src/components/ActionLink/ActionLink.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import * as React from 'react';
44
import { join } from '../../util/classNames';
55
import Block, { TextSize } from '../Block';
66
import Clickable from '../Clickable';
7+
import Strong from '../Strong';
78
import { FixedGridRow, FixedGridColumn, GutterSize } from '../FixedGrid';
89
import { IconSize } from '../Icon';
910
import NavigationLink from '../NavigationLink';
@@ -15,17 +16,20 @@ import { ActionLinkProps, NavigationActionLinkProps, ClickableActionLinkProps }
1516
*/
1617
export default class ActionLink extends React.Component<ActionLinkProps> {
1718
public render() {
18-
const { ariaLabel, className, icon: Icon, text } = this.props;
19+
const { ariaLabel, className, compact = false, icon: Icon, text } = this.props;
20+
21+
const gutterSize = compact ? GutterSize.XSMALL : GutterSize.SMALL;
22+
const iconSize = compact ? IconSize.SMALL : IconSize.MEDIUM;
1923
// Remove Block around Icon when this is addressed: https://github.com/Microsoft/YamUI/issues/327
2024
const content = (
21-
<FixedGridRow gutterSize={GutterSize.SMALL}>
25+
<FixedGridRow gutterSize={gutterSize}>
2226
<FixedGridColumn fixed={true}>
23-
<Block push={2}>
24-
<Icon size={IconSize.MEDIUM} block={true} />
27+
<Block push={compact ? 1 : 2}>
28+
<Icon size={iconSize} block={true} />
2529
</Block>
2630
</FixedGridColumn>
2731
<FixedGridColumn>
28-
<Block textSize={TextSize.MEDIUM_SUB}>{text}</Block>
32+
<Block textSize={TextSize.MEDIUM_SUB}>{compact ? <Strong>{text}</Strong> : text}</Block>
2933
</FixedGridColumn>
3034
</FixedGridRow>
3135
);

src/components/ActionLink/ActionLink.types.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export interface BaseActionLinkProps extends BaseComponentProps {
1717
* The visible text.
1818
*/
1919
text: string;
20+
21+
/**
22+
* A more compressed format.
23+
*/
24+
compact?: boolean;
2025
}
2126

2227
export interface NavigationActionLinkProps extends BaseActionLinkProps {
@@ -35,7 +40,7 @@ export interface ClickableActionLinkProps extends BaseActionLinkProps {
3540
/**
3641
* A click handler.
3742
*/
38-
onClick: ((e: React.MouseEvent<HTMLButtonElement | HTMLLinkElement>) => void);
43+
onClick?: ((ev?: React.MouseEvent<HTMLElement> | React.KeyboardEvent<HTMLElement>) => void);
3944
}
4045

4146
export type ActionLinkProps = NavigationActionLinkProps | ClickableActionLinkProps;

src/components/ActionLink/__snapshots__/ActionLink.test.tsx.snap

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,41 @@ exports[`<ActionLink /> with additional className matches its snapshot 1`] = `
3333
</CustomizedNavigationLink>
3434
`;
3535

36+
exports[`<ActionLink /> with compact set to true matches its snapshot 1`] = `
37+
<CustomizedNavigationLink
38+
ariaLabel="Download the file"
39+
block={true}
40+
className="y-actionLink TEST_CLASSNAME"
41+
href="https://yammer.com"
42+
>
43+
<FixedGridRow
44+
gutterSize="xSmall"
45+
>
46+
<FixedGridColumn
47+
fixed={true}
48+
>
49+
<CustomizedBlock
50+
push={1}
51+
>
52+
<Down
53+
block={true}
54+
size="14"
55+
/>
56+
</CustomizedBlock>
57+
</FixedGridColumn>
58+
<FixedGridColumn>
59+
<CustomizedBlock
60+
textSize="mediumSub"
61+
>
62+
<Strong>
63+
Download
64+
</Strong>
65+
</CustomizedBlock>
66+
</FixedGridColumn>
67+
</FixedGridRow>
68+
</CustomizedNavigationLink>
69+
`;
70+
3671
exports[`<ActionLink /> with href and ariaLabel matches its snapshot 1`] = `
3772
<CustomizedNavigationLink
3873
ariaLabel="Download file"
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
### Examples
2+
3+
```js { "props": { "data-description": "basic", "data-action-states": "[{\"action\":\"none\"},{\"action\":\"click\",\"selector\":\".y-menu-button\"},{\"action\":\"hover\",\"selector\":\".y-menu-button--item-text\"}]" } }
4+
const Like = require('../Icon/icons/Like').default;
5+
const Reply = require('../Icon/icons/Reply').default;
6+
const Share = require('../Icon/icons/Share').default;
7+
const Flag = require('../Icon/icons/Flag').default;
8+
const Strong = require('../Strong').default;
9+
10+
const items = [
11+
{
12+
key: 'like',
13+
icon: Like,
14+
text: 'Like',
15+
onClick: () => action('clicked like'),
16+
},
17+
{
18+
key: 'reply',
19+
icon: Reply,
20+
text: 'Reply',
21+
onClick: () => action('clicked reply'),
22+
},
23+
{
24+
key: 'share',
25+
icon: Share,
26+
text: 'Share',
27+
onClick: () => action('clicked share'),
28+
},
29+
{
30+
key: 'flag',
31+
icon: Flag,
32+
text: 'Follow in Inbox',
33+
onClick: () => action('clicked follow'),
34+
},
35+
];
36+
37+
<div style={{ maxWidth: '400px', height: '60px' }}>
38+
<HorizontalActionList items={items} maxVisibleItemCount={3} />
39+
</div>;
40+
```
41+
42+
```js { "props": { "data-description": "counts", "data-action-states": "[{\"action\":\"none\"},{\"action\":\"hover\",\"selector\":\".y-clickable\"}]" } }
43+
const Like = require('../Icon/icons/Like').default;
44+
const Reply = require('../Icon/icons/Reply').default;
45+
const Share = require('../Icon/icons/Share').default;
46+
47+
const items = [
48+
{
49+
key: 'like',
50+
icon: Like,
51+
text: 'Like',
52+
unlinkedText: '4',
53+
onClick: () => action('clicked like'),
54+
},
55+
{
56+
key: 'reply',
57+
icon: Reply,
58+
text: 'Reply',
59+
unlinkedText: '2',
60+
onClick: () => action('clicked reply'),
61+
},
62+
{
63+
key: 'share',
64+
icon: Share,
65+
text: 'Share',
66+
onClick: () => action('clicked share'),
67+
},
68+
];
69+
70+
<div style={{ maxWidth: '400px' }}>
71+
<HorizontalActionList items={items} maxVisibleItemCount={2} />
72+
</div>;
73+
```
74+
75+
```js { "props": { "data-description": "no overflow" } }
76+
const Like = require('../Icon/icons/Like').default;
77+
const Reply = require('../Icon/icons/Reply').default;
78+
const Share = require('../Icon/icons/Share').default;
79+
80+
const items = [
81+
{
82+
key: 'like',
83+
icon: Like,
84+
text: 'Like',
85+
unlinkedText: '8',
86+
onClick: () => action('clicked like'),
87+
},
88+
{
89+
key: 'reply',
90+
icon: Reply,
91+
text: 'Reply',
92+
onClick: () => action('clicked reply'),
93+
},
94+
];
95+
96+
<div style={{ maxWidth: '400px' }}>
97+
<HorizontalActionList items={items} maxVisibleItemCount={2} />
98+
</div>
99+
```
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*! Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. */
2+
import { mergeStyleSets, ITheme } from 'office-ui-fabric-react/lib/Styling';
3+
import { memoizeFunction } from 'office-ui-fabric-react/lib/Utilities';
4+
5+
export interface HorizontalActionListClassNameProps {
6+
theme: ITheme;
7+
}
8+
9+
export const getClassNames = memoizeFunction((classNameProps: HorizontalActionListClassNameProps) => {
10+
const { theme } = classNameProps;
11+
12+
return mergeStyleSets({
13+
unlinkedText: {
14+
color: theme.semanticColors.link,
15+
paddingLeft: '0.2rem',
16+
},
17+
menuButton: {
18+
top: '0.1rem',
19+
},
20+
});
21+
});
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*! Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. */
2+
import * as React from 'react';
3+
import { shallow, ShallowWrapper } from 'enzyme';
4+
import { HorizontalActionList } from './HorizontalActionList';
5+
import { HorizontalActionListItem, HorizontalActionListProps } from './HorizontalActionList.types';
6+
import Reply from '../Icon/icons/Reply';
7+
import Like from '../Icon/icons/Like';
8+
import { darkTheme } from '../Customizer';
9+
10+
describe('<HorizontalActionList />', () => {
11+
let component: ShallowWrapper<HorizontalActionListProps>;
12+
let items: HorizontalActionListItem[];
13+
14+
beforeEach(() => {
15+
items = [
16+
{
17+
icon: Like,
18+
text: 'like',
19+
ariaLabel: 'like',
20+
key: 'like',
21+
unlinkedText: '1',
22+
unlinkedTextAriaLabel: '1 person has like this post',
23+
onClick: jest.fn().mockName('likeOnClick'),
24+
},
25+
{
26+
icon: Reply,
27+
text: 'reply',
28+
ariaLabel: 'reply',
29+
key: 'reply',
30+
onClick: jest.fn().mockName('replyOnClick'),
31+
},
32+
];
33+
});
34+
35+
describe('with one item', () => {
36+
beforeEach(() => {
37+
component = shallow(<HorizontalActionList items={items.slice(0, 1)} overflowMenuAriaLabel="more items" />);
38+
});
39+
40+
it('matches its snapshot', () => {
41+
expect(component).toMatchSnapshot();
42+
});
43+
});
44+
45+
describe('with all items', () => {
46+
beforeEach(() => {
47+
component = shallow(<HorizontalActionList items={items} overflowMenuAriaLabel="more items" />);
48+
});
49+
50+
it('matches its snapshot', () => {
51+
expect(component).toMatchSnapshot();
52+
});
53+
});
54+
55+
describe('with all items, and maxVisibleItemCount of 1', () => {
56+
beforeEach(() => {
57+
component = shallow(
58+
<HorizontalActionList items={items} maxVisibleItemCount={1} overflowMenuAriaLabel="more items" />,
59+
);
60+
});
61+
62+
it('matches its snapshot', () => {
63+
expect(component).toMatchSnapshot();
64+
});
65+
});
66+
67+
describe('with a theme', () => {
68+
beforeEach(() => {
69+
component = shallow(<HorizontalActionList items={items} theme={darkTheme} overflowMenuAriaLabel="more items" />);
70+
});
71+
72+
it('matches its snapshot', () => {
73+
expect(component).toMatchSnapshot();
74+
});
75+
});
76+
});

0 commit comments

Comments
 (0)