PromucFlow_constructor/app/client/src/components/propertyControls/IconSelectControl.tsx
Paul Li 4f6a62fa53
Icon Button Widget (#5723)
Introducing the **Floating action button / Icon button**. Now you can use this to show small actions as icons. We have added a bunch of styling customisation to this as well. You can bind an onClick action to this and easily trigger an even. The component also automatically resizes within the widget as you scale it while maintaining the right aspect ratio.
2021-08-05 16:46:26 +05:30

206 lines
5.2 KiB
TypeScript

import * as React from "react";
import styled, { createGlobalStyle } from "styled-components";
import { Alignment, Button, Classes, Menu, MenuItem } from "@blueprintjs/core";
import { IconName, IconNames } from "@blueprintjs/icons";
import { ItemListRenderer, ItemRenderer, Select } from "@blueprintjs/select";
import BaseControl, { ControlProps } from "./BaseControl";
import TooltipComponent from "components/ads/Tooltip";
const IconSelectContainerStyles = createGlobalStyle<{
targetWidth: number | undefined;
}>`
.bp3-select-popover {
width: ${({ targetWidth }) => targetWidth}px;
.bp3-input-group {
margin: 5px !important;
}
}
`;
const StyledButton = styled(Button)`
box-shadow: none !important;
border: none !important;
border-radius: 0;
background-color: #ffffff !important;
> span.bp3-icon-caret-down {
color: rgb(169, 167, 167);
}
`;
const StyledMenu = styled(Menu)`
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: minmax(50px, auto);
gap: 8px;
max-height: 170px !important;
padding-left: 5px !important;
padding-right: 5px !important;
&::-webkit-scrollbar {
width: 8px;
background-color: #eeeeee;
}
&::-webkit-scrollbar-thumb {
border-radius: 10px;
-webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
background-color: #939090;
}
`;
const StyledMenuItem = styled(MenuItem)`
flex-direction: column;
align-items: center;
padding: 13px 5px;
&:active,
&:hover,
&.bp3-active {
background-color: #eeeeee !important;
}
> span.bp3-icon {
margin-right: 0;
color: #939090 !important;
}
> div {
width: 100%;
text-align: center;
color: #939090 !important;
}
`;
export interface IconSelectControlProps extends ControlProps {
propertyValue?: IconName;
}
export interface IconSelectControlState {
popoverTargetWidth: number | undefined;
}
const NONE = "(none)";
type IconType = IconName | typeof NONE;
const ICON_NAMES = Object.keys(IconNames).map<IconType>(
(name: string) => IconNames[name as keyof typeof IconNames],
);
ICON_NAMES.push(NONE);
const TypedSelect = Select.ofType<IconType>();
class IconSelectControl extends BaseControl<
IconSelectControlProps,
IconSelectControlState
> {
private iconSelectTargetRef: React.RefObject<HTMLButtonElement>;
private timer?: number;
constructor(props: IconSelectControlProps) {
super(props);
this.iconSelectTargetRef = React.createRef();
this.state = { popoverTargetWidth: 0 };
}
componentDidMount() {
this.timer = setTimeout(() => {
const iconSelectTargetElement = this.iconSelectTargetRef.current;
console.log(
`target width: => `,
iconSelectTargetElement?.getBoundingClientRect().width,
);
this.setState((prevState: IconSelectControlState) => {
return {
...prevState,
popoverTargetWidth: iconSelectTargetElement?.getBoundingClientRect()
.width,
};
});
}, 0);
}
componentWillUnmount() {
if (this.timer) {
clearTimeout(this.timer);
}
}
public render() {
const { propertyValue: iconName } = this.props;
const { popoverTargetWidth } = this.state;
return (
<>
<IconSelectContainerStyles targetWidth={popoverTargetWidth} />
<TypedSelect
className="icon-select-container"
itemListRenderer={this.renderMenu}
itemPredicate={this.filterIconName}
itemRenderer={this.renderIconItem}
items={ICON_NAMES}
noResults={<MenuItem disabled text="No results" />}
onItemSelect={this.handleIconChange}
popoverProps={{ minimal: true }}
>
<StyledButton
alignText={Alignment.LEFT}
className={Classes.TEXT_OVERFLOW_ELLIPSIS}
elementRef={this.iconSelectTargetRef}
fill
icon={iconName}
rightIcon="caret-down"
text={iconName || NONE}
/>
</TypedSelect>
</>
);
}
private renderMenu: ItemListRenderer<IconType> = ({
items,
itemsParentRef,
renderItem,
}) => {
const renderedItems = items.map(renderItem).filter((item) => item != null);
return <StyledMenu ulRef={itemsParentRef}>{renderedItems}</StyledMenu>;
};
private renderIconItem: ItemRenderer<IconName | typeof NONE> = (
icon,
{ handleClick, modifiers },
) => {
if (!modifiers.matchesPredicate) {
return null;
}
return (
<TooltipComponent content={icon}>
<StyledMenuItem
active={modifiers.active}
icon={icon === NONE ? undefined : icon}
key={icon}
onClick={handleClick}
text={icon === NONE ? NONE : undefined}
/>
</TooltipComponent>
);
};
private filterIconName = (
query: string,
iconName: IconName | typeof NONE,
) => {
if (iconName === NONE || query === "") {
return true;
}
return iconName.toLowerCase().indexOf(query.toLowerCase()) >= 0;
};
private handleIconChange = (icon: IconType) =>
this.updateProperty(
this.props.propertyName,
icon === NONE ? undefined : icon,
);
static getControlType() {
return "ICON_SELECT";
}
}
export default IconSelectControl;