如何在单击时展开搜索图标并呈现我已经在代码中构建的搜索栏组件?

Posted

技术标签:

【中文标题】如何在单击时展开搜索图标并呈现我已经在代码中构建的搜索栏组件?【英文标题】:How to expand a search icon on click and render a search bar component I already built in my code? 【发布时间】:2022-01-20 07:08:47 【问题描述】:

所以基本上我拥有的是这个绿色的搜索图标,我希望能够单击它并呈现下面的搜索栏,这是我构建的一个组件。我想实现这样的目标:https://codepen.io/ahmadbassamemran/pen/rNjMXqg 但我确实很难弄清楚如何实现这一目标。样式方面,如何单击绿色图标并打开搜索栏输入组件,并通过单击搜索输入外部,搜索输入本身关闭?现在的问题是,如果我点击绿色图标,搜索栏就会出现,但是如果我点击搜索输入,搜索输入也会消失,因为我相信状态是在同一个容器上定义的。我怎样才能实现只有当我在输入之外单击并仅在输入关闭时才打开搜索栏时才会关闭输入?非常感谢您提供任何线索或提示。这是呈现绿色搜索图标的代码:

const [isExtended, setIsExtended] = useState(false); // this is for the search icon


const extendSearch = () => 
  setIsExtended(!isExtended);
;




          <Nav>
            <h2 style= fontSize: '1.2em' >Add Time</h2>
            <div style= display: 'flex'>
              <SearchIconDiv onClick=extendSearch>
                isExtended ? <SearchIcon /> : <SearchBar/>
              </SearchIconDiv>
              <div
                onClick=() => setShowModal((prev) => !prev)
                style=
                  marginLeft: '5px',
                  cursor: 'pointer',
                  display: 'flex',
                  alignItems: 'center',
                
              >
                <ClosingIcon />
              </div>
            </div>
          </Nav>

我使用了样式化组件:

  const Nav = styled.div`
  display: flex;
  align-items: center;
  justify-content: space-between;
  position: fixed;
  padding: 20px 40px 20px 40px;
  top: 0px;
  /* text-align: center; */
  width: 100%;
  background-color: white;
  border-top-left-radius: 10px;
  border-top-right-radius: 10px;
  align-items: center;
  z-index: 100;
`;

const SearchIconDiv = styled.div`
  margin-right: 5px;
  cursor: pointer;
  width: fit-content;
  height: fit-content;
  position: relative;
  display: flex;
`;

这是搜索栏的完整组件,包括样式组件:

 // react
import  useState, useEffect, useRef  from 'react';
// styled components
import styled from 'styled-components';
// framer motion
import  motion, AnimatePresence  from 'framer-motion';
// moon loader, spinner
import MoonLoader from 'react-spinners/MoonLoader';
// isOutisideClick
import  useClickOutside  from 'react-click-outside-hook';
// icons
import  iosearch, IoClose  from 'react-icons/io5';
import SearchIcon from '../../../Icons/SearchIcon';


const containerVariants = 
  expanded: 
    height: '20em',
    zIndex: '200',
  ,
  collapsed: 
    height: '3em',
  ,
;

const containerTransition =  type: 'spring', damping: 22, stiffness: 150 ;

function SearchBar(props) 
  const [isExpanded, setExpanded] = useState(false);
  const [parentRef, isClickedOutside] = useClickOutside();
  const inputRef = useRef();
  const [searchQuery, setSearchQuery] = useState('');
  const [isLoading, setLoading] = useState(false);
  const [contexts, setContexts] = useState([]);
  const [noContexts, setNoContexts] = useState(false);

  const isEmpty = !contexts || contexts.length === 0;

  const changeHandler = (e) => 
    e.preventDefault();
    if (e.target.value.trim() === '') setNoContexts(false);

    setSearchQuery(e.target.value);
  ;

  const expandContainer = () => 
    setExpanded(true);
  ;

  const collapseContainer = () => 
    setExpanded(false);
    setSearchQuery('');
    setLoading(false);
    setNoContexts(false);
    setContexts([]);
    if (inputRef.current) inputRef.current.value = '';
  ;

  useEffect(() => 
    if (isClickedOutside) collapseContainer();
  , [isClickedOutside]);


  return (
    <SearchBarContainer
      animate=isExpanded ? 'expanded' : 'collapsed'
      variants=containerVariants
      transition=containerTransition
      ref=parentRef
    >
      <SearchInputContainer>
        <SearchInput
          placeholder="Search for contexts"
          onFocus=expandContainer
          ref=inputRef
          value=searchQuery
          onChange=changeHandler
        />
        <SearchIconDiv>
          <SearchIcon />
        </SearchIconDiv>
        /* <AnimatePresence>
          isExpanded && (
            <CloseIcon
              key="close-icon"
              initial= opacity: 0 
              animate= opacity: 1 
              exit= opacity: 0 
              onClick=collapseContainer
              transition= duration: 0.2 
            >
              <IoClose />
            </CloseIcon>
          )
        </AnimatePresence> */
      </SearchInputContainer>
      isExpanded && <LineSeperator />
      isExpanded && (
        <SearchContent>
          isLoading && (
            <LoadingWrapper>
              <MoonLoader loading color="#000" size=20 />
            </LoadingWrapper>
          )
          !isLoading && isEmpty && !noContexts && (
            <LoadingWrapper>
              <WarningMessage>Start typing to Search</WarningMessage>
            </LoadingWrapper>
          )
          !isLoading && noContexts && (
            <LoadingWrapper>
              <WarningMessage>No contexts have been found</WarningMessage>
            </LoadingWrapper>
          )
          !isLoading && !isEmpty && (
            <>
              /* contexts.map(( show ) => (
                <TvShow
                  key=show.id
                  thumbanilSrc=show.image && show.image.medium
                  name=show.name
                  rating=show.rating && show.rating.average
                />
              )) */
            </>
          )
        </SearchContent>
      )
    </SearchBarContainer>
  );


