From fb0857c1125fcc24b207910780a3db66fae6550e Mon Sep 17 00:00:00 2001 From: motinados Date: Sun, 15 Oct 2023 05:57:06 +0900 Subject: [PATCH 01/17] icon as ReactElement for Icon --- src/components/icon-elements/Icon/Icon.tsx | 39 ++++++++++++++++------ src/stories/icon-elements/Icon.stories.tsx | 20 +++++++++++ src/tests/icon-elements/Icon.test.tsx | 14 +++++++- 3 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/components/icon-elements/Icon/Icon.tsx b/src/components/icon-elements/Icon/Icon.tsx index 267d0171e..e4ffdb758 100644 --- a/src/components/icon-elements/Icon/Icon.tsx +++ b/src/components/icon-elements/Icon/Icon.tsx @@ -1,6 +1,6 @@ "use client"; import { tremorTwMerge } from "lib"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import Tooltip, { useTooltip } from "components/util-elements/Tooltip/Tooltip"; import { makeClassName, mergeRefs, Sizes } from "lib"; @@ -18,7 +18,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; @@ -35,7 +35,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(); @@ -62,14 +86,7 @@ const Icon = React.forwardRef((props, ref) => { {...other} > - + {Icon} ); }); diff --git a/src/stories/icon-elements/Icon.stories.tsx b/src/stories/icon-elements/Icon.stories.tsx index 6fd2787ec..dd0496734 100644 --- a/src/stories/icon-elements/Icon.stories.tsx +++ b/src/stories/icon-elements/Icon.stories.tsx @@ -73,3 +73,23 @@ export const Sizes: Story = { 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: , + }, +}; 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(); + }); }); From e49ad5d27fd8e7256a8ce7bd507e5cf8ceb9e42a Mon Sep 17 00:00:00 2001 From: motinados Date: Mon, 16 Oct 2023 05:49:54 +0900 Subject: [PATCH 02/17] icon as ReactElement for Badge --- src/components/icon-elements/Badge/Badge.tsx | 47 +++++++++++++------- src/stories/icon-elements/Badge.stories.tsx | 20 +++++++++ src/tests/icon-elements/Badge.test.tsx | 14 +++++- 3 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/components/icon-elements/Badge/Badge.tsx b/src/components/icon-elements/Badge/Badge.tsx index 2b3185186..d18df8633 100644 --- a/src/components/icon-elements/Badge/Badge.tsx +++ b/src/components/icon-elements/Badge/Badge.tsx @@ -11,7 +11,7 @@ import { tremorTwMerge, } from "lib"; import { colorPalette } from "lib/theme"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import { badgeProportions, iconSizes } from "./styles"; const makeBadgeClassName = makeClassName("Badge"); @@ -19,14 +19,42 @@ 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", + spacing.twoXs.negativeMarginLeft, + spacing.xs.marginRight, + iconSizes[size].height, + iconSizes[size].width, + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } const { tooltipProps, getReferenceProps } = useTooltip(); @@ -58,18 +86,7 @@ const Badge = React.forwardRef((props, ref) => { {...other} > - {Icon ? ( - - ) : null} + {Icon}

{children}

