Mélanie  Faria

Mélanie Faria

1656666129

Criar Uma Paleta De Comandos Com Tailwind CSS E Headless UI

Como desenvolvedores, muitas vezes nos esforçamos para otimizar nossos fluxos de trabalho o máximo possível, economizando tempo aproveitando ferramentas como o terminal. Uma paleta de comandos é uma dessas ferramentas que exibe a atividade recente em um aplicativo da Web ou de desktop, permitindo navegação rápida, acesso fácil a comandos e atalhos, entre outras coisas.

Para elevar seu nível de produtividade, uma paleta de comandos é essencialmente um componente de interface do usuário que assume a forma de um modal. Uma paleta de comandos é especialmente útil em aplicativos grandes e complexos com muitas partes móveis, por exemplo, onde você pode precisar de vários cliques ou percorrer várias listas suspensas para acessar um recurso.

Neste tutorial, exploraremos como criar uma paleta de comandos totalmente funcional do zero usando o componente Headless UI Combobox e o Tailwind CSS.

Casos de uso do mundo real para uma paleta de comandos

Como desenvolvedor, há uma grande chance de você ter usado uma paleta de comandos antes. A mais popular é a paleta de comandos do VS Code , mas há muitos outros exemplos, incluindo a paleta de comandos do GitHub, Linear, Figma, Slack, monkeytype e muito mais.

O aplicativo GitHub

O GitHub lançou recentemente um recurso de paleta de comandos que ainda está em versão beta pública no momento da escrita. Ele permite que você pule rapidamente para diferentes páginas, pesquise comandos e obtenha sugestões com base no seu contexto atual. Você também pode restringir o escopo dos recursos que está procurando acessando uma das opções ou usando um caractere especial:

Paleta de comandos do Github

O aplicativo Linear

Se você não estiver familiarizado com o Linear , é uma ferramenta de gerenciamento de projetos semelhante ao Jira e Asana que oferece uma ótima experiência ao usuário. Linear tem uma paleta de comandos muito intuitiva que permite acessar toda a funcionalidade do aplicativo com seu design de teclado. Neste tutorial, construiremos uma paleta de comandos semelhante a Linear:

Paleta de comandos do aplicativo linear

Recursos essenciais de uma paleta de comandos

Vários aplicativos modernos estão implementando paletas de comandos como um recurso, mas o que faz um bom componente de paleta de comandos? Aqui está uma lista concisa de coisas a serem observadas:

  • Um atalho simples para abrir a paleta, ou seja,ctrl + k
  • Pode ser acessado de qualquer lugar no aplicativo
  • Possui recursos de pesquisa abrangentes, como pesquisa difusa
  • Os comandos comunicam a intenção e são fáceis de entender
  • Ele fornece acesso a todas as partes do aplicativo de um só lugar

Na próxima seção, construiremos nosso próprio componente que inclui todos os recursos listados acima. Vamos entrar nisso!

 

Construindo o componente

A paleta de comandos não é tão complexa quanto parece, e qualquer um pode construir uma rapidamente. Eu preparei um projeto inicial para este tutorial para que você possa acompanhar facilmente. O projeto inicial é um SPA React e Vite que replica a página de problemas lineares.

Configurando o projeto

Para começar, clone o repositório em seu diretório local, instale as dependências necessárias e inicie o servidor de desenvolvimento. O projeto usa Yarn, mas se você estiver mais confortável com npm ou pnPm, você pode excluir o yarn.lockarquivo antes de executar npm installou pnpm install:

// clone repository
$ git clone https://github.com/Mayowa-Ojo/command-palette
// switch to the 'starter-project' branch
$ git checkout starter-project
// install dependencies
$ yarn
// start dev server
$ yarn dev

Se você visitar localhost:3000, verá a seguinte página:

Clone do repositório Github

O CommandPalettecomponente

Em seguida, vamos construir o componente. Usaremos a interface do usuário comboboxe os dialogcomponentes Headless. comboboxserá o componente base para nossa paleta de comandos. Possui recursos integrados, como gerenciamento de foco e interação com o teclado. Usaremos o dialogcomponente para renderizar nossa paleta de comandos em um modal.

Para estilizar os componentes, usaremos Tailwind CSS. Tailwind é uma biblioteca de utilitários CSS que permite adicionar facilmente estilos embutidos em seus arquivos HTML ou JSX. O projeto inicial já inclui a configuração do Tailwind.

Instale as dependências necessárias da seguinte forma:

$ yarn add @headlessui/react @heroicons/react

Na componentspasta, crie um CommandPalette.jsxarquivo e adicione o seguinte bloco de código:

import { Dialog, Combobox } from "@headlessui/react";

export const CommandPalette = ({ commands }) => {
  const [isOpen, setIsOpen] = useState(true);

  return (
    <Dialog
      open={isOpen}
      onClose={setIsOpen}
      className="fixed inset-0 p-4 pt-[15vh] overflow-y-auto"
    >
      <Dialog.Overlay className="fixed inset-0 backdrop-blur-[1px]" />
      <Combobox
         as="div"
         className="bg-accent-dark max-w-2xl mx-auto rounded-lg shadow-2xl relative flex flex-col"
         onChange={(command) => {
            // we have access to the selected command
            // a redirect can happen here or any action can be executed
            setIsOpen(false);
         }}
      >
         <div className="mx-4 mt-4 px-2 h-[25px] text-xs text-slate-100 bg-primary/30 rounded self-start flex items-center flex-shrink-0">
            Issue
         </div>
         <div className="flex items-center text-lg font-medium border-b border-slate-500">
            <Combobox.Input
               className="p-5 text-white placeholder-gray-200 w-full bg-transparent border-0 outline-none"
               placeholder="Type a command or search..."
            />
         </div>
         <Combobox.Options
            className="max-h-72 overflow-y-auto flex flex-col"
            static
         ></Combobox.Options>
      </Combobox>
   </Dialog>
  );
};

