From 4460529b37a7aaf314e3f3bbaa075592f6d7e814 Mon Sep 17 00:00:00 2001 From: Prateek Shourya Date: Fri, 23 May 2025 13:53:16 +0530 Subject: [PATCH] [WEB-4154] fix: dropdown container classname (#7085) * fix: dropdown container classname * improvement: update string utils for joinWithConjunction * improvement: add more string utils --- packages/ui/src/dropdown/dropdown.d.ts | 2 +- packages/ui/src/dropdown/multi-select.tsx | 22 ++++----- packages/ui/src/dropdown/single-select.tsx | 22 ++++----- packages/utils/src/string.ts | 57 ++++++++++------------ 4 files changed, 48 insertions(+), 55 deletions(-) diff --git a/packages/ui/src/dropdown/dropdown.d.ts b/packages/ui/src/dropdown/dropdown.d.ts index dd441d0a8..8d1159e7c 100644 --- a/packages/ui/src/dropdown/dropdown.d.ts +++ b/packages/ui/src/dropdown/dropdown.d.ts @@ -4,7 +4,7 @@ export interface IDropdown { // root props onOpen?: () => void; onClose?: () => void; - containerClassName?: (isOpen: boolean) => string; + containerClassName?: string | ((isOpen: boolean) => string); tabIndex?: number; placement?: Placement; disabled?: boolean; diff --git a/packages/ui/src/dropdown/multi-select.tsx b/packages/ui/src/dropdown/multi-select.tsx index 25f22c6be..400e2c728 100644 --- a/packages/ui/src/dropdown/multi-select.tsx +++ b/packages/ui/src/dropdown/multi-select.tsx @@ -1,19 +1,14 @@ -import React, { FC, useMemo, useRef, useState } from "react"; -import sortBy from "lodash/sortBy"; -// headless ui import { Combobox } from "@headlessui/react"; -// popper-js +import sortBy from "lodash/sortBy"; +import React, { FC, useMemo, useRef, useState } from "react"; import { usePopper } from "react-popper"; -// plane helpers +// plane imports import { useOutsideClickDetector } from "@plane/hooks"; -// components +// local imports +import { cn } from "../../helpers"; +import { useDropdownKeyPressed } from "../hooks/use-dropdown-key-pressed"; import { DropdownButton } from "./common"; import { DropdownOptions } from "./common/options"; -// hooks -import { useDropdownKeyPressed } from "../hooks/use-dropdown-key-pressed"; -// helper -import { cn } from "../../helpers"; -// types import { IMultiSelectDropdown } from "./dropdown"; export const MultiSelectDropdown: FC = (props) => { @@ -118,7 +113,10 @@ export const MultiSelectDropdown: FC = (props) => { ref={dropdownRef} value={value} onChange={onChange} - className={cn("h-full", containerClassName)} + className={cn( + "h-full", + typeof containerClassName === "function" ? containerClassName(isOpen) : containerClassName + )} tabIndex={tabIndex} multiple onKeyDown={handleKeyDown} diff --git a/packages/ui/src/dropdown/single-select.tsx b/packages/ui/src/dropdown/single-select.tsx index bcdff40c1..9614feb51 100644 --- a/packages/ui/src/dropdown/single-select.tsx +++ b/packages/ui/src/dropdown/single-select.tsx @@ -1,19 +1,14 @@ -import React, { FC, useMemo, useRef, useState } from "react"; -import sortBy from "lodash/sortBy"; -// headless ui import { Combobox } from "@headlessui/react"; -// popper-js +import sortBy from "lodash/sortBy"; +import React, { FC, useMemo, useRef, useState } from "react"; import { usePopper } from "react-popper"; -// plane helpers +// plane imports import { useOutsideClickDetector } from "@plane/hooks"; -// components +// local imports +import { cn } from "../../helpers"; +import { useDropdownKeyPressed } from "../hooks/use-dropdown-key-pressed"; import { DropdownButton } from "./common"; import { DropdownOptions } from "./common/options"; -// hooks -import { useDropdownKeyPressed } from "../hooks/use-dropdown-key-pressed"; -// helper -import { cn } from "../../helpers"; -// types import { ISingleSelectDropdown } from "./dropdown"; export const Dropdown: FC = (props) => { @@ -118,7 +113,10 @@ export const Dropdown: FC = (props) => { ref={dropdownRef} value={value} onChange={onChange} - className={cn("h-full", containerClassName)} + className={cn( + "h-full", + typeof containerClassName === "function" ? containerClassName(isOpen) : containerClassName + )} tabIndex={tabIndex} onKeyDown={handleKeyDown} disabled={disabled} diff --git a/packages/utils/src/string.ts b/packages/utils/src/string.ts index 19840df4d..d663c49c9 100644 --- a/packages/utils/src/string.ts +++ b/packages/utils/src/string.ts @@ -86,36 +86,6 @@ export const copyUrlToClipboard = async (path: string) => { await copyTextToClipboard(url.toString()); }; -/** - * @description Generates a deterministic HSL color based on input string - * @param {string} string - Input string to generate color from - * @returns {string} HSL color string - * @example - * generateRandomColor("hello") // returns consistent HSL color for "hello" - * generateRandomColor("") // returns "rgb(var(--color-primary-100))" - */ -export const generateRandomColor = (string: string): string => { - if (!string) return "rgb(var(--color-primary-100))"; - - string = `${string}`; - - const uniqueId = string.length.toString() + string; - const combinedString = uniqueId + string; - - const hash = Array.from(combinedString).reduce((acc, char) => { - const charCode = char.charCodeAt(0); - return (acc << 5) - acc + charCode; - }, 0); - - const hue = hash % 360; - const saturation = 70; - const lightness = 60; - - const randomColor = `hsl(${hue}, ${saturation}%, ${lightness}%)`; - - return randomColor; -}; - /** * @description Gets first character of first word or first characters of first two words * @param {string} str - Input string @@ -275,6 +245,33 @@ export const checkURLValidity = (url: string): boolean => { return urlPattern.test(url); }; +/** + * Combines array elements with a separator and adds a conjunction before the last element + * @param array Array of strings to combine + * @param separator Separator to use between elements (default: ", ") + * @param conjunction Conjunction to use before last element (default: "and") + * @returns Combined string with conjunction before the last element + */ +export const joinWithConjunction = (array: string[], separator: string = ", ", conjunction: string = "and"): string => { + if (!array || array.length === 0) return ""; + if (array.length === 1) return array[0]; + if (array.length === 2) return `${array[0]} ${conjunction} ${array[1]}`; + + const lastElement = array[array.length - 1]; + const elementsExceptLast = array.slice(0, -1); + + return `${elementsExceptLast.join(separator)}${separator}${conjunction} ${lastElement}`; +}; + +/** + * @description Ensures a URL has a protocol + * @param {string} url + * @returns {string} + * @example + * ensureUrlHasProtocol("example.com") => "http://example.com" + */ +export const ensureUrlHasProtocol = (url: string): string => (url.startsWith("http") ? url : `http://${url}`); + // Browser-only clipboard functions // let copyTextToClipboard: (text: string) => Promise;