Skip to content

Commit 759d0f5

Browse files
committed
[skip ci] repo-sync
1 parent 99798c6 commit 759d0f5

File tree

6 files changed

+114
-29
lines changed

6 files changed

+114
-29
lines changed

src/Select.tsx

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as SelectPrimitive from "@radix-ui/react-select"
33
import * as React from "react"
44

55
import { cn } from "../clsx"
6+
import { SelectEmptyState, SelectEmptyStateProps } from "./Select/SelectEmptyState"
67

78
const Select = SelectPrimitive.Root
89

@@ -56,31 +57,38 @@ SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
5657

5758
const SelectContent = React.forwardRef<
5859
React.ElementRef<typeof SelectPrimitive.Content>,
59-
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
60-
>(({ className, children, position = "popper", ...props }, ref) => (
61-
<SelectPrimitive.Portal>
62-
<SelectPrimitive.Content
63-
ref={ref}
64-
className={cn(
65-
"relative z-[1100] max-h-96 overflow-hidden rounded-md border bg-white text-primary shadow-md data-[side=bottom]:animate-slide-in-top data-[side=left]:animate-slide-in-right data-[side=right]:animate-slide-in-left data-[side=top]:animate-slide-in-bottom data-[state=closed]:animate-out data-[state=open]:animate-in",
66-
position === "popper" &&
67-
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
68-
className
69-
)}
70-
position={position}
71-
sideOffset={-3}
72-
{...props}>
73-
<SelectPrimitive.Viewport
60+
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content> & {
61+
emptyStateProps?: SelectEmptyStateProps
62+
}
63+
>(({ className, children, position = "popper", emptyStateProps, ...props }, ref) => {
64+
const hasChildren = !!(children && (!Array.isArray(children) || children.length))
65+
66+
return (
67+
<SelectPrimitive.Portal>
68+
<SelectPrimitive.Content
69+
ref={ref}
7470
className={cn(
75-
"p-1",
71+
"relative z-[1100] max-h-96 overflow-hidden rounded-md border bg-white text-primary shadow-md data-[side=bottom]:animate-slide-in-top data-[side=left]:animate-slide-in-right data-[side=right]:animate-slide-in-left data-[side=top]:animate-slide-in-bottom data-[state=closed]:animate-out data-[state=open]:animate-in",
7672
position === "popper" &&
77-
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
78-
)}>
79-
{children}
80-
</SelectPrimitive.Viewport>
81-
</SelectPrimitive.Content>
82-
</SelectPrimitive.Portal>
83-
))
73+
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
74+
className
75+
)}
76+
position={position}
77+
sideOffset={-3}
78+
{...props}>
79+
<SelectPrimitive.Viewport
80+
className={cn(
81+
"p-1",
82+
position === "popper" &&
83+
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
84+
)}>
85+
{!hasChildren && <SelectEmptyState {...emptyStateProps} />}
86+
{children}
87+
</SelectPrimitive.Viewport>
88+
</SelectPrimitive.Content>
89+
</SelectPrimitive.Portal>
90+
)
91+
})
8492
SelectContent.displayName = SelectPrimitive.Content.displayName
8593

8694
const SelectLabel = React.forwardRef<

src/Select/CustomMultipleTextSelect.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React, { useEffect, useRef, useState } from "react"
22
import { GroupBase, MultiValue, SetValueAction } from "react-select"
33

