From 8c6d5a656162c4ce59338b25d3f9bafbc7747bb8 Mon Sep 17 00:00:00 2001 From: severinlandolt Date: Fri, 13 Dec 2024 16:51:51 +0100 Subject: [PATCH] fix selects --- src/components/input-elements/selectUtils.ts | 75 ++++++++++++++------ 1 file changed, 54 insertions(+), 21 deletions(-) diff --git a/src/components/input-elements/selectUtils.ts b/src/components/input-elements/selectUtils.ts index c733f92a8..3937e6a75 100644 --- a/src/components/input-elements/selectUtils.ts +++ b/src/components/input-elements/selectUtils.ts @@ -1,40 +1,65 @@ import { tremorTwMerge } from "lib"; import React from "react"; -export interface SelectItemProps { +import { isValidElement, JSXElementConstructor } from "react"; + +interface SelectItemProps { value: string; children?: React.ReactNode; } -export const getNodeText = (node: React.ReactElement): string | React.ReactElement | undefined => { - if (["string", "number"].includes(typeof node)) return node; - if (node instanceof Array) return node.map(getNodeText).join(""); - if (typeof node === "object" && node) return getNodeText(node.props.children); +type ValidElement = React.ReactElement>; + +function isSelectItemElement(node: unknown): node is ValidElement { + if (!isValidElement(node)) return false; + const props = node.props as Partial; + return typeof props.value === "string"; +} + +const getNodeText = (node: unknown): string | undefined => { + if (typeof node === "string" || typeof node === "number") { + return String(node); + } + + if (Array.isArray(node)) { + return node.map(getNodeText).join(""); + } + + if (isValidElement<{ children?: React.ReactNode }>(node)) { + return getNodeText(node.props.children); + } + + return undefined; }; -export function constructValueToNameMapping(children: React.ReactElement[] | React.ReactElement) { +function constructValueToNameMapping(children: React.ReactNode): Map { const valueToNameMapping = new Map(); - React.Children.map(children, (child: React.ReactElement) => { - valueToNameMapping.set(child.props.value, (getNodeText(child) ?? child.props.value) as string); + + const validChildren = React.Children.toArray(children).filter((child): child is ValidElement => + isSelectItemElement(child), + ); + + validChildren.forEach((child) => { + const value = child.props.value; + const displayText = getNodeText(child) ?? value; + valueToNameMapping.set(value, String(displayText)); }); + return valueToNameMapping; } -export function getFilteredOptions( - searchQuery: string, - children: React.ReactElement[], -): React.ReactElement[] { - return React.Children.map(children, (child) => { - const optionText = (getNodeText(child) ?? child.props.value) as string; - if (optionText.toLowerCase().includes(searchQuery.toLowerCase())) return child; +function getFilteredOptions(searchQuery: string, children: React.ReactNode): ValidElement[] { + return React.Children.toArray(children).filter((child): child is ValidElement => { + if (!isSelectItemElement(child)) { + return false; + } + + const optionText = String(getNodeText(child) ?? child.props.value); + return optionText.toLowerCase().includes(searchQuery.toLowerCase()); }); } -export const getSelectButtonColors = ( - hasSelection: boolean, - isDisabled: boolean, - hasError = false, -) => { +const getSelectButtonColors = (hasSelection: boolean, isDisabled: boolean, hasError = false) => { return tremorTwMerge( isDisabled ? "bg-tremor-background-subtle" : "bg-tremor-background-default", !isDisabled && "hover:bg-tremor-background-muted", @@ -45,6 +70,14 @@ export const getSelectButtonColors = ( ); }; -export function hasValue(value: T | null | undefined) { +function hasValue(value: T | null | undefined) { return value !== null && value !== undefined && value !== ""; } + +export { + constructValueToNameMapping, + getFilteredOptions, + getNodeText, + getSelectButtonColors, + hasValue, +};