const SearchBarContainer = styled(motion.div)`
  display: flex;
  flex-direction: column;
  width: 25em;
  height: 3em;
  background-color: #fff;
  border-radius: 10px;
  box-shadow: 0px 0px 11px 0px rgba(0, 0, 0, 0.1);
  z-index: 200;
`;

const SearchInputContainer = styled.div`
  width: 100%;
  min-height: 3em;
  display: flex;
  align-items: center;
  position: relative;
  padding: 2px 15px;
`;

const SearchInput = styled.input`
  width: 100%;
  height: 100%;
  outline: none;
  border: none;
  font-size: 16px;
  color: #12112e;
  font-weight: 500;
  border-radius: 6px;
  background-color: transparent;
  &:focus 
    outline: none;
    &::placeholder 
      opacity: 0;
    
  
  &::placeholder 
    color: #bebebe;
    transition: all 250ms ease-in-out;
  
`;

const SearchIconDiv = styled.span`
  color: #bebebe;
  font-size: 27px;
  margin-right: 0px;
  margin-top: 6px;
  vertical-align: middle;
`;

const CloseIcon = styled(motion.span)`
  color: #bebebe;
  font-size: 23px;
  vertical-align: middle;
  transition: all 200ms ease-in-out;
  cursor: pointer;
  &:hover 
    color: #dfdfdf;
  
`;

const LineSeperator = styled.span`
  display: flex;
  min-width: 100%;
  min-height: 2px;
  background-color: #d8d8d878;
`;

const SearchContent = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  padding: 1em;
  overflow-y: auto;
`;

const LoadingWrapper = styled.div`
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
`;

const WarningMessage = styled.span`
  color: #a1a1a1;
  font-size: 14px;
  display: flex;
  align-self: center;
  justify-self: center;
`;

export default SearchBar;

【问题讨论】:

【参考方案1】:

最简单的方法是制作 2 个单独的组件,带和不带文本输入字段,并设置一个“onClick”事件侦听器以在两者之间切换。

例子:

const [isActive, setIsActive] = useState(false);

// this is to be put into the return part

isActive? <ComponentWithInput/> : <JustTheIcon/>

而事件监听函数可能是这样的:

&lt;JustTheIcon onClick= ()=&gt; setIsActive(true) /&gt;

然后使用您选择的任何动画库为过渡设置动画

【讨论】:

谢谢 Mukul,我想过 - 但是当我尝试时,我遇到了问题:isExtended ? : 你要么处于图标存在的情况,要么你得到整个搜索栏(图标存在于其中) - 我放置了相同的图标,问题是当我在状态扩展时单击 SearchBar 输入,我无法搜索任何内容,因为我单击状态后立即返回 isExtended(false) 嗨!希望我不会太迟回复。我看到了你的代码,问题是setIsExtended(!isExtended)。我解决了这个问题,代码可以在这里看到:codesandbox.io/s/amazing-wozniak-25ht1?file=/src/App.js

以上是关于如何在单击时展开搜索图标并呈现我已经在代码中构建的搜索栏组件?的主要内容,如果未能解决你的问题,请参考以下文章

如何通过单击组件中的图标来滚动和展开手风琴?

如果搜索,则在方向更改时不会恢复工具栏中的搜索图标

滚动时列表的图标状态恢复

ReactJs:如何创建一个包装器组件,该组件在单击时会执行某些代码并呈现子组件?

如何以编程方式关闭 SearchView?

如何在后按时以编程方式关闭 SearchView?在片段中