@vc-shell/framework 1.2.4-beta.5 → 1.2.4-beta.7

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.
Files changed (57) hide show
  1. package/core/utilities/date/formatDate.ts +1 -1
  2. package/dist/{DashboardBarChart-B-g_a-7F.js → DashboardBarChart-BzfKkUke.js} +1 -1
  3. package/dist/{DashboardDonutChart-AktPFUNo.js → DashboardDonutChart-CWfe85Xq.js} +1 -1
  4. package/dist/{DashboardLineChart-BQKqRFhM.js → DashboardLineChart-kdA8VnrR.js} +1 -1
  5. package/dist/{GridstackDashboard-BXqCpiMw.js → GridstackDashboard-CGHYkReX.js} +1 -1
  6. package/dist/framework.js +1 -1
  7. package/dist/{index-DVyGELzS.js → index-tgmgQAD9.js} +6839 -6798
  8. package/dist/index.css +1 -1
  9. package/dist/locales/de.json +2 -0
  10. package/dist/locales/en.json +2 -0
  11. package/dist/shared/components/user-dropdown-button/_internal/user-info.vue.d.ts.map +1 -1
  12. package/dist/shared/pages/_storybook-helpers.d.ts +11 -0
  13. package/dist/shared/pages/_storybook-helpers.d.ts.map +1 -0
  14. package/dist/tsconfig.tsbuildinfo +1 -1
  15. package/dist/ui/components/organisms/vc-app/_internal/menu/VcAppMenu.vue.d.ts.map +1 -1
  16. package/dist/ui/components/organisms/vc-blade/_internal/toolbar/ToolbarMobile.vue.d.ts.map +1 -1
  17. package/dist/ui/components/organisms/vc-popup/vc-popup.vue.d.ts.map +1 -1
  18. package/dist/ui/components/organisms/vc-sidebar/vc-sidebar.vue.d.ts.map +1 -1
  19. package/dist/ui/components/organisms/vc-table/VcDataTable.vue.d.ts +5 -2
  20. package/dist/ui/components/organisms/vc-table/VcDataTable.vue.d.ts.map +1 -1
  21. package/dist/ui/components/organisms/vc-table/VcTableAdapter.vue.d.ts.map +1 -1
  22. package/dist/ui/components/organisms/vc-table/base/BaseVcDataTable.d.ts +9 -1
  23. package/dist/ui/components/organisms/vc-table/base/BaseVcDataTable.d.ts.map +1 -1
  24. package/dist/ui/components/organisms/vc-table/components/DataTableBody.vue.d.ts +7 -0
  25. package/dist/ui/components/organisms/vc-table/components/DataTableBody.vue.d.ts.map +1 -1
  26. package/dist/ui/components/organisms/vc-table/components/DataTableRow.vue.d.ts +2 -0
  27. package/dist/ui/components/organisms/vc-table/components/DataTableRow.vue.d.ts.map +1 -1
  28. package/dist/ui/components/organisms/vc-table/components/TableEmpty.vue.d.ts +7 -9
  29. package/dist/ui/components/organisms/vc-table/components/TableEmpty.vue.d.ts.map +1 -1
  30. package/dist/ui/components/organisms/vc-table/components/TableRow.vue.d.ts +4 -0
  31. package/dist/ui/components/organisms/vc-table/components/TableRow.vue.d.ts.map +1 -1
  32. package/dist/ui/components/organisms/vc-table/types.d.ts +21 -0
  33. package/dist/ui/components/organisms/vc-table/types.d.ts.map +1 -1
  34. package/dist/{vc-editor-BtJrxrBg.js → vc-editor-BNrG1GAG.js} +1 -1
  35. package/dist/{vc-slider-sUKMaKnc.js → vc-slider-Ce0X1_1m.js} +1 -1
  36. package/package.json +5 -5
  37. package/shared/components/user-dropdown-button/_internal/user-info.vue +26 -13
  38. package/shared/pages/ChangePasswordPage/components/change-password/change-password.stories.ts +44 -0
  39. package/shared/pages/ForgotPasswordPage/components/forgot-password/forgot-password.stories.ts +38 -0
  40. package/shared/pages/InvitePage/components/invite/invite.stories.ts +64 -0
  41. package/shared/pages/LoginPage/components/login/login.stories.ts +52 -0
  42. package/shared/pages/ResetPasswordPage/components/reset-password/reset-password.stories.ts +64 -0
  43. package/shared/pages/_storybook-helpers.ts +71 -0
  44. package/ui/components/molecules/vc-select/vc-select.vue +1 -1
  45. package/ui/components/organisms/vc-app/_internal/menu/VcAppMenu.vue +1 -2
  46. package/ui/components/organisms/vc-blade/_internal/toolbar/ToolbarMobile.vue +18 -22
  47. package/ui/components/organisms/vc-popup/vc-popup.vue +3 -4
  48. package/ui/components/organisms/vc-sidebar/vc-sidebar.vue +12 -10
  49. package/ui/components/organisms/vc-table/VcDataTable.vue +58 -11
  50. package/ui/components/organisms/vc-table/VcTableAdapter.vue +31 -84
  51. package/ui/components/organisms/vc-table/base/BaseVcDataTable.ts +10 -0
  52. package/ui/components/organisms/vc-table/components/DataTableBody.vue +10 -0
  53. package/ui/components/organisms/vc-table/components/DataTableRow.vue +3 -0
  54. package/ui/components/organisms/vc-table/components/TableEmpty.vue +18 -12
  55. package/ui/components/organisms/vc-table/components/TableRow.vue +5 -1
  56. package/ui/components/organisms/vc-table/types.ts +22 -0
  57. package/ui/components/organisms/vc-table/vc-data-table.stories.ts +205 -0
