Tailwind CSS

Tailwind CSS

Tailwind CSS - A utility-first CSS framework for rapidly building custom designs. Tailwind CSS is a highly customizable, low-level CSS framework that gives you all of the building blocks you need to build bespoke designs without any annoying opinionated styles you have to fight to override.
Meggie  Flatley

Meggie Flatley

1656741600

Vue Tail Fire: Vue 3 | Vite | Firebase 9 | Tailwind CSS.

Vue-Fire-Tail 😎

Demo Here: Vue-Fire-Tail

Try Logging with the following

Email Address: test@gmail.com
Password: test@12345

Steps

Why Vue Fire Tail

At times it get very irritating when you want to start your new project which is a firebase project in which you have to set Authentication Flow EVERYTIME. You have to add Routes, Protect your routes, create a Login & Register component, Style those component and have a nice Loading animation, Validate your login/register form. and much much more.

Here is Vue-Fire-Tail Boiler plate for you😊. Vue-Fire-Tail is powered by next-gen technologies like Vue 3 and Firebase 9 along with the power of Vite and Tailwind CSS v3.

Latest and greatest Technologies

  1. Vue 3
  2. Firebase v9
  3. Tailwind CSS v3
  4. Font Awesome
  5. Headless UI
  6. Vite

Features

  1. Email Password Authetication & Google Authetication
  2. Forgot Password Feature
  3. Login & SignUp forms validations
  4. Fallback Page (404 Page), Loaders and page transitions for smooth user experience
  5. Font Awesome Icons pre configured.

Quick Start

git clone https://github.com/sushil-kamble/vue-tail-fire.git my-project
yarn

Make sure you replace my-project with your own project name

If you don't have yarn installed

 

npm i

Remove yarn.lock as you will already have package.lock

 

  1. Go to Firebase console. Add Project. In project overview, click on the web icon and register the app. You will see firebaseConfig object, We will require its details further. Click on Authetication, then on set up sign-in method. Enable Email/password authentication & google authentication.
  2. Create a .env.local file in your project's root. Example of .env.local file is given below. Make sure you replace YOUR_FIREBASE_CONSOLE_DETAILS with your firebaseConfig object details.
VITE_APP_API_KEY=YOUR_FIREBASE_CONSOLE_DETAILS
VITE_APP_AUTH_DOMAIN=YOUR_FIREBASE_CONSOLE_DETAILS
VITE_APP_DATABASE_URL=YOUR_FIREBASE_CONSOLE_DETAILS
VITE_APP_PROJECT_ID=YOUR_FIREBASE_CONSOLE_DETAILS
VITE_APP_STORAGE_BUCKET=YOUR_FIREBASE_CONSOLE_DETAILS
VITE_APP_MESSAGING_SENDER_ID=YOUR_FIREBASE_CONSOLE_DETAILS
VITE_APP_APP_ID=YOUR_FIREBASE_CONSOLE_DETAILS
  1. That's It ! Finally run the application
yarn dev # OR npm run dev

Documentation

Pages

  • Home Page
  • Login Page
  • Sign Up Page
  • Forgot Password Page
  • 404 Page
  • Profile Page - Auth Required
  • Database Page - Auth Required

Helpers

  • Class suffixed with t- will be found /src/assets/index.css, they are basically global styles

Some basic html elements and classes are already configured check out, /src/assets/index.css

  • useAuthState in /src/firebase.js returns { user, error, isAuthenticated }
  • useSignOut in /src/firebase.js - Signs Out User
  • getUserState in /src/firebase.js - Returns promise
  • isValidEmail /src/helpers/index.js function returns true if email is valid
  • Loading in /src/components/Loading.vue - Loading spinner
  • Dialog in /src/components/Dialog.vue - Headless UI dialog

Theme

  • Font - Ubuntu (font-text) & Goldman (font-heading)
  • Primary Color - Tailwind - colors.sky["500"]
  • Secondary Color - Tailwind - colors.slate["700"]
  • Background Color - Tailwind - .bg-secondary/90
  • Error Color - Tailwind - colors.red["500"]

Font Awesome

  • Go here
  • Select any icon, suppose you want to use <i class="fas fa-address-book"></i> then import faAddressBook in /src/main.js
  • Add it to library. (See in main.js)
  • Use it like <font-awesome-icon :icon="['fas', 'address-book']" />
  • For more info visit here

Recommended IDE Setup


Author: sushil-kamble
Source code: https://github.com/sushil-kamble/vue-tail-fire
License:

#vite #vitejs #vue #vuejs #firebase #tailwindcss 

Vue Tail Fire: Vue 3 | Vite | Firebase 9 | Tailwind CSS.

Tailwind UI Templates | New Tailwind CSS Templates on Product Hunt

 Tailwind UI Templates - Beautiful Next.js templates built by the Tailwind CSS team

Visually-stunning, easy to customize site templates built with React and Next.js. Designed and built by the makers of Tailwind CSS themselves., it's the perfect starting point for your next project.

Read more: https://www.producthunt.com

#tailwindcss #tailwind #wedev #programming 

Tailwind UI Templates | New Tailwind CSS Templates on Product Hunt
Paris  Kessler

Paris Kessler

1656691200

Vitamin: React TypeScript, TailwindCSS, SPA + PWA, Cypress and CI

Vitamin  

Opinionated Vite starter template.

Features

Getting started

Use this repository as a GitHub template or use degit to clone to your machine with an empty git history:

npx degit wtchnm/Vitamin#main my-app

Then, install the dependencies:

pnpm install

Before you start coding

  •  If you don't plan to use GitHub Actions, delete the .github directory.
  •  Clean up the cypress/integration/index.spec.ts file.
  •  Change the favicon.png, apple-touch-icon.png, android-chrome-192x192.png and android-chrome-512x512.png. favicon.io is a cool tool for generating these assets.
  •  In the src folder, remove the __tests__, api and components folder and the types.ts file.
  •  If you don't plan to use react-query, remove the query client logic in the main.tsx file.
  •  Change the title, description and theme color in the index.html and vite.config.ts. The Inter font is included, so remove it if you want.
  •  Modify or delete the LICENSE file.
  •  Change the name field in package.json.

Scripts

  • pnpm dev - start a development server with hot reload.
  • pnpm build - build for production. The generated files will be on the dist folder.
  • pnpm preview - locally preview the production build.
  • pnpm test - run unit and integration tests related to changed files based on git.
  • pnpm test:ci - run all unit and integration tests in CI mode.
  • pnpm test:e2e - run all e2e tests with the Cypress Test Runner.
  • pnpm test:e2e:headless - run all e2e tests headlessly.
  • pnpm format - format all files with Prettier.
  • pnpm lint - runs TypeScript, ESLint and Stylelint.
  • pnpm validate - runs lint, test:ci and test:e2e:ci.

Author: wtchnm
Source code: https://github.com/wtchnm/Vitamin
License: MIT license

#tailwindcss #typescript #vite 

Vitamin: React TypeScript, TailwindCSS, SPA + PWA, Cypress and CI

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

Criar Uma Paleta De Comandos Com Tailwind CSS E  Headless UI

TailwindCSSとヘッドレスUIを使用してコマンドパレットを作成する

開発者として、私たちはワークフローを可能な限り最適化するように努め、ターミナルなどのツールを活用して時間を節約します。コマンドパレットは、Webまたはデスクトップアプリケーションでの最近のアクティビティを表示するツールの1つであり、クイックナビゲーション、コマンドへの簡単なアクセス、ショートカットなどを可能にします。

生産性レベルを上げるために、コマンドパレットは基本的にモーダルの形式をとるUIコンポーネントです。コマンドパレットは、多くの可動部分がある大規模で複雑なアプリケーションで特に役立ちます。たとえば、リソースにアクセスするために数回クリックしたり、複数のドロップダウンをスキミングしたりする場合があります。

このチュートリアルでは、 Headless UIComboboxコンポーネントとTailwindCSSを使用して、完全に機能するコマンドパレットを最初から作成する方法について説明します。

コマンドパレットの実際のユースケース

開発者として、以前にコマンドパレットを使用したことがある可能性が非常に高くなります。最も人気のあるものはVSCodeコマンドパレットですが、GitHubコマンドパレット、Linear、Figma、Slack、monkeytypeなど、他にも多くの例があります。

GitHubアプリ

GitHubは最近、執筆時点でまだパブリックベータ版であるコマンドパレット機能をリリースしました。すばやく別のページにジャンプしたり、コマンドを検索したり、現在のコンテキストに基づいて提案を取得したりできます。オプションの1つにタブで移動するか、特殊文字を使用して、探しているリソースの範囲を狭めることもできます。

 

リニアアプリ

リニアに慣れていない場合は、JiraやAsanaに似たプロジェクト管理ツールであり、非常に優れたユーザーエクスペリエンスを提供します。リニアには非常に直感的なコマンドパレットがあり、キーボードファーストのデザインでアプリケーション全体の機能にアクセスできます。このチュートリアルでは、Linearに似たコマンドパレットを作成します。

 

コマンドパレットの基本機能

いくつかの最新のアプリケーションは機能としてコマンドパレットを実装していますが、優れたコマンドパレットコンポーネントを作るものは何ですか?注意すべき点の簡潔なリストは次のとおりです。

  • パレットを開くための簡単なショートカット、つまり、ctrl + k
  • アプリケーションのどこからでもアクセスできます
  • あいまい検索などの広範な検索機能があります
  • コマンドは意図を伝え、理解しやすい
  • 1つの場所からアプリケーションのすべての部分へのアクセスを提供します

次のセクションでは、上記のすべての機能を含む独自のコンポーネントを作成します。入りましょう!

 

コンポーネントの構築

