Skip to content

Commit

Permalink
feat(Dropdown): add support for centered dropdown menus (#6490)
Browse files Browse the repository at this point in the history
  • Loading branch information
kyletsang committed Nov 17, 2022
1 parent c0e7e9f commit 500ee94
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 37 deletions.
24 changes: 19 additions & 5 deletions src/Dropdown.tsx
Expand Up @@ -46,7 +46,14 @@ const propTypes = {
/**
* Determines the direction and location of the Menu in relation to it's Toggle.
*/
drop: PropTypes.oneOf(['up', 'start', 'end', 'down']),
drop: PropTypes.oneOf<DropDirection>([
'up',
'up-centered',
'start',
'end',
'down',
'down-centered',
]),

as: PropTypes.elementType,

Expand Down Expand Up @@ -116,6 +123,7 @@ const defaultProps: Partial<DropdownProps> = {
navbar: false,
align: 'start',
autoClose: true,
drop: 'down',
};

const Dropdown: BsPrefixRefForwardingComponent<'div', DropdownProps> =
Expand Down Expand Up @@ -178,6 +186,15 @@ const Dropdown: BsPrefixRefForwardingComponent<'div', DropdownProps> =
[align, drop, isRTL],
);

const directionClasses = {
down: prefix,
'down-centered': `${prefix}-center`,
up: 'dropup',
'up-centered': 'dropup-center dropup',
end: 'dropend',
start: 'dropstart',
};

return (
<DropdownContext.Provider value={contextValue}>
<BaseDropdown
Expand All @@ -197,10 +214,7 @@ const Dropdown: BsPrefixRefForwardingComponent<'div', DropdownProps> =
className={classNames(
className,
show && 'show',
(!drop || drop === 'down') && prefix,
drop === 'up' && 'dropup',
drop === 'end' && 'dropend',
drop === 'start' && 'dropstart',
directionClasses[drop!],
)}
/>
)}
Expand Down
8 changes: 7 additions & 1 deletion src/DropdownContext.ts
@@ -1,7 +1,13 @@
import * as React from 'react';
import { AlignType } from './types';

export type DropDirection = 'up' | 'start' | 'end' | 'down';
export type DropDirection =
| 'up'
| 'up-centered'
| 'start'
| 'end'
| 'down'
| 'down-centered';

