Skip to content

Commit

Permalink
Merge pull request #4745 from nextcloud-libraries/feat/app-settings-i…
Browse files Browse the repository at this point in the history
…cons

[NcAppSettingsDialog] Allow to add icons to the navigation sections
  • Loading branch information
susnux committed Nov 7, 2023
2 parents 7795cfd + 36bbbf1 commit 9fb90b7
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 37 deletions.
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 @@ export default {
}
</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 @@ export default {
<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 @@ export default {

<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 @@ export default {
components: {
NcDialog,
NcVNodes,
},
mixins: [isMobile],
provide() {
return {
registerSection: this.registerSection,
unregisterSection: this.registerSection,
}
},
props: {
/**
Expand Down Expand Up @@ -170,6 +249,11 @@ export default {
linkClicked: false,
addedScrollListener: false,
scroller: null,
/**
* Currently registered settings sections
* @type {{ id: string, name: string, icon?: VNode[] }}
*/
sections: [],
}
},
Expand All @@ -186,6 +270,13 @@ export default {
}
},
/**
* 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 @@ export default {
settingsNavigationAriaLabel() {
return t('Settings navigation')
},
getNavigationItems() {
return this.getSettingsNavigation(this.$slots.default)
},
},
mounted() {
Expand All @@ -224,36 +311,34 @@ export default {
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 @@ export default {
}
&__link {
display: block;
display: flex;
align-content: center;
font-size: 16px;
height: $clickable-area;
margin: 4px 0;
Expand All @@ -349,13 +435,28 @@ export default {
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)
},
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

0 comments on commit 9fb90b7

Please sign in to comment.