コマンドパレットは実際には見た目ほど複雑ではなく、誰でもすばやく作成できます。このチュートリアルのスタータープロジェクトを用意したので、簡単にフォローできます。スタータープロジェクトは、LinearIssuesページを複製するReactandViteSPAです。

プロジェクトの設定

開始するには、リポジトリをローカルディレクトリに複製し、必要な依存関係をインストールして、開発サーバーを起動します。プロジェクトはYarnを使用しますが、npmまたはpnPmに慣れている場合は、yarn.lock実行する前にファイルを削除するnpm installか、次のようにすることができます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

にアクセスするlocalhost:3000と、次のページが表示されます。

 

コンポーネント_CommandPalette

次に、コンポーネントを作成します。ヘッドレスUIcomboboxとdialogコンポーネントを使用します。comboboxコマンドパレットの基本コンポーネントになります。フォーカス管理やキーボード操作などの機能が組み込まれています。コンポーネントを使用dialogして、コマンドパレットをモーダルでレンダリングします。

コンポーネントのスタイルを設定するには、TailwindCSSを使用します。Tailwindは、HTMLまたはJSXファイルにインラインスタイルを簡単に追加できるCSSユーティリティライブラリです。スタータープロジェクトには、Tailwindの構成がすでに含まれています。

次のように必要な依存関係をインストールします。

$ yarn add @headlessui/react @heroicons/react

componentsフォルダにファイルを作成し、CommandPalette.jsx次のコードブロックを追加します。

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>
  );
};

ここでいくつかのことが起こっています。まず、DialogとComboboxコンポーネントをインポートします。Dialogはのラッパーとしてレンダリングされ、モーダルを制御するためにCombobox呼び出されるローカル状態を初期化します。isOpen

Dialog.Overlayコンポーネントの内部をレンダリングしDialogて、モーダルのオーバーレイとして機能させます。これは好きなようにスタイルを設定できますが、ここでは、を使用していますbackdrop-blur。次に、Comboboxコンポーネントをレンダリングし、ハンドラー関数をonChangeプロップに渡します。このハンドラーは、でアイテムが選択されるたびに呼び出されComboboxます。通常、ここでページに移動したり、アクションを実行したりする必要がありますが、今のところ、を閉じるだけDialogです。

Combobox.Inputこのセクションの後半で追加する検索機能を処理します。Combobox.Optionsレンダリングするul結果のリストをラップする要素をレンダリングします。staticコンポーネントの内部管理状態を無視することを示す小道具を渡します。

次に、ファイルにレンダリングCommandPaletteします。App.jsx

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>
   );
};

コマンドパレットがどのように機能するかについて話しましょう。ファイルには事前定義されたコマンドのリストがありdata/seed.jsonます。これらのコマンドは、開いたときにパレットに表示され、検索クエリに基づいてフィルタリングできます。かなり簡単ですよね?

コンポーネント_CommandGroup

CommandPalettecommandsからインポートしたコマンドのリストであるpropを受け取りますseed.json。CommandGroup.jsx次に、フォルダーにファイルを作成しcomponents、次のコードを追加します。

// 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>
   );
};

CommandGroup繰り返しのコードを避けるために、単にコンポーネントを使用しています。線形コマンドパレットを見ると、コマンドがコンテキストに基づいてグループ化されていることがわかります。これを実装するには、同じグループに属するコマンドを除外し、グループごとにそのロジックを繰り返す必要があります。

コンポーネントは2つのCommandGroup小道具、commandsおよびを受け取りgroupます。現在のグループに基づいてコマンドをフィルタリングし、Combobox.Optionコンポーネントを使用してレンダリングします。レンダリングプロップを使用すると、activeアイテムを取得してそれに応じてスタイルを設定できるため、コードをクリーンに保ちながら、CommandGroupグループごとにをレンダリングできます。CommandPalette

mapCommandGroupToIcon上記のコードブロックのどこかに関数があることに注意してください。これは、各グループに異なるアイコンがあり、この関数は現在のグループの正しいアイコンをレンダリングするための単なるヘルパーであるためです。CommandGroup次に、同じファイルのコンポーネントのすぐ下に関数を追加します。

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

CommandGroup次に、でコンポーネントをレンダリングする必要がありますCommandPalette。
次のようにコンポーネントをインポートします。

import { CommandGroup } from "./CommandGroup";

Combobox.Options各グループの内部にレンダリングします。

<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>

レンダリングされているコマンドのリストが表示されます。次のステップは、検索機能を接続することです。

検索機能の実装

でローカル状態変数を作成しますCommandPalette.jsx:

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

状態更新ハンドラーをのonChange小道具に渡しCombobox.Inputます。入力ボックスに入力したすべてのquery文字で更新されます。

<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)}
/>

優れたコマンドパレットの重要なプロパティの1つは、広範な検索機能です。検索クエリとコマンドの単純な文字列比較を行うことができますが、タイプミスやコンテキストは考慮されません。あまり複雑にならないはるかに優れたソリューションは、あいまい検索です。

これにはFuse.jsライブラリを使用します。Fuse.jsは、依存関係がゼロの強力で軽量なあいまい検索ライブラリです。ファジー検索に慣れていない場合、これは完全一致よりも近似一致を優先する文字列照合手法であり、クエリにタイプミスやスペルミスがある場合でも正しい提案を取得できることを意味します。

まず、Fuse.jsライブラリをインストールします。

$ yarn add fuse.js

で、コマンドのリストを使用しCommandPalette.jsxてクラスをインスタンス化します。Fuse

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

このFuseクラスは、一連のコマンドと構成オプションを受け入れます。このkeysフィールドは、Fuse.jsによってインデックスが作成されるコマンドリストのフィールドを登録する場所です。次に、検索を処理してフィルタリングされた結果を返す関数を作成します。

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

が空かどうかを確認し、queryすべてのコマンドを返します。空でない場合はfuse.search、クエリを使用してメソッドを実行します。また、結果をマッピングして新しいオブジェクトを作成しています。これは、Fuse.jsによって返される結果にいくつかの新しいフィールドがあり、既存の構造と一致しないため、一貫性を維持するためです。

次に、各コンポーネントfilteredCommandsのcommands小道具にを渡します。CommandGroup以下のコードのようになります。

// 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"/>

コマンドパレットで検索して、結果がフィルタリングされているかどうかを確認してください。

 

完全に機能するコマンドパレットがありますが、常に開いていることに気付くかもしれません。開いた状態を制御できる必要があります。キーの組み合わせをリッスンし、開いた状態を更新するキーボードイベントを定義しましょう。次のコードをに追加します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);
  };
}, []);

useEffectフックを使用してkeydown、コンポーネントがマウントされたときにキーボードイベントを登録し、クリーンアップ関数を使用して、コンポーネントがマウント解除されたときにリスナーを削除します。

フックでは、キーの組み合わせがに一致するかどうかを確認しますctrl + k。含まれている場合、オープン状態はに設定されtrueます。別のキーの組み合わせを使用することもできますが、ネイティブブラウザのショートカットと衝突する組み合わせを使用しないことが重要です。

それでおしまい!このプロジェクトの完成版は、finished-projectブランチにあります。

react-command-palette:ビルド済みコンポーネント

コマンドパレットコンポーネントを最初から作成する方法を検討しました。ただし、コマンドパレットが必要になるたびに、独自に作成するのではないでしょう。ここで、ビルド済みのコンポーネントが役立ちます。ほとんどのコンポーネントライブラリはコマンドパレットを提供していませんが、react-command-paletteは、アクセス可能でブラウザと互換性のある、よく書かれたコンポーネントです。

このコンポーネントを使用するには、プロジェクトに依存関係としてインストールします。

$ yarn add react-command-palette

コンポーネントをインポートし、次のようにコマンドのリストをコンポーネントに渡します。

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>
  );
}

要件を満たすように外観と動作をカスタマイズするために使用できる構成オプションはたくさんあります。たとえば、このtheme構成では、多数の組み込みテーマから選択したり、独自のカスタムテーマを作成したりできます。

次のステップ

この記事では、コマンドパレット、それらの理想的な使用例、および優れたコマンドパレットを構成する機能について学習しました。また、ヘッドレスUIコンボボックスコンポーネントとTailwindCSSを使用してビルドする方法についても詳細な手順で説明しました。

この機能をアプリケーションにすばやく導入したい場合は、react-command-paletteなどのビルド済みコンポーネントが最適です。読んでいただきありがとうございます。ご不明な点がございましたら、必ずコメントを残してください。

ソース:https ://blog.logrocket.com/react-command-palette-tailwind-css-headless-ui/

#tailwindcss #headless #react 

TailwindCSSとヘッドレスUIを使用してコマンドパレットを作成する
Vida  Herzog

Vida Herzog

1656666000

Sttv: A Starter Template for Svelte, Tailwind, Typescript, and Vite

STTV - Svelte / Typescript / Tailwind / Vite Template

Get up and running with Svelte, Typescript, Tailwind and Vite. Also includes testing setup using Jest.

Installation

npx degit "srmullen/sttv#main" my_app
cd my_app
npm install

Scripts

Start a development server

npm run dev

Run tests

npm run test or npm run test:watch

Build the application

npm run build

Serve the application

npm run serve3


Author: srmullen
Source code: https://github.com/srmullen/sttv
License:

#svelte #vite #typescript #tailwindcss 

Sttv: A Starter Template for Svelte, Tailwind, Typescript, and Vite
Anne  de Morel

Anne de Morel

1656666000

Créer Une Palette De Commandes Avec Tailwind CSS Et Headless UI

En tant que développeurs, nous nous efforçons souvent d'optimiser au maximum nos flux de travail, en gagnant du temps en tirant parti d'outils comme le terminal. Une palette de commandes est l'un de ces outils qui affiche l'activité récente dans une application Web ou de bureau, permettant une navigation rapide, un accès facile aux commandes et aux raccourcis, entre autres.

