Skip to content

Commit 6371244

Browse files
authored
Merge pull request #8477 from nextcloud-libraries/feat/align-settings-title-with-sidebar
feat(NcAppSettingsDialog): Align title with navigation column
2 parents 4aaeb2e + fa8a678 commit 6371244

2 files changed

Lines changed: 227 additions & 42 deletions

File tree

src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue

Lines changed: 196 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import NcVNodes from '../NcVNodes/NcVNodes.vue'
1313
import NcAppSettingsDialogVersion from './NcAppSettingsDialogVersion.vue'
1414
import { useIsMobile } from '../../composables/useIsMobile/index.ts'
1515
import { t } from '../../l10n.ts'
16+
import { isLegacy34 } from '../../utils/legacy.ts'
1617
import { APP_SETTINGS_LEGACY_DESIGN_KEY, APP_SETTINGS_REGISTRATION_KEY } from './useAppSettingsDialog.ts'
1718
1819
export interface IAppSettingsSection {
@@ -215,6 +216,7 @@ function unregisterSection(id: string) {
215216
<NcDialog
216217
v-if="open"
217218
class="app-settings"
219+
:class="{ 'app-settings--legacy': isLegacy34 }"
218220
contentClasses="app-settings__content"
219221
navigationClasses="app-settings__navigation"
220222
:additionalTrapElements
@@ -225,6 +227,9 @@ function unregisterSection(id: string) {
225227
:name
226228
@update:open="handleCloseModal">
227229
<template v-if="hasNavigation" #navigation="{ isCollapsed }">
230+
<div v-if="!isLegacy34" class="app-settings__title">
231+
{{ name }}
232+
</div>
228233
<ul
229234
v-if="!isCollapsed"
230235
class="navigation-list">
@@ -258,68 +263,217 @@ function unregisterSection(id: string) {
258263
</template>
259264

260265
<style lang="scss" scoped>
261-
.app-settings {
266+
$navigation-width: 200px;
267+
$content-inset: calc(3 * var(--default-grid-baseline));
268+
269+
.app-settings:not(.app-settings--legacy) {
270+
--nav-tint: hsl(from var(--color-primary-element-light) h s calc(l * 1.045));
271+
--nav-tint-strong: var(--color-primary-element-light);
272+
273+
:deep(.modal-wrapper .modal-container) {
274+
padding-inline-start: 0 !important;
275+
padding-block-start: 0 !important;
276+
background-color: var(--nav-tint);
277+
overflow: hidden;
278+
max-width: 900px;
279+
}
280+
281+
// Title is rendered inside the navigation slot; hide the native h2 but
282+
// keep it in the a11y tree so the modal still has its accessible name.
283+
:deep(.dialog__name) {
284+
position: absolute;
285+
width: 1px;
286+
height: 1px;
287+
margin: -1px;
288+
padding: 0;
289+
overflow: hidden;
290+
clip-path: inset(100%);
291+
white-space: nowrap;
292+
border: 0;
293+
}
294+
262295
:deep(.app-settings__navigation) {
263-
min-width: 200px;
264-
margin-inline-end: calc(4 * var(--default-grid-baseline));
296+
min-width: $navigation-width;
297+
max-width: $navigation-width;
298+
flex: 0 0 $navigation-width;
299+
// Beat NcDialog's default 20px end-margin so the tint meets the content.
300+
margin-inline-end: 0 !important;
265301
overflow-x: hidden;
266302
overflow-y: auto;
267303
position: relative;
268304
}
269305
270306
:deep(.app-settings__content) {
271-
padding-inline: calc(4 * var(--default-grid-baseline));
307+
padding: $content-inset;
308+
background-color: var(--color-main-background);
309+
border-inline-start: 1px solid var(--color-border-dark);
310+
border-start-start-radius: var(--border-radius-element);
311+
border-end-start-radius: var(--border-radius-element);
272312
}
273-
}
274313
275-
.navigation-list {
276-
height: 100%;
277-
overflow-y: auto;
278-
padding: calc(3 * var(--default-grid-baseline));
279-
280-
&__link {
281-
display: flex;
282-
align-content: center;
283-
font-size: 16px;
284-
height: var(--default-clickable-area);
285-
margin: 4px 0;
286-
line-height: var(--default-clickable-area);
287-
border-radius: var(--border-radius-element);
288-
font-weight: var(--font-weight-element, bold);
289-
padding: 0 calc(4 * var(--default-grid-baseline));
290-
cursor: pointer;
291-
white-space: nowrap;
292-
text-overflow: ellipsis;
293-
overflow: hidden;
294-
background-color: transparent;
295-
border: none;
314+
.app-settings__title {
315+
box-sizing: border-box;
316+
padding: $content-inset;
317+
margin: 0;
318+
font-size: 20px;
319+
font-weight: 700;
320+
}
296321
297-
&:hover,
298-
&:focus {
299-
background-color: var(--color-background-hover);
300-
}
322+
.navigation-list {
323+
height: 100%;
324+
overflow-y: auto;
325+
padding: var(--default-grid-baseline);
326+
327+
&__link {
328+
position: relative;
329+
display: flex;
330+
align-items: center;
331+
font-size: var(--default-font-size);
332+
font-weight: 500;
333+
height: var(--default-clickable-area);
334+
margin: 2px 0;
335+
line-height: var(--default-clickable-area);
336+
border-radius: var(--border-radius-element);
337+
padding-inline: calc(2 * var(--default-grid-baseline));
338+
cursor: pointer;
339+
white-space: nowrap;
340+
text-overflow: ellipsis;
341+
overflow: hidden;
342+
background-color: transparent;
343+
border: none;
344+
color: var(--color-main-text);
345+
346+
&:hover,
347+
&:focus-visible {
348+
background-color: color-mix(in srgb, var(--color-primary-element) 8%, transparent);
349+
}
350+
351+
&:focus-visible {
352+
outline: 2px solid var(--color-main-text);
353+
outline-offset: -2px;
354+
}
355+
356+
&--active {
357+
background-color: var(--nav-tint-strong);
358+
359+
&:hover,
360+
&:focus-visible {
361+
background-color: var(--color-primary-element-light-hover);
362+
}
363+
364+
&::before {
365+
content: '';
366+
position: absolute;
367+
inset-block: var(--default-grid-baseline);
368+
inset-inline-start: 0;
369+
width: 3px;
370+
background-color: var(--color-primary-element);
371+
border-radius: 999px;
372+
}
373+
}
301374
302-
&--active {
303-
background-color: var(--color-primary-element-light) !important;
375+
&--icon {
376+
gap: var(--default-grid-baseline);
377+
}
378+
379+
&-icon {
380+
display: flex;
381+
justify-content: center;
382+
align-content: center;
383+
width: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
384+
max-width: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
385+
}
304386
}
387+
}
305388
306-
&--icon {
307-
padding-inline-start: calc(2 * var(--default-grid-baseline));
308-
gap: var(--default-grid-baseline);
389+
}
390+
391+
@media only screen and (max-width: $breakpoint-mobile) {
392+
.app-settings:not(.app-settings--legacy) {
393+
:deep(.modal-wrapper .modal-container) {
394+
padding-inline-start: 12px !important;
395+
padding-block-start: 4px !important;
396+
background-color: var(--color-main-background);
397+
}
398+
:deep(.dialog__name) {
399+
position: static;
400+
width: auto;
401+
height: auto;
402+
margin: 0 0 12px 0;
403+
padding-inline-end: var(--default-clickable-area);
404+
overflow: visible;
405+
clip-path: none;
406+
white-space: normal;
407+
border: 0;
309408
}
409+
:deep(.app-settings__content) {
410+
border: none;
411+
border-radius: 0;
412+
}
413+
}
414+
}
415+
416+
// Legacy design (NC < 34): keep the original centered-title layout.
417+
.app-settings.app-settings--legacy {
418+
:deep(.app-settings__navigation) {
419+
min-width: 200px;
420+
margin-inline-end: calc(4 * var(--default-grid-baseline));
421+
overflow-x: hidden;
422+
overflow-y: auto;
423+
position: relative;
424+
}
310425
311-
&-icon {
426+
:deep(.app-settings__content) {
427+
padding-inline: calc(4 * var(--default-grid-baseline));
428+
}
429+
430+
.navigation-list {
431+
height: 100%;
432+
overflow-y: auto;
433+
padding: calc(3 * var(--default-grid-baseline));
434+
435+
&__link {
312436
display: flex;
313-
justify-content: center;
314437
align-content: center;
315-
width: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
316-
max-width: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
438+
font-size: 16px;
439+
height: var(--default-clickable-area);
440+
margin: 4px 0;
441+
line-height: var(--default-clickable-area);
442+
border-radius: var(--border-radius-element);
443+
font-weight: var(--font-weight-element, bold);
444+
padding: 0 calc(4 * var(--default-grid-baseline));
445+
cursor: pointer;
446+
white-space: nowrap;
447+
text-overflow: ellipsis;
448+
overflow: hidden;
449+
background-color: transparent;
450+
border: none;
451+
452+
&:hover,
453+
&:focus {
454+
background-color: var(--color-background-hover);
455+
}
456+
457+
&--active {
458+
background-color: var(--color-primary-element-light) !important;
459+
}
460+
461+
&--icon {
462+
padding-inline-start: calc(2 * var(--default-grid-baseline));
463+
gap: var(--default-grid-baseline);
464+
}
465+
466+
&-icon {
467+
display: flex;
468+
justify-content: center;
469+
align-content: center;
470+
width: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
471+
max-width: calc(var(--default-clickable-area) - 2 * var(--default-grid-baseline));
472+
}
317473
}
318474
}
319-
}
320475
321-
@media only screen and (max-width: $breakpoint-small-mobile) {
322-
.app-settings {
476+
@media only screen and (max-width: $breakpoint-small-mobile) {
323477
:deep(.dialog__name) {
324478
padding-inline-start: 16px;
325479
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*!
2+
* SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3+
* SPDX-License-Identifier: AGPL-3.0-or-later
4+
*/
5+
6+
import { mount } from '@vue/test-utils'
7+
import { expect, test, vi } from 'vitest'
8+
import { nextTick } from 'vue'
9+
import NcAppSettingsDialog from '../../../../src/components/NcAppSettingsDialog/NcAppSettingsDialog.vue'
10+
import { resizeWindowWidth } from '../../testing-utils.ts'
11+
12+
vi.mock('../../../../src/utils/legacy.ts', () => ({ isLegacy: false, isLegacy34: false }))
13+
14+
test('NcAppSettingsDialog renders title in navigation column on Nextcloud >= 34', async () => {
15+
await resizeWindowWidth(1024)
16+
17+
const wrapper = mount(NcAppSettingsDialog, {
18+
props: {
19+
open: true,
20+
showNavigation: true,
21+
name: 'My settings',
22+
},
23+
global: {
24+
stubs: { teleport: true },
25+
},
26+
})
27+
await nextTick()
28+
29+
expect(wrapper.find('.app-settings--legacy').exists()).toBe(false)
30+
expect(wrapper.find('.app-settings__title').text()).toBe('My settings')
31+
})

0 commit comments

Comments
 (0)