init
Some checks failed
Close stale issues and PRs / stale (push) Has been cancelled

This commit is contained in:
2025-09-02 14:49:16 +08:00
commit 38ba663466
2885 changed files with 391107 additions and 0 deletions

View File

@@ -0,0 +1,408 @@
import React, { Component, ReactElement, ReactNode } from 'react';
import { WithTranslation } from 'react-i18next';
import { GestureResponderEvent } from 'react-native';
import { IStore } from '../../../app/types';
import { NOTIFY_CLICK_MODE } from '../../../toolbox/types';
import { combineStyles } from '../../styles/functions.any';
import { Styles } from './AbstractToolboxItem';
import ToolboxItem from './ToolboxItem';
export interface IProps extends WithTranslation {
/**
* Function to be called after the click handler has been processed.
*/
afterClick?: Function;
/**
* The button's background color.
*/
backgroundColor?: string;
/**
* The button's key.
*/
buttonKey?: string;
/**
* Whether or not the button is displayed in a context menu.
*/
contextMenu?: boolean;
/**
* An extra class name to be added at the end of the element's class name
* in order to enable custom styling.
*/
customClass?: string;
/**
* Extra styles which will be applied in conjunction with `styles` or
* `toggledStyles` when the button is disabled;.
*/
disabledStyles?: Styles;
/**
* Redux dispatch function.
*/
dispatch: IStore['dispatch'];
/**
* External handler for click action.
*/
handleClick?: Function;
/**
* Whether the button open a menu or not.
*/
isMenuButton?: boolean;
/**
* Notify mode for `toolbarButtonClicked` event -
* whether to only notify or to also prevent button click routine.
*/
notifyMode?: string;
/**
* Whether to show the label or not.
*/
showLabel?: boolean;
/**
* Collection of styles for the button.
*/
styles?: Styles;
/**
* Collection of styles for the button, when in toggled state.
*/
toggledStyles?: Styles;
/**
* From which direction the tooltip should appear, relative to the button.
*/
tooltipPosition?: string;
/**
* Whether this button is visible or not.
*/
visible?: boolean;
}
/**
* Default style for disabled buttons.
*/
export const defaultDisabledButtonStyles = {
iconStyle: {
opacity: 0.5
},
labelStyle: {
opacity: 0.5
},
style: undefined,
underlayColor: undefined
};
/**
* An abstract implementation of a button.
*/
export default class AbstractButton<P extends IProps, S = any> extends Component<P, S> {
static defaultProps = {
afterClick: undefined,
disabledStyles: defaultDisabledButtonStyles,
showLabel: false,
styles: undefined,
toggledStyles: undefined,
tooltipPosition: 'top',
visible: true
};
/**
* The button's background color.
*
* @abstract
*/
backgroundColor?: string;
/**
* A succinct description of what the button does. Used by accessibility
* tools and torture tests.
*
* If `toggledAccessibilityLabel` is defined, this is used only when the
* button is not toggled on.
*
* @abstract
*/
accessibilityLabel: string;
/**
* This is the same as `accessibilityLabel`, replacing it when the button
* is toggled on.
*
* @abstract
*/
toggledAccessibilityLabel: string;
labelProps: Object;
/**
* The icon of this button.
*
* @abstract
*/
icon: Object;
/**
* The text associated with this button. When `showLabel` is set to
* {@code true}, it will be displayed alongside the icon.
*
* @abstract
*/
label: string;
/**
* The label for this button, when toggled.
*/
toggledLabel: string;
/**
* The icon of this button, when toggled.
*
* @abstract
*/
toggledIcon: Object;
/**
* The text to display in the tooltip. Used only on web.
*
* If `toggleTooltip` is defined, this is used only when the button is not
* toggled on.
*
* @abstract
*/
tooltip?: string;
/**
* The text to display in the tooltip when the button is toggled on.
*
* Used only on web.
*
* @abstract
*/
toggledTooltip?: string;
/**
* Initializes a new {@code AbstractButton} instance.
*
* @param {IProps} props - The React {@code Component} props to initialize
* the new {@code AbstractButton} instance with.
*/
constructor(props: P) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onClick = this._onClick.bind(this);
}
/**
* Helper function to be implemented by subclasses, which should be used
* to handle a key being down.
*
* @protected
* @returns {void}
*/
_onKeyDown() {
// To be implemented by subclass.
}
/**
* Helper function to be implemented by subclasses, which should be used
* to handle the button being clicked / pressed.
*
* @protected
* @returns {void}
*/
_handleClick() {
// To be implemented by subclass.
}
/**
* Helper function to be implemented by subclasses, which may return a
* new React Element to be appended at the end of the button.
*
* @protected
* @returns {ReactElement|null}
*/
_getElementAfter(): ReactElement | null {
return null;
}
/**
* Gets the current icon, taking the toggled state into account. If no
* toggled icon is provided, the regular icon will also be used in the
* toggled state.
*
* @private
* @returns {string}
*/
_getIcon() {
return (
this._isToggled() ? this.toggledIcon : this.icon
) || this.icon;
}
/**
* Gets the current label, taking the toggled state into account. If no
* toggled label is provided, the regular label will also be used in the
* toggled state.
*
* @private
* @returns {string}
*/
_getLabel() {
return (this._isToggled() ? this.toggledLabel : this.label)
|| this.label;
}
/**
* Gets the current accessibility label, taking the toggled state into
* account. If no toggled label is provided, the regular accessibility label
* will also be used in the toggled state.
*
* The accessibility label is not visible in the UI, it is meant to be
* used by assistive technologies, mainly screen readers.
*
* @private
* @returns {string}
*/
_getAccessibilityLabel() {
return (this._isToggled()
? this.toggledAccessibilityLabel
: this.accessibilityLabel
) || this.accessibilityLabel;
}
/**
* Gets the current styles, taking the toggled state into account. If no
* toggled styles are provided, the regular styles will also be used in the
* toggled state.
*
* @private
* @returns {?Styles}
*/
_getStyles(): Styles | undefined {
const { disabledStyles, styles, toggledStyles } = this.props;
const buttonStyles
= (this._isToggled() ? toggledStyles : styles) || styles;
if (this._isDisabled() && buttonStyles && disabledStyles) {
return {
iconStyle: combineStyles(
buttonStyles.iconStyle ?? {}, disabledStyles.iconStyle ?? {}),
labelStyle: combineStyles(
buttonStyles.labelStyle ?? {}, disabledStyles.labelStyle ?? {}),
style: combineStyles(
buttonStyles.style ?? {}, disabledStyles.style ?? {}),
underlayColor:
disabledStyles.underlayColor || buttonStyles.underlayColor
};
}
return buttonStyles;
}
/**
* Get the tooltip to display when hovering over the button.
*
* @private
* @returns {string}
*/
_getTooltip() {
return (this._isToggled() ? this.toggledTooltip : this.tooltip)
|| this.tooltip
|| '';
}
/**
* Helper function to be implemented by subclasses, which must return a
* boolean value indicating if this button is disabled or not.
*
* @protected
* @returns {boolean}
*/
_isDisabled() {
return false;
}
/**
* Helper function to be implemented by subclasses, which must return a
* {@code boolean} value indicating if this button is toggled or not or
* undefined if the button is not toggleable.
*
* @protected
* @returns {?boolean}
*/
_isToggled(): boolean | undefined {
return undefined;
}
/**
* Handles clicking / pressing the button.
*
* @param {Object} e - Event.
* @private
* @returns {void}
*/
_onClick(e?: React.MouseEvent | GestureResponderEvent) {
const { afterClick, buttonKey, handleClick, notifyMode } = this.props;
if (typeof APP !== 'undefined' && notifyMode) {
APP.API.notifyToolbarButtonClicked(
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
);
}
if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
if (handleClick) {
handleClick();
}
this._handleClick();
}
afterClick?.(e);
// blur after click to release focus from button to allow PTT.
// @ts-ignore
e?.currentTarget?.blur?.();
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {React$Node}
*/
override render(): ReactNode {
const props: any = {
...this.props,
accessibilityLabel: this._getAccessibilityLabel(),
elementAfter: this._getElementAfter(),
icon: this._getIcon(),
label: this._getLabel(),
labelProps: this.labelProps,
styles: this._getStyles(),
toggled: this._isToggled(),
tooltip: this._getTooltip()
};
return (
<ToolboxItem
disabled = { this._isDisabled() }
onClick = { this._onClick }
onKeyDown = { this._onKeyDown }
{ ...props } />
);
}
}

