@vc-shell/vc-app-skill 2.0.0-alpha.28 → 2.0.0-alpha.30

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 CHANGED
@@ -1,3 +1,13 @@
1
+ # [2.0.0-alpha.30](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.29...v2.0.0-alpha.30) (2026-03-30)
2
+
3
+
4
+ ### Features
5
+
6
+ * **docs:** add documentation for usePopup and useResponsive composables ([342a50a](https://github.com/VirtoCommerce/vc-shell/commit/342a50ada1545637782e01f5452a60839aa90fd2))
7
+ # [2.0.0-alpha.29](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.28...v2.0.0-alpha.29) (2026-03-26)
8
+
9
+ **Note:** Version bump only for package @vc-shell/vc-app-skill
10
+
1
11
  # [2.0.0-alpha.28](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.27...v2.0.0-alpha.28) (2026-03-26)
2
12
 
3
13
  **Note:** Version bump only for package @vc-shell/vc-app-skill
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vc-shell/vc-app-skill",
3
- "version": "2.0.0-alpha.28",
3
+ "version": "2.0.0-alpha.30",
4
4
  "description": "AI coding skill for scaffolding and generating VirtoCommerce Shell applications. Works with Claude Code, OpenCode, Gemini, Codex, Cursor.",
5
5
  "bin": "./bin/install.cjs",
6
6
  "files": [
package/runtime/VERSION CHANGED
@@ -1 +1 @@
1
- 2.0.0-alpha.28
1
+ 2.0.0-alpha.30
@@ -1 +1 @@
1
- Synced from framework at commit 8bc5a83cc on 2026-03-26T07:20:52.461Z
1
+ Synced from framework at commit 37522732b on 2026-03-30T14:31:59.196Z
@@ -123,6 +123,14 @@ These are reactive `ComputedRef` values that reflect the current blade's state.
123
123
  | `setError` | `(error: unknown) => void` | Display an error banner on the blade |
124
124
  | `clearError` | `() => void` | Clear the blade error banner |
125
125
 
126
+ #### Banner Management (blade context required)
127
+
128
+ | Method | Signature | Description |
129
+ |----------------|--------------------------------------------------------------|----------------------------------------------------------------|
130
+ | `addBanner` | `(options: Omit<IBladeBanner, "id" \| "_system">) => string` | Add a custom banner to the blade. Returns the banner's unique ID. |
131
+ | `removeBanner` | `(id: string) => void` | Remove a specific banner by its ID |
132
+ | `clearBanners` | `() => void` | Remove all custom banners (system error/modified banners are preserved) |
133
+
126
134
  #### Lifecycle Hooks (blade context required)
127
135
 
128
136
  | Method | Signature | Description |
@@ -373,6 +381,74 @@ async function loadData() {
373
381
  </script>
374
382
  ```
375
383
 
384
+ ### Banner Management
385
+
386
+ Add custom banners (info, warning, danger, success) to the top of a blade. Banners appear between the header and toolbar, sorted by severity: danger > warning > info > success.
387
+
388
+ ```vue
389
+ <script setup lang="ts">
390
+ import { h } from "vue";
391
+ import { useBlade } from "@vc-shell/framework";
392
+
393
+ const { addBanner, removeBanner, clearBanners } = useBlade();
394
+
395
+ // Simple text banner
396
+ const bannerId = addBanner({
397
+ variant: "info",
398
+ message: "This record is in read-only mode",
399
+ });
400
+
401
+ // Warning with dismiss button
402
+ addBanner({
403
+ variant: "warning",
404
+ message: "License expires in 7 days",
405
+ dismissible: true,
406
+ });
407
+
408
+ // Banner with an action button
409
+ addBanner({
410
+ variant: "success",
411
+ message: "Import completed (42 items)",
412
+ dismissible: true,
413
+ action: {
414
+ label: "View report",
415
+ handler: () => openReport(),
416
+ },
417
+ });
418
+
419
+ // Banner with custom render function
420
+ addBanner({
421
+ variant: "info",
422
+ render: () => h("span", [
423
+ "Data synced from ",
424
+ h("b", "Warehouse A"),
425
+ " at 14:32",
426
+ ]),
427
+ });
428
+
429
+ // Remove a specific banner
430
+ removeBanner(bannerId);
431
+
432
+ // Clear all custom banners (system error/modified banners are preserved)
433
+ clearBanners();
434
+ </script>
435
+ ```
436
+
437
+ #### IBladeBanner
438
+
439
+ The options object passed to `addBanner`:
440
+
441
+ | Property | Type | Default | Description |
442
+ |---------------|-----------------------------------|----------|----------------------------------------------------|
443
+ | `variant` | `"danger" \| "warning" \| "info" \| "success"` | required | Color scheme and default icon |
444
+ | `message` | `string` | -- | Plain text message |
445
+ | `render` | `() => VNode` | -- | Custom render function (takes priority over message) |
446
+ | `dismissible` | `boolean` | `false` | Show a close button to dismiss the banner |
447
+ | `icon` | `string` | -- | Override the default variant icon (lucide icon name) |
448
+ | `action` | `{ label: string; handler: () => void }` | -- | Action button displayed on the right side |
449
+
450
+ > **Note:** `addBanner` returns a unique string ID. Use it with `removeBanner(id)` to programmatically remove a specific banner. `clearBanners()` removes all custom banners but preserves system banners (error and unsaved changes).
451
+
376
452
  ---
377
453
 
378
454
  ## Blade Context: defineBladeContext / injectBladeContext
@@ -17,7 +17,7 @@ The pattern follows a "define once, inject anywhere" approach: the blade compone
17
17
  // In a blade's <script setup>
18
18
  import { defineBladeContext, injectBladeContext } from '@vc-shell/framework';
19
19
 
20
- // Provide context (blade component)
20
+ // Provide context refs/computeds are auto-unwrapped for consumers
21
21
  defineBladeContext({ item, disabled, loading });
22
22
 
23
23
  // Or with a computed for selective exposure
@@ -29,7 +29,9 @@ defineBladeContext(computed(() => ({ id: item.value?.id })));
29
29
  import { injectBladeContext } from '@vc-shell/framework';
30
30
 
31
31
  const ctx = injectBladeContext();
32
+ // Refs are already unwrapped — access values directly, no .value needed
32
33
  const entityId = computed(() => ctx.value.id as string);
34
+ const item = computed(() => ctx.value.item as { id: string; name: string });
33
35
  ```
34
36
 
35
37
  ## API
@@ -90,6 +92,7 @@ defineBladeContext(computed(() => ({
90
92
 
91
93
  ## Details
92
94
 
95
+ - **Automatic ref unwrapping**: `defineBladeContext` shallow-unwraps all ref/computed values in the provided object. Consumers get plain values directly (`ctx.value.item` instead of `ctx.value.item.value`). This works reactively — when the source ref changes, the context updates automatically.
93
96
  - **Reactivity**: The provided context is always wrapped in a `computed`, so consumers receive a `ComputedRef` regardless of whether the provider passed a plain object, a ref, or a getter. Changes propagate automatically.
94
97
  - **Injection key**: Uses `BladeContextKey` from `framework/injection-keys.ts`. This is a framework-level Symbol, so there is no risk of key collision with application code.
95
98
  - **Error handling**: `injectBladeContext` throws an `InjectionError` with a descriptive message if called outside a blade component tree. This fails fast during development rather than silently returning `undefined`.
@@ -112,7 +112,7 @@ A complete example of an external widget that shows an unread message count and
112
112
  **1. Register the external widget (module index.ts):**
113
113
 
114
114
  ```typescript
115
- import { createAppModule, registerExternalWidget, IBladeInstance } from "@vc-shell/framework";
115
+ import { createAppModule, registerExternalWidget, BladeDescriptor } from "@vc-shell/framework";
116
116
  import { markRaw } from "vue";
117
117
  import { MessageWidget } from "./components/widgets";
118
118
 
@@ -120,7 +120,7 @@ registerExternalWidget({
120
120
  id: "MessageWidget",
121
121
  component: markRaw(MessageWidget),
122
122
  targetBlades: ["ProductDetails", "OrderDetails"],
123
- isVisible: (blade?: IBladeInstance) => !!blade?.param,
123
+ isVisible: (blade?: BladeDescriptor) => !!blade?.param,
124
124
  });
125
125
  ```
126
126
 
@@ -0,0 +1,195 @@
1
+ # usePopup
2
+
3
+ Programmatic popup management for modal dialogs. Returns methods to open confirmation, error, and info dialogs, as well as fully custom popup components with typed props and emits. Popups are rendered in a dedicated container at the app root level, ensuring they overlay all other content including blades and sidebars.
4
+
5
+ ## When to Use
6
+
7
+ - Show confirmation dialogs before destructive actions (delete, discard, bulk operations)
8
+ - Display error or info messages in a modal overlay
9
+ - Open custom popup components with typed props and emits
10
+ - When NOT to use: for passive feedback (use `notification()` toast instead), for navigation flows (use blade navigation instead)
11
+
12
+ ## Quick Start
13
+
14
+ ```vue
15
+ <script setup lang="ts">
16
+ import { usePopup } from "@vc-shell/framework";
17
+
18
+ const { showConfirmation, showError } = usePopup();
19
+
20
+ async function deleteProduct(id: string) {
21
+ const confirmed = await showConfirmation(
22
+ "Are you sure you want to delete this product?"
23
+ );
24
+ if (!confirmed) return;
25
+
26
+ try {
27
+ await api.deleteProduct(id);
28
+ } catch (error) {
29
+ showError(error.message || "Failed to delete product.");
30
+ }
31
+ }
32
+ </script>
33
+
34
+ <template>
35
+ <VcBlade title="Product">
36
+ <VcButton variant="danger" @click="deleteProduct(product.id)">
37
+ Delete
38
+ </VcButton>
39
+ </VcBlade>
40
+ </template>
41
+ ```
42
+
43
+ ## API
44
+
45
+ ### Parameters
46
+
47
+ | Parameter | Type | Required | Description |
48
+ |---|---|---|---|
49
+ | `options` | `MaybeRef<UsePopupProps<T>>` | No | Configuration for a custom popup component. Omit for built-in dialogs only. |
50
+
51
+ #### `UsePopupProps<T>`
52
+
53
+ | Field | Type | Required | Description |
54
+ |---|---|---|---|
55
+ | `component` | `ComponentPublicInstanceConstructor` | Yes | The popup component to render |
56
+ | `props` | `RawProps<T>` | No | Props to pass to the component (typed from the component's `defineProps`) |
57
+ | `emits` | `RawEmits<T>` | No | Event handlers (typed from the component's `defineEmits`) |
58
+ | `slots` | `Record<string, string \| Component \| Slot>` | No | Named slots — strings render as text, components render as VNodes |
59
+
60
+ ### Returns (`IUsePopup`)
61
+
62
+ | Method | Signature | Description |
63
+ |---|---|---|
64
+ | `open` | `() => void` | Push the popup onto the stack and render it |
65
+ | `close` | `() => void` | Remove the popup from the stack |
66
+ | `showConfirmation` | `(message: string \| Ref<string>) => Promise<boolean>` | Warning dialog with Confirm/Cancel buttons. Resolves `true` on confirm, `false` on cancel or close. |
67
+ | `showError` | `(message: string \| Ref<string>) => void` | Error-styled popup with a close button |
68
+ | `showInfo` | `(message: string \| Ref<string>) => void` | Info-styled popup with a close button |
69
+
70
+ ## How It Works
71
+
72
+ `usePopup` is backed by the `VcPopupHandler` plugin, which maintains a reactive array of popup instances. When you call `open()` or `showConfirmation()`, a popup descriptor is pushed into this array. The `VcPopupContainer` component (mounted once at the app root) renders all active popups as an overlay stack. Calling `close()` removes the descriptor, and Vue reactivity handles the unmount.
73
+
74
+ The plugin instance is resolved via `inject(PopupPluginKey)`. If called outside the component tree (e.g., in a utility function), it falls back to the singleton `popupPluginInstance`.
75
+
76
+ Multiple popups can be open simultaneously — they stack with the most recent on top.
77
+
78
+ ## Recipe: Delete Confirmation with Notification
79
+
80
+ ```vue
81
+ <script setup lang="ts">
82
+ import { usePopup, useBlade } from "@vc-shell/framework";
83
+ import { useNotifications } from "@vc-shell/framework";
84
+
85
+ const { showConfirmation, showError } = usePopup();
86
+ const { closeSelf } = useBlade();
87
+
88
+ async function deleteOrder(id: string) {
89
+ const confirmed = await showConfirmation(
90
+ "Are you sure you want to delete this order? This action cannot be undone."
91
+ );
92
+ if (!confirmed) return;
93
+
94
+ try {
95
+ await api.deleteOrder(id);
96
+ closeSelf();
97
+ } catch (error) {
98
+ showError(error.message || "Failed to delete order.");
99
+ }
100
+ }
101
+ </script>
102
+ ```
103
+
104
+ ## Recipe: Custom Popup with Form
105
+
106
+ For complex interactions beyond simple confirmation, create a custom popup component:
107
+
108
+ ```ts
109
+ import { usePopup } from "@vc-shell/framework";
110
+ import AddNotePopup from "./AddNotePopup.vue";
111
+
112
+ const { open, close } = usePopup({
113
+ component: AddNotePopup,
114
+ props: { entityId: "prod-1" },
115
+ emits: {
116
+ onConfirm: async (note: string) => {
117
+ await api.addNote("prod-1", note);
118
+ close();
119
+ },
120
+ onCancel: () => close(),
121
+ },
122
+ });
123
+
124
+ open();
125
+ ```
126
+
127
+ ## Recipe: Reactive Options
128
+
129
+ The `options` parameter accepts `MaybeRef`, so you can pass a computed or ref that updates the popup config dynamically:
130
+
131
+ ```ts
132
+ import { computed } from "vue";
133
+ import { usePopup } from "@vc-shell/framework";
134
+ import ConfirmPopup from "./ConfirmPopup.vue";
135
+
136
+ const selectedCount = ref(0);
137
+
138
+ const { open, close } = usePopup(
139
+ computed(() => ({
140
+ component: ConfirmPopup,
141
+ props: { message: `Delete ${selectedCount.value} items?` },
142
+ emits: { onConfirm: () => { performDelete(); close(); } },
143
+ }))
144
+ );
145
+ ```
146
+
147
+ ## Common Mistakes
148
+
149
+ **Wrong: not awaiting showConfirmation**
150
+ ```ts
151
+ // The delete runs immediately — confirmation is ignored
152
+ showConfirmation("Delete?");
153
+ await api.deleteProduct(id); // runs before user clicks!
154
+ ```
155
+
156
+ **Correct: await the result**
157
+ ```ts
158
+ const confirmed = await showConfirmation("Delete?");
159
+ if (!confirmed) return;
160
+ await api.deleteProduct(id);
161
+ ```
162
+
163
+ ---
164
+
165
+ **Wrong: nesting many popups**
166
+ ```ts
167
+ // 3+ deep popup stacks confuse users
168
+ const a = await showConfirmation("Step 1?");
169
+ if (a) {
170
+ const b = await showConfirmation("Step 2?");
171
+ if (b) {
172
+ const c = await showConfirmation("Step 3?");
173
+ }
174
+ }
175
+ ```
176
+
177
+ **Correct: use a blade for multi-step workflows**
178
+ ```ts
179
+ // Complex flows belong in blades, not popups
180
+ openBlade({ name: "WizardBlade", options: { step: 1 } });
181
+ ```
182
+
183
+ ## Tips
184
+
185
+ - **Always `await` showConfirmation** before proceeding with the destructive action.
186
+ - **Use `showConfirmation` for yes/no questions**, custom popups for forms or complex interactions.
187
+ - **Calling `usePopup()` without arguments** gives you only the built-in dialogs (`showConfirmation`, `showError`, `showInfo`). Pass `options` only when you need a custom component.
188
+ - **Avoid nesting popups more than 2 levels deep.** Consider blades for complex multi-step workflows.
189
+ - **The message parameter accepts `Ref<string>`** — useful for dynamic messages that update while the popup is open (e.g., progress messages).
190
+
191
+ ## Related
192
+
193
+ - [VcPopup](../../../ui/components/organisms/vc-popup/vc-popup.docs.md) — the default popup shell component (header, content, actions)
194
+ - [useBlade](../useBlade/useBlade.docs.md) — for panel-based UI; use popups for modal interruptions only
195
+ - `notification()` — for passive, non-blocking feedback (toasts)
@@ -0,0 +1,178 @@
1
+ # useResponsive
2
+
3
+ Reactive breakpoint state for building responsive blade UIs. Returns refs that indicate the current viewport category (phone, tablet, mobile, desktop) and whether the device supports touch input. Replaces the legacy `$isMobile.value` global properties in templates and `inject(IsMobileKey)` in script setup with a single, consistent API.
4
+
5
+ ## When to Use
6
+
7
+ - Conditionally render desktop vs. mobile layouts in blade pages and components
8
+ - Apply responsive CSS classes based on viewport size
9
+ - Toggle behavior (e.g., disable drag-and-drop on touch devices, switch column visibility)
10
+ - When NOT to use: for CSS-only responsive changes, prefer Tailwind breakpoint prefixes (`md:`, `lg:`) instead of JavaScript-driven conditionals
11
+
12
+ ## Quick Start
13
+
14
+ ```vue
15
+ <script setup lang="ts">
16
+ import { useResponsive } from "@vc-shell/framework";
17
+
18
+ const { isMobile, isDesktop } = useResponsive();
19
+ </script>
20
+
21
+ <template>
22
+ <VcBlade title="Orders">
23
+ <!-- Vue auto-unwraps refs from script setup — no .value needed -->
24
+ <div v-if="isDesktop" class="tw-flex tw-gap-4">
25
+ <OrdersTable />
26
+ <OrdersSummary />
27
+ </div>
28
+ <OrdersTable v-else />
29
+ </VcBlade>
30
+ </template>
31
+ ```
32
+
33
+ ## API
34
+
35
+ ### Parameters
36
+
37
+ None. The composable reads breakpoint state from the framework's provide/inject context, which is set up automatically by `VcApp`.
38
+
39
+ ### Returns (`UseResponsiveReturn`)
40
+
41
+ | Property | Type | Default | Description |
42
+ |---|---|---|---|
43
+ | `isMobile` | `Ref<boolean>` | `false` | `true` when viewport width < 1024px |
44
+ | `isDesktop` | `Ref<boolean>` | `true` | `true` when viewport width >= 1024px |
45
+ | `isPhone` | `Ref<boolean>` | `false` | `true` when viewport width < 480px |
46
+ | `isTablet` | `Ref<boolean>` | `false` | `true` when 480px <= viewport width < 1024px |
47
+ | `isTouch` | `boolean` | `false` | `true` on touch-capable devices (not reactive — set once at app init) |
48
+
49
+ Breakpoint thresholds: phone < 480px, tablet 480–1023px, desktop >= 1024px. These match the framework's `setupBreakpoints()` configuration.
50
+
51
+ Note: `isMobile` is the union of `isPhone` and `isTablet` — it covers all viewports below the desktop threshold.
52
+
53
+ ## How It Works
54
+
55
+ Under the hood, `useResponsive()` calls `inject()` for each breakpoint key (`IsMobileKey`, `IsDesktopKey`, etc.) with sensible defaults. The framework's root `VcApp` component provides these refs during app initialization using VueUse's `useBreakpoints`. Because the return values are the same `Ref<boolean>` instances provided at the app level, they are reactive and shared across all components.
56
+
57
+ The defaults ensure the composable works even outside the VcApp provider tree (e.g., in unit tests or Storybook), defaulting to desktop mode.
58
+
59
+ ## Recipe: Responsive Blade with Mobile Card Layout
60
+
61
+ ```vue
62
+ <script setup lang="ts">
63
+ import { useResponsive, useBlade } from "@vc-shell/framework";
64
+
65
+ const { isMobile, isDesktop } = useResponsive();
66
+ const { openBlade } = useBlade();
67
+ </script>
68
+
69
+ <template>
70
+ <VcBlade title="Products">
71
+ <VcDataTable
72
+ :items="products"
73
+ :total-count="totalCount"
74
+ @row-click="({ data }) => openBlade({ name: 'ProductDetails', param: data.id })"
75
+ >
76
+ <VcColumn
77
+ id="image"
78
+ title=""
79
+ type="image"
80
+ :width="60"
81
+ :always-visible="true"
82
+ mobile-role="image"
83
+ />
84
+ <VcColumn
85
+ id="name"
86
+ :title="t('NAME')"
87
+ :sortable="true"
88
+ :always-visible="true"
89
+ mobile-role="title"
90
+ />
91
+ <!-- Extra columns visible only on desktop -->
92
+ <VcColumn id="sku" :title="t('SKU')" :sortable="true" />
93
+ <VcColumn id="price" :title="t('PRICE')" type="money" :sortable="true" />
94
+ <VcColumn id="createdDate" :title="t('DATE')" type="date-ago" :sortable="true" />
95
+ </VcDataTable>
96
+ </VcBlade>
97
+ </template>
98
+ ```
99
+
100
+ ## Recipe: Touch-Aware Drag and Drop
101
+
102
+ ```vue
103
+ <script setup lang="ts">
104
+ import { useResponsive } from "@vc-shell/framework";
105
+
106
+ const { isTouch } = useResponsive();
107
+ </script>
108
+
109
+ <template>
110
+ <VcDataTable
111
+ :items="items"
112
+ :reorderable-rows="!isTouch"
113
+ @reorder="onReorder"
114
+ >
115
+ <!-- columns -->
116
+ </VcDataTable>
117
+ </template>
118
+ ```
119
+
120
+ ## Common Mistakes
121
+
122
+ **Wrong: using `$isMobile.value` in template (deprecated)**
123
+ ```vue
124
+ <template>
125
+ <!-- $isMobile is a global property — requires .value, no auto-unwrap -->
126
+ <div v-if="$isMobile.value">Mobile</div>
127
+ </template>
128
+ ```
129
+
130
+ **Correct: using `useResponsive()` destructure**
131
+ ```vue
132
+ <script setup lang="ts">
133
+ import { useResponsive } from "@vc-shell/framework";
134
+ const { isMobile } = useResponsive();
135
+ </script>
136
+ <template>
137
+ <!-- Vue auto-unwraps refs from script setup — clean and type-safe -->
138
+ <div v-if="isMobile">Mobile</div>
139
+ </template>
140
+ ```
141
+
142
+ ---
143
+
144
+ **Wrong: using `inject(IsMobileKey)` directly**
145
+ ```vue
146
+ <script setup lang="ts">
147
+ import { inject, ref } from "vue";
148
+ import { IsMobileKey } from "@vc-shell/framework";
149
+ // Verbose, requires importing both inject and the key
150
+ const isMobile = inject(IsMobileKey, ref(false));
151
+ </script>
152
+ ```
153
+
154
+ **Correct: using `useResponsive()`**
155
+ ```vue
156
+ <script setup lang="ts">
157
+ import { useResponsive } from "@vc-shell/framework";
158
+ // One import, one line, all breakpoints available
159
+ const { isMobile } = useResponsive();
160
+ </script>
161
+ ```
162
+
163
+ ## Tips
164
+
165
+ - **Destructure only what you need.** `const { isMobile } = useResponsive()` is fine — unused refs have no overhead since they already exist at the app level.
166
+ - **Prefer CSS breakpoints for styling.** Use `useResponsive()` for structural changes (different component trees), but for spacing/sizing tweaks use Tailwind responsive prefixes (`md:tw-px-4`).
167
+ - **`isTouch` is not reactive.** It's determined once at app startup based on `ontouchstart` / `maxTouchPoints`. It won't change if a user connects a mouse to a tablet mid-session.
168
+ - **Works in tests without providers.** Defaults to desktop mode (`isDesktop: true`, `isMobile: false`), so most component tests don't need to provide breakpoint keys unless testing mobile-specific behavior.
169
+
170
+ ## Migration
171
+
172
+ See [migration guide #36](../../../../migration/36-use-responsive.md) for automated codemod and manual migration instructions.
173
+
174
+ ## Related
175
+
176
+ - `VcApp` — provides the breakpoint refs that `useResponsive()` reads
177
+ - `VcBlade` — uses `isMobile` internally for mobile blade layout
178
+ - `VcDataTable` — uses `isMobile` for mobile card rendering and pull-to-refresh
@@ -21,7 +21,7 @@ This centralized approach has several advantages:
21
21
  | Key | Type | Description |
22
22
  |-----|------|-------------|
23
23
  | `NavigationViewLocationKey` | `BladeVNode` | Current blade VNode location in navigation |
24
- | `BladeInstanceKey` | `ComputedRef<IBladeInstance>` | Current blade instance metadata |
24
+ | `BladeDescriptorKey` | `ComputedRef<BladeDescriptor>` | Current blade descriptor metadata |
25
25
  | `BladeBackButtonKey` | `Component \| undefined` | Custom back button component for a blade |
26
26
  | `BladeDataKey` | *(from blade-navigation types)* | Data passed between parent/child blades |
27
27
  | `BladeContextKey` | `ComputedRef<Record<string, unknown>>` | Blade-exposed context for widgets/extensions |
@@ -83,7 +83,7 @@ This centralized approach has several advantages:
83
83
  | Deprecated | Use Instead |
84
84
  |------------|-------------|
85
85
  | `navigationViewLocation` | `NavigationViewLocationKey` |
86
- | `BladeInstance` | `BladeInstanceKey` |
86
+ | `BladeDescriptor` | `BladeDescriptorKey` |
87
87
  | `NotificationTemplatesSymbol` | `NotificationTemplatesKey` |
88
88
  | `BLADE_BACK_BUTTON` | `BladeBackButtonKey` |
89
89
  | `TOOLBAR_SERVICE` | `ToolbarServiceKey` |
@@ -355,7 +355,7 @@ interface IBladeToolbar {
355
355
  clickHandler?(): void;
356
356
  disabled?: boolean | ComputedRef<boolean>;
357
357
  isVisible?: boolean | Ref<boolean> | ComputedRef<boolean>
358
- | ((blade?: IBladeInstance) => boolean);
358
+ | ((blade?: BladeDescriptor) => boolean);
359
359
  separator?: "left" | "right" | "both";
360
360
  }
361
361
  ```
@@ -1,135 +0,0 @@
1
- # PopupHandler
2
-
3
- A global popup management system that renders modal dialogs on demand. Installed as a Vue plugin (`VcPopupHandler`), it provides the `usePopup` composable for programmatic popup control. Popups are rendered in a dedicated container at the app root level, ensuring they overlay all other content including blades and sidebars.
4
-
5
- The system supports both built-in dialog types (confirmation, error, info) and fully custom popup components with typed props and emits.
6
-
7
- ## When to Use
8
-
9
- - To show confirmation dialogs before destructive actions (delete, discard, bulk operations)
10
- - To display error or info messages in a modal overlay
11
- - To open custom popup components with typed props and emits
12
- - Do NOT use for passive feedback (use `notification()` toast instead)
13
- - Do NOT use for navigation (use blade navigation instead)
14
-
15
- ## Basic Usage
16
-
17
- ```ts
18
- import { usePopup } from "@vc-shell/framework";
19
-
20
- const { showConfirmation, showError, showInfo } = usePopup();
21
-
22
- // Confirmation dialog (returns Promise<boolean>)
23
- const confirmed = await showConfirmation("Delete this item?");
24
-
25
- // Error popup
26
- showError("Something went wrong.");
27
-
28
- // Info popup
29
- showInfo("Operation completed successfully.");
30
- ```
31
-
32
- ### Custom Popup Component
33
-
34
- ```ts
35
- import { usePopup } from "@vc-shell/framework";
36
- import MyDialog from "./MyDialog.vue";
37
-
38
- const { open, close } = usePopup({
39
- component: MyDialog,
40
- props: { title: "Custom Dialog" },
41
- emits: { onConfirm: () => close() },
42
- });
43
-
44
- open();
45
- ```
46
-
47
- ## API (`usePopup`)
48
-
49
- | Method | Signature | Description |
50
- |--------|-----------|-------------|
51
- | `open` | `() => void` | Push the popup onto the stack and render it |
52
- | `close` | `() => void` | Remove the popup from the stack |
53
- | `showConfirmation` | `(message: string \| Ref<string>) => Promise<boolean>` | Warning dialog with confirm/cancel |
54
- | `showError` | `(message: string \| Ref<string>) => void` | Error-styled popup |
55
- | `showInfo` | `(message: string \| Ref<string>) => void` | Info-styled popup |
56
-
57
- ## Recipe: Delete Confirmation Before API Call
58
-
59
- The most common popup pattern is asking for confirmation before a destructive action:
60
-
61
- ```vue
62
- <script setup lang="ts">
63
- import { usePopup, notification } from "@vc-shell/framework";
64
-
65
- const { showConfirmation } = usePopup();
66
-
67
- async function deleteProduct(id: string) {
68
- const confirmed = await showConfirmation(
69
- "Are you sure you want to delete this product? This action cannot be undone."
70
- );
71
-
72
- if (!confirmed) return;
73
-
74
- try {
75
- await api.deleteProduct(id);
76
- notification("Product deleted.", { type: "success" });
77
- closeSelf();
78
- } catch (error) {
79
- showError(error.message || "Failed to delete product.");
80
- }
81
- }
82
- </script>
83
- ```
84
-
85
- ## Recipe: Custom Popup with Form
86
-
87
- Create a custom popup component for complex interactions:
88
-
89
- ```ts
90
- // Using a custom popup component
91
- import { usePopup } from "@vc-shell/framework";
92
- import AddNotePopup from "./AddNotePopup.vue";
93
-
94
- const { open, close } = usePopup({
95
- component: AddNotePopup,
96
- props: { entityId: "prod-1" },
97
- emits: {
98
- onConfirm: async (note: string) => {
99
- await api.addNote(entityId, note);
100
- close();
101
- notification("Note added.", { type: "success" });
102
- },
103
- onCancel: () => close(),
104
- },
105
- });
106
-
107
- open();
108
- ```
109
-
110
- ## Key Parts
111
-
112
- | Export | Description |
113
- |--------|-------------|
114
- | `VcPopupHandler` | Vue plugin that installs the popup registry |
115
- | `VcPopupContainer` | Renders all active popups (placed once in the app root) |
116
- | `usePopup` | Composable for opening/closing popups |
117
-
118
- ## Details
119
-
120
- - **Popup stack**: Multiple popups can be open simultaneously, stacking with the most recent on top.
121
- - **Promise-based confirmation**: `showConfirmation` returns a `Promise<boolean>` that resolves to `true` on confirm, `false` on cancel.
122
- - **Plugin requirement**: `VcPopupHandler` must be installed as a Vue plugin, and `VcPopupContainer` must be mounted in the component tree. Both are automatically set up in the standard vc-shell app shell.
123
-
124
- ## Tips
125
-
126
- - Always `await` the result of `showConfirmation` before proceeding with the destructive action.
127
- - For simple yes/no questions, use `showConfirmation`. For forms or complex interactions, create a custom popup component.
128
- - The `usePopup` composable can be called without arguments (for built-in dialogs) or with a component config (for custom popups).
129
- - Avoid nesting popups more than 2 levels deep. Consider blades for complex multi-step workflows.
130
-
131
- ## Related Components
132
-
133
- - **VcPopup** - The default popup shell component (header, content, actions)
134
- - **VcBlade** - For panel-based UI; use popups for modal interruptions
135
- - **notification()** - For passive, non-blocking feedback