diff --git a/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx b/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx index b7b51c8582..45b0a97206 100644 --- a/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx +++ b/services/web/frontend/js/shared/components/ol/ol-autocomplete.tsx @@ -31,6 +31,7 @@ export type OLAutocompleteProps = { createOptionPrefix?: string useFuzzySearch?: boolean inputRef?: React.ForwardedRef + expandUp?: boolean } type OLAutocompleteDisplayItem = @@ -55,6 +56,7 @@ function OLAutocompleteInternal({ createOptionPrefix = '+ Create', useFuzzySearch = false, inputRef, + expandUp = false, }: OLAutocompleteProps) { const { t } = useTranslation() @@ -163,8 +165,8 @@ function OLAutocompleteInternal({ onChange('') } - return ( -
+ const getSearchBar = () => ( +
)}
+
+ ) + const getResultsList = () => ( + <> + + ) + + return ( +
+ {expandUp ? ( + <> + {getResultsList()} + {getSearchBar()} + + ) : ( + <> + {getSearchBar()} + {getResultsList()} + + )}
) } diff --git a/services/web/frontend/stories/shared/autocomplete.stories.tsx b/services/web/frontend/stories/shared/autocomplete.stories.tsx index f5449afbbd..8fa395d04d 100644 --- a/services/web/frontend/stories/shared/autocomplete.stories.tsx +++ b/services/web/frontend/stories/shared/autocomplete.stories.tsx @@ -239,6 +239,30 @@ export const GroupedFuzzySearch: StoryObj = { }, } +export const ExpandUp: StoryObj = { + render: args => , + args: { + items: sampleItems, + label: 'Framework (expand up)', + placeholder: 'Dropdown expands upward', + showLabel: true, + allowCreate: true, + expandUp: true, + }, +} + +export const ExpandUpGrouped: StoryObj = { + render: args => , + args: { + items: entryTypeItems, + label: 'Entry type (expand up)', + placeholder: 'Grouped items expanding upward', + showLabel: true, + allowCreate: true, + expandUp: true, + }, +} + const meta: Meta = { title: 'Shared / Components / Autocomplete', component: OLAutocomplete, @@ -254,6 +278,7 @@ const meta: Meta = { 'disabled', 'createOptionPrefix', 'useFuzzySearch', + 'expandUp', ], }, }, @@ -300,6 +325,11 @@ const meta: Meta = { control: 'boolean', description: 'Enable fuzzy search matching for suggestions', }, + expandUp: { + control: 'boolean', + description: + 'When true, the dropdown expands upward and the search bar appears below the results list', + }, }, } diff --git a/services/web/frontend/stylesheets/components/all.scss b/services/web/frontend/stylesheets/components/all.scss index 9234008b67..e4bb2d3921 100644 --- a/services/web/frontend/stylesheets/components/all.scss +++ b/services/web/frontend/stylesheets/components/all.scss @@ -47,5 +47,5 @@ @import 'group-members'; @import 'upgrade-benefits'; @import 'labeled-divider'; -@import 'ol-autocomplete'; +@import 'autocomplete'; @import 'group-users'; diff --git a/services/web/frontend/stylesheets/components/ol-autocomplete.scss b/services/web/frontend/stylesheets/components/autocomplete.scss similarity index 71% rename from services/web/frontend/stylesheets/components/ol-autocomplete.scss rename to services/web/frontend/stylesheets/components/autocomplete.scss index 03fdc5fd81..96e6ee3ba2 100644 --- a/services/web/frontend/stylesheets/components/ol-autocomplete.scss +++ b/services/web/frontend/stylesheets/components/autocomplete.scss @@ -9,5 +9,10 @@ margin: 0; margin-top: 2px; max-height: 400px; + + &.select-dropdown-menu-expand-up { + margin-top: 0; + margin-bottom: 2px; + } } } diff --git a/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx b/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx index eb09bb9113..4559fb99f3 100644 --- a/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx +++ b/services/web/test/frontend/components/shared/ol-autocomplete.spec.tsx @@ -58,6 +58,7 @@ function render(props: RenderProps) { disabled={props.disabled} createOptionPrefix={props.createOptionPrefix} useFuzzySearch={props.useFuzzySearch} + expandUp={props.expandUp} /> @@ -528,4 +529,54 @@ describe('', function () { cy.contains('+ Create').should('exist') }) }) + + describe('expandUp prop', function () { + it('renders search bar before results list when expandUp is false', function () { + render({ items: testItems, expandUp: false }) + cy.findByRole('combobox').click() + + cy.get('.ol-autocomplete').within(() => { + cy.get('.dropdown-menu').then($menu => { + cy.findByRole('combobox').then($input => { + const inputTop = $input[0].getBoundingClientRect().top + const menuTop = $menu[0].getBoundingClientRect().top + expect(inputTop).to.be.lessThan(menuTop) + }) + }) + }) + }) + + it('renders results list before search bar when expandUp is true', function () { + render({ items: testItems, expandUp: true }) + cy.findByRole('combobox').click() + + cy.get('.ol-autocomplete').within(() => { + cy.get('.dropdown-menu').then($menu => { + cy.findByRole('combobox').then($input => { + const inputTop = $input[0].getBoundingClientRect().top + const menuTop = $menu[0].getBoundingClientRect().top + expect(menuTop).to.be.lessThan(inputTop) + }) + }) + }) + }) + + it('applies correct margin class when expandUp is false', function () { + render({ items: testItems, expandUp: false }) + + cy.get('.ol-autocomplete').within(() => { + cy.get('.mb-3').should('exist') + cy.get('.mt-3').should('not.exist') + }) + }) + + it('applies correct margin class when expandUp is true', function () { + render({ items: testItems, expandUp: true }) + + cy.get('.ol-autocomplete').within(() => { + cy.get('.mt-3').should('exist') + cy.get('.mb-3').should('not.exist') + }) + }) + }) })