@retailcrm/embed-ui-v1-components 0.9.27 → 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.
@@ -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:
@@ -119,6 +119,98 @@ examples:
119
119
  console.log(row)
120
120
  }
121
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']">
140
+ <UiPopperConnector>
141
+ <UiButton
142
+ :aria-label="`Edit ${row.name}`"
143
+ appearance="tertiary"
144
+ size="sm"
145
+ @click="edit(row)"
146
+ >
147
+ <IconEdit aria-hidden="true" />
148
+ </UiButton>
149
+
150
+ <UiTooltip>
151
+ Edit {{ row.name }}
152
+ </UiTooltip>
153
+ </UiPopperConnector>
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>
171
+ </UiTableColumn>
172
+ </UiTable>
173
+ </template>
174
+
175
+ <script lang="ts" setup>
176
+ import IconDelete from '@retailcrm/embed-ui-v1-components/assets/sprites/ui/delete.svg'
177
+ import IconEdit from '@retailcrm/embed-ui-v1-components/assets/sprites/ui/edit.svg'
178
+ import {
179
+ UiButton,
180
+ UiPopperConnector,
181
+ UiTable,
182
+ UiTableColumn,
183
+ UiTooltip,
184
+ } from '@retailcrm/embed-ui-v1-components/remote'
185
+
186
+ type Row = {
187
+ id: number
188
+ name: string
189
+ }
190
+
191
+ const rows: Row[] = [
192
+ { id: 101, name: 'Anna Smith' },
193
+ { id: 102, name: 'Ilya Johnson' },
194
+ ]
195
+
196
+ const edit = (row: Row) => {
197
+ console.log(row)
198
+ }
199
+
200
+ const remove = (row: Row) => {
201
+ console.log(row)
202
+ }
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>
122
214
  - title: Expandable rows table
123
215
  notes:
124
216
  - Use the expand slot and cell toggle helper for expandable row details.
@@ -644,6 +736,9 @@ composition:
644
736
  actions:
645
737
  notes:
646
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.
647
742
  - Keep visible button text out of dense table rows; provide aria-label and matching UiTooltip text.
648
743
  - Use package sprite icons and set aria-hidden on the icon itself.
649
744
  - Wrap action buttons and UiTooltip in UiPopperConnector. Add UiPopperTarget only for custom non-component targets.
@@ -659,11 +754,13 @@ ai_notes:
659
754
  - Use UiTableSorter for sortable headers.
660
755
  - Prefer UiTableColumn `v-slot` for ordinary custom cells when label and sizing are configured through props.
661
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.
662
758
  avoid:
663
759
  - Do not hide table filters in page header actions.
664
760
  - Do not wrap UiTable in an additional white card, panel, or content-surface container.
665
761
  - Do not use UiButton inside table footer pagination.
666
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.
667
764
  - Do not put pagination only in local state when the screen has routable result sets.
668
765
  - Do not import table internals from host or src paths.
669
766
  - Do not use `<template #cell>` for every column when the default slot would be simpler.
@@ -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.27",
5
+ "version": "0.9.28",
6
6
  "license": "MIT",
7
7
  "author": "RetailDriverLLC <integration@retailcrm.ru>",
8
8
  "repository": {