@vc-shell/vc-app-skill 2.0.0-alpha.29 → 2.0.0-alpha.31

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.31](https://github.com/VirtoCommerce/vc-shell/compare/v2.0.0-alpha.30...v2.0.0-alpha.31) (2026-04-01)
2
+
3
+ **Note:** Version bump only for package @vc-shell/vc-app-skill
4
+
5
+ # [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)
6
+
7
+
8
+ ### Features
9
+
10
+ * **docs:** add documentation for usePopup and useResponsive composables ([342a50a](https://github.com/VirtoCommerce/vc-shell/commit/342a50ada1545637782e01f5452a60839aa90fd2))
1
11
  # [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)
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.29",
3
+ "version": "2.0.0-alpha.31",
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.29
1
+ 2.0.0-alpha.31
@@ -1 +1 @@
1
- Synced from framework at commit d863d7620 on 2026-03-26T14:29:54.970Z
1
+ Synced from framework at commit 2f71da38b on 2026-04-01T16:03:16.012Z
@@ -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
@@ -1,163 +1,162 @@
1
1
  # useDynamicProperties
2
2
 
3
- Generic composable for managing dynamic (runtime-defined) property values with support for multilanguage, multivalue, dictionary, color, and measurement property types. This is one of the most complex composables in the framework because it must handle the combinatorial explosion of property configurations: a property can be multilanguage AND multivalue AND dictionary-backed, each combination requiring different get/set logic. The composable handles loading dictionary items from the API, getting/setting property values by locale, unit-of-measure lookups, and color code management.
3
+ Composable for managing dynamic (runtime-defined) property values with support for multilanguage, multivalue, dictionary, color, and measurement property types.
4
4
 
5
- The composable is generic over the property, value, dictionary item, and measurement types, allowing it to work with both the standard platform types and custom extensions.
5
+ Internally uses a strategy pattern each property type (regular, boolean, dictionary, measure, color) has a dedicated handler with `get`/`set` methods.
6
6
 
7
7
  ## When to Use
8
8
 
9
9
  - In product, category, or any entity detail blades that display platform dynamic properties
10
10
  - When properties are defined at runtime (not compile-time) and may be multilanguage or dictionary-based
11
11
  - When you need to render and edit property values that can be text, boolean, number, datetime, dictionary selection, color picker, or measurement with units
12
- - When NOT to use: for static, compile-time form fields, use standard Vue reactive state. For simple key-value pairs without multilanguage/dictionary support, plain refs are sufficient.
12
+ - When NOT to use: for static, compile-time form fields, use standard Vue reactive state
13
13
 
14
14
  ## Quick Start
15
15
 
16
16
  ```typescript
17
- import { useDynamicProperties } from '@vc-shell/framework';
18
- import { PropertyValue, PropertyDictionaryItem } from '@core/api/platform';
19
-
20
- const { loading, loadDictionaries, getPropertyValue, setPropertyValue, loadMeasurements } =
21
- useDynamicProperties(
22
- (criteria) => api.searchPropertyDictionaryItems(criteria),
23
- PropertyValue,
24
- PropertyDictionaryItem,
25
- (measureId, locale) => api.searchMeasurements(measureId, locale),
26
- );
17
+ import { useDynamicProperties } from "@vc-shell/framework";
18
+
19
+ const { getPropertyValue, setPropertyValue, loadDictionaries, loadMeasurements, loading } =
20
+ useDynamicProperties({
21
+ searchDictionary: (criteria) => api.searchPropertyDictionaryItems(criteria),
22
+ searchMeasurements: (measureId, locale) => api.searchMeasurements(measureId, locale),
23
+ });
27
24
  ```
28
25
 
29
26
  ## API
30
27
 
31
- ### Parameters
28
+ ### Options
32
29
 
33
- | Parameter | Type | Required | Description |
34
- |-----------|------|----------|-------------|
35
- | `searchDictionaryItemsFunction` | `(criteria) => Promise<TPropertyDictionaryItem[] \| undefined>` | Yes | API function to search dictionary items by property ID and keyword. Called when the user opens a dictionary dropdown. |
36
- | `PropertyValueConstructor` | `new (data?) => TPropertyValue` | Yes | Constructor class for creating property value instances. Used when creating new values during `setPropertyValue`. |
37
- | `PropertyDictionaryItemConstructor` | `new (data?) => TPropertyDictionaryItem` | Yes | Constructor class for creating dictionary item instances. Used when localizing dictionary items. |
38
- | `searchMeasurementFunction` | `(measureId, locale?) => Promise<TMeasurement[] \| undefined>` | No | API function for loading measurement/unit-of-measure dictionaries. Only needed if you have measure-type properties. |
30
+ | Option | Type | Required | Description |
31
+ |--------|------|----------|-------------|
32
+ | `searchDictionary` | `(criteria: IBasePropertyDictionaryItemSearchCriteria) => Promise<IBasePropertyDictionaryItem[] \| undefined>` | Yes | API function to search dictionary items by property ID and keyword |
33
+ | `searchMeasurements` | `(measureId: string, locale?: string) => Promise<IBaseMeasurementDictionaryItem[] \| undefined>` | No | API function for loading measurement units. Only needed for measure-type properties |
39
34
 
40
35
  ### Returns
41
36
 
42
37
  | Property | Type | Description |
43
38
  |----------|------|-------------|
44
- | `loading` | `ComputedRef<boolean>` | Whether a dictionary or measurement lookup is in progress. |
45
- | `loadDictionaries` | `(propertyId, keyword?, locale?) => Promise<TPropertyDictionaryItem[] \| undefined>` | Loads dictionary items for a property. If `locale` is provided, resolves localized values from `localizedValues`. |
46
- | `getPropertyValue` | `(property, locale) => string \| TPropertyValue[] \| boolean` | Reads the display value for a property in the given locale. Returns string for simple values, array for multivalue, boolean for boolean type. |
47
- | `setPropertyValue` | `(params: SetPropertyValueParams) => void` | Writes a value to a property. Dispatches to specialized handlers based on property type (measure, color, dictionary, regular). |
48
- | `loadMeasurements` | `(measureId, keyword?, locale?) => Promise<TMeasurement[] \| undefined>` | Loads measurement units for a measure-type property. No-op if `searchMeasurementFunction` was not provided. |
39
+ | `getPropertyValue` | `(property, locale) => PropertyDisplayValue` | Read display value for a property. Returns string, boolean, or array depending on type. Does NOT mutate the property. |
40
+ | `setPropertyValue` | `(params: SetPropertyValueParams) => void` | Write value to a property. Handles type-specific transformation and empty cleanup. |
41
+ | `loadDictionaries` | `(propertyId, keyword?, locale?) => Promise<...>` | Load dictionary items. If locale is provided, resolves localized values. |
42
+ | `loadMeasurements` | `(measureId, keyword?, locale?) => Promise<...>` | Load measurement units. No-op if `searchMeasurements` was not provided. |
43
+ | `loading` | `ComputedRef<boolean>` | Whether a dictionary lookup is in progress. |
49
44
 
50
45
  ### SetPropertyValueParams
51
46
 
52
47
  | Field | Type | Description |
53
48
  |-------|------|-------------|
54
- | `property` | `TProperty` | The property object to update. Modified in place. |
55
- | `value` | `string \| TPropertyValue[] \| TPropertyDictionaryItem[]` | The new value. Type depends on property configuration. |
56
- | `dictionary` | `TPropertyDictionaryItem[]?` | Dictionary items for dictionary properties. Required when setting a dictionary value. |
49
+ | `property` | `IBaseProperty` | The property object to update. Modified in place. |
50
+ | `value` | `string \| IBasePropertyValue[] \| (IBasePropertyDictionaryItem & { value: string })[]` | The new value. Type depends on property configuration. |
51
+ | `dictionary` | `IBasePropertyDictionaryItem[]?` | Dictionary items. Required when setting a dictionary value. |
57
52
  | `locale` | `string?` | Current locale for multilanguage properties. |
58
- | `initialProp` | `TProperty?` | Original property state. Used for empty-value detection -- if the original was empty and the new value is empty, a placeholder is preserved; otherwise, the value array is cleared. |
59
53
  | `unitOfMeasureId` | `string?` | Unit of measure ID for measure-type properties. |
60
54
  | `colorCode` | `string?` | Color hex code for color-type properties. |
61
55
 
56
+ ### PropertyDisplayValue
57
+
58
+ ```ts
59
+ type PropertyDisplayValue = string | IBasePropertyValue[] | boolean;
60
+ ```
61
+
62
62
  ## How It Works
63
63
 
64
- ### Value Getting
64
+ ### Strategy Resolution
65
65
 
66
- `getPropertyValue` dispatches based on property flags:
66
+ The composable resolves a strategy handler based on property flags:
67
67
 
68
- - **Multilanguage**: Finds the value entry matching the locale. For multivalue, returns all entries for that locale as an array. For dictionary, returns the `valueId`. If no entry exists for the locale, creates a default entry using the first available alias.
69
- - **Single-language**: Returns the first value entry. For multivalue, returns all entries as an array. For dictionary, returns the `valueId`.
70
- - **Boolean**: Returns `false` if no value entry exists (instead of empty string).
68
+ | Priority | Condition | Strategy | Handles |
69
+ |----------|-----------|----------|---------|
70
+ | 1 | `valueType === "Measure"` | measureStrategy | Numeric input with unit dropdown |
71
+ | 2 | `valueType === "Color"` && !dictionary | colorStrategy | Color picker, multivalue colors |
72
+ | 3 | `valueType === "Boolean"` | booleanStrategy | Checkbox/switch |
73
+ | 4 | `dictionary === true` | dictionaryStrategy | Select dropdowns with localized options |
74
+ | 5 | (default) | regularStrategy | ShortText, LongText, Number, Integer, DateTime |
71
75
 
72
- ### Value Setting
76
+ ### Value Getting
73
77
 
74
- `setPropertyValue` checks property type in order:
75
- 1. **Measure** (`valueType === "Measure"`): Creates a single value entry with `unitOfMeasureId`.
76
- 2. **Color** (`valueType === "Color"`, no dictionary): Creates value entry/entries with `colorCode`.
77
- 3. **Dictionary**: Resolves the selected dictionary item(s), expanding `localizedValues` into per-locale value entries for multilanguage dictionaries.
78
- 4. **Regular**: Handles the remaining cases -- simple text, number, boolean, datetime.
78
+ Each strategy's `get()` reads from `property.values` without mutation:
79
79
 
80
- ### Dictionary Localization
80
+ - **Multilanguage**: Finds value matching locale, falls back to value without languageCode
81
+ - **Multivalue**: Returns full array (filtered by locale for multilanguage)
82
+ - **Dictionary**: Returns `valueId` instead of `value`
83
+ - **Boolean**: Returns `false` (not `""`) when no value exists
81
84
 
82
- When `loadDictionaries` is called with a locale, each dictionary item's `localizedValues` array is searched for the matching locale. The localized display value replaces the generic `alias` for UI rendering. This is why the function returns `Promise<TPropertyDictionaryItem[]>` -- the items are cloned and enriched.
85
+ ### Value Setting
83
86
 
84
- ## Recipe: Rendering a Dynamic Property Form
87
+ Each strategy's `set()` writes to `property.values`:
85
88
 
86
- ```vue
87
- <script setup lang="ts">
88
- import { useDynamicProperties, useLanguages } from '@vc-shell/framework';
89
- import { ref, watch } from 'vue';
90
-
91
- const { currentLocale } = useLanguages();
92
- const { loading, loadDictionaries, getPropertyValue, setPropertyValue } =
93
- useDynamicProperties(searchDictionaryItems, PropertyValue, PropertyDictionaryItem);
94
-
95
- const properties = ref<Property[]>([]);
96
-
97
- // Display values, keyed by property ID
98
- function displayValue(property: Property) {
99
- return getPropertyValue(property, currentLocale.value);
100
- }
101
-
102
- // Handle value change from a form input
103
- async function onValueChange(property: Property, newValue: string) {
104
- if (property.dictionary) {
105
- const dictItems = await loadDictionaries(property.id!, '', currentLocale.value);
106
- setPropertyValue({
107
- property,
108
- value: newValue,
109
- dictionary: dictItems ?? [],
110
- locale: currentLocale.value,
111
- });
112
- } else {
113
- setPropertyValue({
114
- property,
115
- value: newValue,
116
- locale: currentLocale.value,
117
- });
118
- }
119
- }
120
- </script>
121
- ```
89
+ - **Empty values are cleaned up automatically** via `cleanEmptyValues()`. When user clears a field, scaffolding objects are removed and `property.values` becomes `[]`. This prevents false modification detection in `useBladeForm`.
90
+ - **Multilanguage cleanup**: Only the value for the current locale is removed; other locales are preserved.
91
+ - **Dictionary**: Expands `localizedValues` into per-locale value entries.
122
92
 
123
- ## Recipe: Dictionary Property with Search-as-You-Type
93
+ ### Empty Value Cleanup (cleanEmptyValues)
124
94
 
125
- ```vue
126
- <script setup lang="ts">
127
- import { useDynamicProperties } from '@vc-shell/framework';
128
- import { ref } from 'vue';
95
+ When `setPropertyValue` receives an empty value (`""`, `null`, `undefined`), it cleans up `property.values` instead of leaving scaffolding objects:
129
96
 
130
- const { loadDictionaries } = useDynamicProperties(/* ... */);
97
+ ```
98
+ Before: values: [{ value: "", languageCode: "en", propertyId: "abc", ... }]
99
+ After: values: []
100
+ ```
131
101
 
132
- const dictOptions = ref<PropertyDictionaryItem[]>([]);
102
+ This ensures that `useBladeForm`'s deep comparison correctly detects "no change" when user clears a field that was originally empty.
133
103
 
134
- async function onDictionarySearch(property: Property, keyword: string, locale: string) {
135
- const items = await loadDictionaries(property.id!, keyword, locale);
136
- dictOptions.value = items ?? [];
137
- }
138
- </script>
104
+ ## Recipe: With VcDynamicProperty
139
105
 
106
+ ```vue
140
107
  <template>
141
- <VcSelect
142
- :options="dictOptions"
143
- option-value="id"
144
- option-label="value"
145
- @search="(keyword) => onDictionarySearch(property, keyword, currentLocale)"
146
- @update:model-value="(val) => onValueChange(property, val)"
108
+ <VcDynamicProperty
109
+ v-for="property in properties"
110
+ :key="property.id"
111
+ :property="property"
112
+ :model-value="getPropertyValue(property, currentLocale)"
113
+ :options-getter="loadDictionaries"
114
+ :measurements-getter="loadMeasurements"
115
+ :current-language="currentLocale"
116
+ :value-type="property.valueType ?? ''"
117
+ :dictionary="property.dictionary"
118
+ :multivalue="property.multivalue"
119
+ :multilanguage="property.multilanguage"
120
+ @update:model-value="(ev) => setPropertyValue({ property, ...ev })"
147
121
  />
148
122
  </template>
123
+
124
+ <script setup lang="ts">
125
+ import { useDynamicProperties } from "@vc-shell/framework";
126
+
127
+ const { getPropertyValue, setPropertyValue, loadDictionaries, loadMeasurements } =
128
+ useDynamicProperties({
129
+ searchDictionary: searchDictionaryItems,
130
+ searchMeasurements: searchMeasurementItems,
131
+ });
132
+ </script>
149
133
  ```
150
134
 
151
135
  ## Tips
152
136
 
153
- - **`setPropertyValue` mutates the property in place.** Unlike `useAssets` which returns new arrays, this composable modifies `property.values` directly. This is by design because dynamic properties are typically part of a larger entity object that gets saved as a whole.
154
- - **Always pass `dictionary` when setting dictionary values.** Without the dictionary items, the composable cannot resolve `valueId` to the correct alias and localized values. Omitting it results in incomplete value entries.
155
- - **Empty value handling depends on `initialProp`.** When the user clears a field, the behavior differs based on whether the property originally had a value. If it did, clearing sets `values: []` (explicit empty). If it was always empty, a placeholder value entry is preserved. This distinction matters for the platform's diff/save logic.
156
- - **Boolean properties always have a value entry.** Unlike other types where clearing removes the value, boolean properties always maintain a value entry (with `value: false`). This ensures checkboxes render correctly.
157
- - **The composable is generic but not abstract.** You must pass concrete constructor classes, not interfaces. The constructors are called with `new` to create value and dictionary item instances.
137
+ - **`setPropertyValue` mutates the property in place.** `property.values` is modified directly. This is by design because dynamic properties are typically part of a larger entity object saved as a whole.
138
+ - **Always pass `dictionary` when setting dictionary values.** Without dictionary items, the composable cannot resolve `valueId` to the correct alias and localized values.
139
+ - **Boolean properties always have a value entry.** Unlike other types where clearing removes the value, boolean properties maintain a value entry (`value: false`). This ensures checkboxes render correctly.
140
+ - **No class constructors needed.** Values are created as plain objects via `createValue()`. No factory classes required.
141
+
142
+ ## File Structure
143
+
144
+ ```
145
+ useDynamicProperties/
146
+ index.ts — composable entry point (~60 lines)
147
+ types.ts — all interfaces
148
+ utils.ts — isEmptyValue, createValue, cleanEmptyValues, type guards
149
+ strategies/
150
+ index.ts — resolveStrategy() registry
151
+ types.ts — PropertyValueStrategy interface
152
+ regular.ts — ShortText, LongText, Number, Integer, DateTime
153
+ boolean.ts — Boolean
154
+ dictionary.ts — Dictionary (all multi/single × language/value)
155
+ measure.ts — Measure
156
+ color.ts — Color
157
+ ```
158
158
 
159
159
  ## Related
160
160
 
161
- - [useLanguages](../useLanguages/useLanguages.docs.md) -- locale management for multilanguage property rendering
162
- - `IBaseProperty`, `IBasePropertyValue` -- base interfaces defined in the composable file
163
- - `PropertyValue`, `PropertyDictionaryItem` from `@core/api/platform` -- concrete types for the standard platform
161
+ - [useBladeForm](../useBladeForm/useBladeForm.docs.md) form state management that uses `semanticEqual` for modification detection. `cleanEmptyValues` ensures compatibility.
162
+ - [VcDynamicProperty](../../../../ui/components/organisms/vc-dynamic-property/vc-dynamic-property.docs.md) UI component that renders dynamic properties
@@ -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)