Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 13 additions & 11 deletions SearchBox/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,27 @@ This code component provides a wrapper around the [Fluent UI SearchBox](https://

## Properties

### Output Properties
- **SearchText** - The action items to render. The first item is considered the root item.

| Property | Description |
| -------- | ----------- |
| `SearchText` | The action items to render. The first item is considered the root item. |
- **IconName** - Name of the Fluent UI icon (see [Fluent UI Icons](https://developer.microsoft.com/en-us/fluentui#/styles/web/icons)).

### Style properties
- **Underlined** - Whether or not the SearchBox is underlined.

| Property | Description |
| -------- | ----------- |
| `Theme` |Accepts a JSON string that is generated using [Fluent UI Theme Designer (windows.net)](https://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/theming-designer/). Leaving this blank will use the default theme defined by Power Apps. Leaving this blank will use the default theme defined by Power Apps. See [theming](theme.md) for guidance on how to configure. |
| `AccessibilityLabel` | Screen reader aria-label |
- **DisableAnimation** - Whether or not to animate the SearchBox icon on focus.

## Behavior
- **PlaceholderText** - Placeholder for the search box.

## Additional properties

- **Theme** - Accepts a JSON string that is generated using [Fluent UI Theme Designer (windows.net)](https://fabricweb.z5.web.core.windows.net/pr-deploy-site/refs/heads/master/theming-designer/). Leaving this blank will use the default theme defined by Power Apps. Leaving this blank will use the default theme defined by Power Apps. See [theming](theme.md) for guidance on how to configure.
- **AccessibilityLabel** - Screen reader aria-label

## Example

### Connecting SearchBox to a Datasource

On any dataset `Items` property (e.g., in a gallery or DetailsList controls), add the following Power Fx formula:

```Power Fx
```powerapps-dot
Search( Accounts, SearchBox_1.SearchText, "name" )
```
2 changes: 2 additions & 0 deletions SearchBox/SearchBox/ControlManifest.Input.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<property name="Underlined" display-name-key="Underlined" of-type="TwoOptions" usage="input" required="false" />
<property name="DisableAnimation" display-name-key="DisableAnimation" of-type="TwoOptions" usage="input" required="false" />
<property name="PlaceHolderText" display-name-key="PlaceHolderText" of-type="SingleLine.Text" usage="input" required="false" default-value="Search" />
<property name="InputEvent" display-name-key="InputEvent" of-type="SingleLine.Text" usage="input" required="false"/>

<resources>
<code path="index.ts" order="1" />
<resx path="strings/SearchBox.1033.resx" version="1.0.0" />
Expand Down
8 changes: 8 additions & 0 deletions SearchBox/SearchBox/ManifestConstants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const enum InputEvents {
SetFocus = 'SetFocus',
}

export const enum InputProperties {
InputEvent = 'InputEvent',
SelectedKey = 'SelectedKey',
}
1 change: 1 addition & 0 deletions SearchBox/SearchBox/__mocks__/mock-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ export function getMockParameters(): IInputs {
PlaceHolderText: new MockStringProperty(),
IconName: new MockStringProperty(),
DisableAnimation: new MockTwoOptionsProperty(),
InputEvent: new MockStringProperty(),
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ exports[`SearchBox renders 1`] = `
iconName=""
onChanged={[Function]}
placeholderText=""
setFocus=""
themeJSON=""
underLined={false}
width={-1}
Expand All @@ -19,9 +20,6 @@ exports[`SearchBox theme 1`] = `
<div
className=

{
width: -1px;
}
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
Expand Down Expand Up @@ -60,6 +58,7 @@ exports[`SearchBox theme 1`] = `
padding-left: 4px;
padding-right: 0;
padding-top: 1px;
width: -1px;
}
@media screen and (-ms-high-contrast: active), screen and (forced-colors: active){& {
border-color: WindowText;
Expand Down Expand Up @@ -96,16 +95,22 @@ exports[`SearchBox theme 1`] = `
<i
aria-hidden={true}
className=
ms-Icon-placeHolder
ms-SearchBox-icon
{
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
display: inline-block;
font-family: "FabricMDL2Icons";
font-style: normal;
font-weight: normal;
opacity: 1;
speak: none;
transition: opacity 0.167s 0s;
width: 1em;
}
data-icon-name=""
/>
data-icon-name="search"
>
</i>
</div>
<input
aria-label=""
Expand Down Expand Up @@ -150,12 +155,12 @@ exports[`SearchBox theme 1`] = `
display: none;
}
disabled={false}
id="SearchBox2"
id="SearchBox6"
onBlur={[Function]}
onChange={[Function]}
onInput={[Function]}
onKeyDown={[Function]}
placeholder=""
placeholder="search"
role="searchbox"
value=""
/>
Expand Down
19 changes: 18 additions & 1 deletion SearchBox/SearchBox/__tests__/searchbox-lifecycle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IInputs } from '../generated/ManifestTypes';
import { MockContext, MockState } from '../__mocks__/mock-context';
import { getMockParameters } from '../__mocks__/mock-parameters';
import * as renderer from 'react-test-renderer';
import { mount } from 'enzyme';

// Since requestAnimationFrame does not exist in the test DOM, mock it
window.requestAnimationFrame = jest.fn().mockImplementation((callback) => {
Expand All @@ -29,14 +30,30 @@ describe('SearchBox', () => {
expect(element).toMatchSnapshot();
});

it('on search text enter', () => {
const { component, context, notifyOutputChanged } = createComponent();
const searchText = 'Hello';
component.init(context, notifyOutputChanged);
const searchBoxElement = component.updateView(context);
const searchBox = mount(searchBoxElement);
const searchBoxComponent = searchBox.find('.ms-SearchBox-field').first();
expect(searchBoxComponent.length).toEqual(1);

searchBoxComponent.simulate('change', { target: { value: searchText } });
const outputs = component.getOutputs();
expect(outputs.SearchText).toEqual(searchText);
});

it('theme', async () => {
const { component, context, notifyOutputChanged } = createComponent();
context.parameters.Theme.raw = JSON.stringify({
palette: {
themePrimary: '#0078d4',
},
});

context.parameters.IconName.raw = 'search';
context.parameters.PlaceHolderText.raw = 'search';
context.parameters.Underlined.raw = false;
component.init(context, notifyOutputChanged);
const personaComponent = renderer.create(component.updateView(context));
expect(personaComponent.toJSON()).toMatchSnapshot();
Expand Down
7 changes: 1 addition & 6 deletions SearchBox/SearchBox/components/Component.types.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
/**
* @license Copyright (c) Microsoft Corporation. All rights reserved.
*/

//import { ISearchBoxProps } from '@fluentui/react'

export interface ISearchBoxComponentProps {
width?: number;
height?: number;
Expand All @@ -15,4 +9,5 @@ export interface ISearchBoxComponentProps {
iconName?: string;
disabled?: boolean;
disableAnimation?: boolean;
setFocus?: string;
}
17 changes: 15 additions & 2 deletions SearchBox/SearchBox/components/SearchBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import { SearchBox, createTheme, IPartialTheme, ThemeProvider, IIconProps, merge
import { ISearchBoxComponentProps } from './Component.types';

export const SearchBoxComponent = React.memo((props: ISearchBoxComponentProps) => {
const { onChanged, themeJSON, ariaLabel, placeholderText, underLined, disabled, disableAnimation } = props;
const { onChanged, themeJSON, ariaLabel, placeholderText, underLined, disabled, disableAnimation, setFocus } =
props;
const filterIcon: IIconProps = { iconName: props.iconName };
const rootRef = React.useRef<HTMLDivElement>(null);
const theme = React.useMemo(() => {
try {
return themeJSON ? createTheme(JSON.parse(themeJSON) as IPartialTheme) : undefined;
Expand All @@ -21,8 +23,17 @@ export const SearchBoxComponent = React.memo((props: ISearchBoxComponentProps) =
width: props.width,
});

React.useEffect(() => {
if (setFocus && setFocus !== '' && rootRef) {
const searchBoxes = (rootRef.current as HTMLElement).getElementsByClassName('ms-SearchBox-field');
if (searchBoxes && searchBoxes.length > 0) {
(searchBoxes[0] as HTMLInputElement).focus();
}
}
}, [setFocus, rootRef]);

return (
<ThemeProvider theme={theme} className={wrapperClass}>
<ThemeProvider theme={theme}>
<SearchBox
placeholder={placeholderText}
onChange={onChange}
Expand All @@ -31,6 +42,8 @@ export const SearchBoxComponent = React.memo((props: ISearchBoxComponentProps) =
iconProps={filterIcon}
disabled={disabled}
disableAnimation={disableAnimation}
className={wrapperClass}
ref={rootRef}
/>
</ThemeProvider>
);
Expand Down
11 changes: 10 additions & 1 deletion SearchBox/SearchBox/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ import * as React from 'react';
import { IInputs, IOutputs } from './generated/ManifestTypes';
import { SearchBoxComponent } from './components/SearchBox';
import { ISearchBoxComponentProps } from './components/Component.types';

import { InputEvents } from './ManifestConstants';
export class SearchBox implements ComponentFramework.ReactControl<IInputs, IOutputs> {
context: ComponentFramework.Context<IInputs>;
notifyOutputChanged: () => void;
searchTextValue: string | null;
setFocus = '';

/**
* Used to initialize the control instance. Controls can kick off remote server calls and other initialization actions here.
Expand All @@ -28,6 +29,13 @@ export class SearchBox implements ComponentFramework.ReactControl<IInputs, IOutp
public updateView(context: ComponentFramework.Context<IInputs>): React.ReactElement {
const allocatedWidth = parseInt(context.mode.allocatedWidth as unknown as string);
const allocatedHeight = parseInt(context.mode.allocatedHeight as unknown as string);
const inputEvent = this.context.parameters.InputEvent.raw;
const eventChanged = inputEvent && this.setFocus !== inputEvent;

if (eventChanged && inputEvent.startsWith(InputEvents.SetFocus)) {
this.setFocus = inputEvent;
}

const props: ISearchBoxComponentProps = {
onChanged: this.onChange,
themeJSON: context.parameters.Theme.raw ?? '',
Expand All @@ -39,6 +47,7 @@ export class SearchBox implements ComponentFramework.ReactControl<IInputs, IOutp
disableAnimation: context.parameters.DisableAnimation.raw ?? false,
width: allocatedWidth,
height: allocatedHeight,
setFocus: this.setFocus,
};

return React.createElement(SearchBoxComponent, props);
Expand Down
3 changes: 3 additions & 0 deletions SearchBox/SearchBox/strings/SearchBox.1033.resx
Original file line number Diff line number Diff line change
Expand Up @@ -139,4 +139,7 @@
<data name="DisableAnimation" xml:space="preserve">
<value>Disable Animation</value>
</data>
<data name="InputEvent" xml:space="preserve">
<value>Input Event</value>
</data>
</root>
39 changes: 0 additions & 39 deletions SearchBox/coverage/clover.xml

This file was deleted.

3 changes: 0 additions & 3 deletions SearchBox/coverage/coverage-final.json

This file was deleted.

Loading