diff --git a/jsapp/js/account/organization/MemberRoleSelector.tsx b/jsapp/js/account/organization/MemberRoleSelector.tsx index f4110f9c90..b82194be9a 100644 --- a/jsapp/js/account/organization/MemberRoleSelector.tsx +++ b/jsapp/js/account/organization/MemberRoleSelector.tsx @@ -1,7 +1,7 @@ -import KoboSelect from 'jsapp/js/components/common/koboSelect'; +import {Select} from 'jsapp/js/components/common/Select'; import {usePatchOrganizationMember} from './membersQuery'; import {OrganizationUserRole} from './organizationQuery'; -import {type KoboDropdownPlacement} from 'jsapp/js/components/common/koboDropdown'; +import {LoadingOverlay} from '@mantine/core'; interface MemberRoleSelectorProps { username: string; @@ -9,43 +9,38 @@ interface MemberRoleSelectorProps { role: OrganizationUserRole; /** The role of the currently logged in user. */ currentUserRole: OrganizationUserRole; - placement: KoboDropdownPlacement; } -export default function MemberRoleSelector( - {username, role, currentUserRole, placement}: MemberRoleSelectorProps -) { +export default function MemberRoleSelector({ + username, + role, +}: MemberRoleSelectorProps) { const patchMember = usePatchOrganizationMember(username); - const canModifyRole = ( - currentUserRole === 'owner' || - currentUserRole === 'admin' - ); + const handleRoleChange = (newRole: string | null) => { + if (newRole) { + patchMember.mutateAsync({role: newRole as OrganizationUserRole}); + } + }; return ( - { - if (newRole) { - patchMember.mutateAsync({role: newRole as OrganizationUserRole}); - } - }} - isPending={patchMember.isPending} - isDisabled={!canModifyRole} - /> + <> + + + ))} + +); + +/** + * Clear button is added to the right side of the input when an option is selected + */ +export const Clearable: Story = { + args: { + clearable: true, + value: data[3].value, + }, +}; + +/** + * Items are filtered by the input when typing the value. Custom icon can be added to the `leftSection` property + */ +export const Searchable: Story = { + args: { + searchable: true, + }, +}; + +/** + * Select with large data set and scrollable dropdown + */ +export const Scrollable: Story = { + args: { + data: largeData, + }, +}; + +export default meta; diff --git a/jsapp/js/components/common/Select.tsx b/jsapp/js/components/common/Select.tsx new file mode 100644 index 0000000000..e1406e0071 --- /dev/null +++ b/jsapp/js/components/common/Select.tsx @@ -0,0 +1,62 @@ +import type {SelectProps, ComboboxItem} from '@mantine/core'; +import {CloseButton, Group, Select as MantineSelect} from '@mantine/core'; +import type {IconSize} from './icon'; +import Icon from './icon'; +import {useState} from 'react'; + +declare module '@mantine/core/lib/components/Select' { + /** @deprecated use Kobo implementation instead. (deprecating a new interface because can't augment variables) */ + export interface Select {} +} + +const iconSizeMap: Record = { + xs: 'xxs', + sm: 'xs', + md: 's', + lg: 'm', + xl: 'l', +}; + +export const Select = (props: SelectProps) => { + const [value, setValue] = useState(props.value || null); + const [isOpened, setIsOpened] = useState( + props.defaultDropdownOpened || false + ); + + const onChange = (newValue: string | null, option: ComboboxItem) => { + setValue(newValue); + props.onChange?.(newValue, option); + }; + + const clear = () => { + setValue(null); + props.onClear?.(); + }; + + const iconSize = + typeof props.size === 'string' ? iconSizeMap[props.size] : 's'; + + const clearButton = + props.clearable && value && !props.disabled && !props.readOnly ? ( + } + /> + ) : null; + + return ( + setIsOpened(true)} + onDropdownClose={() => setIsOpened(false)} + rightSection={ + + {clearButton} + + + } + /> + ); +}; diff --git a/jsapp/js/components/common/koboSelect.stories.tsx b/jsapp/js/components/common/koboSelect.stories.tsx index a097c8f044..7ba9bce701 100644 --- a/jsapp/js/components/common/koboSelect.stories.tsx +++ b/jsapp/js/components/common/koboSelect.stories.tsx @@ -3,7 +3,7 @@ import type {ComponentStory, ComponentMeta} from '@storybook/react'; import KoboSelect from 'js/components/common/koboSelect'; export default { - title: 'common/KoboSelect', + title: 'commonDeprecated/KoboSelect', component: KoboSelect, argTypes: { selectedOption: { diff --git a/jsapp/js/theme/kobo/Select.module.css b/jsapp/js/theme/kobo/Select.module.css new file mode 100644 index 0000000000..d9603363a4 --- /dev/null +++ b/jsapp/js/theme/kobo/Select.module.css @@ -0,0 +1,43 @@ +.input { + border-color: var(--mantine-color-gray-6); + + &::placeholder { + color: var(--mantine-color-gray-4); + opacity: 1; + } +} + +.dropdown { + border-color: var(--mantine-color-gray-6); +} + +.option { + border-radius: 0; + color: var(--mantine-color-gray-1); +} + +.option:hover { + background-color: var(--mantine-color-blue-9); +} + +.option[data-combobox-selected], +.option[data-combobox-active] { + background-color: var(--mantine-color-blue-9); +} + +.section[data-position='right'] { + width: auto; +} + +.section i { + display: flex; + color: var(--mantine-color-gray-4); +} + +.section button { + background-color: transparent; +} + +.section button:hover { + background-color: transparent; +} diff --git a/jsapp/js/theme/kobo/Select.tsx b/jsapp/js/theme/kobo/Select.tsx new file mode 100644 index 0000000000..22263da54a --- /dev/null +++ b/jsapp/js/theme/kobo/Select.tsx @@ -0,0 +1,15 @@ +import {Select} from '@mantine/core'; + +import classes from './Select.module.css'; + +export const SelectThemeKobo = Select.extend({ + classNames: classes, + defaultProps: { + withCheckIcon: false, + allowDeselect: false, + comboboxProps: { + offset: 0, + dropdownPadding: 0, + }, + }, +}); diff --git a/jsapp/js/theme/kobo/index.ts b/jsapp/js/theme/kobo/index.ts index 6a485c558d..30167bd159 100644 --- a/jsapp/js/theme/kobo/index.ts +++ b/jsapp/js/theme/kobo/index.ts @@ -5,6 +5,7 @@ import {TableThemeKobo} from './Table'; import {TooltipThemeKobo} from './Tooltip'; import {MenuThemeKobo} from './Menu'; import {AlertThemeKobo} from './Alert'; +import {SelectThemeKobo} from './Select'; export const themeKobo = createTheme({ primaryColor: 'blue', @@ -68,7 +69,7 @@ export const themeKobo = createTheme({ 'hsl(30, 100%, 90%)', // #ffe8cc ($kobo-light-amber) '#000', '#000', - ] + ], }, // Typography @@ -107,5 +108,6 @@ export const themeKobo = createTheme({ Menu: MenuThemeKobo, Tooltip: TooltipThemeKobo, Table: TableThemeKobo, + Select: SelectThemeKobo, }, });