Skip to content

Commit 468a66b

Browse files
authored
[Calling] Reaction mobile view button animations (#4252)
1 parent 1f84011 commit 468a66b

18 files changed

+310
-25
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "feature",
4+
"workstream": "Reaction",
5+
"comment": "[Calling] Reaction mobile view buttons (#4260)",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "improvement",
4+
"workstream": "Componentize the mobile view reaction button",
5+
"comment": "Componentizing the reaction button for mobile view",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "improvement",
4+
"workstream": "Add mobile view reactions for drawer menu",
5+
"comment": "Reaction mobile view button animations",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "improvement",
4+
"workstream": "removing unwanted dependency",
5+
"comment": "Reaction mobile button PR feedback",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "improvement",
4+
"workstream": "Reactions",
5+
"comment": "CC refactor and UI styles unit update",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "feature",
4+
"workstream": "Reactions",
5+
"comment": "Componentizing the reaction button for mobile view",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "prerelease",
3+
"area": "improvement",
4+
"workstream": "Knit pick",
5+
"comment": "Reaction mobile view button animations",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}

packages/react-components/review/beta/react-components.api.md

+11
Original file line numberDiff line numberDiff line change
@@ -1031,6 +1031,7 @@ export interface _DrawerMenuItemProps {
10311031
itemKey: string;
10321032
// (undocumented)
10331033
onItemClick?: (ev?: React_2.MouseEvent<HTMLElement> | React_2.KeyboardEvent<HTMLElement>, itemKey?: string) => void;
1034+
onRendererContent?: () => JSX.Element;
10341035
secondaryComponent?: JSX.Element;
10351036
secondaryIconProps?: IIconProps;
10361037
secondaryText?: string;
@@ -1937,6 +1938,16 @@ export interface ReactionButtonStrings {
19371938
tooltipDisabledContent?: string;
19381939
}
19391940

1941+
// @internal
1942+
export const _ReactionDrawerMenuItem: (props: _ReactionMenuItemProps) => JSX.Element;
1943+
1944+
// @internal
1945+
export interface _ReactionMenuItemProps {
1946+
disabled?: boolean;
1947+
onReactionClick?: (reaction: string) => Promise<void>;
1948+
reactionResources?: ReactionResources;
1949+
}
1950+
19401951
// @beta
19411952
export interface ReactionResources {
19421953
applauseReaction?: ReactionSprite;

packages/react-components/review/stable/react-components.api.md

+1
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,7 @@ export interface _DrawerMenuItemProps {
801801
itemKey: string;
802802
// (undocumented)
803803
onItemClick?: (ev?: React_2.MouseEvent<HTMLElement> | React_2.KeyboardEvent<HTMLElement>, itemKey?: string) => void;
804+
onRendererContent?: () => JSX.Element;
804805
secondaryComponent?: JSX.Element;
805806
secondaryIconProps?: IIconProps;
806807
secondaryText?: string;

packages/react-components/src/components/Drawer/DrawerMenu.tsx

+38-22
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ export interface _DrawerMenuProps {
4646
styles?: _DrawerMenuStyles;
4747
}
4848

49+
const isDrawerMenuItem = (item: _DrawerMenuItemProps): boolean => {
50+
return item.onRendererContent === undefined;
51+
};
52+
4953
/**
5054
* Takes a set of menu items and returns a created menu inside a {@link _DrawerSurface}.
5155
*
@@ -61,7 +65,7 @@ export const _DrawerMenu = (props: _DrawerMenuProps): JSX.Element => {
6165
const menuItemsToRender = useMemo(() => {
6266
let items: _DrawerMenuItemProps[] | undefined = props.items;
6367
for (const subMenuKey of selectedKeyPath) {
64-
items = items?.find((item) => item.itemKey === subMenuKey)?.subMenuProps;
68+
items = items?.find((item) => isDrawerMenuItem(item) && item.itemKey === subMenuKey)?.subMenuProps;
6569
}
6670
return items;
6771
}, [props.items, selectedKeyPath]);
@@ -85,7 +89,7 @@ export const _DrawerMenu = (props: _DrawerMenuProps): JSX.Element => {
8589

8690
// Ensure the first item has a border radius that matches the DrawerSurface
8791
const borderRadius = useTheme().effects.roundedCorner4;
88-
const firstItemStyle = menuItemsToRender && menuItemsToRender[0]?.styles;
92+
const firstItemStyle = menuItemsToRender?.[0] && menuItemsToRender[0]?.styles;
8993
const modifiedFirstItemStyle = useMemo(
9094
() =>
9195
merge(firstItemStyle ?? {}, {
@@ -105,26 +109,38 @@ export const _DrawerMenu = (props: _DrawerMenuProps): JSX.Element => {
105109
heading={props.heading}
106110
>
107111
<Stack styles={props.styles} role="menu" data-ui-id="drawer-menu">
108-
{menuItemsToRender?.slice(0, 1).map((item) => (
109-
<DrawerMenuItem
110-
{...item}
111-
key={`${item.itemKey}` + '0'}
112-
shouldFocusOnMount={true}
113-
styles={modifiedFirstItemStyle}
114-
onItemClick={(ev, itemKey) => {
115-
onItemClick(item, ev, itemKey);
116-
}}
117-
/>
118-
))}
119-
{menuItemsToRender?.slice(1).map((item, i) => (
120-
<DrawerMenuItem
121-
{...item}
122-
key={`${item.itemKey}` + `${i + 1}`}
123-
onItemClick={(ev, itemKey) => {
124-
onItemClick(item, ev, itemKey);
125-
}}
126-
/>
127-
))}
112+
{menuItemsToRender?.slice(0, 1).map((item) =>
113+
isDrawerMenuItem(item) ? (
114+
<DrawerMenuItem
115+
{...item}
116+
key={`${item.itemKey}` + '0'}
117+
shouldFocusOnMount={item.itemKey === 'reactions' ? false : true}
118+
styles={modifiedFirstItemStyle}
119+
onItemClick={
120+
item.itemKey === 'reactions'
121+
? undefined
122+
: (ev, itemKey) => {
123+
onItemClick(item, ev, itemKey);
124+
}
125+
}
126+
/>
127+
) : (
128+
item.onRendererContent?.()
129+
)
130+
)}
131+
{menuItemsToRender?.slice(1).map((item, i) =>
132+
isDrawerMenuItem(item) ? (
133+
<DrawerMenuItem
134+
{...item}
135+
key={`${item.itemKey}` + `${i + 1}`}
136+
onItemClick={(ev, itemKey) => {
137+
onItemClick(item, ev, itemKey);
138+
}}
139+
/>
140+
) : (
141+
item.onRendererContent?.()
142+
)
143+
)}
128144
</Stack>
129145
</_DrawerSurface>
130146
);

packages/react-components/src/components/Drawer/DrawerMenuItem.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export interface _DrawerMenuItemProps {
5454
* Property to set the focus since this is the first item in the menu
5555
*/
5656
shouldFocusOnMount?: boolean;
57+
/**
58+
* Custom JSX item injection for custom mobile view button on drawers
59+
*/
60+
onRendererContent?: () => JSX.Element;
5761
}
5862

5963
/**
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
/* @conditional-compile-remove(reaction) */
5+
import { IRawStyle, IStyle, mergeStyles, Stack } from '@fluentui/react';
6+
/* @conditional-compile-remove(reaction) */
7+
import React from 'react';
8+
/* @conditional-compile-remove(reaction) */
9+
import { useTheme } from '../../theming/FluentThemeProvider';
10+
/* @conditional-compile-remove(reaction) */
11+
import { mobileViewEmojiStyles, mobileViewMenuItemStyle } from '../styles/ReactionButton.styles';
12+
/* @conditional-compile-remove(reaction) */
13+
import { IconButton } from '@fluentui/react';
14+
/* @conditional-compile-remove(reaction) */
15+
import { _DrawerMenuItemProps, ReactionResources } from '../..';
16+
17+
/* @conditional-compile-remove(reaction) */
18+
/**
19+
* Props for the ReactionMenuItem
20+
*
21+
* @internal
22+
*/
23+
export interface _ReactionMenuItemProps {
24+
/**
25+
* Reaction resources to render for mobile button menus for reaction
26+
*/
27+
reactionResources?: ReactionResources;
28+
/**
29+
* reaction click event from the call adapter.
30+
*/
31+
onReactionClick?: (reaction: string) => Promise<void>;
32+
/**
33+
* Whether the menu item is disabled
34+
* @defaultvalue false
35+
*/
36+
disabled?: boolean;
37+
}
38+
39+
/* @conditional-compile-remove(reaction) */
40+
/**
41+
* Maps the individual item in menuProps.items passed in the {@link DrawerMenu} into a UI component.
42+
*
43+
* @internal
44+
*/
45+
export const _ReactionDrawerMenuItem = (props: _ReactionMenuItemProps): JSX.Element => {
46+
const theme = useTheme();
47+
const resources = props.reactionResources;
48+
const emojiResource: Map<string, string | undefined> = new Map([
49+
['like', resources?.likeReaction?.url],
50+
['heart', resources?.heartReaction?.url],
51+
['laugh', resources?.laughReaction?.url],
52+
['applause', resources?.applauseReaction?.url],
53+
['surprised', resources?.surprisedReaction?.url]
54+
]);
55+
const emojis: string[] = ['like', 'heart', 'laugh', 'applause', 'surprised'];
56+
57+
const borderRadius = useTheme().effects.roundedCorner4;
58+
const modifiedFirstItemStyle = {
59+
root: {
60+
borderTopRightRadius: borderRadius,
61+
borderTopLeftRadius: borderRadius,
62+
marginTop: '12px'
63+
}
64+
};
65+
66+
return (
67+
<Stack
68+
id="reaction"
69+
role="menuitem"
70+
horizontal
71+
className={mergeStyles(
72+
drawerMenuItemRootStyles(theme.palette.neutralLight, theme.fonts.small),
73+
props.disabled ? disabledDrawerMenuItemRootStyles(theme.palette.neutralQuaternaryAlt) : undefined,
74+
modifiedFirstItemStyle.root
75+
)}
76+
>
77+
<div style={mobileViewMenuItemStyle()}>
78+
{emojis.map((emoji, index) => {
79+
const resourceUrl = emojiResource.get(emoji.toString());
80+
81+
return (
82+
<IconButton
83+
key={index}
84+
onClick={() => {
85+
props.onReactionClick?.(emoji);
86+
}}
87+
style={mobileViewEmojiStyles(resourceUrl ? resourceUrl : '', 'running')}
88+
/>
89+
);
90+
})}
91+
</div>
92+
</Stack>
93+
);
94+
};
95+
96+
/* @conditional-compile-remove(reaction) */
97+
const drawerMenuItemRootStyles = (hoverBackground: string, fontSize: IRawStyle): IStyle => ({
98+
...fontSize,
99+
height: '3rem',
100+
lineHeight: '3rem',
101+
padding: '0rem 0.75rem',
102+
cursor: 'pointer',
103+
':hover, :focus': {
104+
background: hoverBackground
105+
}
106+
});
107+
108+
/* @conditional-compile-remove(reaction) */
109+
const disabledDrawerMenuItemRootStyles = (background: string): IStyle => ({
110+
pointerEvents: 'none',
111+
background: background,
112+
':hover, :focus': {
113+
background: background
114+
}
115+
});

packages/react-components/src/components/Drawer/index.ts

+4
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,7 @@ export type { _DrawerMenuItemProps } from './DrawerMenuItem';
77

88
export { _DrawerSurface } from './DrawerSurface';
99
export type { _DrawerSurfaceProps, _DrawerSurfaceStyles } from './DrawerSurface';
10+
/* @conditional-compile-remove(reaction) */
11+
export { _ReactionDrawerMenuItem } from './ReactionDrawerMenuItem';
12+
/* @conditional-compile-remove(reaction) */
13+
export type { _ReactionMenuItemProps } from './ReactionDrawerMenuItem';

packages/react-components/src/components/styles/ReactionButton.styles.ts

+49-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const emojiStyles = (backgroundImage: string, animationPlayState: string)
4444
justifyContent: 'center',
4545
alignItems: 'center',
4646
backgroundPosition: 'center',
47-
backgroundSize: `44px 2142px`,
47+
backgroundSize: `2.75rem 133.875rem`,
4848
transition: 'opacity 2s',
4949
backgroundColor: 'transparent',
5050
transform: `${animationPlayState === 'running' ? 'scale(0.8)' : 'scale(0.6)'}`
@@ -62,8 +62,8 @@ export const reactionEmojiMenuStyles = (): React.CSSProperties => {
6262
justifyContent: 'center',
6363
alignItems: 'center',
6464
flexDirection: 'row',
65-
width: '220px',
66-
height: '42px'
65+
width: '13.75rem',
66+
height: '2.625rem'
6767
};
6868
};
6969

@@ -82,3 +82,49 @@ export const reactionToolTipHostStyle = (): ITooltipHostStyles => {
8282
}
8383
};
8484
};
85+
86+
/* @conditional-compile-remove(reaction) */
87+
/**
88+
*
89+
* @private
90+
*/
91+
export const mobileViewMenuItemStyle = (): React.CSSProperties => {
92+
return {
93+
display: 'flex',
94+
justifyContent: 'space-between',
95+
alignItems: 'center',
96+
flexDirection: 'row',
97+
width: '100%',
98+
height: '2.625rem'
99+
};
100+
};
101+
102+
/* @conditional-compile-remove(reaction) */
103+
/**
104+
* @param backgroundImage - the uri for the reaction emoji resource
105+
* @param animationPlayState - the value is either 'running' or 'paused' based on the mouse hover event
106+
*
107+
* @private
108+
*/
109+
export const mobileViewEmojiStyles = (backgroundImage: string, animationPlayState: string): React.CSSProperties => {
110+
const imageResourceUrl = `url(${backgroundImage})`;
111+
return {
112+
display: 'flex',
113+
flexDirection: 'column',
114+
height: '100%',
115+
width: '2.75rem',
116+
backgroundImage: imageResourceUrl,
117+
animationName: playFrames(),
118+
animationDuration: '8.12s',
119+
animationTimingFunction: `steps(102)`,
120+
animationPlayState: animationPlayState,
121+
animationIterationCount: 'infinite',
122+
justifyContent: 'center',
123+
alignItems: 'center',
124+
backgroundPosition: 'center',
125+
backgroundSize: `2.75rem 133.875rem`,
126+
transition: 'opacity 2s',
127+
backgroundColor: 'transparent',
128+
transform: `${animationPlayState === 'running' ? 'scale(0.8)' : 'scale(0.6)'}`
129+
};
130+
};

0 commit comments

Comments
 (0)