diff --git a/src/stories/icon-elements/Badge.stories.tsx b/src/stories/icon-elements/Badge.stories.tsx index 81422a5e7..ac97ab87d 100644 --- a/src/stories/icon-elements/Badge.stories.tsx +++ b/src/stories/icon-elements/Badge.stories.tsx @@ -58,6 +58,26 @@ export const Colors: Story = { ...BadgeTemplateColors, }; +export const IconAsReactElement: Story = { + args: { + icon: , + }, +}; + +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/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(); + }); }); From ac080d407547a933d7770d45fcaa30d4bec85bc8 Mon Sep 17 00:00:00 2001 From: motinados Date: Tue, 17 Oct 2023 04:58:36 +0900 Subject: [PATCH 03/17] icon as ReactElement for MultiSelect --- .../MultiSelect/MultiSelect.tsx | 55 +++++++++++++------ .../input-elements/MultiSelect.stories.tsx | 11 ++++ src/tests/input-elements/MultiSelect.test.tsx | 26 ++++++++- 3 files changed, 75 insertions(+), 17 deletions(-) diff --git a/src/components/input-elements/MultiSelect/MultiSelect.tsx b/src/components/input-elements/MultiSelect/MultiSelect.tsx index 555eba0a2..45500e3b7 100644 --- a/src/components/input-elements/MultiSelect/MultiSelect.tsx +++ b/src/components/input-elements/MultiSelect/MultiSelect.tsx @@ -1,6 +1,6 @@ "use client"; import { tremorTwMerge } from "lib"; -import React, { useMemo, useState } from "react"; +import React, { cloneElement, isValidElement, useMemo, useState } from "react"; import { SelectedValueContext } from "contexts"; @@ -22,7 +22,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; children: React.ReactElement[] | React.ReactElement; } @@ -40,7 +40,42 @@ const MultiSelect = React.forwardRef((props, r ...other } = props; - 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", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + sizing.lg.height, + sizing.lg.width, + ), + }); + } else { + const IconElm = icon as React.ElementType | React.JSXElementConstructor; + Icon = ( + + ); + } + } const [selectedValue, setSelectedValue] = useInternalState(defaultValue, value); const [searchQuery, setSearchQuery] = useState(""); @@ -105,19 +140,7 @@ const MultiSelect = React.forwardRef((props, r spacing.md.paddingLeft, )} > - + {Icon} )}
diff --git a/src/stories/input-elements/MultiSelect.stories.tsx b/src/stories/input-elements/MultiSelect.stories.tsx index 5b18fea86..d72ea7629 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"; @@ -28,11 +29,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/tests/input-elements/MultiSelect.test.tsx b/src/tests/input-elements/MultiSelect.test.tsx index 7b79a6c33..f7a142c32 100644 --- a/src/tests/input-elements/MultiSelect.test.tsx +++ b/src/tests/input-elements/MultiSelect.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import MultiSelect from "components/input-elements/MultiSelect/MultiSelect"; @@ -14,4 +14,28 @@ describe("SelectBox", () => { , ); }); + + test("renders with the Icon as ElementType", () => { + const Icon = () => Icon; + render( + + + Option Two + Option Three + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders with the Icon as ReactElement", () => { + const Icon = () => Icon; + render( + }> + + Option Two + Option Three + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); From 901398cf8c1035c92926e923680d6d9910abf21a Mon Sep 17 00:00:00 2001 From: motinados Date: Tue, 17 Oct 2023 05:20:16 +0900 Subject: [PATCH 04/17] icon as ReactElement for SearchSelect --- .../SearchSelect/SearchSelect.tsx | 55 +++++++++++++------ .../input-elements/SearchSelect.stories.tsx | 9 +++ .../input-elements/SearchSelect.test.tsx | 26 ++++++++- 3 files changed, 73 insertions(+), 17 deletions(-) diff --git a/src/components/input-elements/SearchSelect/SearchSelect.tsx b/src/components/input-elements/SearchSelect/SearchSelect.tsx index c32388a1e..6d3e57243 100644 --- a/src/components/input-elements/SearchSelect/SearchSelect.tsx +++ b/src/components/input-elements/SearchSelect/SearchSelect.tsx @@ -1,7 +1,7 @@ "use client"; import { useInternalState } from "hooks"; import { tremorTwMerge } from "lib"; -import React, { useMemo, useState } from "react"; +import React, { cloneElement, isValidElement, useMemo, useState } from "react"; import { Combobox } from "@headlessui/react"; import { ArrowDownHeadIcon, XCircleIcon } from "assets"; @@ -21,7 +21,7 @@ export interface SearchSelectProps extends React.HTMLAttributes onValueChange?: (value: string) => void; placeholder?: string; disabled?: boolean; - icon?: React.ElementType | React.JSXElementConstructor; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; enableClear?: boolean; children: React.ReactElement[] | React.ReactElement; } @@ -45,7 +45,42 @@ const SearchSelect = React.forwardRef((props, const [searchQuery, setSearchQuery] = useState(""); 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", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + sizing.lg.height, + sizing.lg.width, + ), + }); + } else { + const IconElm = icon as React.ElementType | React.JSXElementConstructor; + Icon = ( + + ); + } + } + const valueToNameMapping = useMemo(() => constructValueToNameMapping(children), [children]); const filteredOptions = useMemo( () => getFilteredOptions(searchQuery, children as React.ReactElement[]), @@ -88,19 +123,7 @@ const SearchSelect = React.forwardRef((props, spacing.md.paddingLeft, )} > - + {Icon} )} diff --git a/src/stories/input-elements/SearchSelect.stories.tsx b/src/stories/input-elements/SearchSelect.stories.tsx index 83c417652..ee68810c0 100644 --- a/src/stories/input-elements/SearchSelect.stories.tsx +++ b/src/stories/input-elements/SearchSelect.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { SearchSelect } from "components"; @@ -43,6 +44,14 @@ export const Icon: Story = { }, }; +export const IconAsReactElement: Story = { + render: SimpleSearchSelect, + args: { + defaultValue: "5", + icon: , + }, +}; + export const Disabled: Story = { render: SimpleSearchSelect, args: { diff --git a/src/tests/input-elements/SearchSelect.test.tsx b/src/tests/input-elements/SearchSelect.test.tsx index 2e0cc8046..fe4daaeda 100644 --- a/src/tests/input-elements/SearchSelect.test.tsx +++ b/src/tests/input-elements/SearchSelect.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import SelectBox from "components/input-elements/SearchSelect/SearchSelect"; @@ -14,4 +14,28 @@ describe("SelectBox", () => { , ); }); + + test("renders with Icon as ElementType", () => { + const Icon = () => Icon; + render( + + + Option Two + Option Three + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders with Icon as ReactElement", () => { + const Icon = () => Icon; + render( + }> + + Option Two + Option Three + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); From 6215796052942580d8b747550a02599e21b1fd50 Mon Sep 17 00:00:00 2001 From: motinados Date: Tue, 17 Oct 2023 05:40:12 +0900 Subject: [PATCH 05/17] icon as ReactElement for TextInput and NumbeInput --- src/components/input-elements/BaseInput.tsx | 66 +++++++++++++------ .../input-elements/TextInput/TextInput.tsx | 2 +- .../input-elements/NumberInput.stories.tsx | 8 +++ .../input-elements/TextInput.stories.tsx | 8 +++ src/tests/input-elements/NumberInput.test.tsx | 12 ++++ src/tests/input-elements/TextInput.test.tsx | 14 +++- 6 files changed, 89 insertions(+), 21 deletions(-) diff --git a/src/components/input-elements/BaseInput.tsx b/src/components/input-elements/BaseInput.tsx index df23e328f..137afc73e 100644 --- a/src/components/input-elements/BaseInput.tsx +++ b/src/components/input-elements/BaseInput.tsx @@ -2,13 +2,20 @@ import { ExclamationFilledIcon, EyeIcon, EyeOffIcon } from "assets"; import { getSelectButtonColors, hasValue } from "components/input-elements/selectUtils"; import { border, mergeRefs, sizing, spacing, tremorTwMerge } from "lib"; -import React, { ReactNode, useCallback, useRef, useState } from "react"; +import React, { + ReactNode, + cloneElement, + isValidElement, + useCallback, + useRef, + useState, +} from "react"; export interface BaseInputProps extends React.InputHTMLAttributes { type?: "text" | "password" | "email" | "url" | "number"; defaultValue?: string | number; value?: string | number; - icon?: React.ElementType | React.JSXElementConstructor; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; error?: boolean; errorMessage?: string; disabled?: boolean; @@ -39,7 +46,43 @@ 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", + // light + "text-tremor-content-subtle", + // light + "dark:text-dark-tremor-content-subtle", + sizing.lg.height, + sizing.lg.width, + spacing.md.marginLeft, + ), + }); + } else { + const IconElm = icon as React.ElementType | React.JSXElementConstructor; + Icon = ( + + ); + } + } const inputRef = useRef(null); @@ -90,22 +133,7 @@ const BaseInput = React.forwardRef((props, ref handleFocusChange(false); }} > - {Icon ? ( - - ) : null} + {Icon} ; + icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; error?: boolean; errorMessage?: string; disabled?: boolean; diff --git a/src/stories/input-elements/NumberInput.stories.tsx b/src/stories/input-elements/NumberInput.stories.tsx index e2ff815fe..811b69d5c 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"; @@ -28,6 +29,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/TextInput.stories.tsx b/src/stories/input-elements/TextInput.stories.tsx index fd6073e54..a272fc8be 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"; @@ -28,6 +29,13 @@ export const WithIcon: Story = { }, }; +export const WithIconAsReactElement: Story = { + render: SimpleTextInput, + args: { + icon: , + }, +}; + export const WithNoPlaceholder: Story = { render: SimpleTextInput, args: { diff --git a/src/tests/input-elements/NumberInput.test.tsx b/src/tests/input-elements/NumberInput.test.tsx index 41599e267..549d30771 100644 --- a/src/tests/input-elements/NumberInput.test.tsx +++ b/src/tests/input-elements/NumberInput.test.tsx @@ -36,4 +36,16 @@ describe("NumberInput", () => { fireEvent.mouseUp(stepUp); expect(inputEl.value).toBe("2"); }); + + test("renders with Icon as ElementType", () => { + const Icon = () => Icon; + render(); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders with Icon as ReactElement", () => { + const Icon = () => Icon; + render(} />); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); diff --git a/src/tests/input-elements/TextInput.test.tsx b/src/tests/input-elements/TextInput.test.tsx index 17d624542..32bd9b385 100644 --- a/src/tests/input-elements/TextInput.test.tsx +++ b/src/tests/input-elements/TextInput.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import { TextInput } from "components"; import React from "react"; @@ -18,4 +18,16 @@ describe("TextInput", () => { test("renders the TextInput component with url type", () => { render(); }); + + test("renders with Icon as ElementType", () => { + const Icon = () => Icon; + render(); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders with Icon as ReactElement", () => { + const Icon = () => Icon; + render(} />); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); From d6b35354f75a8bd9140e061d7ccedd02fc7073f1 Mon Sep 17 00:00:00 2001 From: motinados Date: Wed, 18 Oct 2023 06:06:12 +0900 Subject: [PATCH 06/17] icon as ReactElement for Tab --- src/components/input-elements/Tabs/Tab.tsx | 44 +++++++++++++------ .../input-elements/TabGroup.stories.tsx | 34 +++++++++++--- src/tests/input-elements/Tabs.test.tsx | 40 ++++++++++++++++- 3 files changed, 98 insertions(+), 20 deletions(-) diff --git a/src/components/input-elements/Tabs/Tab.tsx b/src/components/input-elements/Tabs/Tab.tsx index 23db11927..bf022b721 100644 --- a/src/components/input-elements/Tabs/Tab.tsx +++ b/src/components/input-elements/Tabs/Tab.tsx @@ -1,7 +1,7 @@ "use client"; import { Tab as HeadlessTab } from "@headlessui/react"; import { colorPalette, getColorClassNames, tremorTwMerge } from "lib"; -import React, { useContext } from "react"; +import React, { ReactElement, cloneElement, isValidElement, useContext } from "react"; import { TabVariant, TabVariantContext } from "components/input-elements/Tabs/TabList"; import { BaseColorContext } from "contexts"; @@ -43,7 +43,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) => { @@ -51,7 +51,33 @@ 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", + sizing.lg.height, + sizing.lg.width, + children ? spacing.sm.marginRight : "", + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } return ( ((props, ref) => { )} {...other} > - {Icon ? ( - - ) : null} + {Icon} {children ? {children} : null} ); diff --git a/src/stories/input-elements/TabGroup.stories.tsx b/src/stories/input-elements/TabGroup.stories.tsx index 06809fed3..7c4d8822a 100644 --- a/src/stories/input-elements/TabGroup.stories.tsx +++ b/src/stories/input-elements/TabGroup.stories.tsx @@ -19,12 +19,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"]; @@ -32,7 +40,7 @@ function MyTab(props: MyTabProps) { {tabLabels.map((label, index) => ( - + {showText ? label : null} ))} @@ -67,12 +75,12 @@ function WithControlledStateTemplate({ ...args }) { } function TabSet({ showText = true, ...args }) { - const { color } = args; + const { color, tabIcon } = args; return ( <>
- - + +
); @@ -103,6 +111,18 @@ const ControlledTabSetTemplate: Story = { render: WithControlledStateTemplate, }; +const TabSetWithTabIconTemplate: Story = { + render: ({ ...args }) => ( +
+ {Object.values([CalendarIcon, ]).map((tabIcon, i) => ( +
+ +
+ ))} +
+ ), +}; + // Stories export const Default: Story = { @@ -135,3 +155,7 @@ export const Colors: Story = { export const Controlled: Story = { ...ControlledTabSetTemplate, }; + +export const WithTabIcon: Story = { + ...TabSetWithTabIconTemplate, +}; diff --git a/src/tests/input-elements/Tabs.test.tsx b/src/tests/input-elements/Tabs.test.tsx index 8cce78119..eb7e6dcc9 100644 --- a/src/tests/input-elements/Tabs.test.tsx +++ b/src/tests/input-elements/Tabs.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import Tab from "components/input-elements/Tabs/Tab"; @@ -24,4 +24,42 @@ describe("SelectBox", () => {
, ); }); + + test("renders with the Icon as ElementType", () => { + const Icon = () => Icon; + render( + + + Option One + Option Two + Option Three + + + Hello World 1 + Hello World 2 + Hello World 3 + + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders with Icon as ReactElement", () => { + const Icon = () => Icon; + render( + + + }>Option One + Option Two + Option Three + + + Hello World 1 + Hello World 2 + Hello World 3 + + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); From 1a946cf4b4a02209f2c33bb3434e07f60546d3e7 Mon Sep 17 00:00:00 2001 From: motinados Date: Thu, 19 Oct 2023 05:14:40 +0900 Subject: [PATCH 07/17] icon as ReactElement for Select --- .../input-elements/Select/Select.tsx | 56 +++++++++++++------ src/stories/input-elements/Select.stories.tsx | 9 +++ src/tests/input-elements/Select.test.tsx | 26 ++++++++- 3 files changed, 74 insertions(+), 17 deletions(-) diff --git a/src/components/input-elements/Select/Select.tsx b/src/components/input-elements/Select/Select.tsx index 443d7e93f..d0161b8b4 100644 --- a/src/components/input-elements/Select/Select.tsx +++ b/src/components/input-elements/Select/Select.tsx @@ -2,7 +2,7 @@ import { ArrowDownHeadIcon, XCircleIcon } from "assets"; import { border, makeClassName, sizing, spacing } from "lib"; -import React, { useMemo } from "react"; +import React, { cloneElement, isValidElement, useMemo } from "react"; import { constructValueToNameMapping, getSelectButtonColors, hasValue } from "../selectUtils"; import { Listbox } from "@headlessui/react"; @@ -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; children: React.ReactElement[] | React.ReactElement; } @@ -37,7 +37,43 @@ const Select = React.forwardRef((props, ref) => { } = props; 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", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + sizing.lg.height, + sizing.lg.width, + ), + }); + } else { + const IconElm = icon as React.JSXElementConstructor; + Icon = ( + + ); + } + } + const valueToNameMapping = useMemo(() => constructValueToNameMapping(children), [children]); const handleReset = () => { @@ -89,19 +125,7 @@ const Select = React.forwardRef((props, ref) => { spacing.md.paddingLeft, )} > - + {Icon} )} diff --git a/src/stories/input-elements/Select.stories.tsx b/src/stories/input-elements/Select.stories.tsx index 82b021f62..00897630c 100644 --- a/src/stories/input-elements/Select.stories.tsx +++ b/src/stories/input-elements/Select.stories.tsx @@ -1,3 +1,4 @@ +import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { Select } from "components"; @@ -43,6 +44,14 @@ export const UncontrolledIcon: Story = { }, }; +export const UncontrolledWithIconAsReactElement: Story = { + render: SimpleSelect, + args: { + defaultValue: "5", + icon: , + }, +}; + export const UncontrolledDisabled: Story = { render: SimpleSelect, args: { diff --git a/src/tests/input-elements/Select.test.tsx b/src/tests/input-elements/Select.test.tsx index 92dea20f0..96d2cb06f 100644 --- a/src/tests/input-elements/Select.test.tsx +++ b/src/tests/input-elements/Select.test.tsx @@ -1,4 +1,4 @@ -import { render } from "@testing-library/react"; +import { render, screen } from "@testing-library/react"; import React from "react"; import Select from "components/input-elements/Select/Select"; @@ -14,4 +14,28 @@ describe("Select", () => { , ); }); + + test("renders the Select component with Icon as JSXElementConstructor", () => { + const Icon = () => Icon; + render( + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders the Select component with Icon as ReactElement", () => { + const Icon = () => Icon; + render( + , + ); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); From 527e9ca9dded494b45fef4fa0f9032448d2fda36 Mon Sep 17 00:00:00 2001 From: motinados Date: Thu, 19 Oct 2023 05:48:35 +0900 Subject: [PATCH 08/17] icon as ReactElement for SelectItem --- .../input-elements/Select/SelectItem.tsx | 56 +++++++++++++------ src/stories/input-elements/Select.stories.tsx | 10 +++- .../input-elements/helpers/SimpleSelect.tsx | 14 +++++ src/tests/input-elements/Select.test.tsx | 32 ++++++++++- 4 files changed, 92 insertions(+), 20 deletions(-) diff --git a/src/components/input-elements/Select/SelectItem.tsx b/src/components/input-elements/Select/SelectItem.tsx index b7e17c66f..08466ab95 100644 --- a/src/components/input-elements/Select/SelectItem.tsx +++ b/src/components/input-elements/Select/SelectItem.tsx @@ -1,6 +1,6 @@ "use client"; import { tremorTwMerge } from "lib"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import { Listbox } from "@headlessui/react"; import { makeClassName } from "lib"; @@ -11,13 +11,47 @@ 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", + // light + "text-tremor-content-subtle", + // dark + "dark:text-dark-tremor-content-subtle", + sizing.lg.width, + spacing.xs.marginRight, + ), + }); + } else { + const IconElm = icon as React.ElementType; + Icon = ( + + ); + } + } return ( ((props, ref) value={value} {...other} > - {Icon && ( - - )} + {Icon} {children ?? value} ); diff --git a/src/stories/input-elements/Select.stories.tsx b/src/stories/input-elements/Select.stories.tsx index 00897630c..c48624e81 100644 --- a/src/stories/input-elements/Select.stories.tsx +++ b/src/stories/input-elements/Select.stories.tsx @@ -2,7 +2,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { Select } from "components"; -import { SimpleSelect, SimpleSelectControlled } from "./helpers/SimpleSelect"; +import { SimpleSelect, SimpleSelect2, SimpleSelectControlled } from "./helpers/SimpleSelect"; import { CalendarIcon } from "assets"; @@ -52,6 +52,14 @@ export const UncontrolledWithIconAsReactElement: Story = { }, }; +export const UncontrolledWithIconAsReactElement2: Story = { + render: SimpleSelect2, + args: { + defaultValue: "5", + icon: , + }, +}; + export const UncontrolledDisabled: Story = { render: SimpleSelect, args: { diff --git a/src/stories/input-elements/helpers/SimpleSelect.tsx b/src/stories/input-elements/helpers/SimpleSelect.tsx index 24f4abb34..84a942a6b 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 SimpleSelectControlled = (args: any) => { const [value, setValue] = React.useState("5"); return ( diff --git a/src/tests/input-elements/Select.test.tsx b/src/tests/input-elements/Select.test.tsx index 96d2cb06f..3736cef0c 100644 --- a/src/tests/input-elements/Select.test.tsx +++ b/src/tests/input-elements/Select.test.tsx @@ -1,4 +1,4 @@ -import { render, screen } from "@testing-library/react"; +import { fireEvent, render, screen } from "@testing-library/react"; import React from "react"; import Select from "components/input-elements/Select/Select"; @@ -38,4 +38,34 @@ describe("Select", () => { ); expect(screen.queryByTestId("icon")).toBeTruthy(); }); + + test("renders the SelectItem component with Icon as ElementType", () => { + const Icon = () => Icon; + const placeholder = "Select..."; + render( + , + ); + expect(screen.queryByTestId("icon")).not.toBeTruthy(); + fireEvent.click(screen.getByText(placeholder)); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); + + test("renders the SelectItem component with Icon as ReactElement", () => { + const Icon = () => Icon; + const placeholder = "Select..."; + render( + , + ); + expect(screen.queryByTestId("icon")).not.toBeTruthy(); + fireEvent.click(screen.getByText(placeholder)); + expect(screen.queryByTestId("icon")).toBeTruthy(); + }); }); From e5c1a6584d25d7f398c68edabbd56c39f1df53b3 Mon Sep 17 00:00:00 2001 From: motinados Date: Thu, 19 Oct 2023 17:09:09 +0900 Subject: [PATCH 09/17] icon as ReactElement for Button --- .../input-elements/Button/Button.tsx | 33 +++++++++++++------ src/stories/input-elements/Button.stories.tsx | 14 ++++++++ src/tests/input-elements/Button.test.tsx | 14 +++++++- 3 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/components/input-elements/Button/Button.tsx b/src/components/input-elements/Button/Button.tsx index 79ef572f2..967a6e6ad 100644 --- a/src/components/input-elements/Button/Button.tsx +++ b/src/components/input-elements/Button/Button.tsx @@ -1,7 +1,7 @@ "use client"; import Tooltip, { useTooltip } from "components/util-elements/Tooltip/Tooltip"; import { tremorTwMerge } from "lib"; -import React from "react"; +import React, { cloneElement, isValidElement } from "react"; import { Transition } from "react-transition-group"; import { border, HorizontalPositions, makeClassName, mergeRefs, Sizes, sizing, spacing } from "lib"; @@ -16,7 +16,7 @@ export interface ButtonIconOrSpinnerProps { loading: boolean; iconSize: string; iconPosition: string; - Icon: React.ElementType | undefined; + icon: React.ReactElement | undefined; needMargin: boolean; transitionState: string; } @@ -25,12 +25,10 @@ export const ButtonIconOrSpinner = ({ loading, iconSize, iconPosition, - Icon, + icon, needMargin, transitionState, }: ButtonIconOrSpinnerProps) => { - Icon = Icon!; - const margin = !needMargin ? "" : iconPosition === HorizontalPositions.Left @@ -46,6 +44,13 @@ export const ButtonIconOrSpinner = ({ exited: defaultSpinnerSize, }; + let Icon: React.ReactElement | undefined; + 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; @@ -90,7 +95,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; @@ -152,7 +165,7 @@ const Button = React.forwardRef((props, ref) => loading={loading} iconSize={iconSize} iconPosition={iconPosition} - Icon={Icon} + icon={Icon} transitionState={state} needMargin={needIconMargin} /> @@ -169,7 +182,7 @@ const Button = React.forwardRef((props, ref) => loading={loading} iconSize={iconSize} iconPosition={iconPosition} - Icon={Icon} + icon={Icon} transitionState={state} needMargin={needIconMargin} /> diff --git a/src/stories/input-elements/Button.stories.tsx b/src/stories/input-elements/Button.stories.tsx index faf7d94db..d1b9d0408 100644 --- a/src/stories/input-elements/Button.stories.tsx +++ b/src/stories/input-elements/Button.stories.tsx @@ -162,6 +162,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/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( - )} - + {showLoadingText ? loadingText : children} + + ) : null} + {showButtonIconOrSpinner && iconPosition === HorizontalPositions.Right ? ( + + ) : null} + ); }); diff --git a/src/components/input-elements/DateRangePicker/DateRangePicker.tsx b/src/components/input-elements/DateRangePicker/DateRangePicker.tsx index 1904b8c79..3e505ec1a 100644 --- a/src/components/input-elements/DateRangePicker/DateRangePicker.tsx +++ b/src/components/input-elements/DateRangePicker/DateRangePicker.tsx @@ -304,9 +304,9 @@ const DateRangePicker = React.forwardRef(( // common "w-full outline-none text-left whitespace-nowrap truncate rounded-r-tremor-default transition duration-100 border px-4 py-2", // light - "border-tremor-border shadow-tremor-input text-tremor-content-emphasis focus:border-tremor-brand-subtle", + "border-tremor-border text-tremor-content-emphasis focus:border-tremor-brand-subtle", // dark - "dark:border-dark-tremor-border dark:shadow-dark-tremor-input dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle", + "dark:border-dark-tremor-border dark:text-dark-tremor-content-emphasis dark:focus:border-dark-tremor-brand-subtle", getSelectButtonColors(hasValue(value), disabled), )} > diff --git a/src/components/input-elements/MultiSelect/MultiSelect.tsx b/src/components/input-elements/MultiSelect/MultiSelect.tsx index 66f1044a7..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, { cloneElement, isValidElement, useMemo, 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"; @@ -10,20 +10,24 @@ import { getFilteredOptions, getSelectButtonColors } from "../selectUtils"; const makeMultiSelectClassName = makeClassName("MultiSelect"); -export interface MultiSelectProps extends React.HTMLAttributes { +export interface MultiSelectProps extends React.HTMLAttributes { defaultValue?: string[]; + name?: string; value?: string[]; onValueChange?: (value: string[]) => void; placeholder?: string; placeholderSearch?: string; disabled?: boolean; icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; + required?: boolean; + error?: boolean; + errorMessage?: string; children: React.ReactNode; } -const MultiSelect = React.forwardRef((props, ref) => { +const MultiSelect = React.forwardRef((props, ref) => { const { - defaultValue, + defaultValue = [], value, onValueChange, placeholder = "Select...", @@ -32,8 +36,14 @@ const MultiSelect = React.forwardRef((props, r icon, children, className, + required, + name, + error = false, + errorMessage, + id, ...other } = props; + const listboxButtonRef = useRef(null); let Icon: React.ReactElement | undefined; @@ -98,210 +108,255 @@ const MultiSelect = React.forwardRef((props, r }; return ( - { - onValueChange?.(values); - setSelectedValue(values); - }) as any - } - disabled={disabled} +
- {({ value }) => ( - <> - 0, disabled), - )} - > - {Icon && ( - - {Icon} - - )} -
- {value.length > 0 ? ( -
- {optionsAvailable - .filter((option) => value.includes(option.props.value)) - .map((option, index) => { - return ( -
-
- {option.props.children ?? option.props.value} -
-
{ - e.preventDefault(); - const newValue = value.filter((v) => v !== option.props.value); - onValueChange?.(newValue); - setSelectedValue(newValue); - }} - > - -
-
- ); - })} -
- ) : ( - {placeholder} - )} -
- - - -
- - {/* coditionally displayed XCircle */} - {hasSelection && !disabled ? ( - - ) : null} - - { + e.preventDefault(); + }} + name={name} + disabled={disabled} + multiple + id={id} + onFocus={() => { + const listboxButton = listboxButtonRef.current; + if (listboxButton) listboxButton.focus(); + }} + > + + {filteredOptions.map((child: any) => { + const value = child.props.value; + const name = child.props.children; + return ( + + ); + })} + + { + onValueChange?.(values); + setSelectedValue(values); + }) as any + } + disabled={disabled} + className={tremorTwMerge( + // common + "w-full min-w-[10rem] relative text-tremor-default", + className, + )} + id={id} + multiple + {...other} + > + {({ value }) => ( + <> + 0, disabled, error), )} + ref={listboxButtonRef} > -
+ {Icon} + + )} +
+ {value.length > 0 ? ( +
+ {optionsAvailable + .filter((option) => value.includes(option.props.value)) + .map((option, index) => { + return ( +
+
+ {option.props.children ?? option.props.value} +
+
{ + e.preventDefault(); + const newValue = value.filter((v) => v !== option.props.value); + onValueChange?.(newValue); + setSelectedValue(newValue); + }} + > + +
+
+ ); + })} +
+ ) : ( + {placeholder} + )} +
+ + + + + + {hasSelection && !disabled ? ( + + ) : null} + + - - + + + + { + if (e.code === "Space" && (e.target as HTMLInputElement).value !== "") { + e.stopPropagation(); + } + }} + onChange={(e) => setSearchQuery(e.target.value)} + value={searchQuery} /> - - { - if (e.code === "Space" && (e.target as HTMLInputElement).value !== "") { - e.stopPropagation(); - } - }} - onChange={(e) => setSearchQuery(e.target.value)} - value={searchQuery} - /> -
- - {filteredOptions} - -
-
- - )} - +
+ + {filteredOptions} + + + + + )} +
+ {error && errorMessage ? ( +

+ {errorMessage} +

+ ) : null} +
); }); diff --git a/src/components/input-elements/SearchSelect/SearchSelect.tsx b/src/components/input-elements/SearchSelect/SearchSelect.tsx index 97948bccd..1a47356c7 100644 --- a/src/components/input-elements/SearchSelect/SearchSelect.tsx +++ b/src/components/input-elements/SearchSelect/SearchSelect.tsx @@ -1,13 +1,6 @@ "use client"; -// <<<<<<< HEAD -// import { useInternalState } from "hooks"; -// import { tremorTwMerge } from "lib"; -// import React, { cloneElement, isValidElement, useMemo, useState } from "react"; - -// ======= -import React, { cloneElement, isValidElement, useMemo, useState } from "react"; +import React, { cloneElement, isValidElement, useMemo, useRef } from "react"; import { useInternalState } from "hooks"; -// >>>>>>> main import { Combobox, Transition } from "@headlessui/react"; import { ArrowDownHeadIcon, XCircleIcon } from "assets"; import { makeClassName, tremorTwMerge } from "lib"; @@ -20,34 +13,48 @@ import { const makeSearchSelectClassName = makeClassName("SearchSelect"); -export interface SearchSelectProps extends React.HTMLAttributes { +export interface SearchSelectProps extends React.HTMLAttributes { defaultValue?: string; + name?: string; + searchValue?: string; + onSearchValueChange?: (value: string) => void; value?: string; onValueChange?: (value: string) => void; placeholder?: string; disabled?: boolean; icon?: React.ElementType | React.JSXElementConstructor | React.ReactElement; + required?: boolean; + error?: boolean; + errorMessage?: string; enableClear?: boolean; children: React.ReactNode; } const makeSelectClassName = makeClassName("SearchSelect"); -const SearchSelect = React.forwardRef((props, ref) => { +const SearchSelect = React.forwardRef((props, ref) => { const { - defaultValue, + defaultValue = "", + searchValue, + onSearchValueChange, value, onValueChange, placeholder = "Select...", disabled = false, icon, enableClear = true, + name, + required, + error = false, + errorMessage, children, className, + id, ...other } = props; + const comboboxButtonRef = useRef(null); - const [searchQuery, setSearchQuery] = useState(""); + const [searchQuery, setSearchQuery] = useInternalState("", searchValue); const [selectedValue, setSelectedValue] = useInternalState(defaultValue, value); let Icon: React.ReactElement | undefined; @@ -89,7 +96,7 @@ const SearchSelect = React.forwardRef((props, }, [children]); const filteredOptions = useMemo( - () => getFilteredOptions(searchQuery, reactElementChildren), + () => getFilteredOptions(searchQuery ?? "", reactElementChildren), [searchQuery, reactElementChildren], ); @@ -97,122 +104,168 @@ const SearchSelect = React.forwardRef((props, setSelectedValue(""); setSearchQuery(""); onValueChange?.(""); + onSearchValueChange?.(""); }; return ( - { - onValueChange?.(value); - setSelectedValue(value); - }) as any - } - disabled={disabled} +
- {({ value }) => ( - <> - - {Icon && ( - - {Icon} - - )} - - { + e.preventDefault(); + }} + name={name} + disabled={disabled} + id={id} + onFocus={() => { + const comboboxButton = comboboxButtonRef.current; + if (comboboxButton) comboboxButton.click(); + }} + > + + {filteredOptions.map((child: any) => { + const value = child.props.value; + const name = child.props.children; + return ( + + ); + })} + + { + onValueChange?.(value); + setSelectedValue(value); + }) as any + } + disabled={disabled} + className={tremorTwMerge( + // common + "w-full min-w-[10rem] relative text-tremor-default", + className, + )} + id={id} + {...other} + > + {({ value }) => ( + <> + + {Icon && ( + + {Icon} + )} - placeholder={placeholder} - onChange={(event) => setSearchQuery(event.target.value)} - displayValue={(value: string) => valueToNameMapping.get(value) ?? ""} - /> -
- { + onSearchValueChange?.(event.target.value); + setSearchQuery(event.target.value); + }} + displayValue={(value: string) => valueToNameMapping.get(value) ?? ""} /> -
-
- {enableClear && selectedValue ? ( - - ) : null} - {filteredOptions.length > 0 && ( - - + +
+ + + {enableClear && selectedValue ? ( + + ) : null} + {filteredOptions.length > 0 && ( + + + {filteredOptions} + + + )} + + )} +
+ {error && errorMessage ? ( +

+ {errorMessage} +

+ ) : null} + ); }); diff --git a/src/components/input-elements/Select/Select.tsx b/src/components/input-elements/Select/Select.tsx index e1cfbd451..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, { cloneElement, isValidElement, useMemo } 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"; @@ -10,30 +10,41 @@ import { useInternalState } from "hooks"; const makeSelectClassName = makeClassName("Select"); -export interface SelectProps extends React.HTMLAttributes { +export interface SelectProps extends React.HTMLAttributes { value?: string; + name?: string; defaultValue?: string; onValueChange?: (value: string) => void; placeholder?: string; disabled?: boolean; icon?: React.JSXElementConstructor | React.ReactElement; enableClear?: boolean; + required?: boolean; + error?: boolean; + errorMessage?: string; children: React.ReactNode; } -const Select = React.forwardRef((props, ref) => { +const Select = React.forwardRef((props, ref) => { const { - defaultValue, + defaultValue = "", value, onValueChange, placeholder = "Select...", disabled = false, icon, - enableClear = true, + enableClear = false, + required, children, + name, + error = false, + errorMessage, className, + id, ...other } = props; + const listboxButtonRef = useRef(null); + const childrenArray = Children.toArray(children); // @sev const [selectedValue, setSelectedValue] = useInternalState(defaultValue, value); @@ -81,112 +92,155 @@ const Select = React.forwardRef((props, ref) => { }; return ( - { - onValueChange?.(value); - setSelectedValue(value); - }) as any - } - disabled={disabled} +
- {({ value }) => ( - <> - - {Icon && ( - - {Icon} + + { + onValueChange?.(value); + setSelectedValue(value); + }) as any + } + disabled={disabled} + className={tremorTwMerge( + // common + "w-full min-w-[10rem] relative text-tremor-default", + className, + )} + id={id} + {...other} + > + {({ value }) => ( + <> + + {Icon && ( + + {Icon} + + )} + + {value ? valueToNameMapping.get(value) ?? placeholder : placeholder} - )} - - {value ? valueToNameMapping.get(value) ?? placeholder : placeholder} - - - - - - {enableClear && selectedValue ? ( - + ) : null} + - - - ) : null} - - - {children} - - - - )} - + > + {children} + + + + )} + + {error && errorMessage ? ( +

+ {errorMessage} +

+ ) : null} +
); }); diff --git a/src/components/input-elements/Switch/Switch.tsx b/src/components/input-elements/Switch/Switch.tsx index a72ff686a..fc69aae56 100644 --- a/src/components/input-elements/Switch/Switch.tsx +++ b/src/components/input-elements/Switch/Switch.tsx @@ -93,7 +93,7 @@ const Switch = React.forwardRef((props, ref) => { disabled={disabled} className={tremorTwMerge( makeSwitchClassName("switch"), - "w-10 h-5 group relative inline-flex flex-shrink-0 cursor-pointer items-center justify-center rounded-tremor-full", + "w-10 h-5 group relative inline-flex shrink-0 cursor-pointer items-center justify-center rounded-tremor-full", "focus:outline-none", disabled ? "cursor-not-allowed" : "", )} diff --git a/src/components/layout-elements/Dialog/Dialog.tsx b/src/components/layout-elements/Dialog/Dialog.tsx index 0d79b5861..c1d0a6237 100644 --- a/src/components/layout-elements/Dialog/Dialog.tsx +++ b/src/components/layout-elements/Dialog/Dialog.tsx @@ -38,7 +38,6 @@ const Dialog = React.forwardRef((props, ref) => { )} > -
{children}
diff --git a/src/components/list-elements/Table/TableHeaderCell.tsx b/src/components/list-elements/Table/TableHeaderCell.tsx index 35da15e89..62ff8c461 100644 --- a/src/components/list-elements/Table/TableHeaderCell.tsx +++ b/src/components/list-elements/Table/TableHeaderCell.tsx @@ -17,9 +17,9 @@ const TableHeaderCell = React.forwardRef< // common "whitespace-nowrap text-left font-semibold top-0 px-4 py-3.5", // light - "text-tremor-content", + "text-tremor-content-strong", // dark - "dark:text-dark-tremor-content", + "dark:text-dark-tremor-content-strong", className, )} {...other} diff --git a/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx b/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx index 549f5c6b9..ebc3182f3 100644 --- a/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx +++ b/src/components/spark-elements/SparkAreaChart/SparkAreaChart.tsx @@ -1,12 +1,13 @@ "use client"; import React from "react"; -import { Area, AreaChart as ReChartsAreaChart, ResponsiveContainer, XAxis } from "recharts"; +import { Area, AreaChart as ReChartsAreaChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; import { BaseColors, colorPalette, getColorClassNames, themeColorRange, tremorTwMerge } from "lib"; import { CurveType } from "../../../lib/inputTypes"; import BaseSparkChartProps from "../common/BaseSparkChartProps"; -import { constructCategoryColors } from "components/chart-elements/common/utils"; +import { constructCategoryColors, getYAxisDomain } from "components/chart-elements/common/utils"; import NoData from "components/chart-elements/common/NoData"; +import { AxisDomain } from "recharts/types/util/types"; export interface SparkAreaChartProps extends BaseSparkChartProps { stack?: boolean; @@ -28,16 +29,21 @@ const AreaChart = React.forwardRef((props, curveType = "linear", connectNulls = false, noDataText, + autoMinValue = false, + minValue, + maxValue, className, ...other } = props; const categoryColors = constructCategoryColors(categories, colors); + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); return (
{data?.length ? ( + {categories.map((category) => { return ( diff --git a/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx b/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx index 7a9c2e954..4218fc324 100644 --- a/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx +++ b/src/components/spark-elements/SparkBarChart/SparkBarChart.tsx @@ -2,12 +2,13 @@ import { colorPalette, getColorClassNames, tremorTwMerge } from "lib"; import React from "react"; -import { Bar, BarChart as ReChartsBarChart, ResponsiveContainer, XAxis } from "recharts"; +import { Bar, BarChart as ReChartsBarChart, ResponsiveContainer, XAxis, YAxis } from "recharts"; import { BaseColors, themeColorRange } from "lib"; import BaseSparkChartProps from "../common/BaseSparkChartProps"; -import { constructCategoryColors } from "components/chart-elements/common/utils"; +import { constructCategoryColors, getYAxisDomain } from "components/chart-elements/common/utils"; import NoData from "components/chart-elements/common/NoData"; +import { AxisDomain } from "recharts/types/util/types"; export interface SparkBarChartProps extends BaseSparkChartProps { stack?: boolean; @@ -25,10 +26,14 @@ const SparkBarChart = React.forwardRef((prop animationDuration = 900, showAnimation = false, noDataText, + autoMinValue = false, + minValue, + maxValue, className, ...other } = props; const categoryColors = constructCategoryColors(categories, colors); + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); return (
@@ -39,6 +44,7 @@ const SparkBarChart = React.forwardRef((prop stackOffset={relative ? "expand" : "none"} margin={{ top: 0, left: -1.5, right: -1.5, bottom: 0 }} > + {categories.map((category) => ( ((pr curveType = "linear", connectNulls = false, noDataText, + autoMinValue = false, + minValue, + maxValue, className, ...other } = props; const categoryColors = constructCategoryColors(categories, colors); + const yAxisDomain = getYAxisDomain(autoMinValue, minValue, maxValue); return (
{data?.length ? ( + {categories.map((category) => ( ((props, ref) => { <>
-
-
((props, ref) "font-semibold text-tremor-metric", color ? getColorClassNames(color, colorPalette.darkText).textColor - : "text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis", + : "text-tremor-content-strong dark:text-dark-tremor-content-strong", className, )} {...other} diff --git a/src/components/text-elements/Subtitle/Subtitle.tsx b/src/components/text-elements/Subtitle/Subtitle.tsx index b38ab88c7..1f5992cc9 100644 --- a/src/components/text-elements/Subtitle/Subtitle.tsx +++ b/src/components/text-elements/Subtitle/Subtitle.tsx @@ -17,7 +17,7 @@ const Subtitle = React.forwardRef((props, r className={tremorTwMerge( color ? getColorClassNames(color, colorPalette.lightText).textColor - : "text-tremor-content-subtle dark:text-dark-tremor-content-subtle", + : "text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis", className, )} {...other} diff --git a/src/components/text-elements/Title/Title.tsx b/src/components/text-elements/Title/Title.tsx index e8eb42209..e40859d88 100644 --- a/src/components/text-elements/Title/Title.tsx +++ b/src/components/text-elements/Title/Title.tsx @@ -19,7 +19,7 @@ const Title = React.forwardRef((props, ref) => "font-medium text-tremor-title", color ? getColorClassNames(color, colorPalette.darkText).textColor - : "text-tremor-content-emphasis dark:text-dark-tremor-content-emphasis", + : "text-tremor-content-strong dark:text-dark-tremor-content-strong", className, )} {...other} diff --git a/src/components/util-elements/Tooltip/Tooltip.tsx b/src/components/util-elements/Tooltip/Tooltip.tsx index 259357d87..115e8499c 100644 --- a/src/components/util-elements/Tooltip/Tooltip.tsx +++ b/src/components/util-elements/Tooltip/Tooltip.tsx @@ -83,7 +83,7 @@ const Tooltip = ({ text, open, x, y, refs, strategy, getFloatingProps }: Tooltip
= T & { key?: string; value: number; name: string; @@ -33,19 +33,21 @@ const getWidthsFromValues = (dataValues: number[]) => { }); }; -export interface BarListProps extends React.HTMLAttributes { - data: Bar[]; +export interface BarListProps extends React.HTMLAttributes { + data: Bar[]; valueFormatter?: ValueFormatter; color?: Color; showAnimation?: boolean; + onValueChange?: (payload: Bar) => void; } -const BarList = React.forwardRef((props, ref) => { +function BarListInner(props: BarListProps, ref: React.ForwardedRef) { const { data = [], color, valueFormatter = defaultValueFormatter, showAnimation = false, + onValueChange, className, ...other } = props; @@ -117,7 +119,15 @@ const BarList = React.forwardRef((props, ref) => { transition: showAnimation ? "all 1s" : "", }} > -
+
{ + onValueChange?.(item); + }} + > {Icon} {item.href ? ( ((props, ref) => { makeBarListClassName("barLink"), // common "whitespace-nowrap hover:underline truncate text-tremor-default", + onValueChange ? "cursor-pointer" : "", // light "text-tremor-content-emphasis", // dark "dark:text-dark-tremor-content-emphasis", )} + onClick={() => { + onValueChange?.(item); + }} > {item.name} @@ -142,11 +156,15 @@ const BarList = React.forwardRef((props, ref) => { makeBarListClassName("barText"), // common "whitespace-nowrap truncate text-tremor-default", + onValueChange ? "cursor-pointer" : "", // light "text-tremor-content-emphasis", // dark "dark:text-dark-tremor-content-emphasis", )} + onClick={() => { + onValueChange?.(item); + }} > {item.name}

@@ -185,8 +203,12 @@ const BarList = React.forwardRef((props, ref) => {
); -}); +} + +BarListInner.displayName = "BarList"; -BarList.displayName = "BarList"; +const BarList = React.forwardRef(BarListInner) as ( + p: BarListProps & { ref?: React.ForwardedRef }, +) => ReturnType; export default BarList; diff --git a/src/lib/theme.ts b/src/lib/theme.ts index dd6ec3d60..c40a83a1a 100644 --- a/src/lib/theme.ts +++ b/src/lib/theme.ts @@ -16,8 +16,10 @@ export const colorPalette = { darkBorder: 700, lightRing: 200, ring: 300, + iconRing: 500, lightText: 400, text: 500, + iconText: 600, darkText: 700, darkestText: 900, icon: 500, diff --git a/src/stories/chart-elements/AreaChart.stories.tsx b/src/stories/chart-elements/AreaChart.stories.tsx index 20564c833..a1bfb8af6 100644 --- a/src/stories/chart-elements/AreaChart.stories.tsx +++ b/src/stories/chart-elements/AreaChart.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { AreaChart } from "components"; -import { CustomTooltipType } from "components/chart-elements/common/CustomTooltipProps"; +import { CustomTooltipProps } from "components/chart-elements/common/CustomTooltipProps"; import { Color, currencyValueFormatter } from "lib"; import { simpleBaseChartData as data, @@ -74,10 +74,6 @@ export const ChangedCategoriesOrder: Story = { args: { categories: ["Successful Payments", "Sales"] }, }; -export const LessColorsThanCategories: Story = { - args: { colors: ["green"] }, -}; - export const LongValues: Story = { args: { categories: ["This is an edge case"] }, }; @@ -228,7 +224,7 @@ export const CustomTooltipSimple: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -263,7 +259,7 @@ export const CustomTooltipPreviousDay: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -307,7 +303,7 @@ export const CustomTooltipComplex: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -355,36 +351,9 @@ export const CustomTooltipComplex: Story = { }, }; -// keep because of if statement -// const ResponsiveTemplate: ComponentStory = (args) => { -// if (args.onValueChange?.length === 0) { -// args.onValueChange = undefined; -// } - -// return ( -// <> -// Desktop -// -// -// -// Mobile -//
-// -// -// -//
-// -// ); -// }; - -// const DefaultTemplate: ComponentStory = ({ ...args }) => { -// if (args.onValueChange?.length === 0) { -// args.onValueChange = undefined; -// } - -// return ( -// -// -// -// ); -// +export const tickGap: Story = { + args: { + data: longBaseChartData, + tickGap: 200, + }, +}; diff --git a/src/stories/chart-elements/BarChart.stories.tsx b/src/stories/chart-elements/BarChart.stories.tsx index 727936a22..4381d5756 100644 --- a/src/stories/chart-elements/BarChart.stories.tsx +++ b/src/stories/chart-elements/BarChart.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { BarChart } from "components"; -import { CustomTooltipType } from "components/chart-elements/common/CustomTooltipProps"; +import { CustomTooltipProps } from "components/chart-elements/common/CustomTooltipProps"; import { Color, currencyValueFormatter } from "lib"; import { simpleBaseChartData as data, @@ -100,10 +100,6 @@ export const ChangedCategoriesOrder: Story = { args: { categories: ["Successful Payments", "Sales"] }, }; -export const LessColorsThanCategories: Story = { - args: { colors: ["green"] }, -}; - export const LongValues: Story = { args: { categories: ["This is an edge case"] }, }; @@ -244,7 +240,7 @@ export const CustomTooltipSimple: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -279,7 +275,7 @@ export const CustomTooltipPreviousDay: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -323,7 +319,7 @@ export const CustomTooltipComplex: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -370,3 +366,10 @@ export const CustomTooltipComplex: Story = { }, }, }; + +export const tickGap: Story = { + args: { + data: longBaseChartData, + tickGap: 200, + }, +}; diff --git a/src/stories/chart-elements/DonutChart.stories.tsx b/src/stories/chart-elements/DonutChart.stories.tsx index 51e94966a..3ae2a563f 100644 --- a/src/stories/chart-elements/DonutChart.stories.tsx +++ b/src/stories/chart-elements/DonutChart.stories.tsx @@ -4,7 +4,7 @@ import type { Meta, StoryObj } from "@storybook/react"; import { DonutChart } from "components"; -import { CustomTooltipType } from "components/chart-elements/common/CustomTooltipProps"; +import { CustomTooltipProps } from "components/chart-elements/common/CustomTooltipProps"; import { currencyValueFormatter } from "lib"; import { simpleBaseChartData as data2, @@ -144,7 +144,7 @@ export const CustomTooltipSimple: Story = { index: "month", category: "Sales", valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; const categoryPayload = payload?.[0]; diff --git a/src/stories/chart-elements/LineChart.stories.tsx b/src/stories/chart-elements/LineChart.stories.tsx index 69dedaae4..865f5fe77 100644 --- a/src/stories/chart-elements/LineChart.stories.tsx +++ b/src/stories/chart-elements/LineChart.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { LineChart } from "components"; -import { CustomTooltipType } from "components/chart-elements/common/CustomTooltipProps"; +import { CustomTooltipProps } from "components/chart-elements/common/CustomTooltipProps"; import { Color, currencyValueFormatter } from "lib"; import { simpleBaseChartData as data, @@ -64,10 +64,6 @@ export const ChangedCategoriesOrder: Story = { args: { categories: ["Successful Payments", "Sales"] }, }; -export const LessColorsThanCategories: Story = { - args: { colors: ["green"] }, -}; - export const LongValues: Story = { args: { categories: ["This is an edge case"] }, }; @@ -188,7 +184,7 @@ export const CustomTooltipSimple: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -223,7 +219,7 @@ export const CustomTooltipPreviousDay: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -267,7 +263,7 @@ export const CustomTooltipComplex: Story = { categories: ["Sales"], colors: customTooltipColors, valueFormatter: currencyValueFormatter, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -314,3 +310,10 @@ export const CustomTooltipComplex: Story = { }, }, }; + +export const tickGap: Story = { + args: { + data: longBaseChartData, + tickGap: 200, + }, +}; diff --git a/src/stories/chart-elements/ScatterChart.stories.tsx b/src/stories/chart-elements/ScatterChart.stories.tsx index 5bb658666..72d923444 100644 --- a/src/stories/chart-elements/ScatterChart.stories.tsx +++ b/src/stories/chart-elements/ScatterChart.stories.tsx @@ -3,7 +3,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; import { ScatterChart } from "components"; -import { CustomTooltipType } from "components/chart-elements/common/CustomTooltipProps"; +import { CustomTooltipProps } from "components/chart-elements/common/CustomTooltipProps"; import { Color } from "lib"; import { simpleScatterChartData as data, @@ -157,7 +157,7 @@ export const CustomTooltip: Story = { args: { colors: customTooltipColors, category: customTooltipIndex, - customTooltip: (props: CustomTooltipType) => { + customTooltip: (props: CustomTooltipProps) => { const { payload, active, label } = props; if (!active || !payload) return null; @@ -184,3 +184,9 @@ export const CustomTooltip: Story = { }, }, }; + +export const tickGap: Story = { + args: { + tickGap: 500, + }, +}; diff --git a/src/stories/icon-elements/Icon.stories.tsx b/src/stories/icon-elements/Icon.stories.tsx index 8caa2320c..8b7ca3f78 100644 --- a/src/stories/icon-elements/Icon.stories.tsx +++ b/src/stories/icon-elements/Icon.stories.tsx @@ -65,6 +65,20 @@ const IconTemplateColors: Story = { }, }; +const IconShrink: Story = { + render: ({ ...args }) => { + return ( +
+ + + + + +
+ ); + }, +}; + export const Default: Story = { args: {}, }; @@ -96,3 +110,7 @@ export const ColorsWithIconAsReactElement: Story = { icon: , }, }; + +export const Shrink: Story = { + ...IconShrink, +}; diff --git a/src/stories/input-elements/MultiSelect.stories.tsx b/src/stories/input-elements/MultiSelect.stories.tsx index f87e384f6..42a13690f 100644 --- a/src/stories/input-elements/MultiSelect.stories.tsx +++ b/src/stories/input-elements/MultiSelect.stories.tsx @@ -5,6 +5,7 @@ import { MultiSelect } from "components"; import { SimpleMultiSelect, SimpleMultiSelectControlled, + SimpleMultiSelectForm, SimpleMultiSelectWithStaticAndDynamicChildren, } from "./helpers/SimpleMultiSelect"; @@ -61,3 +62,18 @@ export const Controlled: Story = { render: SimpleMultiSelectControlled, args: {}, }; + +export const Error: Story = { + render: SimpleMultiSelect, + args: { + error: true, + errorMessage: "Error message", + }, +}; + +export const Form: Story = { + render: SimpleMultiSelectForm, + args: { + required: true, + }, +}; diff --git a/src/stories/input-elements/SearchSelect.stories.tsx b/src/stories/input-elements/SearchSelect.stories.tsx index e99187403..fbda963f3 100644 --- a/src/stories/input-elements/SearchSelect.stories.tsx +++ b/src/stories/input-elements/SearchSelect.stories.tsx @@ -7,6 +7,8 @@ import { SimpleSearchSelect2, SimpleSearchSelectControlled, SimpleSearchSelectWithStaticAndDynamicChildren, + SimpleSearchSelectForm, + SimpleSearchSelectServerSideRendering, } from "./helpers/SimpleSearchSelect"; import { CalendarIcon } from "assets"; @@ -66,6 +68,14 @@ export const IconAsReactElement: Story = { }, }; +export const Error: Story = { + render: SimpleSearchSelect, + args: { + error: true, + errorMessage: "Error message", + }, +}; + export const Disabled: Story = { render: SimpleSearchSelect, args: { @@ -78,3 +88,17 @@ export const Controlled: Story = { render: SimpleSearchSelectControlled, args: {}, }; + +export const Form: Story = { + render: SimpleSearchSelectForm, + args: { + required: true, + }, +}; + +export const ServerSideRendering: Story = { + render: SimpleSearchSelectServerSideRendering, + args: { + required: true, + }, +}; diff --git a/src/stories/input-elements/Select.stories.tsx b/src/stories/input-elements/Select.stories.tsx index 51e890e29..7d611bab2 100644 --- a/src/stories/input-elements/Select.stories.tsx +++ b/src/stories/input-elements/Select.stories.tsx @@ -6,6 +6,7 @@ import { SimpleSelect, SimpleSelect2, SimpleSelectControlled, + SimpleSelectForm, SimpleSelectWithStaticAndDynamicChildren, } from "./helpers/SimpleSelect"; @@ -50,7 +51,7 @@ export const UncontrolledDefaultValue: Story = { }, }; -export const UncontrolledIcon: Story = { +export const Icon: Story = { render: SimpleSelect, args: { defaultValue: "5", @@ -85,3 +86,18 @@ export const UncontrolledDisabled: Story = { export const Controlled: Story = { render: SimpleSelectControlled, }; + +export const Error: Story = { + render: SimpleSelect, + args: { + error: true, + errorMessage: "Error message", + }, +}; + +export const Form: Story = { + render: SimpleSelectForm, + args: { + required: true, + }, +}; diff --git a/src/stories/input-elements/TextInput.stories.tsx b/src/stories/input-elements/TextInput.stories.tsx index bca667790..4430a26d6 100644 --- a/src/stories/input-elements/TextInput.stories.tsx +++ b/src/stories/input-elements/TextInput.stories.tsx @@ -95,6 +95,14 @@ export const WithTypePassword: Story = { }, }; +export const WithTypePasswordError: Story = { + render: SimpleTextInput, + args: { + type: "password", + error: true, + }, +}; + export const WithAutoFocus: Story = { args: { autoFocus: true, diff --git a/src/stories/input-elements/helpers/SimpleMultiSelect.tsx b/src/stories/input-elements/helpers/SimpleMultiSelect.tsx index 523468c8e..5db12adfb 100644 --- a/src/stories/input-elements/helpers/SimpleMultiSelect.tsx +++ b/src/stories/input-elements/helpers/SimpleMultiSelect.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { Button, MultiSelect, MultiSelectItem } from "components"; +import { Button, MultiSelect, MultiSelectItem, TextInput } from "components"; export const SimpleMultiSelect = (args: any) => ( @@ -22,12 +22,34 @@ export const SimpleMultiSelectWithStaticAndDynamicChildren = (args: any) => { ); }; +export const SimpleMultiSelectForm = (args: any) => { + return ( +
+ + + + Five + Three + One + + +

You will find your selected value in the URL params after submiting the form

+
+ ); +}; + export const SimpleMultiSelectControlled = () => { const [value, setValue] = React.useState([]); const handleValueChange = (values: string[]) => { setValue(values); - // You can perform any additional actions here when the value changes. }; const handleReset = () => { @@ -70,5 +92,3 @@ export const SimpleMultiSelectControlled = () => {
); }; - -export default SimpleMultiSelectControlled; diff --git a/src/stories/input-elements/helpers/SimpleSearchSelect.tsx b/src/stories/input-elements/helpers/SimpleSearchSelect.tsx index cd988afdc..ab2499289 100644 --- a/src/stories/input-elements/helpers/SimpleSearchSelect.tsx +++ b/src/stories/input-elements/helpers/SimpleSearchSelect.tsx @@ -1,7 +1,7 @@ import React from "react"; import { CalendarIcon } from "assets"; -import { Button, SearchSelect, SearchSelectItem } from "components"; +import { Button, SearchSelect, SearchSelectItem, TextInput } from "components"; export const SimpleSearchSelect = (args: any) => ( @@ -39,18 +39,47 @@ export const SimpleSearchSelectWithStaticAndDynamicChildren = (args: any) => { ); }; -export const SimpleSearchSelectControlled = (args: any) => { - const [value, setValue] = React.useState("5"); +export const SimpleSearchSelectForm = (args: any) => { + return ( +
+ + + + Five + Three + One + + +

You will find your selected value in the URL params after submiting the form

+
+ ); +}; + +export function SimpleSearchSelectControlled() { + const [value, setValue] = React.useState("5"); + + const handleValueChange = (newValue: string) => { + setValue(newValue); + }; + + const handleReset = () => { + setValue(""); + }; + + const handleSetToOne = () => { + setValue("1"); + }; + return (
- { - setValue(value); - alert(value); - }} - {...args} - > + One Two Three @@ -62,14 +91,7 @@ export const SimpleSearchSelectControlled = (args: any) => { Nine Ten - { - setValue(value); - alert(value); - }} - {...args} - > + One Two Three @@ -81,9 +103,64 @@ export const SimpleSearchSelectControlled = (args: any) => { Nine Ten - - + +

value: {value}

); -}; +} + +export function SimpleSearchSelectServerSideRendering() { + const [searchQuery, setSearchQuery] = React.useState(""); + + interface User { + id: number; + name: string; + email: string; + } + const [options, setOptions] = React.useState([]); + + const [value, setValue] = React.useState(); + + const handleSearchQueryChange = (query: string) => { + setSearchQuery(query); + }; + + const handleValueChange = (newValue: string) => { + setValue(newValue); + }; + + const handleReset = () => { + setValue(""); + }; + + React.useEffect(() => { + if (searchQuery) { + fetch("https://jsonplaceholder.typicode.com/users") + .then((response) => response.json()) + .then((data) => { + setOptions(data); + }) + .catch((error) => console.error("Error fetching user data:", error)); + } + }, [searchQuery]); + + return ( +
+ + {options.map((option) => ( + + {`${option.name} (${option.email})`} + + ))} + + +

Selected User ID: {value}

+
+ ); +} diff --git a/src/stories/input-elements/helpers/SimpleSelect.tsx b/src/stories/input-elements/helpers/SimpleSelect.tsx index db49ec06c..535c4e636 100644 --- a/src/stories/input-elements/helpers/SimpleSelect.tsx +++ b/src/stories/input-elements/helpers/SimpleSelect.tsx @@ -1,7 +1,7 @@ import React from "react"; import { CalendarIcon } from "assets"; -import { Button, Select, SelectItem } from "components"; +import { Button, Select, SelectItem, TextInput } from "components"; export const SimpleSelect = (args: any) => ( + + Five + Three + One + + +

You will find your selected value in the URL params after submiting the form

+ + ); +}; diff --git a/src/stories/list-elements/Table.stories.tsx b/src/stories/list-elements/Table.stories.tsx index 962f10c78..605e715d3 100644 --- a/src/stories/list-elements/Table.stories.tsx +++ b/src/stories/list-elements/Table.stories.tsx @@ -1,16 +1,7 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import { - Table, - TableBody, - TableCell, - TableFoot, - TableFooterCell, - TableHead, - TableHeaderCell, - TableRow, -} from "components"; +import { Table, TableBody, TableCell, TableHead, TableHeaderCell, TableRow } from "components"; import BadgeDelta from "components/icon-elements/BadgeDelta/BadgeDelta"; import { DeltaType } from "lib"; @@ -35,7 +26,6 @@ const data = [ status: "overperforming", deltaType: "moderateIncrease", hours: 100, - productivity: "n/a", }, { id: 2, @@ -45,7 +35,6 @@ const data = [ status: "overperforming", deltaType: "moderateIncrease", hours: 110, - productivity: "n/a", }, { id: 3, @@ -55,7 +44,6 @@ const data = [ status: "underperforming", deltaType: "moderateDecrease", hours: 90, - productivity: "n/a", }, { id: 4, @@ -65,7 +53,6 @@ const data = [ status: "overperforming", deltaType: "moderateDecrease", hours: 92, - productivity: "n/a", }, { id: 5, @@ -75,7 +62,6 @@ const data = [ status: "underperforming", deltaType: "moderateDecrease", hours: 95, - productivity: "n/a", }, { id: 6, @@ -85,17 +71,15 @@ const data = [ status: "overperforming", deltaType: "moderateIncrease", hours: 98, - productivity: "n/a", }, { id: 7, name: "Employee 7", sales: "800000", region: "Region G", - status: "underperforming", - deltaType: "moderateDecrease", + status: "average", + deltaType: "unchanged", hours: 101, - productivity: "n/a", }, { id: 8, @@ -105,7 +89,6 @@ const data = [ status: "overperforming", deltaType: "moderateDecrease", hours: 104, - productivity: "n/a", }, { id: 9, @@ -115,17 +98,15 @@ const data = [ status: "underperforming", deltaType: "moderateIncrease", hours: 107, - productivity: "n/a", }, { id: 10, name: "Employee 10", sales: "1100000", region: "Region J", - status: "overperforming", - deltaType: "moderateDecrease", + status: "average", + deltaType: "unchanged", hours: 110, - productivity: "n/a", }, { id: 11, @@ -135,7 +116,6 @@ const data = [ status: "underperforming", deltaType: "moderateDecrease", hours: 113, - productivity: "n/a", }, { id: 12, @@ -145,7 +125,6 @@ const data = [ status: "overperforming", deltaType: "moderateIncrease", hours: 116, - productivity: "n/a", }, { id: 13, @@ -155,7 +134,6 @@ const data = [ status: "underperforming", deltaType: "moderateDecrease", hours: 119, - productivity: "n/a", }, ]; @@ -169,10 +147,8 @@ export const Default: Story = { Region Status Working Hours (h) - Productivity - {data.map((item) => ( @@ -180,7 +156,7 @@ export const Default: Story = { {item.sales} {item.region} - + {item.status} @@ -188,15 +164,6 @@ export const Default: Story = { ))} - - - - 4642 - - - 15h - - ), args: { diff --git a/src/stories/spark-elements/SparkAreaChart.stories.tsx b/src/stories/spark-elements/SparkAreaChart.stories.tsx index eae81515f..bc6c4bcb6 100644 --- a/src/stories/spark-elements/SparkAreaChart.stories.tsx +++ b/src/stories/spark-elements/SparkAreaChart.stories.tsx @@ -10,6 +10,7 @@ import { } from "../chart-elements/helpers/testData"; import { SparkAreaChart } from "components/spark-elements"; import ExampleCard from "./helpers/ExampleCard"; +import { smallVariantionDatas } from "./helpers/testData"; const meta: Meta = { title: "Visualizations/Chart/SparkAreaChart", @@ -55,10 +56,6 @@ export const ChangedCategoriesOrder: Story = { args: { categories: ["Successful Payments", "Sales"] }, }; -export const LessColorsThanCategories: Story = { - args: { colors: ["green"] }, -}; - export const NoData: Story = { args: { data: [] }, }; @@ -140,3 +137,12 @@ export const WithCard: Story = { ), ], }; + +export const WithAutoMinValue: Story = { + args: { + data: smallVariantionDatas, + index: "ts", + categories: ["avg_price"], + autoMinValue: true, + }, +}; diff --git a/src/stories/spark-elements/SparkBarChart.stories.tsx b/src/stories/spark-elements/SparkBarChart.stories.tsx index 26362b489..404e76b81 100644 --- a/src/stories/spark-elements/SparkBarChart.stories.tsx +++ b/src/stories/spark-elements/SparkBarChart.stories.tsx @@ -10,6 +10,7 @@ import { } from "../chart-elements/helpers/testData"; import { SparkBarChart } from "components/spark-elements"; import ExampleCard from "./helpers/ExampleCard"; +import { smallVariantionDatas } from "./helpers/testData"; const meta: Meta = { title: "Visualizations/Chart/SparkBarChart", @@ -59,10 +60,6 @@ export const ChangedCategoriesOrder: Story = { args: { categories: ["Successful Payments", "Sales"] }, }; -export const LessColorsThanCategories: Story = { - args: { colors: ["green"] }, -}; - export const NoData: Story = { args: { data: [] }, }; @@ -133,3 +130,12 @@ export const WithCard: Story = { ), ], }; + +export const WithAutoMinValue: Story = { + args: { + data: smallVariantionDatas, + index: "ts", + categories: ["avg_price"], + autoMinValue: true, + }, +}; diff --git a/src/stories/spark-elements/SparkLineChart.stories.tsx b/src/stories/spark-elements/SparkLineChart.stories.tsx index 3846085cd..f151c0b7b 100644 --- a/src/stories/spark-elements/SparkLineChart.stories.tsx +++ b/src/stories/spark-elements/SparkLineChart.stories.tsx @@ -10,6 +10,7 @@ import { } from "../chart-elements/helpers/testData"; import { SparkLineChart } from "components/spark-elements"; import ExampleCard from "./helpers/ExampleCard"; +import { smallVariantionDatas } from "./helpers/testData"; const meta: Meta = { title: "Visualizations/Chart/SparkLineChart", @@ -45,10 +46,6 @@ export const ChangedCategoriesOrder: Story = { args: { categories: ["Successful Payments", "Sales"] }, }; -export const LessColorsThanCategories: Story = { - args: { colors: ["green"] }, -}; - export const NoData: Story = { args: { data: [] }, }; @@ -130,3 +127,12 @@ export const WithCard: Story = { ), ], }; + +export const WithAutoMinValue: Story = { + args: { + data: smallVariantionDatas, + index: "ts", + categories: ["avg_price"], + autoMinValue: true, + }, +}; diff --git a/src/stories/spark-elements/helpers/testData.ts b/src/stories/spark-elements/helpers/testData.ts new file mode 100644 index 000000000..86eff8c53 --- /dev/null +++ b/src/stories/spark-elements/helpers/testData.ts @@ -0,0 +1,142 @@ +export const smallVariantionDatas = [ + { ts: "2024-02-05T18:36:00.000Z", avg_price: 3112.96 }, + { ts: "2024-02-05T18:37:00.000Z", avg_price: 3112.8749999999995 }, + { ts: "2024-02-05T18:38:00.000Z", avg_price: 3112.62 }, + { ts: "2024-02-05T18:39:00.000Z", avg_price: 3113.4491666666668 }, + { ts: "2024-02-05T18:40:00.000Z", avg_price: 3113.5900000000006 }, + { ts: "2024-02-05T18:41:00.000Z", avg_price: 3113.5900000000006 }, + { ts: "2024-02-05T18:42:00.000Z", avg_price: 3113.5900000000006 }, + { ts: "2024-02-05T18:43:00.000Z", avg_price: 3113.590000000001 }, + { ts: "2024-02-05T18:44:00.000Z", avg_price: 3113.590000000001 }, + { ts: "2024-02-05T18:45:00.000Z", avg_price: 3113.590000000001 }, + { ts: "2024-02-05T18:46:00.000Z", avg_price: 3113.590000000001 }, + { ts: "2024-02-05T18:47:00.000Z", avg_price: 3113.590000000001 }, + { ts: "2024-02-05T18:48:00.000Z", avg_price: 3113.590000000001 }, + { ts: "2024-02-05T18:49:00.000Z", avg_price: 3113.882 }, + { ts: "2024-02-05T18:50:00.000Z", avg_price: 3115.05 }, + { ts: "2024-02-05T18:51:00.000Z", avg_price: 3115.0499999999997 }, + { ts: "2024-02-05T18:52:00.000Z", avg_price: 3112.694333333334 }, + { ts: "2024-02-05T18:53:00.000Z", avg_price: 3111.2300000000005 }, + { ts: "2024-02-05T18:54:00.000Z", avg_price: 3111.2300000000005 }, + { ts: "2024-02-05T18:55:00.000Z", avg_price: 3111.230000000001 }, + { ts: "2024-02-05T18:56:00.000Z", avg_price: 3111.230000000001 }, + { ts: "2024-02-05T18:57:00.000Z", avg_price: 3111.2300000000005 }, + { ts: "2024-02-05T18:58:00.000Z", avg_price: 3111.2300000000005 }, + { ts: "2024-02-05T18:59:00.000Z", avg_price: 3111.2300000000005 }, + { ts: "2024-02-05T19:00:00.000Z", avg_price: 3111.230000000001 }, + { ts: "2024-02-05T19:01:00.000Z", avg_price: 3111.958 }, + { ts: "2024-02-05T19:02:00.000Z", avg_price: 3112.01 }, + { ts: "2024-02-05T19:03:00.000Z", avg_price: 3112.0099999999998 }, + { ts: "2024-02-05T19:04:00.000Z", avg_price: 3111.989 }, + { ts: "2024-02-05T19:05:00.000Z", avg_price: 3111.8221666666664 }, + { ts: "2024-02-05T19:06:00.000Z", avg_price: 3111.87 }, + { ts: "2024-02-05T19:07:00.000Z", avg_price: 3111.87 }, + { ts: "2024-02-05T19:08:00.000Z", avg_price: 3111.87 }, + { ts: "2024-02-05T19:09:00.000Z", avg_price: 3111.87 }, + { ts: "2024-02-05T19:10:00.000Z", avg_price: 3111.8700000000003 }, + { ts: "2024-02-05T19:11:00.000Z", avg_price: 3111.8699999999994 }, + { ts: "2024-02-05T19:12:00.000Z", avg_price: 3111.8700000000003 }, + { ts: "2024-02-05T19:13:00.000Z", avg_price: 3112.026 }, + { ts: "2024-02-05T19:14:00.000Z", avg_price: 3112.2300000000005 }, + { ts: "2024-02-05T19:15:00.000Z", avg_price: 3111.4049999999997 }, + { ts: "2024-02-05T19:16:00.000Z", avg_price: 3111.252 }, + { ts: "2024-02-05T19:17:00.000Z", avg_price: 3111.846666666667 }, + { ts: "2024-02-05T19:18:00.000Z", avg_price: 3112.86 }, + { ts: "2024-02-05T19:19:00.000Z", avg_price: 3112.692 }, + { ts: "2024-02-05T19:20:00.000Z", avg_price: 3112.019999999999 }, + { ts: "2024-02-05T19:21:00.000Z", avg_price: 3112.0199999999995 }, + { ts: "2024-02-05T19:22:00.000Z", avg_price: 3112.0199999999995 }, + { ts: "2024-02-05T19:23:00.000Z", avg_price: 3113.5308333333332 }, + { ts: "2024-02-05T19:24:00.000Z", avg_price: 3114.61 }, + { ts: "2024-02-05T19:25:00.000Z", avg_price: 3114.6099999999997 }, + { ts: "2024-02-05T19:26:00.000Z", avg_price: 3114.61 }, + { ts: "2024-02-05T19:27:00.000Z", avg_price: 3114.61 }, + { ts: "2024-02-05T19:28:00.000Z", avg_price: 3114.6820000000002 }, + { ts: "2024-02-05T19:29:00.000Z", avg_price: 3114.7699999999995 }, + { ts: "2024-02-05T19:30:00.000Z", avg_price: 3114.769999999999 }, + { ts: "2024-02-05T19:31:00.000Z", avg_price: 3114.769999999999 }, + { ts: "2024-02-05T19:32:00.000Z", avg_price: 3114.7699999999995 }, + { ts: "2024-02-05T19:33:00.000Z", avg_price: 3114.7699999999995 }, + { ts: "2024-02-05T19:34:00.000Z", avg_price: 3114.7699999999995 }, + { ts: "2024-02-05T19:35:00.000Z", avg_price: 3114.769999999999 }, + { ts: "2024-02-05T19:36:00.000Z", avg_price: 3114.769999999999 }, + { ts: "2024-02-05T19:37:00.000Z", avg_price: 3114.5096666666664 }, + { ts: "2024-02-05T19:38:00.000Z", avg_price: 3113.3500000000004 }, + { ts: "2024-02-05T19:39:00.000Z", avg_price: 3113.3500000000004 }, + { ts: "2024-02-05T19:40:00.000Z", avg_price: 3113.3500000000004 }, + { ts: "2024-02-05T19:41:00.000Z", avg_price: 3113.402 }, + { ts: "2024-02-05T19:42:00.000Z", avg_price: 3113.61 }, + { ts: "2024-02-05T19:43:00.000Z", avg_price: 3113.61 }, + { ts: "2024-02-05T19:44:00.000Z", avg_price: 3113.61 }, + { ts: "2024-02-05T19:45:00.000Z", avg_price: 3113.6099999999997 }, + { ts: "2024-02-05T19:46:00.000Z", avg_price: 3113.61 }, + { ts: "2024-02-05T19:47:00.000Z", avg_price: 3113.61 }, + { ts: "2024-02-05T19:48:00.000Z", avg_price: 3113.61 }, + { ts: "2024-02-05T19:49:00.000Z", avg_price: 3113.508166666667 }, + { ts: "2024-02-05T19:50:00.000Z", avg_price: 3113.4800000000005 }, + { ts: "2024-02-05T19:51:00.000Z", avg_price: 3113.480000000001 }, + { ts: "2024-02-05T19:52:00.000Z", avg_price: 3115.1179999999995 }, + { ts: "2024-02-05T19:53:00.000Z", avg_price: 3115.2999999999997 }, + { ts: "2024-02-05T19:54:00.000Z", avg_price: 3115.564833333333 }, + { ts: "2024-02-05T19:55:00.000Z", avg_price: 3117.5700000000006 }, + { ts: "2024-02-05T19:56:00.000Z", avg_price: 3118.8389999999995 }, + { ts: "2024-02-05T19:57:00.000Z", avg_price: 3121.8 }, + { ts: "2024-02-05T19:58:00.000Z", avg_price: 3121.7999999999997 }, + { ts: "2024-02-05T19:59:00.000Z", avg_price: 3121.7999999999997 }, + { ts: "2024-02-05T20:00:00.000Z", avg_price: 3120.780333333333 }, + { ts: "2024-02-05T20:01:00.000Z", avg_price: 3117.4299999999994 }, + { ts: "2024-02-05T20:02:00.000Z", avg_price: 3117.7874999999995 }, + { ts: "2024-02-05T20:03:00.000Z", avg_price: 3116.5 }, + { ts: "2024-02-05T20:04:00.000Z", avg_price: 3115.8933333333325 }, + { ts: "2024-02-05T20:05:00.000Z", avg_price: 3114.6799999999994 }, + { ts: "2024-02-05T20:06:00.000Z", avg_price: 3114.6799999999994 }, + { ts: "2024-02-05T20:07:00.000Z", avg_price: 3117.9186666666665 }, + { ts: "2024-02-05T20:08:00.000Z", avg_price: 3120.36 }, + { ts: "2024-02-05T20:09:00.000Z", avg_price: 3120.36 }, + { ts: "2024-02-05T20:10:00.000Z", avg_price: 3120.36 }, + { ts: "2024-02-05T20:11:00.000Z", avg_price: 3120.301999999999 }, + { ts: "2024-02-05T20:12:00.000Z", avg_price: 3120.6383333333333 }, + { ts: "2024-02-05T20:13:00.000Z", avg_price: 3121.2480000000005 }, + { ts: "2024-02-05T20:14:00.000Z", avg_price: 3122.21 }, + { ts: "2024-02-05T20:15:00.000Z", avg_price: 3120.6016666666674 }, + { ts: "2024-02-05T20:16:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:17:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:18:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:19:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:20:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:21:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:22:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:23:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:24:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:25:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:26:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:27:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:28:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:29:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:30:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:31:00.000Z", avg_price: 3119.83 }, + { ts: "2024-02-05T20:32:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:33:00.000Z", avg_price: 3119.8300000000004 }, + { ts: "2024-02-05T20:34:00.000Z", avg_price: 3119.067666666667 }, + { ts: "2024-02-05T20:35:00.000Z", avg_price: 3118.343500000001 }, + { ts: "2024-02-05T20:36:00.000Z", avg_price: 3120.8791666666666 }, + { ts: "2024-02-05T20:37:00.000Z", avg_price: 3121.9400000000005 }, + { ts: "2024-02-05T20:38:00.000Z", avg_price: 3123.2384999999995 }, + { ts: "2024-02-05T20:39:00.000Z", avg_price: 3123.5299999999993 }, + { ts: "2024-02-05T20:40:00.000Z", avg_price: 3125.2688333333335 }, + { ts: "2024-02-05T20:41:00.000Z", avg_price: 3126.2200000000007 }, + { ts: "2024-02-05T20:42:00.000Z", avg_price: 3130.6138333333333 }, + { ts: "2024-02-05T20:43:00.000Z", avg_price: 3132.65 }, + { ts: "2024-02-05T20:44:00.000Z", avg_price: 3132.902833333333 }, + { ts: "2024-02-05T20:45:00.000Z", avg_price: 3133.0599999999995 }, + { ts: "2024-02-05T20:46:00.000Z", avg_price: 3132.617333333333 }, + { ts: "2024-02-05T20:47:00.000Z", avg_price: 3131.4 }, + { ts: "2024-02-05T20:48:00.000Z", avg_price: 3131.3999999999996 }, + { ts: "2024-02-05T20:49:00.000Z", avg_price: 3131.4 }, + { ts: "2024-02-05T20:50:00.000Z", avg_price: 3136.072 }, + { ts: "2024-02-05T20:51:00.000Z", avg_price: 3137.2400000000002 }, + { ts: "2024-02-05T20:52:00.000Z", avg_price: 3137.24 }, + { ts: "2024-02-05T20:53:00.000Z", avg_price: 3137.24 }, + { ts: "2024-02-05T20:54:00.000Z", avg_price: 3137.2400000000002 }, + { ts: "2024-02-05T20:55:00.000Z", avg_price: 3137.24 }, +]; diff --git a/src/stories/vis-elements/BarList.stories.tsx b/src/stories/vis-elements/BarList.stories.tsx index 4d52667b6..de7f3ec3d 100644 --- a/src/stories/vis-elements/BarList.stories.tsx +++ b/src/stories/vis-elements/BarList.stories.tsx @@ -90,3 +90,11 @@ export const IndividualColors: Story = { valueFormatter: (value) => `${value} USD`, }, }; + +export const WithOnValueChange: Story = { + render: (args) => , + args: { + data: getData(), + onValueChange: (data) => alert(JSON.stringify(data)), + }, +}; diff --git a/src/tests/input-elements/MultiSelect.test.tsx b/src/tests/input-elements/MultiSelect.test.tsx index 27855fc2d..66884aaa2 100644 --- a/src/tests/input-elements/MultiSelect.test.tsx +++ b/src/tests/input-elements/MultiSelect.test.tsx @@ -43,7 +43,7 @@ describe("MultiSelect", () => { const placeholder = "Select options..."; const items = ["item1", "item2"]; render( - + item0 {items.map((item) => { return ; @@ -51,10 +51,10 @@ describe("MultiSelect", () => { , ); - fireEvent.click(screen.getByText(placeholder)); + fireEvent.click(screen.getByTestId("first-select")); - expect(screen.queryByText("item0")).toBeTruthy(); - expect(screen.queryByText("item1")).toBeTruthy(); - expect(screen.queryByText("item2")).toBeTruthy(); + expect(screen.queryAllByText("item0")).toBeTruthy(); + expect(screen.queryAllByText("item1")).toBeTruthy(); + expect(screen.queryAllByText("item2")).toBeTruthy(); }); }); diff --git a/src/tests/input-elements/SearchSelect.test.tsx b/src/tests/input-elements/SearchSelect.test.tsx index d1d561b2c..1144da78c 100644 --- a/src/tests/input-elements/SearchSelect.test.tsx +++ b/src/tests/input-elements/SearchSelect.test.tsx @@ -71,7 +71,7 @@ describe("SearchSelect", () => { const placeholder = "Select options..."; const items = ["item1", "item2"]; render( - + item0 {items.map((item) => { return ; @@ -79,10 +79,10 @@ describe("SearchSelect", () => { , ); - fireEvent.click(screen.getByPlaceholderText(placeholder)); + fireEvent.click(screen.getByTestId("first-select")); - expect(screen.queryByText("item0")).toBeTruthy(); - expect(screen.queryByText("item1")).toBeTruthy(); - expect(screen.queryByText("item2")).toBeTruthy(); + expect(screen.queryAllByText("item0")).toBeTruthy(); + expect(screen.queryAllByText("item1")).toBeTruthy(); + expect(screen.queryAllByText("item2")).toBeTruthy(); }); }); diff --git a/src/tests/input-elements/Select.test.tsx b/src/tests/input-elements/Select.test.tsx index 9d290833a..9b0edaabd 100644 --- a/src/tests/input-elements/Select.test.tsx +++ b/src/tests/input-elements/Select.test.tsx @@ -73,7 +73,7 @@ describe("Select", () => { const placeholder = "Select options..."; const items = ["item1", "item2"]; render( - item0 {items.map((item) => { return ; @@ -81,10 +81,10 @@ describe("Select", () => { , ); - fireEvent.click(screen.getByText(placeholder)); + fireEvent.click(screen.getByTestId("first-select")); - expect(screen.queryByText("item0")).toBeTruthy(); - expect(screen.queryByText("item1")).toBeTruthy(); - expect(screen.queryByText("item2")).toBeTruthy(); + expect(screen.queryAllByText("item0")).toBeTruthy(); + expect(screen.queryAllByText("item1")).toBeTruthy(); + expect(screen.queryAllByText("item2")).toBeTruthy(); }); }); diff --git a/tailwind.config.js b/tailwind.config.js index 9a25c63c4..188c0e158 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -56,7 +56,7 @@ module.exports = { emphasis: colors.gray[300], }, border: { - DEFAULT: colors.gray[700], + DEFAULT: colors.gray[800], }, ring: { DEFAULT: colors.gray[800], From c8f665e14ff4d4b4de93512b3e7908d163645343 Mon Sep 17 00:00:00 2001 From: motinados Date: Wed, 27 Mar 2024 06:02:52 +0900 Subject: [PATCH 16/17] Fix interactive component click in SearchSelect and Select tests --- src/tests/input-elements/SearchSelect.test.tsx | 14 ++++++++------ src/tests/input-elements/Select.test.tsx | 14 ++++++++------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/tests/input-elements/SearchSelect.test.tsx b/src/tests/input-elements/SearchSelect.test.tsx index 1144da78c..0c682bd34 100644 --- a/src/tests/input-elements/SearchSelect.test.tsx +++ b/src/tests/input-elements/SearchSelect.test.tsx @@ -39,7 +39,7 @@ describe("SearchSelect", () => { expect(screen.queryByTestId("icon")).toBeTruthy(); }); - test("renders the SearchSelectItem component with Icon as ElementType", () => { + test("renders the SearchSelectItem component with Icon as ElementType", async () => { const Icon = () => Icon; render( @@ -49,11 +49,12 @@ describe("SearchSelect", () => { , ); expect(screen.queryByTestId("icon")).not.toBeTruthy(); - fireEvent.click(screen.getByRole("combobox")); - expect(screen.queryByTestId("icon")).toBeTruthy(); + // Need to click the interactive component + fireEvent.click(screen.getAllByRole("combobox")[1]); + expect(await screen.findByTestId("icon")).toBeTruthy(); }); - test("renders the SearchSelectItem component with Icon as ReatElement", () => { + test("renders the SearchSelectItem component with Icon as ReatElement", async () => { const Icon = () => Icon; render( @@ -63,8 +64,9 @@ describe("SearchSelect", () => { , ); expect(screen.queryByTestId("icon")).not.toBeTruthy(); - fireEvent.click(screen.getByRole("combobox")); - expect(screen.queryByTestId("icon")).toBeTruthy(); + // Need to click the interactive component + fireEvent.click(screen.getAllByRole("combobox")[1]); + expect(await screen.findByTestId("icon")).toBeTruthy(); }); test("renders the SearchSelect component with static and dynamic children", () => { diff --git a/src/tests/input-elements/Select.test.tsx b/src/tests/input-elements/Select.test.tsx index 9b0edaabd..8bd1b9cd8 100644 --- a/src/tests/input-elements/Select.test.tsx +++ b/src/tests/input-elements/Select.test.tsx @@ -39,7 +39,7 @@ describe("Select", () => { expect(screen.queryByTestId("icon")).toBeTruthy(); }); - test("renders the SelectItem component with Icon as ElementType", () => { + test("renders the SelectItem component with Icon as ElementType", async () => { const Icon = () => Icon; const placeholder = "Select..."; render( @@ -50,11 +50,12 @@ describe("Select", () => { , ); expect(screen.queryByTestId("icon")).not.toBeTruthy(); - fireEvent.click(screen.getByText(placeholder)); - expect(screen.queryByTestId("icon")).toBeTruthy(); + // Need to click the interactive component + fireEvent.click(screen.getAllByText(placeholder)[1]); + expect(await screen.findByTestId("icon")).toBeTruthy(); }); - test("renders the SelectItem component with Icon as ReactElement", () => { + test("renders the SelectItem component with Icon as ReactElement", async () => { const Icon = () => Icon; const placeholder = "Select..."; render( @@ -65,8 +66,9 @@ describe("Select", () => { , ); expect(screen.queryByTestId("icon")).not.toBeTruthy(); - fireEvent.click(screen.getByText(placeholder)); - expect(screen.queryByTestId("icon")).toBeTruthy(); + // Need to click the interactive component + fireEvent.click(screen.getAllByText(placeholder)[1]); + expect(await screen.findByTestId("icon")).toBeTruthy(); }); test("renders the Select component with static and dynamic children", () => { From 9f710fab6526b3dfed845e7093d7221f3d1ea763 Mon Sep 17 00:00:00 2001 From: motinados Date: Wed, 27 Mar 2024 06:17:29 +0900 Subject: [PATCH 17/17] Add missing closing bracket --- src/components/input-elements/SearchSelect/SearchSelect.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/input-elements/SearchSelect/SearchSelect.tsx b/src/components/input-elements/SearchSelect/SearchSelect.tsx index 45b7a4f9f..0a73a4651 100644 --- a/src/components/input-elements/SearchSelect/SearchSelect.tsx +++ b/src/components/input-elements/SearchSelect/SearchSelect.tsx @@ -175,6 +175,8 @@ const SearchSelect = React.forwardRef((prop > {Icon} + )} +