@@ -9,7 +9,7 @@
9
9
  :selection="internalSelection"
10
10
  :is-row-selectable="rowSelectable"
11
11
  :row-actions="effectiveRowActions"
12
- :row-class="rowClassWithHighlight"
12
+ :active-item-id="selectedItemId ? toValue(selectedItemId) : undefined"
13
13
  :loading="loadingValue"
14
14
  :resizable-columns="props.resizableColumns"
15
15
  :reorderable-columns="props.reorderableColumns"
@@ -28,6 +28,8 @@
28
28
  :pagination="paginationConfig"
29
29
  :add-row="addRowConfig"
30
30
  :variant="props.variant"
31
+ :empty-state="mappedEmptyState"
32
+ :not-found-state="mappedNotFoundState"
31
33
  @sort="handleSort"
32
34
  @update:selection="handleSelectionUpdate"
33
35
  @row-click="handleRowClick"
@@ -107,52 +109,12 @@
107
109
  />
108
110
  </template>
109
111
 
110
- <!-- Empty / NotFound -->
111
- <template #empty>
112
- <template v-if="isNotFoundState && ($slots.notfound || props.notfound)">
113
- <slot name="notfound">
114
- <div
115
- v-if="props.notfound"
116
- class="vc-table-adapter__empty-state"
117
- >
118
- <VcIcon
119
- v-if="props.notfound.icon"
120
- :icon="props.notfound.icon"
121
- size="xxl"
122
- />
123
- <p class="vc-table-adapter__empty-text">{{ toValue(props.notfound.text) }}</p>
124
- <a
125
- v-if="props.notfound.action"
126
- class="vc-table-adapter__empty-action"
127
- @click="props.notfound.clickHandler?.()"
128
- >
129
- {{ toValue(props.notfound.action) }}
130
- </a>
131
- </div>
132
- </slot>
133
- </template>
134
- <template v-else>
135
- <slot name="empty">
136
- <div
137
- v-if="props.empty"
138
- class="vc-table-adapter__empty-state"
139
- >
140
- <VcIcon
141
- v-if="props.empty.icon"
142
- :icon="props.empty.icon"
143
- size="xxl"
144
- />
145
- <p class="vc-table-adapter__empty-text">{{ toValue(props.empty.text) }}</p>
146
- <a
147
- v-if="props.empty.action"
148
- class="vc-table-adapter__empty-action"
149
- @click="props.empty.clickHandler?.()"
150
- >
151
- {{ toValue(props.empty.action) }}
152
- </a>
153
- </div>
154
- </slot>
155
- </template>
112
+ <!-- Empty / NotFound — delegate rendering to VcDataTable via props, passthrough custom slots -->
113
+ <template v-if="$slots.notfound" #not-found>
114
+ <slot name="notfound" />
115
+ </template>
116
+ <template v-if="$slots.empty" #empty>
117
+ <slot name="empty" />
156
118
  </template>
