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

[NcAppSettingsDialog] Allow to add icons to the navigation sections #4745

Merged
merged 2 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
175 changes: 138 additions & 37 deletions src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,69 @@
}
</script>
```

You can also add icons to the section navigation:

```vue
<template>
<div>
<NcButton @click="settingsOpen = true">Show Settings</NcButton>
<NcAppSettingsDialog :open.sync="settingsOpen" :show-navigation="true" name="Application settings">
<NcAppSettingsSection id="asci-name-1" name="Instagram">
<template #icon>
<Instagram :size="20" />
</template>
<p style="height: 100vh;">
Instagram setting
</p>
</NcAppSettingsSection>
<NcAppSettingsSection id="asci-name-2" name="Mastodon">
<template #icon>
<Mastodon :size="20" />
</template>
<p style="height: 100vh;">
Mastodon setting
</p>
</NcAppSettingsSection>
<NcAppSettingsSection id="asci-name-3" name="Twitch">
<template #icon>
<Twitch :size="20" />
</template>
<p style="height: 100vh;">
Twitch setting
</p>
</NcAppSettingsSection>
<NcAppSettingsSection id="asci-name-4" name="Twitter">
<template #icon>
<Twitter :size="20" />
</template>
Twitter setting
</NcAppSettingsSection>
</NcAppSettingsDialog>
</div>
</template>

<script>
import Instagram from 'vue-material-design-icons/Instagram.vue'
import Mastodon from 'vue-material-design-icons/Mastodon.vue'
import Twitch from 'vue-material-design-icons/Twitch.vue'
import Twitter from 'vue-material-design-icons/Twitter.vue'

