Skip to content
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

[Feature] [WRS-1468] Add search field in studio-ui image picker #115

Merged
merged 9 commits into from
Jan 18, 2024
2 changes: 1 addition & 1 deletion src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
}

.sui-canvas {
height: calc(100% - 4rem - 5rem);
height: calc(100% - 5rem);
width: 100%;
}

Expand Down
2 changes: 1 addition & 1 deletion src/App.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import styled from 'styled-components';

export const MainContentContainer = styled.div`
display: flex;
height: 100vh;
height: calc(100vh - 4rem);
width: 100%;
`;

Expand Down
19 changes: 19 additions & 0 deletions src/components/itemBrowser/ItemBrowser.styles.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ export const ResourcesContainer = styled.div`
${mobileMediaQuery} {
grid-template-columns: minmax(7.5rem, 1fr) minmax(7.5rem, 1fr);
gap: 1rem;
max-height: 500px;
overflow: scroll;
height: auto;
}
`;

Expand Down Expand Up @@ -88,3 +91,19 @@ export const BreadCrumbsWrapper = styled.div`
display: flex;
margin-bottom: 1rem;
`;

export const SearchInputWrapper = styled.div<{ hasSearchQuery?: boolean; isMobile?: boolean }>`
width: ${(props) => (props.isMobile ? '100%' : '16.25rem')};
${(props) =>
props.hasSearchQuery &&
`
margin-bottom: 1rem;
`};
`;

export const EmptySearchResultContainer = styled.div`
padding: 0 3.75rem 0 calc(3.75rem - 1.25rem);
color: ${Colors.SECONDARY_FONT};
font-size: 0.875rem;
text-align: center;
`;
110 changes: 93 additions & 17 deletions src/components/itemBrowser/ItemBrowser.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import {
AvailableIcons,
BreadCrumb,
PreviewCard as ChiliPreview,
Colors,
Icon,
Input,
Panel,
PreviewCardVariant,
PreviewType,
Expand All @@ -15,7 +18,13 @@ import { useVariablePanelContext } from '../../contexts/VariablePanelContext';
import { ContentType } from '../../contexts/VariablePanelContext.types';
import { AssetType } from '../../utils/ApiTypes';
import { getDataIdForSUI, getDataTestIdForSUI } from '../../utils/dataIds';
import { BreadCrumbsWrapper, LoadPageContainer, ResourcesContainer } from './ItemBrowser.styles';
import {
BreadCrumbsWrapper,
LoadPageContainer,
ResourcesContainer,
SearchInputWrapper,
EmptySearchResultContainer,
} from './ItemBrowser.styles';
import { ItemCache } from './ItemCache';

const TOP_BAR_HEIGHT_REM = '4rem';
Expand Down Expand Up @@ -68,6 +77,8 @@ function ItemBrowser<
});
const [isLoading, setIsLoading] = useState(false);
const [list, setList] = useState<ItemCache<T>[]>([]);
const [searchKeyWord, setSearchKeyWord] = useState('');
const [searchQuery, setSearchQuery] = useState('');
const moreData = !!nextPageToken?.token;

const {
Expand All @@ -94,7 +105,7 @@ function ItemBrowser<
return { token: null, requested: true };
});
setList(() => []);
}, [navigationStack]);
}, [navigationStack, searchQuery]);

useEffect(() => {
setBreadcrumbStack([]);
Expand All @@ -110,7 +121,6 @@ function ItemBrowser<
// in case the useEffect runs again (dependencies change) while the promise did not resolve, the cleanup function
// makes sure that the result that is not relevant anymore, won't affect the state.
let ignore = false;

if (!nextPageToken.requested) return;
setIsLoading(true);
// declare the async data fetching function
Expand All @@ -123,6 +133,7 @@ function ItemBrowser<
collection: `/${navigationStack?.join('/') ?? ''}`,
pageToken: nextPageToken.token ? nextPageToken.token : '',
pageSize: 15,
filter: [searchQuery],
},
{},
);
Expand Down Expand Up @@ -168,7 +179,7 @@ function ItemBrowser<
ignore = true;
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [nextPageToken, contentType]);
}, [nextPageToken, contentType, searchQuery]);