4-
import { cx } from "../../clsx"
4+
import { cn, cx } from "../../clsx"
55
import { Checkbox } from "../Checkbox"
66
import { Select } from "./Select"
7+
import { SelectEmptyState } from "./SelectEmptyState"
78
import {
89
MultipleTextSelectOptionExtraArgs,
910
TSelectMenuListProps,
@@ -151,9 +152,12 @@ function MenuList<OptionType extends MultipleSelectOptionType>(
151152
<div
152153
ref={menuRef}
153154
{...props.innerProps}
154-
className="border-gray-300 rounded-md border bg-white shadow-lg focus:outline-none"
155+
className={cn("border-gray-300 rounded-md border bg-white shadow-lg focus:outline-none", {
156+
"cursor-auto": !options.length,
157+
})}
155158
tabIndex={-1}
156159
onKeyDown={handleKeyDown}>
160+
{!options.length && <SelectEmptyState />}
157161
{options.map((option, index) => (
158162
<SelectOption
159163
key={option.value}

src/Select/Select.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import ReactSelect, {
88
ControlProps,
99
GroupBase,
1010
InputProps,
11+
MenuListProps,
1112
MultiValue,
1213
MultiValueProps,
1314
OnChangeValue,
@@ -36,6 +37,7 @@ import {
3637
TSelectProps,
3738
TSelectSingleValueProps,
3839
} from "./index"
40+
import { SelectEmptyState, SelectEmptyStateProps } from "./SelectEmptyState"
3941
import { DownOutlined } from "@loft-enterprise/icons"
4042

4143
/**
@@ -231,15 +233,25 @@ function SelectMenuList<
231233
OptionType extends TSelectOptionType,
232234
IsMulti extends boolean = false,
233235
Group extends GroupBase<OptionType> = GroupBase<OptionType>,
234-
>(props: TSelectMenuListProps<OptionType, IsMulti, Group>): React.ReactElement {
236+
>(
237+
props: TSelectMenuListProps<OptionType, IsMulti, Group> & {
238+
emptyStateProps?: SelectEmptyStateProps
239+
}
240+
): React.ReactElement {
235241
const height = props.options.length < 7 ? props.options.length * 44 : 156
236242

237243
return (
238244
<div
239245
ref={props.innerRef}
240-
style={{ maxHeight: `${height}px` }}
246+
style={{ maxHeight: props.options.length ? `${height}px` : undefined }}
241247
// if options are 1 item, set the height to fit the content
242-
className={`border-gray-300 select-menu-list mt-1 overflow-y-auto rounded-md border bg-white shadow-lg`}>
248+
className={cn(
249+
`border-gray-300 select-menu-list mt-1 overflow-y-auto rounded-md border bg-white shadow-lg`,
250+
{
251+
"cursor-auto": !props.options.length,
252+
}
253+
)}>
254+
{!props.options.length && <SelectEmptyState {...props.emptyStateProps} />}
243255
{props.children}
244256
</div>
245257
)
@@ -368,11 +380,13 @@ const Select = React.memo(
368380
placeholder,
369381
isClearable,
370382
usePortal,
383+
emptyStateProps,
371384
variant = SelectVariant.STANDARD,
372385
...props
373386
}: TSelectProps<OptionType, IsMulti> & {
374387
variant?: SelectVariant
375388
usePortal?: boolean
389+
emptyStateProps?: SelectEmptyStateProps
376390
}): React.ReactElement => {
377391
const [internalValue, setInternalValue] = useState<
378392
TSelectOptionType | MultiValue<OptionType> | null
@@ -478,7 +492,9 @@ const Select = React.memo(
478492
Input: InputComponent as unknown as ComponentType<
479493
InputProps<OptionType, IsMulti, GroupBase<OptionType>>
480494
>,
481-
MenuList: SelectMenuList,
495+
MenuList: React.memo((props: MenuListProps<OptionType, IsMulti>) => (
496+
<SelectMenuList {...props} emptyStateProps={emptyStateProps} />
497+
)),
482498
SingleValue: React.memo((props: SingleValueProps<OptionType, IsMulti>) => (
483499
<SelectSingleValue {...props} variant={variant} hasPrefix={hasPrefix} />
484500
)),
@@ -507,6 +523,7 @@ const Select = React.memo(
507523
isDisabled,
508524
disabledOptionTooltipText,
509525
hasPrefix,
526+
emptyStateProps,
510527
]
511528
) as Partial<SelectComponents<OptionType, IsMulti, GroupBase<OptionType>>> | undefined
512529

src/Select/SelectEmptyState.tsx

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from "react"
2+
3+
import { Button } from "../Button"
4+
import { ReactComponent as Empty } from "../images/empty.svg"
5+
import { PlusOutlined } from "@loft-enterprise/icons"
6+
7+
export type SelectEmptyStateProps = {
8+
onCreateNew?: () => void
9+
label?: string
10+
createNewLabel?: string
11+
}
12+
13+
function SelectEmptyState(props: SelectEmptyStateProps) {
14+
return (
15+
<div className={"flex w-full flex-col items-center justify-center p-8"}>
16+
<Empty className={"mb-2"} />
17+
<div className={"select-none text-sm font-normal text-primary"}>
18+
{props.label ?? "No items found"}
19+
</div>
20+
{props.onCreateNew && (
21+
<Button variant={"outlined"} className={"mt-2"} onClick={props.onCreateNew}>
22+
<div className={"flex flex-row items-center"}>
23+
<PlusOutlined className={"mr-2"} />
24+
<div>{props.createNewLabel ?? "Create new"}</div>
25+
</div>
26+
</Button>
27+
)}
28+
</div>
29+
)
30+
}
31+
32+
SelectEmptyState.displayName = "SelectEmptyState"
33+
34+
export { SelectEmptyState }

src/images/empty.svg

Lines changed: 12 additions & 0 deletions
Loading

src/images/svg.d.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
declare module "*.svg" {
2+
import * as React from "react"
3+
4+
export const ReactComponent: React.FunctionComponent<
5+
React.SVGProps<SVGSVGElement> & { title?: string }
6+
>
7+
8+
const src: string
9+
export default src
10+
}

0 commit comments

Comments
 (0)