157
119
 
158
120
  <!-- Loading -->
@@ -200,8 +162,7 @@ import VcDataTable from "@ui/components/organisms/vc-table/VcDataTable.vue";
200
162
  import VcColumn from "@ui/components/organisms/vc-table/components/VcColumn.vue";
201
163
  import { GlobalFiltersButton } from "@ui/components/organisms/vc-table/components";
202
164
  import { VcDropdownPanel } from "@ui/components/molecules";
203
- import { VcIcon } from "@ui/components/atoms";
204
- import type { TableAction, TableEmptyAction, DataTablePagination, AddRowConfig } from "@ui/components/organisms/vc-table/types";
165
+ import type { TableAction, TableEmptyAction, DataTablePagination, AddRowConfig, TableStateConfig } from "@ui/components/organisms/vc-table/types";
205
166
  import type { ITableColumns, IActionBuilderResult } from "@core/types";
206
167
 
207
168
  // ============================================================================
@@ -383,16 +344,6 @@ const effectiveRowActions = computed<((item: T) => TableAction<T>[]) | undefined
383
344
  return (item: T) => (props.itemActionBuilder!(item) ?? []) as TableAction<T>[];
384
345
  });
385
346
 
386
- // Row class: apply highlight for selectedItemId
387
- const rowClassWithHighlight = computed(() => {
388
- const selectedId = toValue(props.selectedItemId);
389
- if (!selectedId) return undefined;
390
- return (data: T) => {
391
- const rowId = String((data as any).id ?? "");
392
- return rowId === selectedId ? "vc-data-table__row--highlighted" : "";
393
- };
394
- });
395
-
396
347
  // Pagination
