✨(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:
committed by
Anthony LC
parent
c3da23f5d3
commit
ceaf1e28f9
@@ -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>
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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)'}>
|
||||||
|
|||||||
@@ -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} />
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user