View File

@@ -0,0 +1,32 @@
import { IconHangup } from '../../icons/svg';
import AbstractButton, { IProps } from './AbstractButton';
/**
* An abstract implementation of a button for disconnecting a conference.
*/
export default class AbstractHangupButton<P extends IProps, S=any>
extends AbstractButton<P, S> {
override icon = IconHangup;
/**
* Handles clicking / pressing the button, and disconnects the conference.
*
* @protected
* @returns {void}
*/
override _handleClick() {
this._doHangup();
}
/**
* Helper function to perform the actual hangup action.
*
* @protected
* @returns {void}
*/
_doHangup() {
// To be implemented by subclass.
}
}

View File

@@ -0,0 +1,229 @@
import React, { Component, ReactElement, ReactNode } from 'react';
import { WithTranslation } from 'react-i18next';
import { GestureResponderEvent } from 'react-native';
import type { StyleType } from '../../styles/functions.any';
import { TOOLTIP_POSITION } from '../../ui/constants.any';
export type Styles = {
/**
* Style for the item's icon.
*/
iconStyle?: StyleType;
/**
* Style for the item's label.
*/
labelStyle?: StyleType;
/**
* Style for the item itself.
*/
style?: StyleType;
/**
* Color for the item underlay (shows when clicked).
*/
underlayColor?: string;
};
export interface IProps extends WithTranslation {
/**
* A succinct description of what the item does. Used by accessibility
* tools and torture tests.
*/
accessibilityLabel: string;
/**
* An extra class name to be added at the end of the element's class name
* in order to enable custom styling.
*/
customClass?: string;
/**
* Whether this item is disabled or not. When disabled, clicking an the item
* has no effect, and it may reflect on its style.
*/
disabled: boolean;
/**
* A React Element to display at the end of {@code ToolboxItem}.
*/
elementAfter?: ReactNode;
/**
* The icon to render for this {@code ToolboxItem}.
*/
icon: Function;
/**
* The text associated with this item. When `showLabel` is set to
* {@code true}, it will be displayed alongside the icon.
*/
label: string;
labelProps: any;
/**
* On click handler.
*/
onClick: (e?: React.MouseEvent<HTMLElement> | GestureResponderEvent) => void;
/**
* Whether to show the label or not.
*/
showLabel: boolean;
/**
* Collection of styles for the item. Used only on native.
*/
styles?: Styles;
/**
* True if the item is toggled, false otherwise.
*/
toggled?: boolean;
/**
* The text to display in the tooltip. Used only on web.
*/
tooltip?: string;
/**
* From which direction the tooltip should appear, relative to the
* item. Used only on web.
*/
tooltipPosition: TOOLTIP_POSITION;
/**
* Whether this item is visible or not.
*/
visible: boolean;
}
/**
* Abstract (base) class for an item in {@link Toolbox}. The item can be located
* anywhere in the {@link Toolbox}, it will morph its shape to accommodate it.
*
* @abstract
*/
export default class AbstractToolboxItem<P extends IProps> extends Component<P> {
/**
* Default values for {@code AbstractToolboxItem} component's properties.
*
* @static
*/
static defaultProps = {
disabled: false,
label: '',
showLabel: false,
t: undefined,
tooltip: '',
tooltipPosition: 'top',
visible: true
};
/**
* Initializes a new {@code AbstractToolboxItem} instance.
*
* @param {Object} props - The React {@code Component} props to initialize
* the new {@code AbstractToolboxItem} instance with.
*/
constructor(props: P) {
super(props);
// Bind event handlers so they are only bound once per instance.
this._onClick = this._onClick.bind(this);
}
/**
* Helper property to get the item label. If a translation function was
* provided then it will be translated using it.
*
* @protected
* @returns {?string}
*/
get label(): string | undefined {
return this._maybeTranslateAttribute(this.props.label, this.props.labelProps);
}
/**
* Helper property to get the item tooltip. If a translation function was
* provided then it will be translated using it.
*
* @protected
* @returns {?string}
*/
get tooltip(): string | undefined {
return this._maybeTranslateAttribute(this.props.tooltip ?? '');
}
/**
* Helper property to get the item accessibilityLabel. If a translation
* function was provided then it will be translated using it.
*
* @protected
* @returns {?string}
*/
get accessibilityLabel(): string {
return this._maybeTranslateAttribute(this.props.accessibilityLabel);
}
/**
* Utility function to translate the given string, if a translation
* function is available.
*
* @param {string} text - What needs translating.
* @param {string} textProps - Additional properties for translation text.
* @private
* @returns {string}
*/
_maybeTranslateAttribute(text: string, textProps?: any) {
const { t } = this.props;
if (textProps) {
return typeof t === 'function' ? t(text, textProps) : `${text} ${textProps}`;
}
return typeof t === 'function' ? t(text) : text;
}
/**
* Handles clicking/pressing this {@code AbstractToolboxItem} by
* forwarding the event to the {@code onClick} prop of this instance if any.
*
* @protected
* @returns {void}
*/
_onClick(...args: any) {
const { disabled, onClick } = this.props;
disabled || onClick?.(...args);
}
/**
* Renders this {@code AbstractToolboxItem} (if it is {@code visible}). To
* be implemented/overridden by extenders. The default implementation of
* {@code AbstractToolboxItem} does nothing.
*
* @protected
* @returns {ReactElement}
*/
_renderItem(): ReactElement | null {
// To be implemented by a subclass.
return null;
}
/**
* Implements React's {@link Component#render()}.
*
* @inheritdoc
* @returns {ReactElement}
*/
override render() {
return this.props.visible ? this._renderItem() : null;
}
}

