Skip to content

Commit 5bbd80e

Browse files
authored
feat: sticky footer section for dropdown (#889)
1 parent 2d3342b commit 5bbd80e

File tree

3 files changed

+151
-14
lines changed

3 files changed

+151
-14
lines changed

packages/dropdownMenu/components/DropdownMenu.tsx

+49-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import * as React from "react";
2-
import { cx } from "@emotion/css";
2+
import { css, cx } from "@emotion/css";
33
import Downshift, { ControllerStateAndHelpers } from "downshift";
44
import { Dropdownable } from "../../dropdownable";
55
import { border, buttonReset, display } from "../../shared/styles/styleUtils";
@@ -8,6 +8,42 @@ import PopoverListItem from "../../popover/components/PopoverListItem";
88
import { DropdownSectionProps } from "./DropdownSection";
99
import { DropdownMenuItemProps } from "./DropdownMenuItem";
1010
import { Direction } from "../../dropdownable/components/Dropdownable";
11+
import {
12+
spaceM,
13+
themeBgPrimary
14+
} from "../../design-tokens/build/js/designTokens";
15+
16+
const stickyFooter = css`
17+
background-color: ${themeBgPrimary};
18+
position: sticky;
19+
bottom: 0;
20+
left: 0;
21+
right: 0;
22+
padding: 2px 0;
23+
width: 100%;
24+
&::before {
25+
content: " ";
26+
top: -17px;
27+
width: inherit;
28+
height: ${spaceM};
29+
position: absolute;
30+
background: linear-gradient(transparent, ${themeBgPrimary});
31+
}
32+
`;
33+
34+
const SectionWrapper = ({ children, footer = false, sectionIndex }) => {
35+
return (
36+
<div
37+
key={`dropdown-${sectionIndex}`}
38+
className={cx(
39+
{ [border("top")]: sectionIndex !== 0 },
40+
{ [stickyFooter]: footer }
41+
)}
42+
>
43+
{children}
44+
</div>
45+
);
46+
};
1147

1248
export interface DropdownMenuProps {
1349
/**
@@ -95,7 +131,7 @@ const DropdownMenu = (props: DropdownMenuProps) => {
95131
}>(
96132
(acc, item, sectionIndex) => {
97133
const { sections = [] } = acc;
98-
const { children } = item.props;
134+
const { children, footer } = item.props;
99135
const menuItems = React.Children.toArray(
100136
children
101137
) as React.ReactElement[];
@@ -120,7 +156,16 @@ const DropdownMenu = (props: DropdownMenuProps) => {
120156
});
121157

122158
return {
123-
sections: [...sections, childrenWithKeys],
159+
sections: [
160+
...sections,
161+
<SectionWrapper
162+
footer={footer}
163+
key={`dropdown-${sectionIndex}`}
164+
sectionIndex={sectionIndex}
165+
>
166+
{childrenWithKeys}
167+
</SectionWrapper>
168+
],
124169
menuItemIndex: acc.menuItemIndex
125170
};
126171
},
@@ -156,17 +201,7 @@ const DropdownMenu = (props: DropdownMenuProps) => {
156201
{ suppressRefError: true }
157202
)}
158203
>
159-
{getDropdownContents(
160-
highlightedIndex,
161-
getItemProps
162-
).sections.map((sectionContent, i) => (
163-
<div
164-
key={`dropdown-${i}`}
165-
className={cx({ [border("top")]: i !== 0 })}
166-
>
167-
{sectionContent}
168-
</div>
169-
))}
204+
{getDropdownContents(highlightedIndex, getItemProps).sections}
170205
</PopoverBox>
171206
}
172207
disablePortal={disablePortal}

packages/dropdownMenu/components/DropdownSection.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ export interface DropdownSectionProps {
55
children:
66
| React.ReactElement<DropdownMenuItemProps>
77
| Array<React.ReactElement<DropdownMenuItemProps>>;
8+
/**
9+
* Allows one section to be a sticky footer within the dropdown
10+
*/
11+
footer?: boolean;
812
}
913

1014
const DropdownSection = ({ children }: DropdownSectionProps) => <>{children}</>;

packages/dropdownMenu/stories/Dropdown.stories.tsx

+98
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { grafanaLogo, kibanaLogo, kubernetesLogo } from "./avatarImgs";
1313
import PrimaryDropdownButton from "../../button/components/PrimaryDropdownButton";
1414
import { DropdownMenuProps } from "../components/DropdownMenu";
1515
import { Direction } from "../../dropdownable/components/Dropdownable";
16+
import { ProductIcons } from "../../icons/dist/product-icons-enum";
1617

1718
export default {
1819
title: "Overlays/DropdownMenu",
@@ -286,3 +287,100 @@ export const WithIconsAndAvatarsPositionEnd = args => (
286287
</DropdownSection>
287288
</DropdownMenu>
288289
);
290+
291+
export const WithFooterSection = args => (
292+
<DropdownMenu
293+
menuMaxHeight="50vh"
294+
trigger={<PrimaryDropdownButton>Workspaces</PrimaryDropdownButton>}
295+
{...args}
296+
>
297+
<DropdownSection>
298+
<DropdownMenuItem key="workspace1" value="workspace1">
299+
<DropdownMenuItemIcon shape={ProductIcons.Global} />
300+
Global
301+
</DropdownMenuItem>
302+
</DropdownSection>
303+
<DropdownSection>
304+
<DropdownMenuItem key="default-workspace" value="default-workspace">
305+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
306+
Default Workspace
307+
</DropdownMenuItem>
308+
<DropdownMenuItem
309+
key="management-cluster-workspace"
310+
value="management-cluster-workspace"
311+
>
312+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
313+
Management Cluster Workspace
314+
</DropdownMenuItem>
315+
<DropdownMenuItem key="workspace1" value="workspace1">
316+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
317+
Workspace 1
318+
</DropdownMenuItem>
319+
<DropdownMenuItem key="workspace2" value="workspace2">
320+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
321+
Workspace 2
322+
</DropdownMenuItem>
323+
<DropdownMenuItem key="workspace3" value="workspace3">
324+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
325+
Workspace 3
326+
</DropdownMenuItem>
327+
<DropdownMenuItem key="workspace4" value="workspace4">
328+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
329+
Workspace 4
330+
</DropdownMenuItem>
331+
<DropdownMenuItem key="workspace5" value="workspace5">
332+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
333+
Workspace 5
334+
</DropdownMenuItem>
335+
<DropdownMenuItem key="workspace6" value="workspace6">
336+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
337+
Workspace 6
338+
</DropdownMenuItem>
339+
<DropdownMenuItem key="workspace7" value="workspace7">
340+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
341+
Workspace 7
342+
</DropdownMenuItem>
343+
<DropdownMenuItem key="workspace8" value="workspace8">
344+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
345+
Workspace 8
346+
</DropdownMenuItem>
347+
<DropdownMenuItem key="workspace9" value="workspace9">
348+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
349+
Workspace 9
350+
</DropdownMenuItem>
351+
<DropdownMenuItem key="workspace10" value="workspace10">
352+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
353+
Workspace 10
354+
</DropdownMenuItem>
355+
<DropdownMenuItem key="workspace11" value="workspace11">
356+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
357+
Workspace 11
358+
</DropdownMenuItem>
359+
<DropdownMenuItem key="workspace12" value="workspace12">
360+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
361+
Workspace 12
362+
</DropdownMenuItem>
363+
<DropdownMenuItem key="workspace13" value="workspace13">
364+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
365+
Workspace 13
366+
</DropdownMenuItem>
367+
<DropdownMenuItem key="workspace14" value="workspace14">
368+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
369+
Workspace 14
370+
</DropdownMenuItem>
371+
<DropdownMenuItem key="workspace15" value="workspace15">
372+
<DropdownMenuItemIcon shape={ProductIcons.Components} />
373+
Workspace 15
374+
</DropdownMenuItem>
375+
</DropdownSection>
376+
377+
<DropdownSection footer>
378+
<DropdownMenuItem key="create-workspace" value="create-workspace">
379+
Create Workspace
380+
</DropdownMenuItem>
381+
<DropdownMenuItem key="manage-workspaces" value="manage-workspaces">
382+
Manage Workspaces
383+
</DropdownMenuItem>
384+
</DropdownSection>
385+
</DropdownMenu>
386+
);

0 commit comments

Comments
 (0)