Pour élever votre niveau de productivité, une palette de commandes est essentiellement un composant d'interface utilisateur qui prend la forme d'un modal. Une palette de commandes est particulièrement utile dans les grandes applications complexes avec de nombreuses pièces mobiles, par exemple, où il peut vous falloir plusieurs clics ou parcourir plusieurs listes déroulantes pour accéder à une ressource.

Dans ce didacticiel, nous allons explorer comment créer une palette de commandes entièrement fonctionnelle à partir de zéro à l'aide du composant Headless UI Combobox et de Tailwind CSS.

Cas d'utilisation réels pour une palette de commandes

En tant que développeur, il y a de fortes chances que vous ayez déjà utilisé une palette de commandes. La plus populaire est la palette de commandes VS Code , mais il existe de nombreux autres exemples, notamment la palette de commandes GitHub, Linear, Figma, Slack, monkeytype , etc.

L'application GitHub

GitHub a récemment publié une fonctionnalité de palette de commandes qui est toujours en version bêta publique au moment de la rédaction. Il vous permet d'accéder rapidement à différentes pages, de rechercher des commandes et d'obtenir des suggestions en fonction de votre contexte actuel. Vous pouvez également restreindre l'étendue des ressources que vous recherchez en cliquant sur l'une des options ou en utilisant un caractère spécial :

Palette de commandes Github

L'application linéaire

Si vous n'êtes pas familier avec Linear , c'est un outil de gestion de projet similaire à Jira et Asana qui offre une très bonne expérience utilisateur. Linear a une palette de commandes très intuitive qui vous permet d'accéder à l'ensemble des fonctionnalités de l'application grâce à sa conception axée sur le clavier. Dans ce didacticiel, nous allons créer une palette de commandes similaire à Linear :

Palette de commandes d'application linéaire

Fonctionnalités essentielles d'une palette de commandes

Plusieurs applications modernes implémentent des palettes de commandes en tant que fonctionnalité, mais qu'est-ce qui fait un bon composant de palette de commandes ? Voici une liste concise des éléments à surveiller :

  • Un simple raccourci pour ouvrir la palette, c'est-à-dire,ctrl + k
  • Il peut être accessible de n'importe où dans l'application
  • Il dispose de fonctionnalités de recherche étendues, telles que la recherche floue
  • Les commandes communiquent l'intention et sont faciles à comprendre
  • Il permet d'accéder à toutes les parties de l'application à partir d'un seul endroit

Dans la section suivante, nous allons créer notre propre composant qui comprend toutes les fonctionnalités répertoriées ci-dessus. Allons-y !

 

Construire le composant

La palette de commandes n'est pas aussi complexe qu'il n'y paraît, et n'importe qui peut en créer une rapidement. J'ai préparé un projet de démarrage pour ce tutoriel afin que vous puissiez facilement suivre. Le projet de démarrage est un SPA React et Vite qui reproduit la page des problèmes linéaires.

Mise en place du projet

Pour commencer, clonez le référentiel dans votre répertoire local, installez les dépendances nécessaires et démarrez le serveur de développement. Le projet utilise Yarn, mais si vous êtes plus à l'aise avec npm ou pnPm, vous pouvez supprimer le yarn.lockfichier avant de lancer 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

Si vous visitez localhost:3000, vous verrez la page suivante :

Clone du référentiel Github

Le CommandPalettecomposant

Ensuite, nous allons construire le composant. Nous utiliserons l'interface utilisateur comboboxet dialogles composants Headless. comboboxsera le composant de base de notre palette de commandes. Il possède des fonctionnalités intégrées telles que la gestion de la mise au point et l'interaction au clavier. Nous utiliserons le dialogcomposant pour rendre notre palette de commandes dans un modal.

Pour styliser les composants, nous utiliserons Tailwind CSS. Tailwind est une bibliothèque d'utilitaires CSS qui vous permet d'ajouter facilement des styles en ligne dans vos fichiers HTML ou JSX. Le projet de démarrage inclut déjà la configuration pour Tailwind.

Installez les dépendances nécessaires comme suit :

$ yarn add @headlessui/react @heroicons/react

Dans le componentsdossier, créez un CommandPalette.jsxfichier et ajoutez le bloc de code suivant :

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>
  );
};

Quelques choses se passent ici. Tout d'abord, nous importons les composants Dialoget Combobox. Dialogest rendu comme un wrapper autour du Combobox, et nous initialisons un état local appelé isOpenpour contrôler le modal.

Nous rendons un Dialog.Overlayà l'intérieur du Dialogcomposant pour servir de superposition pour le modal. Vous pouvez le styliser comme vous le souhaitez, mais ici, nous utilisons simplement backdrop-blur. Ensuite, nous rendons le Comboboxcomposant et passons une fonction de gestionnaire à la onChangeprop. Ce gestionnaire est appelé chaque fois qu'un élément est sélectionné dans le fichier Combobox. Vous voudriez généralement naviguer vers une page ou exécuter une action ici, mais pour l'instant, nous fermons simplement le fichier Dialog.

Combobox.Inputgérera la fonctionnalité de recherche, que nous ajouterons plus tard dans cette section. Combobox.Optionsrend un ulélément qui encapsule la liste des résultats que nous rendrons. Nous passons une staticprop qui indique que nous voulons ignorer l'état géré en interne du composant.

Ensuite, nous rendons notre CommandPalettedans le App.jsxfichier :

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>
   );
};

Parlons du fonctionnement de notre palette de commandes. Nous avons une liste de commandes prédéfinies dans le data/seed.jsonfichier. Ces commandes seront affichées dans la palette lors de son ouverture et peuvent être filtrées en fonction de la requête de recherche. Assez simple, non ?

Le CommandGroupcomposant

CommandPalettereçoit un commandsaccessoire, qui est la liste des commandes que nous avons importées de seed.json. Maintenant, créez un CommandGroup.jsxfichier dans le componentsdossier et ajoutez le code suivant :

// 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>
   );
};

Nous utilisons simplement le CommandGroupcomposant pour éviter un code répétitif. Si vous regardez la palette de commandes linéaires, vous verrez que les commandes sont regroupées en fonction du contexte. Pour implémenter cela, nous devons filtrer les commandes qui appartiennent au même groupe et répéter cette logique pour chaque groupe.

Le CommandGroupcomposant reçoit deux accessoires, commandset group. Nous allons filtrer les commandes en fonction du groupe actuel et les rendre à l'aide du Combobox.Optioncomposant. En utilisant les accessoires de rendu, nous pouvons obtenir l' activeélément et le styler en conséquence, ce qui nous permet de rendre le CommandGrouppour chaque groupe CommandPalettetout en gardant le code propre.

Notez que nous avons une mapCommandGroupToIconfonction quelque part dans le bloc de code ci-dessus. En effet, chaque groupe a une icône différente et la fonction n'est qu'une aide pour rendre l'icône correcte pour le groupe actuel. Maintenant, ajoutez la fonction juste en dessous du CommandGroupcomposant dans le même fichier :

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

Maintenant, nous devons rendre le CommandGroupcomposant dans CommandPalette.
Importez le composant comme suit :

import { CommandGroup } from "./CommandGroup";

Affichez-le à l'intérieur du Combobox.Optionspour chaque groupe :

<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>

Vous devriez voir la liste des commandes rendues maintenant. L'étape suivante consiste à câbler la fonctionnalité de recherche.

Implémentation de la fonctionnalité de recherche

Créez une variable d'état local dansCommandPalette.jsx :

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

Transmettez le gestionnaire de mise à jour d'état à la onChangeprop dans Combobox.Input. Le querysera mis à jour avec chaque caractère que vous saisissez dans la zone de saisie :

<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)}
/>

L'une des principales propriétés d'une bonne palette de commandes est une fonctionnalité de recherche étendue. Nous pouvons simplement faire une simple comparaison de chaîne de la requête de recherche avec les commandes, mais cela ne tiendrait pas compte des fautes de frappe et du contexte. Une bien meilleure solution qui n'introduit pas trop de complexité est une recherche floue.

Nous utiliserons la bibliothèque Fuse.js pour cela. Fuse.js est une bibliothèque de recherche puissante, légère et floue sans aucune dépendance. Si vous n'êtes pas familier avec la recherche floue, il s'agit d'une technique de correspondance de chaînes qui privilégie la correspondance approximative à la correspondance exacte, ce qui implique que vous pouvez obtenir des suggestions correctes même si la requête contient des fautes de frappe ou des fautes d'orthographe.

Tout d'abord, installez la bibliothèque Fuse.js :

$ yarn add fuse.js

Dans CommandPalette.jsx, instanciez la Fuseclasse avec une liste de commandes :

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

La Fuseclasse accepte un tableau de commandes et d'options de configuration. Le keyschamp est l'endroit où nous enregistrons les champs de la liste des commandes à indexer par Fuse.js. Maintenant, créez une fonction qui gérera la recherche et renverra les résultats filtrés :

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

Nous vérifions si le queryest vide, renvoyons toutes les commandes, et sinon, exécutons la fuse.searchméthode avec la requête. De plus, nous mappons les résultats pour créer un nouvel objet. Ceci afin de maintenir la cohérence car les résultats renvoyés par Fuse.js ont de nouveaux champs et ne correspondront pas à la structure que nous avons déjà.

Maintenant, passez le filteredCommandsà l' commandsaccessoire dans chaque CommandGroupcomposant. Cela devrait ressembler au code ci-dessous :

// 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"/>

Essayez de rechercher dans la palette de commandes et voyez si les résultats sont filtrés :

Filtre de recherche de la palette de commandes