useEffect(() => {
return () => {
Expand Down Expand Up @@ -207,6 +218,8 @@ function ItemBrowser<
} else {
selectItem(listItem.instance);
setNavigationStack([]);
setSearchQuery('');
setSearchKeyWord('');
}
};

Expand Down Expand Up @@ -251,6 +264,11 @@ function ItemBrowser<
// eslint-disable-next-line no-nested-ternary
const panelTitle = isMobileSize ? null : contentType === ContentType.IMAGE_PANEL ? imagePanelTitle : null;

const handleSearch = (keyword: string) => {
setSearchQuery(keyword);
setNavigationStack([]);
setBreadcrumbStack([]);
};
return (
<Panel
parentOverflow
Expand All @@ -260,20 +278,78 @@ function ItemBrowser<
isModal={false}
padding="0"
>
<BreadCrumbsWrapper>
<BreadCrumb
href={`Home${breacrumbStackString.length ? '\\' : ''}${breacrumbStackString}`}
color={Colors.SECONDARY_FONT}
activeColor={Colors.PRIMARY_FONT}
onClick={(breadCrumb: string) => {
const newNavigationStack = navigationStack?.splice(0, navigationStack.indexOf(breadCrumb) + 1);
const newBreadcrumbStack = breadcrumbStack?.splice(0, breadcrumbStack.indexOf(breadCrumb) + 1);
setNavigationStack(newNavigationStack);
setBreadcrumbStack(newBreadcrumbStack);
}}
/>
</BreadCrumbsWrapper>
{connectorCapabilities[connectorId]?.filtering && (
<SearchInputWrapper hasSearchQuery={!!searchQuery} isMobile={isMobileSize}>
<Input
type="text"
name="search"
placeholder="Search"
value={searchKeyWord}
onChange={(e) => setSearchKeyWord(e.target.value)}
onBlur={() => handleSearch(searchKeyWord)}
width="260px"
leftIcon={{
icon: (
<Icon
editorComponent
dataId={getDataIdForSUI('media-panel-search-icon')}
dataTestId={getDataTestIdForSUI('media-panel-search-icon')}
icon={AvailableIcons.faMagnifyingGlass}
/>
),
label: 'Search icon',
}}
dataId={getDataIdForSUI('media-panel-search-input')}
dataTestId={getDataTestIdForSUI('media-panel-search-input')}
rightIcon={
searchKeyWord
? {
label: 'Clear search icon',
icon: (
<Icon
dataId={getDataIdForSUI('media-panel-clear-search-icon')}
dataTestId={getDataTestIdForSUI('media-panel-clear-search-icon')}
icon={AvailableIcons.faXmark}
/>
),
onClick: () => {
setSearchKeyWord('');
setSearchQuery('');
},
}
: undefined
}
isHighlightOnClick
/>
</SearchInputWrapper>
)}
{!searchQuery && (
<BreadCrumbsWrapper>
<BreadCrumb
href={`Home${breacrumbStackString.length ? '\\' : ''}${breacrumbStackString}`}
color={Colors.SECONDARY_FONT}
activeColor={Colors.PRIMARY_FONT}
onClick={(breadCrumb: string) => {
const newNavigationStack = navigationStack?.splice(
0,
navigationStack.indexOf(breadCrumb) + 1,
);
const newBreadcrumbStack = breadcrumbStack?.splice(
0,
breadcrumbStack.indexOf(breadCrumb) + 1,
);
setNavigationStack(newNavigationStack);
setBreadcrumbStack(newBreadcrumbStack);
}}
/>
</BreadCrumbsWrapper>
)}
<ScrollbarWrapper height={height ?? leftPanelHeight} scrollbarWidth="0">
{elements.length === 0 && !isLoading && searchQuery && (
<EmptySearchResultContainer>
No search results found. Maybe try another keyword?
</EmptySearchResultContainer>
)}
<ResourcesContainer
data-id={getDataIdForSUI('resources-container')}
data-testid={getDataTestIdForSUI('resources-container')}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ export const LeftPanelContainer = styled.div`
background-color: ${Colors.PRIMARY_WHITE};
border-right: 2px solid ${Colors.PRIMARY_DROPDOWN_BACKGROUND};
overflow: scroll;
margin-bottom: 3rem;
padding-left: 0;

&::-webkit-scrollbar {
Expand Down
1 change: 1 addition & 0 deletions src/components/variables/VariablesPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function VariablesPanel(props: VariablesPanelProps) {
onTrayHidden={showVariablesPanel}
styles={css`
height: ${contentType === ContentType.IMAGE_PANEL ? '100%' : 'auto'};
overflow: ${showVariablesList ? 'auto' : 'hidden'};
`}
hideCloseButton={mobileOptionsListOpen}
>
Expand Down
62 changes: 58 additions & 4 deletions src/tests/LeftPanel.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { getDataTestId } from '@chili-publish/grafx-shared-components';
import EditorSDK from '@chili-publish/studio-sdk';
import { render, waitFor } from '@testing-library/react';
import { render, waitFor, screen } from '@testing-library/react';
import { mock } from 'jest-mock-extended';
import { act } from 'react-dom/test-utils';
import LeftPanel from '../components/layout-panels/leftPanel/LeftPanel';
Expand All @@ -10,11 +10,11 @@ import { mockConnectors } from './mocks/mockConnectors';
import { variables } from './mocks/mockVariables';
import { getDataTestIdForSUI } from '../utils/dataIds';

jest.mock('@chili-publish/studio-sdk');
const mockSDK = mock<EditorSDK>();

beforeEach(() => {
jest.mock('@chili-publish/studio-sdk');
const mockSDK = mock<EditorSDK>();
global.URL.createObjectURL = jest.fn();

mockSDK.mediaConnector.query = jest
.fn()
.mockImplementation()
Expand Down Expand Up @@ -207,4 +207,58 @@ describe('Image Panel', () => {
expect(window.SDK.variable.setImageVariableConnector).toBeCalledTimes(1);
expect(window.SDK.variable.setValue).toBeCalledTimes(1);
});
test('Do not render search input when filtering is not supported', async () => {
mockSDK.mediaConnector.getCapabilities = jest
.fn()
.mockImplementation()
.mockReturnValue(
Promise.resolve({
parsedData: {
copy: false,
detail: true,
filtering: false,
query: true,
remove: false,
upload: false,
},
}),
);

const { getAllByTestId, getAllByRole } = render(
<VariablePanelContextProvider connectors={mockConnectors}>
<LeftPanel variables={variables} isDocumentLoaded />
</VariablePanelContextProvider>,
);
const imagePicker = await waitFor(() => getAllByTestId(getDataTestId('image-picker-content'))[0]);
await act(async () => {
imagePicker.click();
});
const image = getAllByRole('img', { name: /grafx/i })[0];

await act(async () => {
image.click();
});

const input = screen.queryByTestId(getDataTestIdForSUI('media-panel-search-input'));
expect(input).toBeNull();
});
test('Render search input when filtering is supported', async () => {
const { getAllByTestId, getAllByRole, getByTestId } = render(
<VariablePanelContextProvider connectors={mockConnectors}>
<LeftPanel variables={variables} isDocumentLoaded />
</VariablePanelContextProvider>,
);
const imagePicker = await waitFor(() => getAllByTestId(getDataTestId('image-picker-content'))[0]);
await act(async () => {
imagePicker.click();
});
const image = getAllByRole('img', { name: /grafx/i })[0];

await act(async () => {
image.click();
});

const input = getByTestId(getDataTestIdForSUI('media-panel-search-input'));
expect(input).toBeInTheDocument();
});
});
Loading