Skip to content

Commit

Permalink
feat: add context-menu (#7350)
Browse files Browse the repository at this point in the history
* chore(ContextMenu): scaffold new component

* feat(context-menu): build basic structure and styling

* feat(context-menu): build basic nesting support

* feat(context-menu): use button markup

* feat(context-menu): add support for disabled options

* feat(context-menu): add support fort shortcuts

* feat(context-menu): add support for icons

* feat(context-menu): add divider component

* feat(context-menu): add support for selectable option

* fix(context-menu): also indent options when sibling is selectable

* feat(context-menu): add support for radio groups

* fix(context-menu): add support for initialSelectedItem in radio groups

* fix(context-menu): also indent options when sibling is radio

* feat(context-menu): add support for keyboard navigation

* feat(context-menu): dynamically reverse menu direction

* feat(context-menu): add props.onClose

* chore(context-menu): add inline notification to story

* fix(context-menu): fix styling issues

* refactor(context-menu): rename SelectableContextMenuOption to ContextMenuSelectableOption

* fix(context-menu): don't overwrite checkbox or radio role

* fix(context-menu): automatically close menu after item was clicked

* fix(context-menu): improve behaviour with screenreaders

* fix(context-menu): add onChange to radio group and checkbox

* test(context-menu): update public-api and index snapshots

* chore(context-menu): correct formatting

* test(context-menu): add render tests

* chore: run prettier

* fix(context-menu): focus entire menu on open

* fix(context-menu): only open submenu on long hover or right arrow

* feat(context-menu): always keep menu within body boundaries

* fix(context-menu): implement review feedback

* fix(context-menu): don't close when disabled or divider was clicked

* fix(context-menu): prevent menu from closing immediately in Safari

* refactor(context-menu): separate between external and internal component

* refactor(context-menu): mark as unstable

* feat(context-menu): export useContextMenu hook

* test(context-menu): replace Option component with Item component

* test: update react index snapshot

* fix(context-menu): open submenus on enter

* fix(context-menu): fix lint issue

* fix(context-menu): use menuitem* roles for checkbox and radio

* fix(context-menu): remove aria-label from menuitems

* refactor(context-menu): remove button markup

* test(context-menu): update test to reflect markup changes

* fix(context-menu): use ul without role for radio groups

* fix(context-menu): fix aria-haspopup attribute on parent node

* fix(context-menu): add aria-expanded attribute on items with children

* fix(context-menu): add "group" role to radio groups

* fix(context-menu): extract radio items from group when top level

* chore: add story with multiple radio groups

* feat(context-menu): add ContextMenuGroup component

* fix(context-menu): support "space" to activate items

* chore: add context menu story to experimental group

* fix(context-menu): prevents error where user must click twice to open

Co-authored-by: Andrea N. Cardona <andreancardona@gmail.com>
Co-authored-by: TJ Egan <tw15egan@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
  • Loading branch information
4 people committed Mar 10, 2021
1 parent e70264b commit 59bfae0
Show file tree
Hide file tree
Showing 19 changed files with 1,611 additions and 0 deletions.
126 changes: 126 additions & 0 deletions packages/components/src/components/context-menu/_context-menu.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
//
// Copyright IBM Corp. 2020
//
// This source code is licensed under the Apache-2.0 license found in the
// LICENSE file in the root directory of this source tree.
//

@import '../../globals/scss/vars';
@import '../../globals/scss/vendor/@carbon/elements/scss/import-once/import-once';
@import '../../globals/scss/helper-mixins';

/// Context Menu styles
/// @access private
/// @group context-menu
@mixin context-menu {
.#{$prefix}--context-menu {
@include box-shadow;

position: fixed;
z-index: z('modal');
min-width: 13rem;
max-width: 18rem;
padding: $spacing-02 0;
background-color: $ui-01;
visibility: hidden;
}

.#{$prefix}--context-menu--open {
visibility: visible;

&:focus {
@include focus-outline('border');
}
}

.#{$prefix}--context-menu--invisible {
opacity: 0;
}

.#{$prefix}--context-menu-option {
position: relative;
height: $spacing-07;
background-color: $ui-01;
cursor: pointer;
transition: background-color $duration--fast-01 motion(standard, productive);

&:focus {
@include focus-outline('outline');
}
}

.#{$prefix}--context-menu-option--active,
.#{$prefix}--context-menu-option:hover {
background-color: $hover-ui;
}

.#{$prefix}--context-menu-option > .#{$prefix}--context-menu {
margin-top: calc(#{$spacing-02} * -1);
}

.#{$prefix}--context-menu-option__content {
display: flex;
align-items: center;
justify-content: space-between;
height: 100%;
padding: 0 $spacing-05;
}

.#{$prefix}--context-menu-option__content--disabled {
background-color: $ui-01;
cursor: not-allowed;
}

.#{$prefix}--context-menu-option__content--disabled
.#{$prefix}--context-menu-option__label,
.#{$prefix}--context-menu-option__content--disabled
.#{$prefix}--context-menu-option__info,
.#{$prefix}--context-menu-option__content--disabled
.#{$prefix}--context-menu-option__icon {
color: $disabled-02;
}

.#{$prefix}--context-menu-option__content--indented
.#{$prefix}--context-menu-option__label {
margin-left: $spacing-05;
}

