diff --git a/src/components/icon-elements/Badge/Badge.tsx b/src/components/icon-elements/Badge/Badge.tsx index bd0e1e52e..3ea69bbb1 100644 --- a/src/components/icon-elements/Badge/Badge.tsx +++ b/src/components/icon-elements/Badge/Badge.tsx @@ -1,5 +1,5 @@ "use client"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import Tooltip, { useTooltip } from "components/util-elements/Tooltip/Tooltip"; import { Color, @@ -18,14 +18,38 @@ const makeBadgeClassName = makeClassName("Badge"); export interface BadgeProps extends React.HTMLAttributes { color?: Color; size?: Size; - icon?: React.ElementType; + icon?: React.ElementType | React.ReactElement; tooltip?: string; } const Badge = React.forwardRef((props, ref) => { const { color, icon, size = Sizes.SM, tooltip, className, children, ...other } = props; - const Icon = icon ? icon : null; + let Icon; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeBadgeClassName("icon"), + "shrink-0 -ml-1 mr-1.5", + iconSizes[size].height, + iconSizes[size].width, + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } const { tooltipProps, getReferenceProps } = useTooltip(); @@ -61,16 +85,7 @@ const Badge = React.forwardRef((props, ref) => { {...other} > - {Icon ? ( - - ) : null} + {Icon} {children} diff --git a/src/components/icon-elements/Icon/Icon.tsx b/src/components/icon-elements/Icon/Icon.tsx index f2cf17cc9..d6c7a61f5 100644 --- a/src/components/icon-elements/Icon/Icon.tsx +++ b/src/components/icon-elements/Icon/Icon.tsx @@ -1,5 +1,5 @@ "use client"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import Tooltip, { useTooltip } from "components/util-elements/Tooltip/Tooltip"; import { makeClassName, mergeRefs, Sizes, tremorTwMerge } from "lib"; @@ -17,7 +17,7 @@ export const IconVariants: { [key: string]: IconVariant } = { }; export interface IconProps extends React.HTMLAttributes { - icon: React.ElementType; + icon: React.ElementType | React.ReactElement; variant?: IconVariant; tooltip?: string; size?: Size; @@ -34,7 +34,31 @@ const Icon = React.forwardRef((props, ref) => { className, ...other } = props; - const Icon = icon; + + let Icon; + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeIconClassName("icon"), + "shrink-0", + iconSizes[size].height, + iconSizes[size].width, + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + const iconColorStyles = getIconColors(variant, color); const { tooltipProps, getReferenceProps } = useTooltip(); @@ -61,14 +85,7 @@ const Icon = React.forwardRef((props, ref) => { {...other} > - + {Icon} ); }); diff --git a/src/components/input-elements/BaseInput.tsx b/src/components/input-elements/BaseInput.tsx index 59b4d3b40..c2f73d051 100644 --- a/src/components/input-elements/BaseInput.tsx +++ b/src/components/input-elements/BaseInput.tsx @@ -1,5 +1,12 @@ "use client"; -import React, { ReactNode, useCallback, useRef, useState } from "react"; +import React, { + ReactNode, + cloneElement, + isValidElement, + useCallback, + useRef, + useState, +} from "react"; import { ExclamationFilledIcon, EyeIcon, EyeOffIcon } from "assets"; import { getSelectButtonColors, hasValue } from "components/input-elements/selectUtils"; import { mergeRefs, tremorTwMerge } from "lib"; @@ -8,7 +15,7 @@ export interface BaseInputProps extends React.InputHTMLAttributes; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; error?: boolean; errorMessage?: string; disabled?: boolean; @@ -43,7 +50,37 @@ const BaseInput = React.forwardRef((props, ref [isPasswordVisible, setIsPasswordVisible], ); - const Icon = icon; + let Icon; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeInputClassName("icon"), + // common + "shrink-0 h-5 w-5 mx-2.5 absolute left-0 flex items-center", + // light + "text-tremor-content-subtle", + // light + "dark:text-dark-tremor-content-subtle", + ), + }); + } else { + const IconElm = icon as React.ElementType | React.JSXElementConstructor; + Icon = ( + + ); + } + } const inputRef = useRef(null); @@ -96,19 +133,7 @@ const BaseInput = React.forwardRef((props, ref className, )} > - {Icon ? ( - - ) : null} + {Icon} { - Icon = Icon!; - const margin = !needMargin ? "" : iconPosition === HorizontalPositions.Left @@ -45,6 +43,13 @@ export const ButtonIconOrSpinner = ({ exited: defaultSpinnerSize, }; + let Icon: React.ReactElement | null = null; + if (icon) { + Icon = cloneElement(icon, { + className: tremorTwMerge(makeButtonClassName("icon"), "shrink-0", iconSize, margin), + }); + } + return loading ? ( ) : ( - + Icon ); }; export interface ButtonProps extends React.ButtonHTMLAttributes { - icon?: React.ElementType; + icon?: React.ElementType | React.ReactElement; iconPosition?: HorizontalPosition; size?: Size; color?: Color; @@ -89,7 +94,15 @@ const Button = React.forwardRef((props, ref) => ...other } = props; - const Icon = icon; + let Icon: React.ReactElement | undefined; + if (icon) { + if (isValidElement(icon)) { + Icon = icon as React.ReactElement; + } else { + const IconElm = icon as React.ElementType; + Icon = ; + } + } const isDisabled = loading || disabled; const showButtonIconOrSpinner = Icon !== undefined || loading; @@ -154,7 +167,7 @@ const Button = React.forwardRef((props, ref) => loading={loading} iconSize={iconSize} iconPosition={iconPosition} - Icon={Icon} + icon={Icon} transitionStatus={transitionState.status} needMargin={needIconMargin} /> @@ -174,7 +187,7 @@ const Button = React.forwardRef((props, ref) => loading={loading} iconSize={iconSize} iconPosition={iconPosition} - Icon={Icon} + icon={Icon} transitionStatus={transitionState.status} needMargin={needIconMargin} /> diff --git a/src/components/input-elements/MultiSelect/MultiSelect.tsx b/src/components/input-elements/MultiSelect/MultiSelect.tsx index babc9bcf4..fc4bc44e4 100644 --- a/src/components/input-elements/MultiSelect/MultiSelect.tsx +++ b/src/components/input-elements/MultiSelect/MultiSelect.tsx @@ -1,5 +1,5 @@ "use client"; -import React, { isValidElement, useMemo, useRef, useState } from "react"; +import React, { cloneElement, isValidElement, useMemo, useRef, useState } from "react"; import { SelectedValueContext } from "contexts"; import { useInternalState } from "hooks"; import { ArrowDownHeadIcon, SearchIcon, XCircleIcon } from "assets"; @@ -18,7 +18,7 @@ export interface MultiSelectProps extends React.HTMLAttributes placeholder?: string; placeholderSearch?: string; disabled?: boolean; - icon?: React.ElementType | React.JSXElementConstructor; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; required?: boolean; error?: boolean; errorMessage?: string; @@ -45,7 +45,38 @@ const MultiSelect = React.forwardRef((props, } = props; const listboxButtonRef = useRef(null); - const Icon = icon; + let Icon: React.ReactElement | undefined; + + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeMultiSelectClassName("Icon"), + // common + "flex-none h-5 w-5", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + ), + }); + } else { + const IconElm = icon as React.ElementType | React.JSXElementConstructor; + Icon = ( + + ); + } + } const [selectedValue, setSelectedValue] = useInternalState(defaultValue, value); @@ -155,17 +186,7 @@ const MultiSelect = React.forwardRef((props, "absolute inset-y-0 left-0 flex items-center ml-px pl-2.5", )} > - + {Icon} )}
diff --git a/src/components/input-elements/SearchSelect/SearchSelect.tsx b/src/components/input-elements/SearchSelect/SearchSelect.tsx index 5d33cb27f..0a73a4651 100644 --- a/src/components/input-elements/SearchSelect/SearchSelect.tsx +++ b/src/components/input-elements/SearchSelect/SearchSelect.tsx @@ -1,5 +1,6 @@ "use client"; -import React, { isValidElement, useMemo, useRef } from "react"; + +import React, { cloneElement, isValidElement, useMemo, useRef } from "react"; import { useInternalState } from "hooks"; import { Combobox, Transition } from "@headlessui/react"; import { ArrowDownHeadIcon, XCircleIcon } from "assets"; @@ -22,7 +23,7 @@ export interface SearchSelectProps extends React.HTMLAttributes void; placeholder?: string; disabled?: boolean; - icon?: React.ElementType | React.JSXElementConstructor; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; required?: boolean; error?: boolean; errorMessage?: string; @@ -57,7 +58,37 @@ const SearchSelect = React.forwardRef((prop const [searchQuery, setSearchQuery] = useInternalState("", searchValue); const [selectedValue, setSelectedValue] = useInternalState(defaultValue, value); - const Icon = icon; + let Icon: React.ReactElement | undefined; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeSearchSelectClassName("Icon"), + // common + "flex-none h-5 w-5", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + ), + }); + } else { + const IconElm = icon as React.ElementType | React.JSXElementConstructor; + Icon = ( + + ); + } + } const { reactElementChildren, valueToNameMapping } = useMemo(() => { const reactElementChildren = React.Children.toArray(children).filter(isValidElement); @@ -142,17 +173,7 @@ const SearchSelect = React.forwardRef((prop "absolute inset-y-0 left-0 flex items-center ml-px pl-2.5", )} > - + {Icon} )} diff --git a/src/components/input-elements/SearchSelect/SearchSelectItem.tsx b/src/components/input-elements/SearchSelect/SearchSelectItem.tsx index 229f52d69..52a5aec24 100644 --- a/src/components/input-elements/SearchSelect/SearchSelectItem.tsx +++ b/src/components/input-elements/SearchSelect/SearchSelectItem.tsx @@ -1,5 +1,5 @@ "use client"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import { makeClassName, tremorTwMerge } from "lib"; @@ -9,12 +9,43 @@ const makeSearchSelectItemClassName = makeClassName("SearchSelectItem"); export interface SearchSelectItemProps extends React.HTMLAttributes { value: string; - icon?: React.ElementType; + icon?: React.ElementType | React.ReactElement; } const SearchSelectItem = React.forwardRef((props, ref) => { const { value, icon, className, children, ...other } = props; - const Icon = icon; + + let Icon; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeSearchSelectItemClassName("icon"), + // common + "flex-none h-5 w-5 mr-3", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } return ( ( value={value} {...other} > - {Icon && ( - - )} + {Icon} {children ?? value} ); diff --git a/src/components/input-elements/Select/Select.tsx b/src/components/input-elements/Select/Select.tsx index 232ff693d..e167e8028 100644 --- a/src/components/input-elements/Select/Select.tsx +++ b/src/components/input-elements/Select/Select.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { isValidElement, useMemo, Children, useRef } from "react"; +import React, { isValidElement, useMemo, Children, useRef, cloneElement } from "react"; import { ArrowDownHeadIcon, XCircleIcon } from "assets"; import { makeClassName, tremorTwMerge } from "lib"; import { constructValueToNameMapping, getSelectButtonColors, hasValue } from "../selectUtils"; @@ -17,7 +17,7 @@ export interface SelectProps extends React.HTMLAttributes { onValueChange?: (value: string) => void; placeholder?: string; disabled?: boolean; - icon?: React.JSXElementConstructor; + icon?: React.JSXElementConstructor | React.ReactElement; enableClear?: boolean; required?: boolean; error?: boolean; @@ -47,7 +47,39 @@ const Select = React.forwardRef((props, ref) => { const childrenArray = Children.toArray(children); // @sev const [selectedValue, setSelectedValue] = useInternalState(defaultValue, value); - const Icon = icon; + + let Icon: React.ReactElement | undefined; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeSelectClassName("Icon"), + // common + "flex-none h-5 w-5", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + ), + }); + } else { + const IconElm = icon as React.JSXElementConstructor; + Icon = ( + + ); + } + } + const valueToNameMapping = useMemo(() => { const reactElementChildren = React.Children.toArray(children).filter(isValidElement); const valueToNameMapping = constructValueToNameMapping(reactElementChildren); @@ -136,17 +168,7 @@ const Select = React.forwardRef((props, ref) => { "absolute inset-y-0 left-0 flex items-center ml-px pl-2.5", )} > - + {Icon} )} diff --git a/src/components/input-elements/Select/SelectItem.tsx b/src/components/input-elements/Select/SelectItem.tsx index a331129dd..eac232244 100644 --- a/src/components/input-elements/Select/SelectItem.tsx +++ b/src/components/input-elements/Select/SelectItem.tsx @@ -1,5 +1,5 @@ "use client"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import { Listbox } from "@headlessui/react"; import { makeClassName, tremorTwMerge } from "lib"; @@ -7,13 +7,43 @@ const makeSelectItemClassName = makeClassName("SelectItem"); export interface SelectItemProps extends React.HTMLAttributes { value: string; - icon?: React.ElementType; + icon?: React.ElementType | React.ReactElement; } const SelectItem = React.forwardRef((props, ref) => { const { value, icon, className, children, ...other } = props; - const Icon = icon; + let Icon: React.ReactElement | undefined; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge( + makeSelectItemClassName("icon"), + // common + "flex-none w-5 h-5 mr-1.5", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } return ( ((props, ref) value={value} {...other} > - {Icon && ( - - )} + {Icon} {children ?? value} ); diff --git a/src/components/input-elements/Tabs/Tab.tsx b/src/components/input-elements/Tabs/Tab.tsx index 51ba0d34d..abfbb6064 100644 --- a/src/components/input-elements/Tabs/Tab.tsx +++ b/src/components/input-elements/Tabs/Tab.tsx @@ -1,7 +1,19 @@ "use client"; import { Tab as HeadlessTab } from "@headlessui/react"; +// <<<<<<< HEAD +// import { +// colorPalette, +// getColorClassNames, +// tremorTwMerge, +// makeClassName, +// sizing, +// spacing, +// } from "lib"; +// import React, { ReactElement, cloneElement, isValidElement, useContext } from "react"; +// ======= import { colorPalette, getColorClassNames, tremorTwMerge, makeClassName } from "lib"; -import React, { useContext } from "react"; +import React, { ReactElement, cloneElement, isValidElement, useContext } from "react"; +// >>>>>>> main import { TabVariant, TabVariantContext } from "components/input-elements/Tabs/TabList"; import { BaseColorContext } from "contexts"; @@ -41,7 +53,7 @@ function getVariantStyles(tabVariant: TabVariant, color?: Color) { } export interface TabProps extends React.ButtonHTMLAttributes { - icon?: React.ElementType; + icon?: React.ElementType | React.ReactElement; } const Tab = React.forwardRef((props, ref) => { @@ -49,7 +61,29 @@ const Tab = React.forwardRef((props, ref) => { const variant = useContext(TabVariantContext); const color = useContext(BaseColorContext); - const Icon = icon; + let Icon; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as ReactElement, { + className: tremorTwMerge( + makeTabClassName("icon"), + "flex-none h-5 w-5", + children ? "mr-2" : "", + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } return ( ((props, ref) => { )} {...other} > - {Icon ? ( - - ) : null} + {Icon} {children ? {children} : null} ); diff --git a/src/components/input-elements/TextInput/TextInput.tsx b/src/components/input-elements/TextInput/TextInput.tsx index ce316591f..42602027c 100644 --- a/src/components/input-elements/TextInput/TextInput.tsx +++ b/src/components/input-elements/TextInput/TextInput.tsx @@ -8,7 +8,7 @@ export type TextInputProps = Omit void; - icon?: React.ElementType | React.JSXElementConstructor; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; error?: boolean; errorMessage?: string; disabled?: boolean; diff --git a/src/components/text-elements/Callout/Callout.tsx b/src/components/text-elements/Callout/Callout.tsx index e524f4e52..bea862941 100644 --- a/src/components/text-elements/Callout/Callout.tsx +++ b/src/components/text-elements/Callout/Callout.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import { getColorClassNames, makeClassName, tremorTwMerge, Color } from "lib"; import { colorPalette } from "lib/theme"; @@ -6,14 +6,29 @@ const makeCalloutClassName = makeClassName("Callout"); export interface CalloutProps extends React.HTMLAttributes { title: string; - icon?: React.ElementType; + icon?: React.ElementType | React.ReactElement; color?: Color; } const Callout = React.forwardRef((props, ref) => { const { title, icon, color, className, children, ...other } = props; - const Icon = icon; + let Icon; + if (icon) { + if (isValidElement(icon)) { + Icon = cloneElement(icon as React.ReactElement, { + className: tremorTwMerge(makeCalloutClassName("icon"), "flex-none h-5 w-5 mr-1.5"), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } + return (
((props, ref) => { {...other} >
- {Icon ? ( - - ) : null} + {Icon}

{title}

= T & { key?: string; value: number; name: string; - icon?: React.JSXElementConstructor; + icon?: React.JSXElementConstructor | React.ReactElement; href?: string; target?: string; color?: Color; @@ -68,7 +68,37 @@ function BarListInner(props: BarListProps, ref: React.ForwardedRef

{data.map((item, idx) => { - const Icon = item.icon; + let Icon; + if (item.icon) { + if (isValidElement(item.icon)) { + Icon = cloneElement(item.icon as React.ReactElement, { + className: tremorTwMerge( + makeBarListClassName("barIcon"), + // common + "flex-none h-5 w-5 mr-2", + // light + "text-tremor-content", + // dark + "dark:text-dark-tremor-content", + ), + }); + } else { + const IconElm = item.icon as React.ElementType; + Icon = ( + + ); + } + } return (
(props: BarListProps, ref: React.ForwardedRef - {Icon ? ( - - ) : null} + {Icon} {item.href ? ( , + }, +}; + +export const SizesWithIconAsReactElement: Story = { + ...BadgeTemplateSizes, + args: { + icon: , + }, +}; + +export const ColorsWithIconAsReactElement: Story = { + ...BadgeTemplateColors, + args: { + icon: , + }, +}; + export const NoIcon: Story = { args: { icon: undefined, diff --git a/src/stories/icon-elements/Icon.stories.tsx b/src/stories/icon-elements/Icon.stories.tsx index 8d5fde76a..8b7ca3f78 100644 --- a/src/stories/icon-elements/Icon.stories.tsx +++ b/src/stories/icon-elements/Icon.stories.tsx @@ -91,6 +91,26 @@ export const Colors: Story = { ...IconTemplateColors, }; +export const IconAsReactElement: Story = { + args: { + icon: , + }, +}; + +export const SizesWithIconAsReactElement: Story = { + ...IconTemplateSizes, + args: { + icon: , + }, +}; + +export const ColorsWithIconAsReactElement: Story = { + ...IconTemplateColors, + args: { + icon: , + }, +}; + export const Shrink: Story = { ...IconShrink, }; diff --git a/src/stories/input-elements/Button.stories.tsx b/src/stories/input-elements/Button.stories.tsx index 376e03e7b..7bdd57c12 100644 --- a/src/stories/input-elements/Button.stories.tsx +++ b/src/stories/input-elements/Button.stories.tsx @@ -166,6 +166,20 @@ export const Default: Story = { }, }; +export const WithIcon: Story = { + args: { + children: "Default", + icon: ArrowRightIcon, + }, +}; + +export const WithIconAsReactElement: Story = { + args: { + children: "Default", + icon: , + }, +}; + export const Sizes: Story = { ...SizesTemplate, args: { diff --git a/src/stories/input-elements/MultiSelect.stories.tsx b/src/stories/input-elements/MultiSelect.stories.tsx index f08ebb0eb..42a13690f 100644 --- a/src/stories/input-elements/MultiSelect.stories.tsx +++ b/src/stories/input-elements/MultiSelect.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { MultiSelect } from "components"; @@ -42,11 +43,21 @@ export const UncontrolledIcon: Story = { args: { icon: CalendarIcon, defaultValue: ["5", "1"] }, }; +export const UncontrolledWithIconAsReactElement: Story = { + render: SimpleMultiSelect, + args: { icon: , defaultValue: ["5", "1"] }, +}; + export const UncontrolledDisabled: Story = { render: SimpleMultiSelect, args: { icon: CalendarIcon, defaultValue: ["5", "1"], disabled: true }, }; +export const UncontrolledDisabledWithIconAsReactElement: Story = { + render: SimpleMultiSelect, + args: { icon: , defaultValue: ["5", "1"], disabled: true }, +}; + export const Controlled: Story = { render: SimpleMultiSelectControlled, args: {}, diff --git a/src/stories/input-elements/NumberInput.stories.tsx b/src/stories/input-elements/NumberInput.stories.tsx index 845aa44cb..e259faadb 100644 --- a/src/stories/input-elements/NumberInput.stories.tsx +++ b/src/stories/input-elements/NumberInput.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { CalendarIcon } from "assets"; @@ -32,6 +33,13 @@ export const Icon: Story = { }, }; +export const IconAsReactElement: Story = { + render: SimpleNumberInput, + args: { + icon: , + }, +}; + export const NoPlaceholder: Story = { render: SimpleNumberInput, args: { diff --git a/src/stories/input-elements/SearchSelect.stories.tsx b/src/stories/input-elements/SearchSelect.stories.tsx index 0794261d9..fbda963f3 100644 --- a/src/stories/input-elements/SearchSelect.stories.tsx +++ b/src/stories/input-elements/SearchSelect.stories.tsx @@ -1,8 +1,10 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { SearchSelect } from "components"; import { SimpleSearchSelect, + SimpleSearchSelect2, SimpleSearchSelectControlled, SimpleSearchSelectWithStaticAndDynamicChildren, SimpleSearchSelectForm, @@ -58,6 +60,14 @@ export const Icon: Story = { }, }; +export const IconAsReactElement: Story = { + render: SimpleSearchSelect2, + args: { + defaultValue: "5", + icon: , + }, +}; + export const Error: Story = { render: SimpleSearchSelect, args: { diff --git a/src/stories/input-elements/Select.stories.tsx b/src/stories/input-elements/Select.stories.tsx index a304bd030..7d611bab2 100644 --- a/src/stories/input-elements/Select.stories.tsx +++ b/src/stories/input-elements/Select.stories.tsx @@ -1,8 +1,10 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { Select } from "components"; import { SimpleSelect, + SimpleSelect2, SimpleSelectControlled, SimpleSelectForm, SimpleSelectWithStaticAndDynamicChildren, @@ -57,6 +59,22 @@ export const Icon: Story = { }, }; +export const UncontrolledWithIconAsReactElement: Story = { + render: SimpleSelect, + args: { + defaultValue: "5", + icon: , + }, +}; + +export const UncontrolledWithIconAsReactElement2: Story = { + render: SimpleSelect2, + args: { + defaultValue: "5", + icon: , + }, +}; + export const UncontrolledDisabled: Story = { render: SimpleSelect, args: { diff --git a/src/stories/input-elements/Tabs.stories.tsx b/src/stories/input-elements/Tabs.stories.tsx index 72482a65a..1305c6045 100644 --- a/src/stories/input-elements/Tabs.stories.tsx +++ b/src/stories/input-elements/Tabs.stories.tsx @@ -22,12 +22,20 @@ interface MyTabProps { defaultIndex?: number; showText?: boolean; color: Color; + tabIcon?: React.ElementType | React.ReactElement; args?: any; } //Components function MyTab(props: MyTabProps) { - const { variant = "line", defaultIndex = 0, showText = true, color = "blue", args } = props; + const { + variant = "line", + defaultIndex = 0, + showText = true, + color = "blue", + tabIcon = CalendarIcon, + args, + } = props; const tabLabels = ["This is a very Long Tab Value that is used as an edge case", "Three", "One"]; @@ -35,7 +43,7 @@ function MyTab(props: MyTabProps) { {tabLabels.map((label, index) => ( - + {showText ? label : null} ))} @@ -70,11 +78,12 @@ function WithControlledStateTemplate({ ...args }) { } function TabSet({ showText = true, ...args }) { + const { tabIcon } = args; return ( <>
- - + +
); @@ -105,6 +114,18 @@ const ControlledTabSetTemplate: Story = { render: WithControlledStateTemplate, }; +const TabSetWithTabIconTemplate: Story = { + render: ({ ...args }) => ( +
+ {Object.values([CalendarIcon, ]).map((tabIcon, i) => ( +
+ +
+ ))} +
+ ), +}; + // Stories export const Default: Story = { @@ -137,3 +158,7 @@ export const Colors: Story = { export const Controlled: Story = { ...ControlledTabSetTemplate, }; + +export const WithTabIcon: Story = { + ...TabSetWithTabIconTemplate, +}; diff --git a/src/stories/input-elements/TextInput.stories.tsx b/src/stories/input-elements/TextInput.stories.tsx index d5eb511a5..4430a26d6 100644 --- a/src/stories/input-elements/TextInput.stories.tsx +++ b/src/stories/input-elements/TextInput.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { CalendarIcon } from "assets"; @@ -32,6 +33,13 @@ export const WithIcon: Story = { }, }; +export const WithIconAsReactElement: Story = { + render: SimpleTextInput, + args: { + icon: , + }, +}; + export const WithNoPlaceholder: Story = { render: SimpleTextInput, args: { diff --git a/src/stories/input-elements/helpers/SimpleSearchSelect.tsx b/src/stories/input-elements/helpers/SimpleSearchSelect.tsx index de9f52420..ab2499289 100644 --- a/src/stories/input-elements/helpers/SimpleSearchSelect.tsx +++ b/src/stories/input-elements/helpers/SimpleSearchSelect.tsx @@ -15,6 +15,18 @@ export const SimpleSearchSelect = (args: any) => ( ); +export const SimpleSearchSelect2 = (args: any) => ( + + }> + Very Long DropdownItem Value as an edge case + + } /> + }> + One + + +); + export const SimpleSearchSelectWithStaticAndDynamicChildren = (args: any) => { const items = ["item1", "item2"]; return ( diff --git a/src/stories/input-elements/helpers/SimpleSelect.tsx b/src/stories/input-elements/helpers/SimpleSelect.tsx index 4640f2b82..535c4e636 100644 --- a/src/stories/input-elements/helpers/SimpleSelect.tsx +++ b/src/stories/input-elements/helpers/SimpleSelect.tsx @@ -17,6 +17,20 @@ export const SimpleSelect = (args: any) => ( ); +export const SimpleSelect2 = (args: any) => ( + +); + export const SimpleSelectWithStaticAndDynamicChildren = (args: any) => { const items = ["item1", "item2"]; return ( diff --git a/src/stories/text-elements/Callout.stories.tsx b/src/stories/text-elements/Callout.stories.tsx index 2b7b05eef..e6c8f078c 100644 --- a/src/stories/text-elements/Callout.stories.tsx +++ b/src/stories/text-elements/Callout.stories.tsx @@ -63,3 +63,15 @@ export const Icon: Story = { icon: ArrowUpRightIcon, }, }; + +export const IconAsReactElement: Story = { + ...CalloutTemplate, + args: { + title: "Performance Metric", + children: + "You are outranking 83% of the sales representatives in your cohort. Sit repellendus qui ut at blanditis \ + et quo et molestiae. Doloribus dolores nostrum quia qui natus officia quod et dolorem. Sit repellendus \ + qui ut at blanditiis et quo et molestiae", + icon: , + }, +}; diff --git a/src/stories/vis-elements/BarList.stories.tsx b/src/stories/vis-elements/BarList.stories.tsx index e8137e027..de7f3ec3d 100644 --- a/src/stories/vis-elements/BarList.stories.tsx +++ b/src/stories/vis-elements/BarList.stories.tsx @@ -55,6 +55,13 @@ export const Icon: Story = { }, }; +export const IconAsReactElement: Story = { + args: { + data: getData(Array(5).fill({ icon: })), + valueFormatter: (value) => `${value} USD`, + }, +}; + export const Links: Story = { args: { data: getData(Array(4).fill({ href: "https://www.tremor.so/" })), diff --git a/src/tests/icon-elements/Badge.test.tsx b/src/tests/icon-elements/Badge.test.tsx index 8b8d510b4..4fb67838a 100644 --- a/src/tests/icon-elements/Badge.test.tsx +++ b/src/tests/icon-elements/Badge.test.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import Badge from "components/icon-elements/Badge/Badge"; @@ -8,4 +8,16 @@ describe("Badge", () => { test("renders the Badge component with default props", () => { render(Badge); }); + + test("renders the Badge component with Icon as ElementType", () => { + const Icon = () => Icon; + render(); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders the Badge component with Icon as ReactElement", () => { + const Icon = () => Icon; + render(} />); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); diff --git a/src/tests/icon-elements/Icon.test.tsx b/src/tests/icon-elements/Icon.test.tsx index c760f5577..f1441b882 100644 --- a/src/tests/icon-elements/Icon.test.tsx +++ b/src/tests/icon-elements/Icon.test.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import ArrowUpIcon from "assets/ArrowUpIcon"; @@ -10,4 +10,16 @@ describe("Icon", () => { test("renders the Icon component with default props", () => { render(); }); + + test("renders the Icon as React.ElementType", () => { + const DummyIcon = () => Icon; + render(); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders the Icon as React.ReactElement", () => { + const DummyIcon = () => Icon; + render(} />); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); diff --git a/src/tests/input-elements/Button.test.tsx b/src/tests/input-elements/Button.test.tsx index 7bdb4c32a..7f75c9fa0 100644 --- a/src/tests/input-elements/Button.test.tsx +++ b/src/tests/input-elements/Button.test.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-undef */ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import Button from "components/input-elements/Button/Button"; @@ -8,4 +8,16 @@ describe("Button", () => { test("renders the Button component with default props", () => { render(); }); + + test("renders the Button component with Icon as ElementType", () => { + const Icon = () => Icon; + render(