export default {
components: {
Instagram,
Mastodon,
Twitch,
Twitter,
},
data() {
return {
settingsOpen: false,
}
},
}
</script>
```
</docs>

<template>
Expand All @@ -84,16 +147,24 @@
<ul :aria-label="settingsNavigationAriaLabel"
:class="{ 'navigation-list': true, 'navigation-list--collapsed': isCollapsed }"
role="tablist">
<li v-for="item in getNavigationItems" :key="item.id">
<a :aria-selected="item.id === selectedSection"
<li v-for="section in sections" :key="section.id">
<a :aria-selected="section.id === selectedSection"
:class="{
'navigation-list__link': true,
'navigation-list__link--active': item.id === selectedSection,
'navigation-list__link--active': section.id === selectedSection,
'navigation-list__link--icon': hasNavigationIcons,
}"
role="tab"
tabindex="0"
@click="handleSettingsNavigationClick(item.id)"
@keydown.enter="handleSettingsNavigationClick(item.id)">{{ item.name }}</a>
@click="handleSettingsNavigationClick(section.id)"
@keydown.enter="handleSettingsNavigationClick(section.id)">
<div v-if="hasNavigationIcons" class="navigation-list__link-icon">
<NcVNodes v-if="section.icon" :vnodes="section.icon" />
</div>
<span class="navigation-list__link-text">
{{ section.name }}
</span>
</a>
</li>
</ul>
</template>
Expand All @@ -105,6 +176,7 @@

<script>
import NcDialog from '../NcDialog/index.js'
import NcVNodes from '../NcVNodes/index.js'
import isMobile from '../../mixins/isMobile/index.js'
import { t } from '../../l10n.js'

Expand All @@ -116,9 +188,16 @@

components: {
NcDialog,
NcVNodes,
},

mixins: [isMobile],
provide() {
return {
registerSection: this.registerSection,
unregisterSection: this.registerSection,
}
},

props: {
/**
Expand Down Expand Up @@ -170,6 +249,11 @@
linkClicked: false,
addedScrollListener: false,
scroller: null,
/**
* Currently registered settings sections
* @type {{ id: string, name: string, icon?: VNode[] }}

Check warning on line 254 in src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue

View workflow job for this annotation

GitHub Actions / eslint

The type 'VNode' is undefined
*/
sections: [],
}
},

Expand All @@ -186,6 +270,13 @@
}
},

/**
* Check if one or more navigation entries provide icons
*/
hasNavigationIcons() {
return this.sections.some(({ icon }) => !!icon)
},

hasNavigation() {
if (this.isMobile || !this.showNavigation) {
return false
Expand All @@ -197,10 +288,6 @@
settingsNavigationAriaLabel() {
return t('Settings navigation')
},

getNavigationItems() {
return this.getSettingsNavigation(this.$slots.default)
},
},

mounted() {
Expand All @@ -224,36 +311,34 @@

methods: {
/**
* Builds the settings navigation menu
*
* @param {object} slots The default slots object passed from the render function.
* @return {Array} the navigation items
* Called when a new section is registered
* @param {string} id The section ID
* @param {string} name The section name
* @param {import('vue').VNode[]|undefined} icon Optional icon component
*/
getSettingsNavigation(slots) {
// Array of navigationitems strings
const navigationItems = slots.filter(vNode => vNode.componentOptions).map(vNode => {
return {
id: vNode.componentOptions.propsData?.id,
name: vNode.componentOptions.propsData?.name,
}
})
const navigationNames = slots.map(item => item.name)
const navigationIds = slots.map(item => item.id)

registerSection(id, name, icon) {
// Check for the uniqueness of section names
navigationItems.forEach((element, index) => {
const newNamesArray = [...navigationNames]
const newIdArray = [...navigationIds]
newNamesArray.splice(index, 1)
newIdArray.splice(index, 1)
if (newNamesArray.includes(element.name)) {
throw new Error(`Duplicate section name found: ${element}. Settings navigation sections must have unique section names.`)
}
if (newIdArray.includes(element.id)) {
throw new Error(`Duplicate section id found: ${element}. Settings navigation sections must have unique section ids.`)
}
if (this.sections.some(({ id: otherId }) => id === otherId)) {
throw new Error(`Duplicate section id found: ${id}. Settings navigation sections must have unique section ids.`)
}
if (this.sections.some(({ name: otherName }) => name === otherName)) {
throw new Error(`Duplicate section name found: ${name}. Settings navigation sections must have unique section names.`)
}

const newSections = [...this.sections, { id, name, icon }]
// Sort sections by order in slots
this.sections = newSections.sort(({ id: idA }, { id: idB }) => {
const indexOf = (id) => this.$slots.default.indexOf(vnode => vnode?.componentOptions?.propsData?.id === id)
return indexOf(idA) - indexOf(idB)
})
return navigationItems
},

/**
* Called when a new section is unregistered
* @param {string} id The section ID
*/
unregisterSection(id) {
this.sections = this.sections.filter(({ id: otherId }) => id === otherId)
},

/**
Expand Down Expand Up @@ -335,7 +420,8 @@
}

&__link {
display: block;
display: flex;
align-content: center;
font-size: 16px;
height: $clickable-area;
margin: 4px 0;
Expand All @@ -349,13 +435,28 @@
overflow: hidden;
background-color: transparent;
border: none;

&:hover,
&:focus {
background-color: var(--color-background-hover);
}

&--active {
background-color: var(--color-primary-element-light) !important;
}

&--icon {
padding-inline-start: 8px;
gap: 4px;
}

&-icon {
display: flex;
justify-content: center;
align-content: center;
width: 36px;
max-width: 36px;
}
}
}

Expand Down
21 changes: 21 additions & 0 deletions src/components/NcAppSettingsSection/NcAppSettingsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,15 @@
{{ name }}
</h3>
<slot />
<!-- @slot Optonal icon to for the secion in the navigation -->
<slot v-if="false" name="icon" />
</div>
</template>

<script>
export default {
name: 'NcAppSettingsSection',
inject: ['registerSection', 'unregisterSection'],

props: {
name: {
Expand All @@ -54,6 +57,24 @@ export default {
return 'settings-section_' + this.id
},
},
// Reactive changes for section navigation
watch: {
id(newId, oldId) {
this.unregisterSection(oldId)
this.registerSection(newId, this.name, this.$slots?.icon)
raimund-schluessler marked this conversation as resolved.
Show resolved Hide resolved
},
name(newName) {
this.unregisterSection(this.id)
this.registerSection(this.id, newName, this.$slots?.icon)
},
},
mounted() {
// register section for navigation
this.registerSection(this.id, this.name, this.$slots?.icon)
},
beforeDestroy() {
this.unregisterSection(this.id)
},
}

</script>
Expand Down