Skip to content

Commit

Permalink
add: a button to clear text in searchable-dropdown
Browse files Browse the repository at this point in the history
  • Loading branch information
Odaimoko committed Apr 29, 2024
1 parent 685ef4a commit a2e79ca
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 42 deletions.
3 changes: 3 additions & 0 deletions src/ui/react-view/props-typing/i-interactable-id.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface I_InteractableId {
interactableId?: string;
}
50 changes: 35 additions & 15 deletions src/ui/react-view/view-template/icon-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {IRenderable} from "../../common/i-renderable";
import React from "react";
import {getIcon} from "obsidian";
import {HtmlStringComponent} from "./html-string-component";
import {I_InteractableId} from "../props-typing/i-interactable-id";

export const CssClass_Link = "cm-underline";
export const obsidianIconTopOffset = 4;
Expand Down Expand Up @@ -41,33 +42,52 @@ export function InternalLinkView({content, onIconClicked, onContentClicked, styl
/>
}

interface I_IconClickable {
content?: IRenderable;
onIconClicked?: () => void;
onContentClicked?: () => void;
clickable?: boolean;

}

/**
* Can also be used as non-clickable
*/
export function ClickableIconView({content, onIconClicked, onContentClicked, iconName, style, clickable}: {
content?: IRenderable,
onIconClicked?: () => void,
onContentClicked?: () => void,
export function ClickableIconView({
content,
onIconClicked,
onContentClicked,
iconName,
style,
clickable,
interactableId
}: {
iconName: string,
clickable?: boolean,
} & I_Stylable) {
} & I_IconClickable & I_Stylable & I_InteractableId) {
return <ClickableView clickable={clickable} icon={<ObsidianIconView iconName={iconName}/>} content={content}
onIconClicked={onIconClicked} onContentClicked={onContentClicked} style={style}/>
onIconClicked={onIconClicked} onContentClicked={onContentClicked} style={style}
interactableId={interactableId}/>
}

export function ClickableView({icon, content, style, clickable = true, onIconClicked, onContentClicked}: {
content?: IRenderable,
onIconClicked?: () => void,
onContentClicked?: () => void,
export function ClickableView({
icon,
content,
style,
clickable = true,
onIconClicked,
onContentClicked,
interactableId
}: {
icon: IRenderable,
clickable?: boolean,
} & I_Stylable) {
} & I_IconClickable & I_Stylable & I_InteractableId) {
onIconClicked = clickable ? onIconClicked : undefined;
onContentClicked = clickable ? onContentClicked : undefined;
return <span style={style}>

{clickable ? <a className={CssClass_Link} onClick={onIconClicked}>
{clickable ? <a className={CssClass_Link} id={`${interactableId}_normalLink`} onClick={onIconClicked}>
{icon}
</a> : icon}
<span onClick={onContentClicked}>
<span id={`${interactableId}_spanLink`} onClick={onContentClicked}>
{content}
</span>
</span>
Expand Down
70 changes: 43 additions & 27 deletions src/ui/react-view/view-template/searchable-dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {isCharacterInput} from "../../../utils/react-user-input";
import {IRenderable} from "../../common/i-renderable";
import {toggleValueInArray} from "../workflow-filter";
import {dropdownSelectedColor} from "../style-def";
import {ClickableIconView} from "./icon-view";


interface I_OptionItem {
Expand Down Expand Up @@ -40,7 +41,7 @@ export const SearchableDropdown = (props: {
RenderView?: (props: { item: OptionValueType }) => IRenderable;
singleSelect?: boolean;
currentOptionValues?: OptionValueType[]; // Current selected option values. If singleSelect, we don't need to pass this.
dropdownId: string;
dropdownId: string; // used to check if we clicked outside the dropdown. The direct interactable element should have a prefix of this id.
} & StyleProps) => {
const singleSelect = props.singleSelect ?? true; // Default to single select
if (!singleSelect) {
Expand All @@ -50,7 +51,7 @@ export const SearchableDropdown = (props: {
console.error("Multiple Select Dropdown must pass currentOptionValues. Data are ", props.data)
}
}
const searchString = props.dropdownId;
const dropdownId = props.dropdownId;
const [searchText, setSearchText] = useState("")
const {dropDownDisplay, setDropDownDisplay, showDropdown} = usePopup();
const filtered = props.data.filter(k => k.name.toLowerCase().includes(searchText.toLowerCase()))
Expand All @@ -73,7 +74,9 @@ export const SearchableDropdown = (props: {
onKeyDown={handleBaseKeyboard}
onBlur={(event) => {
// Hide Dropdown if we lose focus
if (event.relatedTarget && event.relatedTarget.id.startsWith(searchString)) {
// target is the element that lost focus (input), relatedTarget is the element that gains focus
devLog(event.relatedTarget?.id)
if (event.relatedTarget?.id?.startsWith(dropdownId)) {
// Let the project_choice button handle the click event
// Otherwise when we lose focus and hide the dropdown, the button will not be triggered.
} else {
Expand All @@ -83,39 +86,51 @@ export const SearchableDropdown = (props: {
}
}}
>
<input ref={inputRef}
type="text" placeholder={props.placeholder}
value={searchText}
onChange={(event) => {
const text = event.target.value;
// when there is text, show the dropdown
if (text && text.length > 0) {
showDropdown()
} else {
hideDropdown()
}
handleSetSearchText(text)
}}

onFocus={() => {
// show when click search input box

devLog("Input Focused");
showDropdown();
}}
/>
<span id={`${dropdownId}_input`} style={{display: "flex", alignItems: "center"}}>
<input ref={inputRef}
type="text" placeholder={props.placeholder}
value={searchText}
onChange={(event) => {
const text = event.target.value;
// when there is text, show the dropdown
if (text && text.length > 0) {
showDropdown()
} else {
hideDropdown()
}
handleSetSearchText(text)
}}

onFocus={() => {
// show when click search input box

devLog("Input Focused");
showDropdown();
}}
/>
<ClickableIconView style={{marginLeft: -25, paddingTop: 5}}
onIconClicked={() => {
setSearchText("")
// input's focus is already lost onclick, so we need to
// 1. show the already hidden dropdown.
// a. must. merely focusing on the input box will show the dropdown on the next event loop, which causes blinking dropdown.
// 2. focus on it again, so that user can input text immediately.
showDropdown()
inputRef.current?.focus()
}} iconName={"x-circle"}/>
</span>
{/*Add background so it won't be transparent. */}
<div id={`${searchString}s`} style={getDropdownStyle(dropDownDisplay)}
<div id={`${dropdownId}s`} style={getDropdownStyle(dropDownDisplay)}
>
<VStack spacing={2}>
{filtered.map((option: OptionValueType) => {
const childId = `${searchString}_${option.name}`;
const childId = `${dropdownId}_${option.name}`;
// @ts-ignore
const has = !singleSelect ? props.currentOptionValues.includes(option) : false;
return (
<button style={{background: has ? dropdownSelectedColor : "none"}} id={childId}
onClick={(event) => {
// console.log(event.target) // html element
// console.log(event.target) // html element
if (singleSelect) {
devLog(`Single Select Option Value ${option.name}`)
props.handleSetOptionValues([getProjectOptionValue(option)])
Expand All @@ -141,6 +156,7 @@ export const SearchableDropdown = (props: {
</VStack>

</div>

</div>

function handleBaseKeyboard(event: KeyboardEvent) {
Expand Down

0 comments on commit a2e79ca

Please sign in to comment.