@retailcrm/embed-ui-v1-components 0.9.26 → 0.9.28

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/dist/remote.cjs CHANGED
@@ -2466,6 +2466,12 @@ const slotNeedsBodyCellWrapper = (children) => {
2466
2466
  }
2467
2467
  return !nodes.every(isCellVNode);
2468
2468
  };
2469
+ const hasSlotContent = (children) => {
2470
+ if (!children) {
2471
+ return false;
2472
+ }
2473
+ return normalizeNodes(children).length > 0;
2474
+ };
2469
2475
  const renderGroupHead = (slot, props) => {
2470
2476
  return slot?.(props) ?? [String(props.group.key)];
2471
2477
  };
@@ -2692,17 +2698,25 @@ const _sfc_main$6 = vue.defineComponent({
2692
2698
  const pageSize = slots["footer-page-size"]?.(footerData);
2693
2699
  const exportControl = slots["footer-export"]?.(footerData);
2694
2700
  const pagination = slots["footer-pagination"]?.(footerData);
2701
+ const hasSummary = hasSlotContent(summary);
2702
+ const hasPageSize = hasSlotContent(pageSize);
2703
+ const hasExportControl = hasSlotContent(exportControl);
2704
+ const hasPagination = hasSlotContent(pagination);
2705
+ const hasAnyStructuredFooterContent = hasSummary || hasPageSize || hasExportControl || hasPagination;
2706
+ if (!hasAnyStructuredFooterContent) {
2707
+ return null;
2708
+ }
2695
2709
  return vue.h(UiTableSection, { kind: "foot", key: "footer" }, () => vue.h(UiTableRow, () => vue.h(UiTableBodyCell, {
2696
2710
  colspan: columnsCount,
2697
2711
  class: "ui-v1-table__footer-cell"
2698
2712
  }, () => vue.h("div", { class: "ui-v1-table__footer" }, [
2699
- summary ? vue.h("div", { class: "ui-v1-table__footer-meta" }, [summary]) : null,
2700
- pageSize || exportControl || pagination ? vue.h("div", { class: "ui-v1-table__footer-controls" }, [
2701
- pageSize || exportControl ? vue.h("div", { class: "ui-v1-table__footer-main" }, [
2713
+ hasSummary ? vue.h("div", { class: "ui-v1-table__footer-meta" }, [summary]) : null,
2714
+ hasPageSize || hasExportControl || hasPagination ? vue.h("div", { class: "ui-v1-table__footer-controls" }, [
2715
+ hasPageSize || hasExportControl ? vue.h("div", { class: "ui-v1-table__footer-main" }, [
2702
2716
  pageSize,
2703
2717
  exportControl
2704
2718
  ]) : null,
2705
- pagination ? vue.h("div", { class: "ui-v1-table__footer-side" }, [pagination]) : null
2719
+ hasPagination ? vue.h("div", { class: "ui-v1-table__footer-side" }, [pagination]) : null
2706
2720
  ]) : null
2707
2721
  ]))));
2708
2722
  }
package/dist/remote.js CHANGED
@@ -2464,6 +2464,12 @@ const slotNeedsBodyCellWrapper = (children) => {
2464
2464
  }
2465
2465
  return !nodes.every(isCellVNode);
2466
2466
  };
2467
+ const hasSlotContent = (children) => {
2468
+ if (!children) {
2469
+ return false;
2470
+ }
2471
+ return normalizeNodes(children).length > 0;
2472
+ };
2467
2473
  const renderGroupHead = (slot, props) => {
2468
2474
  return slot?.(props) ?? [String(props.group.key)];
2469
2475
  };
@@ -2690,17 +2696,25 @@ const _sfc_main$6 = defineComponent({
2690
2696
  const pageSize = slots["footer-page-size"]?.(footerData);
2691
2697
  const exportControl = slots["footer-export"]?.(footerData);
2692
2698
  const pagination = slots["footer-pagination"]?.(footerData);
2699
+ const hasSummary = hasSlotContent(summary);
2700
+ const hasPageSize = hasSlotContent(pageSize);
2701
+ const hasExportControl = hasSlotContent(exportControl);
2702
+ const hasPagination = hasSlotContent(pagination);
2703
+ const hasAnyStructuredFooterContent = hasSummary || hasPageSize || hasExportControl || hasPagination;
2704
+ if (!hasAnyStructuredFooterContent) {
2705
+ return null;
2706
+ }
2693
2707
  return h(UiTableSection, { kind: "foot", key: "footer" }, () => h(UiTableRow, () => h(UiTableBodyCell, {
2694
2708
  colspan: columnsCount,
2695
2709
  class: "ui-v1-table__footer-cell"
2696
2710
  }, () => h("div", { class: "ui-v1-table__footer" }, [
2697
- summary ? h("div", { class: "ui-v1-table__footer-meta" }, [summary]) : null,
2698
- pageSize || exportControl || pagination ? h("div", { class: "ui-v1-table__footer-controls" }, [
2699
- pageSize || exportControl ? h("div", { class: "ui-v1-table__footer-main" }, [
2711
+ hasSummary ? h("div", { class: "ui-v1-table__footer-meta" }, [summary]) : null,
2712
+ hasPageSize || hasExportControl || hasPagination ? h("div", { class: "ui-v1-table__footer-controls" }, [
2713
+ hasPageSize || hasExportControl ? h("div", { class: "ui-v1-table__footer-main" }, [
2700
2714
  pageSize,
2701
2715
  exportControl
2702
2716
  ]) : null,
2703
- pagination ? h("div", { class: "ui-v1-table__footer-side" }, [pagination]) : null
2717
+ hasPagination ? h("div", { class: "ui-v1-table__footer-side" }, [pagination]) : null
2704
2718
  ]) : null
2705
2719
  ]))));
2706
2720
  }
package/docs/AI.md CHANGED
@@ -126,6 +126,10 @@ screen where users scan and refine datasets:
126
126
  - reset `page` to `1` when filters or sorting change;
127
127
  - debounce free-text search before writing query or fetching data;
128
128
  - use `UiTableSorter` for sortable headers;
129
+ - for ordinary `UiTableColumn` cell customization, set header metadata through props such as `label`, `width`,
130
+ and `trim`, then use `v-slot` on `UiTableColumn` instead of `<template #cell>`;
131
+ - use the `#cell` slot only when the column also needs another named slot such as `#label`, or when explicit
132
+ slot naming makes a complex column easier to read;
129
133
  - use `UiTable` footer slots for summary, page-size controls, export, and pagination;
130
134
  - compose table footer controls with `UiTableFooterSection` and `UiTableFooterButton`, not regular `UiButton`;
131
135
  - use chevron icon assets for table footer previous/next controls instead of text glyphs;
@@ -92,8 +92,10 @@ ai_notes:
92
92
  do:
93
93
  - Use for compact error presentation.
94
94
  - Keep the message short and specific.
95
+ - Put field-level errors inside the matching UiField instead of below the field wrapper.
95
96
  avoid:
96
97
  - Do not use for multi-paragraph explanations or page-level failures.
98
+ - Do not leave field-level errors disconnected from the control they describe.
97
99
 
98
100
  composition:
99
101
  works_well_with:
@@ -101,7 +103,9 @@ composition:
101
103
  - UiAlert
102
104
  patterns:
103
105
  - title: Field or section error
104
- notes: Use where the user needs to see a concise local error message.
106
+ notes: >
107
+ Use where the user needs to see a concise local error message. For field-level validation,
108
+ render UiError inside the corresponding UiField default slot, directly after the control.
105
109
  - title: Page-level failure
106
110
  notes: Use UiAlert or UiInfobox instead when the error needs explanation and actions.
107
111
 
@@ -109,6 +109,56 @@ examples:
109
109
 
110
110
  const duration = ref(30)
111
111
  </script>
112
+ - title: Field validation with local error
113
+ notes:
114
+ - Keep the field-level error inside the UiField default slot, directly after the control it describes.
115
+ - Forward id and ARIA slot props into the real control; invalid styling alone is not enough for validation wiring.
116
+ - Add local margin to UiError when the design needs a visual gap below the control.
117
+ code: |
118
+ <template>
119
+ <UiField
120
+ id="specialist-name"
121
+ v-slot="field"
122
+ :invalid="Boolean(error)"
123
+ label="Name"
124
+ required
125
+ >
126
+ <UiTextbox
127
+ :id="field.id"
128
+ v-model:value="name"
129
+ :invalid="field.invalid"
130
+ :input-attributes="{
131
+ 'aria-labelledby': field.ariaLabelledby,
132
+ 'aria-invalid': field.ariaInvalid,
133
+ }"
134
+ />
135
+
136
+ <UiError
137
+ v-if="error"
138
+ :class="$style['form-field-error']"
139
+ :message="error"
140
+ />
141
+ </UiField>
142
+ </template>
143
+
144
+ <script lang="ts" setup>
145
+ import { ref } from 'vue'
146
+
147
+ import {
148
+ UiError,
149
+ UiField,
150
+ UiTextbox,
151
+ } from '@retailcrm/embed-ui-v1-components/remote'
152
+
153
+ const name = ref('')
154
+ const error = ref('Name is required')
155
+ </script>
156
+
157
+ <style lang="less" module>
158
+ .form-field-error {
159
+ margin-top: 4px;
160
+ }
161
+ </style>
112
162
  use_when:
113
163
  - You need a labeled form control with consistent field semantics.
114
164
  - You need to pass id, aria-labelledby, and aria-invalid into an inner control.
@@ -305,6 +355,10 @@ composition:
305
355
  patterns:
306
356
  - title: Safe field wiring
307
357
  notes: Use slot props for semantics, not just for visual wrapping.
358
+ - title: Field-level validation
359
+ notes: >
360
+ Place UiError inside the default slot immediately after the control, and complete id,
361
+ aria-labelledby, and aria-invalid wiring on the real control.
308
362
 
309
363
 
310
364
  ai_notes:
@@ -314,8 +368,11 @@ ai_notes:
314
368
  - Use v-slot on UiField when the field uses only the default slot.
315
369
  - Use UiField as a semantic wrapper for a single textbox, select, number, date, or time control.
316
370
  - Place validation errors inside the relevant field composition instead of rendering them as unrelated sibling blocks.
371
+ - Add local spacing to UiError itself when the error needs a visible gap from the control.
317
372
  avoid:
318
373
  - Do not use UiField as a generic visual container without control semantics.
319
374
  - Do not use UiField for UiSwitch settings rows; pair UiSwitch with a visible label and hint text instead.
320
375
  - Do not use UiField for simple UiCheckbox rows; pair UiCheckbox with a visible label and optional hint instead.
321
376
  - Do not ignore id and ariaLabelledby when accessibility matters.
377
+ - Do not render field-level UiError as a sibling immediately after the closing UiField.
378
+ - Do not rely only on the invalid prop when the control can receive field ARIA slot props.
@@ -213,7 +213,9 @@ api:
213
213
  - disabled
214
214
  - counter
215
215
  - accent
216
- notes: Base selectable option node.
216
+ notes: >
217
+ Base selectable option node. Nullable values are allowed when they are meaningful to the model,
218
+ but Vue keys for repeated option nodes must be stable and non-null.
217
219
  - name: UiSelectOptionGroup
218
220
  key_props:
219
221
  - label
@@ -44,14 +44,19 @@ ai_notes:
44
44
  - Use as the default child node of UiSelect.
45
45
  - Provide stable value and human-readable label.
46
46
  - Use description for secondary explanation, not for long help text.
47
+ - Keep Vue `v-for` keys stable and non-null even when the option value itself can be null.
48
+ - Normalize persisted option DTOs to non-null ids, or use another stable domain key such as code, slug, or a temporary client id.
47
49
  avoid:
48
50
  - Do not put arbitrary controls inside options.
51
+ - Do not use nullable DTO ids directly as Vue keys.
52
+ - Do not use array index as the fallback key for dynamic options that can be sorted, filtered, inserted, or removed.
49
53
 
50
54
  behavior:
51
55
  notes:
52
56
  - disabled prevents selection while keeping the option visible.
53
57
  - counter and accent help scan dense option lists.
54
58
  - In filterable selects, label and description participate in matching.
59
+ - A nullable option value is valid for selection semantics, but the Vue key for a repeated option node must still be stable and non-null.
55
60
 
56
61
  composition:
57
62
  works_well_with:
@@ -32,24 +32,18 @@ examples:
32
32
  :rows="rows"
33
33
  row-key="id"
34
34
  >
35
- <UiTableColumn label="Title">
36
- <template #cell="{ row }">
37
- <strong>{{ row.title }}</strong>
38
- </template>
35
+ <UiTableColumn v-slot="{ row }" label="Title">
36
+ <strong>{{ row.title }}</strong>
39
37
  </UiTableColumn>
40
38
 
41
- <UiTableColumn label="Customer" width="180">
42
- <template #cell="{ row }">
43
- {{ row.customer }}
44
- </template>
39
+ <UiTableColumn v-slot="{ row }" label="Customer" width="180">
40
+ {{ row.customer }}
45
41
  </UiTableColumn>
46
42
 
47
- <UiTableColumn label="Status" width="160">
48
- <template #cell="{ row }">
49
- <UiTag :background="statusBackgroundByName[row.status]" size="md" saturated :ticker="false">
50
- {{ row.status }}
51
- </UiTag>
52
- </template>
43
+ <UiTableColumn v-slot="{ row }" label="Status" width="160">
44
+ <UiTag :background="statusBackgroundByName[row.status]" size="md" saturated :ticker="false">
45
+ {{ row.status }}
46
+ </UiTag>
53
47
  </UiTableColumn>
54
48
  </UiTable>
55
49
  </template>
@@ -78,14 +72,71 @@ examples:
78
72
  :rows="rows"
79
73
  row-key="id"
80
74
  >
81
- <UiTableColumn label="Name">
82
- <template #cell="{ row }">
83
- {{ row.name }}
84
- </template>
75
+ <UiTableColumn v-slot="{ row }" label="Name">
76
+ {{ row.name }}
77
+ </UiTableColumn>
78
+
79
+ <UiTableColumn v-slot="{ row }" :width="48" label="" trim>
80
+ <UiPopperConnector>
81
+ <UiButton
82
+ :aria-label="`Edit ${row.name}`"
83
+ appearance="tertiary"
84
+ size="sm"
85
+ @click="edit(row)"
86
+ >
87
+ <IconEdit aria-hidden="true" />
88
+ </UiButton>
89
+
90
+ <UiTooltip>
91
+ Edit {{ row.name }}
92
+ </UiTooltip>
93
+ </UiPopperConnector>
85
94
  </UiTableColumn>
95
+ </UiTable>
96
+ </template>
97
+
98
+ <script lang="ts" setup>
99
+ import IconEdit from '@retailcrm/embed-ui-v1-components/assets/sprites/ui/edit.svg'
100
+ import {
101
+ UiButton,
102
+ UiPopperConnector,
103
+ UiTable,
104
+ UiTableColumn,
105
+ UiTooltip,
106
+ } from '@retailcrm/embed-ui-v1-components/remote'
107
+
108
+ type Row = {
109
+ id: number
110
+ name: string
111
+ }
112
+
113
+ const rows: Row[] = [
114
+ { id: 101, name: 'Anna Smith' },
115
+ { id: 102, name: 'Ilya Johnson' },
116
+ ]
86
117
 
87
- <UiTableColumn :width="48" label="" trim>
88
- <template #cell="{ row }">
118
+ const edit = (row: Row) => {
119
+ console.log(row)
120
+ }
121
+ </script>
122
+ - title: Multiple row actions in one final column
123
+ notes:
124
+ - Use one stable final action column for several row commands unless the product task explicitly asks for separate semantic columns.
125
+ - Keep the action cell width fixed and prevent it from wrapping primary row content.
126
+ - Each icon-only action needs aria-label and UiTooltip text.
127
+ code: |
128
+ <template>
129
+ <UiTable
130
+ bordered
131
+ :rows="rows"
132
+ row-key="id"
133
+ >
134
+ <UiTableColumn v-slot="{ row }" label="Name">
135
+ {{ row.name }}
136
+ </UiTableColumn>
137
+
138
+ <UiTableColumn v-slot="{ row }" :width="104" label="" trim>
139
+ <div :class="$style['specialist-table__actions']">
89
140
  <UiPopperConnector>
90
141
  <UiButton
91
142
  :aria-label="`Edit ${row.name}`"
@@ -100,12 +151,29 @@ examples:
100
151
  Edit {{ row.name }}
101
152
  </UiTooltip>
102
153
  </UiPopperConnector>
103
- </template>
154
+
155
+ <UiPopperConnector>
156
+ <UiButton
157
+ :aria-label="`Delete ${row.name}`"
158
+ appearance="tertiary"
159
+ size="sm"
160
+ variant="danger"
161
+ @click="remove(row)"
162
+ >
163
+ <IconDelete aria-hidden="true" />
164
+ </UiButton>
165
+
166
+ <UiTooltip>
167
+ Delete {{ row.name }}
168
+ </UiTooltip>
169
+ </UiPopperConnector>
170
+ </div>
104
171
  </UiTableColumn>
105
172
  </UiTable>
106
173
  </template>
107
174
 
108
175
  <script lang="ts" setup>
176
+ import IconDelete from '@retailcrm/embed-ui-v1-components/assets/sprites/ui/delete.svg'
109
177
  import IconEdit from '@retailcrm/embed-ui-v1-components/assets/sprites/ui/edit.svg'
110
178
  import {
111
179
  UiButton,
@@ -128,7 +196,21 @@ examples:
128
196
  const edit = (row: Row) => {
129
197
  console.log(row)
130
198
  }
199
+
200
+ const remove = (row: Row) => {
201
+ console.log(row)
202
+ }
131
203
  </script>
204
+
205
+ <style lang="less" module>
206
+ .specialist-table__actions {
207
+ display: flex;
208
+ align-items: center;
209
+ justify-content: flex-end;
210
+ gap: 8px;
211
+ white-space: nowrap;
212
+ }
213
+ </style>
132
214
  - title: Expandable rows table
133
215
  notes:
134
216
  - Use the expand slot and cell toggle helper for expandable row details.
@@ -139,18 +221,14 @@ examples:
139
221
  :rows="rows"
140
222
  row-key="id"
141
223
  >
142
- <UiTableColumn :width="44" label="" trim>
143
- <template #cell="{ expanded, toggle }">
144
- <button class="table-expand" type="button" @click="toggle">
145
- {{ expanded ? '-' : '+' }}
146
- </button>
147
- </template>
224
+ <UiTableColumn v-slot="{ expanded, toggle }" :width="44" label="" trim>
225
+ <button class="table-expand" type="button" @click="toggle">
226
+ {{ expanded ? '-' : '+' }}
227
+ </button>
148
228
  </UiTableColumn>
149
229
 
150
- <UiTableColumn label="Title">
151
- <template #cell="{ row }">
152
- {{ row.title }}
153
- </template>
230
+ <UiTableColumn v-slot="{ row }" label="Title">
231
+ {{ row.title }}
154
232
  </UiTableColumn>
155
233
 
156
234
  <template #expand="{ row }">
@@ -186,16 +264,12 @@ examples:
186
264
  row-key="id"
187
265
  :group-by="groupByStatus"
188
266
  >
189
- <UiTableColumn label="Title">
190
- <template #cell="{ row }">
191
- {{ row.title }}
192
- </template>
267
+ <UiTableColumn v-slot="{ row }" label="Title">
268
+ {{ row.title }}
193
269
  </UiTableColumn>
194
270
 
195
- <UiTableColumn label="Status" width="160">
196
- <template #cell="{ row }">
197
- {{ row.status }}
198
- </template>
271
+ <UiTableColumn v-slot="{ row }" label="Status" width="160">
272
+ {{ row.status }}
199
273
  </UiTableColumn>
200
274
 
201
275
  <template #group-head="{ group }">
@@ -241,10 +315,8 @@ examples:
241
315
  :rows="rows"
242
316
  row-key="id"
243
317
  >
244
- <UiTableColumn label="Title">
245
- <template #cell="{ row }">
246
- {{ row.title }}
247
- </template>
318
+ <UiTableColumn v-slot="{ row }" label="Title">
319
+ {{ row.title }}
248
320
  </UiTableColumn>
249
321
 
250
322
  <template #footer-summary="{ rowsCount }">
@@ -440,16 +512,12 @@ examples:
440
512
  :rows="rows"
441
513
  row-key="id"
442
514
  >
443
- <UiTableColumn label="Title">
444
- <template #cell="{ row }">
445
- {{ row.title }}
446
- </template>
515
+ <UiTableColumn v-slot="{ row }" label="Title">
516
+ {{ row.title }}
447
517
  </UiTableColumn>
448
518
 
449
- <UiTableColumn label="Status" width="160">
450
- <template #cell="{ row }">
451
- {{ row.status }}
452
- </template>
519
+ <UiTableColumn v-slot="{ row }" label="Status" width="160">
520
+ {{ row.status }}
453
521
  </UiTableColumn>
454
522
  </UiTable>
455
523
  </template>
@@ -649,6 +717,11 @@ composition:
649
717
  - Use UiTableSorter inside a UiTableColumn label slot for sortable columns.
650
718
  - Reset pagination to the first page when sorting changes.
651
719
  - Persist sort key and direction in URL query parameters when routing is available.
720
+ columns:
721
+ notes:
722
+ - Configure header metadata with UiTableColumn props such as label, width, align, valign, and trim.
723
+ - Prefer `v-slot` on UiTableColumn for ordinary cell content when the header is configured through props.
724
+ - Use `#cell` only when the column also needs another named slot such as `#label`, or when explicit naming improves readability.
652
725
  pagination:
653
726
  notes:
654
727
  - Use footer-summary, footer-page-size, footer-export, and footer-pagination slots for structured footer controls.
@@ -663,6 +736,9 @@ composition:
663
736
  actions:
664
737
  notes:
665
738
  - Prefer narrow action columns with icon-only UiButton controls for row commands.
739
+ - If a row has multiple actions, keep them together in one final action column by default.
740
+ - Split row actions into separate columns only when each column has a distinct user-facing meaning or the task explicitly requires it.
741
+ - Give the action column a stable width and an inner flex container so actions do not resize or wrap primary row content.
666
742
  - Keep visible button text out of dense table rows; provide aria-label and matching UiTooltip text.
667
743
  - Use package sprite icons and set aria-hidden on the icon itself.
668
744
  - Wrap action buttons and UiTooltip in UiPopperConnector. Add UiPopperTarget only for custom non-component targets.
@@ -676,14 +752,18 @@ ai_notes:
676
752
  - Compose footer controls with UiTableFooterSection and UiTableFooterButton.
677
753
  - Copy the Entity list table footer example when building a realistic entity-list footer.
678
754
  - Use UiTableSorter for sortable headers.
755
+ - Prefer UiTableColumn `v-slot` for ordinary custom cells when label and sizing are configured through props.
679
756
  - Use icon-only row action buttons with aria-label and UiTooltip in action columns.
757
+ - Put several row actions into one final action cell unless separate columns are explicitly required.
680
758
  avoid:
681
759
  - Do not hide table filters in page header actions.
682
760
  - Do not wrap UiTable in an additional white card, panel, or content-surface container.
683
761
  - Do not use UiButton inside table footer pagination.
684
762
  - Do not put visible text buttons in dense table action columns.
763
+ - Do not create several neighboring empty action columns for edit, delete, and similar compact row commands.
685
764
  - Do not put pagination only in local state when the screen has routable result sets.
686
765
  - Do not import table internals from host or src paths.
766
+ - Do not use `<template #cell>` for every column when the default slot would be simpler.
687
767
 
688
768
  behavior:
689
769
  notes:
@@ -11,6 +11,35 @@ public_import:
11
11
  related_components:
12
12
  - UiTable
13
13
 
14
+ examples:
15
+ - title: Prop label with default cell slot
16
+ notes:
17
+ - Use this shape when header metadata is covered by props and only the cell body is custom.
18
+ code: |
19
+ <UiTableColumn v-slot="{ row }" label="Name" min-width="180">
20
+ <UiLink size="small">{{ row.name }}</UiLink>
21
+ </UiTableColumn>
22
+ - title: Custom label and explicit cell slot
23
+ notes:
24
+ - Use named slots when the column uses a custom label, for example a sorter.
25
+ - Keeping `#cell` explicit makes the two slot zones easier to scan.
26
+ code: |
27
+ <UiTableColumn min-width="180">
28
+ <template #label>
29
+ <UiTableSorter
30
+ :direction="sort.direction"
31
+ :active="sort.field === 'name'"
32
+ @click="setSort('name')"
33
+ >
34
+ Name
35
+ </UiTableSorter>
36
+ </template>
37
+
38
+ <template #cell="{ row }">
39
+ <UiLink size="small">{{ row.name }}</UiLink>
40
+ </template>
41
+ </UiTableColumn>
42
+
14
43
  use_when:
15
44
  - You define columns for UiTable.
16
45
 
@@ -30,12 +59,19 @@ api:
30
59
  - name: default
31
60
  zone: cell
32
61
  creates: Cell content.
62
+ notes: >
63
+ Prefer `v-slot` on UiTableColumn when the header label is already supplied by the `label`
64
+ prop and only the cell body is customized.
33
65
  - name: cell
34
66
  zone: cell
35
67
  creates: Explicit cell content.
68
+ notes: >
69
+ Use `#cell` when the column also needs another named slot such as `#label`, or when explicit
70
+ naming makes a complex column easier to read.
36
71
  - name: label
37
72
  zone: header-label
38
73
  creates: Header label content.
74
+ notes: Use only when the header label needs custom markup, for example UiTableSorter.
39
75
 
40
76
  rendered_structure:
41
77
  descriptive_only: true
@@ -60,6 +96,10 @@ composition:
60
96
  notes: Put the row's main UiLink in the first meaningful text column and set link size to small.
61
97
  - title: Sortable header
62
98
  notes: Use the label slot with UiTableSorter when the column supports sorting.
99
+ - title: Prop label plus custom cell
100
+ notes: Use `<UiTableColumn v-slot="{ row }" label="Name">` instead of `<template #cell>` when `label` prop is enough for the header.
101
+ - title: Custom label plus custom cell
102
+ notes: Use separate `<template #label>` and `<template #cell>` blocks when the header is built with a slot.
63
103
  - title: Status column
64
104
  notes: Use UiTag for categorical statuses instead of raw colored text.
65
105
 
@@ -68,8 +108,12 @@ ai_notes:
68
108
  - Set minWidth for important text columns so generated tables remain scannable.
69
109
  - Use align="right" for numbers, money, and percentages.
70
110
  - Use trim only for narrow checkbox, icon, or action columns.
111
+ - Prefer `v-slot` on UiTableColumn for ordinary custom cell content when the header is configured through props.
112
+ - Use the label slot only when the header itself needs custom markup.
113
+ - Use explicit `#cell` together with `#label` when both header and cell content are custom.
71
114
  avoid:
72
115
  - Do not place filters inside column labels; filters belong above the table.
116
+ - Do not wrap every cell body in `<template #cell>` when the default slot would express the same thing more simply.
73
117
 
74
118
  behavior:
75
119
  notes:
@@ -24,9 +24,15 @@ expected_structure:
24
24
  - The page root wrapper should not repeat that padding.
25
25
  - Content can include text, buttons, fields, checkboxes, radio groups, switches, and other form controls.
26
26
  - Textbox, select, number, date, and time controls usually live inside UiField.
27
+ - Field-level validation errors should be rendered as UiError inside the corresponding UiField, directly after the control.
28
+ - Bounded numeric settings, quantities, durations, limits, and ordering values should use UiNumberStepper instead of UiTextbox.
29
+ - UiTextbox with numeric input attributes is acceptable only when stepper controls do not fit the product interaction.
27
30
  - UiSwitch and simple UiCheckbox rows should use their own row layout with a visible label and optional hint, not UiField.
28
31
  - A bottom save panel is optional.
29
32
  - If a bottom save panel is used, its main save or apply action should be Success Primary.
33
+ - Show page-level API failures with UiAlert variant="danger".
34
+ - Show successful saves with a closable UiAlert variant="success" or another documented project success pattern.
35
+ - Use UiLoader when loading blocks the settings form; keep already loaded form chrome stable during save requests when possible.
30
36
 
31
37
  recommended_components:
32
38
  - name: UiPageHeader
@@ -37,8 +43,16 @@ recommended_components:
37
43
  profile: ../components/UiField.yml
38
44
  - name: UiTextbox
39
45
  profile: ../components/UiTextbox.yml
46
+ - name: UiNumberStepper
47
+ profile: ../components/UiNumberStepper.yml
40
48
  - name: UiSelect
41
49
  profile: ../components/UiSelect.yml
50
+ - name: UiError
51
+ profile: ../components/UiError.yml
52
+ - name: UiAlert
53
+ profile: ../components/UiAlert.yml
54
+ - name: UiLoader
55
+ profile: ../components/UiLoader.yml
42
56
  - name: UiCheckbox
43
57
  profile: ../components/UiCheckbox.yml
44
58
  - name: UiRadio
@@ -58,6 +72,8 @@ ai_notes:
58
72
  do:
59
73
  - Keep form controls grouped by task or semantic section.
60
74
  - Use UiField around labeled textbox, select, number, date, and time controls.
75
+ - Put UiError inside the UiField that owns the invalid control.
76
+ - Use UiNumberStepper for bounded numeric settings and values with meaningful increments.
61
77
  - Use UiSwitch for compact enable or disable settings.
62
78
  - Use UiCheckbox for checkbox groups, table selection, acknowledgements, and checkbox-shaped boolean choices.
63
79
  - Use tabs only when they reduce visible complexity without hiding required work.
@@ -68,3 +84,5 @@ ai_notes:
68
84
  - Do not put content-surface padding on the root page wrapper.
69
85
  - Do not use UiCheckbox as the default replacement for UiSwitch just because a setting is boolean.
70
86
  - Do not wrap UiSwitch or simple UiCheckbox rows in UiField.
87
+ - Do not implement bounded numeric settings as plain UiTextbox controls unless the exception is documented.
88
+ - Do not render field errors as unrelated sibling blocks outside UiField.
@@ -28,6 +28,12 @@ expected_structure:
28
28
  - UiTable should not be wrapped in an extra white content surface.
29
29
  - A wrapper around UiTable should only handle layout, width, or scrolling, without card chrome or content-surface padding.
30
30
  - Row action columns should use icon-only buttons with aria-label and UiTooltip instead of visible text buttons.
31
+ - Multiple row actions should live in one final action column with a stable width unless the task explicitly asks for separate columns.
32
+ - Settings-dependent columns should be conditional at the UiTableColumn level, not permanent columns that render empty values.
33
+ - If a setting disables a dependent column, avoid loading expensive data used only by that column.
34
+ - Show initial or blocking loading with UiLoader in the table/content area while keeping filters and page actions understandable.
35
+ - Show page-level API failures with UiAlert variant="danger".
36
+ - Use UiTable '#empty' for table-owned empty states; use UiInfobox when the empty state needs richer guidance or an action outside the table body.
31
37
  - The table may scroll and may support export.
32
38
 
33
39
  recommended_components:
@@ -51,6 +57,12 @@ recommended_components:
51
57
  profile: ../components/UiTableFooterSection.yml
52
58
  - name: UiTableFooterButton
53
59
  profile: ../components/UiTableFooterButton.yml
60
+ - name: UiLoader
61
+ profile: ../components/UiLoader.yml
62
+ - name: UiAlert
63
+ profile: ../components/UiAlert.yml
64
+ - name: UiInfobox
65
+ profile: ../components/UiInfobox.yml
54
66
 
55
67
  table_footer_rules:
56
68
  - Use UiTableFooterSection and UiTableFooterButton for table footer pagination.
@@ -66,10 +78,15 @@ ai_notes:
66
78
  - Persist filters, sorting, page, and page size in query parameters when routing is available.
67
79
  - Reset page to 1 when filters or sorting change.
68
80
  - Use narrow icon-only row action columns with matching aria-label and UiTooltip text.
81
+ - Put edit, delete, and similar compact row actions into one final action cell by default.
82
+ - Conditionally render settings-dependent columns at the column declaration, for example with `v-if` on UiTableColumn.
83
+ - Skip dependent API loads when the current settings make the dependent table data invisible.
69
84
  - Use tertiary for low-emphasis header or row-adjacent commands when a secondary button would be too visually heavy.
70
85
  avoid:
71
86
  - Do not hide filters in page header actions.
72
87
  - Do not put pagination only in local state when the result set is routable.
73
88
  - Do not place UiTable inside an additional white card or padded content surface.
74
89
  - Do not use visible text buttons for dense table row actions.
90
+ - Do not split compact row commands into several neighboring empty columns unless each column has a separate product meaning.
91
+ - Do not keep a settings-dependent column visible with blank cells when the setting is disabled.
75
92
  - Do not make every header action secondary when only one action should draw attention.
@@ -151,6 +151,12 @@ expected_structure:
151
151
  - Destructive footer actions are often icon-only buttons aligned to the right edge.
152
152
  - Destructive icon-only actions should usually open UiPopconfirm with okVariant="danger"; its confirmation button is primary by default.
153
153
  - Content can be flexible, but should stay compact.
154
+ - Default sidebar forms use one form or grid container with a consistent gap.
155
+ - Each labeled textbox, select, number, date, or time control in a sidebar form should live in UiField.
156
+ - Field-level errors should use UiError inside the corresponding UiField, directly after the control.
157
+ - Bounded numeric sidebar fields should use UiNumberStepper unless stepper controls do not fit the product interaction.
158
+ - Footer actions should be placed through the UiModalSidebar footer slot, usually primary save and secondary or tertiary cancel.
159
+ - Sidebar titles should stay short enough for the available width.
154
160
  - Small tables can be placed in sidebars.
155
161
 
156
162
  recommended_components:
@@ -166,8 +172,14 @@ recommended_components:
166
172
  profile: ../components/UiField.yml
167
173
  - name: UiTextbox
168
174
  profile: ../components/UiTextbox.yml
175
+ - name: UiNumberStepper
176
+ profile: ../components/UiNumberStepper.yml
169
177
  - name: UiSelect
170
178
  profile: ../components/UiSelect.yml
179
+ - name: UiError
180
+ profile: ../components/UiError.yml
181
+ - name: UiAlert
182
+ profile: ../components/UiAlert.yml
171
183
  - name: UiTable
172
184
  profile: ../components/UiTable.yml
173
185
  notes: Use only for small, simple tables.
@@ -176,6 +188,9 @@ ai_notes:
176
188
  do:
177
189
  - Use for inspect, edit, or secondary workflows related to the current list or page.
178
190
  - Keep the flow compact.
191
+ - Use a single local form/grid container for sidebar forms.
192
+ - Put every labeled scalar form control in UiField and keep UiError inside that field.
193
+ - Use the footer slot for save/cancel actions rather than a page footer component.
179
194
  - Split footer actions into meaningful left and right groups when secondary or destructive actions need separation.
180
195
  - Use 12px or 16px gaps inside each footer action group.
181
196
  - Put destructive icon-only actions on the right and confirm them with UiPopconfirm okVariant="danger"; the confirmation button is primary by default.
@@ -183,4 +198,7 @@ ai_notes:
183
198
  - Do not use for bulky or complex interfaces.
184
199
  - Do not use collapse blocks inside sidebars.
185
200
  - Do not use two-column content on separate surfaces.
201
+ - Do not use a two-column form inside a sidebar unless the product task explicitly requires it and the content still fits.
202
+ - Do not place UiPageFooter inside UiModalSidebar.
203
+ - Do not mix unrelated spacing strategies inside one sidebar form; keep gaps owned by the form container and local error margins.
186
204
  - Use a full page or modal window for bulky flows.
@@ -35,6 +35,12 @@ shared_rules:
35
35
  - Do not duplicate content-surface padding on the page root; outer page spacing is controlled by the CRM host layout.
36
36
  - Prefer public components from @retailcrm/embed-ui-v1-components/remote.
37
37
  - Do not invent custom chrome for pages, forms, tables, tabs, buttons, modals, or sidebars when a documented component fits.
38
+ - For Vue remote pages and page-like components, prefer local CSS Modules with `<style module lang="less">`; use shared global styles only as an intentional feature-level abstraction.
39
+ - Page-specific CSS Module class names should still describe the block they style, for example `entity-list__actions`, `settings-form__field-error`, or `modal-sidebar-form__footer`.
40
+ - Use UiLoader for loading states, UiAlert variant="danger" for page-level API failures, and UiAlert variant="success" or an approved local success pattern for successful saves.
41
+ - Use UiInfobox for richer explanatory empty states; use UiTable #empty when the empty state belongs directly to a table body.
42
+ - If a column or dependent UI exists only for an enabled setting, render the column conditionally instead of showing empty cells.
43
+ - Avoid loading expensive dependent API data when the setting that needs it is disabled.
38
44
 
39
45
  decision_guide:
40
46
  - need: Searchable, filterable registry.
@@ -63,9 +69,13 @@ ai_notes:
63
69
  - Keep operational CRM screens dense, scannable, and work-focused.
64
70
  - Prefer variant="success" for the main save/apply/create action and keep it visually dominant.
65
71
  - Prefer appearance="tertiary" for supportive actions that sit near content but should stay visually quiet.
72
+ - Keep page-specific layout styles in CSS Modules unless the task records a shared styling abstraction.
73
+ - Choose one documented loading, error, success, and empty-state pattern for the page before finishing.
66
74
  avoid:
67
75
  - Do not turn internal CRM work screens into marketing-style layouts.
68
76
  - Do not hide filters or primary workflow controls away from the content they affect.
69
77
  - Do not place multiple Success Primary, Default Primary, or Danger Primary actions with the same variant in the page-level scope or inside one collapse-box scope.
70
78
  - Do not default every non-primary action to appearance="secondary"; choose by local action importance.
71
79
  - Do not add page-surface padding to both the page root and the white content surface.
80
+ - Do not use a shared `admin.less` or other global page stylesheet only to avoid local composition styles.
81
+ - Do not show settings-dependent table columns as permanent empty placeholder columns.
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@retailcrm/embed-ui-v1-components",
3
3
  "bin": "bin/embed-ui-v1-components.mjs",
4
4
  "type": "module",
5
- "version": "0.9.26",
5
+ "version": "0.9.28",
6
6
  "license": "MIT",
7
7
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
8
8
  "repository": {
@@ -82,8 +82,8 @@
82
82
  "@storybook/vue3": "^10.3.5",
83
83
  "@storybook/vue3-vite": "^10.3.5",
84
84
  "@vitejs/plugin-vue": "^6.0.2",
85
- "@vitest/browser": "4.1.3",
86
- "@vitest/browser-playwright": "4.1.3",
85
+ "@vitest/browser": "4.1.8",
86
+ "@vitest/browser-playwright": "4.1.8",
87
87
  "@vue/compiler-sfc": "^3.5.25",
88
88
  "@vue/test-utils": "^2.4.6",
89
89
  "@yandex/ymaps3-types": "^1.0.19072130",
@@ -100,7 +100,7 @@
100
100
  "vite": "^7.3.2",
101
101
  "vite-plugin-dts": "^4.5.4",
102
102
  "vite-svg-loader": "^5.1.0",
103
- "vitest": "^4.1.3",
103
+ "vitest": "4.1.8",
104
104
  "vue": "^3.5.32",
105
105
  "vue-i18n": "10.0.8"
106
106
  }
@@ -32,6 +32,7 @@ Use this skill before changing frontend UI built with `@retailcrm/embed-ui-v1-co
32
32
  ## High-signal checks
33
33
 
34
34
  - For entity lists, use `UiTable`, `UiTableColumn`, and table footer slots.
35
+ - For ordinary table cells, configure `UiTableColumn` headers through props and use `v-slot` on the column; reserve `#cell` for columns that also need another named slot or extra clarity.
35
36
  - For table pagination, follow the reference example in `UiTable.yml`: button sizes, active state, dividers, arrow assets, and scoped selector pattern.
36
37
  - For form fields, use `UiField` with the matching control and forward slot props such as `id` when the control accepts them.
37
38
  - Put validation errors inside the relevant field composition, not as unrelated sibling blocks.