@jskit-ai/users-web 0.2.16 → 0.2.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/fixtures/account-settings/main.js +3 -2
- package/fixtures/translated-app/main.js +3 -2
- package/locales/en.json +1 -1
- package/package.json +11 -8
- package/patterns/account-settings/example/src/components/account/settings/AccountSettingsNotificationsSection.vue +2 -8
- package/patterns/account-settings/example/src/components/account/settings/AccountSettingsPreferencesSection.vue +2 -8
- package/patterns/account-settings/example/src/components/account/settings/AccountSettingsProfileSection.vue +5 -11
- package/patterns/account-settings/example/src/components/account/settings/AccountSettingsSignInMethodsSection.vue +3 -9
- package/patterns/account-settings/example/src/pages/account/index.vue +7 -0
- package/src/client/components/AccountSettingsClientElement.vue +32 -63
- package/src/client/components/ProfileClientElement.vue +4 -4
- package/test/accountSettings.browser.test.js +4 -2
- package/test/settingsPlacementContract.test.js +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
- The account settings page and the account-settings pattern's sections sit directly on the page, without bordered sheets, and the section list has no extra border or tint.
|
|
6
|
+
- The account settings page has no in-page title or Back button: it gives the shell's top app bar its title (`useShellPageTitle`) and a Done action (`ShellPageAction`) that returns to `returnTo`, another of the app's origins included, as Back did. Message keys: `jskit.users.accountSettings.back` is removed and `jskit.users.accountSettings.done` ("Done") is new. The pattern's section buttons no longer set a fixed height; they are Material 3's 40dp buttons inside material-web's 48dp touch target.
|
|
7
|
+
- Account settings text uses Vuetify 4's Material 3 type classes (`text-body-medium`, `text-title-large`, ...) instead of Material 2 ones, which Vuetify 4 no longer styles, and the account settings page pattern declares its title and its parent (the home surface's root). Upgrading: see the guide's [Move an app to the Material 3 defaults](https://mobily-enterprises.github.io/jskit-ai/guide/app-setup/material-3-migration).
|
|
8
|
+
|
|
3
9
|
## 0.2.0 - 2026-10-29
|
|
4
10
|
|
|
5
11
|
- Translation ready: every user-visible text is a message key with its English in `locales/en.json`, so an app translates it by supplying translation files only. With no translations, texts and behaviour are unchanged. See the guide "Translating a JSKIT app".
|
|
@@ -2,10 +2,11 @@ import { createApp, h, reactive, ref, shallowReactive } from "vue";
|
|
|
2
2
|
import { createRouter, createWebHistory, RouterView } from "vue-router";
|
|
3
3
|
import { QueryClient, VueQueryPlugin } from "@tanstack/vue-query";
|
|
4
4
|
import "vuetify/styles";
|
|
5
|
+
import "@jskit-ai/material-web/client/material.css";
|
|
6
|
+
import { createMaterialVuetifyOptions } from "@jskit-ai/material-web/client";
|
|
5
7
|
import { createVuetify } from "vuetify";
|
|
6
8
|
import * as components from "vuetify/components";
|
|
7
9
|
import * as directives from "vuetify/directives";
|
|
8
|
-
import { aliases, mdi } from "vuetify/iconsets/mdi-svg";
|
|
9
10
|
import { SHELL_WEB_ERROR_RUNTIME_KEY } from "@jskit-ai/shell-web/client/error";
|
|
10
11
|
import { SHELL_WEB_PLACEMENT_RUNTIME_KEY } from "@jskit-ai/shell-web/client/placement";
|
|
11
12
|
import AccountSettingsClientElement from "../../src/client/components/AccountSettingsClientElement.vue";
|
|
@@ -135,6 +136,6 @@ app.provide(SHELL_WEB_PLACEMENT_RUNTIME_KEY, placement);
|
|
|
135
136
|
app.provide(SHELL_WEB_ERROR_RUNTIME_KEY, errorRuntime);
|
|
136
137
|
app.use(router);
|
|
137
138
|
app.use(VueQueryPlugin, { queryClient: new QueryClient({ defaultOptions: { queries: { retry: false } } }) });
|
|
138
|
-
app.use(createVuetify({ components, directives,
|
|
139
|
+
app.use(createVuetify({ components, directives, ...createMaterialVuetifyOptions() }));
|
|
139
140
|
await router.isReady();
|
|
140
141
|
app.mount("#app");
|
|
@@ -4,10 +4,11 @@ import { createI18n } from "vue-i18n";
|
|
|
4
4
|
import { createRouter, createWebHistory, RouterView } from "vue-router";
|
|
5
5
|
import { QueryClient, VueQueryPlugin, useQuery } from "@tanstack/vue-query";
|
|
6
6
|
import "vuetify/styles";
|
|
7
|
+
import "@jskit-ai/material-web/client/material.css";
|
|
8
|
+
import { createMaterialVuetifyOptions } from "@jskit-ai/material-web/client";
|
|
7
9
|
import { createVuetify } from "vuetify";
|
|
8
10
|
import * as components from "vuetify/components";
|
|
9
11
|
import * as directives from "vuetify/directives";
|
|
10
|
-
import { aliases, mdi } from "vuetify/iconsets/mdi-svg";
|
|
11
12
|
import { registerJskitMessages, setJskitI18n } from "@jskit-ai/kernel/client/i18n";
|
|
12
13
|
import { silentLogger } from "@jskit-ai/kernel/shared/support/silentLogger";
|
|
13
14
|
import { createHttpClient } from "@jskit-ai/http-runtime/client";
|
|
@@ -158,7 +159,7 @@ app.use(i18n);
|
|
|
158
159
|
app.use(pinia);
|
|
159
160
|
app.use(router);
|
|
160
161
|
app.use(VueQueryPlugin, { queryClient });
|
|
161
|
-
app.use(createVuetify({ components, directives,
|
|
162
|
+
app.use(createVuetify({ components, directives, ...createMaterialVuetifyOptions() }));
|
|
162
163
|
|
|
163
164
|
const { shell } = ShellWebClientProvider.setup({ components: componentRegistry, logger: silentLogger, queryClient, router });
|
|
164
165
|
shell.error.configure({});
|
package/locales/en.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"accountSettings": {
|
|
5
5
|
"title": "Account settings",
|
|
6
6
|
"subtitle": "Global profile, preferences, notifications, and account controls.",
|
|
7
|
-
"
|
|
7
|
+
"done": "Done",
|
|
8
8
|
"loadFailedTitle": "Unable to load account settings",
|
|
9
9
|
"loadFailed": "Unable to load settings.",
|
|
10
10
|
"retry": "Retry",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/users-web",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test --test-concurrency=1"
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
"./locales/en.json": "./locales/en.json"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@jskit-ai/uploads-image-web": "0.2.
|
|
17
|
-
"@jskit-ai/users-core": "0.2.
|
|
16
|
+
"@jskit-ai/uploads-image-web": "0.2.10",
|
|
17
|
+
"@jskit-ai/users-core": "0.2.12"
|
|
18
18
|
},
|
|
19
19
|
"peerDependencies": {
|
|
20
|
-
"@jskit-ai/auth-web": "0.2.
|
|
21
|
-
"@jskit-ai/http-runtime": "0.2.
|
|
22
|
-
"@jskit-ai/http-web": "0.2.
|
|
23
|
-
"@jskit-ai/kernel": "0.2.
|
|
24
|
-
"@jskit-ai/shell-web": "0.2.
|
|
20
|
+
"@jskit-ai/auth-web": "0.2.13",
|
|
21
|
+
"@jskit-ai/http-runtime": "0.2.10",
|
|
22
|
+
"@jskit-ai/http-web": "0.2.16",
|
|
23
|
+
"@jskit-ai/kernel": "0.2.10",
|
|
24
|
+
"@jskit-ai/shell-web": "0.2.11",
|
|
25
25
|
"@tanstack/vue-query": "^5.90.5",
|
|
26
26
|
"vue": "^3.5.13",
|
|
27
27
|
"vue-router": "^5.0.4",
|
|
@@ -228,5 +228,8 @@
|
|
|
228
228
|
"singleton": true
|
|
229
229
|
}
|
|
230
230
|
}
|
|
231
|
+
},
|
|
232
|
+
"devDependencies": {
|
|
233
|
+
"@jskit-ai/material-web": "0.1.0"
|
|
231
234
|
}
|
|
232
235
|
}
|
|
@@ -10,7 +10,7 @@ const notifications = props.runtime.notifications;
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<template>
|
|
13
|
-
<
|
|
13
|
+
<div class="account-settings-section">
|
|
14
14
|
<div class="account-settings-section__body">
|
|
15
15
|
<v-form @submit.prevent="notifications.submit" novalidate>
|
|
16
16
|
<v-switch
|
|
@@ -47,7 +47,7 @@ const notifications = props.runtime.notifications;
|
|
|
47
47
|
</v-btn>
|
|
48
48
|
</v-form>
|
|
49
49
|
</div>
|
|
50
|
-
</
|
|
50
|
+
</div>
|
|
51
51
|
</template>
|
|
52
52
|
|
|
53
53
|
<style scoped>
|
|
@@ -58,10 +58,4 @@ const notifications = props.runtime.notifications;
|
|
|
58
58
|
.account-settings-section__body {
|
|
59
59
|
padding: 1rem;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
@media (max-width: 640px) {
|
|
63
|
-
.account-settings-section__body :deep(.v-btn) {
|
|
64
|
-
min-height: 48px;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
61
|
</style>
|
|
@@ -10,7 +10,7 @@ const preferences = props.runtime.preferences;
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<template>
|
|
13
|
-
<
|
|
13
|
+
<div class="account-settings-section">
|
|
14
14
|
<div class="account-settings-section__body">
|
|
15
15
|
<v-form @submit.prevent="preferences.submit" novalidate>
|
|
16
16
|
<v-row>
|
|
@@ -117,7 +117,7 @@ const preferences = props.runtime.preferences;
|
|
|
117
117
|
</v-btn>
|
|
118
118
|
</v-form>
|
|
119
119
|
</div>
|
|
120
|
-
</
|
|
120
|
+
</div>
|
|
121
121
|
</template>
|
|
122
122
|
|
|
123
123
|
<style scoped>
|
|
@@ -128,10 +128,4 @@ const preferences = props.runtime.preferences;
|
|
|
128
128
|
.account-settings-section__body {
|
|
129
129
|
padding: 1rem;
|
|
130
130
|
}
|
|
131
|
-
|
|
132
|
-
@media (max-width: 640px) {
|
|
133
|
-
.account-settings-section__body :deep(.v-btn) {
|
|
134
|
-
min-height: 48px;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
131
|
</style>
|
|
@@ -10,16 +10,16 @@ const profile = props.runtime.profile;
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<template>
|
|
13
|
-
<
|
|
13
|
+
<div class="account-settings-section">
|
|
14
14
|
<div class="account-settings-section__body">
|
|
15
15
|
<v-form @submit.prevent="profile.submit" novalidate>
|
|
16
16
|
<v-row class="mb-2">
|
|
17
17
|
<v-col cols="12" md="4" class="d-flex flex-column align-center justify-center">
|
|
18
18
|
<v-avatar :size="profile.avatar.size" color="surface-variant" rounded="circle" class="mb-3">
|
|
19
19
|
<v-img v-if="profile.avatar.effectiveUrl" :src="profile.avatar.effectiveUrl" cover />
|
|
20
|
-
<span v-else class="text-
|
|
20
|
+
<span v-else class="text-title-large">{{ profile.initials.value }}</span>
|
|
21
21
|
</v-avatar>
|
|
22
|
-
<div class="text-
|
|
22
|
+
<div class="text-body-small on-surface-variant">Preview size: {{ profile.avatar.size }} px</div>
|
|
23
23
|
</v-col>
|
|
24
24
|
|
|
25
25
|
<v-col cols="12" md="8">
|
|
@@ -44,7 +44,7 @@ const profile = props.runtime.profile;
|
|
|
44
44
|
</v-btn>
|
|
45
45
|
</div>
|
|
46
46
|
|
|
47
|
-
<div v-if="profile.selectedAvatarFileName.value" class="text-
|
|
47
|
+
<div v-if="profile.selectedAvatarFileName.value" class="text-body-small on-surface-variant mb-2">
|
|
48
48
|
Selected file: {{ profile.selectedAvatarFileName.value }}
|
|
49
49
|
</div>
|
|
50
50
|
</v-col>
|
|
@@ -86,7 +86,7 @@ const profile = props.runtime.profile;
|
|
|
86
86
|
</v-btn>
|
|
87
87
|
</v-form>
|
|
88
88
|
</div>
|
|
89
|
-
</
|
|
89
|
+
</div>
|
|
90
90
|
</template>
|
|
91
91
|
|
|
92
92
|
<style scoped>
|
|
@@ -97,10 +97,4 @@ const profile = props.runtime.profile;
|
|
|
97
97
|
.account-settings-section__body {
|
|
98
98
|
padding: 1rem;
|
|
99
99
|
}
|
|
100
|
-
|
|
101
|
-
@media (max-width: 640px) {
|
|
102
|
-
.account-settings-section__body :deep(.v-btn) {
|
|
103
|
-
min-height: 48px;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
100
|
</style>
|
|
@@ -10,7 +10,7 @@ const signInMethods = props.runtime.signInMethods;
|
|
|
10
10
|
</script>
|
|
11
11
|
|
|
12
12
|
<template>
|
|
13
|
-
<
|
|
13
|
+
<div class="account-settings-section">
|
|
14
14
|
<v-list lines="two" class="py-0">
|
|
15
15
|
<v-list-item
|
|
16
16
|
v-for="method in signInMethods.methods.value"
|
|
@@ -43,11 +43,11 @@ const signInMethods = props.runtime.signInMethods;
|
|
|
43
43
|
</v-list>
|
|
44
44
|
<p
|
|
45
45
|
v-if="signInMethods.methods.value.some((method) => method.linked && !method.canUnlink)"
|
|
46
|
-
class="account-settings-section__hint text-body-
|
|
46
|
+
class="account-settings-section__hint text-body-medium on-surface-variant mb-0"
|
|
47
47
|
>
|
|
48
48
|
Your only sign-in method can't be unlinked. Set a password or link another provider first.
|
|
49
49
|
</p>
|
|
50
|
-
</
|
|
50
|
+
</div>
|
|
51
51
|
</template>
|
|
52
52
|
|
|
53
53
|
<style scoped>
|
|
@@ -58,10 +58,4 @@ const signInMethods = props.runtime.signInMethods;
|
|
|
58
58
|
.account-settings-section__hint {
|
|
59
59
|
padding: 0 1rem 1rem;
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
@media (max-width: 640px) {
|
|
63
|
-
.account-settings-section :deep(.v-btn) {
|
|
64
|
-
min-height: 48px;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
61
|
</style>
|
|
@@ -2,6 +2,9 @@
|
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import { useRoute, useRouter } from "vue-router";
|
|
4
4
|
import { normalizeOneOf } from "@jskit-ai/kernel/shared/support/normalize";
|
|
5
|
+
import ShellPageAction from "@jskit-ai/shell-web/client/components/ShellPageAction";
|
|
6
|
+
import ShellPageActions from "@jskit-ai/shell-web/client/components/ShellPageActions";
|
|
7
|
+
import { useShellPageTitle } from "@jskit-ai/shell-web/client/composables/useShellPageTitle";
|
|
5
8
|
import { useAccountSettingsRuntime } from "../composables/useAccountSettingsRuntime.js";
|
|
6
9
|
import { useAccountSettingsSections } from "../account-settings/sections.js";
|
|
7
10
|
import { useJskitText } from "../i18n.js";
|
|
@@ -11,6 +14,10 @@ const runtime = useAccountSettingsRuntime();
|
|
|
11
14
|
const route = useRoute();
|
|
12
15
|
const router = useRouter();
|
|
13
16
|
const sections = useAccountSettingsSections();
|
|
17
|
+
// The app bar carries the page's title and its Done action, which returns to where the person came from (returnTo,
|
|
18
|
+
// which may be another of the app's origins).
|
|
19
|
+
useShellPageTitle(() => t("jskit.users.accountSettings.title"));
|
|
20
|
+
const doneTarget = computed(() => runtime.backNavigationTarget.value);
|
|
14
21
|
const sectionValues = computed(() => Object.freeze(sections.value.map((section) => section.value)));
|
|
15
22
|
const defaultSection = computed(() => {
|
|
16
23
|
if (sectionValues.value.includes("profile")) {
|
|
@@ -58,23 +65,17 @@ const activeTab = computed({
|
|
|
58
65
|
|
|
59
66
|
<template>
|
|
60
67
|
<section class="settings-view py-2 py-md-4">
|
|
61
|
-
<
|
|
62
|
-
<
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
:to="runtime.backNavigationTarget.value.sameOrigin ? runtime.backNavigationTarget.value.href : undefined"
|
|
73
|
-
:href="runtime.backNavigationTarget.value.sameOrigin ? undefined : runtime.backNavigationTarget.value.href"
|
|
74
|
-
>
|
|
75
|
-
{{ t("jskit.users.accountSettings.back") }}
|
|
76
|
-
</v-btn>
|
|
77
|
-
</header>
|
|
68
|
+
<ShellPageActions>
|
|
69
|
+
<ShellPageAction
|
|
70
|
+
:label="t('jskit.users.accountSettings.done')"
|
|
71
|
+
:to="doneTarget.sameOrigin ? doneTarget.href : undefined"
|
|
72
|
+
:href="doneTarget.sameOrigin ? undefined : doneTarget.href"
|
|
73
|
+
/>
|
|
74
|
+
</ShellPageActions>
|
|
75
|
+
<div class="settings-panel">
|
|
76
|
+
<p class="settings-panel__intro text-body-medium on-surface-variant">
|
|
77
|
+
{{ t("jskit.users.accountSettings.subtitle") }}
|
|
78
|
+
</p>
|
|
78
79
|
|
|
79
80
|
<div class="settings-panel__body">
|
|
80
81
|
<template v-if="runtime.loadingSettings.value">
|
|
@@ -82,8 +83,8 @@ const activeTab = computed({
|
|
|
82
83
|
<v-skeleton-loader type="text@2, paragraph, button" />
|
|
83
84
|
</template>
|
|
84
85
|
<div v-else-if="runtime.settingsLoadError.value" class="settings-panel__state">
|
|
85
|
-
<h2 class="text-
|
|
86
|
-
<p class="text-body-
|
|
86
|
+
<h2 class="text-title-large mb-2">{{ t("jskit.users.accountSettings.loadFailedTitle") }}</h2>
|
|
87
|
+
<p class="text-body-medium on-surface-variant mb-4">{{ runtime.settingsLoadError.value }}</p>
|
|
87
88
|
<v-btn
|
|
88
89
|
color="primary"
|
|
89
90
|
variant="tonal"
|
|
@@ -94,19 +95,18 @@ const activeTab = computed({
|
|
|
94
95
|
</v-btn>
|
|
95
96
|
</div>
|
|
96
97
|
<template v-else-if="sections.length < 1">
|
|
97
|
-
<p class="text-body-
|
|
98
|
+
<p class="text-body-medium on-surface-variant mb-0">{{ t("jskit.users.accountSettings.noSections") }}</p>
|
|
98
99
|
</template>
|
|
99
100
|
<template v-else>
|
|
100
101
|
<v-progress-linear v-if="runtime.refreshingSettings.value" indeterminate class="mb-4" />
|
|
101
102
|
<v-row class="settings-layout" no-gutters>
|
|
102
103
|
<v-col cols="12" md="3" lg="2" class="pr-md-4 mb-4 mb-md-0">
|
|
103
|
-
<v-list nav
|
|
104
|
+
<v-list nav class="settings-section-list">
|
|
104
105
|
<v-list-item
|
|
105
106
|
v-for="section in sections"
|
|
106
107
|
:key="section.value"
|
|
107
108
|
:title="text(section.title)"
|
|
108
109
|
:active="activeTab === section.value"
|
|
109
|
-
rounded="lg"
|
|
110
110
|
@click="activeTab = section.value"
|
|
111
111
|
/>
|
|
112
112
|
</v-list>
|
|
@@ -114,7 +114,13 @@ const activeTab = computed({
|
|
|
114
114
|
|
|
115
115
|
<v-col cols="12" md="9" lg="10">
|
|
116
116
|
<v-window v-model="activeTab" :touch="false" class="settings-sections-window">
|
|
117
|
-
<v-window-item
|
|
117
|
+
<v-window-item
|
|
118
|
+
v-for="section in sections"
|
|
119
|
+
:key="section.value"
|
|
120
|
+
:value="section.value"
|
|
121
|
+
:transition="false"
|
|
122
|
+
:reverse-transition="false"
|
|
123
|
+
>
|
|
118
124
|
<component
|
|
119
125
|
:is="section.component"
|
|
120
126
|
v-bind="section.usesSharedRuntime ? { runtime } : undefined"
|
|
@@ -125,32 +131,20 @@ const activeTab = computed({
|
|
|
125
131
|
</v-row>
|
|
126
132
|
</template>
|
|
127
133
|
</div>
|
|
128
|
-
</
|
|
134
|
+
</div>
|
|
129
135
|
</section>
|
|
130
136
|
</template>
|
|
131
137
|
|
|
132
138
|
<style scoped>
|
|
133
139
|
.settings-panel {
|
|
134
|
-
background-color: rgb(var(--v-theme-surface));
|
|
135
140
|
overflow: hidden;
|
|
136
141
|
}
|
|
137
142
|
|
|
138
|
-
.settings-
|
|
139
|
-
|
|
140
|
-
display: flex;
|
|
141
|
-
gap: 1rem;
|
|
142
|
-
justify-content: space-between;
|
|
143
|
+
.settings-panel__intro {
|
|
144
|
+
margin: 0;
|
|
143
145
|
padding: 1rem 1rem 0;
|
|
144
146
|
}
|
|
145
147
|
|
|
146
|
-
.settings-panel__title {
|
|
147
|
-
font-size: clamp(1.35rem, 2vw, 1.85rem);
|
|
148
|
-
font-weight: 650;
|
|
149
|
-
letter-spacing: -0.02em;
|
|
150
|
-
line-height: 1.15;
|
|
151
|
-
margin: 0 0 0.35rem;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
148
|
.settings-panel__body {
|
|
155
149
|
padding: 1rem;
|
|
156
150
|
}
|
|
@@ -161,29 +155,4 @@ const activeTab = computed({
|
|
|
161
155
|
padding: 2rem 1rem;
|
|
162
156
|
text-align: center;
|
|
163
157
|
}
|
|
164
|
-
|
|
165
|
-
.settings-section-list {
|
|
166
|
-
border: 1px solid rgba(var(--v-theme-outline), 0.35);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
:deep(.settings-section-list .v-list-item--active) {
|
|
170
|
-
background-color: rgba(var(--v-theme-primary), 0.14);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
:deep(.settings-sections-window .v-window-x-transition-enter-active),
|
|
174
|
-
:deep(.settings-sections-window .v-window-x-transition-leave-active),
|
|
175
|
-
:deep(.settings-sections-window .v-window-x-reverse-transition-enter-active),
|
|
176
|
-
:deep(.settings-sections-window .v-window-x-reverse-transition-leave-active) {
|
|
177
|
-
transition: none !important;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
@media (max-width: 960px) {
|
|
181
|
-
.settings-panel__header {
|
|
182
|
-
flex-direction: column;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
.settings-panel__header :deep(.v-btn) {
|
|
186
|
-
min-height: 48px;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
158
|
</style>
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
:data-testid="uiTestIds.card"
|
|
10
10
|
>
|
|
11
11
|
<div v-if="resolvedFeatures.header" class="profile-client-panel__header">
|
|
12
|
-
<h2 class="text-
|
|
12
|
+
<h2 class="text-title-medium mb-0">{{ copyText.title }}</h2>
|
|
13
13
|
</div>
|
|
14
14
|
<v-divider v-if="resolvedFeatures.header" />
|
|
15
15
|
<div class="profile-client-panel__body">
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
<v-col cols="12" md="4" class="d-flex flex-column align-center justify-center">
|
|
21
21
|
<v-avatar :size="state.preferencesForm.avatarSize" color="surface-variant" rounded="circle" class="mb-3">
|
|
22
22
|
<v-img v-if="state.profileAvatar.effectiveUrl" :src="state.profileAvatar.effectiveUrl" cover />
|
|
23
|
-
<span v-else class="text-
|
|
23
|
+
<span v-else class="text-title-large">{{ state.profileInitials }}</span>
|
|
24
24
|
</v-avatar>
|
|
25
|
-
<div class="text-
|
|
25
|
+
<div class="text-body-small on-surface-variant">
|
|
26
26
|
{{ copyText.previewSizePrefix }} {{ state.preferencesForm.avatarSize }} {{ copyText.previewSizeSuffix }}
|
|
27
27
|
</div>
|
|
28
28
|
</v-col>
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
</v-btn>
|
|
44
44
|
<slot name="avatar-actions-extra" :state="state" :actions="actions" />
|
|
45
45
|
</div>
|
|
46
|
-
<div v-if="state.selectedAvatarFileName" class="text-
|
|
46
|
+
<div v-if="state.selectedAvatarFileName" class="text-body-small on-surface-variant mb-2">
|
|
47
47
|
{{ copyText.selectedFilePrefix }} {{ state.selectedAvatarFileName }}
|
|
48
48
|
</div>
|
|
49
49
|
</v-col>
|
|
@@ -127,7 +127,9 @@ test("the profile section loads, saves, shows a refused field and removes the av
|
|
|
127
127
|
const name = page.getByLabel("Display name");
|
|
128
128
|
await expect(name).toHaveValue("Ada");
|
|
129
129
|
await expect(page.getByLabel("Email", { exact: true })).toHaveValue("ada@example.com");
|
|
130
|
-
|
|
130
|
+
// The page's Done action (in the shell's app bar, or in place without one) returns to returnTo; there is no in-page h1.
|
|
131
|
+
await expect(page.getByRole("link", { name: "Done" })).toHaveAttribute("href", "/home");
|
|
132
|
+
await expect(page.getByRole("heading", { level: 1 })).toHaveCount(0);
|
|
131
133
|
|
|
132
134
|
await name.fill(" Ada Lovelace ");
|
|
133
135
|
await page.getByRole("button", { name: "Save profile" }).click();
|
|
@@ -147,7 +149,7 @@ test("the profile section loads, saves, shows a refused field and removes the av
|
|
|
147
149
|
await expect(page.getByRole("button", { name: "Remove avatar" })).toHaveCount(0);
|
|
148
150
|
await expect.poll(() => lastReport(page)).toBe("Avatar removed.");
|
|
149
151
|
|
|
150
|
-
await page.getByRole("link", { name: "
|
|
152
|
+
await page.getByRole("link", { name: "Done" }).click();
|
|
151
153
|
await expect(page.getByTestId("home")).toBeVisible();
|
|
152
154
|
await finish(api);
|
|
153
155
|
});
|
|
@@ -90,7 +90,10 @@ test("package-owned account settings host is placement-backed rather than scaffo
|
|
|
90
90
|
);
|
|
91
91
|
|
|
92
92
|
assert.match(source, /useAccountSettingsSections/);
|
|
93
|
-
|
|
93
|
+
// The app bar carries the title and the Done action; the page keeps only its intro.
|
|
94
|
+
assert.match(source, /settings-panel__intro/);
|
|
95
|
+
assert.match(source, /useShellPageTitle/);
|
|
96
|
+
assert.doesNotMatch(source, /<h1/u);
|
|
94
97
|
assert.doesNotMatch(source, /AccountSettingsProfileSection|AccountSettingsPreferencesSection|AccountSettingsNotificationsSection/);
|
|
95
98
|
});
|
|
96
99
|
|