Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
2050368
relevance behaviour
g-saracca May 28, 2025
5f8fa5b
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca May 28, 2025
1906776
chore: js-dv pr version
g-saracca May 28, 2025
ab18346
feat: reading services and display them in selector
g-saracca May 28, 2025
1709552
feat: fetch with external search only first time
g-saracca May 28, 2025
15d5c2e
chore: comments
g-saracca May 29, 2025
7bde4c7
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca Jun 3, 2025
8b98c57
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca Jun 6, 2025
e6a6c0d
chore: use udpate js-dv pr version
g-saracca Jun 6, 2025
e0fc2eb
fix: clear button being hide
g-saracca Jun 6, 2025
972f13a
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca Aug 4, 2025
c1a4609
feat: use latest js-dv
g-saracca Aug 4, 2025
f9f73eb
feat(Design Sytem): DropdownButton customToggle
g-saracca Aug 4, 2025
86d65c7
feat: clean up search dropdown with dd component
g-saracca Aug 4, 2025
ec21b18
feat: add stars icon if selected search service is diff than solr
g-saracca Aug 11, 2025
9fb3813
feat(DesignSystem): Add type prop
g-saracca Aug 11, 2025
cd7f17d
feat: remove ext search after loading all pages
g-saracca Aug 11, 2025
1117a2f
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca Aug 14, 2025
ff751e8
test: improve coverage in SearchInput
g-saracca Aug 14, 2025
ca1ea8a
test: improve coverage
g-saracca Aug 14, 2025
be65ad9
test: improve coverage again
g-saracca Aug 14, 2025
f649d6f
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca Aug 20, 2025
03b80e1
test: avoid fetching external image and use fixture image
g-saracca Aug 20, 2025
5d7de83
Merge branch 'develop' into feat/search-engine-selector-integration-a…
g-saracca Aug 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/design-system/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ See [Conventional Commits](https://conventionalcommits.org) for commit guideline

# Non Published Changes

- **DropdownButton:**
- Add `customToggle` prop to allow custom toggle components.
- Add `customToggleClassname` and `customToggleMenuClassname` props to allow custom styling of the custom toggle dropdown wrapper and menu.
- Add `align` prop to control the alignment of the dropdown menu.
- **DropdownButtonItem:**
- Add `type` prop to allow specifying the type of the element.
- **SelectAdvanced:** Fix word wrapping in options list to prevent overflow and ensure long text is displayed correctly.

# [2.0.2](https://github.com/IQSS/dataverse-frontend/compare/@iqss/dataverse-design-system@2.0.1...@iqss/dataverse-design-system@2.0.2) (2024-06-23)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import { DropdownButton as DropdownButtonBS } from 'react-bootstrap'
import { ReactNode } from 'react'
import { DropdownButton as DropdownButtonBS, Dropdown } from 'react-bootstrap'
import { ReactNode, ComponentType, ForwardRefExoticComponent } from 'react'
import styles from './DropdownButton.module.scss'
import { IconName } from '../icon/IconName'
import { ButtonGroup } from '../button-group/ButtonGroup'
import { Icon } from '../icon/Icon'

type DropdownButtonVariant = 'primary' | 'secondary' | 'link'

interface CustomToggleProps {
onClick: (event: React.MouseEvent<HTMLElement>) => void
[key: string]: unknown
}

interface DropdownButtonProps {
id: string
title: string
title?: string
variant?: DropdownButtonVariant
icon?: IconName | ReactNode
withSpacing?: boolean
Expand All @@ -17,6 +23,10 @@ interface DropdownButtonProps {
disabled?: boolean
children: ReactNode
ariaLabel?: string
customToggle?: ComponentType<CustomToggleProps> | ForwardRefExoticComponent<CustomToggleProps>
customToggleClassname?: string
customToggleMenuClassname?: string
align?: 'end' | 'start'
}

export function DropdownButton({
Expand All @@ -29,22 +39,37 @@ export function DropdownButton({
onSelect,
disabled,
ariaLabel,
customToggle,
customToggleClassname,
customToggleMenuClassname,
align,
children
}: DropdownButtonProps) {
// If customToggle is provided, use Dropdown instead of DropdownButtonBS
if (customToggle) {
return (
<Dropdown id={id} onSelect={onSelect} className={customToggleClassname} align={align}>
<Dropdown.Toggle as={customToggle} />
<Dropdown.Menu className={customToggleMenuClassname}>{children}</Dropdown.Menu>
</Dropdown>
)
}

return (
<DropdownButtonBS
className={withSpacing ? styles.spacing : ''}
id={id}
title={
<>
{typeof icon === 'string' ? <Icon name={icon} /> : icon}
{title}
{title && title}
</>
}
aria-label={ariaLabel || title}
variant={variant}
as={asButtonGroup ? ButtonGroup : undefined}
disabled={disabled}
align={align}
onSelect={onSelect}>
{children}
</DropdownButtonBS>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ interface DropdownItemProps extends React.HTMLAttributes<HTMLElement> {
children: ReactNode
as?: ElementType
to?: string // When passing <Link> as the `as` prop, this prop is used to pass the URL <DropdownButtonItem as={Link} to="/some-path">...</DropdownButtonItem>
type?: string // For button or input elements
active?: boolean
className?: string
}

export function DropdownButtonItem({
Expand All @@ -18,6 +21,8 @@ export function DropdownButtonItem({
download,
children,
as,
active,
className,
...props
}: DropdownItemProps) {
return (
Expand All @@ -27,6 +32,8 @@ export function DropdownButtonItem({
disabled={disabled}
download={download}
as={as}
active={active}
className={className}
{...props}>
{children}
</DropdownBS.Item>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import type { Meta, StoryObj } from '@storybook/react'
import { forwardRef } from 'react'
import { DropdownButtonItem } from '../../components/dropdown-button/dropdown-button-item/DropdownButtonItem'
import { DropdownButton } from '../../components/dropdown-button/DropdownButton'
import { IconName } from '../../components/icon/IconName'
import { CanvasFixedHeight } from '../CanvasFixedHeight'
import { DropdownSeparator } from '../../components/dropdown-button/dropdown-separator/DropdownSeparator'
import { DropdownHeader } from '../../components/dropdown-button/dropdown-header/DropdownHeader'
import { Icon } from '../../components/icon/Icon'

/**
* ## Description
Expand Down Expand Up @@ -192,3 +194,46 @@ export const UseCaseSelect: Story = {
</CanvasFixedHeight>
)
}

// Custom Toggle Component Example
const CustomToggle = forwardRef<
HTMLButtonElement,
{ onClick: (event: React.MouseEvent<HTMLElement>) => void }
>(({ onClick }, ref) => (
<button
type="button"
ref={ref}
onClick={onClick}
style={{
display: 'grid',
placeItems: 'center',
padding: '8px 16px',
borderRadius: '999px',
cursor: 'pointer',
backgroundColor: 'orange',
fontSize: '22px'
}}>
<Icon name={IconName.COLLECTION} />
</button>
))

CustomToggle.displayName = 'CustomToggle'

export const WithCustomToggle: Story = {
name: 'With Custom Toggle',
render: () => (
<CanvasFixedHeight height={150}>
<DropdownButton
withSpacing
title=""
id="dropdown-custom-toggle"
customToggle={CustomToggle}
onSelect={() => {}}>
<DropdownHeader>Choose an Option</DropdownHeader>
<DropdownButtonItem eventKey="option-1">Custom Option 1</DropdownButtonItem>
<DropdownButtonItem eventKey="option-2">Custom Option 2</DropdownButtonItem>
<DropdownButtonItem eventKey="option-3">Custom Option 3</DropdownButtonItem>
</DropdownButton>
</CanvasFixedHeight>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,30 @@ describe('DropdownButton', () => {
)
cy.findByText(titleText).should('be.disabled')
})

it('renders with custom toggle', () => {
const CustomToggle = ({
onClick
}: {
onClick: (event: React.MouseEvent<HTMLElement>) => void
}) => (
<button type="button" onClick={onClick}>
Custom Toggle
</button>
)

cy.mount(
<DropdownButton
id="dropdown-custom-toggle"
title={titleText}
customToggle={CustomToggle}
onSelect={() => {}}>
<DropdownItem eventKey="option-1">Custom Option 1</DropdownItem>
<DropdownItem eventKey="option-2">Custom Option 2</DropdownItem>
<DropdownItem eventKey="option-3">Custom Option 3</DropdownItem>
</DropdownButton>
)

cy.findByText('Custom Toggle').should('exist')
})
})
4 changes: 4 additions & 0 deletions public/locales/en/homepage.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,9 @@
"content": "Learn about getting started creating your own dataverse repository here.",
"text_button": "Getting started"
}
},
"searchDropdown": {
"header": "Search Services",
"buttonLabel": "Toggle search services dropdown"
}
}
3 changes: 2 additions & 1 deletion src/collection/domain/models/CollectionItemsQueryParams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export enum CollectionItemsQueryParams {
START = 'start',
TYPES = 'types',
QUERY = 'q',
FILTER_QUERIES = 'fqs'
FILTER_QUERIES = 'fqs',
SEARCH_SERVICE = 'search_service'
}
3 changes: 2 additions & 1 deletion src/collection/domain/repositories/CollectionRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export interface CollectionRepository {
getItems(
collectionId: string,
paginationInfo: CollectionItemsPaginationInfo,
searchCriteria?: CollectionSearchCriteria
searchCriteria?: CollectionSearchCriteria,
searchService?: string
): Promise<CollectionItemSubset>
getMyDataItems: (
roleIds: number[],
Expand Down
5 changes: 3 additions & 2 deletions src/collection/domain/useCases/getCollectionItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ export async function getCollectionItems(
collectionRepository: CollectionRepository,
collectionId: string,
paginationInfo: CollectionItemsPaginationInfo,
searchCriteria: CollectionSearchCriteria
searchCriteria: CollectionSearchCriteria,
searchService?: string
): Promise<CollectionItemSubset> {
return collectionRepository
.getItems(collectionId, paginationInfo, searchCriteria)
.getItems(collectionId, paginationInfo, searchCriteria, searchService)
.catch((error: Error) => {
throw new Error(error.message)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,17 @@ export class CollectionJSDataverseRepository implements CollectionRepository {
getItems(
collectionId: string,
paginationInfo: CollectionItemsPaginationInfo,
searchCriteria: CollectionSearchCriteria
searchCriteria: CollectionSearchCriteria,
searchService?: string
): Promise<CollectionItemSubset> {
return getCollectionItems
.execute(collectionId, paginationInfo?.pageSize, paginationInfo?.offset, searchCriteria)
.execute(
collectionId,
paginationInfo?.pageSize,
paginationInfo?.offset,
searchCriteria,
searchService
)
.then((jsCollectionItemSubset) => {
const collectionItemsPreviewsMapped = JSCollectionItemsMapper.toCollectionItemsPreviews(
jsCollectionItemSubset.items
Expand Down
56 changes: 56 additions & 0 deletions src/search/domain/hooks/useGetSearchServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { useCallback, useEffect, useState } from 'react'
import { ReadError } from '@iqss/dataverse-client-javascript'
import { SearchRepository } from '../repositories/SearchRepository'
import { SearchService } from '../models/SearchService'
import { getSearchServices } from '../useCases/getSearchServices'
import { JSDataverseReadErrorHandler } from '@/shared/helpers/JSDataverseReadErrorHandler'

interface useGetSearchServicesProps {
searchRepository: SearchRepository
autoFetch?: boolean
}

export const useGetSearchServices = ({
searchRepository,
autoFetch = true
}: useGetSearchServicesProps) => {
const [searchServices, setSearchServices] = useState<SearchService[]>([])
const [isLoadingSearchServices, setIsLoadingSearchServices] = useState<boolean>(autoFetch)
const [errorSearchServices, setErrorSearchServices] = useState<string | null>(null)

const fetchSearchServices = useCallback(async () => {
setIsLoadingSearchServices(true)
setErrorSearchServices(null)

try {
const searchServicesResponse = await getSearchServices(searchRepository)

setSearchServices(searchServicesResponse)
} catch (err) {
if (err instanceof ReadError) {
const error = new JSDataverseReadErrorHandler(err)
const formattedError =
error.getReasonWithoutStatusCode() ?? /* istanbul ignore next */ error.getErrorMessage()

setErrorSearchServices(formattedError)
} else {
setErrorSearchServices('Something went wrong getting the search services. Try again later.')
}
} finally {
setIsLoadingSearchServices(false)
}
}, [searchRepository])

useEffect(() => {
if (autoFetch) {
void fetchSearchServices()
}
}, [autoFetch, fetchSearchServices])

return {
searchServices,
isLoadingSearchServices,
errorSearchServices,
fetchSearchServices
}
}
4 changes: 4 additions & 0 deletions src/search/domain/models/SearchService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export interface SearchService {
name: string
displayName: string
}
5 changes: 5 additions & 0 deletions src/search/domain/repositories/SearchRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { SearchService } from '../models/SearchService'

export interface SearchRepository {
getServices: () => Promise<SearchService[]>
}
8 changes: 8 additions & 0 deletions src/search/domain/useCases/getSearchServices.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { SearchService } from '../models/SearchService'
import { SearchRepository } from '../repositories/SearchRepository'

export async function getSearchServices(
searchRepository: SearchRepository
): Promise<SearchService[]> {
return searchRepository.getServices()
}
9 changes: 9 additions & 0 deletions src/search/infrastructure/repositories/SearchJSRepository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { SearchService } from '@/search/domain/models/SearchService'
import { SearchRepository } from '@/search/domain/repositories/SearchRepository'
import { getSearchServices } from '@iqss/dataverse-client-javascript'

export class SearchJSRepository implements SearchRepository {
getServices(): Promise<SearchService[]> {
return getSearchServices.execute().then((searchServices) => searchServices)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,6 @@ export const CollectionItemsPanel = ({

const handleSearchSubmit = async (searchValue: string) => {
const isSearchValueEmpty = searchValue === ''

itemsListContainerRef.current?.scrollTo({ top: 0 })

const resetPaginationInfo = new CollectionItemsPaginationInfo()
Expand Down
Loading
Loading