@vc-shell/framework 1.2.4-beta.5 → 1.2.4-beta.6
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/core/utilities/date/formatDate.ts +1 -1
- package/dist/{DashboardBarChart-B-g_a-7F.js → DashboardBarChart-BzfKkUke.js} +1 -1
- package/dist/{DashboardDonutChart-AktPFUNo.js → DashboardDonutChart-CWfe85Xq.js} +1 -1
- package/dist/{DashboardLineChart-BQKqRFhM.js → DashboardLineChart-kdA8VnrR.js} +1 -1
- package/dist/{GridstackDashboard-BXqCpiMw.js → GridstackDashboard-CGHYkReX.js} +1 -1
- package/dist/framework.js +1 -1
- package/dist/{index-DVyGELzS.js → index-tgmgQAD9.js} +6839 -6798
- package/dist/index.css +1 -1
- package/dist/locales/de.json +2 -0
- package/dist/locales/en.json +2 -0
- package/dist/shared/components/user-dropdown-button/_internal/user-info.vue.d.ts.map +1 -1
- package/dist/shared/pages/_storybook-helpers.d.ts +11 -0
- package/dist/shared/pages/_storybook-helpers.d.ts.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/ui/components/organisms/vc-app/_internal/menu/VcAppMenu.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-blade/_internal/toolbar/ToolbarMobile.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-popup/vc-popup.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-sidebar/vc-sidebar.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/VcDataTable.vue.d.ts +5 -2
- package/dist/ui/components/organisms/vc-table/VcDataTable.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/VcTableAdapter.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/base/BaseVcDataTable.d.ts +9 -1
- package/dist/ui/components/organisms/vc-table/base/BaseVcDataTable.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/components/DataTableBody.vue.d.ts +7 -0
- package/dist/ui/components/organisms/vc-table/components/DataTableBody.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/components/DataTableRow.vue.d.ts +2 -0
- package/dist/ui/components/organisms/vc-table/components/DataTableRow.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/components/TableEmpty.vue.d.ts +7 -9
- package/dist/ui/components/organisms/vc-table/components/TableEmpty.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/components/TableRow.vue.d.ts +4 -0
- package/dist/ui/components/organisms/vc-table/components/TableRow.vue.d.ts.map +1 -1
- package/dist/ui/components/organisms/vc-table/types.d.ts +21 -0
- package/dist/ui/components/organisms/vc-table/types.d.ts.map +1 -1
- package/dist/{vc-editor-BtJrxrBg.js → vc-editor-BNrG1GAG.js} +1 -1
- package/dist/{vc-slider-sUKMaKnc.js → vc-slider-Ce0X1_1m.js} +1 -1
- package/package.json +5 -5
- package/shared/components/user-dropdown-button/_internal/user-info.vue +26 -13
- package/shared/pages/ChangePasswordPage/components/change-password/change-password.stories.ts +44 -0
- package/shared/pages/ForgotPasswordPage/components/forgot-password/forgot-password.stories.ts +38 -0
- package/shared/pages/InvitePage/components/invite/invite.stories.ts +64 -0
- package/shared/pages/LoginPage/components/login/login.stories.ts +52 -0
- package/shared/pages/ResetPasswordPage/components/reset-password/reset-password.stories.ts +64 -0
- package/shared/pages/_storybook-helpers.ts +71 -0
- package/ui/components/molecules/vc-select/vc-select.vue +1 -1
- package/ui/components/organisms/vc-app/_internal/menu/VcAppMenu.vue +1 -2
- package/ui/components/organisms/vc-blade/_internal/toolbar/ToolbarMobile.vue +18 -22
- package/ui/components/organisms/vc-popup/vc-popup.vue +3 -4
- package/ui/components/organisms/vc-sidebar/vc-sidebar.vue +12 -10
- package/ui/components/organisms/vc-table/VcDataTable.vue +58 -11
- package/ui/components/organisms/vc-table/VcTableAdapter.vue +31 -84
- package/ui/components/organisms/vc-table/base/BaseVcDataTable.ts +10 -0
- package/ui/components/organisms/vc-table/components/DataTableBody.vue +10 -0
- package/ui/components/organisms/vc-table/components/DataTableRow.vue +3 -0
- package/ui/components/organisms/vc-table/components/TableEmpty.vue +18 -12
- package/ui/components/organisms/vc-table/components/TableRow.vue +5 -1
- package/ui/components/organisms/vc-table/types.ts +22 -0
- package/ui/components/organisms/vc-table/vc-data-table.stories.ts +205 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { onUnmounted } from "vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/vue3-vite";
|
|
3
|
+
import Invite from "./Invite.vue";
|
|
4
|
+
import { useUserManagement } from "@core/composables/useUserManagement";
|
|
5
|
+
import { mockPlatformApiFetch, patchUserManagement } from "@shared/pages/_storybook-helpers";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Shared/Pages/Invite",
|
|
9
|
+
component: Invite,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
decorators: [
|
|
12
|
+
() => ({
|
|
13
|
+
setup() {
|
|
14
|
+
mockPlatformApiFetch();
|
|
15
|
+
patchUserManagement();
|
|
16
|
+
},
|
|
17
|
+
template: "<story />",
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
args: {
|
|
21
|
+
userId: "mock-user-id",
|
|
22
|
+
userName: "john@example.com",
|
|
23
|
+
token: "mock-invite-token",
|
|
24
|
+
},
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: "fullscreen",
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component:
|
|
30
|
+
"Invitation acceptance page. User sets a password for their invited account. Validates token on mount, then shows password/confirm fields. Auto-signs in on success.",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} satisfies Meta<typeof Invite>;
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof meta>;
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {};
|
|
40
|
+
|
|
41
|
+
export const WithCustomBackground: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
background: "https://images.unsplash.com/photo-1557683316-973673baf926?w=1920&q=80",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const InvalidToken: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
token: "expired-token",
|
|
50
|
+
},
|
|
51
|
+
decorators: [
|
|
52
|
+
() => ({
|
|
53
|
+
setup() {
|
|
54
|
+
const userManagement = useUserManagement();
|
|
55
|
+
const orig = userManagement.validateToken;
|
|
56
|
+
userManagement.validateToken = async () => false;
|
|
57
|
+
onUnmounted(() => {
|
|
58
|
+
userManagement.validateToken = orig;
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
template: "<story />",
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/vue3-vite";
|
|
2
|
+
import Login from "./Login.vue";
|
|
3
|
+
import { mockPlatformApiFetch, patchUserManagement } from "@shared/pages/_storybook-helpers";
|
|
4
|
+
|
|
5
|
+
const meta = {
|
|
6
|
+
title: "Shared/Pages/Login",
|
|
7
|
+
component: Login,
|
|
8
|
+
tags: ["autodocs"],
|
|
9
|
+
decorators: [
|
|
10
|
+
() => ({
|
|
11
|
+
setup() {
|
|
12
|
+
mockPlatformApiFetch();
|
|
13
|
+
patchUserManagement();
|
|
14
|
+
},
|
|
15
|
+
template: "<story />",
|
|
16
|
+
}),
|
|
17
|
+
],
|
|
18
|
+
args: {
|
|
19
|
+
title: "Welcome back",
|
|
20
|
+
subtitle: "Sign in to your account",
|
|
21
|
+
},
|
|
22
|
+
parameters: {
|
|
23
|
+
layout: "fullscreen",
|
|
24
|
+
docs: {
|
|
25
|
+
description: {
|
|
26
|
+
component:
|
|
27
|
+
"Login page with username/password form, SSO provider support, and forgot-password link. SSO providers require a live API and won't appear in Storybook.",
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
} satisfies Meta<typeof Login>;
|
|
32
|
+
|
|
33
|
+
export default meta;
|
|
34
|
+
type Story = StoryObj<typeof meta>;
|
|
35
|
+
|
|
36
|
+
export const Default: Story = {};
|
|
37
|
+
|
|
38
|
+
export const WithCustomBackground: Story = {
|
|
39
|
+
args: {
|
|
40
|
+
background: "https://images.unsplash.com/photo-1557683316-973673baf926?w=1920&q=80",
|
|
41
|
+
title: "Vendor Portal",
|
|
42
|
+
subtitle: "Manage your products and orders",
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const SSOOnly: Story = {
|
|
47
|
+
args: {
|
|
48
|
+
ssoOnly: true,
|
|
49
|
+
title: "Enterprise Login",
|
|
50
|
+
subtitle: "Sign in with your organization account",
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { onUnmounted } from "vue";
|
|
2
|
+
import type { Meta, StoryObj } from "@storybook/vue3-vite";
|
|
3
|
+
import ResetPassword from "./ResetPassword.vue";
|
|
4
|
+
import { useUserManagement } from "@core/composables/useUserManagement";
|
|
5
|
+
import { mockPlatformApiFetch, patchUserManagement } from "@shared/pages/_storybook-helpers";
|
|
6
|
+
|
|
7
|
+
const meta = {
|
|
8
|
+
title: "Shared/Pages/ResetPassword",
|
|
9
|
+
component: ResetPassword,
|
|
10
|
+
tags: ["autodocs"],
|
|
11
|
+
decorators: [
|
|
12
|
+
() => ({
|
|
13
|
+
setup() {
|
|
14
|
+
mockPlatformApiFetch();
|
|
15
|
+
patchUserManagement();
|
|
16
|
+
},
|
|
17
|
+
template: "<story />",
|
|
18
|
+
}),
|
|
19
|
+
],
|
|
20
|
+
args: {
|
|
21
|
+
userId: "mock-user-id",
|
|
22
|
+
userName: "john@example.com",
|
|
23
|
+
token: "mock-reset-token",
|
|
24
|
+
},
|
|
25
|
+
parameters: {
|
|
26
|
+
layout: "fullscreen",
|
|
27
|
+
docs: {
|
|
28
|
+
description: {
|
|
29
|
+
component:
|
|
30
|
+
"Reset password page reached via email link. Validates token on mount, then allows setting a new password with confirmation. Auto-signs in on success.",
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} satisfies Meta<typeof ResetPassword>;
|
|
35
|
+
|
|
36
|
+
export default meta;
|
|
37
|
+
type Story = StoryObj<typeof meta>;
|
|
38
|
+
|
|
39
|
+
export const Default: Story = {};
|
|
40
|
+
|
|
41
|
+
export const WithCustomBackground: Story = {
|
|
42
|
+
args: {
|
|
43
|
+
background: "https://images.unsplash.com/photo-1557683316-973673baf926?w=1920&q=80",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export const InvalidToken: Story = {
|
|
48
|
+
args: {
|
|
49
|
+
token: "expired-token",
|
|
50
|
+
},
|
|
51
|
+
decorators: [
|
|
52
|
+
() => ({
|
|
53
|
+
setup() {
|
|
54
|
+
const userManagement = useUserManagement();
|
|
55
|
+
const orig = userManagement.validateToken;
|
|
56
|
+
userManagement.validateToken = async () => false;
|
|
57
|
+
onUnmounted(() => {
|
|
58
|
+
userManagement.validateToken = orig;
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
template: "<story />",
|
|
62
|
+
}),
|
|
63
|
+
],
|
|
64
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { defineComponent, onUnmounted } from "vue";
|
|
2
|
+
import { useRouter } from "vue-router";
|
|
3
|
+
import { useUserManagement } from "@core/composables/useUserManagement";
|
|
4
|
+
import { IdentityResult, SecurityResult, SignInResult } from "@core/api/platform";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Intercepts window.fetch for platform API endpoints that don't exist in Storybook.
|
|
8
|
+
* Returns safe empty responses so composables like useSettings don't throw.
|
|
9
|
+
*/
|
|
10
|
+
export function mockPlatformApiFetch() {
|
|
11
|
+
const originalFetch = window.fetch;
|
|
12
|
+
|
|
13
|
+
window.fetch = async function (input: RequestInfo | URL, init?: RequestInit) {
|
|
14
|
+
const url = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url;
|
|
15
|
+
|
|
16
|
+
// useSettings → GET /api/platform/settings/ui/customization
|
|
17
|
+
if (url.includes("/api/platform/settings/ui/customization")) {
|
|
18
|
+
return new Response(JSON.stringify({ defaultValue: null }), {
|
|
19
|
+
status: 200,
|
|
20
|
+
headers: { "Content-Type": "application/json" },
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return originalFetch.call(window, input, init);
|
|
25
|
+
} as typeof fetch;
|
|
26
|
+
|
|
27
|
+
onUnmounted(() => {
|
|
28
|
+
window.fetch = originalFetch;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Patches useUserManagement methods with safe no-op/success stubs
|
|
34
|
+
* and ensures auth-related routes exist for router.push() calls.
|
|
35
|
+
*/
|
|
36
|
+
export function patchUserManagement() {
|
|
37
|
+
const userManagement = useUserManagement();
|
|
38
|
+
const router = useRouter();
|
|
39
|
+
|
|
40
|
+
const originals = {
|
|
41
|
+
signIn: userManagement.signIn,
|
|
42
|
+
signOut: userManagement.signOut,
|
|
43
|
+
validateToken: userManagement.validateToken,
|
|
44
|
+
validatePassword: userManagement.validatePassword,
|
|
45
|
+
resetPasswordByToken: userManagement.resetPasswordByToken,
|
|
46
|
+
requestPasswordReset: userManagement.requestPasswordReset,
|
|
47
|
+
changeUserPassword: userManagement.changeUserPassword,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
userManagement.signIn = async () => new SignInResult({ succeeded: true });
|
|
51
|
+
userManagement.signOut = async () => {};
|
|
52
|
+
userManagement.validateToken = async () => true;
|
|
53
|
+
userManagement.validatePassword = async () => new IdentityResult({ succeeded: true, errors: [] });
|
|
54
|
+
userManagement.resetPasswordByToken = async () => new SecurityResult({ succeeded: true, errors: [] });
|
|
55
|
+
userManagement.requestPasswordReset = async () => ({ succeeded: true });
|
|
56
|
+
userManagement.changeUserPassword = async () => new SecurityResult({ succeeded: true, errors: [] });
|
|
57
|
+
|
|
58
|
+
for (const name of ["Login", "ForgotPassword", "ChangePassword"] as const) {
|
|
59
|
+
if (!router.hasRoute(name)) {
|
|
60
|
+
router.addRoute({
|
|
61
|
+
name,
|
|
62
|
+
path: `/${name.toLowerCase()}`,
|
|
63
|
+
component: defineComponent({ template: "<div />" }),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onUnmounted(() => {
|
|
69
|
+
Object.assign(userManagement, originals);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -136,8 +136,7 @@ const isItemActive = (url?: string): boolean => {
|
|
|
136
136
|
--app-menu-close-color: var(--app-menu-burger-color, var(--primary-500));
|
|
137
137
|
--app-menu-burger-color: var(--primary-500);
|
|
138
138
|
|
|
139
|
-
--app-backdrop-overlay
|
|
140
|
-
--app-backdrop-overlay: rgb(from var(--app-backdrop-overlay-bg) r g b / 75%);
|
|
139
|
+
--app-backdrop-overlay: var(--overlay-bg);
|
|
141
140
|
|
|
142
141
|
--app-backdrop-shadow-color: var(--additional-950);
|
|
143
142
|
--app-backdrop-shadow:
|
|
@@ -167,12 +167,9 @@ function handleItemClick(item: IBladeToolbar) {
|
|
|
167
167
|
--blade-toolbar-mobile-pill-bg-color: var(--primary-500);
|
|
168
168
|
--blade-toolbar-mobile-toggle-border-color: var(--primary-200);
|
|
169
169
|
--blade-toolbar-mobile-toggle-icon-color: var(--additional-50);
|
|
170
|
-
--blade-toolbar-mobile-
|
|
171
|
-
--blade-toolbar-mobile-action-bg: var(--additional-50);
|
|
170
|
+
--blade-toolbar-mobile-action-bg: var(--surface-color);
|
|
172
171
|
--blade-toolbar-mobile-action-text: var(--neutrals-800);
|
|
173
|
-
|
|
174
|
-
// Circle button colors (same as ToolbarCircleButton — must be defined here
|
|
175
|
-
// because ToolbarCircleButton doesn't mount on mobile)
|
|
172
|
+
// Circle button colors
|
|
176
173
|
--blade-toolbar-circle-button-text-color: var(--additional-50);
|
|
177
174
|
--blade-toolbar-circle-button-bg-color: var(--neutrals-500);
|
|
178
175
|
--blade-toolbar-circle-button-main-bg-color: var(--primary-500);
|
|
@@ -189,9 +186,9 @@ $touch-min: 44px;
|
|
|
189
186
|
// ── Backdrop ──────────────────────────────────────────────────────────
|
|
190
187
|
&__backdrop {
|
|
191
188
|
@apply tw-fixed tw-inset-0 tw-z-[55];
|
|
192
|
-
background:
|
|
193
|
-
backdrop-filter: blur(var(--
|
|
194
|
-
-webkit-backdrop-filter: blur(var(--
|
|
189
|
+
background: var(--overlay-bg);
|
|
190
|
+
backdrop-filter: blur(var(--overlay-blur));
|
|
191
|
+
-webkit-backdrop-filter: blur(var(--overlay-blur));
|
|
195
192
|
}
|
|
196
193
|
|
|
197
194
|
// ── Menu container ────────────────────────────────────────────────────
|
|
@@ -223,21 +220,25 @@ $touch-min: 44px;
|
|
|
223
220
|
gap: 12px;
|
|
224
221
|
padding: 4px 4px 4px 16px;
|
|
225
222
|
min-height: $touch-min;
|
|
226
|
-
border:
|
|
223
|
+
border: 1px solid var(--surface-border);
|
|
227
224
|
border-radius: 28px;
|
|
228
|
-
background: var(--blade-toolbar-mobile-action-bg);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
225
|
+
background: color-mix(in srgb, var(--blade-toolbar-mobile-action-bg) 92%, transparent);
|
|
226
|
+
backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
|
|
227
|
+
-webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
|
|
228
|
+
box-shadow: var(--shadow-sm);
|
|
232
229
|
cursor: pointer;
|
|
233
230
|
|
|
234
|
-
// Initial state (hidden)
|
|
235
231
|
opacity: 0;
|
|
236
232
|
transform: translateY(16px);
|
|
237
233
|
transition:
|
|
238
234
|
opacity 0.28s $spring,
|
|
239
235
|
transform 0.28s $spring;
|
|
240
|
-
|
|
236
|
+
|
|
237
|
+
&:hover:not(:disabled) {
|
|
238
|
+
background: var(--blade-toolbar-mobile-action-bg);
|
|
239
|
+
box-shadow: var(--shadow-md);
|
|
240
|
+
transition-duration: 0.15s;
|
|
241
|
+
}
|
|
241
242
|
|
|
242
243
|
&:active:not(:disabled) {
|
|
243
244
|
transform: scale(0.96) !important;
|
|
@@ -249,7 +250,6 @@ $touch-min: 44px;
|
|
|
249
250
|
cursor: default;
|
|
250
251
|
}
|
|
251
252
|
|
|
252
|
-
// Focus-visible ring for keyboard navigation
|
|
253
253
|
&:focus-visible {
|
|
254
254
|
outline: 2px solid var(--blade-toolbar-mobile-pill-bg-color);
|
|
255
255
|
outline-offset: 2px;
|
|
@@ -291,9 +291,7 @@ $touch-min: 44px;
|
|
|
291
291
|
color: var(--blade-toolbar-mobile-toggle-icon-color);
|
|
292
292
|
cursor: pointer;
|
|
293
293
|
-webkit-tap-highlight-color: transparent;
|
|
294
|
-
box-shadow:
|
|
295
|
-
0 4px 12px rgba(0, 0, 0, 0.16),
|
|
296
|
-
0 1px 4px rgba(0, 0, 0, 0.08);
|
|
294
|
+
box-shadow: var(--shadow-sm);
|
|
297
295
|
|
|
298
296
|
&:active {
|
|
299
297
|
transform: scale(0.92);
|
|
@@ -321,9 +319,7 @@ $touch-min: 44px;
|
|
|
321
319
|
border-radius: calc(#{$touch-min} / 2);
|
|
322
320
|
background: var(--blade-toolbar-mobile-pill-bg-color);
|
|
323
321
|
overflow: hidden;
|
|
324
|
-
box-shadow:
|
|
325
|
-
0 4px 12px rgba(0, 0, 0, 0.16),
|
|
326
|
-
0 1px 4px rgba(0, 0, 0, 0.08);
|
|
322
|
+
box-shadow: var(--shadow-sm);
|
|
327
323
|
}
|
|
328
324
|
|
|
329
325
|
&__pill-action {
|
|
@@ -234,8 +234,8 @@ function handleDialogDismiss(): void {
|
|
|
234
234
|
<style lang="scss">
|
|
235
235
|
:root {
|
|
236
236
|
--vc-popup-border-radius: var(--popup-border-radius, 6px);
|
|
237
|
-
--vc-popup-shadow: var(--popup-shadow,
|
|
238
|
-
--vc-popup-overlay-blur: var(--popup-overlay-blur,
|
|
237
|
+
--vc-popup-shadow: var(--popup-shadow, var(--shadow-md));
|
|
238
|
+
--vc-popup-overlay-blur: var(--popup-overlay-blur, var(--overlay-blur));
|
|
239
239
|
|
|
240
240
|
// Deprecated aliases (prefer --vc-popup-* variables for new themes)
|
|
241
241
|
--popup-close-btn-bg: var(--neutrals-100);
|
|
@@ -247,8 +247,7 @@ function handleDialogDismiss(): void {
|
|
|
247
247
|
--popup-success-icon-color: var(--success-500);
|
|
248
248
|
--popup-info-icon-color: var(--info-500);
|
|
249
249
|
--popup-footer-separator: var(--neutrals-200);
|
|
250
|
-
--popup-overlay
|
|
251
|
-
--popup-overlay: rgb(from var(--popup-overlay-color) r g b / 75%);
|
|
250
|
+
--popup-overlay: var(--popup-overlay-override, var(--overlay-bg));
|
|
252
251
|
--popup-bg: var(--additional-50);
|
|
253
252
|
}
|
|
254
253
|
|
|
@@ -563,10 +563,10 @@ defineExpose({
|
|
|
563
563
|
--vc-sidebar-bg-elevated: color-mix(in srgb, var(--additional-50) 90%, white 10%);
|
|
564
564
|
--vc-sidebar-title-color: var(--additional-950);
|
|
565
565
|
--vc-sidebar-subtitle-color: var(--neutrals-600);
|
|
566
|
-
--vc-sidebar-overlay-color:
|
|
566
|
+
--vc-sidebar-overlay-color: var(--overlay-bg);
|
|
567
567
|
--vc-sidebar-close-color: var(--neutrals-600);
|
|
568
|
-
--vc-sidebar-border-color:
|
|
569
|
-
--vc-sidebar-shadow:
|
|
568
|
+
--vc-sidebar-border-color: var(--surface-border);
|
|
569
|
+
--vc-sidebar-shadow: var(--shadow-md);
|
|
570
570
|
--vc-sidebar-radius: 16px;
|
|
571
571
|
--vc-sidebar-inset-gap: 12px;
|
|
572
572
|
--vc-sidebar-header-height: 72px;
|
|
@@ -575,7 +575,9 @@ defineExpose({
|
|
|
575
575
|
@apply tw-fixed tw-inset-0 tw-pointer-events-none;
|
|
576
576
|
|
|
577
577
|
&__overlay {
|
|
578
|
-
@apply tw-absolute tw-inset-0 tw-
|
|
578
|
+
@apply tw-absolute tw-inset-0 tw-pointer-events-auto;
|
|
579
|
+
backdrop-filter: blur(var(--overlay-blur));
|
|
580
|
+
-webkit-backdrop-filter: blur(var(--overlay-blur));
|
|
579
581
|
background: var(--vc-sidebar-overlay-color);
|
|
580
582
|
}
|
|
581
583
|
|
|
@@ -616,13 +618,11 @@ defineExpose({
|
|
|
616
618
|
|
|
617
619
|
&--elevated {
|
|
618
620
|
background: var(--vc-sidebar-bg-elevated);
|
|
619
|
-
box-shadow:
|
|
620
|
-
0 28px 64px rgb(15 23 42 / 0.22),
|
|
621
|
-
0 10px 24px rgb(15 23 42 / 0.16);
|
|
621
|
+
box-shadow: var(--shadow-lg);
|
|
622
622
|
}
|
|
623
623
|
|
|
624
624
|
&--minimal {
|
|
625
|
-
box-shadow:
|
|
625
|
+
box-shadow: var(--shadow-sm);
|
|
626
626
|
border-color: color-mix(in srgb, var(--neutrals-200) 86%, white 14%);
|
|
627
627
|
}
|
|
628
628
|
|
|
@@ -668,7 +668,8 @@ defineExpose({
|
|
|
668
668
|
@apply tw-border-b-[color:var(--vc-sidebar-border-color)] tw-sticky tw-top-0 tw-z-[1];
|
|
669
669
|
min-height: var(--vc-sidebar-header-height);
|
|
670
670
|
background: inherit;
|
|
671
|
-
backdrop-filter: saturate(
|
|
671
|
+
backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
|
|
672
|
+
-webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
|
|
672
673
|
}
|
|
673
674
|
|
|
674
675
|
&__title-wrap {
|
|
@@ -725,7 +726,8 @@ defineExpose({
|
|
|
725
726
|
@apply tw-mt-auto tw-sticky tw-bottom-0 tw-z-[1] tw-border-t tw-border-solid tw-border-t-[color:var(--vc-sidebar-border-color)];
|
|
726
727
|
min-height: var(--vc-sidebar-footer-height);
|
|
727
728
|
background: inherit;
|
|
728
|
-
backdrop-filter: saturate(
|
|
729
|
+
backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
|
|
730
|
+
-webkit-backdrop-filter: saturate(var(--glass-saturate)) blur(var(--glass-blur));
|
|
729
731
|
}
|
|
730
732
|
}
|
|
731
733
|
|
|
@@ -139,8 +139,11 @@
|
|
|
139
139
|
:get-column-width="cols.getEffectiveColumnWidth"
|
|
140
140
|
:get-cell-style="cols.getCellStyle"
|
|
141
141
|
:show-selection-cell="hasSelectionColumn && !isSelectionViaColumn"
|
|
142
|
-
:empty-
|
|
143
|
-
:empty-
|
|
142
|
+
:empty-icon="resolvedEmptyIcon"
|
|
143
|
+
:empty-title="resolvedEmptyTitle"
|
|
144
|
+
:empty-description="resolvedEmptyDescription"
|
|
145
|
+
:empty-action-label="resolvedEmptyActionLabel"
|
|
146
|
+
:empty-action-handler="resolvedEmptyActionHandler"
|
|
144
147
|
:loading-text="loadingText"
|
|
145
148
|
:grouping-enabled="rowGrouping.isGroupingEnabled.value"
|
|
146
149
|
:grouped-data="rowGrouping.groupedData.value"
|
|
@@ -213,10 +216,11 @@
|
|
|
213
216
|
/>
|
|
214
217
|
</template>
|
|
215
218
|
<template
|
|
216
|
-
v-if="$slots.empty"
|
|
219
|
+
v-if="$slots['not-found'] || $slots.empty"
|
|
217
220
|
#empty
|
|
218
221
|
>
|
|
219
|
-
<slot name="
|
|
222
|
+
<slot v-if="isNotFoundState && $slots['not-found']" name="not-found" />
|
|
223
|
+
<slot v-else name="empty" />
|
|
220
224
|
</template>
|
|
221
225
|
<template
|
|
222
226
|
v-if="$slots.loading"
|
|
@@ -283,12 +287,16 @@
|
|
|
283
287
|
@refresh="emit('pull-refresh')"
|
|
284
288
|
>
|
|
285
289
|
<template #empty>
|
|
286
|
-
<slot name="
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
290
|
+
<slot v-if="isNotFoundState && $slots['not-found']" name="not-found" />
|
|
291
|
+
<slot v-else-if="$slots.empty" name="empty" />
|
|
292
|
+
<TableEmpty
|
|
293
|
+
v-else
|
|
294
|
+
:icon="resolvedEmptyIcon"
|
|
295
|
+
:title="resolvedEmptyTitle"
|
|
296
|
+
:description="resolvedEmptyDescription"
|
|
297
|
+
:action-label="resolvedEmptyActionLabel"
|
|
298
|
+
:action-handler="resolvedEmptyActionHandler"
|
|
299
|
+
/>
|
|
292
300
|
</template>
|
|
293
301
|
</DataTableMobileView>
|
|
294
302
|
</div>
|
|
@@ -367,7 +375,7 @@ import {
|
|
|
367
375
|
import { ColumnCollector, type ColumnInstance } from "@ui/components/organisms/vc-table/utils/ColumnCollector";
|
|
368
376
|
import { ColumnCollectorKey, FilterContextKey, HasFlexColumnsKey, IsColumnReorderingKey } from "@ui/components/organisms/vc-table/keys";
|
|
369
377
|
import { IsMobileKey } from "@framework/injection-keys";
|
|
370
|
-
import type { VcColumnProps, VcDataTableExtendedProps, FilterValue, EditChange, TableAction, SortMeta, MobileSwipeAction } from "@ui/components/organisms/vc-table/types";
|
|
378
|
+
import type { VcColumnProps, VcDataTableExtendedProps, FilterValue, EditChange, TableAction, SortMeta, MobileSwipeAction, TableStateConfig } from "@ui/components/organisms/vc-table/types";
|
|
371
379
|
import type { DataTablePersistedState } from "@ui/components/organisms/vc-table/composables";
|
|
372
380
|
|
|
373
381
|
const props = withDefaults(defineProps<VcDataTableExtendedProps<T>>(), {
|
|
@@ -420,7 +428,10 @@ const props = withDefaults(defineProps<VcDataTableExtendedProps<T>>(), {
|
|
|
420
428
|
searchable: false,
|
|
421
429
|
searchValue: undefined,
|
|
422
430
|
searchPlaceholder: "Search...",
|
|
431
|
+
emptyState: undefined,
|
|
432
|
+
notFoundState: undefined,
|
|
423
433
|
searchDebounce: 300,
|
|
434
|
+
activeItemId: undefined,
|
|
424
435
|
});
|
|
425
436
|
|
|
426
437
|
// Emits
|
|
@@ -484,6 +495,8 @@ const emit = defineEmits<{
|
|
|
484
495
|
filter: [event: { filters: Record<string, unknown>; filteredValue: T[] }];
|
|
485
496
|
|
|
486
497
|
// === Row Interactions ===
|
|
498
|
+
/** v-model update for active (highlighted) row ID */
|
|
499
|
+
"update:activeItemId": [value: string | undefined];
|
|
487
500
|
/** Emitted when a row is clicked */
|
|
488
501
|
"row-click": [event: { data: T; index: number; originalEvent: Event }];
|
|
489
502
|
/** Emitted when a row action button/menu item is activated */
|
|
@@ -557,6 +570,8 @@ defineSlots<{
|
|
|
557
570
|
}) => VNode[];
|
|
558
571
|
/** Custom content for expanded rows */
|
|
559
572
|
expansion?: (props: { data: T; index: number }) => VNode[];
|
|
573
|
+
/** Custom not-found state content (shown when search yields no results) */
|
|
574
|
+
"not-found"?: () => VNode[];
|
|
560
575
|
/** Custom empty state content */
|
|
561
576
|
empty?: () => VNode[];
|
|
562
577
|
/** Custom loading state content */
|
|
@@ -579,6 +594,34 @@ const { t } = useI18n({ useScope: "global" });
|
|
|
579
594
|
|
|
580
595
|
const emptyTitle = computed(() => t("COMPONENTS.ORGANISMS.VC_TABLE.EMPTY_TITLE"));
|
|
581
596
|
const emptyDescription = computed(() => t("COMPONENTS.ORGANISMS.VC_TABLE.EMPTY_DESCRIPTION"));
|
|
597
|
+
const notFoundTitle = computed(() => t("COMPONENTS.ORGANISMS.VC_TABLE.NOT_FOUND_TITLE"));
|
|
598
|
+
const notFoundDescription = computed(() => t("COMPONENTS.ORGANISMS.VC_TABLE.NOT_FOUND_DESCRIPTION"));
|
|
599
|
+
|
|
600
|
+
/** Detect not-found state: items empty + active search or filters */
|
|
601
|
+
const isNotFoundState = computed(
|
|
602
|
+
() => displayItems.value.length === 0 && !props.loading && (internalSearchValue.value !== "" || hasActiveFilters.value),
|
|
603
|
+
);
|
|
604
|
+
|
|
605
|
+
/** Resolved title/description/icon/action for the current empty-like state */
|
|
606
|
+
const resolvedEmptyIcon = computed(() =>
|
|
607
|
+
isNotFoundState.value ? props.notFoundState?.icon : props.emptyState?.icon,
|
|
608
|
+
);
|
|
609
|
+
const resolvedEmptyTitle = computed(() =>
|
|
610
|
+
isNotFoundState.value
|
|
611
|
+
? (props.notFoundState?.title ?? notFoundTitle.value)
|
|
612
|
+
: (props.emptyState?.title ?? emptyTitle.value),
|
|
613
|
+
);
|
|
614
|
+
const resolvedEmptyDescription = computed(() =>
|
|
615
|
+
isNotFoundState.value
|
|
616
|
+
? (props.notFoundState?.description ?? notFoundDescription.value)
|
|
617
|
+
: (props.emptyState?.description ?? emptyDescription.value),
|
|
618
|
+
);
|
|
619
|
+
const resolvedEmptyActionLabel = computed(() =>
|
|
620
|
+
isNotFoundState.value ? props.notFoundState?.actionLabel : props.emptyState?.actionLabel,
|
|
621
|
+
);
|
|
622
|
+
const resolvedEmptyActionHandler = computed(() =>
|
|
623
|
+
isNotFoundState.value ? props.notFoundState?.actionHandler : props.emptyState?.actionHandler,
|
|
624
|
+
);
|
|
582
625
|
const loadingText = computed(() => t("COMPONENTS.ORGANISMS.VC_TABLE.LOADING"));
|
|
583
626
|
|
|
584
627
|
/** Track whether data has ever been loaded — distinguishes initial load from refresh */
|
|
@@ -1412,6 +1455,7 @@ const getRowProps = (item: T, index: number) => ({
|
|
|
1412
1455
|
|
|
1413
1456
|
// Selection
|
|
1414
1457
|
isSelected: selection.isSelected(item),
|
|
1458
|
+
isActive: props.activeItemId != null && getItemKey(item, index) === String(props.activeItemId),
|
|
1415
1459
|
isSelectable: selection.canSelect(item),
|
|
1416
1460
|
selectionMode: effectiveSelectionMode.value,
|
|
1417
1461
|
showSelectionCell: hasSelectionColumn.value && !isSelectionViaColumn.value,
|
|
@@ -1494,6 +1538,9 @@ const handleRowSelectionChange = (item: T, eventOrValue?: Event | boolean) => {
|
|
|
1494
1538
|
};
|
|
1495
1539
|
|
|
1496
1540
|
const handleRowClick = (item: T, index: number, event: Event) => {
|
|
1541
|
+
const itemKey = getItemKey(item, index);
|
|
1542
|
+
const isSameItem = props.activeItemId != null && itemKey === String(props.activeItemId);
|
|
1543
|
+
emit("update:activeItemId", isSameItem ? undefined : itemKey);
|
|
1497
1544
|
emit("row-click", { data: item, index, originalEvent: event });
|
|
1498
1545
|
const target = event.target as HTMLElement;
|
|
1499
1546
|
const isCheckboxClick = target.tagName === "INPUT" && target.getAttribute("type") === "checkbox";
|