曾 俊

曾 俊

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 installpnpm 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 UIcomboboxdialog组件。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>
  );
};

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

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

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

接下来,我们CommandPaletteApp.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组件接收两个 propscommandsgroup. 我们将根据当前组过滤命令并使用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":

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

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("");

将状态更新处理程序传递onChangeCombobox.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 

What is GEEK

Buddha Community

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

Reggie Hudson

1627084440

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

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

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

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

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

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

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

#headless #ui #react

#headless #ui #react #tailwind css #css

What is Tailwind CSS useful for?

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

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

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

Hire CSS Developer

Want to develop a website or re-design using CSS Development?

We build a website and we implemented CSS successfully if you are planning to Hire CSS Developer from HourlyDeveloper.io, We can fill your Page with creative colors and attractive Designs. We provide services in Web Designing, Website Redesigning and etc.

For more details…!!
Consult with our experts:- https://bit.ly/3hUdppS

#hire css developer #css development company #css development services #css development #css developer #css

Tailwind CSS tutorial

In this tutorial I would like to introduce you to one of the fastest growing and promising CSS Frameworks at the moment, Tailwind CSS. It is different from other frameworks, such as Bootstrap, because it is built on a new way of building user interfaces using a utility-first CSS classes structure, as opposed to the OOCSS structure from other frameworks.

By the end of this guide you will be able to install, configure and build a responsive hero section (live demo) using the utility-first classes from Tailwind CSS and configure the project using the recommended PostCSS powered Tailwind configuration file for better maintainability and versatility.

Here’s the table of contents for this tutorial for Tailwind CSS:

  • Introducing Tailwind CSS
  • Adding Tailwind CSS to your project via a package manager
  • Creating the configuration file and process your CSS with Tailwind
  • Building a responsive hero section using the utility-first classes from Tailwind
  • Customize fonts, colors and add extra classes using the configuration file
  • Reduce loading time and file size by purging the unused classes from your CSS
  • Conclusion and summary

Read the full tutorial from Themesberg.

#tailwind #tailwind-css #tailwind-css-tutorial #tutorial #open-source