Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Points popover and overflow #9616

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
a3c28d0
Update KDropdownMenu usage to the latest KDS updates
MisRob Sep 14, 2022
eb7bbc7
Update to temporary KDS reference
marcellamaki Jun 8, 2022
7d9f3c6
Add points popover display with KIconButton; new icon TBD
marcellamaki Jun 29, 2022
b18b56e
Add function to generate an array of the number of NavLinks that will…
marcellamaki Jul 1, 2022
4ea1c0d
DeviceIndex dynamic tab display working (no menu display)
marcellamaki Jul 26, 2022
9e5a73e
Update DeviceTopNav to have overflow dropdown menu (requires supporti…
marcellamaki Jul 26, 2022
e7073f8
Code cleanup on DevicePage new navigation (working, requires KDS upda…
marcellamaki Jul 29, 2022
d1956c3
Add resuable overflow menu component to core app API spc
marcellamaki Aug 2, 2022
272653e
Refactoring all top navs with reusable menu component and code cleanup
marcellamaki Aug 2, 2022
1930723
Code cleanup; remove commented out code and lingering rebase leftovers
marcellamaki Aug 10, 2022
6abe722
Tests and cleanup
marcellamaki Aug 15, 2022
ae1953b
Add aria label to point button
marcellamaki Aug 16, 2022
09af39b
Add aria label to overflow button
marcellamaki Aug 16, 2022
c4d0f41
update dependency conflicts
marcellamaki Aug 18, 2022
1a84883
Ensure classId exists before creating route, which requires classId v…
marcellamaki Aug 22, 2022
5735194
Update color and opacity to meet WCAG AA contrast standard 4.5:1
marcellamaki Aug 23, 2022
4862c53
Ensure points popover closes by main window click, escape key, or tab…
marcellamaki Aug 23, 2022
cd31723
Add new tooltipPosition prop (extension, non-breaking) to KIconButton…
marcellamaki Aug 23, 2022
547f7ca
Point to specific KDS working branch commit for testing of correspond…
marcellamaki Aug 23, 2022
876051d
Code cleanup, small feedback implementation
marcellamaki Aug 31, 2022
5b20d61
Use refs rather than class to identify component
marcellamaki Aug 31, 2022
c98cf05
Fix media query to responsiveWindowMixin
marcellamaki Sep 2, 2022
43c39f5
Slight refactor to ensure visibility/hidden links are being checked b…
marcellamaki Sep 20, 2022
93acf3a
Linting after rebase
marcellamaki Sep 22, 2022
ed8df00
Update tests
marcellamaki Sep 22, 2022
9d99aca
fix tests, fix console error with stray aria label
marcellamaki Sep 22, 2022
5b11d34
Clean up links to be computed props across all files
marcellamaki Sep 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions kolibri/core/assets/src/core-app/apiSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import SidePanelModal from '../views/SidePanelModal';
import SideNav from '../views/SideNav';
import Navbar from '../views/Navbar';
import NavbarLink from '../views/Navbar/NavbarLink';
import HorizontalNavBarWithOverflowMenu from '../views/HorizontalNavBarWithOverflowMenu';
import CoreLogo from '../views/CoreLogo';
import LanguageSwitcherList from '../views/language-switcher/LanguageSwitcherList';
import LanguageSwitcherModal from '../views/language-switcher/LanguageSwitcherModal';
Expand Down Expand Up @@ -163,6 +164,7 @@ export default {
SideNav,
Navbar,
NavbarLink,
HorizontalNavBarWithOverflowMenu,
LanguageSwitcherModal,
LanguageSwitcherList,
ElapsedTime,
Expand Down
83 changes: 75 additions & 8 deletions kolibri/core/assets/src/views/AppBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -38,21 +38,40 @@
</template>

<template #actions>
<div>
<div aria-live="polite">
<slot name="app-bar-actions"></slot>
<div class="total-points">
<slot name="totalPointsMenuItem"></slot>
</div>
<span v-if="isLearner">
<KIconButton
ref="pointsButton"
icon="pointsActive"
:ariaLabel="$tr('pointsAriaLabel')"
/>
<div
v-if="pointsDisplayed"
class="points-popover"
:style="{
color: $themeTokens.text,
padding: '8px',
backgroundColor: $themeTokens.surface,
}"
>
{{ $tr('pointsMessage', { points: totalPoints }) }}
</div>
</span>
<span v-if="isUserLoggedIn" tabindex="-1">
<KIcon
icon="person"
:style="{
fill: $themeTokens.textInverted,
height: '20px',
width: '20px',
height: '24px',
width: '24px',
margin: '4px',
top: '8px',
}"
/>
<span class="username">{{ usernameForDisplay }}</span>
<span class="username">
{{ usernameForDisplay }}
</span>
</span>

</div>
Expand Down Expand Up @@ -101,6 +120,7 @@
},
data() {
return {
pointsDisplayed: false,
userSyncStatus: null,
isPolling: false,
// poll every 10 seconds
Expand All @@ -109,7 +129,7 @@
};
},
computed: {
...mapGetters(['isUserLoggedIn']),
...mapGetters(['isUserLoggedIn', 'totalPoints', 'isLearner']),
...mapState({
username: state => state.core.session.username,
fullName: state => state.core.session.full_name,
Expand All @@ -122,9 +142,11 @@
},
created() {
window.addEventListener('click', this.handleWindowClick);
window.addEventListener('keydown', this.handlePopoverByKeyboard, true);
},
beforeDestroy() {
window.removeEventListener('click', this.handleWindowClick);
window.removeEventListener('keydown', this.handlePopoverByKeyboard, true);
this.isPolling = false;
},
methods: {
Expand All @@ -142,6 +164,25 @@
}, this.pollingInterval);
}
},
handleWindowClick(event) {
if (this.$refs.pointsButton && this.$refs.pointsButton.$el) {
if (!this.$refs.pointsButton.$el.contains(event.target) && this.pointsDisplayed) {
this.pointsDisplayed = false;
} else if (
this.$refs.pointsButton &&
this.$refs.pointsButton.$el &&
this.$refs.pointsButton.$el.contains(event.target)
) {
this.pointsDisplayed = !this.pointsDisplayed;
}
}
return event;
},
handlePopoverByKeyboard(event) {
if ((event.key == 'Tab' || event.key == 'Escape') && this.pointsDisplayed) {
this.pointsDisplayed = false;
}
},
setPollingInterval(status) {
if (status === SyncStatus.QUEUED) {
// check more frequently for updates if the user is waiting to sync,
Expand All @@ -158,6 +199,15 @@
context:
"This message is providing additional context to the screen-reader users, but is not visible in the Kolibri UI.\n\nIn this case the screen-reader will announce the message when user navigates to the 'hamburger' button with the keyboard, to indicate that it allows them to open the sidebar navigation menu.",
},
pointsMessage: {
message: 'You earned { points, number } points',
context: 'Notification indicating how many points a leaner has earned.',
},
pointsAriaLabel: {
message: 'Points earned',
context:
'Information for screen reader users about what information they will get by clicking a button',
},
},
};