Nous avons une palette de commandes entièrement fonctionnelle, mais vous remarquerez peut-être qu'elle est toujours ouverte. Nous devons pouvoir contrôler son état ouvert. Définissons un événement clavier qui écoutera une combinaison de touches et mettra à jour l'état ouvert. Ajoutez le code suivant à 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);
  };
}, []);

Nous utilisons un useEffectcrochet pour enregistrer un keydownévénement de clavier lorsque le composant est monté, et nous utilisons une fonction de nettoyage pour supprimer l'écouteur lorsque le composant se démonte.

Dans le Hook, nous vérifions si la combinaison de touches correspond à ctrl + k. Si c'est le cas, l'état ouvert est défini sur true. Vous pouvez également utiliser une combinaison de touches différente, mais il est important de ne pas utiliser de combinaisons qui entrent en conflit avec les raccourcis natifs du navigateur.

C'est ça! Vous pouvez trouver la version finale de ce projet sur la branche du projet fini .

react-command-palette : composant prédéfini

Nous avons exploré comment créer un composant de palette de commandes à partir de zéro. Cependant, vous préféreriez probablement ne pas créer la vôtre chaque fois que vous avez besoin d'une palette de commandes. C'est là qu'un composant prédéfini peut être utile. La plupart des bibliothèques de composants n'offrent pas de palette de commandes, mais react-command-palette est un composant bien écrit, accessible et compatible avec les navigateurs.

Pour utiliser ce composant, installez-le en tant que dépendance dans votre projet :

$ yarn add react-command-palette

Importez le composant et transmettez-lui votre liste de commandes comme suit :

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>
  );
}

Il existe de nombreuses options de configuration que vous pouvez utiliser pour personnaliser l'apparence et le comportement en fonction de vos besoins. Par exemple, la themeconfiguration vous permet de choisir parmi un certain nombre de thèmes intégrés ou de créer votre propre thème personnalisé.

Prochaines étapes

Dans cet article, vous avez découvert les palettes de commandes, leurs cas d'utilisation idéaux et les fonctionnalités qui constituent une bonne palette de commandes. Vous avez également exploré dans les étapes détaillées comment en créer un à l'aide du composant combobox de l'interface utilisateur sans tête et du CSS Tailwind.

Si vous souhaitez simplement intégrer rapidement cette fonctionnalité dans votre application, un composant prédéfini tel que react-command-palette est la solution. Merci d'avoir lu et n'oubliez pas de laisser un commentaire si vous avez des questions.

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

#tailwindcss #headless #react 

Créer Une Palette De Commandes Avec Tailwind CSS Et Headless UI
曾 俊

曾 俊

1656662700

使用 Tailwind CSS 和 Headless UI 创建命令面板

作为开发人员,我们经常努力尽可能优化我们的工作流程,通过利用终端等工具来节省时间。命令面板就是这样一种工具,它可以显示 Web 或桌面应用程序中的最近活动,从而实现快速导航、轻松访问命令和快捷方式等。

为了提高您的工作效率,命令面板本质上是一个采用模式形式的 UI 组件。命令面板在具有许多移动部件的大型复杂应用程序中特别有用,例如,您可能需要单击几次或浏览多个下拉列表才能访问资源。

在本教程中,我们将探索如何使用Headless UI Combobox 组件和 Tailwind CSS从头开始​​构建功能齐全的命令面板。

命令调色板的真实用例

作为开发人员,您以前很有可能使用过命令调色板。最受欢迎的是VS Code 命令面板,但还有许多其他示例,包括 GitHub 命令面板、Linear、Figma、Slack、monkeytype等。

GitHub 应用程序

GitHub 最近发布了一个命令调色板功能,在撰写本文时仍处于公开测试阶段。它使您可以快速跳转到不同的页面、搜索命令并根据您当前的上下文获取建议。您还可以通过切换到其中一个选项或使用特殊字符来缩小您正在寻找的资源的范围:

Github 命令面板

线性应用程序

如果您不熟悉Linear,它是一个类似于 Jira 和 Asana 的项目管理工具,可提供非常棒的用户体验。Linear 有一个非常直观的命令面板,可让您通过其键盘优先设计访问整个应用程序的功能。在本教程中,我们将构建一个类似于 Linear 的命令面板:

线性应用命令面板

命令选项板的基本功能

一些现代应用程序正在将命令选项板作为一项功能实现,但是什么是好的命令选项板组件?以下是需要注意的事项的简要清单:

  • 打开调色板的简单快捷方式,即ctrl + k
  • 它可以从应用程序的任何地方访问
  • 它具有广泛的搜索功能,例如模糊搜索
  • 命令传达意图并且易于理解
  • 它提供从一个地方访问应用程序的每个部分

在下一节中,我们将构建自己的组件,其中包含上面列出的所有功能。让我们开始吧!

 

构建组件

命令面板实际上并不像看起来那么复杂,任何人都可以快速构建一个。我为本教程准备了一个入门项目,以便您轻松学习。启动项目是复制线性问题页面的 React 和 Vite SPA。

设置项目

首先,将存储库克隆到本地目录,安装必要的依赖项,然后启动开发服务器。该项目使用 Yarn,但如果您更习惯使用 npm 或 pnPm,您可以yarn.lock在运行之前删除文件npm install或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

如果您访问localhost:3000,您将看到以下页面:

Github 存储库克隆

组件_CommandPalette

接下来,我们将构建组件。我们将使用 Headless UIcombobox和dialog组件。combobox将是我们命令面板的基础组件。它具有诸如焦点管理和键盘交互等内置功能。我们将使用该dialog组件以模式呈现我们的命令调色板。

为了给组件设置样式,我们将使用 Tailwind CSS。Tailwind 是一个 CSS 实用程序库,可让您轻松地在 HTML 或 JSX 文件中添加内联样式。启动项目已包含 Tailwind 的配置。

安装必要的依赖项,如下所示:

$ yarn add @headlessui/react @heroicons/react

在components文件夹中,创建一个CommandPalette.jsx文件并添加以下代码块:

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>
  );
};

这里正在发生一些事情。首先,我们导入Dialog和Combobox组件。Dialog被呈现为 的包装器Combobox,我们初始化一个本地状态,调用isOpen它来控制模态。

Dialog.Overlay我们在组件内部渲染一个Dialog作为模态的叠加层。您可以随意设置它的样式,但在这里,我们只是使用backdrop-blur. 然后,我们渲染Combobox组件并将处理函数传递给onChange道具。每当在Combobox. 您通常希望在此处导航到页面或执行操作,但现在,我们只需关闭Dialog.

Combobox.Input将处理搜索功能,我们将在本节后面添加。Combobox.Options呈现一个ul包含我们将呈现的结果列表的元素。我们传入一个staticprop,表明我们要忽略组件的内部管理状态。

接下来,我们CommandPalette在App.jsx文件中渲染我们的:

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>
   );
};

让我们谈谈我们的命令面板将如何工作。我们在文件中有一个预定义命令的列表data/seed.json。这些命令将在打开时显示在调色板中,并且可以根据搜索查询进行过滤。相当简单,对吧?

组件_CommandGroup

CommandPalette接收一个commandsprop,它是我们从中导入的命令列表seed.json。现在,在文件夹中创建一个CommandGroup.jsx文件components并添加以下代码:

// 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>
   );
};

我们只是使用CommandGroup组件来避免一些重复的代码。如果您查看线性命令选项板,您会看到命令是根据上下文分组的。为了实现这一点,我们需要过滤掉属于同一组的命令,并为每个组重复该逻辑。

该CommandGroup组件接收两个 propscommands和group. 我们将根据当前组过滤命令并使用Combobox.Option组件呈现它们。使用渲染道具,我们可以获取active项目并相应地对其进行样式设置,从而允许我们在保持代码清洁CommandGroup的同时为每个组渲染。CommandPalette

请注意,我们mapCommandGroupToIcon在上面的代码块中的某处有一个函数。这是因为每个组都有不同的图标,该函数只是为当前组渲染正确图标的助手。CommandGroup现在,在同一文件中的组件下方添加函数:

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

现在,我们需要CommandGroup在CommandPalette.
导入组件如下:

import { CommandGroup } from "./CommandGroup";

Combobox.Options在每个组内渲染它:

<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>

您应该看到现在正在呈现的命令列表。下一步是连接搜索功能。

实现搜索功能

在 中创建局部状态变量CommandPalette.jsx:

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

将状态更新处理程序传递onChange给Combobox.Input. 将query使用您在输入框中键入的每个字符进行更新:

<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)}
/>

一个好的命令面板的关键属性之一是广泛的搜索功能。我们可以将搜索查询与命令进行简单的字符串比较,但这不会考虑拼写错误和上下文。一个不会引入太多复杂性的更好的解决方案是模糊搜索。

我们将为此使用Fuse.js库。Fuse.js 是一个功能强大、轻量级、零依赖的模糊搜索库。如果您不熟悉模糊搜索,它是一种字符串匹配技术,它倾向于近似匹配而不是精确匹配,这意味着即使查询有拼写错误或拼写错误,您也可以获得正确的建议。

首先,安装 Fuse.js 库:

$ yarn add fuse.js

在中CommandPalette.jsx,使用命令列表实例化Fuse类:

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

该类Fuse接受一系列命令和配置选项。该keys字段是我们在命令列表中注册哪些字段将被 Fuse.js 索引的地方。现在,创建一个处理搜索并返回过滤结果的函数:

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

我们检查是否query为空,返回所有命令,如果不是,则fuse.search使用查询运行该方法。此外,我们正在映射结果以创建一个新对象。这是为了保持一致性,因为 Fuse.js 返回的结果有一些新字段,与我们已有的结构不匹配。

现在,将 传递filteredCommands给每个组件中的commands道具。CommandGroup它应该类似于下面的代码:

// 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"/>

尝试在命令面板中搜索并查看结果是否被过滤:

