Skip to content

Commit

Permalink
fix(select): hideEmptyContent API (#4219)
Browse files Browse the repository at this point in the history
* fix(select): hideEmptyContent API

* test(select): hideEmptyContent tests

* docs(select): hideEmptyContent API

* chore(select): hideEmptyContent storybook

* chore(changeset): add hideEmptyContent API to select

* refactor(select): hideEmptyContent nitpick

* test(select): hideEmptyContent UI assertions

* fix(select): hideEmptyContent default false

* docs(select): hideEmptyContent default false
  • Loading branch information
Peterl561 authored Dec 4, 2024
1 parent 62b77e6 commit 1855ba4
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 12 deletions.
6 changes: 6 additions & 0 deletions .changeset/rotten-jobs-pull.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@nextui-org/use-aria-multiselect": patch
"@nextui-org/select": patch
---

add hideEmptyContent API to select
10 changes: 8 additions & 2 deletions apps/docs/content/docs/components/select.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -485,7 +485,7 @@ the popover and listbox components.
},
{
attribute: "endContent",
type: "ReactNode",
type: "ReactNode",
description: "Element to be rendered in the right side of the select.",
default: "-"
},
Expand Down Expand Up @@ -515,7 +515,7 @@ the popover and listbox components.
},
{
attribute: "itemHeight",
type: "number",
type: "number",
description: "The fixed height of each item in pixels. Required when using virtualization.",
default: "32"
},
Expand Down Expand Up @@ -603,6 +603,12 @@ the popover and listbox components.
description: "Whether the select should disable the rotation of the selector icon.",
default: "false"
},
{
attribute: "hideEmptyContent",
type: "boolean",
description: "Whether the listbox will be prevented from opening when there are no items.",
default: "false"
},
{
attribute: "popoverProps",
type: "PopoverProps",
Expand Down
51 changes: 51 additions & 0 deletions packages/components/select/__tests__/select.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -828,6 +828,57 @@ describe("Select", () => {
"Invalid value",
);
});

it("should not open dropdown when hideEmptyContent is true", async () => {
const wrapper = render(
<Select
hideEmptyContent
aria-label="Favorite Animal"
data-testid="hide-empty-content-true-test"
label="Favorite Animal"
>
{[]}
</Select>,
);

const select = wrapper.getByTestId("hide-empty-content-true-test");

// open the select dropdown
await user.click(select);

// assert that the select is not open
expect(select).not.toHaveAttribute("aria-expanded", "true");
// assert that the listbox is not rendered
expect(wrapper.queryByRole("listbox")).not.toBeInTheDocument();
});

it("should open dropdown when hideEmptyContent is false", async () => {
const wrapper = render(
<Select
aria-label="Favorite Animal"
data-testid="hide-empty-content-false-test"
hideEmptyContent={false}
label="Favorite Animal"
>
{[]}
</Select>,
);

const select = wrapper.getByTestId("hide-empty-content-false-test");

// open the select dropdown
await user.click(select);

// assert that the select is open
expect(select).toHaveAttribute("aria-expanded", "true");

const listbox = wrapper.getByRole("listbox");

// assert that the listbox is rendered
expect(listbox).toBeInTheDocument();
// assert that the listbox items are not rendered
expect(wrapper.queryByRole("option")).not.toBeInTheDocument();
});
});

describe("Select virtualization tests", () => {
Expand Down
7 changes: 7 additions & 0 deletions packages/components/select/src/use-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ export type UseSelectProps<T> = Omit<
* @default undefined
*/
isVirtualized?: boolean;
/**
* Whether the listbox will be prevented from opening when there are no items.
* @default false
*/
hideEmptyContent?: boolean;
};

export function useSelect<T extends object>(originalProps: UseSelectProps<T>) {
Expand Down Expand Up @@ -209,6 +214,7 @@ export function useSelect<T extends object>(originalProps: UseSelectProps<T>) {
onClose,
className,
classNames,
hideEmptyContent = false,
...otherProps
} = props;

Expand Down Expand Up @@ -263,6 +269,7 @@ export function useSelect<T extends object>(originalProps: UseSelectProps<T>) {
isDisabled: originalProps.isDisabled,
isInvalid: originalProps.isInvalid,
defaultOpen,
hideEmptyContent,
onOpenChange: (open) => {
onOpenChange?.(open);
if (!open) {
Expand Down
33 changes: 33 additions & 0 deletions packages/components/select/stories/select.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,31 @@ const StartContentTemplate = ({color, variant, ...args}: SelectProps) => (
</Select>
);

const EmptyTemplate = ({color, variant, ...args}: SelectProps) => (
<div className="w-full justify-center flex gap-2">
<Select
hideEmptyContent
className="max-w-xs"
color={color}
label="Hide empty content"
variant={variant}
{...args}
>
{[]}
</Select>
<Select
className="max-w-xs"
color={color}
hideEmptyContent={false}
label="Show empty content"
variant={variant}
{...args}
>
{[]}
</Select>
</div>
);

const CustomItemsTemplate = ({color, variant, ...args}: SelectProps<User>) => (
<div className="w-full justify-center flex gap-2">
<Select
Expand Down Expand Up @@ -864,6 +889,14 @@ export const StartContent = {
},
};

export const EmptyContent = {
render: EmptyTemplate,

args: {
...defaultProps,
},
};

export const WithDescription = {
render: MirrorTemplate,

Expand Down
25 changes: 15 additions & 10 deletions packages/hooks/use-aria-multiselect/src/use-multiselect-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ export interface MultiSelectProps<T>
* @default true
*/
shouldFlip?: boolean;
/**
* Whether the menu should be hidden when there are no items.
*/
hideEmptyContent?: boolean;
}

export interface MultiSelectState<T>
Expand Down Expand Up @@ -82,6 +86,8 @@ export function useMultiSelectState<T extends {}>(props: MultiSelectProps<T>): M
value: listState.selectedKeys,
});

const shouldHideContent = listState.collection.size === 0 && props.hideEmptyContent;

return {
...validationState,
...listState,
Expand All @@ -91,18 +97,17 @@ export function useMultiSelectState<T extends {}>(props: MultiSelectProps<T>): M
triggerState.close();
},
open(focusStrategy: FocusStrategy | null = null) {
// Don't open if the collection is empty.
if (listState.collection.size !== 0) {
setFocusStrategy(focusStrategy);
triggerState.open();
}
if (shouldHideContent) return;

setFocusStrategy(focusStrategy);
triggerState.open();
},
toggle(focusStrategy: FocusStrategy | null = null) {
if (listState.collection.size !== 0) {
setFocusStrategy(focusStrategy);
triggerState.toggle();
validationState.commitValidation();
}
if (shouldHideContent) return;

setFocusStrategy(focusStrategy);
triggerState.toggle();
validationState.commitValidation();
},
isFocused,
setFocused,
Expand Down

0 comments on commit 1855ba4

Please sign in to comment.