@sap-ux/ui-components
Version:
SAP UI Components Library
260 lines • 11.1 kB
JavaScript
import React from 'react';
import ReactDOM from 'react-dom';
import { Toggle } from '@fluentui/react';
import { getMessageInfo, MessageWrapper } from '../../helper/ValidationMessage/index.js';
import { UIIcon } from '../UIIcon/index.js';
import { UiIcons } from '../Icons.js';
export var UIToggleSize;
(function (UIToggleSize) {
UIToggleSize["Standard"] = "Standard";
UIToggleSize["Small"] = "Small";
})(UIToggleSize || (UIToggleSize = {}));
const TOGGLE_SIZE = {
width: 30,
height: 18,
padding: '0 1px',
margin: '0',
label: {
fontSize: 13,
padding: '0px 0px 1px 0px'
},
circle: {
width: 14,
height: 14,
borderWidth: 1
}
};
const ICON_STYLE = { display: 'flex' };
const DISABLED_OPACITY = 0.4;
const COLORS = {
pill: {
unchecked: {
background: 'var(--vscode-editorWidget-background)',
borderColor: 'var(--vscode-editorWidget-border)',
hover: {
background: 'var(--vscode-editorWidget-background)',
borderColor: 'var(--vscode-editorWidget-border)'
}
},
checked: {
background: 'var(--vscode-editorWidget-background)',
borderColor: 'var(--vscode-contrastActiveBorder, var(--vscode-editorWidget-border))',
hover: {
background: 'var(--vscode-editorWidget-background)',
borderColor: 'var(--vscode-contrastActiveBorder, var(--vscode-editorWidget-border))'
}
},
focus: {
outline: '1px solid var(--vscode-focusBorder) !important'
}
},
thumb: {
unchecked: {
background: 'var(--vscode-button-secondaryBackground)',
borderColor: 'var(--vscode-button-secondaryBorder, var(--vscode-button-border, transparent))',
hover: {
borderColor: 'var(--vscode-button-secondaryBorder, var(--vscode-button-border, transparent))',
background: 'var(--vscode-contrastBorder, var(--vscode-button-secondaryHoverBackground))'
}
},
checked: {
background: 'var(--vscode-button-background)',
borderColor: 'var(--vscode-contrastActiveBorder, var(--vscode-button-border, transparent))',
hover: {
borderColor: 'var(--vscode-contrastActiveBorder, var(--vscode-button-border, transparent))',
background: 'var(--vscode-contrastActiveBorder, var(--vscode-button-hoverBackground))'
}
}
}
};
/**
* UIToggle component.
* based on https://developer.microsoft.com/en-us/fluentui#/controls/web/toggle
*
* @exports
* @class UIToggle
* @extends {React.Component<IToggleProps, {}>}
*/
export class UIToggle extends React.Component {
/**
* Initializes component properties.
*
* @param {UIToggleProps} props
*/
constructor(props) {
super(props);
this.toggleRootRef = React.createRef();
this.handleChange = this.handleChange.bind(this);
this.replaceThumbWithIcon = this.replaceThumbWithIcon.bind(this);
}
/**
* Lifecycle method called immediately after a component is mounted.
* Executes initialization logic such as DOM manipulations or fetching data.
*
* @returns {void} This method does not return a value.
*/
componentDidMount() {
this.replaceThumbWithIcon(this.props.checked ?? this.props.defaultChecked);
}
/**
* Determines whether the component should update when the new props are received.
* This method is typically used for performance optimization.
*
* @param {UIToggleProps} nextProps - The next props to be received by the component.
* @returns {boolean} A boolean value indicating whether the component should update.
*/
shouldComponentUpdate(nextProps) {
if (nextProps.checked !== this.props.checked) {
this.replaceThumbWithIcon(nextProps.checked);
}
return true;
}
/**
* Handles the change event triggered by the user interaction.
*
* @param {React.MouseEvent<HTMLElement>} event - The mouse event object associated with the interaction.
* @param {boolean} [checked] - An optional parameter indicating the current state of the interaction.
* @returns {void} This method does not return a value.
*/
handleChange(event, checked) {
this.replaceThumbWithIcon(checked);
this.props.onChange?.(event, checked);
}
/**
* Replaces the thumb element of a toggle switch with an icon based on the toggle's state.
*
* @param {boolean} [checked] Optional. Represents the state of the toggle switch. If not provided, it checks `defaultChecked` prop or defaults to `false`.
* @returns {void} Does not return a value.
*/
replaceThumbWithIcon(checked = false) {
if (this.toggleRootRef.current) {
const thumbElement = this.toggleRootRef.current?.querySelector('.ms-Toggle-thumb');
if (thumbElement) {
ReactDOM.render(React.createElement(UIIcon, { iconName: checked ? UiIcons.SwitchOn : UiIcons.SwitchOff, style: ICON_STYLE }), thumbElement);
}
}
}
/**
* Method returns size of margin bottom for root container depending on current props state.
*
* @param {string} [message] Validation message.
* @returns {number | undefined} Value for margin bottom.
*/
getMarginBottom(message) {
const { inlineLabel } = this.props;
if (message) {
return inlineLabel ? 0 : 4;
}
return undefined;
}
/**
* @returns {JSX.Element}
*/
render() {
const { inlineLabelLeft, labelFlexGrow, inlineLabel } = this.props;
const sizeInfo = TOGGLE_SIZE;
const messageInfo = getMessageInfo(this.props);
const styles = (styleProps) => {
return {
root: {
...(labelFlexGrow && {
flexGrow: 1,
justifyContent: 'space-between'
}),
margin: sizeInfo?.margin,
marginBottom: this.getMarginBottom(messageInfo.message)
},
label: {
color: 'var(--vscode-foreground)',
fontWeight: 'normal',
fontSize: sizeInfo?.label.fontSize,
padding: sizeInfo?.label.padding,
...(inlineLabel && { marginLeft: 10 }),
...(inlineLabelLeft && {
order: 0,
marginLeft: 0,
marginRight: 10
}),
opacity: this.props.disabled ? DISABLED_OPACITY : 'inherit'
},
pill: {
height: sizeInfo?.height,
width: sizeInfo?.width,
padding: sizeInfo?.padding,
borderRadius: 'var(--vscode-cornerRadius-circle, 9999px)',
background: COLORS.pill.checked.background,
borderColor: COLORS.pill.checked.borderColor,
borderStyle: 'solid',
':hover .ms-Toggle-thumb': {
background: COLORS.thumb.checked.hover.background,
borderColor: COLORS.thumb.checked.hover.borderColor
},
':hover': {
background: COLORS.pill.checked.hover.background,
borderColor: COLORS.pill.checked.hover.borderColor
},
':disabled': {
background: COLORS.pill.checked.background,
borderColor: COLORS.pill.checked.borderColor,
opacity: DISABLED_OPACITY
},
...(!styleProps.checked && {
background: COLORS.pill.unchecked.background,
borderColor: COLORS.pill.unchecked.borderColor,
borderStyle: 'solid',
// This is a bug: the best implementation approach is to set hover styles in the "thumb" section.
// However, the hover styles for the unchecked thumb don't work properly
':hover .ms-Toggle-thumb': {
background: COLORS.thumb.unchecked.hover.background,
borderColor: COLORS.thumb.unchecked.hover.borderColor
},
':hover': {
background: COLORS.pill.unchecked.hover.background,
borderColor: COLORS.pill.unchecked.hover.borderColor
},
':disabled': {
background: COLORS.pill.unchecked.background,
borderColor: COLORS.pill.unchecked.borderColor,
opacity: DISABLED_OPACITY
}
}),
selectors: {
':focus::after': {
// Overwrite flunt ui focus borders
border: 'none !important',
outline: COLORS.pill.focus.outline
}
}
},
thumb: {
height: sizeInfo?.circle.height,
width: sizeInfo?.circle.width,
borderWidth: sizeInfo?.circle.borderWidth,
backgroundPosition: 'center',
borderColor: COLORS.thumb.checked.borderColor,
backgroundColor: COLORS.thumb.checked.background,
':hover': {
background: COLORS.thumb.checked.hover.background,
borderColor: COLORS.thumb.checked.hover.borderColor
},
...(!styleProps.checked && {
borderColor: COLORS.thumb.unchecked.borderColor,
backgroundColor: COLORS.thumb.unchecked.background
}),
'svg': {
width: '100%',
height: '100%',
path: {
fill: styleProps.checked
? 'var(--vscode-button-foreground)'
: 'var(--vscode-button-secondaryForeground)'
}
}
}
};
};
const toggleComponent = (React.createElement(Toggle, { ...this.props, styles: styles, ref: this.toggleRootRef, onChange: this.handleChange }));
return messageInfo.message ? (React.createElement(MessageWrapper, { message: messageInfo }, toggleComponent)) : (toggleComponent);
}
}
//# sourceMappingURL=UIToggle.js.map