命令面板搜索过滤器

我们有一个功能齐全的命令面板,但您可能会注意到它始终处于打开状态。我们需要能够控制它的打开状态。让我们定义一个键盘事件,它将侦听组合键并更新打开状态。将以下代码添加到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);
  };
}, []);

我们使用useEffectHook 在组件挂载时注册keydown键盘事件,并在组件卸载时使用清理功能移除侦听器。

在 Hook 中,我们检查组合键是否匹配ctrl + k。如果是,则打开状态设置为true。您也可以使用不同的组合键,但重要的是不要使用与本机浏览器快捷方式冲突的组合。

而已!您可以在完成项目分支上找到该项目的完成版本。

react-command-palette:预构建组件

我们已经探索了如何从头开始构建命令选项板组件。但是,您可能不希望每次需要命令调色板时都自己构建。这就是预构建组件可能有用的地方。大多数组件库不提供命令调色板,但react-command-palette是一个编写良好的组件,可访问且与浏览器兼容。

要使用此组件,请将其作为依赖项安装在您的项目中:

$ yarn add react-command-palette

导入组件并将您的命令列表传递给它,如下所示:

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>
  );
}

您可以使用许多配置选项来自定义外观和行为以满足您的要求。例如,theme配置允许您从许多内置主题中进行选择或创建自己的自定义主题。

下一步

在本文中,您了解了命令选项板、它们的理想用例以及构成良好命令选项板的功能。您还详细探索了如何使用 Headless UI 组合框组件和 Tailwind CSS 构建一个。

如果您只想在应用程序中快速发布此功能,那么像 react-command-palette 这样的预构建组件是不错的选择。感谢您的阅读,如果您有任何问题,请务必发表评论。

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

#tailwindcss #headless #react 

使用 Tailwind CSS 和 Headless UI 创建命令面板

Build Elm Web Apps using Vite, Tailwind CSS and Daisy UI

Vite, Elm, Tailwind CSS, and Daisy UI

Opinionated template for building Elm applications using Vite JS, Tailwind CSS, and Daisy UI.

Tooling

Vite JS

Elm

Packages

Shortlist of Elm packages that could be beneficial. Included as suggestions:

To remove them: 1) feel free to not use them, and 2) run npm run review.

Tailwind CSS

The official plugins are installed by default:

Daisy UI

"The most popular, free and open-source Tailwind CSS component library".

Daisy UI: a Tailwind CSS classes component library, aiming to work on all frameworks. The CSS nature of Daisy UI makes Elm integration possible and a breeze to use. Examples included:

  • the whole page is a Daisy UI hero section
  • the counter buttons are Daisy UI buttons
  • basic Daisy UI 'light' and 'dark' theming/coloring (via Elm ports)

Node

  • nvm: easily and consistently manage Node. (.nvimrc provided).

VS Code

Lint/Format

Elm + Tailwind CSS

Git, GitLab, GitHub

For consistent git management, the following extensions and their settings are provided:

Get Started

Install tiged

To clone this repository, devoid of .git, you can either use npx degit or install tiged locally and run degit without npx. YMMV.

Tiged is a fork that addresses a number of issues, and should be preferred. To install it, run:

npm uninstall -g degit
npm install -g tiged

Most likely, npx degit would invoke degit. A locally installed binary makes sure you are running tiged instead.

Peruse the template

  1. Clone the template:
    1. using npx: npx degit gacallea/elm_vite_tailwind_template my-elm-app
    2. local binary: degit gacallea/elm_vite_tailwind_template my-elm-app
  2. Enter the project: cd my-elm-app
  3. Install dependencies: npm install
  4. Start developing: npm run dev

Available Commands

scriptaction
postinstallelm-tooling install
devvite
prebuildelm-tooling install
buildvite build
previewvite preview
standardstandard --fix src/**/*.js
markdownstandard-markdown --fix src/**/*.md
stylelintstylelint --fix "src/**/*.(css|scss|sass)"
reviewelm-review --fix-all
testelm-test-rs

Contributing

Contributions and constructive criticism are welcome. If you think I'm overdoing it, feel free to discuss. I'm still experimenting with this, as a learning opportunity, and I strive to improve the template as much as possible.

pre-commit hooks (WIP)

This repo relies on pre-commit-ci to make sure all suggested coding standards are enforced with git hooks. You could also install pre-commit locally to apply the same configuration locally.

Acknowledgements

This templated was inspired by Lindsay K Wardell's template.


Author: gacallea
Source code: https://github.com/gacallea/elm_vite_tailwind_template
License: GPL-3.0 license

#vue #vite #tailwindcss 

Build Elm Web Apps using Vite, Tailwind CSS and Daisy UI
Diego  Elizondo

Diego Elizondo

1656662400

Cree Una Paleta De Comandos Con Tailwind CSS Y Headless UI

Como desarrolladores, a menudo nos esforzamos por optimizar nuestros flujos de trabajo tanto como sea posible, ahorrando tiempo al aprovechar herramientas como la terminal. Una paleta de comandos es una de esas herramientas que muestra la actividad reciente en una aplicación web o de escritorio, lo que permite una navegación rápida, un fácil acceso a los comandos y accesos directos, entre otras cosas.

Para elevar su nivel de productividad, una paleta de comandos es esencialmente un componente de interfaz de usuario que toma la forma de un modal. Una paleta de comandos es especialmente útil en aplicaciones grandes y complejas con muchas partes móviles, por ejemplo, donde puede necesitar varios clics o hojear varios menús desplegables para acceder a un recurso.

En este tutorial, exploraremos cómo crear una paleta de comandos totalmente funcional desde cero utilizando el componente Headless UI Combobox y Tailwind CSS.

Casos de uso del mundo real para una paleta de comandos

Como desarrollador, existe una gran posibilidad de que haya usado una paleta de comandos anteriormente. La más popular es la paleta de comandos de VS Code , pero hay muchos otros ejemplos, incluida la Paleta de comandos de GitHub, Linear, Figma, Slack, monkeytype y más.

La aplicación GitHub

GitHub lanzó recientemente una función de paleta de comandos que aún se encuentra en versión beta pública al momento de escribir este artículo. Le permite saltar rápidamente a diferentes páginas, buscar comandos y obtener sugerencias basadas en su contexto actual. También puede reducir el alcance de los recursos que está buscando tabulando una de las opciones o usando un carácter especial:

 

La aplicación lineal

Si no está familiarizado con Linear , es una herramienta de gestión de proyectos similar a Jira y Asana que ofrece una experiencia de usuario realmente excelente. Linear tiene una paleta de comandos muy intuitiva que le permite acceder a toda la funcionalidad de la aplicación con su diseño de teclado. En este tutorial, crearemos una paleta de comandos similar a Lineal:

 

Características esenciales de una paleta de comandos

Varias aplicaciones modernas están implementando paletas de comandos como una característica, pero ¿qué hace que un componente de paleta de comandos sea bueno? Aquí hay una lista concisa de cosas a tener en cuenta:

  • Un atajo simple para abrir la paleta, es decir,ctrl + k
  • Se puede acceder desde cualquier lugar de la aplicación.
  • Tiene amplias funciones de búsqueda, como la búsqueda difusa.
  • Los comandos comunican la intención y son fáciles de entender
  • Proporciona acceso a todas las partes de la aplicación desde un solo lugar.

En la siguiente sección, crearemos nuestro propio componente que incluye todas las funciones enumeradas anteriormente. ¡Entremos en ello!

 

Construyendo el componente

La paleta de comandos en realidad no es tan compleja como parece y cualquiera puede crear una rápidamente. He preparado un proyecto inicial para este tutorial para que pueda seguirlo fácilmente. El proyecto inicial es un React and Vite SPA que replica la página de problemas de Linear.

Configurando el proyecto

Para comenzar, clone el repositorio en su directorio local, instale las dependencias necesarias e inicie el servidor de desarrollo. El proyecto usa Yarn, pero si se siente más cómodo con npm o pnPm, puede eliminar el yarn.lockarchivo antes de ejecutarlo npm installo 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

Si visitas localhost:3000, verás la siguiente página:

 

el CommandPalettecomponente

A continuación, construiremos el componente. Usaremos la interfaz de usuario sin cabeza comboboxy los dialogcomponentes. comboboxserá el componente base para nuestra paleta de comandos. Tiene funciones integradas como la gestión del enfoque y la interacción con el teclado. Usaremos el dialogcomponente para representar nuestra paleta de comandos en un modal.

Para diseñar los componentes, usaremos Tailwind CSS. Tailwind es una biblioteca de utilidades CSS que le permite agregar fácilmente estilos en línea en sus archivos HTML o JSX. El proyecto inicial ya incluye la configuración para Tailwind.

Instale las dependencias necesarias de la siguiente manera:

$ yarn add @headlessui/react @heroicons/react

En la componentscarpeta, cree un CommandPalette.jsxarchivo y agregue el siguiente bloque 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>
  );
};

Algunas cosas están sucediendo aquí. Primero, importamos los componentes Dialogy . se representa como un envoltorio alrededor de , e inicializamos un estado local llamado para controlar el modal.ComboboxDialogComboboxisOpen

Representamos un Dialog.Overlayinterior del Dialogcomponente para que sirva como superposición para el modal. Puedes diseñar esto como quieras, pero aquí, solo estamos usando backdrop-blur. Luego, renderizamos el Comboboxcomponente y pasamos una función de controlador al onChangeaccesorio. Se llama a este controlador cada vez que se selecciona un elemento en el archivo Combobox. Por lo general, querrá navegar a una página o ejecutar una acción aquí, pero por ahora, solo cerramos el archivo Dialog.

Combobox.Inputmanejará la función de búsqueda, que agregaremos más adelante en esta sección. Combobox.Optionsrepresenta un ulelemento que envuelve la lista de resultados que representaremos. Pasamos un staticaccesorio que indica que queremos ignorar el estado administrado internamente del componente.

