Skip to content

Commit fc14539

Browse files
committed
feat(sidebar-tabs): Redesign active tab as rounded filled pill
The active sidebar tab now renders as a fully-rounded filled pill with an inset, rounded blue indicator that grows into place when selected, replacing the previous full-width bottom border and bold label treatment. Tabs keep a small gap between each other to eliminate the hover radius artifact, and the tab icon slot now receives the active state so consumers can render filled/outlined icon variants. Signed-off-by: nfebe <fenn25.fn@gmail.com>
1 parent d66bbf6 commit fc14539

5 files changed

Lines changed: 91 additions & 20 deletions

File tree

src/components/NcAppSidebar/NcAppSidebar.vue

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,10 @@ Using `v-show` directly will result in usability issues due to internal focus tr
4141
Search tab content
4242
</NcAppSidebarTab>
4343
<NcAppSidebarTab name="Settings" id="settings-tab">
44-
<template #icon>
45-
<IconCogOutline :size="20" />
44+
<!-- The `selected` slot prop lets you swap to a filled icon variant for the active tab. -->
45+
<template #icon="{ selected }">
46+
<IconCog v-if="selected" :size="20" />
47+
<IconCogOutline v-else :size="20" />
4648
</template>
4749
Settings tab content
4850
</NcAppSidebarTab>
@@ -58,12 +60,14 @@ Using `v-show` directly will result in usability issues due to internal focus tr
5860

5961
<script>
6062
import IconMagnify from 'vue-material-design-icons/Magnify.vue'
63+
import IconCog from 'vue-material-design-icons/Cog.vue'
6164
import IconCogOutline from 'vue-material-design-icons/CogOutline.vue'
6265
import IconShareVariantOutline from 'vue-material-design-icons/ShareVariantOutline.vue'
6366

