onColumnResize('displayname', width)"
+ @resize-end="$emit('resize-column-end')"
+ @reset="onColumnReset('displayname')" />
|
+ |
@@ -50,7 +50,7 @@
|
+ class="row__cell row__cell--large row__cell--multiline row__cell--subadmins">
@@ -58,7 +58,7 @@
|
-
+ |
{{ userQuota }} ({{ usedSpace }})
{{ userLanguage.name }}
@@ -83,7 +83,7 @@
|
+ class="row__cell row__cell--large row__cell--storage">
{{ user.backend }}
{{ userFirstLogin }}
|
@@ -105,7 +105,7 @@
{{ userLastLogin }}
|
diff --git a/apps/settings/src/components/Users/shared/styles.scss b/apps/settings/src/components/Users/shared/styles.scss
index 22c85910a03f0..9374709504deb 100644
--- a/apps/settings/src/components/Users/shared/styles.scss
+++ b/apps/settings/src/components/Users/shared/styles.scss
@@ -84,6 +84,25 @@
width: var(--cell-width-groups);
}
+ // Columns whose width can be adjusted by the user, see UserColumnResizer
+ @each $column, $fallback in (
+ 'displayname': '--cell-width',
+ 'username': '--cell-width',
+ 'email': '--cell-width',
+ 'quota': '--cell-width',
+ 'first-login': '--cell-width',
+ 'last-login': '--cell-width',
+ 'groups': '--cell-width-groups',
+ 'subadmins': '--cell-width-large',
+ 'languages': '--cell-width-large',
+ 'storage': '--cell-width-large',
+ ) {
+ &--#{$column} {
+ min-width: var(--user-list-column-#{$column}, var(#{$fallback}));
+ width: var(--user-list-column-#{$column}, var(#{$fallback}));
+ }
+ }
+
&--obfuscated {
min-width: 400px;
width: 400px;
diff --git a/apps/settings/src/utils/userListColumns.spec.ts b/apps/settings/src/utils/userListColumns.spec.ts
new file mode 100644
index 0000000000000..465b2e6a5dfd8
--- /dev/null
+++ b/apps/settings/src/utils/userListColumns.spec.ts
@@ -0,0 +1,64 @@
+/**
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+import { beforeEach, describe, expect, it } from 'vitest'
+import {
+ clampColumnWidth,
+ COLUMN_MAX_WIDTH,
+ COLUMN_MIN_WIDTH,
+ loadColumnWidths,
+ saveColumnWidths,
+} from './userListColumns.ts'
+
+const STORAGE_KEY = 'settings:accounts-list:column-widths'
+
+describe('clampColumnWidth', () => {
+ it('keeps values within bounds', () => {
+ expect(clampColumnWidth(250)).toBe(250)
+ })
+
+ it('clamps values below the minimum', () => {
+ expect(clampColumnWidth(1)).toBe(COLUMN_MIN_WIDTH)
+ })
+
+ it('clamps values above the maximum', () => {
+ expect(clampColumnWidth(99999)).toBe(COLUMN_MAX_WIDTH)
+ })
+
+ it('rounds fractional values', () => {
+ expect(clampColumnWidth(250.4)).toBe(250)
+ expect(clampColumnWidth(250.6)).toBe(251)
+ })
+})
+
+describe('loadColumnWidths and saveColumnWidths', () => {
+ beforeEach(() => {
+ window.localStorage.clear()
+ })
+
+ it('returns an empty object when nothing is stored', () => {
+ expect(loadColumnWidths()).toEqual({})
+ })
+
+ it('round-trips stored widths', () => {
+ saveColumnWidths({ email: 250, groups: 500 })
+ expect(loadColumnWidths()).toEqual({ email: 250, groups: 500 })
+ })
+
+ it('returns an empty object on invalid JSON', () => {
+ window.localStorage.setItem(STORAGE_KEY, 'not json')
+ expect(loadColumnWidths()).toEqual({})
+ })
+
+ it('drops non numeric values', () => {
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify({ email: 'wide', quota: null, groups: 300 }))
+ expect(loadColumnWidths()).toEqual({ groups: 300 })
+ })
+
+ it('clamps out of range stored values', () => {
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify({ email: 1, groups: 99999 }))
+ expect(loadColumnWidths()).toEqual({ email: COLUMN_MIN_WIDTH, groups: COLUMN_MAX_WIDTH })
+ })
+})
diff --git a/apps/settings/src/utils/userListColumns.ts b/apps/settings/src/utils/userListColumns.ts
new file mode 100644
index 0000000000000..89bb0e280bcc9
--- /dev/null
+++ b/apps/settings/src/utils/userListColumns.ts
@@ -0,0 +1,33 @@
+/**
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+const STORAGE_KEY = 'settings:accounts-list:column-widths'
+
+export const COLUMN_MIN_WIDTH = 100
+export const COLUMN_MAX_WIDTH = 1000
+export const COLUMN_RESIZE_STEP = 16
+
+export function clampColumnWidth(width: number): number {
+ return Math.min(Math.max(Math.round(width), COLUMN_MIN_WIDTH), COLUMN_MAX_WIDTH)
+}
+
+export function loadColumnWidths(): Record {
+ try {
+ const stored = JSON.parse(window.localStorage.getItem(STORAGE_KEY) ?? '{}')
+ const widths: Record = {}
+ for (const [column, width] of Object.entries(stored)) {
+ if (typeof width === 'number' && Number.isFinite(width)) {
+ widths[column] = clampColumnWidth(width)
+ }
+ }
+ return widths
+ } catch {
+ return {}
+ }
+}
+
+export function saveColumnWidths(widths: Record): void {
+ window.localStorage.setItem(STORAGE_KEY, JSON.stringify(widths))
+}
|