@retailcrm/embed-ui-v1-components 0.9.23 → 0.9.25
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/AGENTS.md +12 -1
- package/README.md +9 -0
- package/bin/embed-ui-v1-components.mjs +52 -3
- package/bin/postinstall.mjs +3 -0
- package/docs/AI.md +49 -13
- package/docs/FORMAT.md +1 -0
- package/docs/PROFILES.md +8 -0
- package/docs/README.md +3 -1
- package/docs/STYLING.md +37 -1
- package/docs/profiles/components/UiButton.yml +3 -1
- package/docs/profiles/components/UiCollapseBox.yml +24 -0
- package/docs/profiles/components/UiField.yml +64 -10
- package/docs/profiles/components/UiModalSidebar.yml +22 -6
- package/docs/profiles/components/UiPageFooter.yml +4 -1
- package/docs/profiles/components/UiSelect.yml +8 -0
- package/docs/profiles/components/UiSelectOption.yml +2 -0
- package/docs/profiles/components/UiSwitch.yml +68 -2
- package/docs/profiles/components/UiTable.yml +76 -0
- package/docs/profiles/components/UiTooltip.yml +1 -0
- package/docs/profiles/pages/CardSettingsPage.yml +5 -0
- package/docs/profiles/pages/CollapseBlockPage.yml +6 -0
- package/docs/profiles/pages/EntityListPage.yml +7 -1
- package/docs/profiles/pages/ModalSidebar.yml +40 -12
- package/docs/profiles/pages/MultiColumnPage.yml +2 -0
- package/docs/profiles/pages/PageComposition.yml +14 -3
- package/docs/profiles/widgets/WidgetComposition.yml +113 -0
- package/package.json +3 -2
- package/templates/skills/embed-ui-v1-components-ui/SKILL.md.txt +46 -0
package/AGENTS.md
CHANGED
|
@@ -70,6 +70,9 @@ Commonly used exports from `remote` include:
|
|
|
70
70
|
- Prefer package public exports over reimplementing CRM-styled controls manually.
|
|
71
71
|
- Match component choice to semantics:
|
|
72
72
|
use `UiField` for labeled form controls, `UiAlert` for state messages, `UiPageHeader` for page-level headings.
|
|
73
|
+
- When a component uses only the default slot, prefer the `v-slot` directive on the component instead of `<template #default>`.
|
|
74
|
+
- For widget targets, keep inline UI compact: prefer `UiToolbarButton`, `UiToolbarLink`, short text, and icons.
|
|
75
|
+
- Move complex widget UI into `UiModalSidebar` or `UiModalWindow` instead of expanding the target slot.
|
|
73
76
|
- Keep imports on the public package boundary.
|
|
74
77
|
- If you are unsure whether something is public, assume only exports from `remote` and `assets/*` are safe for consumer code.
|
|
75
78
|
- If a needed capability is missing from the public API, say that clearly instead of suggesting internal imports.
|
|
@@ -96,7 +99,7 @@ Commonly used exports from `remote` include:
|
|
|
96
99
|
|
|
97
100
|
<UiPageFooter>
|
|
98
101
|
<template #actions>
|
|
99
|
-
<UiButton @click="save">
|
|
102
|
+
<UiButton variant="success" @click="save">
|
|
100
103
|
Сохранить
|
|
101
104
|
</UiButton>
|
|
102
105
|
</template>
|
|
@@ -120,6 +123,14 @@ const save = () => {}
|
|
|
120
123
|
</script>
|
|
121
124
|
```
|
|
122
125
|
|
|
126
|
+
For page actions, use `Success Primary` for the strongest save/apply/create action, usually in
|
|
127
|
+
`UiPageFooter`. Use `Default Primary` for another important action with a different meaning, and
|
|
128
|
+
move neighboring actions to secondary or tertiary appearances.
|
|
129
|
+
`UiPageHeader` and `UiPageFooter` share the same page-level action scope. When a page is split into
|
|
130
|
+
`UiCollapseBox` sections, each collapse footer starts a local action scope: it can have its own
|
|
131
|
+
single `Default Primary` action, while neighboring local actions should still use secondary or
|
|
132
|
+
tertiary appearances.
|
|
133
|
+
|
|
123
134
|
## If You Need More Context
|
|
124
135
|
|
|
125
136
|
- Package README:
|
package/README.md
CHANGED
|
@@ -68,3 +68,12 @@ npx @retailcrm/embed-ui-v1-components init-agents
|
|
|
68
68
|
Если `AGENTS.md` уже существует, команда допишет в конец инструкции для
|
|
69
69
|
`@retailcrm/embed-ui-v1-components`, если такого блока там еще нет. С `--force`
|
|
70
70
|
можно обновить уже существующий блок пакета.
|
|
71
|
+
|
|
72
|
+
Для project-level skills можно создать `.agents/skills/embed-ui-v1-components-ui/SKILL.md`:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx @retailcrm/embed-ui-v1-components init-skills
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Skill описывает повторяемый workflow для выбора page pattern, чтения профилей компонентов,
|
|
79
|
+
проверки styling constraints и ревью table pagination/form/widget composition.
|
|
@@ -10,9 +10,12 @@ const DEFAULT_NEWLINE = '\n'
|
|
|
10
10
|
const AGENTS_SECTION_HEADER = '## @retailcrm/embed-ui-v1-components'
|
|
11
11
|
const AGENTS_SECTION_START = '<!-- embed-ui-agents:start -->'
|
|
12
12
|
const AGENTS_SECTION_END = '<!-- embed-ui-agents:end -->'
|
|
13
|
+
const SKILL_NAME = 'embed-ui-v1-components-ui'
|
|
14
|
+
const SKILL_TEMPLATE_PATH = `templates/skills/${SKILL_NAME}/SKILL.md.txt`
|
|
13
15
|
|
|
14
16
|
const HELP_TEXT = `Usage:
|
|
15
17
|
npx ${PACKAGE_NAME} init-agents [target] [options]
|
|
18
|
+
npx ${PACKAGE_NAME} init-skills [target] [options]
|
|
16
19
|
|
|
17
20
|
Options:
|
|
18
21
|
-f, --force Replace existing package section in AGENTS.md
|
|
@@ -20,6 +23,7 @@ Options:
|
|
|
20
23
|
|
|
21
24
|
Examples:
|
|
22
25
|
npx ${PACKAGE_NAME} init-agents
|
|
26
|
+
npx ${PACKAGE_NAME} init-skills
|
|
23
27
|
npx ${PACKAGE_NAME} init-agents ./my-project
|
|
24
28
|
npx ${PACKAGE_NAME} init-agents --force
|
|
25
29
|
`
|
|
@@ -250,6 +254,14 @@ ${AGENTS_SECTION_END}
|
|
|
250
254
|
`
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
const createSkill = (target, packageDocsPath) => {
|
|
258
|
+
const packageRoot = getCurrentPackageRoot() ?? findPackageRoot(target)
|
|
259
|
+
const templatePath = path.join(packageRoot, SKILL_TEMPLATE_PATH)
|
|
260
|
+
const template = fs.readFileSync(templatePath, 'utf8')
|
|
261
|
+
|
|
262
|
+
return template.replaceAll('__PACKAGE_DOCS_PATH__', packageDocsPath)
|
|
263
|
+
}
|
|
264
|
+
|
|
253
265
|
const findMarkedSectionRange = (content) => {
|
|
254
266
|
const start = content.indexOf(AGENTS_SECTION_START)
|
|
255
267
|
const end = content.indexOf(AGENTS_SECTION_END, start + AGENTS_SECTION_START.length)
|
|
@@ -381,15 +393,52 @@ const initAgents = (target, force) => {
|
|
|
381
393
|
console.log(`The ${PACKAGE_NAME} instructions were appended to the end of the file.`)
|
|
382
394
|
}
|
|
383
395
|
|
|
396
|
+
const initSkills = (target, force) => {
|
|
397
|
+
if (!fs.existsSync(target)) {
|
|
398
|
+
throw new Error(`Target path does not exist: ${target}`)
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const stat = fs.statSync(target)
|
|
402
|
+
|
|
403
|
+
if (!stat.isDirectory()) {
|
|
404
|
+
throw new Error(`Target path is not a directory: ${target}`)
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const packageDocsPath = createPackageDocsPath(target)
|
|
408
|
+
const skillPath = path.join(target, '.agents', 'skills', SKILL_NAME, 'SKILL.md')
|
|
409
|
+
const fileExists = fs.existsSync(skillPath)
|
|
410
|
+
|
|
411
|
+
if (fileExists && !force) {
|
|
412
|
+
console.log(`${skillPath} already exists`)
|
|
413
|
+
console.log('Nothing was changed. Re-run with --force to refresh that skill.')
|
|
414
|
+
return
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (!fs.existsSync(path.dirname(skillPath))) {
|
|
418
|
+
fs.mkdirSync(path.dirname(skillPath), { recursive: true })
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
fs.writeFileSync(skillPath, createSkill(target, packageDocsPath), 'utf8')
|
|
422
|
+
|
|
423
|
+
const action = fileExists ? 'updated' : 'created'
|
|
424
|
+
console.log(`SKILL: ${action} ${skillPath}`)
|
|
425
|
+
}
|
|
426
|
+
|
|
384
427
|
const main = () => {
|
|
385
428
|
try {
|
|
386
429
|
const options = parseArgs(process.argv.slice(2))
|
|
387
430
|
|
|
388
|
-
if (options.command
|
|
389
|
-
|
|
431
|
+
if (options.command === 'init-agents') {
|
|
432
|
+
initAgents(options.target, options.force)
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (options.command === 'init-skills') {
|
|
437
|
+
initSkills(options.target, options.force)
|
|
438
|
+
return
|
|
390
439
|
}
|
|
391
440
|
|
|
392
|
-
|
|
441
|
+
throw new Error(`Unknown command: ${options.command}`)
|
|
393
442
|
} catch (error) {
|
|
394
443
|
console.error(error instanceof Error ? error.message : String(error))
|
|
395
444
|
console.error('')
|
package/bin/postinstall.mjs
CHANGED
|
@@ -33,6 +33,9 @@ console.log(`[${PACKAGE_NAME}] Component profiles live in docs/profiles/componen
|
|
|
33
33
|
if (!hasAgentsFile) {
|
|
34
34
|
console.log(`[${PACKAGE_NAME}] To scaffold AGENTS.md for this project, run:`)
|
|
35
35
|
console.log(` npx ${PACKAGE_NAME} init-agents`)
|
|
36
|
+
console.log(`[${PACKAGE_NAME}] To install project-level skills, run:`)
|
|
37
|
+
console.log(` npx ${PACKAGE_NAME} init-skills`)
|
|
36
38
|
} else {
|
|
37
39
|
console.log(`[${PACKAGE_NAME}] AGENTS.md already exists in this project, so no scaffold was created automatically`)
|
|
40
|
+
console.log(`[${PACKAGE_NAME}] Project-level skills can be installed with: npx ${PACKAGE_NAME} init-skills`)
|
|
38
41
|
}
|
package/docs/AI.md
CHANGED
|
@@ -32,27 +32,29 @@ When generating UI code, use this order:
|
|
|
32
32
|
3. open a detailed profile from [`PROFILES.md`](./PROFILES.md) if one exists;
|
|
33
33
|
4. read the relevant page profile from [`PROFILES.md`](./PROFILES.md) when the task is about complete
|
|
34
34
|
pages, modals, sidebars, filters, tables, or settings layouts;
|
|
35
|
-
5.
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
5. read [`WidgetComposition.yml`](./profiles/widgets/WidgetComposition.yml) when the task is about UI
|
|
36
|
+
mounted through `defineWidgetRunner`;
|
|
37
|
+
6. use [`FORMAT.md`](./FORMAT.md) as the schema for what information is considered reliable;
|
|
38
|
+
7. read [`STYLING.md`](./STYLING.md) when the task is about classes, variables, typography, or visual zones;
|
|
39
|
+
8. if no profile exists yet, fall back to public type declarations and state any inference explicitly.
|
|
38
40
|
|
|
39
41
|
## Runtime Embedding References
|
|
40
42
|
|
|
41
43
|
When generating code for a CRM extension, separate UI component choice from runtime placement:
|
|
42
44
|
|
|
43
|
-
-
|
|
45
|
+
- In `@retailcrm/embed-ui-v1-endpoint`, `docs/targets.md` explains that `target` is the CRM
|
|
44
46
|
embedding point, while `context` is the reactive CRM data available at that point.
|
|
45
|
-
-
|
|
47
|
+
- In `@retailcrm/embed-ui-v1-endpoint`, `docs/menu-placements.md` explains how host/manifest menu
|
|
46
48
|
items map to remote page codes.
|
|
47
|
-
-
|
|
48
|
-
and `definePageRunner` are connected.
|
|
49
|
-
-
|
|
49
|
+
- In `@retailcrm/embed-ui-v1-endpoint`, `docs/page-routes.md` explains how page `code`, CRM route
|
|
50
|
+
names, and `definePageRunner` are connected.
|
|
51
|
+
- In `@retailcrm/embed-ui-v1-endpoint`, `docs/define-widget-runner.md` shows how a widget receives
|
|
50
52
|
the current `target` prop.
|
|
51
|
-
-
|
|
53
|
+
- In `@retailcrm/embed-ui-v1-endpoint`, `docs/define-page-runner.md` shows how a page receives the
|
|
52
54
|
current `code` prop.
|
|
53
|
-
-
|
|
54
|
-
`order/card`, `customer/card`, `user/current`, and `settings`.
|
|
55
|
-
-
|
|
55
|
+
- In `@retailcrm/embed-ui-v1-contexts`, `docs/ru/CONCEPT.md` explains predefined CRM contexts such
|
|
56
|
+
as `order/card`, `customer/card`, `user/current`, and `settings`.
|
|
57
|
+
- In `@retailcrm/embed-ui-v1-contexts`, `docs/ru/CUSTOM.md` explains custom-field contexts.
|
|
56
58
|
|
|
57
59
|
## Default Recommendation For Common Forms
|
|
58
60
|
|
|
@@ -87,12 +89,16 @@ Default screen rules:
|
|
|
87
89
|
card/settings page, multi-column page, collapse-block page, modal sidebar, or modal window;
|
|
88
90
|
- use `UiPageHeader` for page identity and top-level actions;
|
|
89
91
|
- use `UiPageFooter` for page-level save/cancel/delete actions instead of recreating a local footer;
|
|
92
|
+
- use one `Success Primary` button for the strongest save/apply/create action that commits a result; use `Default Primary`
|
|
93
|
+
only for another important action with a different meaning;
|
|
94
|
+
- apply the 24px top and 32px side/bottom padding rule to white content surfaces, not to the page root wrapper;
|
|
90
95
|
- keep filters and controls near the content they affect;
|
|
91
96
|
- use `UiField` around labeled form controls;
|
|
92
97
|
- use `UiTable` for structured result lists;
|
|
93
98
|
- use `UiLink` for navigation and inline links, `UiButton` for commands;
|
|
94
99
|
- use `UiLoader` with `overlay: true` when loading should dim the covered page or module content;
|
|
95
100
|
- keep public imports on `@retailcrm/embed-ui-v1-components/remote`;
|
|
101
|
+
- when a component uses only the default slot, prefer `v-slot` on the component instead of `<template #default>`;
|
|
96
102
|
- avoid custom markup that recreates textbox, select, button, link, or table chrome.
|
|
97
103
|
|
|
98
104
|
## Default Recommendation For Table Screens
|
|
@@ -101,6 +107,8 @@ When building a registry, catalog, journal, search result, order list, customer
|
|
|
101
107
|
screen where users scan and refine datasets:
|
|
102
108
|
|
|
103
109
|
- put search and filters directly above `UiTable`;
|
|
110
|
+
- do not wrap `UiTable` in an extra white card or padded content surface;
|
|
111
|
+
- use a plain layout or scroll wrapper around `UiTable` only when width or overflow control is needed;
|
|
104
112
|
- use `UiTextbox` for free-text search and `UiSelect` or compact toggle controls for finite filters;
|
|
105
113
|
- keep filters, sorting, page, and page size in GET query parameters when the host app has routing;
|
|
106
114
|
- hydrate initial filter and pagination state from the current query;
|
|
@@ -112,7 +120,9 @@ screen where users scan and refine datasets:
|
|
|
112
120
|
- use chevron icon assets for table footer previous/next controls instead of text glyphs;
|
|
113
121
|
- add local CSS for table footer layout and states; use [`UiTable.yml`](./profiles/components/UiTable.yml)
|
|
114
122
|
for the reference table footer example;
|
|
115
|
-
- set `size="small"` on `UiLink` inside table cells so links match table body typography
|
|
123
|
+
- set `size="small"` on `UiLink` inside table cells so links match table body typography;
|
|
124
|
+
- prefer icon-only row action buttons inside dense table rows; keep the same action text in
|
|
125
|
+
`aria-label` and `UiTooltip`.
|
|
116
126
|
|
|
117
127
|
Suggested query names:
|
|
118
128
|
|
|
@@ -121,6 +131,32 @@ Suggested query names:
|
|
|
121
131
|
- `sort` and `direction` for sorting;
|
|
122
132
|
- `page` and `pageSize` for pagination.
|
|
123
133
|
|
|
134
|
+
## Default Recommendation For Forms
|
|
135
|
+
|
|
136
|
+
When building forms with remote controls:
|
|
137
|
+
|
|
138
|
+
- use `v-model:value` for value-bearing controls such as `UiTextbox`, `UiSelect`, `UiNumberStepper`,
|
|
139
|
+
and `UiSwitch`;
|
|
140
|
+
- if a field looks filled but backend validation receives an empty value, check the Network payload
|
|
141
|
+
before changing the component binding;
|
|
142
|
+
- use `UiField` for labeled text, select, date, and number controls;
|
|
143
|
+
- forward `UiField` slot props into the actual child control when it accepts `id`, especially
|
|
144
|
+
`UiTextbox`, `UiSelect`, and `UiNumberStepper`;
|
|
145
|
+
- when a component uses only the default slot, prefer `v-slot` on the component instead of
|
|
146
|
+
`<template #default>`;
|
|
147
|
+
- do not wrap `UiSwitch` in `UiField`; place the switch next to a visible label and optional hint,
|
|
148
|
+
and connect `UiSwitch :id` with `label :for`.
|
|
149
|
+
|
|
150
|
+
## Default Recommendation For Widgets
|
|
151
|
+
|
|
152
|
+
When building a widget mounted into a CRM target, keep the inline target UI compact and predictable:
|
|
153
|
+
|
|
154
|
+
- read [`WidgetComposition.yml`](./profiles/widgets/WidgetComposition.yml) before composing widget UI;
|
|
155
|
+
- render only simple inline UI in the target: `UiToolbarButton`, `UiToolbarLink`, short text, and icons;
|
|
156
|
+
- move forms, tables, maps, filters, summaries, and multi-step flows into `UiModalSidebar` or `UiModalWindow`;
|
|
157
|
+
- avoid custom panels, page headers, page footers, wide fixed layouts, or standalone screens inside the target slot;
|
|
158
|
+
- read the endpoint target profile before choosing labels, modal type, or data access.
|
|
159
|
+
|
|
124
160
|
## External Documentation Patterns
|
|
125
161
|
|
|
126
162
|
These references are useful when extending the profiles and examples in this package:
|
package/docs/FORMAT.md
CHANGED
|
@@ -242,6 +242,7 @@ A short list of rules specifically for code generation:
|
|
|
242
242
|
- Use short, concrete statements instead of vague praise.
|
|
243
243
|
- Use the exact names of props, emits, and slots.
|
|
244
244
|
- For slots, describe not only the name, but also what the slot does and which content restrictions exist.
|
|
245
|
+
- In examples with only a default slot, prefer `v-slot` on the component instead of `<template #default>`.
|
|
245
246
|
- For styling, distinguish between safe CSS variables and descriptive class names.
|
|
246
247
|
- Keep runnable examples in YAML profiles when they clarify safe public usage.
|
|
247
248
|
- Do not mix "how the component looks right now" with "what is publicly guaranteed".
|
package/docs/PROFILES.md
CHANGED
|
@@ -7,6 +7,7 @@ The current profile layer is structured like this:
|
|
|
7
7
|
- the index stays in markdown so both humans and agents can navigate it easily;
|
|
8
8
|
- component profiles live in `docs/profiles/components/*.yml`;
|
|
9
9
|
- page-composition profiles live in `docs/profiles/pages/*.yml`;
|
|
10
|
+
- widget-composition profiles live in `docs/profiles/widgets/*.yml`;
|
|
10
11
|
- YAML is the source of truth for structure, props, slots, emits, composition, page patterns, and AI rules.
|
|
11
12
|
|
|
12
13
|
## Table Of Contents
|
|
@@ -34,6 +35,7 @@ Use these entrypoints:
|
|
|
34
35
|
- [`COMPONENTS.md`](./COMPONENTS.md) for the full linked component index
|
|
35
36
|
- `docs/profiles/components/*.yml` for per-component machine-readable profiles
|
|
36
37
|
- `docs/profiles/pages/*.yml` for page, modal, sidebar, filter, table, and settings-layout profiles
|
|
38
|
+
- `docs/profiles/widgets/*.yml` for embedded widget composition profiles
|
|
37
39
|
|
|
38
40
|
Current high-signal core profiles:
|
|
39
41
|
|
|
@@ -62,6 +64,10 @@ Current page profiles:
|
|
|
62
64
|
- [`ModalSidebar`](./profiles/pages/ModalSidebar.yml)
|
|
63
65
|
- [`ModalWindow`](./profiles/pages/ModalWindow.yml)
|
|
64
66
|
|
|
67
|
+
Current widget profiles:
|
|
68
|
+
|
|
69
|
+
- [`WidgetComposition`](./profiles/widgets/WidgetComposition.yml)
|
|
70
|
+
|
|
65
71
|
## What To Read First
|
|
66
72
|
|
|
67
73
|
- `key_props` if you need to choose a component quickly.
|
|
@@ -71,6 +77,7 @@ Current page profiles:
|
|
|
71
77
|
- `examples` if you need copyable usage snippets.
|
|
72
78
|
- `ai_notes` if the agent needs safe defaults and anti-patterns.
|
|
73
79
|
- `profiles/pages/*.yml` if the task is about a full page, modal, sidebar, filter, table, or settings layout.
|
|
80
|
+
- `profiles/widgets/*.yml` if the task is about UI mounted through `defineWidgetRunner`.
|
|
74
81
|
|
|
75
82
|
## Styling Reads
|
|
76
83
|
|
|
@@ -82,3 +89,4 @@ Current page profiles:
|
|
|
82
89
|
- All new updates should be made in YAML profiles.
|
|
83
90
|
- Keep component-level details in `profiles/components`.
|
|
84
91
|
- Keep page-composition details in `profiles/pages`.
|
|
92
|
+
- Keep widget-composition details in `profiles/widgets`.
|
package/docs/README.md
CHANGED
|
@@ -62,7 +62,9 @@ If you need to generate UI quickly:
|
|
|
62
62
|
5. the relevant profile from [`PROFILES.md`](./PROFILES.md)
|
|
63
63
|
6. the relevant page profile from [`PROFILES.md`](./PROFILES.md) if the task is about complete pages,
|
|
64
64
|
modals, sidebars, filters, tables, or settings layouts
|
|
65
|
-
7. [`
|
|
65
|
+
7. the widget composition profile from [`PROFILES.md`](./PROFILES.md) if the task is about UI mounted
|
|
66
|
+
through `defineWidgetRunner`
|
|
67
|
+
8. [`STYLING.md`](./STYLING.md) if the task is about classes, variables, typography, or layout tuning
|
|
66
68
|
|
|
67
69
|
If you are extending the docs:
|
|
68
70
|
|
package/docs/STYLING.md
CHANGED
|
@@ -77,6 +77,13 @@ Base font family:
|
|
|
77
77
|
|
|
78
78
|
- `-apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", "Roboto", sans-serif`
|
|
79
79
|
|
|
80
|
+
Extension UI should normally not set `font-family` in local styles. Let the CRM host and Embed UI
|
|
81
|
+
components inherit the shared stack.
|
|
82
|
+
|
|
83
|
+
If a local style must set `font-family`, use the shared stack above exactly. Do not replace it with
|
|
84
|
+
Arial, Inter, Roboto-only, or another custom stack unless the task or project `AGENTS.md` records a
|
|
85
|
+
project-specific design requirement.
|
|
86
|
+
|
|
80
87
|
Main sizes:
|
|
81
88
|
|
|
82
89
|
- `h1`: `40px / 44px`
|
|
@@ -125,6 +132,21 @@ Profiles split CSS variables into practical groups:
|
|
|
125
132
|
- `internal_layout_variables`
|
|
126
133
|
useful for reasoning and debugging, but not recommended as external override points.
|
|
127
134
|
|
|
135
|
+
## State Styling
|
|
136
|
+
|
|
137
|
+
Interactive states should come from the component implementation, documented props, and documented
|
|
138
|
+
CSS variables.
|
|
139
|
+
|
|
140
|
+
For controls such as `UiSelect`, `UiTextbox`, and buttons:
|
|
141
|
+
|
|
142
|
+
- do not add custom selected, active, focused, pressed, or invalid outlines/borders just because a
|
|
143
|
+
state is visible in Figma or CRM;
|
|
144
|
+
- first check the component profile for the exact root/state classes, zones, CSS variables, and
|
|
145
|
+
notes about safe overrides;
|
|
146
|
+
- use documented variables or local wrapper styles only when the profile says that pattern is safe;
|
|
147
|
+
- if design review asks for a state that the profile does not document, treat it as a project-specific
|
|
148
|
+
requirement and record that requirement before changing CSS.
|
|
149
|
+
|
|
128
150
|
## Typical Safe Strategy
|
|
129
151
|
|
|
130
152
|
For style-sensitive generation:
|
|
@@ -132,7 +154,21 @@ For style-sensitive generation:
|
|
|
132
154
|
1. choose the correct component and size prop first;
|
|
133
155
|
2. use documented slots to create the right visual zones;
|
|
134
156
|
3. use documented CSS variables if a theme override is needed;
|
|
135
|
-
4. avoid relying on internal descendant selectors unless the profile says that is safe
|
|
157
|
+
4. avoid relying on internal descendant selectors unless the profile says that is safe;
|
|
158
|
+
5. state which styling source is being followed when the change was requested by design feedback.
|
|
159
|
+
|
|
160
|
+
## Design Feedback Triage
|
|
161
|
+
|
|
162
|
+
When design feedback does not match the current implementation, resolve the source before editing:
|
|
163
|
+
|
|
164
|
+
1. check public RetailCRM docs, local package docs, the component profile, the page profile, this
|
|
165
|
+
`STYLING.md`, Figma, and CRM computed styles when available;
|
|
166
|
+
2. if Embed UI docs describe the rule, follow the documented rule or update the component profile
|
|
167
|
+
before relying on source-code internals;
|
|
168
|
+
3. if Embed UI docs do not describe the requested rule, treat it as a project-specific design
|
|
169
|
+
requirement;
|
|
170
|
+
4. record project-specific values in the task text or project `AGENTS.md`;
|
|
171
|
+
5. when sources conflict, state the chosen source before changing markup or CSS.
|
|
136
172
|
|
|
137
173
|
## How To Mention Styles In Profiles
|
|
138
174
|
|
|
@@ -228,7 +228,9 @@ composition:
|
|
|
228
228
|
|
|
229
229
|
ai_notes:
|
|
230
230
|
do:
|
|
231
|
-
- Start with appearance=primary for the main CTA.
|
|
231
|
+
- Start with appearance=primary for the main CTA, then choose variant by meaning.
|
|
232
|
+
- Use variant="success" for the strongest create/save/apply action when it commits a result, and keep only one Success Primary on a page.
|
|
233
|
+
- Use the default variant for an important primary action that is not the strongest commit action.
|
|
232
234
|
- Use secondary or tertiary for neighboring actions near titles.
|
|
233
235
|
avoid:
|
|
234
236
|
- Do not replace UiButton with UiLink when the action should read as a button.
|
|
@@ -76,6 +76,26 @@ examples:
|
|
|
76
76
|
import MyCustomIcon from '@retailcrm/embed-ui-v1-components/assets/sprites/actions/info.svg'
|
|
77
77
|
import { UiCollapseBox } from '@retailcrm/embed-ui-v1-components/remote'
|
|
78
78
|
</script>
|
|
79
|
+
- title: Section with local action
|
|
80
|
+
code: |
|
|
81
|
+
<template>
|
|
82
|
+
<UiCollapseBox>
|
|
83
|
+
<template #title>Connection settings</template>
|
|
84
|
+
<template #body-content>Section form content</template>
|
|
85
|
+
<template #footer-content>
|
|
86
|
+
<UiButton @click="saveSection">Save section</UiButton>
|
|
87
|
+
</template>
|
|
88
|
+
</UiCollapseBox>
|
|
89
|
+
</template>
|
|
90
|
+
|
|
91
|
+
<script lang="ts" setup>
|
|
92
|
+
import {
|
|
93
|
+
UiButton,
|
|
94
|
+
UiCollapseBox,
|
|
95
|
+
} from '@retailcrm/embed-ui-v1-components/remote'
|
|
96
|
+
|
|
97
|
+
const saveSection = () => {}
|
|
98
|
+
</script>
|
|
79
99
|
use_when:
|
|
80
100
|
- You need a ready-to-use collapsible box.
|
|
81
101
|
- You need grouped expandable sections with consistent visuals.
|
|
@@ -112,8 +132,12 @@ ai_notes:
|
|
|
112
132
|
do:
|
|
113
133
|
- Use UiCollapseBox for structured disclosure UI.
|
|
114
134
|
- Put the summary in the header and details in the body; avoid hiding critical required fields.
|
|
135
|
+
- Treat the footer and footer-content slots as a local action area for the section.
|
|
136
|
+
- Use one Default Primary button in a collapse footer for the main local action when the page has several independent sections.
|
|
137
|
+
- Use Success Primary in a collapse footer only when that section's local action is the strongest create/save/apply action.
|
|
115
138
|
avoid:
|
|
116
139
|
- Do not rebuild collapse header UI manually when this component already fits.
|
|
140
|
+
- Do not put several primary buttons of the same variant in one collapse footer.
|
|
117
141
|
|
|
118
142
|
composition:
|
|
119
143
|
works_well_with:
|
|
@@ -13,22 +13,21 @@ related_components:
|
|
|
13
13
|
- UiTextbox
|
|
14
14
|
- UiSelect
|
|
15
15
|
- UiCheckbox
|
|
16
|
+
- UiNumberStepper
|
|
16
17
|
- UiDatePicker
|
|
17
18
|
|
|
18
19
|
examples:
|
|
19
20
|
- title: Basic usage
|
|
20
21
|
code: |
|
|
21
22
|
<template>
|
|
22
|
-
<UiField id="name-field" label="Name" hint="At least 3 characters">
|
|
23
|
-
<
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
:
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/>
|
|
31
|
-
</template>
|
|
23
|
+
<UiField v-slot="field" id="name-field" label="Name" hint="At least 3 characters">
|
|
24
|
+
<UiTextbox
|
|
25
|
+
:id="field.id"
|
|
26
|
+
:input-attributes="{
|
|
27
|
+
'aria-labelledby': field.ariaLabelledby,
|
|
28
|
+
'aria-invalid': field.ariaInvalid,
|
|
29
|
+
}"
|
|
30
|
+
/>
|
|
32
31
|
</UiField>
|
|
33
32
|
</template>
|
|
34
33
|
|
|
@@ -61,6 +60,56 @@ examples:
|
|
|
61
60
|
<script lang="ts" setup>
|
|
62
61
|
import { UiField, UiTextbox } from '@retailcrm/embed-ui-v1-components/remote'
|
|
63
62
|
</script>
|
|
63
|
+
- title: Select inside a field
|
|
64
|
+
code: |
|
|
65
|
+
<template>
|
|
66
|
+
<UiField v-slot="field" id="manager-field" label="Manager">
|
|
67
|
+
<UiSelect
|
|
68
|
+
:id="field.id"
|
|
69
|
+
v-model:value="manager"
|
|
70
|
+
:invalid="field.ariaInvalid === 'true'"
|
|
71
|
+
placeholder="Select manager"
|
|
72
|
+
>
|
|
73
|
+
<UiSelectOption value="anna" label="Anna Smith" />
|
|
74
|
+
<UiSelectOption value="ilya" label="Ilya Johnson" />
|
|
75
|
+
</UiSelect>
|
|
76
|
+
</UiField>
|
|
77
|
+
</template>
|
|
78
|
+
|
|
79
|
+
<script lang="ts" setup>
|
|
80
|
+
import { ref } from 'vue'
|
|
81
|
+
|
|
82
|
+
import {
|
|
83
|
+
UiField,
|
|
84
|
+
UiSelect,
|
|
85
|
+
UiSelectOption,
|
|
86
|
+
} from '@retailcrm/embed-ui-v1-components/remote'
|
|
87
|
+
|
|
88
|
+
const manager = ref<string | null>(null)
|
|
89
|
+
</script>
|
|
90
|
+
- title: Number stepper inside a field
|
|
91
|
+
code: |
|
|
92
|
+
<template>
|
|
93
|
+
<UiField v-slot="field" id="duration-field" label="Duration, minutes">
|
|
94
|
+
<UiNumberStepper
|
|
95
|
+
:id="field.id"
|
|
96
|
+
v-model:value="duration"
|
|
97
|
+
:min="0"
|
|
98
|
+
:step="15"
|
|
99
|
+
/>
|
|
100
|
+
</UiField>
|
|
101
|
+
</template>
|
|
102
|
+
|
|
103
|
+
<script lang="ts" setup>
|
|
104
|
+
import { ref } from 'vue'
|
|
105
|
+
|
|
106
|
+
import {
|
|
107
|
+
UiField,
|
|
108
|
+
UiNumberStepper,
|
|
109
|
+
} from '@retailcrm/embed-ui-v1-components/remote'
|
|
110
|
+
|
|
111
|
+
const duration = ref(30)
|
|
112
|
+
</script>
|
|
64
113
|
use_when:
|
|
65
114
|
- You need a labeled form control with consistent field semantics.
|
|
66
115
|
- You need to pass id, aria-labelledby, and aria-invalid into an inner control.
|
|
@@ -109,6 +158,7 @@ api:
|
|
|
109
158
|
- one form control
|
|
110
159
|
- UiTextbox
|
|
111
160
|
- UiSelect
|
|
161
|
+
- UiNumberStepper
|
|
112
162
|
- UiDatePicker
|
|
113
163
|
avoid:
|
|
114
164
|
- several unrelated controls
|
|
@@ -248,6 +298,7 @@ composition:
|
|
|
248
298
|
works_well_with:
|
|
249
299
|
- UiTextbox
|
|
250
300
|
- UiSelect
|
|
301
|
+
- UiNumberStepper
|
|
251
302
|
- UiDatePicker
|
|
252
303
|
- UiTimePicker
|
|
253
304
|
patterns:
|
|
@@ -258,7 +309,10 @@ composition:
|
|
|
258
309
|
ai_notes:
|
|
259
310
|
do:
|
|
260
311
|
- Forward slot props into the actual control in most form scenarios.
|
|
312
|
+
- Pass field.id to child controls that accept id, including UiTextbox, UiSelect, and UiNumberStepper.
|
|
313
|
+
- Use v-slot on UiField when the field uses only the default slot.
|
|
261
314
|
- Use UiField as a semantic wrapper for a single control.
|
|
262
315
|
avoid:
|
|
263
316
|
- Do not use UiField as a generic visual container without control semantics.
|
|
317
|
+
- Do not use UiField for UiSwitch settings rows; pair UiSwitch with a visible label and hint text instead.
|
|
264
318
|
- Do not ignore id and ariaLabelledby when accessibility matters.
|
|
@@ -44,18 +44,30 @@ examples:
|
|
|
44
44
|
</div>
|
|
45
45
|
|
|
46
46
|
<template #footer>
|
|
47
|
-
<
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
<div style="display: flex; align-items: center; gap: 12px;">
|
|
48
|
+
<UiButton @click="modalSidebarInner = false">
|
|
49
|
+
Save
|
|
50
|
+
</UiButton>
|
|
51
|
+
|
|
52
|
+
<UiButton appearance="secondary" @click="modalSidebarInner = false">
|
|
53
|
+
Close
|
|
54
|
+
</UiButton>
|
|
55
|
+
</div>
|
|
50
56
|
</template>
|
|
51
57
|
</UiModalSidebar>
|
|
52
58
|
</div>
|
|
53
59
|
</div>
|
|
54
60
|
|
|
55
61
|
<template v-if="footer" #footer>
|
|
56
|
-
<
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
<div style="display: flex; align-items: center; gap: 12px;">
|
|
63
|
+
<UiButton @click="open = false">
|
|
64
|
+
Save
|
|
65
|
+
</UiButton>
|
|
66
|
+
|
|
67
|
+
<UiButton appearance="secondary" @click="open = false">
|
|
68
|
+
Close
|
|
69
|
+
</UiButton>
|
|
70
|
+
</div>
|
|
59
71
|
</template>
|
|
60
72
|
</UiModalSidebar>
|
|
61
73
|
</div>
|
|
@@ -125,6 +137,9 @@ ai_notes:
|
|
|
125
137
|
do:
|
|
126
138
|
- Use UiModalSidebar when page context should stay visually connected to the modal content.
|
|
127
139
|
- Use for inspect, edit, or secondary workflows that relate to the current list or page.
|
|
140
|
+
- Group several footer buttons in flex rows; 12px and 16px are the common gaps inside a group.
|
|
141
|
+
- Footer actions may be split into left and right groups when their meanings differ.
|
|
142
|
+
- Put destructive icon-only actions on the right and confirm them with UiPopconfirm okVariant="danger"; the confirmation button is primary by default.
|
|
128
143
|
avoid:
|
|
129
144
|
- Do not use for short confirmations; use UiModalWindow.
|
|
130
145
|
|
|
@@ -133,6 +148,7 @@ composition:
|
|
|
133
148
|
- UiTable
|
|
134
149
|
- UiField
|
|
135
150
|
- UiButton
|
|
151
|
+
- UiPopconfirm
|
|
136
152
|
patterns:
|
|
137
153
|
- title: Row detail side panel
|
|
138
154
|
notes: Open from a table row when the user should keep list context visible.
|
|
@@ -84,7 +84,7 @@ api:
|
|
|
84
84
|
- wide content blocks
|
|
85
85
|
- nested page sections
|
|
86
86
|
layout_effect: Renders as the flexible left area and can contain multiple actions.
|
|
87
|
-
notes: Use this slot for save, apply, cancel, or save-and-exit actions.
|
|
87
|
+
notes: Use this slot for save, apply, cancel, or save-and-exit actions. The main save/apply action is usually a Success Primary UiButton.
|
|
88
88
|
- name: aside
|
|
89
89
|
zone: right-side aside action group
|
|
90
90
|
creates: Separate aside action area.
|
|
@@ -156,6 +156,8 @@ ai_notes:
|
|
|
156
156
|
do:
|
|
157
157
|
- Import UiPageFooter from @retailcrm/embed-ui-v1-components/remote.
|
|
158
158
|
- Use the actions slot for primary and secondary page actions.
|
|
159
|
+
- Use UiButton variant="success" for the strongest save/apply/create action in the footer.
|
|
160
|
+
- Keep neighboring footer actions less dominant unless they are the page's single Default Primary or Danger Primary by meaning.
|
|
159
161
|
- Use the aside slot for a separated destructive or secondary action.
|
|
160
162
|
- Put UiButton or another public action component inside slots.
|
|
161
163
|
- Add page-level positioning only in the host/page layout when the footer must stick to a specific shell area.
|
|
@@ -164,3 +166,4 @@ ai_notes:
|
|
|
164
166
|
- Do not import UiPageFooter from internal package paths.
|
|
165
167
|
- Do not expect UiPageFooter to create buttons from props.
|
|
166
168
|
- Do not expect UiPageFooter to be fixed to the bottom of the window automatically.
|
|
169
|
+
- Do not put multiple Success Primary, Default Primary, or Danger Primary actions with the same variant in the footer.
|
|
@@ -312,6 +312,10 @@ styling:
|
|
|
312
312
|
- The trigger reuses the textbox visual model.
|
|
313
313
|
- The dropdown reuses popper variables for padding, radius, and floating surface geometry.
|
|
314
314
|
- Classes are descriptive implementation hooks, not a stable external styling contract.
|
|
315
|
+
- Selected, active, focused, pressed, invalid, and disabled states should come from UiSelect and
|
|
316
|
+
UiSelectOption implementation styles or documented CSS variables.
|
|
317
|
+
- Do not add custom outlines or borders for selected options unless the task records a
|
|
318
|
+
project-specific design requirement.
|
|
315
319
|
root_classes:
|
|
316
320
|
- .ui-v1-select
|
|
317
321
|
- .ui-v1-select__trigger
|
|
@@ -375,6 +379,8 @@ behavior:
|
|
|
375
379
|
- In multiple mode values are toggled inside the array model.
|
|
376
380
|
- Filtering matches option labels and descriptions.
|
|
377
381
|
- If nothing matches, a no-result block is shown.
|
|
382
|
+
- Selected option styling is owned by the select option implementation; do not recreate it with
|
|
383
|
+
local borders or outline styles.
|
|
378
384
|
keyboard:
|
|
379
385
|
- Arrow keys move the active highlight.
|
|
380
386
|
- Escape closes the dropdown.
|
|
@@ -414,3 +420,5 @@ ai_notes:
|
|
|
414
420
|
- Do not place arbitrary div wrappers inside the option tree.
|
|
415
421
|
- Do not choose UiSelect when free text input is the real need.
|
|
416
422
|
- Do not assume the dropdown lives in normal document flow next to the trigger.
|
|
423
|
+
- Do not add custom selected-option outlines, borders, or active-state chrome unless a
|
|
424
|
+
project-specific design requirement explicitly asks for it.
|