export type DropdownContextValue = {
align?: AlignType;
Expand Down
2 changes: 2 additions & 0 deletions src/DropdownMenu.tsx
Expand Up @@ -112,6 +112,8 @@ export function getDropdownMenuPlacement(
placement = alignEnd ? rightEnd : rightStart;
else if (dropDirection === 'start')
placement = alignEnd ? leftEnd : leftStart;
else if (dropDirection === 'down-centered') placement = 'bottom';
else if (dropDirection === 'up-centered') placement = 'top';
return placement;
}

Expand Down
8 changes: 8 additions & 0 deletions test/DropdownMenuSpec.tsx
Expand Up @@ -110,11 +110,13 @@ describe('<Dropdown.Menu>', () => {
it('should return top placement', () => {
getDropdownMenuPlacement(false, 'up', false).should.equal('top-start');
getDropdownMenuPlacement(true, 'up', false).should.equal('top-end');
getDropdownMenuPlacement(true, 'up-centered', false).should.equal('top');
});

it('should return top placement for RTL', () => {
getDropdownMenuPlacement(false, 'up', true).should.equal('top-end');
getDropdownMenuPlacement(true, 'up', true).should.equal('top-start');
getDropdownMenuPlacement(true, 'up-centered', true).should.equal('top');
});

it('should return end placement', () => {
Expand All @@ -132,11 +134,17 @@ describe('<Dropdown.Menu>', () => {
'bottom-start',
);
getDropdownMenuPlacement(true, 'down', false).should.equal('bottom-end');
getDropdownMenuPlacement(true, 'down-centered', false).should.equal(
'bottom',
);
});

it('should return bottom placement for RTL', () => {
getDropdownMenuPlacement(false, 'down', true).should.equal('bottom-end');
getDropdownMenuPlacement(true, 'down', true).should.equal('bottom-start');
getDropdownMenuPlacement(true, 'down-centered', true).should.equal(
'bottom',
);
});

it('should return start placement', () => {
Expand Down
25 changes: 25 additions & 0 deletions test/DropdownSpec.tsx
Expand Up @@ -40,6 +40,31 @@ describe('<Dropdown>', () => {
});
});

it('renders div with drop=down-centered', () => {
const { container } = render(
<Dropdown title="Drop" drop="down-centered">
{dropdownChildren}
</Dropdown>,
);

container.firstElementChild!.classList.should.not.contain(['dropdown']);
container.firstElementChild!.classList.should.contain([`dropdown-center`]);
});

it('renders div with drop=up-centered', () => {
const { container } = render(
<Dropdown title="Drop" drop="up-centered">
{dropdownChildren}
</Dropdown>,
);

container.firstElementChild!.classList.should.not.contain(['dropdown']);
container.firstElementChild!.classList.should.contain([
'dropup-center',
'dropup',
]);
});

it('renders toggle with Dropdown.Toggle', () => {
const { getByText } = render(simpleDropdown);

Expand Down
66 changes: 35 additions & 31 deletions www/src/examples/Dropdown/DropDirections.js
Expand Up @@ -6,40 +6,44 @@ function DropDirectioExample() {
return (
<>
<div className="mb-2">
{['up', 'down', 'start', 'end'].map((direction) => (
<DropdownButton
as={ButtonGroup}
key={direction}
id={`dropdown-button-drop-${direction}`}
drop={direction}
variant="secondary"
title={` Drop ${direction} `}
>
<Dropdown.Item eventKey="1">Action</Dropdown.Item>
<Dropdown.Item eventKey="2">Another action</Dropdown.Item>
<Dropdown.Item eventKey="3">Something else here</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item eventKey="4">Separated link</Dropdown.Item>
</DropdownButton>
))}
{['up', 'up-centered', 'down', 'down-centered', 'start', 'end'].map(
(direction) => (
<DropdownButton
as={ButtonGroup}
key={direction}
id={`dropdown-button-drop-${direction}`}
drop={direction}
variant="secondary"
title={` Drop ${direction} `}
>
<Dropdown.Item eventKey="1">Action</Dropdown.Item>
<Dropdown.Item eventKey="2">Another action</Dropdown.Item>
<Dropdown.Item eventKey="3">Something else here</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item eventKey="4">Separated link</Dropdown.Item>
</DropdownButton>
),
)}
</div>

<div>
{['up', 'down', 'start', 'end'].map((direction) => (
<SplitButton
key={direction}
id={`dropdown-button-drop-${direction}`}
drop={direction}
variant="secondary"
title={`Drop ${direction}`}
>
<Dropdown.Item eventKey="1">Action</Dropdown.Item>
<Dropdown.Item eventKey="2">Another action</Dropdown.Item>
<Dropdown.Item eventKey="3">Something else here</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item eventKey="4">Separated link</Dropdown.Item>
</SplitButton>
))}
{['up', 'up-centered', 'down', 'down-centered', 'start', 'end'].map(
(direction) => (
<SplitButton
key={direction}
id={`dropdown-button-drop-${direction}`}
drop={direction}
variant="secondary"
title={`Drop ${direction}`}
>
<Dropdown.Item eventKey="1">Action</Dropdown.Item>
<Dropdown.Item eventKey="2">Another action</Dropdown.Item>
<Dropdown.Item eventKey="3">Something else here</Dropdown.Item>
<Dropdown.Divider />
<Dropdown.Item eventKey="4">Separated link</Dropdown.Item>
</SplitButton>
),
)}
</div>
</>
);
Expand Down

0 comments on commit 500ee94

Please sign in to comment.