Skip to content

Commit

Permalink
feat: add zIndex prop (#56)
Browse files Browse the repository at this point in the history
* feat: add zIndex prop

* add zIndex to README
  • Loading branch information
mrderyk authored Mar 18, 2024
1 parent 9524c44 commit 59b656a
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 51 deletions.
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ import { ReactSearch } from "@vectara/react-search";
customerId="CUSTOMER_ID"
corpusId="CORPUS_ID"
apiKey="API_KEY"
placeholder="Search for anything"
isDeeplinkable
placeholder={ /* (optional) string to be used as search input placeholder */ }
isDeeplinkable={ /* (optional) boolean indicating if search can be deeplinked */ }
openResultsInNewTab={ /* (optional) boolean indicating if links should open in a new tab */ }
zIndex={ /* (optional) number representing the z-index the search modal should have */ }
/>;
```

Expand Down Expand Up @@ -89,6 +91,10 @@ Defaults to `false`. Set this option if you want to persist a search query to a

Defaults to `false`. Set this option if you want a search result to open in a new tab.

##### `zIndex` (optional)

Customize the z-index of the search modal

### Power your own search UI with the useSearch hook

Install React-Search:
Expand Down
4 changes: 3 additions & 1 deletion docs/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,14 @@ const generateCodeSnippet = (
props.push(`openResultsInNewTab={${openResultsInNewTab}}`);
}

props.push(`zIndex={ /* (optional) number representing the z-index the search modal should have */ }`);

return `import { ReactSearch } from "@vectara/react-search";
export const App = () => (
<div>
<ReactSearch
${props.join("\n ")}
${props.join("\n ")}
/>
</div>
);`;
Expand Down
95 changes: 50 additions & 45 deletions src/SearchModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,53 +10,58 @@ type Props = {
onClose: () => void;
isOpen?: boolean;
children?: ReactNode[];
zIndex: number;
};

export const SearchModal = forwardRef(({ onClose, isOpen, children }: Props, ref: ForwardedRef<HTMLDivElement>) => {
const returnFocusElRef = useRef<HTMLElement | null>(null);

// Return focus on unmount.
useEffect(() => {
if (isOpen) {
// We have to be more specific when picking out the return focus element.
// This is because document.activeElement is now a custom element containing a shadow DOM
// In order to properly return focus, we key in on the actual button inside the shadow DOM.
returnFocusElRef.current = document.activeElement?.shadowRoot?.querySelector("button") as HTMLElement;
} else {
returnFocusElRef.current?.focus();
returnFocusElRef.current = null;
}
}, [isOpen]);

// Allow contents to respond to blur events before unmounting.
const onCloseDelayed = () => {
window.setTimeout(() => {
onClose();
}, 0);
};

return (
<VuiPortal>
<div className="vrsStyleWrapper">
{isOpen && (
<VuiScreenBlock>
<FocusOn
onEscapeKey={onCloseDelayed}
onClickOutside={onCloseDelayed}
// Enable manual focus return to work.
returnFocus={false}
// Enable focus on contents when it's open,
// but enable manual focus return to work when it's closed.
autoFocus={isOpen}
>
<SearchModalContents ref={ref}>{children}</SearchModalContents>
</FocusOn>
</VuiScreenBlock>
)}
</div>
</VuiPortal>
);
});
export const SearchModal = forwardRef(
({ onClose, isOpen, children, zIndex }: Props, ref: ForwardedRef<HTMLDivElement>) => {
const returnFocusElRef = useRef<HTMLElement | null>(null);

// Return focus on unmount.
useEffect(() => {
if (isOpen) {
// We have to be more specific when picking out the return focus element.
// This is because document.activeElement is now a custom element containing a shadow DOM
// In order to properly return focus, we key in on the actual button inside the shadow DOM.
returnFocusElRef.current = document.activeElement?.shadowRoot?.querySelector("button") as HTMLElement;
} else {
returnFocusElRef.current?.focus();
returnFocusElRef.current = null;
}
}, [isOpen]);

// Allow contents to respond to blur events before unmounting.
const onCloseDelayed = () => {
window.setTimeout(() => {
onClose();
}, 0);
};

return (
<VuiPortal>
<div className="vrsStyleWrapper">
{isOpen && (
<div style={{ zIndex }}>
<VuiScreenBlock>
<FocusOn
onEscapeKey={onCloseDelayed}
onClickOutside={onCloseDelayed}
// Enable manual focus return to work.
returnFocus={false}
// Enable focus on contents when it's open,
// but enable manual focus return to work when it's closed.
autoFocus={isOpen}
>
<SearchModalContents ref={ref}>{children}</SearchModalContents>
</FocusOn>
</VuiScreenBlock>
</div>
)}
</div>
</VuiPortal>
);
}
);

interface SearchModalContentsProps {
children: ReactNode;
Expand Down
9 changes: 6 additions & 3 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const ReactSearchInternal: FC<Props> = ({
historySize = 10,
placeholder = "Search",
isDeeplinkable = false,
openResultsInNewTab = false
openResultsInNewTab = false,
zIndex = 9999
}) => {
// Compute a unique ID for this search component.
// This creates a namespace, and ensures that stored search results
Expand Down Expand Up @@ -234,7 +235,7 @@ const ReactSearchInternal: FC<Props> = ({
</VuiFlexContainer>
</button>
</div>
<SearchModal isOpen={isOpen} onClose={closeModalAndResetResults}>
<SearchModal isOpen={isOpen} onClose={closeModalAndResetResults} zIndex={zIndex}>
<form>
<div className="vrsSearchForm">
<div className="vrsCloseButtonWrapper">
Expand Down Expand Up @@ -333,7 +334,7 @@ class ReactSearchWebComponent extends HTMLElement {
// All of these are observed as lower-cased, because HTML tag attributes are implicitly converted to be lower-cased.
// We avoid extra work by passing props to this web component as they come in, i.e. customerId,
// but in order to properly observe them, we need to use their final lower-cased form.
return ["customerid", "corpusid", "apikey", "placeholder", "isdeeplinkable", "openresultsinnewtab"];
return ["customerid", "corpusid", "apikey", "placeholder", "isdeeplinkable", "openresultsinnewtab", "zindex"];
}

constructor() {
Expand Down Expand Up @@ -364,6 +365,7 @@ class ReactSearchWebComponent extends HTMLElement {
const placeholder = this.getAttribute("placeholder") ?? undefined;
const isDeepLinkable = this.getAttribute("isdeeplinkable") === "true";
const openResultsInNewTab = this.getAttribute("openresultsinnewtab") === "true";
const zIndex = this.getAttribute("zIndex") !== null ? parseInt(this.getAttribute("zIndex")!) : undefined;

ReactDOM.render(
<>
Expand All @@ -374,6 +376,7 @@ class ReactSearchWebComponent extends HTMLElement {
placeholder={placeholder}
isDeeplinkable={isDeepLinkable}
openResultsInNewTab={openResultsInNewTab}
zIndex={zIndex}
/>
</>,
this.mountPoint
Expand Down
3 changes: 3 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ export interface Props {

// Whether to open selected results in a new browser tab.
openResultsInNewTab?: boolean;

// Used to control the search modal's z-index. Defaults to 9999.
zIndex?: number;
}

export type DeserializedSearchResult = {
Expand Down

0 comments on commit 59b656a

Please sign in to comment.