IconPicker

This commit is contained in:
Sergej Schwabauer 2022-03-03 19:39:44 +01:00
parent 4c9a148dbf
commit f6ec1e1639
7 changed files with 248 additions and 1 deletions

View File

@ -20,3 +20,4 @@ npm run serve
npm install @pnp/spfx-property-controls --save --save-exact
npm run serve
gulp clean; gulp build; gulp bundle --ship; gulp package-solution --ship;
npm run serve

View File

@ -0,0 +1,5 @@
.autocomplete {
display: block;
}

View File

@ -0,0 +1,165 @@
import * as React from 'react';
// import styles from './Autocomplete.module.scss';
import { TextField, ITextFieldProps, Callout, DirectionalHint, ITextField } from 'office-ui-fabric-react';
import { isNullOrEmpty, cssClasses, getDeepOrDefault, isFunction } from '@spfxappdev/utility';
export interface IAutocompleteProps extends Omit<ITextFieldProps, "componentRef"> {
showSuggestionsOnFocus?: boolean;
minValueLength?: number;
onLoadSuggestions?(newValue: string): void;
onRenderSuggestions?(): JSX.Element;
}
interface IAutocompleteState {
currentValue: string;
isFlyoutVisible: boolean;
}
export class Autocomplete extends React.Component<IAutocompleteProps, IAutocompleteState> {
public state: IAutocompleteState = {
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue,
isFlyoutVisible: false,
};
public static defaultProps: IAutocompleteProps = {
showSuggestionsOnFocus: false,
minValueLength: 3
};
private textFieldReference: ITextField = null;
private textFieldDomElement: HTMLInputElement = null;
private userIsTyping: boolean = false;
private lastValue: string = "";
public render(): React.ReactElement<IAutocompleteProps> {
return (<>
<TextField {...this.props}
className={cssClasses("styles.autocomplete", this.props.className)}
componentRef={(input: ITextField) => {
this.textFieldReference = input;
this.textFieldDomElement = getDeepOrDefault<HTMLInputElement>(input, "_textElement.current", null);
}}
onFocus={(ev: any) => {
if(this.props.showSuggestionsOnFocus) {
this.handleSuggestionListVisibility();
}
if(isFunction(this.props.onFocus)) {
this.props.onFocus(ev);
}
}}
onBlur={(ev: any) => {
this.onTextFieldBlur();
if(isFunction(this.props.onBlur)) {
this.props.onBlur(ev);
}
}}
onChange={(ev: any, newValue: string) => {
this.onValueChanged(newValue);
if(isFunction(this.props.onChange)) {
this.props.onChange(ev, newValue);
}
}}
defaultValue={this.state.currentValue}
/>
{this.renderSuggesstionsFlyout()}
</>);
}
private renderSuggesstionsFlyout(): JSX.Element {
return (<Callout
hidden={!this.state.isFlyoutVisible}
className={"styles.bagHeaderMegamenu"}
directionalHintFixed={true}
gapSpace={0}
// calloutWidth={window.outerWidth}
isBeakVisible={false}
target={this.textFieldDomElement}
onDismiss={() => {
this.hideSuggesstionsFlyout();
}}
preventDismissOnScroll={true}
directionalHint={DirectionalHint.bottomCenter}>
{isFunction(this.props.onRenderSuggestions) && this.props.onRenderSuggestions()}
</Callout>
);
}
private onValueChanged(newValue: string): void {
this.userIsTyping = true;
this.state.currentValue = newValue;
this.setState({
currentValue: newValue
});
this.handleSuggestionListVisibility();
}
private onTextFieldBlur(): void {
this.userIsTyping = false;
}
private handleSuggestionListVisibility(): void {
let val = this.state.currentValue;
if(isNullOrEmpty(val)) {
this.hideSuggesstionsFlyout();
return;
}
if(val.length < this.props.minValueLength) {
this.hideSuggesstionsFlyout();
return;
}
let valueWasChanged = false;
if(!val.Equals(this.lastValue)) {
this.userIsTyping = false;
valueWasChanged = true;
}
if(!valueWasChanged) {
this.showSuggesstionsFlyout();
return;
}
window.setTimeout(() => {
if(this.userIsTyping) {
return;
}
this.showSuggesstionsFlyout();
if(isFunction(this.props.onLoadSuggestions)) {
this.props.onLoadSuggestions(this.state.currentValue);
}
}, 150);
}
private hideSuggesstionsFlyout(): void {
this.setState({
isFlyoutVisible: false
});
}
private showSuggesstionsFlyout(): void {
this.setState({
isFlyoutVisible: true
});
}
}

View File

@ -0,0 +1,4 @@
.iconpicker {
display: block;
}

View File

@ -0,0 +1,68 @@
import * as React from 'react';
import styles from './IconPicker.module.scss';
import { ITextFieldProps, Icon } from 'office-ui-fabric-react';
import { allIcons } from './availableIcons';
import { isNullOrEmpty, cssClasses } from '@spfxappdev/utility';
import { Autocomplete, IAutocompleteProps } from '@src/components/autocomplete/Autocomplete';
export interface IIconPickerProps extends Omit<ITextFieldProps, "componentRef">, IAutocompleteProps {
enableDialogPicker?: boolean;
dialogPickerIconName?: string;
}
interface IIconPickerState {
currentValue: string;
isFlyoutVisible: boolean;
}
export class IconPicker extends React.Component<IIconPickerProps, IIconPickerState> {
public state: IIconPickerState = {
currentValue: isNullOrEmpty(this.props.defaultValue) ? "" : this.props.defaultValue,
isFlyoutVisible: false,
};
public static defaultProps: IIconPickerProps = {
dialogPickerIconName: "GroupedList",
enableDialogPicker: true,
showSuggestionsOnFocus: false,
minValueLength: 0
};
public render(): React.ReactElement<IIconPickerProps> {
return (<>
<Autocomplete {...this.props}
className={cssClasses(styles.iconpicker)}
defaultValue={this.state.currentValue}
onLoadSuggestions={(newValue: string) => {
this.state.currentValue = newValue;
this.setState({
currentValue: newValue,
isFlyoutVisible: true
});
}}
onRenderSuggestions={() => {
return this.renderSuggesstionsFlyout();
}}
iconProps={{
iconName: this.state.currentValue
}} />
</>);
}
private renderSuggesstionsFlyout(): JSX.Element {
return (<>
<ul>
{allIcons.Where(icon => icon.StartsWith(this.state.currentValue)).map((iconName: string): JSX.Element => {
return (<li key={`Icon_${iconName}`}><Icon iconName={iconName} /></li>);
})}
</ul>
</>
);
}
}

File diff suppressed because one or more lines are too long

View File

@ -11,6 +11,7 @@ import '@spfxappdev/utility/lib/extensions/ArrayExtensions';
import { IconButton } from '@microsoft/office-ui-fabric-react-bundle';
import { MarkerIcon } from './MarkerIcon';
import * as strings from 'MapWebPartStrings';
import { IconPicker } from '@src/components/iconPicker/IconPicker';
export interface IManageMarkerCategoriesDialogProps {
markerCategories: IMarkerCategory[];
@ -163,7 +164,7 @@ export default class ManageMarkerCategoriesDialog extends React.Component<IManag
/>
</div>
<div className='spfxappdev-grid-col spfxappdev-sm3'>
<TextField
<IconPicker
defaultValue={categoryItem.iconProperties.iconName}
onChange={(ev: any, name: string) => {
this.state.markerCategories[index].iconProperties.iconName = name;