Skip to content

Commit c7f291d

Browse files
authored
feat(create-modal): search for templates (#6954)
* feat: style search box * feat: search state management and cleanup * fix: type reference * fix: reset search query only on explicit tab state change * fix: address comments on search semantics * fix: grid split
1 parent b7f1306 commit c7f291d

30 files changed

+297
-940
lines changed

packages/app/src/app/components/CreateNewSandbox/CreateSandbox/CreateSandbox.tsx

Lines changed: 93 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
ThemeProvider,
88
} from '@codesandbox/components';
99
import { useActions, useAppState } from 'app/overmind';
10-
import React, { ReactNode, useState } from 'react';
10+
import React, { ReactNode, useState, useEffect } from 'react';
1111
import { TabStateReturn, useTabState } from 'reakit/Tab';
1212
import slugify from '@codesandbox/common/lib/utils/slugify';
1313
import { getTemplateIcon } from '@codesandbox/common/lib/utils/getTemplateIcon';
@@ -31,6 +31,8 @@ import { useOfficialTemplates } from './useOfficialTemplates';
3131
import { useTeamTemplates } from './useTeamTemplates';
3232
import { CloudBetaBadge } from '../../CloudBetaBadge';
3333
import { CreateSandboxParams } from './types';
34+
import { SearchBox } from './SearchBox';
35+
import { SearchResults } from './SearchResults';
3436

3537
export const COLUMN_MEDIA_THRESHOLD = 1600;
3638

