From aab30dc91c5b0a752706144c5e28341d86d4db3a Mon Sep 17 00:00:00 2001 From: Riccardo Perra Date: Sat, 25 Nov 2023 20:19:59 +0100 Subject: [PATCH] Add new features to Listbox component This commit introduces new functionalities to the Listbox component. Properties like 'size', 'theme', and 'bordered' have been added providing more customization options. Additionally, a 'toPx' function has been implemented to simplify CSS value conversions. This function is used for more precise size values for the Listbox items. Storybook stories have also been updated to accommodate these changes. --- .../kit/src/components/Listbox/Listbox.css.ts | 50 +++++++++++++------ .../kit/src/components/Listbox/Listbox.tsx | 17 ++++++- .../components/Listbox/VirtualizedListbox.tsx | 4 +- packages/kit/src/components/Listbox/sizes.ts | 7 +++ packages/kit/src/utils/css.ts | 4 ++ .../storybook/src/stories/Listbox.stories.tsx | 22 ++++++++ 6 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 packages/kit/src/components/Listbox/sizes.ts diff --git a/packages/kit/src/components/Listbox/Listbox.css.ts b/packages/kit/src/components/Listbox/Listbox.css.ts index 6136a9c..d975f97 100644 --- a/packages/kit/src/components/Listbox/Listbox.css.ts +++ b/packages/kit/src/components/Listbox/Listbox.css.ts @@ -1,7 +1,9 @@ import { createTheme, style } from "@vanilla-extract/css"; import { tokens } from "../../foundation/contract.css"; -import { themeTokens } from "../../foundation"; +import { themeTokens, themeVars } from "../../foundation"; import { componentStateStyles } from "@kobalte/vanilla-extract"; +import { LISTBOX_ITEM_SIZE } from "./sizes"; +import { toPx } from "../../utils/css"; export const [listTheme, listThemeVars] = createTheme({ contentBackground: tokens.dropdownBackground, @@ -20,14 +22,6 @@ export const [listTheme, listThemeVars] = createTheme({ indicatorSize: "20px", }); -const sizesCss = { - xs: "30px", - sm: "36px", - md: "40px", - lg: "48px", - xl: "56px", -}; - const ButtonSizes = { xs: "xs", sm: "sm", @@ -36,7 +30,32 @@ const ButtonSizes = { xl: "xl", } as const; -export const list = style([listTheme]); +export const list = style([ + listTheme, + { + borderRadius: themeTokens.radii.sm, + selectors: { + "&[data-bordered]": { + padding: themeTokens.spacing["2"], + border: `1px solid ${themeVars.separator}`, + }, + "&[data-theme=primary]": { + vars: { + [listThemeVars.itemTextColor]: tokens.dropdownItemTextColor, + [listThemeVars.itemHoverBackground]: tokens.brandAccentHover, + [listThemeVars.itemHoverTextColor]: tokens.dropdownItemHoverTextColor, + }, + }, + "&[data-theme=neutral]": { + vars: { + [listThemeVars.itemTextColor]: tokens.dropdownItemTextColor, + [listThemeVars.itemHoverBackground]: tokens.dropdownItemHoverBackground, + [listThemeVars.itemHoverTextColor]: tokens.dropdownItemHoverTextColor, + }, + }, + }, + }, +]); /** * TODO: same as select! @@ -68,17 +87,18 @@ export const item = style([ { selectors: { [`&[data-size=${ButtonSizes.xs}]`]: { - height: "30px", + height: toPx(LISTBOX_ITEM_SIZE.xs), fontSize: themeTokens.fontSize.sm, - minHeight: "1.25rem", + borderRadius: themeTokens.radii.xs, + minHeight: 0, }, [`&[data-size=${ButtonSizes.sm}]`]: { - height: "36px", + height: toPx(LISTBOX_ITEM_SIZE.sm), fontSize: themeTokens.fontSize.md, - minHeight: "1.25rem", + minHeight: 0, }, [`&[data-size=${ButtonSizes.md}]`]: { - height: "42px", + height: toPx(LISTBOX_ITEM_SIZE.md), fontSize: themeTokens.fontSize.md, }, }, diff --git a/packages/kit/src/components/Listbox/Listbox.tsx b/packages/kit/src/components/Listbox/Listbox.tsx index b548227..bc77b34 100644 --- a/packages/kit/src/components/Listbox/Listbox.tsx +++ b/packages/kit/src/components/Listbox/Listbox.tsx @@ -9,15 +9,28 @@ export type ListboxProps = Omit< "renderItem" > & { size?: "xs" | "sm" | "md"; + theme?: "primary" | "neutral"; + itemLabel?: (item: Option) => JSXElement; + bordered?: boolean; }; export function Listbox(props: ListboxProps) { - const [local, others] = splitProps(props, ["class"]); + const [local, others] = splitProps(props, [ + "class", + "size", + "itemLabel", + "bordered", + "theme", + ]); return ( } + renderItem={node => ( + + )} {...others} /> ); diff --git a/packages/kit/src/components/Listbox/VirtualizedListbox.tsx b/packages/kit/src/components/Listbox/VirtualizedListbox.tsx index 45e0ec9..91e8c79 100644 --- a/packages/kit/src/components/Listbox/VirtualizedListbox.tsx +++ b/packages/kit/src/components/Listbox/VirtualizedListbox.tsx @@ -1,6 +1,7 @@ import { createVirtualizer } from "@tanstack/solid-virtual"; import { Listbox, ListboxItem, ListboxProps } from "./Listbox"; import { For } from "solid-js"; +import { LISTBOX_ITEM_SIZE } from "./sizes"; type VirtualizedListboxProps = Omit< ListboxProps, @@ -38,7 +39,8 @@ export function VirtualizedListbox( getScrollElement: () => listboxRef, estimateSize: (index: number) => // TODO: fix that size - props.virtualizerOptions?.estimateSize?.(index) ?? 42, + props.virtualizerOptions?.estimateSize?.(index) ?? + LISTBOX_ITEM_SIZE[props.size ?? "md"], // TODO: why error? // @ts-ignore getItemKey: (index: number) => { diff --git a/packages/kit/src/components/Listbox/sizes.ts b/packages/kit/src/components/Listbox/sizes.ts new file mode 100644 index 0000000..eddae4b --- /dev/null +++ b/packages/kit/src/components/Listbox/sizes.ts @@ -0,0 +1,7 @@ +export const LISTBOX_ITEM_SIZE: Record = { + xs: 30, + sm: 36, + md: 40, +}; + +export type ListboxItemSizeKey = "xs" | "sm" | "md"; diff --git a/packages/kit/src/utils/css.ts b/packages/kit/src/utils/css.ts index 57a95c7..9c36f59 100644 --- a/packages/kit/src/utils/css.ts +++ b/packages/kit/src/utils/css.ts @@ -1,3 +1,7 @@ export function mergeClasses(...classNames: Array) { return classNames.filter(Boolean).join(" "); } + +export function toPx(value: number) { + return `${value}px`; +} diff --git a/packages/storybook/src/stories/Listbox.stories.tsx b/packages/storybook/src/stories/Listbox.stories.tsx index 4d9789f..f8370d3 100644 --- a/packages/storybook/src/stories/Listbox.stories.tsx +++ b/packages/storybook/src/stories/Listbox.stories.tsx @@ -11,6 +11,17 @@ const meta = { control: { type: "radio" }, options: ["multiple", "none", "single"], }, + size: { + control: { type: "radio" }, + options: ["xs", "sm", "md"], + }, + bordered: { + type: "boolean", + }, + theme: { + control: { type: "radio" }, + options: ["primary", "neutral"], + }, }, tags: ["autodocs"], @@ -70,6 +81,17 @@ export const ListboxMedium: Story = { }, }; +export const ListboxOptionWithCustomItemLabel: Story = { + name: "Listbox - Custom Item Label", + args: { + size: "md", + defaultValue: ["Item1"], + options: ["Item1", "Item2", "Item3"], + selectionMode: "multiple", + itemLabel: (item: string) => Custom {item}, + }, +}; + export const ListboxVirtualized: Story = { name: "Listbox Virtualized", render: args => {