A continuación, renderizamos nuestro CommandPaletteen el App.jsxarchivo:

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>
   );
};

Hablemos de cómo funcionará nuestra paleta de comandos. Tenemos una lista de comandos predefinidos en el data/seed.jsonarchivo. Estos comandos se mostrarán en la paleta cuando se abra y se pueden filtrar según la consulta de búsqueda. Bastante simple, ¿verdad?

el CommandGroupcomponente

CommandPaletterecibe un commandsaccesorio, que es la lista de comandos que importamos de seed.json. Ahora, cree un CommandGroup.jsxarchivo en la componentscarpeta y agregue el siguiente 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>
   );
};

Simplemente estamos usando el CommandGroupcomponente para evitar algún código repetitivo. Si observa la paleta de comandos Lineal, verá que los comandos se agrupan según el contexto. Para implementar esto, necesitamos filtrar los comandos que pertenecen al mismo grupo y repetir esa lógica para cada grupo.

El CommandGroupcomponente recibe dos props, commandsy group. Filtraremos los comandos según el grupo actual y los renderizaremos usando el Combobox.Optioncomponente. Usando accesorios de representación, podemos obtener el activeelemento y diseñarlo en consecuencia, lo que nos permite representar CommandGrouppara cada grupo CommandPalettemientras mantenemos el código limpio.

Tenga en cuenta que tenemos una mapCommandGroupToIconfunción en algún lugar del bloque de código anterior. Esto se debe a que cada grupo tiene un ícono diferente y la función es solo una ayuda para representar el ícono correcto para el grupo actual. Ahora, agregue la función justo debajo del CommandGroupcomponente en el mismo archivo:

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

Ahora, necesitamos renderizar el CommandGroupcomponente en formato CommandPalette.
Importe el componente de la siguiente manera:

import { CommandGroup } from "./CommandGroup";

Renderízalo dentro de 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>

Debería ver la lista de comandos que se están procesando ahora. El siguiente paso es conectar la función de búsqueda.

Implementando la función de búsqueda

Cree una variable de estado local en CommandPalette.jsx:

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

Pase el controlador de actualización de estado al onChangeaccesorio en Combobox.Input. Se queryactualizará con cada carácter que escriba en el cuadro 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)}
/>

Una de las propiedades clave de una buena paleta de comandos es la amplia funcionalidad de búsqueda. Podemos hacer una simple comparación de cadenas de la consulta de búsqueda con los comandos, sin embargo, eso no tendría en cuenta los errores tipográficos y el contexto. Una solución mucho mejor que no introduce demasiada complejidad es una búsqueda difusa.

Usaremos la biblioteca Fuse.js para esto. Fuse.js es una biblioteca de búsqueda difusa, ligera y potente sin dependencias. Si no está familiarizado con la búsqueda aproximada, es una técnica de coincidencia de cadenas que favorece la coincidencia aproximada sobre la coincidencia exacta, lo que implica que puede obtener sugerencias correctas incluso si la consulta tiene errores ortográficos o tipográficos.

Primero, instale la biblioteca Fuse.js:

$ yarn add fuse.js

En CommandPalette.jsx, crea una instancia de la Fuseclase con una lista de comandos:

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

La Fuseclase acepta una serie de comandos y opciones de configuración. El keyscampo es donde registramos qué campos están en la lista de comandos para ser indexados por Fuse.js. Ahora, cree una función que manejará la búsqueda y devolverá los resultados filtrados:

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

Comprobamos si queryestá vacío, devolvemos todos los comandos y, si no, ejecutamos el fuse.searchmétodo con la consulta. Además, estamos mapeando los resultados para crear un nuevo objeto. Esto es para mantener la consistencia porque los resultados devueltos por Fuse.js tienen algunos campos nuevos y no coincidirán con la estructura que ya tenemos.

Ahora, pasa el filteredCommandsal commandsaccesorio en cada CommandGroupcomponente. Debería verse como el siguiente código:

// 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"/>

Intente buscar en la paleta de comandos y vea si los resultados se están filtrando:

 

Tenemos una paleta de comandos completamente funcional, pero puede notar que siempre está abierta. Necesitamos poder controlar su estado abierto. Definamos un evento de teclado que escuchará una combinación de teclas y actualizará el estado abierto. Agregue el siguiente 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);
  };
}, []);

Usamos un useEffectgancho para registrar un keydownevento de teclado cuando se monta el componente y usamos una función de limpieza para eliminar el oyente cuando se desmonta el componente.

En el Hook, comprobamos si la combinación de teclas coincide ctrl + k. Si es así, entonces el estado abierto se establece en true. También puede usar una combinación de teclas diferente, pero es importante no usar combinaciones que entren en conflicto con los accesos directos nativos del navegador.

¡Eso es todo! Puede encontrar la versión terminada de este proyecto en la rama de proyectos terminados .

react-command-palette: componente preconstruido

Hemos explorado cómo crear un componente de paleta de comandos desde cero. Sin embargo, probablemente prefiera no crear la suya propia cada vez que necesite una paleta de comandos. Ahí es donde un componente preconstruido puede ser útil. La mayoría de las bibliotecas de componentes no ofrecen una paleta de comandos, pero react-command-palette es un componente bien escrito que es accesible y compatible con el navegador.

Para usar este componente, instálelo como una dependencia en su proyecto:

$ yarn add react-command-palette

Importe el componente y pásele su lista de comandos de la siguiente manera:

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>
  );
}

Hay muchas opciones de configuración que puede usar para personalizar la apariencia y el comportamiento para cumplir con sus requisitos. Por ejemplo, la themeconfiguración le permite elegir entre varios temas integrados o crear su propio tema personalizado.

Próximos pasos

En este artículo, aprendió sobre las paletas de comandos, los casos de uso ideales para ellas y qué características componen una buena paleta de comandos. También ha explorado en pasos detallados cómo crear uno usando el componente de cuadro combinado de interfaz de usuario sin cabeza y Tailwind CSS.

Si solo desea enviar rápidamente esta función en su aplicación, entonces un componente preconstruido como react-command-palette es el camino a seguir. Gracias por leer, y asegúrese de dejar un comentario si tiene alguna pregunta.

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

#tailwindcss #headless #react 

Cree Una Paleta De Comandos Con Tailwind CSS Y Headless UI
Hoang  Kim

Hoang Kim

1656662400

Tạo Bảng Lệnh Với Tailwind CSS Và Headless UI Combobox

Là nhà phát triển, chúng tôi thường cố gắng tối ưu hóa quy trình công việc của mình nhiều nhất có thể, tiết kiệm thời gian bằng cách tận dụng các công cụ như thiết bị đầu cuối. Bảng lệnh là một trong những công cụ hiển thị hoạt động gần đây trong ứng dụng web hoặc máy tính để bàn, cho phép điều hướng nhanh chóng, dễ dàng truy cập vào các lệnh và phím tắt, trong số những thứ khác.

Để nâng cao mức năng suất của bạn, bảng lệnh về cơ bản là một thành phần giao diện người dùng có dạng một phương thức. Ví dụ, một bảng lệnh đặc biệt hữu ích trong các ứng dụng lớn, phức tạp với nhiều bộ phận chuyển động, nơi bạn có thể mất vài lần nhấp chuột hoặc lướt qua nhiều menu thả xuống để truy cập tài nguyên.

Trong hướng dẫn này, chúng ta sẽ khám phá cách tạo một bảng lệnh đầy đủ chức năng từ đầu bằng cách sử dụng thành phần Headless UI Combobox và Tailwind CSS.

Các trường hợp sử dụng trong thế giới thực cho bảng lệnh

Là một nhà phát triển, có khả năng rất cao là bạn đã sử dụng bảng lệnh trước đó. Phổ biến nhất là bảng lệnh VS Code , nhưng có nhiều ví dụ khác, bao gồm Bảng lệnh GitHub, Linear, Figma, Slack, Monkeytype , v.v.

Ứng dụng GitHub

GitHub gần đây đã phát hành một tính năng bảng lệnh vẫn đang trong giai đoạn thử nghiệm công khai tại thời điểm viết bài. Nó cho phép bạn nhanh chóng chuyển đến các trang khác nhau, tìm kiếm lệnh và nhận đề xuất dựa trên ngữ cảnh hiện tại của bạn. Bạn cũng có thể thu hẹp phạm vi tài nguyên bạn đang tìm kiếm bằng cách chuyển sang một trong các tùy chọn hoặc sử dụng một ký tự đặc biệt:

Bảng lệnh Github

Ứng dụng tuyến tính

Nếu bạn không quen thuộc với Linear , thì đó là một công cụ quản lý dự án tương tự như Jira và Asana mang lại trải nghiệm người dùng thực sự tuyệt vời. Linear có một bảng lệnh rất trực quan cho phép bạn truy cập toàn bộ chức năng của ứng dụng với thiết kế ưu tiên bàn phím. Trong hướng dẫn này, chúng tôi sẽ xây dựng một bảng lệnh tương tự như Linear:

Bảng lệnh ứng dụng tuyến tính

Các tính năng cơ bản của bảng lệnh

Một số ứng dụng hiện đại đang triển khai bảng lệnh như một tính năng, nhưng điều gì tạo nên một thành phần bảng lệnh tốt? Dưới đây là danh sách ngắn gọn những điều cần chú ý:

  • Một phím tắt đơn giản để mở bảng màu, tức làctrl + k
  • Nó có thể được truy cập từ bất kỳ đâu trong ứng dụng
  • Nó có các tính năng tìm kiếm mở rộng, chẳng hạn nhÆ° tìm kiếm mờ
  • Các lệnh truyền đạt ý định và dễ hiểu
  • Nó cung cấp quyền truy cập vào mọi phần của ứng dụng từ một nÆ¡i