.#{$prefix}--context-menu-option__label {
@include type-style('body-short-01');

flex-grow: 1;
// add top/bottom padding to make sure letters are not cut off by hidden overflow
padding: $spacing-02 0;
overflow: hidden;
color: $text-01;
white-space: nowrap;
text-align: start;
text-overflow: ellipsis;
}

.#{$prefix}--context-menu-option__info {
display: inline-flex;
margin-left: $spacing-05;
color: $icon-01;
}

.#{$prefix}--context-menu-option__icon {
display: flex;
align-items: center;
width: 1rem;
height: 1rem;
margin-right: $spacing-03;
color: $icon-01;
}

.#{$prefix}--context-menu-divider {
width: 100%;
height: 1px;
margin: $spacing-02 0;
background-color: $ui-03;
}
}

@include exports('context-menu') {
@include context-menu;
}
1 change: 1 addition & 0 deletions packages/components/src/globals/scss/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ $deprecations--message: 'Deprecated code was found, this code will be removed be
@import '../../components/code-snippet/code-snippet';
@import '../../components/overflow-menu/overflow-menu';
@import '../../components/content-switcher/content-switcher';
@import '../../components/context-menu/context-menu';
@import '../../components/date-picker/date-picker';
@import '../../components/dropdown/dropdown';
@import '../../components/loading/loading';
Expand Down
1 change: 1 addition & 0 deletions packages/react/.storybook/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ $prefix: 'bx';
@import '~carbon-components/src/components/code-snippet/code-snippet';
@import '~carbon-components/src/components/overflow-menu/overflow-menu';
@import '~carbon-components/src/components/content-switcher/content-switcher';
@import '~carbon-components/src/components/context-menu/context-menu';
@import '~carbon-components/src/components/date-picker/date-picker';
@import '~carbon-components/src/components/dropdown/dropdown';
@import '~carbon-components/src/components/loading/loading';
Expand Down
155 changes: 155 additions & 0 deletions packages/react/__tests__/__snapshots__/PublicAPI-test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -7608,6 +7608,161 @@ Map {
},
},
},
"unstable_ContextMenu" => Object {
"ContextMenuDivider": Object {},
"ContextMenuGroup": Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"label": Object {
"isRequired": true,
"type": "node",
},
},
},
"ContextMenuItem": Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"disabled": Object {
"type": "bool",
},
"label": Object {
"isRequired": true,
"type": "node",
},
"shortcut": Object {
"type": "node",
},
},
},
"ContextMenuRadioGroup": Object {
"propTypes": Object {
"initialSelectedItem": Object {
"type": "string",
},
"items": Object {
"args": Array [
Object {
"type": "string",
},
],
"isRequired": true,
"type": "arrayOf",
},
"label": Object {
"isRequired": true,
"type": "string",
},
"onChange": Object {
"type": "func",
},
},
},
"ContextMenuSelectableItem": Object {
"propTypes": Object {
"initialChecked": Object {
"type": "bool",
},
"label": Object {
"isRequired": true,
"type": "node",
},
"onChange": Object {
"type": "func",
},
},
},
"propTypes": Object {
"children": Object {
"type": "node",
},
"level": Object {
"type": "number",
},
"onClose": Object {
"type": "func",
},
"open": Object {
"type": "bool",
},
"x": Object {
"type": "number",
},
"y": Object {
"type": "number",
},
},
},
"unstable_ContextMenuDivider" => Object {},
"unstable_ContextMenuGroup" => Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"label": Object {
"isRequired": true,
"type": "node",
},
},
},
"unstable_ContextMenuItem" => Object {
"propTypes": Object {
"children": Object {
"type": "node",
},
"disabled": Object {
"type": "bool",
},
"label": Object {
"isRequired": true,
"type": "node",
},
"shortcut": Object {
"type": "node",
},
},
},
"unstable_ContextMenuRadioGroup" => Object {
"propTypes": Object {
"initialSelectedItem": Object {
"type": "string",
},
"items": Object {
"args": Array [
Object {
"type": "string",
},
],
"isRequired": true,
"type": "arrayOf",
},
"label": Object {
"isRequired": true,
"type": "string",
},
"onChange": Object {
"type": "func",
},
},
},
"unstable_ContextMenuSelectableItem" => Object {
"propTypes": Object {
"initialChecked": Object {
"type": "bool",
},
"label": Object {
"isRequired": true,
"type": "node",
},
"onChange": Object {
"type": "func",
},
},
},
"unstable_useContextMenu" => Object {},
"unstable_Heading" => Object {
"propTypes": Object {
"children": Object {
Expand Down
7 changes: 7 additions & 0 deletions packages/react/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,19 @@ describe('Carbon Components React', () => {
"TooltipDefinition",
"TooltipIcon",
"UnorderedList",
"unstable_ContextMenu",
"unstable_ContextMenuDivider",
"unstable_ContextMenuGroup",
"unstable_ContextMenuItem",
"unstable_ContextMenuRadioGroup",
"unstable_ContextMenuSelectableItem",
"unstable_Heading",
"unstable_PageSelector",
"unstable_Pagination",
"unstable_Section",
"unstable_TreeNode",
"unstable_TreeView",
"unstable_useContextMenu",
]
`);
});
Expand Down
Loading

0 comments on commit 59bfae0

Please sign in to comment.