Skip to content

Commit

Permalink
Merge pull request #638 from AlexVelezLl/release-v4-prep
Browse files Browse the repository at this point in the history
Prepare release v4.3.0
  • Loading branch information
AlexVelezLl authored May 14, 2024
2 parents d48617e + c8a7bbd commit 3200a87
Show file tree
Hide file tree
Showing 7 changed files with 273 additions and 25 deletions.
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,44 @@ Changelog is rather internal in nature. See release notes for the public overvie

## Version 3.x.x (`release-v3` branch)

- [#583]
- **Description:** KDropdownMenu menu support to show context menus with `isContextMenu` prop.
- **Products impact:** new API.
- **Addresses:** https://github.com/learningequality/kolibri-design-system/issues/571, https://github.com/learningequality/studio/issues/4403.
- **Components:** KDropdownMenu.
- **Breaking:** no.
- **Impacts a11y:** no.
- **Guidance:** -.

- [#583]
- **Description:** New `useKContextMenu` private composable
- **Products impact:** - .
- **Addresses:** - .
- **Components:** - .
- **Breaking:** - .
- **Impacts a11y:** - .
- **Guidance:** -.

- [#583]
- **Description:** Expose the event object as second argument on KDropdownMenu's select event.
- **Products impact:** updated API.
- **Addresses:** - .
- **Components:** KDropdownMenu.
- **Breaking:** no.
- **Impacts a11y:** no.
- **Guidance:** -.

- [#583]
- **Description:** KDropdownMenu menu support to show a header slot.
- **Products impact:** new API.
- **Addresses:** - .
- **Components:** KDropdownMenu.
- **Breaking:** no.
- **Impacts a11y:** no.
- **Guidance:** -.

[#583]: https://github.com/learningequality/kolibri-design-system/pull/583

- [#611]
- **Description:** Adds a new `maxWidth` prop
- **Products impact:** new API
Expand Down
51 changes: 51 additions & 0 deletions docs/pages/kdropdownmenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,57 @@
<p>
Please see the <DocsInternalLink href="/buttons#dropdowns" text="Dropdown section of the Buttons and links page" /> on the buttons page for more details about how to use with a button, and a code example.
</p>

<h3>Context menu</h3>

<p>
This component can be also used to create a context menu, which is a dropdown menu that appears when a user right-clicks on an element.
</p>

<DocsShow block>
<p>
For example, you can right click on this paragraph to see a context menu.
</p>
<KDropdownMenu
isContextMenu
:options="[
{ label: 'Option 1' },
{ label: 'Option 2' },
{ label: 'Option 3' },
]"
/>
</DocsShow>

<DocsShow block>
<p>
Note that just one context menu can be open at a time. If you right-click on this paragraph, any other context menu will close.
</p>
<KDropdownMenu
isContextMenu
:options="[
{ label: 'Option 1' },
{ label: 'Option 2' },
]"
/>
</DocsShow>

<p>
To achieve this, set the <code>isContextMenu</code> prop to true.
The context menu will then be attached to the parent element.
</p>
<DocsShowCode language="html">
<div>
<p> ... </p>
<KDropdownMenu
isContextMenu
:options="[
{ label: 'Option 1' },
{ label: 'Option 2' },
{ label: 'Option 3' },
]"
/>
</div>
</DocsShowCode>
</DocsPageSection>
</DocsPageTemplate>

Expand Down
98 changes: 79 additions & 19 deletions lib/KDropdownMenu.vue
Original file line number Diff line number Diff line change
@@ -1,29 +1,40 @@
<template>

<UiPopover
ref="popover"
:z-index="99"
:containFocus="true"
:dropdownPosition="position"
:constrainToScrollParent="constrainToScrollParent"
@close="handleClose"
@open="handleOpen"
>
<UiMenu
ref="menu"
:options="options"
:hasIcons="hasIcons"
@select="handleSelection"
/>
</UiPopover>
<div>
<UiPopover
v-if="trigger"
ref="popover"
:z-index="100"
:trigger="trigger"
:containFocus="true"
:dropdownPosition="position"
:positionX="contextMenuPosition[0]"
:positionY="contextMenuPosition[1]"
:openOn="isContextMenu ? 'manual' : 'click'"
:constrainToScrollParent="constrainToScrollParent"
@close="handleClose"
@open="handleOpen"
>
<!-- Slot to set a header to the dropdown menu -->
<slot name="header"></slot>
<UiMenu
ref="menu"
:options="options"
:hasIcons="hasIcons"
@select="handleSelection"
/>
</UiPopover>
</div>

</template>


<script>
import UiPopover from './keen/UiPopover';
import { computed } from '@vue/composition-api';
import UiMenu from './keen/UiMenu';
import UiPopover from './keen/UiPopover';
import useKContextMenu from './composables/_useKContextMenu';
/**
* The KDropdownMenu component is used to contain multiple actions
Expand All @@ -34,6 +45,21 @@
UiPopover,
UiMenu,
},
setup(props) {
if (props.isContextMenu) {
const { clientX, clientY, isActive } = useKContextMenu();
const contextMenuPosition = computed(() => [clientX.value, clientY.value]);
return {
contextMenuPosition,
isContextMenuActive: isActive,
};
}
return {
contextMenuPosition: [],
isContextMenuActive: null,
};
},
props: {
/**
* The dropdown menu popover flips its position to avoid overflows within the parent. Setting it to false disables the flipping behavior.
Expand Down Expand Up @@ -79,6 +105,40 @@
].includes(val);
},
},
/**
* Whether or not the dropdown is a context menu, if true, the dropdown will open when
* the user right-clicks the parent element
*/
isContextMenu: {
type: Boolean,
default: false,
},
},
data() {
return {
trigger: null,
};
},
watch: {
isContextMenuActive() {
if (this.isContextMenuActive) {
this.$nextTick(() => {
this.$refs.popover.open();
});
} else {
this.$refs.popover.close();
}
},
contextMenuPosition() {
if (this.isContextMenuActive) {
this.$nextTick(() => {
this.$refs.popover.open();
});
}
},
},
mounted() {
this.trigger = this.$el.parentElement;
},
beforeDestroy() {
window.removeEventListener('keydown', this.handleOpenMenuNavigation, true);
Expand Down Expand Up @@ -135,11 +195,11 @@
this.closePopover();
}
},
handleSelection(selection) {
handleSelection(selection, $event) {
/**
* Emitted when an option is selected
*/
this.$emit('select', selection);
this.$emit('select', selection, $event);
this.closePopover();
},
closePopover() {
Expand Down
54 changes: 54 additions & 0 deletions lib/composables/_useKContextMenu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import './composition-api.js'; //Due to @vue/composition-api shortcomings, add plugin prior to use in kolibri, studio and tests

import {
ref,
computed,
onMounted,
onBeforeUnmount,
getCurrentInstance,
} from '@vue/composition-api';

const activeMenu = ref(null);

export default function useKContextMenu() {
const clientX = ref(0);
const clientY = ref(0);

const instance = getCurrentInstance();
const id = instance.uid;

const isActive = computed(() => activeMenu.value === id);

function showMenu(event) {
event.preventDefault();
activeMenu.value = id;
clientX.value = event.clientX;
clientY.value = event.clientY;
}

function hideMenu() {
if (activeMenu.value === id) {
activeMenu.value = null;
}
}

onMounted(() => {
const parent = instance.proxy.$el.parentElement;
parent.addEventListener('contextmenu', showMenu);
window.addEventListener('click', hideMenu);
});

onBeforeUnmount(() => {
const parent = instance.proxy.$el.parentElement;
if (parent) {
parent.removeEventListener('contextmenu', showMenu);
}
window.removeEventListener('click', hideMenu);
});

return {
clientX,
clientY,
isActive,
};
}
8 changes: 4 additions & 4 deletions lib/keen/UiMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
:target="option[keys.target]"

:type="option[keys.type]"
@click.native="selectOption(option)"
@keydown.enter.native="selectOption(option)"
@click.native="($event) => selectOption(option, $event)"
@keydown.enter.native="($event) => selectOption(option, $event)"

@keydown.esc.native.esc="closeMenu"
:style="[ activeOutline ]"
Expand Down Expand Up @@ -107,12 +107,12 @@
},
methods: {
selectOption(option) {
selectOption(option, $event) {
if (option.disabled || option.type === 'divider') {
return;
}
this.$emit('select', option);
this.$emit('select', option, $event);
this.closeMenu();
},
closeMenu() {
Expand Down
47 changes: 46 additions & 1 deletion lib/keen/UiPopover.vue
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@
},
},
zIndex: Number,
positionX: Number,
positionY: Number,
},
data() {
Expand Down Expand Up @@ -188,10 +190,53 @@
* @public
*/
open() {
if (this.tip) {
if (!this.tip) {
return;
}
if (this.positionX || this.positionY) {
this.openInPosition(this.positionX, this.positionY);
} else {
this.tip.show();
}
},
openInPosition(x, y) {
if (!this.tip.popperInstance) {
this.tip.show(); // Ensure popperInstance is created
}
// This logic is similar to what tippy.js uses to position the popup near the cursor.
// see https://github.com/atomiks/tippyjs/blob/v4.3.5/src/createTippy.ts#L395
const isVerticalPlacement = ['top', 'bottom'].includes(this.position.split('-')[0]);
const variation = this.position.split('-')[1];
const size = isVerticalPlacement ? this.$el.clientWidth : this.$el.clientHeight;
const middle = size / 2;
const positionVariationMap = {
start: 0,
end: size,
};
let verticalIncrease;
let horizontalIncrease;
if (isVerticalPlacement) {
verticalIncrease = 0;
horizontalIncrease = variation ? positionVariationMap[variation] : middle;
} else {
verticalIncrease = variation ? positionVariationMap[variation] : middle;
horizontalIncrease = 0;
}
this.tip.popperInstance.reference.getBoundingClientRect = () => {
return {
width: isVerticalPlacement ? size : 0,
height: isVerticalPlacement ? 0 : size,
top: y - verticalIncrease,
bottom: y + verticalIncrease,
left: x - horizontalIncrease,
right: x + horizontalIncrease
};
};
this.tip.show();
},
close(options = { returnFocus: true }) {
if (this.tip) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "kolibri-design-system",
"version": "4.2.1",
"version": "4.3.0",
"private": false,
"description": "The Kolibri Design System defines common design patterns and code for use in Kolibri applications",
"license": "MIT",
Expand Down

0 comments on commit 3200a87

Please sign in to comment.