Algumas coisas estão acontecendo aqui. Primeiro, importamos os componentes Dialoge Combobox. Dialogé renderizado como um wrapper ao redor do Combobox, e inicializamos um estado local chamado isOpenpara controlar o modal.

Renderizamos um Dialog.Overlaydentro do Dialogcomponente para servir como sobreposição para o modal. Você pode estilizar isso como quiser, mas aqui, estamos apenas usando backdrop-blur. Em seguida, renderizamos o Comboboxcomponente e passamos uma função de manipulador para o onChangeprop. Esse manipulador é chamado sempre que um item é selecionado no arquivo Combobox. Normalmente, você deseja navegar para uma página ou executar uma ação aqui, mas, por enquanto, apenas fechamos o arquivo Dialog.

Combobox.Inputlidará com a funcionalidade de pesquisa, que adicionaremos posteriormente nesta seção. Combobox.Optionsrenderiza um ulelemento que envolve a lista de resultados que renderizaremos. Passamos uma staticprop que indica que queremos ignorar o estado gerenciado internamente do componente.

Em seguida, renderizamos nosso CommandPaletteno App.jsxarquivo:

const App = () => {
   return (
      <div className="flex w-full bg-primary h-screen max-h-screen min-h-screen overflow-hidden">
         <Drawer teams={teams} />
         <AllIssues issues={issues} />
         <CommandPalette commands={commands}/>
      </div>
   );
};

Vamos falar sobre como nossa paleta de comandos funcionará. Temos uma lista de comandos predefinidos no data/seed.jsonarquivo. Esses comandos serão exibidos na paleta quando ela for aberta e podem ser filtradas com base na consulta de pesquisa. Bem simples, certo?

O CommandGroupcomponente

CommandPaletterecebe um commandsprop, que é a lista de comandos que importamos do seed.json. Agora, crie um CommandGroup.jsxarquivo na componentspasta e adicione o seguinte código:

// CommandGroup.jsx
import React from "react";
import clsx from "clsx";
import { Combobox } from "@headlessui/react";
import { PlusIcon, ArrowSmRightIcon } from "@heroicons/react/solid";
import {
   CogIcon,
   UserCircleIcon,
   FastForwardIcon,
} from "@heroicons/react/outline";
import { ProjectIcon } from "../icons/ProjectIcon";
import { ViewsIcon } from "../icons/ViewsIcon";
import { TemplatesIcon } from "../icons/TemplatesIcon";
import { TeamIcon } from "../icons/TeamIcon";

export const CommandGroup = ({ commands, group }) => {
   return (
      <React.Fragment>
         {/* only show the header when there are commands belonging to this group */}
         {commands.filter((command) => command.group === group).length >= 1 && (
            <div className="flex items-center h-6 flex-shrink-0 bg-accent/50">
               <span className="text-xs text-slate-100 px-3.5">{group}</span>
            </div>
         )}
         {commands
            .filter((command) => command.group === group)
            .map((command, idx) => (
               <Combobox.Option key={idx} value={command}>
                  {({ active }) => (
                     <div
                        className={clsx(
                           "w-full h-[46px] text-white flex items-center hover:bg-primary/40 cursor-default transition-colors duration-100 ease-in",
                           active ? "bg-primary/40" : ""
                        )}
                     >
                        <div className="px-3.5 flex items-center w-full">
                           <div className="mr-3 flex items-center justify-center w-4">
                              {mapCommandGroupToIcon(
                                 command.group.toLowerCase()
                              )}
                           </div>
                           <span className="text-sm text-left flex flex-auto">
                              {command.name}
                           </span>
                           <span className="text-[10px]">{command.shortcut}</span>
                        </div>
                     </div>
                  )}
               </Combobox.Option>
            ))}
      </React.Fragment>
   );
};

Estamos simplesmente usando o CommandGroupcomponente para evitar algum código repetitivo. Se você observar a paleta de comandos Linear, verá que os comandos são agrupados com base no contexto. Para implementar isso, precisamos filtrar os comandos que pertencem ao mesmo grupo e repetir essa lógica para cada grupo.

O CommandGroupcomponente recebe dois props, commandse group. Filtraremos os comandos com base no grupo atual e os renderizaremos usando o Combobox.Optioncomponente. Usando adereços de renderização, podemos obter o activeitem e estilizá-lo de acordo, permitindo renderizar CommandGrouppara cada grupo CommandPaletteenquanto mantemos o código limpo.

Observe que temos uma mapCommandGroupToIconfunção em algum lugar no bloco de código acima. Isso ocorre porque cada grupo possui um ícone diferente, e a função é apenas um auxiliar para renderizar o ícone correto para o grupo atual. Agora, adicione a função logo abaixo do CommandGroupcomponente no mesmo arquivo:

