Skip to content

Commit 8d09d29

Browse files
author
Julien Castelain
committed
feat(@clayui/drop-down): Allow the menu to be toggled
Two new props have been added to ClayDropDownWithDrillDown: active and onActiveChange. Setting active to true will make the menu initially active (open), and onActiveChange will allow the menu to be toggled. Fixes #4103
1 parent d639da8 commit 8d09d29

File tree

4 files changed

+294
-3
lines changed

4 files changed

+294
-3
lines changed

packages/clay-drop-down/src/DropDownWithDrilldown.tsx

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
* SPDX-License-Identifier: BSD-3-Clause
44
*/
55

6+
import {TInternalStateOnChange, useInternalState} from '@clayui/shared';
67
import classNames from 'classnames';
78
import React from 'react';
89

@@ -11,6 +12,11 @@ import ClayDropDownMenu from './Menu';
1112
import Drilldown from './drilldown';
1213

1314
interface IProps extends React.HTMLAttributes<HTMLDivElement> {
15+
/**
16+
* Flag to indicate if the menu should be initially active (open).
17+
*/
18+
active?: boolean;
19+
1420
/**
1521
* Default position of menu element. Values come from `./Menu`.
1622
*/
@@ -53,6 +59,11 @@ interface IProps extends React.HTMLAttributes<HTMLDivElement> {
5359
*/
5460
offsetFn?: React.ComponentProps<typeof ClayDropDown>['offsetFn'];
5561

62+
/**
63+
* Callback the will be invoked when the active prop is changed.
64+
*/
65+
onActiveChange?: TInternalStateOnChange<boolean>;
66+
5667
/**
5768
* Path to spritemap
5869
*/
@@ -70,6 +81,7 @@ interface IHistory {
7081
}
7182

7283
export const ClayDropDownWithDrilldown: React.FunctionComponent<IProps> = ({
84+
active,
7385
alignmentPosition,
7486
className,
7587
containerElement,
@@ -79,19 +91,23 @@ export const ClayDropDownWithDrilldown: React.FunctionComponent<IProps> = ({
7991
menuWidth,
8092
menus,
8193
offsetFn,
94+
onActiveChange,
8295
spritemap,
8396
trigger,
8497
}: IProps) => {
85-
const [active, setActive] = React.useState(false);
8698
const [activeMenu, setActiveMenu] = React.useState(initialActiveMenu);
8799
const [direction, setDirection] = React.useState<'prev' | 'next'>();
88100
const [history, setHistory] = React.useState<Array<IHistory>>([]);
101+
const [internalActive, setInternalActive] = useInternalState({
102+
onChange: onActiveChange,
103+
value: active,
104+
});
89105

90106
const menuIds = Object.keys(menus);
91107

92108
return (
93109
<ClayDropDown
94-
active={active}
110+
active={internalActive}
95111
alignmentPosition={alignmentPosition}
96112
className={className}
97113
containerElement={containerElement}
@@ -103,7 +119,7 @@ export const ClayDropDownWithDrilldown: React.FunctionComponent<IProps> = ({
103119
menuHeight={menuHeight}
104120
menuWidth={menuWidth}
105121
offsetFn={offsetFn}
106-
onActiveChange={setActive}
122+
onActiveChange={setInternalActive}
107123
trigger={trigger}
108124
>
109125
<Drilldown.Inner>

packages/clay-drop-down/src/__tests__/DropDownWithDrilldown.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,4 +134,52 @@ describe('ClayDropDownWithDrilldown', () => {
134134
document.querySelectorAll('.drilldown-item')[0].classList
135135
).toContain('drilldown-current');
136136
});
137+
138+
it('renders with the menu initially active', () => {
139+
render(
140+
<ClayDropDownWithDrilldown
141+
active
142+
initialActiveMenu="x0a3"
143+
menus={{
144+
x0a3: [
145+
{href: '#', title: 'Hash Link'},
146+
{child: 'x0a4', title: 'Subnav'},
147+
],
148+
x0a4: [{href: '#', title: '2nd hash link'}],
149+
}}
150+
spritemap="#"
151+
trigger={<button data-testid="trigger" />}
152+
/>
153+
);
154+
155+
expect(document.body).toMatchSnapshot();
156+
});
157+
158+
it('the menu can be toggled by clicking in an item', () => {
159+
const onActiveChange = jest.fn();
160+
161+
const {getByTestId} = render(
162+
<ClayDropDownWithDrilldown
163+
initialActiveMenu="x0a3"
164+
menus={{
165+
x0a3: [
166+
{onClick: onActiveChange, title: 'Toggle'},
167+
{child: 'x0a4', title: 'Subnav'},
168+
],
169+
x0a4: [{href: '#', title: '2nd hash link'}],
170+
}}
171+
onActiveChange={onActiveChange}
172+
spritemap="#"
173+
trigger={<button data-testid="trigger" />}
174+
/>
175+
);
176+
177+
fireEvent.click(getByTestId('menu-item-Toggle'));
178+
fireEvent.click(getByTestId('menu-item-Toggle'));
179+
fireEvent.click(getByTestId('menu-item-Toggle'));
180+
181+
expect(onActiveChange).toHaveBeenCalledTimes(3);
182+
183+
expect(document.body).toMatchSnapshot();
184+
});
137185
});

packages/clay-drop-down/src/__tests__/__snapshots__/DropDownWithDrilldown.tsx.snap

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,195 @@ exports[`ClayDropDownWithDrilldown renders dividers 1`] = `
355355
</div>
356356
</body>
357357
`;
358+
359+
exports[`ClayDropDownWithDrilldown renders with the menu initially active 1`] = `
360+
<body>
361+
<div>
362+
<div
363+
class="dropdown"
364+
>
365+
<button
366+
class="dropdown-toggle"
367+
data-testid="trigger"
368+
style=""
369+
/>
370+
</div>
371+
</div>
372+
<div>
373+
<div>
374+
<div
375+
class="dropdown-menu drilldown dropdown-menu-indicator-end show"
376+
style="left: -999px; top: -995px;"
377+
>
378+
<div
379+
class="drilldown-inner"
380+
>
381+
<div
382+
class="drilldown-item drilldown-current"
383+
>
384+
<ul
385+
class="inline-scroller"
386+
>
387+
<li>
388+
<a
389+
class="dropdown-item"
390+
data-testid="menu-item-Hash Link"
391+
href="#"
392+
>
393+
<span
394+
class="dropdown-item-indicator-text-end"
395+
>
396+
Hash Link
397+
</span>
398+
</a>
399+
</li>
400+
<li>
401+
<button
402+
class="dropdown-item btn btn-unstyled"
403+
data-testid="menu-item-Subnav"
404+
type="button"
405+
>
406+
<span
407+
class="dropdown-item-indicator-text-end"
408+
>
409+
Subnav
410+
</span>
411+
<span
412+
class="dropdown-item-indicator-end"
413+
>
414+
<svg
415+
class="lexicon-icon lexicon-icon-angle-right"
416+
role="presentation"
417+
>
418+
<use
419+
xlink:href="##angle-right"
420+
/>
421+
</svg>
422+
</span>
423+
</button>
424+
</li>
425+
</ul>
426+
</div>
427+
<div
428+
class="drilldown-item"
429+
>
430+
<ul
431+
class="inline-scroller"
432+
>
433+
<li>
434+
<a
435+
class="dropdown-item"
436+
data-testid="menu-item-2nd hash link"
437+
href="#"
438+
>
439+
<span
440+
class="dropdown-item-indicator-text-end"
441+
>
442+
2nd hash link
443+
</span>
444+
</a>
445+
</li>
446+
</ul>
447+
</div>
448+
</div>
449+
</div>
450+
</div>
451+
</div>
452+
</body>
453+
`;
454+
455+
exports[`ClayDropDownWithDrilldown the menu can be toggled by clicking in an item 1`] = `
456+
<body>
457+
<div>
458+
<div
459+
class="dropdown"
460+
>
461+
<button
462+
class="dropdown-toggle"
463+
data-testid="trigger"
464+
style=""
465+
/>
466+
</div>
467+
</div>
468+
<div>
469+
<div>
470+
<div
471+
class="dropdown-menu drilldown dropdown-menu-indicator-end"
472+
style="left: -999px; top: -995px;"
473+
>
474+
<div
475+
class="drilldown-inner"
476+
>
477+
<div
478+
class="drilldown-item drilldown-current"
479+
>
480+
<ul
481+
class="inline-scroller"
482+
>
483+
<li>
484+
<button
485+
class="dropdown-item btn btn-unstyled"
486+
data-testid="menu-item-Toggle"
487+
type="button"
488+
>
489+
<span
490+
class="dropdown-item-indicator-text-end"
491+
>
492+
Toggle
493+
</span>
494+
</button>
495+
</li>
496+
<li>
497+
<button
498+
class="dropdown-item btn btn-unstyled"
499+
data-testid="menu-item-Subnav"
500+
type="button"
501+
>
502+
<span
503+
class="dropdown-item-indicator-text-end"
504+
>
505+
Subnav
506+
</span>
507+
<span
508+
class="dropdown-item-indicator-end"
509+
>
510+
<svg
511+
class="lexicon-icon lexicon-icon-angle-right"
512+
role="presentation"
513+
>
514+
<use
515+
xlink:href="##angle-right"
516+
/>
517+
</svg>
518+
</span>
519+
</button>
520+
</li>
521+
</ul>
522+
</div>
523+
<div
524+
class="drilldown-item"
525+
>
526+
<ul
527+
class="inline-scroller"
528+
>
529+
<li>
530+
<a
531+
class="dropdown-item"
532+
data-testid="menu-item-2nd hash link"
533+
href="#"
534+
>
535+
<span
536+
class="dropdown-item-indicator-text-end"
537+
>
538+
2nd hash link
539+
</span>
540+
</a>
541+
</li>
542+
</ul>
543+
</div>
544+
</div>
545+
</div>
546+
</div>
547+
</div>
548+
</body>
549+
`;

packages/clay-drop-down/stories/index.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,41 @@ storiesOf('Components|ClayDropDown', module)
301301
trigger={<ClayButton>{'Click Me'}</ClayButton>}
302302
/>
303303
))
304+
.add('ClayDropDownWithDrillDown/active', () => {
305+
const [active, setActive] = React.useState(true);
306+
307+
const onActiveChange = () => {
308+
setActive(!active);
309+
};
310+
311+
return (
312+
<ClayDropDownWithDrilldown
313+
active={active}
314+
initialActiveMenu="x0a3"
315+
menus={{
316+
x0a3: [
317+
{href: '#', title: 'Hash Link'},
318+
{onClick: () => alert('test'), title: 'Alert!'},
319+
{
320+
onClick: () => {
321+
onActiveChange();
322+
},
323+
title: 'Toggle menu',
324+
},
325+
{child: 'x0a4', title: 'Subnav'},
326+
],
327+
x0a4: [
328+
{href: '#', title: '2nd hash link'},
329+
{child: 'x0a5', title: 'Subnav'},
330+
],
331+
x0a5: [{title: 'The'}, {title: 'End'}],
332+
}}
333+
onActiveChange={onActiveChange}
334+
spritemap={spritemap}
335+
trigger={<ClayButton>{'Click Me'}</ClayButton>}
336+
/>
337+
);
338+
})
304339
.add('ClayDropDownWithItems', () => {
305340
const [value, setValue] = React.useState('');
306341

0 commit comments

Comments
 (0)