@@ -80,6 +82,8 @@ export const CreateSandbox: React.FC<CreateSandboxProps> = ({
8082
const { hasLogIn, activeTeamInfo, user } = useAppState();
8183
const actions = useActions();
8284
const isSyncedSandboxesPage = location.pathname.includes('/synced-sandboxes');
85+
const defaultSelectedTab =
86+
initialTab || isSyncedSandboxesPage ? 'import' : 'quickstart';
8387
const isUser = user?.username === activeTeamInfo?.name;
8488

8589
/**
@@ -131,14 +135,21 @@ export const CreateSandbox: React.FC<CreateSandboxProps> = ({
131135

132136
const tabState = useTabState({
133137
orientation: 'vertical',
134-
selectedId: initialTab || isSyncedSandboxesPage ? 'import' : 'quickstart',
138+
selectedId: defaultSelectedTab,
135139
});
136140

137141
const [viewState, setViewState] = useState<
138-
'initial' | 'fromTemplate' | 'fork' /* | 'search' */
142+
'initial' | 'fromTemplate' | 'fork'
139143
>('initial');
140144
// ❗️ We could combine viewState with selectedtemplate to limit the amout of states.
141145
const [selectedTemplate, setSelectedTemplate] = useState<TemplateFragment>();
146+
const [searchQuery, setSearchQuery] = useState<string>('');
147+
148+
useEffect(() => {
149+
if (searchQuery && tabState.selectedId) {
150+
setSearchQuery('');
151+
}
152+
}, [tabState.selectedId]);
142153

143154
const createFromTemplate = (
144155
template: TemplateFragment,
@@ -189,10 +200,21 @@ export const CreateSandbox: React.FC<CreateSandboxProps> = ({
189200
</HeaderInformation>
190201

191202
{viewState === 'initial' ? (
192-
<div>
193-
{/* ❗️ TODO: Search */}
194-
search
195-
</div>
203+
<SearchBox
204+
value={searchQuery}
205+
onChange={e => {
206+
const query = e.target.value;
207+
if (query) {
208+
// Reset tab panel when typing in the search query box
209+
tabState.select(null);
210+
} else {
211+
// Restore the default tab when search query is removed
212+
tabState.select(defaultSelectedTab);
213+
}
214+
215+
setSearchQuery(query);
216+
}}
217+
/>
196218
) : null}
197219

198220
{/* isModal is undefined on /s/ page */}
@@ -280,66 +302,74 @@ export const CreateSandbox: React.FC<CreateSandboxProps> = ({
280302
</ModalSidebar>
281303

282304
<ModalContent>
283-
{viewState === 'initial' ? (
284-
<>
285-
<Panel tab={tabState} id="quickstart">
286-
<TemplateCategoryList
287-
title="Start from a template"
288-
templates={quickStartTemplates}
289-
onSelectTemplate={selectTemplate}
290-
/>
291-
</Panel>
292-
293-
<Panel tab={tabState} id="import">
294-
<Import />
295-
</Panel>
296-
297-
{showTeamTemplates ? (
298-
<Panel tab={tabState} id="team-templates">
305+
{viewState === 'initial' &&
306+
(searchQuery ? (
307+
<SearchResults
308+
search={searchQuery}
309+
onSelectTemplate={selectTemplate}
310+
/>
311+
) : (
312+
<>
313+
<Panel tab={tabState} id="quickstart">
299314
<TemplateCategoryList
300-
title={`${isUser ? 'My' : activeTeamInfo.name} templates`}
301-
templates={teamTemplates}
315+
title="Start from a template"
316+
templates={quickStartTemplates}
302317
onSelectTemplate={selectTemplate}
303318
/>
304319
</Panel>
305-
) : null}
306-
307-
<Panel tab={tabState} id="cloud-templates">
308-
<TemplateCategoryList
309-
title="Cloud templates"
310-
showBetaTag
311-
templates={officialTemplates.filter(
312-
template => template.sandbox.isV2
313-
)}
314-
onSelectTemplate={selectTemplate}
315-
/>
316-
</Panel>
317-
318-
<Panel tab={tabState} id="official-templates">
319-
<TemplateCategoryList
320-
title="Official templates"
321-
templates={officialTemplates}
322-
onSelectTemplate={selectTemplate}
323-
/>
324-
</Panel>
325-
326-
{essentialState.state === 'success'
327-
? essentialState.essentials.map(essential => (
328-
<Panel
329-
key={essential.key}
330-
tab={tabState}
331-
id={slugify(essential.title)}
332-
>
333-
<TemplateCategoryList
334-
title={essential.title}
335-
templates={essential.templates}
336-
onSelectTemplate={selectTemplate}
337-
/>
338-
</Panel>
339-
))
340-
: null}
341-
</>
342-
) : null}
320+
321+
<Panel tab={tabState} id="import">
322+
<Import />
323+
</Panel>
324+
325+
{showTeamTemplates ? (
326+
<Panel tab={tabState} id="team-templates">
327+
<TemplateCategoryList
328+
title={`${
329+
isUser ? 'My' : activeTeamInfo.name
330+
} templates`}
331+
templates={teamTemplates}
332+
onSelectTemplate={selectTemplate}
333+
/>
334+
</Panel>
335+
) : null}
336+
337+
<Panel tab={tabState} id="cloud-templates">
338+
<TemplateCategoryList
339+
title="Cloud templates"
340+
showBetaTag
341+
templates={officialTemplates.filter(
342+
template => template.sandbox.isV2
343+
)}
344+
onSelectTemplate={selectTemplate}
345+
/>
346+
</Panel>
347+
348+
<Panel tab={tabState} id="official-templates">
349+
<TemplateCategoryList
350+
title="Official templates"
351+
templates={officialTemplates}
352+
onSelectTemplate={selectTemplate}
353+
/>
354+
</Panel>
355+
356+
{essentialState.state === 'success'
357+
? essentialState.essentials.map(essential => (
358+
<Panel
359+
key={essential.key}
360+
tab={tabState}
361+
id={slugify(essential.title)}
362+
>
363+
<TemplateCategoryList
364+
title={essential.title}
365+
templates={essential.templates}
366+
onSelectTemplate={selectTemplate}
367+
/>
368+
</Panel>
369+
))
370+
: null}
371+
</>
372+
))}
343373

344374
{viewState === 'fromTemplate' ? (
345375
<FromTemplate

packages/app/src/app/components/CreateNewSandbox/CreateSandbox/Explore/Explore.tsx

Lines changed: 0 additions & 78 deletions
This file was deleted.

packages/app/src/app/components/CreateNewSandbox/CreateSandbox/Explore/Search/ExploreSearch.tsx

Lines changed: 0 additions & 10 deletions
This file was deleted.

packages/app/src/app/components/CreateNewSandbox/CreateSandbox/Explore/Search/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/app/src/app/components/CreateNewSandbox/CreateSandbox/Explore/SearchResults/ExploreResultList.tsx

Lines changed: 0 additions & 77 deletions
This file was deleted.

0 commit comments

Comments
 (0)