Skip to content

Commit 5a5b9a1

Browse files
[Component and Story] Add background picker component (#2792)
1 parent c5df9e1 commit 5a5b9a1

9 files changed

Lines changed: 351 additions & 2 deletions

File tree

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add Video background effects picker component",
4+
"packageName": "@azure/communication-react",
5+
"email": "2684369+JamesBurnside@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"type": "prerelease",
3+
"comment": "Add Video background effects picker component",
4+
"packageName": "@azure/communication-react",
5+
"email": "2684369+JamesBurnside@users.noreply.github.com",
6+
"dependentChangeType": "patch"
7+
}

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1689,6 +1689,30 @@ export interface VerticalGalleryStyles extends BaseCustomStyles {
16891689
controlBar?: VerticalGalleryControlBarStyles;
16901690
}
16911691

1692+
// @internal
1693+
export type _VideoBackgroundEffectChoiceOption = _VideoEffectsItemProps;
1694+
1695+
// @internal
1696+
export const _VideoBackgroundEffectsPicker: (props: _VideoBackgroundEffectsPickerProps) => JSX.Element;
1697+
1698+
// @internal
1699+
export interface _VideoBackgroundEffectsPickerProps {
1700+
defaultSelectedEffectKey?: string;
1701+
itemsPerRow?: 'wrap' | number;
1702+
label?: string;
1703+
onChange?: (effectKey: string) => void;
1704+
options: _VideoBackgroundEffectChoiceOption[];
1705+
selectedEffectKey?: string;
1706+
styles?: _VideoBackgroundEffectsPickerStyles;
1707+
}
1708+
1709+
// @internal
1710+
export interface _VideoBackgroundEffectsPickerStyles {
1711+
label?: IStyle;
1712+
root?: IStyle;
1713+
rowRoot?: IStyle;
1714+
}
1715+
16921716
// @internal
16931717
export const _VideoEffectsItem: (props: _VideoEffectsItemProps) => JSX.Element;
16941718

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1432,6 +1432,30 @@ export interface VerticalGalleryStyles extends BaseCustomStyles {
14321432
controlBar?: VerticalGalleryControlBarStyles;
14331433
}
14341434