Trong phần tiếp theo, chúng tôi sẽ xây dựng thành phần của riêng mình bao gồm tất cả các tính năng được liệt kê ở trên. Hãy vào đó!

 

Xây dựng thành phần

Bảng lệnh thực ra không quá phức tạp và bất kỳ ai cũng có thể tạo một bảng lệnh một cách nhanh chóng. Tôi đã chuẩn bị một dự án bắt đầu cho hướng dẫn này để bạn có thể dễ dàng làm theo. Dự án khởi động là một React và Vite SPA sao chép trang Các vấn đề tuyến tính.

Thiết lập dự án

Để bắt đầu, hãy sao chép kho lưu trữ vào thư mục cục bộ của bạn, cài đặt các phụ thuộc cần thiết và khởi động máy chủ phát triển. Dự án sử dụng Yarn, nhưng nếu bạn cảm thấy thoải mái hơn với npm hoặc pnPm, bạn có thể xóa yarn.locktệp trước khi chạy npm installhoặc 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

Nếu bạn truy cập localhost:3000, bạn sẽ thấy trang sau:

Bản sao kho lưu trữ Github

Thành CommandPalettephần

Tiếp theo, chúng ta sẽ xây dựng thành phần. Chúng tôi sẽ sử dụng giao diện người dùng Headless comboboxvà dialogcác thành phần. comboboxsẽ là thành phần cơ sở cho bảng lệnh của chúng ta. Nó có các tính năng tích hợp như quản lý tiêu điểm và tương tác bàn phím. Chúng tôi sẽ sử dụng dialogthành phần để hiển thị bảng lệnh của chúng tôi theo một phương thức.

Để tạo kiểu cho các thành phần, chúng tôi sẽ sử dụng Tailwind CSS. Tailwind là một thư viện tiện ích CSS cho phép bạn dễ dàng thêm các kiểu nội tuyến trong các tệp HTML hoặc JSX của mình. Dự án khởi động đã bao gồm cấu hình cho Tailwind.

Cài đặt các phụ thuộc cần thiết như sau:

$ yarn add @headlessui/react @heroicons/react

Trong componentsthư mục, hãy tạo một CommandPalette.jsxtệp và thêm khối mã sau:

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>
  );
};

Một vài điều đang xảy ra ở đây. Đầu tiên, chúng tôi nhập khẩu Dialogvà Comboboxcác thành phần. Dialogđược hiển thị như một trình bao bọc xung quanh Comboboxvà chúng tôi khởi tạo một trạng thái cục bộ được gọi isOpenđể điều khiển phương thức.

Chúng tôi kết xuất một Dialog.Overlaybên trong Dialogthành phần để phục vụ như lớp phủ cho phương thức. Bạn có thể tạo kiểu này theo cách bạn muốn, nhưng ở đây, chúng tôi chỉ đang sử dụng backdrop-blur. Sau đó, chúng tôi kết xuất Comboboxthành phần và chuyển một hàm xử lý cho phần onChangemềm hỗ trợ. Trình xử lý này được gọi bất cứ khi nào một mục được chọn trong Combobox. Bạn thường muốn điều hướng đến một trang hoặc thực hiện một hành động ở đây, nhưng hiện tại, chúng tôi chỉ đóng Dialog.

Combobox.Inputsẽ xử lý chức năng tìm kiếm mà chúng tôi sẽ bổ sung sau trong phần này. Combobox.Optionshiển thị một ulphần tử bao bọc danh sách kết quả mà chúng tôi sẽ hiển thị. Chúng tôi chuyển vào một staticchỗ dựa cho biết chúng tôi muốn bỏ qua trạng thái được quản lý nội bộ của thành phần.

Tiếp theo, chúng tôi kết xuất của chúng tôi CommandPalettetrong App.jsxtệp:

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>
   );
};

Hãy nói về cách bảng lệnh của chúng ta sẽ hoạt động. Chúng tôi có một danh sách các lệnh được xác định trước trong data/seed.jsontệp. Các lệnh này sẽ được hiển thị trong bảng màu khi nó được mở ra và có thể được lọc dựa trên truy vấn tìm kiếm. Khá đơn giản, phải không?

Thành CommandGroupphần

CommandPalettenhận được một chỗ commandsdựa, là danh sách các lệnh mà chúng tôi đã nhập từ đó seed.json. Bây giờ, hãy tạo một CommandGroup.jsxtệp trong componentsthư mục và thêm mã sau:

// 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>
   );
};

Chúng tôi chỉ đơn giản sử dụng CommandGroupthành phần để tránh một số mã lặp lại. Nếu bạn nhìn vào bảng lệnh Linear, bạn sẽ thấy rằng các lệnh được nhóm lại dựa trên ngữ cảnh. Để thực hiện điều này, chúng ta cần lọc ra các lệnh thuộc cùng một nhóm và lặp lại logic đó cho mỗi nhóm.

Thành CommandGroupphần nhận được hai đạo cụ, commandsvà group. Chúng tôi sẽ lọc các lệnh dựa trên nhóm hiện tại và hiển thị chúng bằng cách sử dụng Combobox.Optionthành phần. Sử dụng các đạo cụ kết xuất, chúng tôi có thể lấy activemục và tạo kiểu cho phù hợp, cho phép chúng tôi kết xuất CommandGroupcho từng nhóm trong CommandPalettekhi vẫn giữ mã sạch.

Lưu ý rằng chúng ta có một mapCommandGroupToIconhàm ở đâu đó trong khối mã ở trên. Điều này là do mỗi nhóm có một biểu tượng khác nhau và chức năng này chỉ là một công cụ trợ giúp để hiển thị biểu tượng chính xác cho nhóm hiện tại. Bây giờ, hãy thêm hàm ngay bên dưới CommandGroupthành phần trong cùng một tệp:

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

Bây giờ, chúng ta cần kết xuất CommandGroupthành phần trong CommandPalette.
Nhập thành phần như sau:

import { CommandGroup } from "./CommandGroup";

Kết xuất nó bên trong Combobox.Optionscho mỗi nhóm:

<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>

Bạn sẽ thấy danh sách các lệnh đang được hiển thị ngay bây giờ. Bước tiếp theo là kết nối chức năng tìm kiếm.

Triển khai chức năng tìm kiếm

Tạo một biến trạng thái cục bộ trong CommandPalette.jsx:

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

Chuyển trình xử lý cập nhật trạng thái đến phần onChangehỗ trợ Combobox.Input. querySẽ được cập nhật với mọi ký tự bạn nhập vào ô nhập liệu :

<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)}
/>

Một trong những đặc tính chính của một bảng lệnh tốt là chức năng tìm kiếm mở rộng. Chúng tôi chỉ có thể thực hiện một phép so sánh chuỗi đơn giản của truy vấn tìm kiếm với các lệnh, tuy nhiên điều đó sẽ không tính đến lỗi chính tả và ngữ cảnh. Một giải pháp tốt hơn nhiều mà không giới thiệu quá nhiều phức tạp là tìm kiếm mờ.

Chúng tôi sẽ sử dụng thư viện Fuse.js cho việc này. Fuse.js là một thư viện tìm kiếm mờ, nhẹ, mạnh mẽ và không có phụ thuộc. Nếu bạn không quen với tìm kiếm mờ, thì đó là một kỹ thuật đối sánh chuỗi ủng hộ đối sánh gần đúng hơn đối sánh chính xác, ngụ ý rằng bạn có thể nhận được đề xuất chính xác ngay cả khi truy vấn có lỗi chính tả hoặc lỗi chính tả.

Đầu tiên, hãy cài đặt thư viện Fuse.js:

$ yarn add fuse.js

Trong CommandPalette.jsx, khởi tạo Fuselớp bằng danh sách các lệnh:

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

Lớp Fusechấp nhận một loạt các lệnh và các tùy chọn cấu hình. Trường keyslà nơi chúng ta đăng ký những trường nào trong danh sách lệnh sẽ được lập chỉ mục bởi Fuse.js. Bây giờ, hãy tạo một hàm sẽ xử lý tìm kiếm và trả về kết quả đã lọc:

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

Chúng tôi kiểm tra xem querycó trống không, trả về tất cả các lệnh và nếu không, hãy chạy fuse.searchphương thức với truy vấn. Ngoài ra, chúng tôi đang ánh xạ các kết quả để tạo một đối tượng mới. Điều này là để duy trì tính nhất quán vì kết quả trả về bởi Fuse.js có một số trường mới và sẽ không khớp với cấu trúc mà chúng ta đã có.

Bây giờ, hãy chuyển filteredCommandscho phần commandshỗ trợ trong mỗi CommandGroupthành phần. Nó sẽ giống như mã bên dưới:

// 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"/>

Hãy thử tìm kiếm trong bảng lệnh và xem kết quả có đang được lọc hay không:

Bộ lọc tìm kiếm bảng lệnh

Chúng tôi có một bảng lệnh đầy đủ chức năng, nhưng bạn có thể nhận thấy rằng nó luôn mở. Chúng ta cần có khả năng kiểm soát trạng thái mở của nó. Hãy xác định một sự kiện bàn phím sẽ lắng nghe tổ hợp phím và cập nhật trạng thái mở. Thêm mã sau vào 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);
  };
}, []);

Chúng tôi đang sử dụng useEffectHook để đăng ký keydownsự kiện bàn phím khi thành phần được gắn kết và chúng tôi sử dụng chức năng dọn dẹp để xóa trình nghe khi thành phần ngắt kết nối.

Trong Hook, chúng tôi kiểm tra xem tổ hợp phím có khớp không ctrl + k. Nếu có, thì trạng thái mở được đặt thành true. Bạn cũng có thể sử dụng một tổ hợp phím khác, nhưng điều quan trọng là không sử dụng các tổ hợp xung đột với các phím tắt của trình duyệt gốc.