View File

@@ -0,0 +1,60 @@
import { IconMic, IconMicSlash } from '../../icons/svg';
import AbstractButton, { IProps } from './AbstractButton';
/**
* An abstract implementation of a button for toggling audio mute.
*/
export default class BaseAudioMuteButton<P extends IProps, S=any>
extends AbstractButton<P, S> {
override icon = IconMic;
override toggledIcon = IconMicSlash;
/**
* Handles clicking / pressing the button, and toggles the audio mute state
* accordingly.
*
* @override
* @protected
* @returns {void}
*/
override _handleClick() {
this._setAudioMuted(!this._isAudioMuted());
}
/**
* Helper function to be implemented by subclasses, which must return a
* boolean value indicating if audio is muted or not.
*
* @protected
* @returns {boolean}
*/
_isAudioMuted() {
// To be implemented by subclass.
return false;
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
override _isToggled() {
return this._isAudioMuted();
}
/**
* Helper function to perform the actual setting of the audio mute / unmute
* action.
*
* @param {boolean} _audioMuted - Whether audio should be muted or not.
* @protected
* @returns {void}
*/
_setAudioMuted(_audioMuted: boolean) {
// To be implemented by subclass.
}
}

View File

@@ -0,0 +1,59 @@
import { IconVideo, IconVideoOff } from '../../icons/svg';
import AbstractButton, { IProps } from './AbstractButton';
/**
* An abstract implementation of a button for toggling video mute.
*/
export default class BaseVideoMuteButton<P extends IProps, S=any>
extends AbstractButton<P, S> {
override icon = IconVideo;
override toggledIcon = IconVideoOff;
/**
* Handles clicking / pressing the button, and toggles the video mute state
* accordingly.
*
* @protected
* @returns {void}
*/
override _handleClick() {
this._setVideoMuted(!this._isVideoMuted());
}
/**
* Indicates whether this button is in toggled state or not.
*
* @override
* @protected
* @returns {boolean}
*/
override _isToggled() {
return this._isVideoMuted();
}
/**
* Helper function to be implemented by subclasses, which must return a
* {@code boolean} value indicating if video is muted or not.
*
* @protected
* @returns {boolean}
*/
_isVideoMuted() {
// To be implemented by subclass.
return false;
}
/**
* Helper function to perform the actual setting of the video mute / unmute
* action.
*
* @param {boolean} _videoMuted - Whether video should be muted or not.
* @protected
* @returns {void}
*/
_setVideoMuted(_videoMuted: boolean) {
// To be implemented by subclass.
}
}

View File

@@ -0,0 +1,83 @@
import React from 'react';
import { Text, TextStyle, TouchableHighlight, View, ViewStyle } from 'react-native';
import Icon from '../../icons/components/Icon';
import AbstractToolboxItem, { IProps } from './AbstractToolboxItem';
/**
* Native implementation of {@code AbstractToolboxItem}.
*/
export default class ToolboxItem extends AbstractToolboxItem<IProps> {
/**
* Renders the {@code Icon} part of this {@code ToolboxItem}.
*
* @private
* @returns {ReactElement}
*/
_renderIcon() {
const { styles } = this.props;
return (
<Icon
src = { this.props.icon }
style = { styles?.iconStyle } />
);
}
/**
* Renders this {@code ToolboxItem}. Invoked by {@link AbstractToolboxItem}.
*
* @override
* @protected
* @returns {ReactElement}
*/
_renderItem() {
const {
disabled,
elementAfter,
onClick,
showLabel,
styles,
toggled
} = this.props;
let children = this._renderIcon();
// XXX When using a wrapper View, apply the style to it instead of
// applying it to the TouchableHighlight.
let style = styles?.style;
if (showLabel) {
// XXX TouchableHighlight requires 1 child. If there's a need to
// show both the icon and the label, then these two need to be
// wrapped in a View.
children = (
<View style = { style as ViewStyle }>
{ children }
<Text style = { styles?.labelStyle as TextStyle }>
{ this.label }
</Text>
{ elementAfter }
</View>
);
// XXX As stated earlier, the style was applied to the wrapper View
// (above).
style = undefined;
}
return (
<TouchableHighlight
accessibilityLabel = { this.accessibilityLabel }
accessibilityRole = { 'button' }
accessibilityState = {{ 'selected': Boolean(toggled) }}
disabled = { disabled }
onPress = { onClick }
style = { style as ViewStyle }
underlayColor = { styles?.underlayColor } >
{ children }
</TouchableHighlight>
);
}
}

View File

@@ -0,0 +1,161 @@
import React, { Fragment } from 'react';
import Icon from '../../icons/components/Icon';
import Tooltip from '../../tooltip/components/Tooltip';
import ContextMenuItem from '../../ui/components/web/ContextMenuItem';
import {
default as AbstractToolboxItem,
type IProps as AbstractToolboxItemProps
} from './AbstractToolboxItem';
interface IProps extends AbstractToolboxItemProps {
/**
* The button's background color.
*/
backgroundColor?: string;
/**
* Whether or not the item is displayed in a context menu.
*/
contextMenu?: boolean;
/**
* Whether the button open a menu or not.
*/
isMenuButton?: boolean;
/**
* On key down handler.
*/
onKeyDown: (e?: React.KeyboardEvent) => void;
}
/**
* Web implementation of {@code AbstractToolboxItem}.
*/
export default class ToolboxItem extends AbstractToolboxItem<IProps> {
/**
* Initializes a new {@code ToolboxItem} instance.
*
* @inheritdoc
*/
constructor(props: IProps) {
super(props);
this._onKeyPress = this._onKeyPress.bind(this);
}
/**
* Handles 'Enter' and Space key on the button to trigger onClick for accessibility.
*
* @param {Object} event - The key event.
* @private
* @returns {void}
*/
_onKeyPress(event?: React.KeyboardEvent) {
if (event?.key === 'Enter') {
event.preventDefault();
this.props.onClick();
}
}
/**
* Handles rendering of the actual item. If the label is being shown, which
* is controlled with the `showLabel` prop, the item is rendered for its
* display in an overflow menu, otherwise it will only have an icon, which
* can be displayed on any toolbar.
*
* @protected
* @returns {ReactElement}
*/
override _renderItem() {
const {
backgroundColor,
contextMenu,
isMenuButton,
disabled,
elementAfter,
icon,
onClick,
onKeyDown,
showLabel,
tooltipPosition,
toggled
} = this.props;
const className = showLabel ? 'overflow-menu-item' : 'toolbox-button';
const buttonAttribute = isMenuButton ? 'aria-expanded' : 'aria-pressed';
const props = {
[buttonAttribute]: toggled,
'aria-disabled': disabled,
'aria-label': this.accessibilityLabel,
className: className + (disabled ? ' disabled' : ''),
onClick: disabled ? undefined : onClick,
onKeyDown: disabled ? undefined : onKeyDown,
onKeyPress: this._onKeyPress,
tabIndex: 0,
role: 'button'
};
const elementType = showLabel ? 'li' : 'div';
const useTooltip = this.tooltip && this.tooltip.length > 0;
if (contextMenu) {
return (
<ContextMenuItem
accessibilityLabel = { this.accessibilityLabel }
backgroundColor = { backgroundColor }
disabled = { disabled }
icon = { icon }
onClick = { onClick }
onKeyDown = { onKeyDown }
onKeyPress = { this._onKeyPress }
text = { this.label } />
);
}
let children = (
<Fragment>
{ this._renderIcon() }
{ showLabel && <span>
{ this.label }
</span> }
{ elementAfter }
</Fragment>
);
if (useTooltip) {
children = (
<Tooltip
content = { this.tooltip ?? '' }
position = { tooltipPosition }>
{ children }
</Tooltip>
);
}
return React.createElement(elementType, props, children);
}
/**
* Helper function to render the item's icon.
*
* @private
* @returns {ReactElement}
*/
_renderIcon() {
const { backgroundColor, customClass, disabled, icon, showLabel, toggled } = this.props;
const iconComponent = (<Icon
size = { showLabel ? undefined : 24 }
src = { icon } />);
const elementType = showLabel ? 'span' : 'div';
const className = `${showLabel ? 'overflow-menu-item-icon' : 'toolbox-icon'} ${
toggled ? 'toggled' : ''} ${disabled ? 'disabled' : ''} ${customClass ?? ''}`;
const style = backgroundColor && !showLabel ? { backgroundColor } : {};
return React.createElement(elementType, {
className,
style
}, iconComponent);
}
}

View File

@@ -0,0 +1,162 @@
import React from 'react';
import { NOTIFY_CLICK_MODE } from '../../../../toolbox/types';
import Icon from '../../../icons/components/Icon';
import Tooltip from '../../../tooltip/components/Tooltip';
interface IProps {
/**
* The id of the element this button icon controls.
*/
ariaControls?: string;
/**
* Whether the element popup is expanded.
*/
ariaExpanded?: boolean;
/**
* Whether the element has a popup.
*/
ariaHasPopup?: boolean;
/**
* Aria label for the Icon.
*/
ariaLabel?: string;
/**
* The button's key.
*/
buttonKey?: string;
/**
* The decorated component (ToolboxButton).
*/
children: React.ReactNode;
/**
* Icon of the button.
*/
icon: Function;
/**
* Flag used for disabling the small icon.
*/
iconDisabled: boolean;
/**
* The ID of the icon button.
*/
iconId: string;
/**
* The tooltip used for the icon.
*/
iconTooltip: string;
/**
* Notify mode for `toolbarButtonClicked` event -
* whether to only notify or to also prevent button click routine.
*/
notifyMode?: string;
/**
* Click handler for the small icon.
*/
onIconClick: Function;
/**
* Keydown handler for icon.
*/
onIconKeyDown?: Function;
/**
* Additional styles.
*/
styles?: Object;
}
/**
* Displays the `ToolboxButtonWithIcon` component.
*
* @param {Object} props - Component's props.
* @returns {ReactElement}
*/
export default function ToolboxButtonWithIcon(props: IProps) {
const {
children,
icon,
iconDisabled,
iconTooltip,
buttonKey,
notifyMode,
onIconClick,
onIconKeyDown,
styles,
ariaLabel,
ariaHasPopup,
ariaControls,
ariaExpanded,
iconId
} = props;
const iconProps: {
ariaControls?: string;
ariaExpanded?: boolean;
containerId?: string;
onClick?: (e?: React.MouseEvent) => void;
onKeyDown?: Function;
role?: string;
tabIndex?: number;
} = {};
let className = '';
if (iconDisabled) {
className
= 'settings-button-small-icon settings-button-small-icon--disabled';
} else {
className = 'settings-button-small-icon';
iconProps.onClick = (e?: React.MouseEvent) => {
if (typeof APP !== 'undefined' && notifyMode) {
APP.API.notifyToolbarButtonClicked(
buttonKey, notifyMode === NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY
);
}
if (notifyMode !== NOTIFY_CLICK_MODE.PREVENT_AND_NOTIFY) {
onIconClick(e);
}
};
iconProps.onKeyDown = onIconKeyDown;
iconProps.role = 'button';
iconProps.tabIndex = 0;
iconProps.ariaControls = ariaControls;
iconProps.ariaExpanded = ariaExpanded;
iconProps.containerId = iconId;
}
return (
<div
className = 'settings-button-container'
style = { styles }>
{children}
<div>
<Tooltip
containerClassName = { className }
content = { iconTooltip }
position = 'top'>
<Icon
{ ...iconProps }
ariaHasPopup = { ariaHasPopup }
ariaLabel = { ariaLabel }
size = { 16 }
src = { icon } />
</Tooltip>
</div>
</div>
);
}

View File

@@ -0,0 +1,122 @@
import React from 'react';
import Icon from '../../../icons/components/Icon';
import Popover from '../../../popover/components/Popover.web';
interface IProps {
/**
* Aria label for the Icon.
*/
ariaLabel?: string;
/**
* The decorated component (ToolboxButton).
*/
children: React.ReactNode;
/**
* Icon of the button.
*/
icon?: Function;
/**
* Flag used for disabling the small icon.
*/
iconDisabled?: boolean;
/**
* Popover close callback.
*/
onPopoverClose: Function;
/**
* Popover open callback.
*/
onPopoverOpen: Function;
/**
* The content that will be displayed inside the popover.
*/
popoverContent: React.ReactNode;
/**
* Additional styles.
*/
styles?: Object;
/**
* Whether the trigger for open/ close should be click or hover.
*/
trigger?: 'hover' | 'click';
/**
* Whether or not the popover is visible.
*/
visible: boolean;
}
/**
* Displays the `ToolboxButtonWithIcon` component.
*
* @param {Object} props - Component's props.
* @returns {ReactElement}
*/
export default function ToolboxButtonWithPopup(props: IProps) {
const {
ariaLabel,
children,
icon,
iconDisabled,
onPopoverClose,
onPopoverOpen,
popoverContent,
styles,
trigger,
visible
} = props;
if (!icon) {
return (
<div
className = 'settings-button-container'
style = { styles }>
<Popover
content = { popoverContent }
headingLabel = { ariaLabel }
onPopoverClose = { onPopoverClose }
onPopoverOpen = { onPopoverOpen }
position = 'top'
trigger = { trigger }
visible = { visible }>
{children}
</Popover>
</div>
);
}
return (
<div
className = 'settings-button-container'
style = { styles }>
{children}
<div className = 'settings-button-small-icon-container'>
<Popover
content = { popoverContent }
headingLabel = { ariaLabel }
onPopoverClose = { onPopoverClose }
onPopoverOpen = { onPopoverOpen }
position = 'top'
visible = { visible }>
<Icon
alt = { ariaLabel }
className = { `settings-button-small-icon ${iconDisabled
? 'settings-button-small-icon--disabled'
: ''}` }
size = { 16 }
src = { icon } />
</Popover>
</div>
</div>
);
}