1435+
// @internal
1436+
export type _VideoBackgroundEffectChoiceOption = _VideoEffectsItemProps;
1437+
1438+
// @internal
1439+
export const _VideoBackgroundEffectsPicker: (props: _VideoBackgroundEffectsPickerProps) => JSX.Element;
1440+
1441+
// @internal
1442+
export interface _VideoBackgroundEffectsPickerProps {
1443+
defaultSelectedEffectKey?: string;
1444+
itemsPerRow?: 'wrap' | number;
1445+
label?: string;
1446+
onChange?: (effectKey: string) => void;
1447+
options: _VideoBackgroundEffectChoiceOption[];
1448+
selectedEffectKey?: string;
1449+
styles?: _VideoBackgroundEffectsPickerStyles;
1450+
}
1451+
1452+
// @internal
1453+
export interface _VideoBackgroundEffectsPickerStyles {
1454+
label?: IStyle;
1455+
root?: IStyle;
1456+
rowRoot?: IStyle;
1457+
}
1458+
14351459
// @internal
14361460
export const _VideoEffectsItem: (props: _VideoEffectsItemProps) => JSX.Element;
14371461

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
import { IStyle, Label, mergeStyles, Stack } from '@fluentui/react';
5+
import { useWarnings } from '@fluentui/react-hooks';
6+
import React from 'react';
7+
import { chunk } from '../utils';
8+
import { _VideoEffectsItem, _VideoEffectsItemProps } from './VideoEffectsItem';
9+
10+
/**
11+
* Props for {@link _VideoBackgroundEffectsPicker}
12+
* @internal
13+
*/
14+
export interface _VideoBackgroundEffectsPickerProps {
15+
/**
16+
* The options to display in the picker.
17+
*/
18+
options: _VideoBackgroundEffectChoiceOption[];
19+
20+
/**
21+
* The key of the current selected Video Background Effect.
22+
* If you provide this, you must maintain selection state by observing onChange events and passing a new value in when changed.
23+
*/
24+
selectedEffectKey?: string;
25+
26+
/**
27+
* Callback to invoke when a Video Background Effect is selected.
28+
* @param effectKey - The key of the Video Background Effect that was selected.
29+
*/
30+
onChange?: (effectKey: string) => void;
31+
32+
/**
33+
* The key of the Video Background Effect that is initially selected.
34+
* Only provide this if the picker is an uncontrolled component;
35+
* otherwise, use the `selectedEffectKey` property.
36+
*/
37+
defaultSelectedEffectKey?: string;
38+
39+
/**
40+
* The label to display for the picker.
41+
*/
42+
label?: string;
43+
44+
/**
45+
* The number of items to display per row.
46+
* @default 3
47+
*/
48+
itemsPerRow?: 'wrap' | number;
49+
50+
/**
51+
* Styles for the picker.
52+
*/
53+
styles?: _VideoBackgroundEffectsPickerStyles;
54+
}
55+
56+
/**
57+
* Option for the {@link _VideoBackgroundEffectsPicker}.
58+
* @internal
59+
*/
60+
export type _VideoBackgroundEffectChoiceOption = _VideoEffectsItemProps;
61+
62+
/**
63+
* Styles for the {@link _VideoBackgroundEffectsPicker}.
64+
* @internal
65+
*/
66+
export interface _VideoBackgroundEffectsPickerStyles {
67+
/**
68+
* Styles for the root element.
69+
*/
70+
root?: IStyle;
71+
72+
/**
73+
* Styles for the label.
74+
*/
75+
label?: IStyle;
76+
77+
/**
78+
* Styles for the root of each row element.
79+
*/
80+
rowRoot?: IStyle;
81+
}
82+
83+
/**
84+
* Picker for choosing a Video Background Effect.
85+
*
86+
* @remarks
87+
* This functions similar to a radio group of buttons, where the user can select one of the options.
88+
*
89+
* @internal
90+
*/
91+
export const _VideoBackgroundEffectsPicker = (props: _VideoBackgroundEffectsPickerProps): JSX.Element => {
92+
const [componentControlledSelectedEffectKey, setComponentControlledSelectedEffectKey] = React.useState<
93+
string | undefined
94+
>(props.defaultSelectedEffectKey);
95+
96+
// Warn the developer if they use the component in an incorrect controlled way.
97+
useWarnings({
98+
name: 'VideoBackgroundEffectsPicker',
99+
props,
100+
controlledUsage: {
101+
onChangeProp: 'onChange',
102+
valueProp: 'selectedEffectKey',
103+
defaultValueProp: 'defaultSelectedEffectKey'
104+
}
105+
});
106+
107+
const selectedEffect = props.selectedEffectKey ?? componentControlledSelectedEffectKey;
108+
const setSelectedEffect = (selectedEffectKey: string): void => {
109+
setComponentControlledSelectedEffectKey(selectedEffectKey);
110+
props.onChange?.(selectedEffectKey);
111+
};
112+
113+
const convertedOptions: _VideoEffectsItemProps[] = props.options.map((option) => ({
114+
isSelected: option.key === selectedEffect,
115+
onSelect: () => setSelectedEffect(option.key),
116+
...option
117+
}));
118+
119+
const optionsByRow =
120+
props.itemsPerRow === 'wrap' ? [convertedOptions] : chunk(convertedOptions, props.itemsPerRow ?? 3);
121+
122+
return (
123+
<Stack tokens={{ childrenGap: '0.5rem' }}>
124+
<Label className={mergeStyles(props.styles?.label)}>{props.label}</Label>
125+
{optionsByRow.map((options, rowIndex) => (
126+
<Stack
127+
className={mergeStyles(props.styles?.rowRoot)}
128+
wrap={props.itemsPerRow === 'wrap'}
129+
horizontal
130+
key={rowIndex}
131+
tokens={{ childrenGap: '0.5rem' }}
132+
>
133+
{options.map((option) => (
134+
<_VideoEffectsItem {...option} key={option.key} />
135+
))}
136+
</Stack>
137+
))}
138+
</Stack>
139+
);
140+
};

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,13 @@ export {
206206
_VideoEffectsItemAddImage
207207
} from './VideoEffects/PresetVideoEffectsItems';
208208