Expand All @@ -174,6 +224,8 @@
}

.username {
position: relative;
bottom: 3px;
max-width: 200px;
// overflow-x hidden seems to affect overflow-y also, so include a fixed height
height: 16px;
Expand Down Expand Up @@ -239,6 +291,11 @@
align-items: center;
}

/deep/ .ui-toolbar__right {
display: flex;
align-items: center;
}

.brand-logo {
max-width: 48px;
max-height: 48px;
Expand All @@ -251,4 +308,14 @@
display: none;
}

.points-popover {
@extend %dropshadow-4dp;

position: absolute;
right: 50px;
z-index: 24;
font-size: 12px;
border-radius: 8px;
}

</style>
18 changes: 13 additions & 5 deletions kolibri/core/assets/src/views/ContentRenderer/DownloadButton.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
<template>

<KDropdownMenu
:text="$tr('downloadContent')"
:options="fileOptions"
@select="download"
/>
<KButton
ref="button"
hasDropdown
:primary="$attrs.primary"
>
<span>{{ $tr('downloadContent') }}</span>
<template #menu>
<KDropdownMenu
:options="fileOptions"
@select="download"
/>
</template>
</KButton>

</template>

Expand Down
146 changes: 146 additions & 0 deletions kolibri/core/assets/src/views/HorizontalNavBarWithOverflowMenu.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
<template>

