[Workflow] Add search in variable dropdown (#8479)

- fix Icon variable Plus
- add search input 
- fix dropdown height

## Before

![image](https://github.com/user-attachments/assets/49f73efd-21cc-4ecd-a494-f51edc34dc57)


## After

![image](https://github.com/user-attachments/assets/2af2c7ee-72fd-4dae-a1ef-19e75e1ac026)
This commit is contained in:
martmull
2024-11-14 11:40:06 +01:00
committed by GitHub
parent d72068eb99
commit 9ac949dec8
5 changed files with 46 additions and 17 deletions

View File

@ -15,9 +15,10 @@ const StyledHeader = styled.li`
border-top-right-radius: ${({ theme }) => theme.border.radius.sm}; border-top-right-radius: ${({ theme }) => theme.border.radius.sm};
border-bottom: 1px solid ${({ theme }) => theme.border.color.light}; border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
padding: ${({ theme }) => theme.spacing(1)}; padding: ${({ theme }) => theme.spacing(1)} 0;
user-select: none; user-select: none;
width: inherit;
&:hover { &:hover {
background: ${({ theme, onClick }) => background: ${({ theme, onClick }) =>

View File

@ -3,12 +3,17 @@ import styled from '@emotion/styled';
type StyledDropdownButtonProps = { type StyledDropdownButtonProps = {
isUnfolded: boolean; isUnfolded: boolean;
isActive?: boolean; isActive?: boolean;
transparentBackground?: boolean;
}; };
export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>` export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProps>`
align-items: center; align-items: center;
background: ${({ theme, isUnfolded }) => background: ${({ theme, isUnfolded, transparentBackground }) =>
isUnfolded ? theme.background.transparent.light : theme.background.primary}; transparentBackground
? 'none'
: isUnfolded
? theme.background.transparent.light
: theme.background.primary};
border-radius: ${({ theme }) => theme.border.radius.sm}; border-radius: ${({ theme }) => theme.border.radius.sm};
color: ${({ isActive, theme }) => color: ${({ isActive, theme }) =>
isActive ? theme.color.blue : theme.font.color.secondary}; isActive ? theme.color.blue : theme.font.color.secondary};
@ -22,9 +27,11 @@ export const StyledDropdownButtonContainer = styled.div<StyledDropdownButtonProp
user-select: none; user-select: none;
&:hover { &:hover {
background: ${({ theme, isUnfolded }) => background: ${({ theme, isUnfolded, transparentBackground }) =>
isUnfolded transparentBackground
? theme.background.transparent.medium ? 'transparent'
: theme.background.transparent.light}; : isUnfolded
? theme.background.transparent.medium
: theme.background.transparent.light};
} }
`; `;

View File

@ -11,15 +11,17 @@ import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Editor } from '@tiptap/react'; import { Editor } from '@tiptap/react';
import { useState } from 'react'; import { useState } from 'react';
import { IconVariable } from 'twenty-ui'; import { IconVariablePlus } from 'twenty-ui';
const StyledDropdownVariableButtonContainer = styled( const StyledDropdownVariableButtonContainer = styled(
StyledDropdownButtonContainer, StyledDropdownButtonContainer,
)` )<{ transparentBackground?: boolean }>`
background-color: ${({ theme }) => theme.background.transparent.lighter}; background-color: ${({ theme, transparentBackground }) =>
transparentBackground
? 'transparent'
: theme.background.transparent.lighter};
color: ${({ theme }) => theme.font.color.tertiary}; color: ${({ theme }) => theme.font.color.tertiary};
padding: ${({ theme }) => theme.spacing(0)}; padding: ${({ theme }) => theme.spacing(2)};
margin: ${({ theme }) => theme.spacing(2)};
`; `;
const SearchVariablesDropdown = ({ const SearchVariablesDropdown = ({
@ -65,12 +67,15 @@ const SearchVariablesDropdown = ({
scope: dropdownId, scope: dropdownId,
}} }}
clickableComponent={ clickableComponent={
<StyledDropdownVariableButtonContainer isUnfolded={isDropdownOpen}> <StyledDropdownVariableButtonContainer
<IconVariable size={theme.icon.size.sm} /> isUnfolded={isDropdownOpen}
transparentBackground
>
<IconVariablePlus size={theme.icon.size.sm} />
</StyledDropdownVariableButtonContainer> </StyledDropdownVariableButtonContainer>
} }
dropdownComponents={ dropdownComponents={
<DropdownMenuItemsContainer> <DropdownMenuItemsContainer hasMaxHeight>
{selectedStep ? ( {selectedStep ? (
<SearchVariablesDropdownStepSubItem <SearchVariablesDropdownStepSubItem
step={selectedStep} step={selectedStep}

View File

@ -3,6 +3,7 @@ import { StepOutputSchema } from '@/workflow/search-variables/types/StepOutputSc
import { isObject } from '@sniptt/guards'; import { isObject } from '@sniptt/guards';
import { useState } from 'react'; import { useState } from 'react';
import { IconChevronLeft, MenuItemSelect } from 'twenty-ui'; import { IconChevronLeft, MenuItemSelect } from 'twenty-ui';
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
type SearchVariablesDropdownStepSubItemProps = { type SearchVariablesDropdownStepSubItemProps = {
step: StepOutputSchema; step: StepOutputSchema;
@ -16,6 +17,7 @@ const SearchVariablesDropdownStepSubItem = ({
onBack, onBack,
}: SearchVariablesDropdownStepSubItemProps) => { }: SearchVariablesDropdownStepSubItemProps) => {
const [currentPath, setCurrentPath] = useState<string[]>([]); const [currentPath, setCurrentPath] = useState<string[]>([]);
const [searchInputValue, setSearchInputValue] = useState('');
const getSelectedObject = () => { const getSelectedObject = () => {
let selected = step.outputSchema; let selected = step.outputSchema;
@ -30,6 +32,7 @@ const SearchVariablesDropdownStepSubItem = ({
if (isObject(selectedObject[key])) { if (isObject(selectedObject[key])) {
setCurrentPath([...currentPath, key]); setCurrentPath([...currentPath, key]);
setSearchInputValue('');
} else { } else {
onSelect(`{{${step.id}.${[...currentPath, key].join('.')}}}`); onSelect(`{{${step.id}.${[...currentPath, key].join('.')}}}`);
} }
@ -45,12 +48,25 @@ const SearchVariablesDropdownStepSubItem = ({
const headerLabel = currentPath.length === 0 ? step.name : currentPath.at(-1); const headerLabel = currentPath.length === 0 ? step.name : currentPath.at(-1);
const options = Object.entries(getSelectedObject());
const filteredOptions = searchInputValue
? options.filter(([key]) =>
key.toLowerCase().includes(searchInputValue.toLowerCase()),
)
: options;
return ( return (
<> <>
<DropdownMenuHeader StartIcon={IconChevronLeft} onClick={goBack}> <DropdownMenuHeader StartIcon={IconChevronLeft} onClick={goBack}>
{headerLabel} {headerLabel}
</DropdownMenuHeader> </DropdownMenuHeader>
{Object.entries(getSelectedObject()).map(([key, value]) => ( <DropdownMenuSearchInput
autoFocus
value={searchInputValue}
onChange={(event) => setSearchInputValue(event.target.value)}
/>
{filteredOptions.map(([key, value]) => (
<MenuItemSelect <MenuItemSelect
key={key} key={key}
selected={false} selected={false}

View File

@ -231,7 +231,7 @@ export {
IconUser, IconUser,
IconUserCircle, IconUserCircle,
IconUsers, IconUsers,
IconVariable, IconVariablePlus,
IconVideo, IconVideo,
IconWand, IconWand,
IconWorld, IconWorld,