import * as _ from 'lodash'; import * as React from 'react'; import { Button, Text, Toolbar, Status, ShorthandValue, ComponentStyleFunctionParam, ThemeInput, ToolbarProps, ToolbarItemProps, ToolbarCustomItemProps, ToolbarDividerProps, StatusProps, pxToRem, Provider, mergeThemes, Tooltip, tooltipAsLabelBehavior, teamsDarkTheme, } from '@fluentui/react-northstar'; import { CallControlCloseTrayIcon, CallControlPresentNewIcon, CallControlStopPresentingNewIcon, CallEndIcon, CallVideoIcon, CallVideoOffIcon, ChatIcon, ChevronDownIcon, MicIcon, MicOffIcon, MoreIcon, ParticipantAddIcon, } from '@fluentui/react-icons-northstar'; type CustomStatusVariables = { isRecordingIndicator?: boolean; recordingIndicatorBorderColor?: string; recordingIndicatorBorderStyle?: string; recordingIndicatorBorderWidth?: string; }; type CustomToolbarVariables = { isCt?: boolean; isCtItemDanger?: boolean; isCtItemPrimary?: boolean; isCtItemIconNoFill?: boolean; isCtItemIndicator?: boolean; isCtItemWithNotification?: boolean; ctBorderRadius: string; ctBorderStyle: string; ctBorderWidth: string; ctHeight: string; ctItemBackground: string; ctItemBackgroundHover: string; ctItemBorderColorFocus: string; ctItemColor: string; ctItemColorFocus: string; ctItemColorHover: string; ctItemActiveColor: string; ctItemActiveBackground: string; ctItemActiveBackgroundOverlay: string; ctItemDangerBackground: string; ctItemDangerColorHover: string; ctItemDangerBackgroundHover: string; ctItemIndicatorPadding: string; ctItemNotificationBackgroundColor: string; ctItemNotificationSize: string; ctItemPrimaryBackground: string; ctItemPrimaryBackgroundHover: string; ctItemPrimaryColorHover: string; }; const toolbarVariables = (siteVars): CustomToolbarVariables => ({ ctBorderRadius: '4px', ctBorderStyle: 'solid', ctBorderWidth: '2px', ctHeight: '4rem', ctItemBackground: siteVars.colorScheme.default.background1, ctItemBackgroundHover: siteVars.colorScheme.brand.backgroundHover1, ctItemBorderColorFocus: siteVars.colorScheme.default.borderFocus, ctItemColor: siteVars.colorScheme.default.foreground, ctItemColorFocus: siteVars.colorScheme.default.foregroundFocus, ctItemColorHover: siteVars.colorScheme.default.foregroundHover, ctItemActiveBackground: siteVars.colorScheme.default.backgroundActive1, // FIXME: use variables for colors! ctItemActiveBackgroundOverlay: 'linear-gradient(90deg,rgba(60,62,93,.6),rgba(60,62,93,0) 33%),linear-gradient(135deg,rgba(60,62,93,.6) 33%,rgba(60,62,93,0) 70%),linear-gradient(180deg,rgba(60,62,93,.6) 70%,rgba(60,62,93,0) 94%),linear-gradient(225deg,rgba(60,62,93,.6) 33%,rgba(60,62,93,0) 73%),linear-gradient(270deg,rgba(60,62,93,.6),rgba(60,62,93,0) 33%),linear-gradient(0deg,rgba(98,100,167,.75) 6%,rgba(98,100,167,0) 70%)', ctItemActiveColor: siteVars.colorScheme.default.foregroundActive1, ctItemDangerBackground: siteVars.colorScheme.red.background2, ctItemDangerBackgroundHover: siteVars.colorScheme.red.backgroundHover, ctItemDangerColorHover: siteVars.colorScheme.red.foregroundHover, ctItemIndicatorPadding: pxToRem(8), ctItemNotificationBackgroundColor: siteVars.colors.red[400], ctItemNotificationSize: pxToRem(8), ctItemPrimaryBackground: siteVars.colorScheme.default.background3, ctItemPrimaryBackgroundHover: siteVars.colorScheme.brand.backgroundHover1, ctItemPrimaryColorHover: siteVars.colorScheme.brand.foregroundHover1, }); const darkThemeOverrides: ThemeInput = { componentVariables: { Status: (siteVars): CustomStatusVariables => ({ recordingIndicatorBorderColor: siteVars.colors.white, recordingIndicatorBorderStyle: 'solid', recordingIndicatorBorderWidth: '2px', }), Toolbar: toolbarVariables, ToolbarCustomItem: toolbarVariables, ToolbarDivider: toolbarVariables, ToolbarItem: toolbarVariables, }, componentStyles: { Status: { root: ({ variables: v }: ComponentStyleFunctionParam) => ({ ...(v.isRecordingIndicator && { boxSizing: 'content-box', borderColor: v.recordingIndicatorBorderColor, borderStyle: v.recordingIndicatorBorderStyle, borderWidth: v.recordingIndicatorBorderWidth, }), }), }, Toolbar: { root: ({ variables: v }: ComponentStyleFunctionParam) => ({ ...(v.isCt && { borderRadius: v.ctBorderRadius, height: v.ctHeight, overflow: 'hidden', }), }), }, ToolbarCustomItem: { root: ({ props: p, variables: v, }: ComponentStyleFunctionParam) => ({ ...(v.isCt && { background: v.ctItemBackground, borderStyle: v.ctBorderStyle, borderWidth: v.ctBorderWidth, height: v.ctHeight, ...(v.isCtItemPrimary && { background: v.ctItemPrimaryBackground }), ...(v.isCtItemIndicator && { padding: v.ctItemIndicatorPadding }), ':focus-visible': { background: v.ctItemBackgroundHover, borderColor: v.ctItemBorderColorFocus, color: v.ctItemColorFocus, }, }), }), }, ToolbarItem: { root: ({ props: p, variables: v }: ComponentStyleFunctionParam) => { return { ...(v.isCt && { alignItems: 'center', display: 'flex', justifyContent: 'center', position: 'relative', background: v.ctItemBackground, borderStyle: v.ctBorderStyle, borderWidth: v.ctBorderWidth, borderRadius: 0, height: v.ctHeight, minWidth: v.ctHeight, color: v.ctItemColor, ...(p.active && !v.isCtItemPrimary && { // active intentionally before primary and danger, only affects regular items color: v.ctItemActiveColor, background: v.ctItemActiveBackground, '::before': { content: `''`, position: 'absolute', top: `-${v.ctBorderWidth}`, left: `-${v.ctBorderWidth}`, bottom: `-${v.ctBorderWidth}`, right: `-${v.ctBorderWidth}`, background: v.ctItemActiveBackgroundOverlay, ':focus-visible': { borderStyle: v.ctBorderStyle, borderWidth: v.ctBorderWidth, borderColor: v.ctItemBorderColorFocus, }, }, }), ...(v.isCtItemDanger && { background: v.ctItemDangerBackground, }), ...(v.isCtItemPrimary && { background: v.ctItemPrimaryBackground, }), ':hover': { color: v.ctItemColorHover, background: v.ctItemBackgroundHover, ...(v.isCtItemDanger && { color: v.ctItemDangerColorHover, background: v.ctItemDangerBackgroundHover, }), ...(v.isCtItemPrimary && { color: v.ctItemPrimaryColorHover, background: v.ctItemPrimaryBackgroundHover, }), }, ...(v.isCtItemWithNotification && { '::after': { content: `''`, position: 'absolute', width: v.ctItemNotificationSize, height: v.ctItemNotificationSize, borderRadius: '50%', background: v.ctItemNotificationBackgroundColor, transform: 'translateX(100%) translateY(-100%)', }, }), ':focus-visible': { background: v.ctItemBackgroundHover, borderColor: v.ctItemBorderColorFocus, color: v.ctItemColorFocus, ...(v.isCtItemDanger && { color: v.ctItemDangerColorHover, background: v.ctItemDangerBackgroundHover, }), ...(v.isCtItemPrimary && { color: v.ctItemPrimaryColorHover, background: v.ctItemPrimaryBackgroundHover, }), }, }), ...(v.isCtItemIconNoFill && { '& .ui-icon__filled': { display: 'none', }, '& .ui-icon__outline': { display: 'block', }, '&:hover .ui-icon__filled': { display: 'none', }, '&:hover .ui-icon__outline': { display: 'block', }, }), }; }, }, ToolbarDivider: { root: ({ props: p, variables: v }: ComponentStyleFunctionParam) => ({ ...(v.isCt && { margin: 0, }), }), }, }, }; const tooltips = { videoOn: 'Turn camera off', videoOff: 'Turn camera on', micOn: 'Mute', micOff: 'Unmute', share: 'Share', shareStop: 'Stop sharing', endCall: 'Hang up', moreActions: 'More actions', chat: 'Show conversation', addParticipants: 'Add participants', pptNext: 'Navigate forward', pptPrevious: 'Navigate back', }; interface CustomToolbarProps { layout?: 'standard' | 'desktop-share' | 'powerpoint-presenter'; isRecording?: boolean; cameraActive?: boolean; onCameraChange?: (state: boolean) => void; micActive?: boolean; onMicChange?: (state: boolean) => void; screenShareActive?: boolean; onScreenShareChange?: (state: boolean) => void; sidebarSelected: false | 'chat' | 'participant-add'; onSidebarChange?: (state: false | 'chat' | 'participant-add') => void; chatHasNotification?: boolean; pptSlide?: string; onPptPrevClick?: () => void; onPptNextClick?: () => void; onEndCallClick?: () => void; } type CustomToolbarLayout = (props: CustomToolbarProps) => ToolbarProps['items']; const commonLayout: CustomToolbarLayout = props => [ props.isRecording && { key: 'recording', kind: 'custom' as const, focusable: true, content: , variables: { isCtItemPrimary: true, isCtItemIndicator: true }, }, { key: 'timer-custom', kind: 'custom' as const, focusable: true, content: 10:45, variables: { isCtItemPrimary: true, isCtItemIndicator: true }, }, { key: 'timer-divider', kind: 'divider' as const }, { tooltip: props.cameraActive ? tooltips.videoOn : tooltips.videoOff, active: props.cameraActive, icon: props.cameraActive ? : , key: 'camera', onClick: () => _.invoke(props, 'onCameraChange', !props.cameraActive), variables: { isCtItemPrimary: true }, }, { tooltip: props.micActive ? tooltips.micOn : tooltips.micOff, active: props.micActive, icon: props.micActive ? : , key: 'mic', onClick: () => _.invoke(props, 'onMicChange', !props.micActive), variables: { isCtItemPrimary: true }, }, { tooltip: props.screenShareActive ? tooltips.shareStop : tooltips.share, active: props.screenShareActive, icon: props.screenShareActive ? ( ) : ( ), key: 'screen-share', onClick: () => _.invoke(props, 'onScreenShareChange', !props.screenShareActive), variables: { isCtItemPrimary: true }, }, { tooltip: tooltips.moreActions, key: 'more', icon: , onClick: () => _.invoke(props, 'onMoreClick'), variables: { isCtItemPrimary: true }, }, ].filter(Boolean); const sidebarButtons: CustomToolbarLayout = props => [ { tooltip: tooltips.chat, active: props.sidebarSelected === 'chat', icon: , key: 'chat', onClick: () => _.invoke(props, 'onSidebarChange', props.sidebarSelected === 'chat' ? false : 'chat'), variables: { isCtItemWithNotification: props.chatHasNotification, isCtItemIconNoFill: true }, }, { tooltip: tooltips.addParticipants, active: props.sidebarSelected === 'participant-add', icon: , key: 'participant-add', onClick: () => _.invoke(props, 'onSidebarChange', props.sidebarSelected === 'participant-add' ? false : 'participant-add'), variables: { isCtItemIconNoFill: true }, }, ]; const layoutItems: ShorthandValue = { endCall: props => ({ tooltip: tooltips.endCall, key: 'end-call', icon: , onClick: () => _.invoke(props, 'onEndCallClick'), variables: { isCtItemDanger: true }, }), }; const layouts: Record = { standard: props => [...commonLayout(props), ...sidebarButtons(props), layoutItems.endCall(props)], 'desktop-share': props => [ ...commonLayout(props), ...sidebarButtons(props), { key: 'divider-sidebar', kind: 'divider' }, { key: 'stop-sharing', kind: 'custom', content: