(frontend) refactor QuickSearch components

- Simplified QuickSearchProps by removing unused properties and
enhancing type definitions.
- Updated QuickSearch component to utilize children for rendering,
improving flexibility.
- Added separator prop to QuickSearchInput for better control over
layout.
- Removed data prop from DocSearchModal's QuickSearch to streamline the
component's usage.
This commit is contained in:
Nathan Panchout
2024-12-16 10:15:42 +01:00
committed by Anthony LC
parent c3da23f5d3
commit ceaf1e28f9
5 changed files with 105 additions and 104 deletions

View File

@@ -1,9 +1,10 @@
import { Command } from 'cmdk'; import { Command } from 'cmdk';
import { ReactNode, useRef } from 'react'; import { ReactNode, useRef } from 'react';
import { hasChildrens } from '@/utils/children';
import { Box } from '../Box'; import { Box } from '../Box';
import { QuickSearchGroup } from './QuickSearchGroup';
import { QuickSearchInput } from './QuickSearchInput'; import { QuickSearchInput } from './QuickSearchInput';
import { QuickSearchStyle } from './QuickSearchStyle'; import { QuickSearchStyle } from './QuickSearchStyle';
@@ -21,11 +22,8 @@ export type QuickSearchData<T> = {
showWhenEmpty?: boolean; showWhenEmpty?: boolean;
}; };
export type QuickSearchProps<T> = { export type QuickSearchProps = {
data?: QuickSearchData<T>[];
onFilter?: (str: string) => void; onFilter?: (str: string) => void;
renderElement?: (element: T) => ReactNode;
onSelect?: (element: T) => void;
inputValue?: string; inputValue?: string;
inputContent?: ReactNode; inputContent?: ReactNode;
showInput?: boolean; showInput?: boolean;
@@ -35,19 +33,16 @@ export type QuickSearchProps<T> = {
children?: ReactNode; children?: ReactNode;
}; };
export const QuickSearch = <T,>({ export const QuickSearch = ({
onSelect,
onFilter, onFilter,
inputContent, inputContent,
inputValue, inputValue,
loading, loading,
showInput = true, showInput = true,
data,
renderElement,
label, label,
placeholder, placeholder,
children, children,
}: QuickSearchProps<T>) => { }: QuickSearchProps) => {
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
return ( return (
@@ -58,6 +53,7 @@ export const QuickSearch = <T,>({
{showInput && ( {showInput && (
<QuickSearchInput <QuickSearchInput
loading={loading} loading={loading}
withSeparator={hasChildrens(children)}
inputValue={inputValue} inputValue={inputValue}
onFilter={onFilter} onFilter={onFilter}
placeholder={placeholder} placeholder={placeholder}
@@ -66,20 +62,7 @@ export const QuickSearch = <T,>({
</QuickSearchInput> </QuickSearchInput>
)} )}
<Command.List> <Command.List>
<Box> <Box>{children}</Box>
{!loading &&
data?.map((group) => {
return (
<QuickSearchGroup
key={group.groupName}
group={group}
onSelect={onSelect}
renderElement={renderElement}
/>
);
})}
{children}
</Box>
</Command.List> </Command.List>
</Command> </Command>
</div> </div>

View File

@@ -1,14 +1,15 @@
import { Command } from 'cmdk'; import { Command } from 'cmdk';
import { ReactNode } from 'react';
import { Box } from '../Box'; import { Box } from '../Box';
import { QuickSearchData, QuickSearchProps } from './QuickSearch'; import { QuickSearchData } from './QuickSearch';
import { QuickSearchItem } from './QuickSearchItem'; import { QuickSearchItem } from './QuickSearchItem';
type Props<T> = { type Props<T> = {
group: QuickSearchData<T>; group: QuickSearchData<T>;
onSelect?: QuickSearchProps<T>['onSelect']; renderElement?: (element: T) => ReactNode;
renderElement: QuickSearchProps<T>['renderElement']; onSelect?: (element: T) => void;
}; };
export const QuickSearchGroup = <T,>({ export const QuickSearchGroup = <T,>({
@@ -16,7 +17,6 @@ export const QuickSearchGroup = <T,>({
onSelect, onSelect,
renderElement, renderElement,
}: Props<T>) => { }: Props<T>) => {
console.log('group', group, group.emptyString && group.elements.length === 0);
return ( return (
<Box $margin={{ top: 'base' }}> <Box $margin={{ top: 'base' }}>
<Command.Group <Command.Group

View File

@@ -15,6 +15,7 @@ type Props = {
onFilter?: (str: string) => void; onFilter?: (str: string) => void;
placeholder?: string; placeholder?: string;
children?: ReactNode; children?: ReactNode;
withSeparator?: boolean;
}; };
export const QuickSearchInput = ({ export const QuickSearchInput = ({
loading, loading,
@@ -22,6 +23,7 @@ export const QuickSearchInput = ({
onFilter, onFilter,
placeholder, placeholder,
children, children,
withSeparator: separator = true,
}: Props) => { }: Props) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { spacingsTokens } = useCunninghamTheme(); const { spacingsTokens } = useCunninghamTheme();
@@ -31,7 +33,7 @@ export const QuickSearchInput = ({
return ( return (
<> <>
{children} {children}
<HorizontalSeparator /> {separator && <HorizontalSeparator />}
</> </>
); );
} }
@@ -61,7 +63,7 @@ export const QuickSearchInput = ({
onValueChange={onFilter} onValueChange={onFilter}
/> />
</Box> </Box>
<HorizontalSeparator $withPadding={false} /> {separator && <HorizontalSeparator $withPadding={false} />}
</> </>
); );
}; };

View File

@@ -72,7 +72,6 @@ export const DocSearchModal = ({ ...modalProps }: DocSearchModalProps) => {
<QuickSearch <QuickSearch
placeholder={t('Type the name of a document')} placeholder={t('Type the name of a document')}
loading={loading} loading={loading}
data={[]}
onFilter={handleInputSearch} onFilter={handleInputSearch}
> >
<Box $height={isDesktop ? '500px' : 'calc(100vh - 68px - 1rem)'}> <Box $height={isDesktop ? '500px' : 'calc(100vh - 68px - 1rem)'}>

View File

@@ -1,8 +1,9 @@
import { Button } from '@openfun/cunningham-react'; import { Button, useModal } from '@openfun/cunningham-react';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { css } from 'styled-components'; import { css } from 'styled-components';
import { Box, Icon, StyledLink, Text } from '@/components'; import { Box, Icon, StyledLink, Text } from '@/components';
import { DocShareModal } from '@/features/docs/doc-share/component/DocShareModal';
import { useResponsiveStore } from '@/stores'; import { useResponsiveStore } from '@/stores';
import { Doc, LinkReach } from '../../doc-management'; import { Doc, LinkReach } from '../../doc-management';
@@ -16,90 +17,106 @@ type DocsGridItemProps = {
export const DocsGridItem = ({ doc }: DocsGridItemProps) => { export const DocsGridItem = ({ doc }: DocsGridItemProps) => {
const { isDesktop } = useResponsiveStore(); const { isDesktop } = useResponsiveStore();
const shareModal = useModal();
const isPublic = doc.link_reach === LinkReach.PUBLIC; const isPublic = doc.link_reach === LinkReach.PUBLIC;
const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED; const isAuthenticated = doc.link_reach === LinkReach.AUTHENTICATED;
const isRestricted = doc.link_reach === LinkReach.RESTRICTED; const isRestricted = doc.link_reach === LinkReach.RESTRICTED;
const sharedCount = doc.nb_accesses - 1; const sharedCount = doc.nb_accesses - 1;
const isShared = sharedCount > 0; const isShared = sharedCount > 0;
const handleShareClick = () => {
shareModal.open();
};
return ( return (
<Box <>
$direction="row"
$width="100%"
$align="center"
$gap="20px"
role="row"
$padding={{ vertical: 'xs', horizontal: 'sm' }}
$css={css`
cursor: pointer;
border-radius: 4px;
&:hover {
background-color: var(--c--theme--colors--greyscale-100);
}
`}
>
<StyledLink $css="flex: 7; align-items: center;" href={`/docs/${doc.id}`}>
<Box
data-testid={`docs-grid-name-${doc.id}`}
$flex={6}
$padding={{ right: 'base' }}
>
<SimpleDocItem doc={doc} />
</Box>
{isDesktop && (
<Box $flex={1.3}>
<Text $variation="500" $size="xs">
{DateTime.fromISO(doc.updated_at).toRelative()}
</Text>
</Box>
)}
</StyledLink>
<Box <Box
$flex={1}
$direction="row" $direction="row"
$width="100%"
$align="center" $align="center"
$justify="flex-end" $gap="20px"
$gap="10px" role="row"
$padding={{ vertical: 'xs', horizontal: 'sm' }}
$css={css`
cursor: pointer;
border-radius: 4px;
&:hover {
background-color: var(--c--theme--colors--greyscale-100);
}
`}
> >
{isDesktop && isPublic && ( <StyledLink
<Button $css="flex: 7; align-items: center;"
onClick={(event) => { href={`/docs/${doc.id}`}
event.preventDefault(); >
event.stopPropagation(); <Box
}} data-testid={`docs-grid-name-${doc.id}`}
size="nano" $flex={6}
icon={<Icon $variation="000" iconName="public" />} $padding={{ right: 'base' }}
> >
{isShared ? sharedCount : undefined} <SimpleDocItem doc={doc} />
</Button> </Box>
)} {isDesktop && (
{isDesktop && !isPublic && isRestricted && isShared && ( <Box $flex={1.3}>
<Button <Text $variation="500" $size="xs">
onClick={(event) => { {DateTime.fromISO(doc.updated_at).toRelative()}
event.preventDefault(); </Text>
event.stopPropagation(); </Box>
}} )}
color="tertiary" </StyledLink>
size="nano" <Box
icon={<Icon $variation="800" $theme="primary" iconName="group" />} $flex={1}
> $direction="row"
{sharedCount} $align="center"
</Button> $justify="flex-end"
)} $gap="10px"
{isDesktop && !isPublic && isAuthenticated && ( >
<Button {isDesktop && isPublic && (
onClick={(event) => { <Button
event.preventDefault(); onClick={(event) => {
event.stopPropagation(); event.preventDefault();
}} event.stopPropagation();
size="nano" handleShareClick();
icon={<Icon $variation="000" iconName="corporate_fare" />} }}
> size="nano"
{sharedCount} icon={<Icon $variation="000" iconName="public" />}
</Button> >
)} {isShared ? sharedCount : undefined}
<DocsGridActions doc={doc} /> </Button>
)}
{isDesktop && !isPublic && isRestricted && isShared && (
<Button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShareClick();
}}
color="tertiary"
size="nano"
icon={<Icon $variation="800" $theme="primary" iconName="group" />}
>
{sharedCount}
</Button>
)}
{isDesktop && !isPublic && isAuthenticated && (
<Button
onClick={(event) => {
event.preventDefault();
event.stopPropagation();
handleShareClick();
}}
size="nano"
icon={<Icon $variation="000" iconName="corporate_fare" />}
>
{sharedCount}
</Button>
)}
<DocsGridActions doc={doc} />
</Box>
</Box> </Box>
</Box> {shareModal.isOpen && (
<DocShareModal doc={doc} onClose={shareModal.close} />
)}
</>
); );
}; };