const mapCommandGroupToIcon = (group) => {
   switch (group) {
      case "issue":
         return <PlusIcon className="w-4 h-4 text-white"/>;
      case "project":

Agora, precisamos renderizar o CommandGroupcomponente em CommandPalette.
Importe o componente da seguinte forma:

import { CommandGroup } from "./CommandGroup";

Renderize-o dentro do Combobox.Optionspara cada grupo:

<Combobox.Options
   className="max-h-72 overflow-y-auto flex flex-col"
   static
>
   <CommandGroup commands={commands} group="Issue"/>
   <CommandGroup commands={commands} group="Project"/>
   <CommandGroup commands={commands} group="Views"/>
   <CommandGroup commands={commands} group="Team"/>
   <CommandGroup commands={commands} group="Templates"/>
   <CommandGroup commands={commands} group="Navigation"/>
   <CommandGroup commands={commands} group="Settings"/>
   <CommandGroup commands={commands} group="Account"/>
</Combobox.Options>

Você deve ver a lista de comandos sendo renderizados agora. A próxima etapa é conectar a funcionalidade de pesquisa.

Implementando a funcionalidade de pesquisa

Crie uma variável de estado local em CommandPalette.jsx:

// CommandPalette.jsx
const [query, setQuery] = useState("");

Passe o manipulador de atualização de estado para o onChangeprop em Combobox.Input. O queryserá atualizado com cada caractere que você digitar na caixa de entrada:

<Combobox.Input
  className="p-5 text-white placeholder-gray-200 w-full bg-transparent border-0 outline-none"
  placeholder="Type a command or search..."
  onChange={(e) => setQuery(e.target.value)}
/>

Uma das principais propriedades de uma boa paleta de comandos é a ampla funcionalidade de pesquisa. Podemos apenas fazer uma simples comparação de strings da consulta de pesquisa com os comandos, no entanto, isso não levaria em conta erros de digitação e contexto. Uma solução muito melhor que não introduz muita complexidade é uma pesquisa difusa.

Usaremos a biblioteca Fuse.js para isso. Fuse.js é uma biblioteca de pesquisa poderosa, leve e difusa com zero dependências. Se você não estiver familiarizado com a pesquisa difusa, é uma técnica de correspondência de strings que favorece a correspondência aproximada à correspondência exata, o que implica que você pode obter sugestões corretas mesmo que a consulta tenha erros de digitação ou ortografia.

Primeiro, instale a biblioteca Fuse.js:

$ yarn add fuse.js

Em CommandPalette.jsx, instancie a Fuseclasse com uma lista de comandos:

// CommandPalette.jsx
const fuse = new Fuse(commands, { includeScore: true, keys: ["name"] });

A Fuseclasse aceita uma série de comandos e opções de configuração. O keyscampo é onde registramos quais campos estão na lista de comandos a serem indexados pelo Fuse.js. Agora, crie uma função que tratará da pesquisa e retornará os resultados filtrados:

// CommandPalette.jsx
const filteredCommands =
  query === ""
     ? commands
     : fuse.search(query).map((res) => ({ ...res.item }));

Verificamos se o queryestá vazio, retornamos todos os comandos e, se não estiver, executamos o fuse.searchmétodo com a consulta. Além disso, estamos mapeando os resultados para criar um novo objeto. Isso é para manter a consistência porque os resultados retornados pelo Fuse.js têm alguns campos novos e não corresponderão à estrutura que já temos.

Agora, passe o filteredCommandspara o commandsprop em cada CommandGroupcomponente. Deve ficar como o código abaixo:

// CommandPalette.jsx
<CommandGroup commands={filteredCommands} group="Issue"/>
<CommandGroup commands={filteredCommands} group="Project"/>
<CommandGroup commands={filteredCommands} group="Views"/>
<CommandGroup commands={filteredCommands} group="Team"/>
<CommandGroup commands={filteredCommands} group="Templates"/>
<CommandGroup commands={filteredCommands} group="Navigation"/>
<CommandGroup commands={filteredCommands} group="Settings"/>
<CommandGroup commands={filteredCommands} group="Account"/>

Tente pesquisar na paleta de comandos e veja se os resultados estão sendo filtrados:

Filtro de pesquisa da paleta de comandos

Temos uma paleta de comandos totalmente funcional, mas você pode notar que ela está sempre aberta. Precisamos ser capazes de controlar seu estado aberto. Vamos definir um evento de teclado que escutará uma combinação de teclas e atualizará o estado aberto. Adicione o seguinte código a CommandPalette.jsx:

// CommandPalette.jsx
useEffect(() => {
  const onKeydown = (e) => {
     if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        setIsOpen(true);
     }
  };
  window.addEventListener("keydown", onKeydown);
  return () => {
     window.removeEventListener("keydown", onKeydown);
  };
}, []);

Estamos usando um useEffectHook para registrar um keydownevento de teclado quando o componente é montado e usamos uma função de limpeza para remover o ouvinte quando o componente é desmontado.

No Hook, verificamos se a combinação de teclas corresponde a ctrl + k. Se isso acontecer, o estado aberto será definido como true. Você também pode usar uma combinação de teclas diferente, mas é importante não usar combinações que colidam com os atalhos do navegador nativo.

É isso! Você pode encontrar a versão finalizada deste projeto na ramificação do projeto finalizado .

react-command-palette: componente pré-construído

Exploramos como construir um componente de paleta de comandos do zero. No entanto, você provavelmente preferiria não criar a sua própria sempre que precisar de uma paleta de comandos. É aí que um componente pré-construído pode ser útil. A maioria das bibliotecas de componentes não oferece uma paleta de comandos, mas react-command-palette é um componente bem escrito que é acessível e compatível com navegadores.

Para usar este componente, instale-o como uma dependência em seu projeto:

$ yarn add react-command-palette

Importe o componente e passe sua lista de comandos para ele da seguinte forma:

import React from "react";
import CommandPalette from 'react-command-palette';

const commands = [{
  name: "Foo",
  command() {}
},{
  name: "Bar",
  command() {}
}]

export default function App() {
  return (
    <div>
      <CommandPalette commands={commands} />
    </div>
  );
}

Há muitas opções de configuração que você pode usar para personalizar a aparência e o comportamento para atender aos seus requisitos. Por exemplo, a themeconfiguração permite que você escolha entre vários temas integrados ou crie seu próprio tema personalizado.

Próximos passos

Neste artigo, você aprendeu sobre paletas de comandos, os casos de uso ideais para elas e quais recursos compõem uma boa paleta de comandos. Você também explorou em etapas detalhadas como criar um usando o componente de caixa de combinação Headless UI e Tailwind CSS.

Se você deseja apenas enviar rapidamente esse recurso em seu aplicativo, um componente pré-construído como react-command-palette é o caminho a percorrer. Obrigado por ler, e não se esqueça de deixar um comentário se você tiver alguma dúvida.

Fonte: https://blog.logrocket.com/react-command-palette-tailwind-css-headless-ui/

 #tailwindcss #headless #react

What is GEEK

Buddha Community

Criar Uma Paleta De Comandos Com Tailwind CSS E  Headless UI
Mélanie  Faria

Mélanie Faria

1656666129

Criar Uma Paleta De Comandos Com Tailwind CSS E Headless UI

Como desenvolvedores, muitas vezes nos esforçamos para otimizar nossos fluxos de trabalho o máximo possível, economizando tempo aproveitando ferramentas como o terminal. Uma paleta de comandos é uma dessas ferramentas que exibe a atividade recente em um aplicativo da Web ou de desktop, permitindo navegação rápida, acesso fácil a comandos e atalhos, entre outras coisas.

Para elevar seu nível de produtividade, uma paleta de comandos é essencialmente um componente de interface do usuário que assume a forma de um modal. Uma paleta de comandos é especialmente útil em aplicativos grandes e complexos com muitas partes móveis, por exemplo, onde você pode precisar de vários cliques ou percorrer várias listas suspensas para acessar um recurso.

Neste tutorial, exploraremos como criar uma paleta de comandos totalmente funcional do zero usando o componente Headless UI Combobox e o Tailwind CSS.

Casos de uso do mundo real para uma paleta de comandos

Como desenvolvedor, há uma grande chance de você ter usado uma paleta de comandos antes. A mais popular é a paleta de comandos do VS Code , mas há muitos outros exemplos, incluindo a paleta de comandos do GitHub, Linear, Figma, Slack, monkeytype e muito mais.

O aplicativo GitHub

O GitHub lançou recentemente um recurso de paleta de comandos que ainda está em versão beta pública no momento da escrita. Ele permite que você pule rapidamente para diferentes páginas, pesquise comandos e obtenha sugestões com base no seu contexto atual. Você também pode restringir o escopo dos recursos que está procurando acessando uma das opções ou usando um caractere especial:

Paleta de comandos do Github

O aplicativo Linear

Se você não estiver familiarizado com o Linear , é uma ferramenta de gerenciamento de projetos semelhante ao Jira e Asana que oferece uma ótima experiência ao usuário. Linear tem uma paleta de comandos muito intuitiva que permite acessar toda a funcionalidade do aplicativo com seu design de teclado. Neste tutorial, construiremos uma paleta de comandos semelhante a Linear:

Paleta de comandos do aplicativo linear

Recursos essenciais de uma paleta de comandos

Vários aplicativos modernos estão implementando paletas de comandos como um recurso, mas o que faz um bom componente de paleta de comandos? Aqui está uma lista concisa de coisas a serem observadas:

  • Um atalho simples para abrir a paleta, ou seja,ctrl + k
  • Pode ser acessado de qualquer lugar no aplicativo
  • Possui recursos de pesquisa abrangentes, como pesquisa difusa
  • Os comandos comunicam a intenção e são fáceis de entender
  • Ele fornece acesso a todas as partes do aplicativo de um só lugar

Na próxima seção, construiremos nosso próprio componente que inclui todos os recursos listados acima. Vamos entrar nisso!

 

Construindo o componente

A paleta de comandos não é tão complexa quanto parece, e qualquer um pode construir uma rapidamente. Eu preparei um projeto inicial para este tutorial para que você possa acompanhar facilmente. O projeto inicial é um SPA React e Vite que replica a página de problemas lineares.

Configurando o projeto

Para começar, clone o repositório em seu diretório local, instale as dependências necessárias e inicie o servidor de desenvolvimento. O projeto usa Yarn, mas se você estiver mais confortável com npm ou pnPm, você pode excluir o yarn.lockarquivo antes de executar npm installou pnpm install:

// clone repository
$ git clone https://github.com/Mayowa-Ojo/command-palette
// switch to the 'starter-project' branch
$ git checkout starter-project
// install dependencies
$ yarn
// start dev server
$ yarn dev

Se você visitar localhost:3000, verá a seguinte página:

Clone do repositório Github

O CommandPalettecomponente

Em seguida, vamos construir o componente. Usaremos a interface do usuário comboboxe os dialogcomponentes Headless. comboboxserá o componente base para nossa paleta de comandos. Possui recursos integrados, como gerenciamento de foco e interação com o teclado. Usaremos o dialogcomponente para renderizar nossa paleta de comandos em um modal.

Para estilizar os componentes, usaremos Tailwind CSS. Tailwind é uma biblioteca de utilitários CSS que permite adicionar facilmente estilos embutidos em seus arquivos HTML ou JSX. O projeto inicial já inclui a configuração do Tailwind.

Instale as dependências necessárias da seguinte forma:

$ yarn add @headlessui/react @heroicons/react

Na componentspasta, crie um CommandPalette.jsxarquivo e adicione o seguinte bloco de código:

import { Dialog, Combobox } from "@headlessui/react";

export const CommandPalette = ({ commands }) => {
  const [isOpen, setIsOpen] = useState(true);

  return (
    <Dialog
      open={isOpen}
      onClose={setIsOpen}
      className="fixed inset-0 p-4 pt-[15vh] overflow-y-auto"
    >
      <Dialog.Overlay className="fixed inset-0 backdrop-blur-[1px]" />
      <Combobox
         as="div"
         className="bg-accent-dark max-w-2xl mx-auto rounded-lg shadow-2xl relative flex flex-col"
         onChange={(command) => {
            // we have access to the selected command
            // a redirect can happen here or any action can be executed
            setIsOpen(false);
         }}
      >
         <div className="mx-4 mt-4 px-2 h-[25px] text-xs text-slate-100 bg-primary/30 rounded self-start flex items-center flex-shrink-0">
            Issue
         </div>
         <div className="flex items-center text-lg font-medium border-b border-slate-500">
            <Combobox.Input
               className="p-5 text-white placeholder-gray-200 w-full bg-transparent border-0 outline-none"
               placeholder="Type a command or search..."
            />
         </div>
         <Combobox.Options
            className="max-h-72 overflow-y-auto flex flex-col"
            static
         ></Combobox.Options>
      </Combobox>
   </Dialog>
  );
};

Algumas coisas estão acontecendo aqui. Primeiro, importamos os componentes Dialoge Combobox. Dialogé renderizado como um wrapper ao redor do Combobox, e inicializamos um estado local chamado isOpenpara controlar o modal.

Renderizamos um Dialog.Overlaydentro do Dialogcomponente para servir como sobreposição para o modal. Você pode estilizar isso como quiser, mas aqui, estamos apenas usando backdrop-blur. Em seguida, renderizamos o Comboboxcomponente e passamos uma função de manipulador para o onChangeprop. Esse manipulador é chamado sempre que um item é selecionado no arquivo Combobox. Normalmente, você deseja navegar para uma página ou executar uma ação aqui, mas, por enquanto, apenas fechamos o arquivo Dialog.

Combobox.Inputlidará com a funcionalidade de pesquisa, que adicionaremos posteriormente nesta seção. Combobox.Optionsrenderiza um ulelemento que envolve a lista de resultados que renderizaremos. Passamos uma staticprop que indica que queremos ignorar o estado gerenciado internamente do componente.

Em seguida, renderizamos nosso CommandPaletteno App.jsxarquivo:

const App = () => {
   return (
      <div className="flex w-full bg-primary h-screen max-h-screen min-h-screen overflow-hidden">
         <Drawer teams={teams} />
         <AllIssues issues={issues} />
         <CommandPalette commands={commands}/>
      </div>
   );
};

Vamos falar sobre como nossa paleta de comandos funcionará. Temos uma lista de comandos predefinidos no data/seed.jsonarquivo. Esses comandos serão exibidos na paleta quando ela for aberta e podem ser filtradas com base na consulta de pesquisa. Bem simples, certo?

O CommandGroupcomponente

CommandPaletterecebe um commandsprop, que é a lista de comandos que importamos do seed.json. Agora, crie um CommandGroup.jsxarquivo na componentspasta e adicione o seguinte código:

// CommandGroup.jsx
import React from "react";
import clsx from "clsx";
import { Combobox } from "@headlessui/react";
import { PlusIcon, ArrowSmRightIcon } from "@heroicons/react/solid";
import {
   CogIcon,
   UserCircleIcon,
   FastForwardIcon,
} from "@heroicons/react/outline";
import { ProjectIcon } from "../icons/ProjectIcon";
import { ViewsIcon } from "../icons/ViewsIcon";
import { TemplatesIcon } from "../icons/TemplatesIcon";
import { TeamIcon } from "../icons/TeamIcon";

export const CommandGroup = ({ commands, group }) => {
   return (
      <React.Fragment>
         {/* only show the header when there are commands belonging to this group */}
         {commands.filter((command) => command.group === group).length >= 1 && (
            <div className="flex items-center h-6 flex-shrink-0 bg-accent/50">
               <span className="text-xs text-slate-100 px-3.5">{group}</span>
            </div>
         )}
         {commands
            .filter((command) => command.group === group)
            .map((command, idx) => (
               <Combobox.Option key={idx} value={command}>
                  {({ active }) => (
                     <div
                        className={clsx(
                           "w-full h-[46px] text-white flex items-center hover:bg-primary/40 cursor-default transition-colors duration-100 ease-in",
                           active ? "bg-primary/40" : ""
                        )}
                     >
                        <div className="px-3.5 flex items-center w-full">
                           <div className="mr-3 flex items-center justify-center w-4">
                              {mapCommandGroupToIcon(
                                 command.group.toLowerCase()
                              )}
                           </div>
                           <span className="text-sm text-left flex flex-auto">
                              {command.name}
                           </span>
                           <span className="text-[10px]">{command.shortcut}</span>
                        </div>
                     </div>
                  )}
               </Combobox.Option>
            ))}
      </React.Fragment>
   );
};

Estamos simplesmente usando o CommandGroupcomponente para evitar algum código repetitivo. Se você observar a paleta de comandos Linear, verá que os comandos são agrupados com base no contexto. Para implementar isso, precisamos filtrar os comandos que pertencem ao mesmo grupo e repetir essa lógica para cada grupo.

O CommandGroupcomponente recebe dois props, commandse group. Filtraremos os comandos com base no grupo atual e os renderizaremos usando o Combobox.Optioncomponente. Usando adereços de renderização, podemos obter o activeitem e estilizá-lo de acordo, permitindo renderizar CommandGrouppara cada grupo CommandPaletteenquanto mantemos o código limpo.

Observe que temos uma mapCommandGroupToIconfunção em algum lugar no bloco de código acima. Isso ocorre porque cada grupo possui um ícone diferente, e a função é apenas um auxiliar para renderizar o ícone correto para o grupo atual. Agora, adicione a função logo abaixo do CommandGroupcomponente no mesmo arquivo:

const mapCommandGroupToIcon = (group) => {
   switch (group) {
      case "issue":
         return <PlusIcon className="w-4 h-4 text-white"/>;
      case "project":

Agora, precisamos renderizar o CommandGroupcomponente em CommandPalette.
Importe o componente da seguinte forma:

import { CommandGroup } from "./CommandGroup";

Renderize-o dentro do Combobox.Optionspara cada grupo:

<Combobox.Options
   className="max-h-72 overflow-y-auto flex flex-col"
   static
>
   <CommandGroup commands={commands} group="Issue"/>
   <CommandGroup commands={commands} group="Project"/>
   <CommandGroup commands={commands} group="Views"/>
   <CommandGroup commands={commands} group="Team"/>
   <CommandGroup commands={commands} group="Templates"/>
   <CommandGroup commands={commands} group="Navigation"/>
   <CommandGroup commands={commands} group="Settings"/>
   <CommandGroup commands={commands} group="Account"/>
</Combobox.Options>

Você deve ver a lista de comandos sendo renderizados agora. A próxima etapa é conectar a funcionalidade de pesquisa.

Implementando a funcionalidade de pesquisa

Crie uma variável de estado local em CommandPalette.jsx:

// CommandPalette.jsx
const [query, setQuery] = useState("");

Passe o manipulador de atualização de estado para o onChangeprop em Combobox.Input. O queryserá atualizado com cada caractere que você digitar na caixa de entrada:

<Combobox.Input
  className="p-5 text-white placeholder-gray-200 w-full bg-transparent border-0 outline-none"
  placeholder="Type a command or search..."
  onChange={(e) => setQuery(e.target.value)}
/>

Uma das principais propriedades de uma boa paleta de comandos é a ampla funcionalidade de pesquisa. Podemos apenas fazer uma simples comparação de strings da consulta de pesquisa com os comandos, no entanto, isso não levaria em conta erros de digitação e contexto. Uma solução muito melhor que não introduz muita complexidade é uma pesquisa difusa.

Usaremos a biblioteca Fuse.js para isso. Fuse.js é uma biblioteca de pesquisa poderosa, leve e difusa com zero dependências. Se você não estiver familiarizado com a pesquisa difusa, é uma técnica de correspondência de strings que favorece a correspondência aproximada à correspondência exata, o que implica que você pode obter sugestões corretas mesmo que a consulta tenha erros de digitação ou ortografia.

Primeiro, instale a biblioteca Fuse.js:

$ yarn add fuse.js

Em CommandPalette.jsx, instancie a Fuseclasse com uma lista de comandos:

// CommandPalette.jsx
const fuse = new Fuse(commands, { includeScore: true, keys: ["name"] });

A Fuseclasse aceita uma série de comandos e opções de configuração. O keyscampo é onde registramos quais campos estão na lista de comandos a serem indexados pelo Fuse.js. Agora, crie uma função que tratará da pesquisa e retornará os resultados filtrados:

// CommandPalette.jsx
const filteredCommands =
  query === ""
     ? commands
     : fuse.search(query).map((res) => ({ ...res.item }));

Verificamos se o queryestá vazio, retornamos todos os comandos e, se não estiver, executamos o fuse.searchmétodo com a consulta. Além disso, estamos mapeando os resultados para criar um novo objeto. Isso é para manter a consistência porque os resultados retornados pelo Fuse.js têm alguns campos novos e não corresponderão à estrutura que já temos.

Agora, passe o filteredCommandspara o commandsprop em cada CommandGroupcomponente. Deve ficar como o código abaixo:

// CommandPalette.jsx
<CommandGroup commands={filteredCommands} group="Issue"/>
<CommandGroup commands={filteredCommands} group="Project"/>
<CommandGroup commands={filteredCommands} group="Views"/>
<CommandGroup commands={filteredCommands} group="Team"/>
<CommandGroup commands={filteredCommands} group="Templates"/>
<CommandGroup commands={filteredCommands} group="Navigation"/>
<CommandGroup commands={filteredCommands} group="Settings"/>
<CommandGroup commands={filteredCommands} group="Account"/>

Tente pesquisar na paleta de comandos e veja se os resultados estão sendo filtrados:

Filtro de pesquisa da paleta de comandos

Temos uma paleta de comandos totalmente funcional, mas você pode notar que ela está sempre aberta. Precisamos ser capazes de controlar seu estado aberto. Vamos definir um evento de teclado que escutará uma combinação de teclas e atualizará o estado aberto. Adicione o seguinte código a CommandPalette.jsx:

// CommandPalette.jsx
useEffect(() => {
  const onKeydown = (e) => {
     if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
        e.preventDefault();
        setIsOpen(true);
     }
  };
  window.addEventListener("keydown", onKeydown);
  return () => {
     window.removeEventListener("keydown", onKeydown);
  };
}, []);

Estamos usando um useEffectHook para registrar um keydownevento de teclado quando o componente é montado e usamos uma função de limpeza para remover o ouvinte quando o componente é desmontado.

No Hook, verificamos se a combinação de teclas corresponde a ctrl + k. Se isso acontecer, o estado aberto será definido como true. Você também pode usar uma combinação de teclas diferente, mas é importante não usar combinações que colidam com os atalhos do navegador nativo.

É isso! Você pode encontrar a versão finalizada deste projeto na ramificação do projeto finalizado .

react-command-palette: componente pré-construído

Exploramos como construir um componente de paleta de comandos do zero. No entanto, você provavelmente preferiria não criar a sua própria sempre que precisar de uma paleta de comandos. É aí que um componente pré-construído pode ser útil. A maioria das bibliotecas de componentes não oferece uma paleta de comandos, mas react-command-palette é um componente bem escrito que é acessível e compatível com navegadores.

Para usar este componente, instale-o como uma dependência em seu projeto:

$ yarn add react-command-palette

Importe o componente e passe sua lista de comandos para ele da seguinte forma:

import React from "react";
import CommandPalette from 'react-command-palette';

const commands = [{
  name: "Foo",
  command() {}
},{
  name: "Bar",
  command() {}
}]

export default function App() {
  return (
    <div>
      <CommandPalette commands={commands} />
    </div>
  );
}

Há muitas opções de configuração que você pode usar para personalizar a aparência e o comportamento para atender aos seus requisitos. Por exemplo, a themeconfiguração permite que você escolha entre vários temas integrados ou crie seu próprio tema personalizado.

Próximos passos

Neste artigo, você aprendeu sobre paletas de comandos, os casos de uso ideais para elas e quais recursos compõem uma boa paleta de comandos. Você também explorou em etapas detalhadas como criar um usando o componente de caixa de combinação Headless UI e Tailwind CSS.

Se você deseja apenas enviar rapidamente esse recurso em seu aplicativo, um componente pré-construído como react-command-palette é o caminho a percorrer. Obrigado por ler, e não se esqueça de deixar um comentário se você tiver alguma dúvida.

Fonte: https://blog.logrocket.com/react-command-palette-tailwind-css-headless-ui/

 #tailwindcss #headless #react

Reggie  Hudson

Reggie Hudson

1627084440

Learn Headless UI with React: Listbox Component (Tailwind CSS)

Using Headless UI in React, we easily create a react ready listbox component and then incorporate the Headless UI Transition component as well.

Headless UI Documentation: https://github.com/tailwindlabs/headlessui/blob/develop/packages/%40headlessui-react/README.md

📚 Library(s) needed:
npm install tailwindcss
npm install @headlessui/react

🖥️ Source code: https://devascend.com/github?link=https://github.com/DevAscend/YT-HeadlessUI-React-Tutorials

💡 Have a video request?
Suggest it in the Dev Ascend Discord community server or leave it in the comments below!

🕐 Timestamps:
00:00 Introduction
00:34 Creating the Listbox component
05:45 Incorporating the Transition component

#headless #ui #react

#headless #ui #react #tailwind css #css

Como criar uma barra de navegação fixa com Tailwind CSS

Neste tutorial Tailwind CSS, você aprenderá como criar uma barra de navegação fixa com Tailwind CSS e como personalizá-la.

Você deseja utilizar o Tailwind CSS para criar uma barra de navegação fixa que fica no topo da sua página quando você rola? Neste post, vamos criar isso e também cuidar da capacidade de resposta para dispositivos móveis!

Nas últimas semanas, comecei a mexer com Tailwind CSS pela primeira vez, e devo dizer que estou adorando até agora. Um post que criei há um tempo atrás foi como criar uma barra de navegação pegajosa e responsiva com CSS básico (você pode encontrá-lo aqui ). Assim achei que seria legal criar algo parecido com vento de cauda e ver as diferenças!

Então vamos começar a criar a barra de navegação que você pode ver no gif a seguir:

Barra de navegação corrigida com Tailwind CSS: visualização

Preparação do projeto

Para este projeto, usaremos vite para lançar nosso servidor de desenvolvimento e tailwind como um framework CSS. Para começar, inicializaremos um novo projeto vite e instalaremos as dependências necessárias para um ambiente de desenvolvimento fluente.

Primeiro, vamos criar um novo projeto vite executando:

npm init vite navbar-tw

Na próxima etapa, selecionamos 'vanilla' como nosso framework e pressionamos enter para continuar. A partir daí, você pode optar por usar javascript vanilla ou typescript. Usaremos javascript básico para este guia, então selecione vanilla novamente.

Agora você pode entrar no diretório e instalar os pacotes necessários:

cd navbar-tw
npm install

Com isso, instalamos o vite e temos a capacidade de ver as alterações aplicadas em tempo real após iniciarmos o servidor dev. Mas antes de fazermos isso, vamos instalar o vento de cauda. Além do tailwind, também instalaremos o postcss para facilitar o desenvolvimento:

npm install -D tailwindcss postcss autoprefixer

E então, criamos arquivos de configuração para os diferentes componentes:

npx tailwindcss init -p

Dentro do arquivo tailwind.config.js recém-criado, editaremos a parte de conteúdo da seguinte forma:

module.exports = {
  content: [
    "./index.html"
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}

A última etapa na configuração do tailwind é atualizar o arquivo style.css da seguinte maneira:

@tailwind base;
@tailwind components;
@tailwind utilities;

Agora que configuramos vite e tailwind, podemos iniciar nosso servidor de desenvolvimento com:

npm run dev

Antes de prosseguirmos e construirmos nossa barra de navegação fixa, vamos primeiro limpar alguns arquivos criados pelo vite. Remova o conteúdo em main.js e atualize o index.html da seguinte forma:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="favicon.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Tailwind Navbar</title>
    <link rel="stylesheet" href="style.css">
  </head>
  <body class="bg-gradient-to-t from-blue-900 to-blue-500 h-[200vh]">

    <script src="/main.js"></script>
  </body>
</html>

Isso cria um gradiente azul com a altura de duas vezes sua tela. Geramos este plano de fundo para visualizar o efeito de rolagem pegajosa.

Criando uma barra de navegação fixa para celular

Um princípio de construção com Tailwind CSS é que ele usa um Mobile-First Breakpoint System, o que significa que classes não prefixadas serão aplicadas em todas as telas e classes prefixadas serão aplicadas após o breakpoint definido. Portanto, criaremos primeiro o layout móvel e depois criaremos a versão desktop.

O primeiro passo para nós é definir um cabeçalho incluindo uma imagem (portanto, crie uma imagem chamada avatar.png) e um contêiner incluindo o ícone do menu e os elementos do menu (neste caso, uma div):

<header>
  <div id="img-container">
    <img src="avatar.png" alt="avatar">
  </div>

  <div id="nav-container">
    <div id="nav-icon">
      <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 20 20" fill="currentColor">
        <path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd" />
      </svg>
    </div>

    <ul id="nav-menu">
      <li>Menu Item</li>
      <li>Menu Item</li>
      <li>Menu Item</li>
    </ul>
  </div>
</header>

Agora vamos estilizar todos esses elementos com classes CSS Tailwind. Primeiro é o cabeçalho. Queremos que ele tenha uma altura específica, fique no topo e tenha um fundo branco. Então vamos criar isso.

<header class="h-16 bg-white sticky top-0">
	<!-- ... -->
</header>

Agora temos uma barra que gruda no topo, mas os outros elementos são muito grandes ou estão no lugar errado, então vamos continuar com a imagem. Queremos dentro do cabeçalho do lado esquerdo. Portanto, alteramos as classes assim:

<div id="img-container" class="absolute left-2 top-2 h-12 w-12">
    <img src="avatar.png" alt="avatar" class="h-full w-full">
</div>

E então fazemos o mesmo com o ícone da barra de navegação, logo à direita. Além disso, também queremos os itens de menu abaixo do ícone. Para isso, precisamos atualizar as classes assim:

<div id="nav-container" class="bg-white p-2 flex flex-col items-end">
  <div id="nav-icon" class="h-12 w-12 p-2 group">
    <svg xmlns="<http://www.w3.org/2000/svg>" viewBox="0 0 20 20" fill="currentColor" class="h-full w-full group-hover:fill-blue-500">
      <path fill-rule="evenodd" d="M3 5a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 10a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM3 15a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd" />
    </svg>
  </div>

  <ul id="nav-menu" class="w-full space-y-2 pr-3 font-semibold text-xl text-right">
    <li>Menu Item</li>
    <li>Menu Item</li>
    <li>Menu Item</li>
  </ul>
</div>

Uma coisa a notar é a aula em grupo. Ele nos permite colorir o elemento filho (neste caso, o SVG) ao passar o mouse sobre o elemento do grupo. Este é um método útil adicionado pelo vento de cauda!

Com essas classes em vigor, temos a base para nossa barra de navegação móvel:

Barra de navegação corrigida com Tailwind CSS: Fundação

Tudo parece bom e esperado, mas ainda não podemos abrir e fechar a navegação. Então, vamos adicionar isso na próxima etapa. Portanto, primeiro adicionamos o grupo oculto ao elemento “nav-menu” (fazendo isso, ocultaremos os itens do menu).

<ul id="nav-menu" class="hidden w-full space-y-2 pr-3 font-semibold text-xl text-right">
	...
</ul>

Para mostrá-lo novamente, vamos criar uma função que é executada quando clicamos no ícone de navegação.

Para isso, adicionaremos as seguintes linhas ao main.js:

document.getElementById("nav-icon").addEventListener('click',
  () => document.getElementById("nav-menu").classList.toggle("hidden")
);

Com isso no lugar, agora podemos abrir e fechar o menu de navegação. Como resultado, temos uma barra de navegação funcional para dispositivos móveis!

Criando uma barra de navegação fixa para desktops

Para tornar a barra de navegação responsiva e ter uma boa aparência em desktops, também precisamos alterar os estilos em determinados pontos de interrupção. Nas próximas etapas, precisamos atualizar o ícone de navegação e o menu de navegação.

Vamos começar ocultando o ícone de navegação porque não precisamos dele em dispositivos desktop:

<div id="nav-icon" class="h-12 w-12 p-2 group md:hidden">
  ...
</div>

E então, adicionamos as seguintes classes na área de trabalho para que os itens de menu apareçam em uma linha e com algum espaçamento:

<ul id="nav-menu" class="hidden w-full space-y-2 pr-3 font-semibold text-xl text-right md:h-12 md:flex md:flex-row md:items-center md:justify-end md:space-x-5 md:space-y-0">
	...
</ul>

Isso resulta no seguinte resultado:

Barra de navegação corrigida com Tailwind CSS: Desktop

Algumas personalizações para a barra de navegação fixa

Mover o ícone de navegação

Para mover o ícone de navegação, você precisa editar o contêiner de navegação. No contêiner, você deve alterar a classe items-[…] para se adequar ao estilo desejado. Por exemplo, você pode movê-lo para o centro assim:

<div id="nav-container" class="bg-white p-2 flex flex-col items-center md:flex-row">
	...
</div>

Resultando nisso:

Barra de navegação corrigida com Tailwind CSS: ícone de navegação centralizado

Mover os itens do menu

Você também pode mover os itens de menu atualizando a classe text-[…]. Por exemplo, você pode centralizar os itens de menu assim:

<ul id="nav-menu" class="hidden w-full space-y-2 pr-3 font-semibold text-xl text-center md:h-12 md:flex md:flex-row md:items-center md:justify-end md:space-x-5 md:space-y-0">
	...
</ul>

Resultando nisso:

Barra de navegação corrigida com Tailwind CSS: itens de menu movidos

Crie um efeito de foco para os itens de menu

Você pode adicionar um efeito de foco aos itens de menu adicionando a seguinte classe aos itens da lista:

<li class="hover:text-blue-500">Menu Item</li>

Resultando neste efeito de foco:

Barra de navegação corrigida com Tailwind CSS: efeito de foco no item de menu

Conclusão

Neste post, aprendemos como criar uma barra de navegação fixa com CSS tailwind. Além disso, para ficar no topo, também é responsivo e facilmente editável para suas preferências.

Fonte do artigo original em https://www.programonaut.com

#tailwindcss #tailwind #webdev #css

What is Tailwind CSS useful for?

Let’s talk Tailwind.css. It’s new, it’s customizable, it’s responsive, and a utility-first CSS framework, let’s try it out!

The beauty of Tailwind.css is that to use it is pretty simple. In good old basic CSS, you will declare classes and specifically code the design elements that will come from the class you’ve created(i.e. Font-family, color). In Tailwind, you also use class names, but different from classic CSS, you pull from a pre-created library of class names that you will have access to once you download Tailwind into your app. It’s a matter of understanding what class names you’ll need, and that is going to take a little searching on the Tailwind Docs Website search bar. Nothing like a handy search!

#tailwind-ui #css #web-development #tailwind-css