6467
export default {
6568
components: {
6669
IconMagnify,
70+
IconCog,
6771
IconCogOutline,
6872
IconShareVariantOutline,
6973
},

src/components/NcAppSidebar/NcAppSidebarTabs.vue

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
v-if="hasMultipleTabs || showForSingleTab"
1414
role="tablist"
1515
class="app-sidebar-tabs__nav"
16+
:class="{ 'app-sidebar-tabs__nav--legacy': isLegacy34 }"
1617
@keydown.left.exact.prevent.stop="focusPreviousTab"
1718
@keydown.right.exact.prevent.stop="focusNextTab"
1819
@keydown.tab.exact.prevent.stop="focusActiveTabContent"
@@ -44,6 +45,7 @@
4445
<script>
4546
import { getCanonicalLocale } from '@nextcloud/l10n'
4647
import NcAppSidebarTabsButton from './NcAppSidebarTabsButton.vue'
48+
import { isLegacy34 } from '../../utils/legacy.ts'
4749
4850
export default {
4951
name: 'NcAppSidebarTabs',
@@ -93,6 +95,7 @@ export default {
9395
* Local active (open) tab's ID. It allows to use component without v-model:active
9496
*/
9597
activeTab: props.active,
98+
isLegacy34,
9699
}
97100
},
98101
@@ -255,6 +258,11 @@ export default {
255258
justify-content: stretch;
256259
margin: 10px 8px 0 8px;
257260
border-bottom: 1px solid var(--color-border);
261+
262+
&:not(&--legacy) {
263+
gap: var(--default-grid-baseline);
264+
padding-block-end: var(--default-grid-baseline);
265+
}
258266
}
259267
260268
&__tab {

src/components/NcAppSidebar/NcAppSidebarTabsButton.vue

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import type NcAppSidebarTab from '../NcAppSidebarTab/NcAppSidebarTab.vue'
88
99
import NcVNodes from '../NcVNodes/NcVNodes.vue'
10+
import { isLegacy34 } from '../../utils/legacy.ts'
1011
1112
const selected = defineModel<boolean>('selected', { required: true })
1213
@@ -23,13 +24,14 @@ defineProps<{
2324
class="button-vue"
2425
:class="[$style.sidebarTabsButton, {
2526
[$style.sidebarTabsButton_selected]: selected,
27+
[$style.sidebarTabsButton_legacy]: isLegacy34,
2628
}]"
2729
role="tab"
2830
:aria-selected="selected"
2931
:tabindex="selected ? 0 : -1"
3032
@click="selected = true">
3133
<span :class="$style.sidebarTabsButton__icon">
32-
<NcVNodes :vnodes="tab.renderIcon()">
34+
<NcVNodes :vnodes="tab.renderIcon(selected)">
3335
<span :class="[$style.sidebarTabsButton__legacyIcon, tab.icon]" />
3436
</NcVNodes>
3537
</span>
@@ -42,20 +44,62 @@ defineProps<{
4244
<style module lang="scss">
4345
.sidebarTabsButton {
4446
border: none;
45-
border-bottom: var(--default-grid-baseline) solid transparent !important;
46-
border-radius: var(--border-radius-small);
47-
background-color: var(--color-main-background);
4847
color: var(--color-main-text);
4948
font-size: var(--default-font-size);
5049
cursor: pointer;
5150
display: flex;
5251
flex-direction: column;
5352
gap: var(--default-grid-baseline);
53+
min-width: var(--default-clickable-area);
54+
55+
* {
56+
cursor: pointer;
57+
}
58+
}
59+
60+
// New design (NC34+): rounded pill with a small primary indicator under the
61+
// active tab.
62+
.sidebarTabsButton:not(.sidebarTabsButton_legacy) {
63+
position: relative;
64+
border-radius: var(--border-radius-element);
65+
background-color: var(--color-main-background);
66+
padding: var(--default-grid-baseline);
67+
padding-block-end: calc(var(--default-grid-baseline) * 2);
68+
transition: background-color var(--animation-quick);
69+
70+
&::after {
71+
content: '';
72+
position: absolute;
73+
bottom: 0;
74+
left: 50%;
75+
width: 0;
76+
height: 6px;
77+
border-radius: 999px;
78+
background-color: var(--color-primary-element);
79+
opacity: 0;
80+
transform: translateX(-50%);
81+
transition: width var(--animation-quick), opacity var(--animation-quick);
82+
}
83+
84+
&:hover {
85+
background-color: var(--color-background-hover);
86+
}
87+
88+
&:focus-visible {
89+
outline: 2px solid var(--color-main-text);
90+
outline-offset: 2px;
91+
}
92+
}
93+
94+
// Legacy design (NC < 34): full-width primary border under the active tab.
95+
.sidebarTabsButton_legacy {
96+
border-bottom: var(--default-grid-baseline) solid transparent !important;
97+
border-radius: var(--border-radius-small);
98+
background-color: var(--color-main-background);
5499
padding: var(--border-radius-small);
55100
transition:
56101
background-color var(--animation-quick),
57102
border-bottom-color var(--animation-quick);
58-
min-width: var(--default-clickable-area);
59103
60104
&:hover {
61105
background-color: var(--color-background-hover) !important;
@@ -65,26 +109,38 @@ defineProps<{
65109
&:focus {
66110
background-color: var(--color-main-background) !important;
67111
}
112+
}
113+
114+
.sidebarTabsButton_selected {
115+
cursor: default;
68116
69117
* {
70-
cursor: pointer;
118+
cursor: default;
71119
}
72120
}
73121
74-
.sidebarTabsButton_selected {
122+
.sidebarTabsButton:not(.sidebarTabsButton_legacy).sidebarTabsButton_selected {
123+
background-color: var(--color-background-hover);
124+
125+
&::after {
126+
width: 80%;
127+
opacity: 1;
128+
}
129+
130+
&:hover {
131+
background-color: var(--color-background-dark);
132+
}
133+
}
134+
135+
.sidebarTabsButton_legacy.sidebarTabsButton_selected {
75136
border-bottom-color: var(--color-primary-element) !important;
76-
border-bottom-left-radius: 0px;
77-
border-bottom-right-radius: 0px;
78-
cursor: default;
137+
border-bottom-left-radius: 0;
138+
border-bottom-right-radius: 0;
79139
80140
&:hover {
81141
background-color: var(--color-primary-element-light-hover) !important;
82142
color: var(--color-primary-element-light-text) !important;
83143
}
84-
85-
* {
86-
cursor: default;
87-
}
88144
}
89145
90146
.sidebarTabsButton__name {
@@ -94,7 +150,7 @@ defineProps<{
94150
text-wrap: nowrap;
95151
}
96152
97-
.sidebarTabsButton_selected .sidebarTabsButton__name {
153+
.sidebarTabsButton_legacy.sidebarTabsButton_selected .sidebarTabsButton__name {
98154
font-weight: var(--font-weight-element, bold);
99155
}
100156

src/components/NcAppSidebarTab/NcAppSidebarTab.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,10 +114,11 @@ export default {
114114
/**
115115
* Render tab's icon slot if any
116116
*
117+
* @param {boolean} selected Whether the tab is the active one
117118
* @return {import('vue').VNode[]}
118119
*/
119-
renderIcon() {
120-
return this.$slots.icon?.()
120+
renderIcon(selected = false) {
121+
return this.$slots.icon?.({ selected })
121122
},
122123
},
123124
}

src/utils/legacy.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@
44
*/
55

66
const [majorVersion] = window.OC?.config?.version?.split('.') ?? []
7-
export const isLegacy = Number.parseInt(majorVersion ?? '32') < 32
7+
const major = Number.parseInt(majorVersion ?? '32')
8+
export const isLegacy = major < 32
9+
export const isLegacy34 = major < 34

0 commit comments

Comments
 (0)