-
Notifications
You must be signed in to change notification settings - Fork 3.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
chore: add wds multiselect component #39300
Merged
+1,694
−1,028
Merged
Changes from all commits
Commits
Show all changes
21 commits
Select commit
Hold shift + click to select a range
23ff529
add multiselect
jsartisan 7358341
add multiselect
jsartisan fb85d1a
style changes
jsartisan b6735c4
add multiselect widget
jsartisan 00d3a8d
update comments and types
jsartisan 326a0e2
fix types
jsartisan 87eb7bf
fix types
jsartisan d616dea
fix cylic dependency
jsartisan 530a1a1
update minor comment
jsartisan 4b43438
update minor comment
jsartisan ddeda45
fix circular dependency
jsartisan 7ee36e0
fix cyclic dependency
jsartisan e222ecd
code review fixes
jsartisan 8ee824c
code review fixes
jsartisan 64de1d8
update types
jsartisan 836cae1
fix isDisabled
jsartisan 25ec88d
fix isDisabled
jsartisan 6331ef1
remove the checkbox hack
jsartisan 6059092
fix checkbox click
jsartisan b9a4589
code review fixes
jsartisan 9d486cb
add an constant
jsartisan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -388,4 +388,4 @@ | |
"@types/react": "^17.0.2", | ||
"postcss": "8.4.31" | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
1 change: 1 addition & 0 deletions
1
app/client/packages/design-system/widgets/src/components/FieldError/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from "./FieldError"; | ||
export type { FieldErrorProps } from "./types"; | ||
export { default as fieldErrorStyles } from "./styles.module.css"; | ||
KelvinOm marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
app/client/packages/design-system/widgets/src/components/ListBox/src/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,2 @@ | ||
export { ListBox } from "./ListBox"; | ||
export { default as listStyles } from "./styles.module.css"; | ||
export { default as listBoxStyles } from "./styles.module.css"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
2 changes: 1 addition & 1 deletion
2
app/client/packages/design-system/widgets/src/components/ListBoxItem/src/types.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 12 additions & 17 deletions
29
app/client/packages/design-system/widgets/src/components/Menu/src/Menu.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,35 +1,30 @@ | ||
import { | ||
Popover, | ||
listStyles, | ||
listBoxStyles, | ||
useRootContainer, | ||
POPOVER_LIST_BOX_MAX_HEIGHT, | ||
} from "@appsmith/wds"; | ||
import React, { createContext, useContext } from "react"; | ||
import React from "react"; | ||
import { Menu as HeadlessMenu } from "react-aria-components"; | ||
|
||
import type { MenuProps } from "./types"; | ||
import clsx from "clsx"; | ||
|
||
const MenuNestingContext = createContext(0); | ||
|
||
export const Menu = (props: MenuProps) => { | ||
const { children, className, ...rest } = props; | ||
const root = useRootContainer(); | ||
|
||
const nestingLevel = useContext(MenuNestingContext); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @KelvinOm seems all this is not required anymore. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! Thank you for fixing this! |
||
const isRootMenu = nestingLevel === 0; | ||
|
||
return ( | ||
<MenuNestingContext.Provider value={nestingLevel + 1}> | ||
{/* Only the parent Popover should be placed in the root. Placing child popoves in root would cause the menu to function incorrectly */} | ||
<Popover | ||
UNSTABLE_portalContainer={isRootMenu ? root : undefined} | ||
maxHeight={POPOVER_LIST_BOX_MAX_HEIGHT} | ||
<Popover | ||
UNSTABLE_portalContainer={root} | ||
maxHeight={POPOVER_LIST_BOX_MAX_HEIGHT} | ||
> | ||
<HeadlessMenu | ||
className={clsx(listBoxStyles.listBox, className)} | ||
{...rest} | ||
> | ||
<HeadlessMenu className={clsx(listStyles.listBox, className)} {...rest}> | ||
{children} | ||
</HeadlessMenu> | ||
</Popover> | ||
</MenuNestingContext.Provider> | ||
{children} | ||
</HeadlessMenu> | ||
</Popover> | ||
); | ||
}; |
1 change: 1 addition & 0 deletions
1
app/client/packages/design-system/widgets/src/components/MultiSelect/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./src"; |
185 changes: 185 additions & 0 deletions
185
app/client/packages/design-system/widgets/src/components/MultiSelect/src/MultiSelect.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,185 @@ | ||
import clsx from "clsx"; | ||
import React, { useRef, useState } from "react"; | ||
|
||
import { useField } from "react-aria"; | ||
import { | ||
DialogTrigger, | ||
UNSTABLE_Autocomplete, | ||
useFilter, | ||
ButtonContext, | ||
type Selection, | ||
} from "react-aria-components"; | ||
|
||
import { setInteractionModality } from "@react-aria/interactions"; | ||
|
||
import { Text } from "../../Text"; | ||
import styles from "./styles.module.css"; | ||
import { ListBox } from "../../ListBox"; | ||
import { | ||
Popover, | ||
POPOVER_LIST_BOX_MAX_HEIGHT, | ||
useRootContainer, | ||
} from "../../Popover"; | ||
import { selectStyles } from "../../Select"; | ||
import { TextField } from "../../TextField"; | ||
import { FieldLabel } from "../../FieldLabel"; | ||
import { textInputStyles } from "../../Input"; | ||
import { inputFieldStyles } from "../../Field"; | ||
import type { MultiSelectProps } from "./types"; | ||
import { fieldErrorStyles } from "../../FieldError"; | ||
|
||
import { MultiSelectValue } from "./MultiSelectValue"; | ||
import { Checkbox } from "../../Checkbox"; | ||
import { ListBoxItem } from "../../ListBoxItem"; | ||
|
||
const EmptyState = () => { | ||
return ( | ||
<Text className={styles.emptyState} color="neutral-subtle"> | ||
No options found | ||
</Text> | ||
); | ||
}; | ||
|
||
/** | ||
* Note: React aria components does not provide us any mutliselect componennt or hooks for it. | ||
* We are just replicating the behaviour of mutli select component with all available hooks and components. | ||
* Few things are implemented manually like opening the popover on keydown or keyup when the button is focused | ||
* or focusing the trigger on click of label. | ||
* | ||
* This is a temporary solution until we have a mutli select component from react aria components library. | ||
*/ | ||
export const MultiSelect = <T extends { label: string; value: string }>( | ||
props: MultiSelectProps<T>, | ||
) => { | ||
const { | ||
contextualHelp, | ||
defaultSelectedKeys = new Set(), | ||
disabledKeys, | ||
errorMessage, | ||
excludeFromTabOrder, | ||
isDisabled, | ||
isInvalid, | ||
isLoading, | ||
isRequired, | ||
items, | ||
label, | ||
onSelectionChange: onSelectionChangeProp, | ||
placeholder, | ||
selectedKeys: selectedKeysProp, | ||
size, | ||
} = props; | ||
const root = useRootContainer(); | ||
const [_selectedKeys, _setSelectedKeys] = useState<Selection>(); | ||
const selectedKeys = selectedKeysProp ?? _selectedKeys ?? defaultSelectedKeys; | ||
const setSelectedKeys = onSelectionChangeProp ?? _setSelectedKeys; | ||
const { labelProps } = useField(props); | ||
const { contains } = useFilter({ sensitivity: "base" }); | ||
const triggerRef = useRef<HTMLButtonElement>(null); | ||
// Note we have to use controlled state for the popover as we need a custom logic to open the popover | ||
// for the usecase where we need to open the popover on keydown or keyup when the button is focused. | ||
const [isOpen, setOpen] = useState(false); | ||
|
||
const onKeyDown = (e: React.KeyboardEvent) => { | ||
if (e.key === "ArrowDown" || e.key === "ArrowUp") { | ||
setOpen(true); | ||
} | ||
}; | ||
|
||
const filter = (textValue: string, inputValue: string) => | ||
contains(textValue, inputValue); | ||
|
||
return ( | ||
<ButtonContext.Provider value={{ onKeyDown, ref: triggerRef }}> | ||
<div className={inputFieldStyles.field}> | ||
{Boolean(label) && ( | ||
<FieldLabel | ||
{...labelProps} | ||
contextualHelp={contextualHelp} | ||
isDisabled={isDisabled} | ||
isRequired={isRequired} | ||
// this is required to imitate the behavior where on click of label, the trigger or input is focused. | ||
// In our select component, this is done by the useSelect hook. Since we don't have that for multi select, | ||
// we are doing this manually here | ||
onClick={() => { | ||
if (triggerRef.current) { | ||
triggerRef.current.focus(); | ||
|
||
setInteractionModality("keyboard"); | ||
} | ||
}} | ||
> | ||
{label} | ||
</FieldLabel> | ||
)} | ||
<div | ||
className={clsx( | ||
textInputStyles.inputGroup, | ||
selectStyles.selectInputGroup, | ||
)} | ||
> | ||
<DialogTrigger isOpen={isOpen} onOpenChange={setOpen}> | ||
<MultiSelectValue | ||
excludeFromTabOrder={excludeFromTabOrder} | ||
isDisabled={isDisabled} | ||
isInvalid={isInvalid} | ||
isLoading={isLoading} | ||
items={items} | ||
placeholder={placeholder} | ||
selectedKeys={selectedKeys} | ||
size={size} | ||
triggerRef={triggerRef} | ||
/> | ||
<Popover | ||
UNSTABLE_portalContainer={root} | ||
className={styles.popover} | ||
maxHeight={POPOVER_LIST_BOX_MAX_HEIGHT} | ||
placement="bottom start" | ||
style={ | ||
{ | ||
"--trigger-width": `${triggerRef?.current?.offsetWidth}px`, | ||
} as React.CSSProperties | ||
} | ||
triggerRef={triggerRef} | ||
> | ||
<UNSTABLE_Autocomplete filter={filter}> | ||
<TextField autoFocus className={styles.textField} /> | ||
<ListBox | ||
className={styles.listBox} | ||
disabledKeys={disabledKeys} | ||
items={items} | ||
onSelectionChange={setSelectedKeys} | ||
renderEmptyState={EmptyState} | ||
selectedKeys={selectedKeys} | ||
selectionMode="multiple" | ||
shouldFocusWrap | ||
> | ||
{(item: T) => ( | ||
<ListBoxItem id={item.value} textValue={item.label}> | ||
{({ isSelected }) => ( | ||
<> | ||
<Checkbox | ||
className={styles.listBoxItemCheckbox} | ||
isSelected={isSelected} | ||
/> | ||
{item.label} | ||
</> | ||
)} | ||
</ListBoxItem> | ||
)} | ||
</ListBox> | ||
</UNSTABLE_Autocomplete> | ||
</Popover> | ||
</DialogTrigger> | ||
</div> | ||
{/* We can't use our FieldError component as it only works when used with FieldErrorContext. | ||
We can use it in our Select and other inputs because the implementation is abstracted in the react aria components library. | ||
But since for MultiSelect, we don't have any component from react-aria, we have to manually render the error message here. */} | ||
<div className={fieldErrorStyles.errorText}> | ||
<Text color="negative" size="caption"> | ||
{errorMessage} | ||
</Text> | ||
</div> | ||
</div> | ||
</ButtonContext.Provider> | ||
); | ||
}; |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need 1.6.0 version as it has the AutoComplete component that we need in multi select dropdown for filtering list