Đó là nó! Bạn có thể tìm thấy phiên bản hoàn chỉnh của dự án này trên nhánh dự án đã hoàn thành .

react-command-Palette: Thành phần dựng sẵn

Chúng tôi đã khám phá cách xây dựng một thành phần bảng lệnh từ đầu. Tuy nhiên, có lẽ bạn không nên tự xây dựng mỗi khi bạn cần một bảng lệnh. Đó là nơi mà một thành phần dựng sẵn có thể hữu ích. Hầu hết các thư viện thành phần không cung cấp bảng lệnh, nhưng bảng phản ứng lệnh là một thành phần được viết tốt, có thể truy cập và tương thích với trình duyệt.

Để sử dụng thành phần này, hãy cài đặt nó làm thành phần phụ thuộc trong dự án của bạn:

$ yarn add react-command-palette

Nhập thành phần và chuyển danh sách lệnh của bạn cho nó như sau:

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>
  );
}

Có rất nhiều tùy chọn cấu hình mà bạn có thể sử dụng để tùy chỉnh giao diện và hành vi đáp ứng yêu cầu của mình. Ví dụ: themecấu hình cho phép bạn chọn từ một số chủ đề tích hợp sẵn hoặc tạo chủ đề tùy chỉnh của riêng bạn.

Bước tiếp theo

Trong bài viết này, bạn đã tìm hiểu về các bảng lệnh, các trường hợp sử dụng lý tưởng cho chúng và những tính năng nào tạo nên một bảng lệnh tốt. Bạn cũng đã khám phá các bước chi tiết về cách tạo một công cụ bằng cách sử dụng thành phần hộp kết hợp Headless UI và Tailwind CSS.

Nếu bạn chỉ muốn nhanh chóng đưa tính năng này vào ứng dụng của mình, thì một thành phần được xây dựng sẵn như react-command-Palette là cách để thực hiện. Cảm ơn bạn đã đọc, và hãy để lại bình luận nếu bạn có bất kỳ câu hỏi nào.

Nguồn: https://blog.logrocket.com/react-command-palette-tailwind-css-headless-ui/

#tailwindcss #headless #react 

Tạo Bảng Lệnh Với Tailwind CSS Và Headless UI Combobox

Create A Command Palette with Tailwind CSS and Headless UI

As developers, we often strive to optimize our workflows as much as possible, saving time by leveraging tools like the terminal. A command palette is one such tool that displays recent activity in a web or desktop application, enabling quick navigation, easy access to commands, and shortcuts, among other things.

To elevate your productivity level, a command palette is essentially a UI component that takes the form of a modal. A command palette is especially useful in large, complex applications with many moving parts, for example, where it might take you several clicks or skimming through multiple dropdowns to access a resource.

In this tutorial, we’ll explore how to build a fully functional command palette from scratch using the Headless UI Combobox component and Tailwind CSS.

See more at: https://blog.logrocket.com/react-command-palette-tailwind-css-headless-ui/

#tailwindcss #headless #react 

Create A Command Palette with Tailwind CSS and Headless UI
Paris  Kessler

Paris Kessler

1656601200

Vite: Vue 2 Starter Template using Composition-api and WindiCSS

Vite ⚡ - Vue 2 starter template

Create a Vue 2 application bundled by the lightning fast build tool Vite

✨ Live Demo

Features

Vite plugins

Try it now!

GitHub Template

Create a repo from this template on GitHub

Clone

If you prefer to do it manually with a cleaner git history

npx degit lstoeferle/vite-vue2-windicss-starter my-vite-vue2-app
cd my-vite-vue2-app
yarn install

Usage

Development

Just run and visit http://localhost:3333

yarn dev

Build

To build the App, run

yarn build

And you will see the generated files in dist, which are ready to be served.

Why

Vue 3 is awesome, but we should not forget about supporting Vue 2 ♥️

Credits

This project is inspired by Vitesse, an opinionated Vite starter template for Vue 3.

Big thanks to Anthony Fu for the inspiration and all the amazing tools you create.


Author: lstoeferle
Source code: https://github.com/lstoeferle/vite-vue2-windicss-starter
License: MIT license

#vite #vue #tailwindcss #typescript 

Vite: Vue 2 Starter Template using Composition-api and WindiCSS
Joseph  Murray

Joseph Murray

1656597480

Smelte: UI Framework with Material Components Built With Svelte

Smelte 

UI framework with material components built with Svelte and Tailwind CSS

Features

  • Default typography per Material design spec
  • Material icons
  • CSS ripple animation
  • Image lazy loading
  • Now SSR deployment
  • Color palette generator
  • Improve Purge CSS
  • Theming
  • Image processing (done here)
  • Svelte template (without Sapper)
  • JS ripple animation
  • Dark mode

Demo

Smelte is a UI framework built on top of Svelte and Tailwind CSS using Material Design spec (hence the name). It comes with many components and utility functions making it easy to build beautiful responsive layouts while keeping bundle size and performance at check all thanks to Svelte.

Installation

To get you started you need to add Smelte to your dependencies with your favorite package manager

$ npm install smelte

or

$ yarn add smelte

Then add the Smelte Rollup plugin (after svelte but before css). Webpack support coming soon.

const smelte = require("smelte/rollup-plugin-smelte");

plugins = [
  ...your plugins,
  smelte({
    purge: production,
    output: "public/global.css", // it defaults to static/global.css which is probably what you expect in Sapper
    postcss: [], // Your PostCSS plugins
    whitelist: [], // Array of classnames whitelisted from purging
    whitelistPatterns: [], // Same as above, but list of regexes
    tailwind: {
      theme: {
        extend: {
          spacing: {
            72: "18rem",
            84: "21rem",
            96: "24rem"
          }
        }
      }, // Extend Tailwind theme
      colors: {
        primary: "#b027b0",
        secondary: "#009688",
        error: "#f44336",
        success: "#4caf50",
        alert: "#ff9800",
        blue: "#2196f3",
        dark: "#212121"
      }, // Object of colors to generate a palette from, and then all the utility classes
      darkMode: true,
    }, // Any other props will be applied on top of default Smelte tailwind.config.js
  }),
]

Then you should add Tailwind utilites CSS in your app component.

import "smelte/src/tailwind.css";

You might also need to include material icons in your template's if you use any:

<link
  href="https://fonts.googleapis.com/icon?family=Material+Icons"
  rel="stylesheet"
/>

Or ship them along with Roboto if you would like to use default material font

<link
  href="https://fonts.googleapis.com/css?family=Roboto:300,400,500|Material+Icons&display=swap"
  rel="stylesheet"
/>

And you're good to go and have all the Tailwind CSS power all to yourself!

For treeshaking to work it is recommended to import each component on its own like this:

import Button from "smelte/src/components/Button";
import Treeview from "smelte/src/components/Treeview";

Components

  •  Text field
  •  Button
  •  Select
  •  Checkbox
  •  Radio
  •  List
  •  Chip
  •  Menu
  •  Navigation drawer
  •  Snackbar
  •  Dialog
  •  Card
  •  Slider
  •  Proper customization via class props
  •  Data table
  •  Autocomplete
  •  Tooltip
  •  Revise events (on:change, on:focus, on:input...)
  •  Treeview
  •  Date picker

Download Details:
Author: matyunya
Source Code: https://github.com/matyunya/smelte
License: MIT license

#svelte #javascript #tailwindcss 

Smelte: UI Framework with Material Components Built With Svelte
Meggie  Flatley

Meggie Flatley

1656550800

Opinionated Vite + Vue 3 + TypeScript + Tailwind CSS

Vite + Vue 3 + Typescript + Tailwind Starter

Simple, opinionated, fully typed, and production-ready project template for Vite.

This template is practical and batteries included but not overly complex or bloated. Also explicit configuration over magic; it should be easy to understand how everything works, strip out anything you don't need, and fast to modify things to your needs.

Includes plenty of examples and documentation of how to do things but minimal cruft to delete to get you going.

Please check out the homepage for full docs. A detailed changelog is available. This repository is also mirrored on GitLab.

Features

  • Vue 3.2
  • Pinia store (fully typed Vuex store is available up to version 1.13)
  • Routing using vue-router 4
  • TypeScript 4.7
  • Automatic package and component imports w/ unplugin-auto-import and unplugin-vue-components
  • Tailwind CSS 3 w/ following plugins and configuration preinstalled:
    • @tailwindcss/aspect-ratio
    • @tailwindcss/line-clamp
    • @tailwindcss/typography
    • @tailwindcss/forms
    • firefox-variant
  • PostCSS 8 w/ postcss-nesting plugin and cssnano for minimizing production CSS
  • Eslint
  • Prettier
  • Alias @ to <project_root>/src
  • Predefined and fully typed global variables:
    • VITE_APP_VERSION is read from package.json version at build time
    • VITE_APP_BUILD_EPOCH is populated as new Date().getTime() at build time
  • Using newest script setup syntax w/ Ref sugar (see the official Script Setup documentation and Ref Sugar RFC discussion)
  • Vitest unit tests
  • Playwright e2e + component tests
  • GitHub workflows
    • Renovatebot for keeping up with dependencies
    • Automated unit tests
    • Automated component tests
    • Automated e2e tests
  • GitLab CI config available up to versions 2.x

Elsewhere

Contributing

Contributions are welcome! Please follow the code of conduct when interacting with others.


Author: Uninen
Source code: https://github.com/Uninen/vite-ts-tailwind-starter
License: MIT license

#vite #vitejs #vue #vuejs #typescript #tailwindcss 

Opinionated Vite + Vue 3 + TypeScript + Tailwind CSS