209+
export { _VideoBackgroundEffectsPicker } from './VideoEffects/VideoBackgroundEffectsPicker';
210+
export type {
211+
_VideoBackgroundEffectsPickerProps,
212+
_VideoBackgroundEffectChoiceOption,
213+
_VideoBackgroundEffectsPickerStyles
214+
} from './VideoEffects/VideoBackgroundEffectsPicker';
215+
209216
export type { VerticalGalleryStyles, VerticalGalleryStrings, VerticalGalleryControlBarStyles } from './VerticalGallery';
210217

211218
export * from './Caption';

packages/react-components/src/components/utils.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,3 +195,23 @@ export const customIconName: Partial<{ [key in ErrorType]: string }> = {
195195
export const isValidString = (string: string | undefined): string is string => {
196196
return !!string && string.length > 0;
197197
};
198+
199+
/**
200+
* Chunk an array into rows of a given size.
201+
* @private
202+
*/
203+
export function chunk<T>(options: T[], itemsPerRow: number): T[][] {
204+
const rows: T[][] = [];
205+
let currentRow: T[] = [];
206+
for (const option of options) {
207+
currentRow.push(option);
208+
if (currentRow.length === itemsPerRow) {
209+
rows.push(currentRow);
210+
currentRow = [];
211+
}
212+
}
213+
if (currentRow.length > 0) {
214+
rows.push(currentRow);
215+
}
216+
return rows;
217+
}

packages/storybook/stories/INTERNAL/VideoEffects/VideoEffects.stories.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ import React from 'react';
1414
import { SingleLineBetaBanner } from '../../BetaBanners/SingleLineBetaBanner';
1515
import { COMPONENT_FOLDER_PREFIX } from '../../constants';
1616
import { SelectableVideoEffects } from './snippets/SelectableVideoEffects.snippet';
17+
import { VideoBackgroundEffectsPicker } from './snippets/VideoBackgroundEffectsPicker.snippet';
1718

1819
const SelectableVideoEffectsExample = require('!!raw-loader!./snippets/SelectableVideoEffects.snippet.tsx').default;
20+
const VideoBackgroundEffectsPickerExample =
21+
require('!!raw-loader!./snippets/VideoBackgroundEffectsPicker.snippet.tsx').default;
1922

2023
registerIcons({
2124
icons: {
@@ -32,18 +35,23 @@ const getDocs: () => JSX.Element = () => {
3235
<Title>Video Background Effects</Title>
3336
<SingleLineBetaBanner />
3437
<Description>Components to allow a user to select video background effects.</Description>
35-
<Heading>Video Effects Items</Heading>
38+
<Heading>Video Background Effects Items</Heading>
3639
<Description>A selection of premade video effects items.</Description>
3740
<Canvas mdxSource={SelectableVideoEffectsExample}>
3841
<SelectableVideoEffects />
3942
</Canvas>
43+
<Heading>Video Background Effects Picker</Heading>
44+
<Description>The picker can be used as a choice group to allow users to select backgrounds</Description>
45+
<Canvas mdxSource={VideoBackgroundEffectsPickerExample}>
46+
<VideoBackgroundEffectsPicker />
47+
</Canvas>
4048
</Stack>
4149
);
4250
};
4351

4452
// This must be the only named export from this module, and must be named to match the storybook path suffix.
4553
// This ensures that storybook hoists the story instead of creating a folder with a single entry.
46-
export const VideoEffects = SelectableVideoEffects.bind({});
54+
export const VideoEffects = VideoBackgroundEffectsPicker.bind({});
4755

4856
export default {
4957
id: `${COMPONENT_FOLDER_PREFIX}-internal-video-effects`,

0 commit comments

Comments
 (0)