Fix tab focus state in New select (#7139)
* Fix tab focus state * Update packages/ui/components/form/selectimproved/components/Select.tsx * Remove flex import
This commit is contained in:
1 parent
b6016c8f20
commit
f77e7476d2
2 files changed
+105
-129
No files matched your search
@@ -1,8 +1,7 @@
|
||||
import * as Popover from "@radix-ui/react-popover";
|
||||
import React, { useCallback, useRef, useState } from "react";
|
||||
|
||||
import { classNames as cn } from "@calcom/lib";
|
||||
import { useKeyPress } from "@calcom/lib/hooks/useKeyPress";
|
||||
import useOnClickOutside from "@calcom/lib/hooks/useOnclickOutside";
|
||||
|
||||
import { FiX, FiChevronDown } from "../../../icon";
|
||||
import Options from "./Options";
|
||||
@@ -55,41 +54,12 @@ function Select<T extends Option>({
|
||||
menuIsOpen = false,
|
||||
classNames,
|
||||
}: SelectProps<T>) {
|
||||
const [open, setOpen] = useState<boolean>(menuIsOpen);
|
||||
const [inputValue, setInputValue] = useState<string>("");
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const [open, setOpen] = useState(menuIsOpen);
|
||||
const searchBoxRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const isMultipleValue = Array.isArray(selectedItems) && isMultiple;
|
||||
|
||||
const toggle = useCallback(() => {
|
||||
if (!isDisabled) {
|
||||
setOpen(!open);
|
||||
}
|
||||
}, [isDisabled, open]);
|
||||
|
||||
const closeDropDown = useCallback(() => {
|
||||
if (open) setOpen(false);
|
||||
}, [open]);
|
||||
|
||||
useOnClickOutside(ref, () => {
|
||||
closeDropDown();
|
||||
});
|
||||
|
||||
useKeyPress("Escape", undefined, () => {
|
||||
closeDropDown();
|
||||
});
|
||||
|
||||
const onPressEnterOrSpace = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
e.preventDefault();
|
||||
if ((e.code === "Enter" || e.code === "Space") && !isDisabled) {
|
||||
toggle();
|
||||
}
|
||||
},
|
||||
[isDisabled, toggle]
|
||||
);
|
||||
|
||||
const removeItem = useCallback(
|
||||
(item: Option) => {
|
||||
// remove the item from the selected items
|
||||
@@ -103,6 +73,11 @@ function Select<T extends Option>({
|
||||
[onChange, selectedItems]
|
||||
);
|
||||
|
||||
const closeDropDown = useCallback(() => {
|
||||
console.log({ open });
|
||||
if (open) setOpen(false);
|
||||
}, [open]);
|
||||
|
||||
const handleValueChange = useCallback(
|
||||
(selected: Option) => {
|
||||
function update() {
|
||||
@@ -148,106 +123,108 @@ function Select<T extends Option>({
|
||||
}}
|
||||
selectedItems={selectedItems}
|
||||
handleValueChange={handleValueChange}>
|
||||
<div className="relative w-full" ref={ref}>
|
||||
<div
|
||||
tabIndex={0}
|
||||
aria-expanded={open}
|
||||
onKeyDown={onPressEnterOrSpace}
|
||||
onClick={toggle}
|
||||
className={cn(
|
||||
"flex max-h-[36px] items-center justify-between rounded-md border border-gray-300 text-sm text-gray-400 transition-all duration-300 focus:outline-none",
|
||||
isDisabled
|
||||
? " dark:bg-darkgray-200 dark:border-darkgray-300 border-gray-200 bg-gray-100 text-gray-400 dark:text-gray-500"
|
||||
: "dark:border-darkgray-300 dark:bg-darkgray-50 dark:text-darkgray-500 dark:focus:border-darkgray-700 dark:focus:bg-darkgray-100 dark:focus:text-darkgray-900 dark:hover:text-darkgray-900 bg-white hover:border-gray-600 focus:border-gray-900"
|
||||
)}>
|
||||
<div className="flex w-full grow-0 items-center gap-1 overflow-x-hidden">
|
||||
<>
|
||||
{((isMultipleValue && selectedItems.length === 0) || selectedItems === null) && (
|
||||
<div className="py-2.5 px-3 text-gray-400 dark:text-current">
|
||||
<p>{placeholder}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{Array.isArray(selectedItems) ? (
|
||||
<div className="over-flow-x flex gap-1 p-1 ">
|
||||
{selectedItems.map((item, index) => (
|
||||
<div
|
||||
className={cn(
|
||||
"dark:bg-darkgray-200 flex items-center space-x-2 rounded bg-gray-200 px-2 py-[6px]"
|
||||
)}
|
||||
key={index}>
|
||||
<p
|
||||
className={cn(
|
||||
classNames?.tagItemText ??
|
||||
"dark:text-darkgray-900 cursor-default select-none truncate text-sm leading-none text-gray-700"
|
||||
)}>
|
||||
{item.label}
|
||||
</p>
|
||||
{!isDisabled && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeItem(item);
|
||||
}}>
|
||||
<FiX
|
||||
className={
|
||||
classNames?.tagItemIcon ??
|
||||
"dark:text-darkgray-700 dark:hover:text-darkgray-900 h-4 w-4 text-gray-500 hover:text-gray-900"
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
<div className="relative w-full">
|
||||
<Popover.Root>
|
||||
<Popover.Trigger>
|
||||
<div
|
||||
className={cn(
|
||||
"min-w-64 flex max-h-[36px] items-center justify-between rounded-md border border-gray-300 text-sm text-gray-400 transition-all duration-300 focus:outline-none",
|
||||
isDisabled
|
||||
? " dark:bg-darkgray-200 dark:border-darkgray-300 border-gray-200 bg-gray-100 text-gray-400 dark:text-gray-500"
|
||||
: "dark:border-darkgray-300 dark:bg-darkgray-50 dark:text-darkgray-500 dark:focus:border-darkgray-700 dark:focus:bg-darkgray-100 dark:focus:text-darkgray-900 dark:hover:text-darkgray-900 bg-white hover:border-gray-600 focus:border-gray-900"
|
||||
)}>
|
||||
<div className="flex w-full grow-0 items-center gap-1 overflow-x-hidden">
|
||||
<>
|
||||
{((isMultipleValue && selectedItems.length === 0) || selectedItems === null) && (
|
||||
<div className="py-2.5 px-3 text-gray-400 dark:text-current">
|
||||
<p>{placeholder}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="dark:text-darkgray-900 py-2.5 px-3 text-sm leading-none text-gray-900">
|
||||
<p>{selectedItems?.label}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="dark:text-darkgray-900 flex flex-none items-center rounded-[6px] p-1.5 text-gray-900 opacity-75 ">
|
||||
{isClearable && !isDisabled && selectedItems !== null && (
|
||||
<div className="cursor-pointer" onClick={clearValue}>
|
||||
<FiX
|
||||
className={classNames && classNames.closeIcon ? classNames.closeIcon : "h-5 w-5 p-0.5"}
|
||||
{Array.isArray(selectedItems) ? (
|
||||
<div className="flex gap-1 overflow-x-scroll p-1 ">
|
||||
{selectedItems.map((item, index) => (
|
||||
<div
|
||||
className={cn(
|
||||
"dark:bg-darkgray-200 flex items-center space-x-2 rounded bg-gray-200 px-2 py-[6px]"
|
||||
)}
|
||||
key={index}>
|
||||
<p
|
||||
className={cn(
|
||||
classNames?.tagItemText ??
|
||||
"dark:text-darkgray-900 cursor-default select-none truncate text-sm leading-none text-gray-700"
|
||||
)}>
|
||||
{item.label}
|
||||
</p>
|
||||
{!isDisabled && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
removeItem(item);
|
||||
}}>
|
||||
<FiX
|
||||
className={
|
||||
classNames?.tagItemIcon ??
|
||||
"dark:text-darkgray-700 dark:hover:text-darkgray-900 h-4 w-4 text-gray-500 hover:text-gray-900"
|
||||
}
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="dark:text-darkgray-900 py-2.5 px-3 text-sm leading-none text-gray-900">
|
||||
<p>{selectedItems?.label}</p>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</div>
|
||||
|
||||
<div className="dark:text-darkgray-900 flex flex-none items-center rounded-[6px] p-1.5 text-gray-900 opacity-75 ">
|
||||
<>
|
||||
{isClearable && !isDisabled && selectedItems !== null && (
|
||||
<div className="cursor-pointer" onClick={clearValue}>
|
||||
<FiX
|
||||
className={
|
||||
classNames && classNames.closeIcon ? classNames.closeIcon : "h-5 w-5 p-0.5"
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FiChevronDown className={cn("h-5 w-5 transition duration-300")} />
|
||||
</>
|
||||
</div>
|
||||
</div>
|
||||
</Popover.Trigger>
|
||||
<Popover.Portal>
|
||||
<Popover.Content>
|
||||
<div
|
||||
className={
|
||||
classNames?.menu ??
|
||||
"dark:bg-darkgray-100 dark:border-darkgray-300 min-w-64 z-10 mt-1.5 overflow-x-hidden rounded border bg-white py-1 text-sm text-gray-700 shadow-sm"
|
||||
}>
|
||||
{isSearchable && (
|
||||
<SearchInput
|
||||
searchInputRef={searchBoxRef}
|
||||
value={inputValue}
|
||||
placeholder={searchInputPlaceholder}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Options
|
||||
searchBoxRef={searchBoxRef}
|
||||
list={options}
|
||||
inputValue={inputValue}
|
||||
isMultiple={isMultiple}
|
||||
selected={selectedItems}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<FiChevronDown
|
||||
className={cn("h-5 w-5 transition duration-300", open && " rotate-180 transform")}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{open && !isDisabled && (
|
||||
<div
|
||||
tabIndex={-1}
|
||||
className={
|
||||
classNames?.menu ??
|
||||
"dark:bg-darkgray-100 dark:border-darkgray-300 absolute z-10 mt-1.5 w-full max-w-[256px] overflow-x-hidden rounded border bg-white py-1 text-sm text-gray-700 shadow-sm"
|
||||
}>
|
||||
{isSearchable && (
|
||||
<SearchInput
|
||||
searchInputRef={searchBoxRef}
|
||||
value={inputValue}
|
||||
placeholder={searchInputPlaceholder}
|
||||
onChange={(e) => setInputValue(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Options
|
||||
searchBoxRef={searchBoxRef}
|
||||
list={options}
|
||||
inputValue={inputValue}
|
||||
isMultiple={isMultiple}
|
||||
selected={selectedItems}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Popover.Content>
|
||||
</Popover.Portal>
|
||||
</Popover.Root>
|
||||
</div>
|
||||
</SelectProvider>
|
||||
);
|
||||
|
||||
@@ -14,7 +14,6 @@ import {
|
||||
|
||||
import { Tooltip } from "../tooltip";
|
||||
import { FiPlus, FiX } from "../icon";
|
||||
import { Flex } from "./Flex";
|
||||
|
||||
<Meta title="Layout/Spacing" />
|
||||
|
||||
|
||||
Reference in new issue
Block a user