397
348
  const paginationConfig = computed<DataTablePagination | undefined>(() => {
398
349
  if (props.footer === false || !props.pages) return undefined;
@@ -416,6 +367,27 @@ const isNotFoundState = computed(
416
367
  () => props.items.length === 0 && (!!props.searchValue || (props.activeFilterCount ?? 0) > 0),
417
368
  );
418
369
 
370
+ /** Map legacy TableEmptyAction → TableStateConfig for VcDataTable */
371
+ const mappedEmptyState = computed<TableStateConfig | undefined>(() => {
372
+ if (!props.empty) return undefined;
373
+ return {
374
+ icon: props.empty.icon,
375
+ title: typeof props.empty.text === "string" ? props.empty.text : toValue(props.empty.text),
376
+ actionLabel: props.empty.action ? (typeof props.empty.action === "string" ? props.empty.action : toValue(props.empty.action)) : undefined,
377
+ actionHandler: props.empty.clickHandler,
378
+ };
379
+ });
380
+
381
+ const mappedNotFoundState = computed<TableStateConfig | undefined>(() => {
382
+ if (!props.notfound) return undefined;
383
+ return {
384
+ icon: props.notfound.icon,
385
+ title: typeof props.notfound.text === "string" ? props.notfound.text : toValue(props.notfound.text),
386
+ actionLabel: props.notfound.action ? (typeof props.notfound.action === "string" ? props.notfound.action : toValue(props.notfound.action)) : undefined,
387
+ actionHandler: props.notfound.clickHandler,
388
+ };
389
+ });
390
+
419
391
  // Loading: unwrap MaybeRef
420
392
  const loadingValue = computed(() => toValue(props.loading) ?? false);
421
393
 
@@ -598,30 +570,5 @@ function handleAddRow(event: { defaults: Record<string, unknown>; cancel: () =>
598
570
  <style lang="scss">
599
571
  .vc-table-adapter {
600
572
  @apply tw-flex tw-flex-col tw-grow tw-basis-0 tw-overflow-hidden;
601
-
602
- &__empty-state {
603
- @apply tw-flex tw-flex-col tw-items-center tw-justify-center tw-py-8 tw-gap-3;
604
- color: var(--neutrals-500);
605
- }
606
-
607
- &__empty-text {
608
- @apply tw-text-sm tw-text-center;
609
- color: var(--neutrals-600);
610
- }
611
-
612
- &__empty-action {
613
- @apply tw-text-sm tw-cursor-pointer tw-underline;
614
- color: var(--primary-500);
615
-
616
- &:hover {
617
- color: var(--primary-700);
618
- }
619
- }
620
- }
621
-
622
- /* Row highlight for selectedItemId support.
623
- * No !important — allows hover (higher specificity) to override naturally. */
624
- .vc-data-table__row--highlighted {
625
- background-color: var(--primary-50);
626
573
  }
627
574
  </style>
@@ -267,6 +267,15 @@ export const baseVcDataTableProps = {
267
267
  default: undefined,
268
268
  },
269
269
 
270
+ /**
271
+ * ID of the currently active (highlighted) row.
272
+ * Use v-model:activeItemId for two-way binding.
273
+ */
274
+ activeItemId: {
275
+ type: String,
276
+ default: undefined,
277
+ },
278
+
270
279
  // ============================================================================
271
280
  // COLUMN FEATURES
272
281
  // ============================================================================
@@ -499,6 +508,7 @@ export const vcDataTableEmits = [
499
508
  "filter",
500
509
 
501
510
  // Row interactions
511
+ "update:activeItemId",
502
512
  "row-click",
503
513
  "row-dblclick",
504
514
  "row-contextmenu",
@@ -7,8 +7,11 @@
7
7
  <template v-if="items.length === 0 && !loading">
8
8
  <slot name="empty">
9
9
  <TableEmpty
10
+ :icon="emptyIcon"
10
11
  :title="emptyTitle"
11
12
  :description="emptyDescription"
13
+ :action-label="emptyActionLabel"
14
+ :action-handler="emptyActionHandler"
12
15
  />
13
16
  </slot>
14
17
  </template>
@@ -141,6 +144,7 @@ export interface RowProps<T> {
141
144
  index: number;
142
145
  columns: ColumnInstance[];
143
146
  isSelected: boolean;
147
+ isActive: boolean;
144
148
  isSelectable: boolean;
145
149
  selectionMode?: "single" | "multiple";
146
150
  showSelectionCell: boolean;
@@ -172,6 +176,12 @@ const props = defineProps<{
172
176
  emptyTitle?: string;
173
177
  /** Empty state description */
174
178
  emptyDescription?: string;
179
+ /** Empty state icon */
180
+ emptyIcon?: string;
181
+ /** Empty state action button label */
182
+ emptyActionLabel?: string;
183
+ /** Empty state action button handler */
184
+ emptyActionHandler?: () => void;
175
185
  /** Loading text (kept for custom loading slot consumers) */
176
186
  loadingText?: string;
177
187
  /** Number of skeleton rows to show during loading */
@@ -19,6 +19,7 @@
19
19
  <TableRow
20
20
  v-if="!groupingEnabled || !expandableRowGroups || isGroupRowExpanded"
21
21
  :selected="isSelected"
22
+ :active="isActive"
22
23
  :reorderable="reorderable"
23
24
  :show-drag-handle="showDragHandle"
24
25
  :index="index"
@@ -154,6 +155,8 @@ const props = defineProps<{
154
155
  // === Selection ===
155
156
  /** Whether this row is selected */
156
157
  isSelected?: boolean;
158
+ /** Whether this row is the active (highlighted) row */
159
+ isActive?: boolean;
157
160
  /** Whether this row can be selected */
158
161
  isSelectable?: boolean;
159
162
  /** Selection mode: 'single' or 'multiple' */
@@ -11,28 +11,34 @@
11
11
  </div>
12
12
  </slot>
13
13
  </div>
14
- <div v-if="$slots.action" class="vc-table-composition__empty-action">
15
- <slot name="action" />
14
+ <div v-if="actionLabel || $slots.action" class="vc-table-composition__empty-action">
15
+ <slot name="action">
16
+ <VcButton
17
+ v-if="actionLabel"
18
+ variant="primary"
19
+ @click="actionHandler?.()"
20
+ >
21
+ {{ actionLabel }}
22
+ </VcButton>
23
+ </slot>
16
24
  </div>
17
25
  </div>
18
26
  </template>
19
27
 
20
28
  <script setup lang="ts">
21
- import { VcIcon } from "@ui/components/atoms";
29
+ import { VcIcon, VcButton } from "@ui/components/atoms";
22
30
 
23
31
  defineProps<{
24
- /**
25
- * Icon to display
26
- */
32
+ /** Icon to display */
27
33
  icon?: string;
28
- /**
29
- * Title text
30
- */
34
+ /** Title text */
31
35
  title?: string;
32
- /**
33
- * Description text
34
- */
36
+ /** Description text */
35
37
  description?: string;
38
+ /** Action button label — renders VcButton when provided */
39
+ actionLabel?: string;
40
+ /** Action button click handler */
41
+ actionHandler?: () => void;
36
42
  }>();
37
43
  </script>
38
44
 
@@ -3,7 +3,7 @@
3
3
  role="row"
4
4
  class="vc-table-composition__row"
5
5
  :class="{
6
- 'vc-table-composition__row--selected': selected,
6
+ 'vc-table-composition__row--selected': selected || active,
7
7
  'vc-table-composition__row--clickable': isClickable,
8
8
  'vc-table-composition__row--header': variant === 'header',
9
9
  'vc-table-composition__row--dragging': isDragging,
@@ -48,6 +48,10 @@ const props = withDefaults(
48
48
  * Whether the row is selected
49
49
  */
50
50
  selected?: boolean;
51
+ /**
52
+ * Whether the row is the active (highlighted) row — visual only, does not affect checkbox
53
+ */
54
+ active?: boolean;
51
55
  /**
52
56
  * Row variant
53
57
  */
@@ -69,6 +69,20 @@ export interface TableEmptyAction {
69
69
  clickHandler?: () => void;
70
70
  }
71
71
 
72
+ /** Configuration for empty/not-found state with icon, text, and optional action button */
73
+ export interface TableStateConfig {
74
+ /** Icon name (e.g. "lucide-package-open", "material-shopping_cart") */
75
+ icon?: string;
76
+ /** Title text */
77
+ title?: string;
78
+ /** Description text below the title */
79
+ description?: string;
80
+ /** Action button label — renders VcButton when provided */
81
+ actionLabel?: string;
82
+ /** Action button click handler */
83
+ actionHandler?: () => void;
84
+ }
85
+
72
86
  export type { MaybeRef } from "vue";
73
87
 
74
88
  /** Shared props for editable cell formatters (CellDefault, CellNumber, CellMoney) */
@@ -400,6 +414,8 @@ export interface VcDataTableProps<T = any> {
400
414
  rowClass?: (data: T) => string | object;
401
415
  /** Per-row function returning inline styles */
402
416
  rowStyle?: (data: T) => object;
417
+ /** ID of the currently active (highlighted) row — use with `v-model:activeItemId` */
418
+ activeItemId?: string;
403
419
  /** Visual table variant */
404
420
  variant?: "default" | "striped" | "bordered";
405
421
 
@@ -534,6 +550,12 @@ export interface VcDataTableExtendedProps<T = any> extends VcDataTableProps<T> {
534
550
  * useful when the table is narrowed (e.g. a second blade opens).
535
551
  */
536
552
  showAllColumns?: boolean;
553
+ /** Empty state configuration (shown when items array is empty and no search/filters are active).
554
+ * Overrides default i18n title/description. Use `#empty` slot for fully custom rendering. */
555
+ emptyState?: TableStateConfig;
556
+ /** Not-found state configuration (shown when items array is empty AND search/filters are active).
557
+ * Overrides default i18n title/description. Use `#not-found` slot for fully custom rendering. */
558
+ notFoundState?: TableStateConfig;
537
559
  }
538
560
 
539
561
  /** VcDataTable Emits — all events emitted by VcDataTable */
@@ -495,6 +495,38 @@ export const SingleSelection: StoryFn = () => ({
495
495
  `,
496
496
  });
497
497
 
498
+ export const ActiveItemHighlight: StoryFn = () => ({
499
+ components: { VcDataTable, VcColumn },
500
+ setup() {
501
+ const activeId = ref<string | undefined>(undefined);
502
+
503
+ return { products: mockProducts, activeId };
504
+ },
505
+ template: `
506
+ <div style="height: 400px">
507
+ <p class="tw-mb-2">Active row ID: {{ activeId ?? 'None' }}
508
+ <button class="tw-ml-2 tw-text-sm tw-underline" @click="activeId = undefined">Clear</button>
509
+ </p>
510
+ <VcDataTable
511
+ :items="products"
512
+ v-model:active-item-id="activeId"
513
+ >
514
+ <VcColumn id="name" field="name" title="Name" />
515
+ <VcColumn id="price" field="price" title="Price" type="money" />
516
+ <VcColumn id="stock" field="stock" title="Stock" type="number" />
517
+ <VcColumn id="status" field="status" title="Status" />
518
+ </VcDataTable>
519
+ </div>
520
+ `,
521
+ });
522
+ ActiveItemHighlight.parameters = {
523
+ docs: {
524
+ description: {
525
+ story: "Click a row to highlight it as active. Click again to deselect. Use `v-model:active-item-id` for two-way binding — the table automatically highlights the row matching the given ID and updates the value on row click.",
526
+ },
527
+ },
528
+ };
529
+
498
530
  /**
499
531
  * VcDataTable with single selection via VcColumn (radio button style)
500
532
  */
@@ -4971,3 +5003,176 @@ SearchWithGlobalFilters.parameters = {
4971
5003
  },
4972
5004
  },
4973
5005
  };
5006
+
5007
+ /**
5008
+ * Empty state using the declarative `emptyState` prop (TableStateConfig).
5009
+ * Shown when `items` is empty and there is no active search or filters.
5010
+ */
5011
+ export const EmptyStateConfig: StoryFn = () => ({
5012
+ components: { VcDataTable, VcColumn },
5013
+ setup() {
5014
+ const actionClicked = ref(false);
5015
+ const emptyState = {
5016
+ icon: "fas fa-box-open",
5017
+ title: "No products yet",
5018
+ description: "Your product catalog is empty. Add your first product to get started.",
5019
+ actionLabel: "Add Product",
5020
+ actionHandler: () => {
5021
+ actionClicked.value = true;
5022
+ },
5023
+ };
5024
+ return { products: [] as Product[], emptyState, actionClicked };
5025
+ },
5026
+ template: `
5027
+ <div style="height: 400px">
5028
+ <div v-if="actionClicked" class="tw-mb-2 tw-p-2 tw-bg-success-50 tw-rounded tw-text-sm tw-text-success-700">
5029
+ Action button clicked!
5030
+ </div>
5031
+ <VcDataTable :items="products" :empty-state="emptyState">
5032
+ <VcColumn id="name" field="name" title="Name" />
5033
+ <VcColumn id="price" field="price" title="Price" type="money" />
5034
+ <VcColumn id="stock" field="stock" title="Stock" type="number" />
5035
+ <VcColumn id="status" field="status" title="Status" />
5036
+ </VcDataTable>
5037
+ </div>
5038
+ `,
5039
+ });
5040
+ EmptyStateConfig.parameters = {
5041
+ docs: {
5042
+ description: {
5043
+ story:
5044
+ "Demonstrates the declarative `emptyState` prop (`TableStateConfig`) — " +
5045
+ "icon, title, description, and an optional action button. " +
5046
+ "Shown when `items` is an empty array and no search/filter is active.",
5047
+ },
5048
+ },
5049
+ };
5050
+
5051
+ /**
5052
+ * Not Found state using the declarative `notFoundState` prop (TableStateConfig).
5053
+ * Shown when `items` is empty AND there is an active search or filter.
5054
+ * The table uses `searchable` with a pre-filled search value to trigger the not-found state.
5055
+ */
5056
+ export const NotFoundStateConfig: StoryFn = () => ({
5057
+ components: { VcDataTable, VcColumn },
5058
+ setup() {
5059
+ const searchValue = ref("nonexistent product xyz");
5060
+ const actionClicked = ref(false);
5061
+
5062
+ const notFoundState = {
5063
+ icon: "fas fa-search",
5064
+ title: "No results found",
5065
+ description: "We couldn't find any products matching your search. Try adjusting your query.",
5066
+ actionLabel: "Clear Search",
5067
+ actionHandler: () => {
5068
+ searchValue.value = "";
5069
+ actionClicked.value = true;
5070
+ },
5071
+ };
5072
+
5073
+ const emptyState = {
5074
+ icon: "fas fa-box-open",
5075
+ title: "No products yet",
5076
+ description: "Your product catalog is empty.",
5077
+ };
5078
+
5079
+ return { products: [] as Product[], searchValue, notFoundState, emptyState, actionClicked };
5080
+ },
5081
+ template: `
5082
+ <div style="height: 400px">
5083
+ <div v-if="actionClicked" class="tw-mb-2 tw-p-2 tw-bg-success-50 tw-rounded tw-text-sm tw-text-success-700">
5084
+ Search cleared! (In a real app, items would reload and show the empty state instead.)
5085
+ </div>
5086
+ <VcDataTable
5087
+ :items="products"
5088
+ searchable
5089
+ v-model:search-value="searchValue"
5090
+ search-placeholder="Search products..."
5091
+ :not-found-state="notFoundState"
5092
+ :empty-state="emptyState"
5093
+ >
5094
+ <VcColumn id="name" field="name" title="Name" />
5095
+ <VcColumn id="price" field="price" title="Price" type="money" />
5096
+ <VcColumn id="stock" field="stock" title="Stock" type="number" />
5097
+ <VcColumn id="status" field="status" title="Status" />
5098
+ </VcDataTable>
5099
+ </div>
5100
+ `,
5101
+ });
5102
+ NotFoundStateConfig.parameters = {
5103
+ docs: {
5104
+ description: {
5105
+ story:
5106
+ "Demonstrates the `notFoundState` prop — shown instead of `emptyState` when items " +
5107
+ "is empty AND there is an active search query or filters. " +
5108
+ "The action button clears the search. Both `emptyState` and `notFoundState` " +
5109
+ "can be provided simultaneously — the table picks the right one based on context.",
5110
+ },
5111
+ },
5112
+ };
5113
+
5114
+ /**
5115
+ * Both empty and not-found states — interactive demo.
5116
+ * Search to switch between the two states.
5117
+ */
5118
+ export const EmptyVsNotFound: StoryFn = () => ({
5119
+ components: { VcDataTable, VcColumn },
5120
+ setup() {
5121
+ const searchValue = ref("");
5122
+
5123
+ const emptyState = {
5124
+ icon: "fas fa-box-open",
5125
+ title: "No products yet",
5126
+ description: "Your catalog is empty. Add your first product to get started.",
5127
+ actionLabel: "Add Product",
5128
+ actionHandler: () => alert("Add product clicked!"),
5129
+ };
5130
+
5131
+ const notFoundState = {
5132
+ icon: "fas fa-search",
5133
+ title: "Nothing matches your search",
5134
+ description: "Try a different search term or clear the search field.",
5135
+ actionLabel: "Clear Search",
5136
+ actionHandler: () => {
5137
+ searchValue.value = "";
5138
+ },
5139
+ };
5140
+
5141
+ return { products: [] as Product[], searchValue, emptyState, notFoundState };
5142
+ },
5143
+ template: `
5144
+ <div style="height: 450px">
5145
+ <div class="tw-mb-4 tw-p-3 tw-bg-gradient-to-r tw-from-accent-50 tw-to-primary-50 tw-rounded-lg tw-text-sm">
5146
+ <p class="tw-font-semibold tw-mb-1">Empty vs Not Found</p>
5147
+ <p class="tw-text-neutrals-600">
5148
+ Type anything in the search bar to switch from <strong>empty state</strong> to <strong>not found state</strong>.
5149
+ Clear the search to go back to empty state.
5150
+ </p>
5151
+ </div>
5152
+ <VcDataTable
5153
+ :items="products"
5154
+ searchable
5155
+ v-model:search-value="searchValue"
5156
+ search-placeholder="Type to trigger not-found state..."
5157
+ :empty-state="emptyState"
5158
+ :not-found-state="notFoundState"
5159
+ >
5160
+ <VcColumn id="name" field="name" title="Name" />
5161
+ <VcColumn id="price" field="price" title="Price" type="money" />
5162
+ <VcColumn id="stock" field="stock" title="Stock" type="number" />
5163
+ <VcColumn id="status" field="status" title="Status" />
5164
+ </VcDataTable>
5165
+ </div>
5166
+ `,
5167
+ });
5168
+ EmptyVsNotFound.parameters = {
5169
+ docs: {
5170
+ description: {
5171
+ story:
5172
+ "Interactive demo showing the difference between empty and not-found states. " +
5173
+ "With an empty search field the table shows the `emptyState` config. " +
5174
+ "Type anything in the search bar to see the `notFoundState` config instead. " +
5175
+ "The table automatically switches between the two based on whether a search/filter is active.",
5176
+ },
5177
+ },
5178
+ };