|
| 1 | +import styled from "styled-components"; |
| 2 | +import { RecordConstructorToView } from "lowcoder-core"; |
| 3 | +import { styleControl } from "comps/controls/styleControl"; |
| 4 | +import _ from "lodash"; |
| 5 | +import { |
| 6 | + AvatarStyle, |
| 7 | + AvatarStyleType, |
| 8 | +} from "comps/controls/styleControlConstants"; |
| 9 | +import { UICompBuilder } from "comps/generators/uiCompBuilder"; |
| 10 | +import { withDefault } from "../generators"; |
| 11 | +import { |
| 12 | + NameConfig, |
| 13 | + NameConfigHidden, |
| 14 | + withExposingConfigs, |
| 15 | +} from "comps/generators/withExposing"; |
| 16 | +import { Section, sectionNames } from "lowcoder-design"; |
| 17 | +import { hiddenPropertyView } from "comps/utils/propertyUtils"; |
| 18 | +import { trans } from "i18n"; |
| 19 | +import { NumberControl } from "comps/controls/codeControl"; |
| 20 | +import { IconControl } from "comps/controls/iconControl"; |
| 21 | +import { |
| 22 | + clickEvent, |
| 23 | + eventHandlerControl, |
| 24 | +} from "../controls/eventHandlerControl"; |
| 25 | +import { Avatar, AvatarProps, Badge, Dropdown, Menu } from "antd"; |
| 26 | +import { LeftRightControl, dropdownControl } from "../controls/dropdownControl"; |
| 27 | +import { stringExposingStateControl } from "../controls/codeStateControl"; |
| 28 | +import { BoolControl } from "../controls/boolControl"; |
| 29 | +import { BadgeBasicSection, badgeChildren } from "./badgeComp/badgeConstants"; |
| 30 | +import { DropdownOptionControl } from "../controls/optionsControl"; |
| 31 | +import { ReactElement, useContext } from "react"; |
| 32 | +import { CompNameContext, EditorContext } from "../editorState"; |
| 33 | + |
| 34 | +const AvatarWrapper = styled(Avatar) <AvatarProps & { $cursorPointer: boolean, $style: AvatarStyleType }>` |
| 35 | + background: ${(props) => props.$style.background}; |
| 36 | + color: ${(props) => props.$style.fill}; |
| 37 | + cursor: ${(props) => props.$cursorPointer ? 'pointer' : ''}; |
| 38 | +`; |
| 39 | + |
| 40 | +const Wrapper = styled.div <{ iconSize: number, labelPosition: string }>` |
| 41 | +display: flex; |
| 42 | +width: 100%; |
| 43 | +height: 100%; |
| 44 | +padding: 0px; |
| 45 | +align-items: center; |
| 46 | +flex-direction: ${(props) => props.labelPosition === 'left' ? 'row' : 'row-reverse'}; |
| 47 | +` |
| 48 | + |
| 49 | +const LabelWrapper = styled.div<{ iconSize: number, alignmentPosition: string }>` |
| 50 | +width: calc(100% - ${(props) => props.iconSize}px); |
| 51 | +display: flex; |
| 52 | +padding-left: 5px; |
| 53 | +padding-right: 5px; |
| 54 | +flex-direction: column; |
| 55 | +justify-content: flex-end; |
| 56 | +align-items: ${(props) => props.alignmentPosition === 'left' ? 'flex-start' : 'flex-end'}; |
| 57 | +` |
| 58 | +const LabelSpan = styled.span<{ color: string }>` |
| 59 | +max-width: 100%; |
| 60 | +overflow: hidden; |
| 61 | +text-overflow: ellipsis; |
| 62 | +white-space: nowrap; |
| 63 | +font-weight: bold; |
| 64 | +color: ${(props) => props.color}; |
| 65 | +` |
| 66 | +const CaptionSpan = styled.span<{ color: string }>` |
| 67 | +max-width: 100%; |
| 68 | +overflow: hidden; |
| 69 | +text-overflow: ellipsis; |
| 70 | +white-space: nowrap; |
| 71 | +color: ${(props) => props.color}; |
| 72 | +` |
| 73 | +const EventOptions = [clickEvent] as const; |
| 74 | +const sharpOptions = [ |
| 75 | + { label: trans("avatarComp.square"), value: "square" }, |
| 76 | + { label: trans("avatarComp.circle"), value: "circle" }, |
| 77 | +] as const; |
| 78 | + |
| 79 | +const sideOptions = [ |
| 80 | + { label: trans('labelProp.left'), value: "left" }, |
| 81 | + { label: trans('labelProp.right'), value: "right" }, |
| 82 | +] as const; |
| 83 | + |
| 84 | +const childrenMap = { |
| 85 | + style: styleControl(AvatarStyle), |
| 86 | + icon: withDefault(IconControl, "/icon:solid/user"), |
| 87 | + iconSize: withDefault(NumberControl, 40), |
| 88 | + onEvent: eventHandlerControl(EventOptions), |
| 89 | + shape: dropdownControl(sharpOptions, "circle"), |
| 90 | + title: stringExposingStateControl("title", ""), |
| 91 | + src: stringExposingStateControl("src", ""), |
| 92 | + avatarLabel: stringExposingStateControl("avatarLabel", "{{currentUser.name}}"), |
| 93 | + avatarCatption: stringExposingStateControl("avatarCatption", "{{currentUser.email}}"), |
| 94 | + labelPosition: dropdownControl(sideOptions, 'left'), |
| 95 | + alignmentPosition: withDefault(LeftRightControl, 'left'), |
| 96 | + enableDropdownMenu: BoolControl, |
| 97 | + options: DropdownOptionControl, |
| 98 | + ...badgeChildren, |
| 99 | +}; |
| 100 | + |
| 101 | +const AvatarView = (props: RecordConstructorToView<typeof childrenMap>) => { |
| 102 | + const { shape, title, src, iconSize } = props; |
| 103 | + const comp = useContext(EditorContext).getUICompByName(useContext(CompNameContext)); |
| 104 | + const eventsCount = comp ? Object.keys(comp?.children.comp.children.onEvent.children).length : 0; |
| 105 | + const hasIcon = props.options.findIndex((option) => (option.prefixIcon as ReactElement)?.props.value) > -1; |
| 106 | + const items = props.options |
| 107 | + .filter((option) => !option.hidden) |
| 108 | + .map((option, index) => ({ |
| 109 | + title: option.label, |
| 110 | + label: option.label, |
| 111 | + key: option.label + " - " + index, |
| 112 | + disabled: option.disabled, |
| 113 | + icon: hasIcon && <span>{option.prefixIcon}</span>, |
| 114 | + onEvent: option.onEvent, |
| 115 | + })); |
| 116 | + const menu = ( |
| 117 | + <Menu |
| 118 | + items={items} |
| 119 | + onClick={({ key }) => items.find((o) => o.key === key)?.onEvent("click")} |
| 120 | + /> |
| 121 | + ); |
| 122 | + return ( |
| 123 | + <Dropdown |
| 124 | + menu={{ items }} |
| 125 | + placement={props.labelPosition === 'left' ? "bottomLeft" : "bottomRight"} |
| 126 | + arrow |
| 127 | + disabled={!props.enableDropdownMenu} |
| 128 | + dropdownRender={() => menu} |
| 129 | + > |
| 130 | + <Wrapper iconSize={props.iconSize} labelPosition={props.labelPosition}> |
| 131 | + <Badge |
| 132 | + count={props.badgeCount.value} |
| 133 | + dot={props.badgeType === 'dot'} |
| 134 | + size={props.badgeSize} |
| 135 | + overflowCount={props.overflowCount} |
| 136 | + title={props.badgeTitle} |
| 137 | + offset={props.shape === 'circle' ? [-2, 6] : [0, 0]} |
| 138 | + > |
| 139 | + <AvatarWrapper |
| 140 | + size={iconSize} |
| 141 | + icon={title.value !== '' ? null : props.icon} |
| 142 | + shape={shape} |
| 143 | + $style={props.style} |
| 144 | + src={src.value} |
| 145 | + $cursorPointer={eventsCount > 0} |
| 146 | + onClick={() => props.onEvent("click")} |
| 147 | + > |
| 148 | + {title.value} |
| 149 | + </AvatarWrapper> |
| 150 | + </Badge> |
| 151 | + <LabelWrapper iconSize={props.iconSize} alignmentPosition={props.alignmentPosition}> |
| 152 | + <LabelSpan color={props.style.label}>{props.avatarLabel.value}</LabelSpan> |
| 153 | + <CaptionSpan color={props.style.caption}>{props.avatarCatption.value}</CaptionSpan> |
| 154 | + </LabelWrapper> |
| 155 | + </Wrapper> |
| 156 | + </Dropdown> |
| 157 | + ); |
| 158 | +}; |
| 159 | + |
| 160 | +let AvatarBasicComp = (function () { |
| 161 | + return new UICompBuilder(childrenMap, (props) => <AvatarView {...props} />) |
| 162 | + .setPropertyViewFn((children) => ( |
| 163 | + <> |
| 164 | + <Section name={sectionNames.basic}> |
| 165 | + {children.src.propertyView({ |
| 166 | + label: trans("avatarComp.src"), |
| 167 | + placeholder: "http://xxxx/xx.jpg", |
| 168 | + tooltip: trans("avatarComp.avatarCompTooltip"), |
| 169 | + })} |
| 170 | + {children.title.propertyView({ |
| 171 | + label: trans("avatarComp.title"), |
| 172 | + tooltip: trans("avatarComp.avatarCompTooltip"), |
| 173 | + })} |
| 174 | + {children.icon.propertyView({ |
| 175 | + label: trans("avatarComp.icon"), |
| 176 | + IconType: "All", |
| 177 | + tooltip: trans("avatarComp.avatarCompTooltip"), |
| 178 | + })} |
| 179 | + {children.shape.propertyView({ |
| 180 | + label: trans("avatarComp.shape"), |
| 181 | + radioButton: true, |
| 182 | + })} |
| 183 | + { |
| 184 | + children.iconSize.propertyView({ |
| 185 | + label: trans("avatarComp.iconSize"), |
| 186 | + })} |
| 187 | + { |
| 188 | + children.enableDropdownMenu.propertyView({ |
| 189 | + label: trans("avatarComp.enableDropDown") |
| 190 | + })} |
| 191 | + {children.enableDropdownMenu.getView() && children.options.propertyView({})} |
| 192 | + </Section> |
| 193 | + <Section name={trans('avatarComp.label')}> |
| 194 | + { |
| 195 | + children.avatarLabel.propertyView({ |
| 196 | + label: trans("avatarComp.label"), |
| 197 | + })} |
| 198 | + { |
| 199 | + children.avatarCatption.propertyView({ |
| 200 | + label: trans("avatarComp.caption"), |
| 201 | + })} |
| 202 | + { |
| 203 | + children.labelPosition.propertyView({ |
| 204 | + label: trans("avatarComp.labelPosition"), |
| 205 | + radioButton: true, |
| 206 | + })} |
| 207 | + { |
| 208 | + children.alignmentPosition.propertyView({ |
| 209 | + label: trans("avatarComp.alignmentPosition"), |
| 210 | + radioButton: true, |
| 211 | + })} |
| 212 | + </Section> |
| 213 | + {<BadgeBasicSection {...children} />} |
| 214 | + <Section name={sectionNames.interaction}> |
| 215 | + {children.onEvent.getPropertyView()} |
| 216 | + </Section> |
| 217 | + <Section name={sectionNames.layout}> |
| 218 | + {hiddenPropertyView(children)} |
| 219 | + </Section> |
| 220 | + <Section name={sectionNames.style}> |
| 221 | + {children.style.getPropertyView()} |
| 222 | + </Section> |
| 223 | + </> |
| 224 | + )) |
| 225 | + .build(); |
| 226 | +})(); |
| 227 | + |
| 228 | + |
| 229 | +export const AvatarComp = withExposingConfigs(AvatarBasicComp, [ |
| 230 | + NameConfigHidden, |
| 231 | + new NameConfig("badgeCount", trans("button.textDesc")), |
| 232 | +]); |
0 commit comments