<Navbar>
<div v-for="(link, index) in navigationLinks" :key="index" ref="navLinks">
<!-- seeting navbarlink to "not hidden" is a bit counter intutive, -->
<!-- but it allows us to only sent the "hidden" value when needed -->
<!-- rather than for every link -->
<NavbarLink
v-if="!link.isHidden"
:title="link.title"
:link="link.link"
>
<KIcon
:icon="link.icon"
:color="link.color"
/>
</NavbarLink>
</div>
<span v-if="overflowMenuLinks && overflowMenuLinks.length > 0">
<KIconButton
class="menu-icon"
:tooltip="coreString('moreOptions')"
tooltipPosition="top"
:ariaLabel="coreString('moreOptions')"
icon="optionsHorizontal"
appearance="flat-button"
:color="color"
:primary="false"
>
<template #menu>
<KDropdownMenu
:primary="false"
:disabled="false"
:hasIcons="true"
:options="overflowMenuLinks"
@select="handleSelect"
/>
</template>
</KIconButton>
</span>
</Navbar>

</template>


<script>

import Navbar from 'kolibri.coreVue.components.Navbar';
import NavbarLink from 'kolibri.coreVue.components.NavbarLink';
import commonCoreStrings from 'kolibri.coreVue.mixins.commonCoreStrings';
import responsiveWindowMixin from 'kolibri.coreVue.mixins.responsiveWindowMixin';
import debounce from 'lodash/debounce';

export default {
name: 'HorizontalNavBarWithOverflowMenu',
components: {
Navbar,
NavbarLink,
},
mixins: [commonCoreStrings, responsiveWindowMixin],
props: {
/**
* An array of options objects, with one object per dropdown item
*/
navigationLinks: {
type: Array,
default: () => [],
required: true,
validator(values) {
return values.every(value => value.link.name);
},
},
},
data() {
return {
numberOfNavigationTabsToDisplay: 0,
overflowMenuLinks: [],
};
},
computed: {
color() {
return this.$themeTokens.textInverted;
},
},
mounted() {
this.updateNavigationTabDisplay();
window.addEventListener('resize', this.updateNavigationTabDisplay);
},
beforeDestroy() {
document.removeEventListener('resize', this.debouncedUpdateNavigation);
},
methods: {
debouncedUpdateNavigation() {
return debounce(this.updateNavigationTabDisplay, 1000);
},
handleSelect(option) {
this.$router.push(this.$router.getRoute(option.value.name));
marcellamaki marked this conversation as resolved.
Show resolved Hide resolved
},
generateOverflowMenu() {
const limitedList = this.navigationLinks.slice(
this.numberOfNavigationTabsToDisplay,
this.navigationLinks.length
);
let options = [];
limitedList.forEach(o => options.push({ label: o.title, value: o.link, icon: o.icon }));
this.overflowMenuLinks = options;
},
updateNavigationTabDisplay() {
if (this.$refs.navLinks) {
// to get the list item, rather than the wrapping <div>
const navItems = this.$refs.navLinks.map(item => item.firstElementChild);
let index = 0;
let viewportWidthTakenUp = 0;
let numberOfTabLinks = 0;
if (navItems && navItems.length > 0) {
while (index < navItems.length) {
viewportWidthTakenUp = viewportWidthTakenUp + navItems[index].offsetWidth;
if (viewportWidthTakenUp < window.innerWidth - 60) {
navItems[index].classList.add('visible');
numberOfTabLinks = index + 1;
} else {
navItems[index].classList.remove('visible');
}
index = index + 1;
}
}
this.numberOfNavigationTabsToDisplay = numberOfTabLinks;
this.generateOverflowMenu();
}
},
},
};

</script>


<style lang="scss" scoped>

@import '~kolibri-design-system/lib/styles/definitions';

.menu-icon {
position: absolute;
right: 4px;
}

</style>
Loading