@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 +10 -0
- package/package.json +1 -1
- package/runtime/VERSION +1 -1
- package/runtime/knowledge/docs/_BUILD_HASH.md +1 -1
- package/runtime/knowledge/docs/core/composables/useBlade/useBlade.docs.md +76 -0
- package/runtime/knowledge/docs/core/composables/useDynamicProperties/useDynamicProperties.docs.md +104 -105
- package/runtime/knowledge/docs/core/composables/usePopup/usePopup.docs.md +195 -0
- package/runtime/knowledge/docs/core/composables/useResponsive/useResponsive.docs.md +178 -0
- package/runtime/knowledge/docs/core/composables/useSlowNetworkDetection/useSlowNetworkDetection.docs.md +4 -4
- package/runtime/knowledge/docs/ui/components/atoms/vc-image/vc-image.docs.md +1 -0
- package/runtime/knowledge/docs/ui/components/molecules/vc-image-tile/vc-image-tile.docs.md +1 -0
- package/runtime/knowledge/docs/ui/components/organisms/vc-blade/vc-blade.docs.md +27 -2
- package/runtime/knowledge/docs/ui/components/organisms/vc-gallery/vc-gallery.docs.md +55 -16
- package/runtime/knowledge/docs/core/composables/useBladeContext.docs.md +0 -111
- package/runtime/knowledge/docs/core/composables/useBladeWidgets.docs.md +0 -305
- package/runtime/knowledge/docs/core/composables/useGlobalSearch/useGlobalSearch.docs.md +0 -146
- package/runtime/knowledge/docs/core/composables/useMenuExpanded.docs.md +0 -83
|
@@ -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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# useSlowNetworkDetection
|
|
2
2
|
|
|
3
|
-
Detects slow network conditions and shows a persistent warning notification so users know why the UI is unresponsive. Two detection channels work together: a **proactive** channel reads `navigator.connection.effectiveType` to catch weak connections before any request is made, and a **reactive** channel flags API requests that have been pending for more than
|
|
3
|
+
Detects slow network conditions and shows a persistent warning notification so users know why the UI is unresponsive. Two detection channels work together: a **proactive** channel reads `navigator.connection.effectiveType` to catch weak connections before any request is made, and a **reactive** channel flags API requests that have been pending for more than 10 seconds. The notification auto-dismisses with a 3-second delay after conditions clear, preventing flicker. When the browser goes fully offline, the slow-network notification is suppressed in favor of the existing offline notification from `useConnectionStatus`.
|
|
4
4
|
|
|
5
5
|
Like `useConnectionStatus`, this is a module-level singleton — calling it from multiple components shares the same state and listeners.
|
|
6
6
|
|
|
@@ -41,14 +41,14 @@ const { isSlowNetwork } = useSlowNetworkDetection();
|
|
|
41
41
|
| Property | Type | Description |
|
|
42
42
|
|---|---|---|
|
|
43
43
|
| `isSlowNetwork` | `Readonly<Ref<boolean>>` | `true` when the network is slow (either channel active). Read-only. |
|
|
44
|
-
| `trackRequest` | `(id: string) => void` | Start tracking a request. If it isn't untracked within
|
|
44
|
+
| `trackRequest` | `(id: string) => void` | Start tracking a request. If it isn't untracked within 10 s, it counts as slow. |
|
|
45
45
|
| `untrackRequest` | `(id: string) => void` | Stop tracking a request. Cancels the timer or decrements the slow count. |
|
|
46
46
|
|
|
47
47
|
### Constants
|
|
48
48
|
|
|
49
49
|
| Name | Value | Purpose |
|
|
50
50
|
|---|---|---|
|
|
51
|
-
| `SLOW_REQUEST_THRESHOLD_MS` | `
|
|
51
|
+
| `SLOW_REQUEST_THRESHOLD_MS` | `10000` | Time before a pending request is considered slow |
|
|
52
52
|
| `DISMISS_DELAY_MS` | `3000` | Delay before hiding the notification after recovery |
|
|
53
53
|
| `SLOW_EFFECTIVE_TYPES` | `["slow-2g", "2g"]` | Connection types flagged as slow |
|
|
54
54
|
|
|
@@ -60,7 +60,7 @@ On first call, the composable checks `navigator.connection.effectiveType` (Netwo
|
|
|
60
60
|
|
|
61
61
|
### Channel 2: Request timers (reactive)
|
|
62
62
|
|
|
63
|
-
The fetch interceptor in `framework/core/interceptors/index.ts` calls `trackRequest(id)` before every `/api/*` request and `untrackRequest(id)` in the `finally` block. Each tracked request gets a
|
|
63
|
+
The fetch interceptor in `framework/core/interceptors/index.ts` calls `trackRequest(id)` before every `/api/*` request and `untrackRequest(id)` in the `finally` block. Each tracked request gets a 10-second timer. If the response arrives in time, the timer is cancelled. If not, `isSlowNetwork` becomes `true` and stays `true` until all slow requests complete.
|
|
64
64
|
|
|
65
65
|
### Notification lifecycle
|
|
66
66
|
|
|
@@ -28,6 +28,7 @@ An image display component with predefined sizes, aspect ratio control, and a pl
|
|
|
28
28
|
| `clickable` | `boolean` | `false` | Makes the image interactive with cursor and click event |
|
|
29
29
|
| `emptyIcon` | `string` | `"lucide-image"` | Icon shown when `src` is empty |
|
|
30
30
|
| `alt` | `string` | — | Accessible alt text |
|
|
31
|
+
| `thumbnailSize` | `ThumbnailSize` | — | Load a thumbnail variant instead of full-size image. Values: `"sm"`, `"md"`, `"lg"`, `"64x64"`, `"128x128"`, `"168x168"`, `"216x216"`, `"348x348"` |
|
|
31
32
|
|
|
32
33
|
## Size Reference
|
|
33
34
|
|
|
@@ -40,6 +40,7 @@ function deleteImage() { /* remove from list */ }
|
|
|
40
40
|
| `name` | `string` | — | File name displayed in the tray |
|
|
41
41
|
| `imageFit` | `"contain" \| "cover"` | `"contain"` | How the image fits within the tile |
|
|
42
42
|
| `actions` | `VcImageTileActions` | — | Which built-in action buttons to show |
|
|
43
|
+
| `thumbnailSize` | `ThumbnailSize` | — | Load a thumbnail variant instead of full-size image |
|
|
43
44
|
|
|
44
45
|
## VcImageTileActions Interface
|
|
45
46
|
|
|
@@ -62,7 +62,7 @@ A blade has four visual zones, rendered top-to-bottom:
|
|
|
62
62
|
|--------------------------------------|
|
|
63
63
|
| [Save] [Delete] [Refresh] [More >] | <-- Toolbar
|
|
64
64
|
|--------------------------------------|
|
|
65
|
-
| **
|
|
65
|
+
| ** Status banners (stacked) ** | <-- Status Banners
|
|
66
66
|
|--------------------------------------|
|
|
67
67
|
| |
|
|
68
68
|
| Content (default slot) | <-- Content Area
|
|
@@ -74,7 +74,7 @@ A blade has four visual zones, rendered top-to-bottom:
|
|
|
74
74
|
|
|
75
75
|
**Toolbar** -- Action buttons from the `toolbarItems` prop. Overflow items automatically collapse into a "More" dropdown (via `ResizeObserver`).
|
|
76
76
|
|
|
77
|
-
**Status Banners** --
|
|
77
|
+
**Status Banners** -- Unified, priority-sorted banner area. System banners: yellow when `modified` is `true`, red when the blade has an error (via `setError()`). Custom banners can be added programmatically via `useBlade().addBanner()` — see [useBlade docs](../../../core/composables/useBlade/useBlade.docs.md#banner-management).
|
|
78
78
|
|
|
79
79
|
**Content Area** -- The `default` slot. Scrolls independently of header and toolbar.
|
|
80
80
|
|
|
@@ -399,6 +399,31 @@ import { useBeforeUnload } from "@vc-shell/framework";
|
|
|
399
399
|
useBeforeUnload(hasChanges); // Browser tab close warning
|
|
400
400
|
```
|
|
401
401
|
|
|
402
|
+
## Custom Banners
|
|
403
|
+
|
|
404
|
+
Add informational, warning, or success banners to a blade programmatically. Banners appear between the header and toolbar, sorted by severity.
|
|
405
|
+
|
|
406
|
+
```vue
|
|
407
|
+
<script setup lang="ts">
|
|
408
|
+
import { useBlade } from "@vc-shell/framework";
|
|
409
|
+
|
|
410
|
+
const { addBanner, removeBanner, clearBanners } = useBlade();
|
|
411
|
+
|
|
412
|
+
// Info banner (e.g. read-only mode)
|
|
413
|
+
addBanner({ variant: "info", message: "This record is read-only" });
|
|
414
|
+
|
|
415
|
+
// Dismissible warning with action
|
|
416
|
+
addBanner({
|
|
417
|
+
variant: "warning",
|
|
418
|
+
message: "License expires in 7 days",
|
|
419
|
+
dismissible: true,
|
|
420
|
+
action: { label: "Renew", handler: () => openRenewal() },
|
|
421
|
+
});
|
|
422
|
+
</script>
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
Four variants are available: `danger`, `warning`, `info`, `success`. System banners (error and unsaved changes) are always present and cannot be removed by `clearBanners()`. For the full API reference, see [useBlade — Banner Management](../../../core/composables/useBlade/useBlade.docs.md#banner-management).
|
|
426
|
+
|
|
402
427
|
## Blade Width Control
|
|
403
428
|
|
|
404
429
|
```vue
|
|
@@ -13,17 +13,21 @@ A responsive multi-image gallery with drag-and-drop reorder, file upload, lightb
|
|
|
13
13
|
|
|
14
14
|
| Prop | Type | Default | Description |
|
|
15
15
|
|------|------|---------|-------------|
|
|
16
|
+
| `layout` | `"filmstrip" \| "grid"` | `"filmstrip"` | Layout mode — filmstrip shows a single scrollable row with expand/collapse; grid shows the classic multi-row auto-fill layout. |
|
|
17
|
+
| `label` | `string` | `undefined` | Label text displayed in the gallery header. |
|
|
18
|
+
| `required` | `boolean` | `false` | Shows a required indicator (`*`) on the label. |
|
|
16
19
|
| `images` | `ICommonAsset[]` | `[]` | Array of image assets to display. |
|
|
17
20
|
| `disabled` | `boolean` | `false` | Disables all interactive actions. |
|
|
18
21
|
| `multiple` | `boolean` | `false` | Allow selecting multiple files in upload dialog. |
|
|
19
|
-
| `loading` | `boolean` | `false` | Shows a loading spinner on the
|
|
22
|
+
| `loading` | `boolean` | `false` | Shows a loading overlay with spinner on the gallery. |
|
|
20
23
|
| `itemActions` | `{ preview?: boolean; edit?: boolean; remove?: boolean }` | `{ preview: true, edit: true, remove: true }` | Per-tile action visibility. |
|
|
21
24
|
| `rules` | `IValidationRules` | `undefined` | Validation rules for uploaded files. |
|
|
22
25
|
| `name` | `string` | `"Gallery"` | Field name for validation messages. |
|
|
23
26
|
| `accept` | `string` | `undefined` | Accepted file extensions. |
|
|
24
|
-
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tile size preset. |
|
|
27
|
+
| `size` | `"sm" \| "md" \| "lg"` | `"md"` | Tile size preset. Sizes are smaller on mobile. |
|
|
25
28
|
| `gap` | `number` | `8` | Gap between tiles in pixels. |
|
|
26
29
|
| `imagefit` | `"contain" \| "cover"` | `"contain"` | How images fit within tiles. |
|
|
30
|
+
| `thumbnailSize` | `ThumbnailSize` | auto from `size` | Thumbnail size for tile images. Auto-mapped: sm→128x128, md→216x216, lg→348x348. Preview thumbnails use 64x64. |
|
|
27
31
|
|
|
28
32
|
## Events
|
|
29
33
|
|
|
@@ -44,20 +48,24 @@ A responsive multi-image gallery with drag-and-drop reorder, file upload, lightb
|
|
|
44
48
|
|
|
45
49
|
## Features
|
|
46
50
|
|
|
47
|
-
- **
|
|
48
|
-
- **
|
|
49
|
-
- **
|
|
50
|
-
- **
|
|
51
|
-
- **
|
|
51
|
+
- **Filmstrip layout (default)** -- Single-row scrollable strip powered by Swiper. Navigate with arrows, mouse wheel, or swipe. Click "Expand (N)" to show all images in a grid. "Collapse" returns to filmstrip.
|
|
52
|
+
- **Grid layout** -- Classic auto-fill grid that wraps images into multiple rows. Set `layout="grid"` to use this mode.
|
|
53
|
+
- **Drag-and-drop reorder** -- Drag tiles by the grip handle to reorder (powered by SortableJS). Works on both desktop and touch devices. In filmstrip mode, dragging to the edge auto-scrolls the strip. Emits `sort` with the new array.
|
|
54
|
+
- **External file drop** -- Drop files from the OS onto the gallery to upload. The dashed border acts as a visual drop zone indicator (desktop only). A full overlay appears during drag-over.
|
|
55
|
+
- **Fullscreen preview** -- Click the preview button to open a fullscreen carousel. Swipe or use arrow keys to navigate. Thumbnail strip at the bottom syncs with the main image.
|
|
56
|
+
- **Per-tile actions** -- Each tile shows preview, edit, and remove buttons. On desktop: visible on hover. On mobile: visible on tap. Tile name is shown in the top bar alongside the drag handle.
|
|
57
|
+
- **Loading state** -- When `loading` is true, a pulsing border and spinner overlay appear on the gallery. Upload button is disabled. Swiper navigation is frozen.
|
|
58
|
+
- **Mobile responsive** -- Smaller tile sizes, no drag-and-drop hints, no dashed borders, compact action buttons, tap-to-reveal overlays. Uses `useResponsive` throughout.
|
|
59
|
+
- **Lazy loading** -- Images use native `loading="lazy"` for deferred loading.
|
|
52
60
|
|
|
53
61
|
## Basic Usage
|
|
54
62
|
|
|
55
63
|
```vue
|
|
56
64
|
<VcGallery
|
|
65
|
+
label="Product Images"
|
|
66
|
+
required
|
|
57
67
|
:images="product.images"
|
|
58
|
-
size="md"
|
|
59
68
|
imagefit="cover"
|
|
60
|
-
:item-actions="{ preview: true, edit: true, remove: true }"
|
|
61
69
|
@upload="handleUpload"
|
|
62
70
|
@sort="handleSort"
|
|
63
71
|
@edit="handleEdit"
|
|
@@ -65,6 +73,31 @@ A responsive multi-image gallery with drag-and-drop reorder, file upload, lightb
|
|
|
65
73
|
/>
|
|
66
74
|
```
|
|
67
75
|
|
|
76
|
+
## Filmstrip Layout (Default)
|
|
77
|
+
|
|
78
|
+
```vue
|
|
79
|
+
<VcGallery
|
|
80
|
+
label="Images"
|
|
81
|
+
:images="product.images"
|
|
82
|
+
imagefit="cover"
|
|
83
|
+
@upload="handleUpload"
|
|
84
|
+
@sort="handleSort"
|
|
85
|
+
@remove="handleRemove"
|
|
86
|
+
/>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Classic Grid Layout
|
|
90
|
+
|
|
91
|
+
```vue
|
|
92
|
+
<VcGallery
|
|
93
|
+
layout="grid"
|
|
94
|
+
label="Attachments"
|
|
95
|
+
:images="product.images"
|
|
96
|
+
@upload="handleUpload"
|
|
97
|
+
@sort="handleSort"
|
|
98
|
+
/>
|
|
99
|
+
```
|
|
100
|
+
|
|
68
101
|
## Recipe: Product Image Gallery in a Blade
|
|
69
102
|
|
|
70
103
|
```vue
|
|
@@ -99,6 +132,8 @@ function handleRemove(image: ICommonAsset) {
|
|
|
99
132
|
<template>
|
|
100
133
|
<VcBlade title="Product Images">
|
|
101
134
|
<VcGallery
|
|
135
|
+
label="Product Images"
|
|
136
|
+
required
|
|
102
137
|
:images="images"
|
|
103
138
|
multiple
|
|
104
139
|
accept=".jpg,.png,.webp"
|
|
@@ -152,20 +187,24 @@ function handleRemove(image: ICommonAsset) {
|
|
|
152
187
|
|
|
153
188
|
## Tips
|
|
154
189
|
|
|
155
|
-
- The `size` prop controls tile dimensions: `"sm"` is good for compact grids (e.g., thumbnails in a sidebar), `"md"` is the standard, and `"lg"` works well for hero image management.
|
|
190
|
+
- The `size` prop controls tile dimensions: `"sm"` is good for compact grids (e.g., thumbnails in a sidebar), `"md"` is the standard, and `"lg"` works well for hero image management. Tiles are automatically smaller on mobile.
|
|
156
191
|
- Use `imagefit="cover"` for photo galleries where cropping is acceptable, and `"contain"` for logos or icons where the full image must be visible.
|
|
157
|
-
- The
|
|
192
|
+
- The filmstrip layout is ideal for blades where vertical space is limited. Users can expand to see all images or scroll horizontally. The classic grid layout is better for dedicated media management pages.
|
|
193
|
+
- Use the `label` prop to display a header label integrated with the upload button. Add `required` to show a required indicator.
|
|
158
194
|
- The `startingSortOrder` parameter in the `upload` event tells you where the new files should be inserted in the sort order. Use it to maintain correct ordering when appending new images.
|
|
195
|
+
- On desktop, reorder by dragging the grip handle icon. In filmstrip mode, dragging to the strip edge auto-scrolls to reveal more tiles.
|
|
196
|
+
- On mobile, tap a tile to reveal action buttons and the image name. Drag-and-drop hints and dashed borders are hidden automatically.
|
|
159
197
|
|
|
160
198
|
## Accessibility
|
|
161
199
|
|
|
162
200
|
- Tiles are keyboard-navigable with Tab and action buttons are focusable
|
|
163
|
-
-
|
|
164
|
-
- The upload
|
|
165
|
-
-
|
|
201
|
+
- Fullscreen preview supports keyboard navigation (arrow keys, Escape to close) and swipe gestures
|
|
202
|
+
- The upload button in the header is keyboard-accessible
|
|
203
|
+
- On mobile, tile actions are revealed via tap with click-outside to dismiss
|
|
166
204
|
|
|
167
205
|
## Related Components
|
|
168
206
|
|
|
169
207
|
- **VcImageUpload** -- single-image upload component
|
|
170
|
-
- **VcImageTile** -- the internal tile component used for each image
|
|
171
|
-
- **
|
|
208
|
+
- **VcImageTile** -- the internal tile component used for each image (topbar with name + drag handle, bottom tray with actions)
|
|
209
|
+
- **VcFileUpload** -- the file upload drop zone used in empty gallery state
|
|
210
|
+
- **VcLabel** -- used internally when `label` prop is set
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
# useBladeContext (defineBladeContext / injectBladeContext)
|
|
2
|
-
|
|
3
|
-
Exposes blade-level data to descendant widgets, extensions, and nested components via Vue's provide/inject mechanism. This pair of functions eliminates the need for prop drilling when child widgets or extension points need access to the parent blade's entity data, loading flags, or other shared state.
|
|
4
|
-
|
|
5
|
-
The pattern follows a "define once, inject anywhere" approach: the blade component calls `defineBladeContext` during setup, and any descendant (no matter how deeply nested) can call `injectBladeContext` to read that data reactively.
|
|
6
|
-
|
|
7
|
-
## When to Use
|
|
8
|
-
|
|
9
|
-
- Share blade state (current entity, loading flags, disabled state) with child widgets without prop drilling
|
|
10
|
-
- Access parent blade data from an extension or widget component
|
|
11
|
-
- Expose selective fields to widgets (e.g., only the entity ID) via a computed getter
|
|
12
|
-
- When NOT to use: for cross-blade communication between sibling blades (use `useBlade` / blade messaging instead)
|
|
13
|
-
|
|
14
|
-
## Basic Usage
|
|
15
|
-
|
|
16
|
-
```typescript
|
|
17
|
-
// In a blade's <script setup>
|
|
18
|
-
import { defineBladeContext, injectBladeContext } from '@vc-shell/framework';
|
|
19
|
-
|
|
20
|
-
// Provide context — refs/computeds are auto-unwrapped for consumers
|
|
21
|
-
defineBladeContext({ item, disabled, loading });
|
|
22
|
-
|
|
23
|
-
// Or with a computed for selective exposure
|
|
24
|
-
defineBladeContext(computed(() => ({ id: item.value?.id })));
|
|
25
|
-
```
|
|
26
|
-
|
|
27
|
-
```typescript
|
|
28
|
-
// In a widget or nested component
|
|
29
|
-
import { injectBladeContext } from '@vc-shell/framework';
|
|
30
|
-
|
|
31
|
-
const ctx = injectBladeContext();
|
|
32
|
-
// Refs are already unwrapped — access values directly, no .value needed
|
|
33
|
-
const entityId = computed(() => ctx.value.id as string);
|
|
34
|
-
const item = computed(() => ctx.value.item as { id: string; name: string });
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
## API
|
|
38
|
-
|
|
39
|
-
### defineBladeContext
|
|
40
|
-
|
|
41
|
-
| Parameter | Type | Required | Description |
|
|
42
|
-
|---|---|---|---|
|
|
43
|
-
| `data` | `MaybeRefOrGetter<Record<string, unknown>>` | Yes | Plain object, ref, or getter to expose |
|
|
44
|
-
|
|
45
|
-
Returns `void`. Must be called in the blade's `<script setup>`.
|
|
46
|
-
|
|
47
|
-
### injectBladeContext
|
|
48
|
-
|
|
49
|
-
Takes no parameters. Returns `ComputedRef<Record<string, unknown>>`.
|
|
50
|
-
|
|
51
|
-
Throws `InjectionError` if no ancestor blade has called `defineBladeContext`.
|
|
52
|
-
|
|
53
|
-
## Recipe: Widget Consuming Blade Context
|
|
54
|
-
|
|
55
|
-
A typical pattern is a sidebar widget that needs to load related data based on the current blade entity. The widget does not receive any props from the blade -- it reads the entity ID from the blade context:
|
|
56
|
-
|
|
57
|
-
```vue
|
|
58
|
-
<script setup lang="ts">
|
|
59
|
-
// widgets/RelatedOrdersWidget.vue
|
|
60
|
-
import { computed, watch } from "vue";
|
|
61
|
-
import { injectBladeContext } from "@vc-shell/framework";
|
|
62
|
-
|
|
63
|
-
const ctx = injectBladeContext();
|
|
64
|
-
const customerId = computed(() => ctx.value.id as string | undefined);
|
|
65
|
-
|
|
66
|
-
// Reload orders whenever the customer changes
|
|
67
|
-
watch(customerId, async (id) => {
|
|
68
|
-
if (id) {
|
|
69
|
-
await loadOrders(id);
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
</script>
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
```vue
|
|
76
|
-
<script setup lang="ts">
|
|
77
|
-
// blades/CustomerDetailBlade.vue
|
|
78
|
-
import { ref, computed } from "vue";
|
|
79
|
-
import { defineBladeContext } from "@vc-shell/framework";
|
|
80
|
-
|
|
81
|
-
const customer = ref({ id: "cust-1", name: "Acme Corp" });
|
|
82
|
-
const loading = ref(false);
|
|
83
|
-
|
|
84
|
-
// Expose the customer data to all descendant widgets
|
|
85
|
-
defineBladeContext(computed(() => ({
|
|
86
|
-
id: customer.value?.id,
|
|
87
|
-
name: customer.value?.name,
|
|
88
|
-
loading: loading.value,
|
|
89
|
-
})));
|
|
90
|
-
</script>
|
|
91
|
-
```
|
|
92
|
-
|
|
93
|
-
## Details
|
|
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.
|
|
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.
|
|
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.
|
|
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`.
|
|
99
|
-
- **Scope**: The context is scoped to the Vue component subtree. Each blade in the stack has its own context, so nested blades do not leak data upward or sideways.
|
|
100
|
-
|
|
101
|
-
## Tips
|
|
102
|
-
|
|
103
|
-
- Prefer exposing a computed getter rather than the full reactive object when only a subset of fields is needed. This minimizes unnecessary re-renders in consuming widgets.
|
|
104
|
-
- The context value is untyped (`Record<string, unknown>`). Use type assertions or a typed wrapper in your module if you need type safety (e.g., `ctx.value.id as string`).
|
|
105
|
-
- If a blade does not call `defineBladeContext`, any descendant calling `injectBladeContext` will throw. Make sure all blades that host widgets define their context.
|
|
106
|
-
|
|
107
|
-
## Related
|
|
108
|
-
|
|
109
|
-
- `BladeContextKey` in `framework/injection-keys.ts`
|
|
110
|
-
- `useBladeWidgets` -- widgets that consume blade context
|
|
111
|
-
- `useBladeStack` -- manages the blade navigation stack
|