@open-mercato/ui 0.5.1-develop.2856.35de414092 → 0.5.1-develop.2874.77704bccbd
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/.turbo/turbo-build.log +1 -1
- package/AGENTS.md +204 -121
- package/dist/backend/AppShell.js +25 -28
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/ContextHelp.js +1 -1
- package/dist/backend/ContextHelp.js.map +1 -1
- package/dist/backend/CrudForm.js +12 -15
- package/dist/backend/CrudForm.js.map +2 -2
- package/dist/backend/DataTable.js +9 -10
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/FilterBar.js +6 -8
- package/dist/backend/FilterBar.js.map +2 -2
- package/dist/backend/FilterOverlay.js +10 -10
- package/dist/backend/FilterOverlay.js.map +2 -2
- package/dist/backend/FlashMessages.js +1 -1
- package/dist/backend/FlashMessages.js.map +2 -2
- package/dist/backend/JsonBuilder.js +6 -6
- package/dist/backend/JsonBuilder.js.map +1 -1
- package/dist/backend/NextStepCallout.js +1 -1
- package/dist/backend/NextStepCallout.js.map +1 -1
- package/dist/backend/PerspectiveSidebar.js +2 -2
- package/dist/backend/PerspectiveSidebar.js.map +2 -2
- package/dist/backend/ProfileDropdown.js +1 -1
- package/dist/backend/ProfileDropdown.js.map +1 -1
- package/dist/backend/RowActions.js +1 -1
- package/dist/backend/RowActions.js.map +1 -1
- package/dist/backend/UserMenu.js +2 -2
- package/dist/backend/UserMenu.js.map +1 -1
- package/dist/backend/WebhookSetupGuide.js +11 -11
- package/dist/backend/WebhookSetupGuide.js.map +2 -2
- package/dist/backend/charts/KpiCard.js +3 -3
- package/dist/backend/charts/KpiCard.js.map +1 -1
- package/dist/backend/columns/ColumnChooserPanel.js +1 -1
- package/dist/backend/columns/ColumnChooserPanel.js.map +2 -2
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js +3 -3
- package/dist/backend/custom-fields/FieldDefinitionsEditor.js.map +2 -2
- package/dist/backend/dashboard/DashboardScreen.js +1 -1
- package/dist/backend/dashboard/DashboardScreen.js.map +1 -1
- package/dist/backend/date-range/DateRangeSelect.js +1 -1
- package/dist/backend/date-range/DateRangeSelect.js.map +1 -1
- package/dist/backend/date-range/InlineDateRangeSelect.js +1 -1
- package/dist/backend/date-range/InlineDateRangeSelect.js.map +1 -1
- package/dist/backend/detail/AccessDeniedMessage.js +1 -1
- package/dist/backend/detail/AccessDeniedMessage.js.map +1 -1
- package/dist/backend/detail/ActivitiesSection.js +5 -5
- package/dist/backend/detail/ActivitiesSection.js.map +1 -1
- package/dist/backend/detail/AddressEditor.js +3 -3
- package/dist/backend/detail/AddressEditor.js.map +2 -2
- package/dist/backend/detail/AddressTiles.js +3 -3
- package/dist/backend/detail/AddressTiles.js.map +2 -2
- package/dist/backend/detail/AttachmentMetadataDialog.js +1 -1
- package/dist/backend/detail/AttachmentMetadataDialog.js.map +1 -1
- package/dist/backend/detail/CustomDataSection.js +1 -1
- package/dist/backend/detail/CustomDataSection.js.map +1 -1
- package/dist/backend/detail/InlineEditors.js +5 -5
- package/dist/backend/detail/InlineEditors.js.map +1 -1
- package/dist/backend/detail/NotesSection.js +6 -6
- package/dist/backend/detail/NotesSection.js.map +1 -1
- package/dist/backend/detail/TagsSection.js +1 -1
- package/dist/backend/detail/TagsSection.js.map +1 -1
- package/dist/backend/devtools/UmesDevToolsPanel.js +6 -6
- package/dist/backend/devtools/UmesDevToolsPanel.js.map +2 -2
- package/dist/backend/devtools/components/ConflictWarnings.js +3 -3
- package/dist/backend/devtools/components/ConflictWarnings.js.map +2 -2
- package/dist/backend/devtools/components/EnricherTiming.js +2 -2
- package/dist/backend/devtools/components/EnricherTiming.js.map +2 -2
- package/dist/backend/devtools/components/EventFlow.js +5 -5
- package/dist/backend/devtools/components/EventFlow.js.map +2 -2
- package/dist/backend/devtools/components/ExtensionPointList.js +3 -3
- package/dist/backend/devtools/components/ExtensionPointList.js.map +2 -2
- package/dist/backend/devtools/components/InterceptorActivity.js +6 -6
- package/dist/backend/devtools/components/InterceptorActivity.js.map +2 -2
- package/dist/backend/forms/ActionsDropdown.js +1 -1
- package/dist/backend/forms/ActionsDropdown.js.map +1 -1
- package/dist/backend/forms/FormActionButtons.js +2 -3
- package/dist/backend/forms/FormActionButtons.js.map +2 -2
- package/dist/backend/indexes/PartialIndexBanner.js +8 -8
- package/dist/backend/indexes/PartialIndexBanner.js.map +2 -2
- package/dist/backend/inputs/ComboboxInput.js +1 -1
- package/dist/backend/inputs/ComboboxInput.js.map +2 -2
- package/dist/backend/inputs/DatePicker.js +3 -3
- package/dist/backend/inputs/DatePicker.js.map +1 -1
- package/dist/backend/inputs/DateTimePicker.js +3 -3
- package/dist/backend/inputs/DateTimePicker.js.map +1 -1
- package/dist/backend/inputs/EventSelect.js +1 -1
- package/dist/backend/inputs/EventSelect.js.map +2 -2
- package/dist/backend/inputs/LookupSelect.js +1 -1
- package/dist/backend/inputs/LookupSelect.js.map +1 -1
- package/dist/backend/inputs/SwitchableMarkdownInput.js +1 -1
- package/dist/backend/inputs/SwitchableMarkdownInput.js.map +1 -1
- package/dist/backend/inputs/TagsInput.js +2 -2
- package/dist/backend/inputs/TagsInput.js.map +2 -2
- package/dist/backend/inputs/TimeInput.js +1 -1
- package/dist/backend/inputs/TimeInput.js.map +1 -1
- package/dist/backend/inputs/TimePicker.js +3 -3
- package/dist/backend/inputs/TimePicker.js.map +1 -1
- package/dist/backend/messages/MessageObjectDetail.js +1 -1
- package/dist/backend/messages/MessageObjectDetail.js.map +1 -1
- package/dist/backend/messages/MessageObjectPreview.js +1 -1
- package/dist/backend/messages/MessageObjectPreview.js.map +1 -1
- package/dist/backend/messages/message-compose-form-groups.js +3 -3
- package/dist/backend/messages/message-compose-form-groups.js.map +1 -1
- package/dist/backend/notifications/NotificationCountBadge.js +1 -1
- package/dist/backend/notifications/NotificationCountBadge.js.map +2 -2
- package/dist/backend/notifications/NotificationPanel.js +3 -3
- package/dist/backend/notifications/NotificationPanel.js.map +1 -1
- package/dist/backend/progress/ProgressTopBar.js +4 -4
- package/dist/backend/progress/ProgressTopBar.js.map +2 -2
- package/dist/backend/schedule/ScheduleAgenda.js +1 -1
- package/dist/backend/schedule/ScheduleAgenda.js.map +2 -2
- package/dist/backend/schedule/ScheduleCalendar.js +1 -1
- package/dist/backend/schedule/ScheduleCalendar.js.map +1 -1
- package/dist/backend/schedule/ScheduleGrid.js +1 -1
- package/dist/backend/schedule/ScheduleGrid.js.map +2 -2
- package/dist/backend/version-history/VersionHistoryPanel.js +4 -4
- package/dist/backend/version-history/VersionHistoryPanel.js.map +2 -2
- package/dist/frontend/AuthFooter.js +1 -1
- package/dist/frontend/AuthFooter.js.map +1 -1
- package/dist/frontend/LanguageSwitcher.js +1 -1
- package/dist/frontend/LanguageSwitcher.js.map +1 -1
- package/dist/frontend/Layout.js +2 -2
- package/dist/frontend/Layout.js.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +2 -2
- package/dist/portal/PortalShell.js +15 -15
- package/dist/portal/PortalShell.js.map +2 -2
- package/dist/portal/components/PortalCard.js +2 -2
- package/dist/portal/components/PortalCard.js.map +2 -2
- package/dist/portal/components/PortalNotificationPanel.js +18 -18
- package/dist/portal/components/PortalNotificationPanel.js.map +2 -2
- package/dist/portal/components/PortalPageHeader.js +1 -1
- package/dist/portal/components/PortalPageHeader.js.map +2 -2
- package/dist/primitives/avatar.js +11 -1
- package/dist/primitives/avatar.js.map +2 -2
- package/dist/primitives/badge.js +1 -1
- package/dist/primitives/badge.js.map +1 -1
- package/dist/primitives/button.js +9 -5
- package/dist/primitives/button.js.map +2 -2
- package/dist/primitives/calendar.js +1 -1
- package/dist/primitives/calendar.js.map +1 -1
- package/dist/primitives/checkbox-field.js +63 -0
- package/dist/primitives/checkbox-field.js.map +7 -0
- package/dist/primitives/checkbox.js +31 -17
- package/dist/primitives/checkbox.js.map +2 -2
- package/dist/primitives/dialog.js +4 -4
- package/dist/primitives/dialog.js.map +1 -1
- package/dist/primitives/fancy-button.js +72 -0
- package/dist/primitives/fancy-button.js.map +7 -0
- package/dist/primitives/icon-button.js +20 -4
- package/dist/primitives/icon-button.js.map +2 -2
- package/dist/primitives/kbd.js +27 -0
- package/dist/primitives/kbd.js.map +7 -0
- package/dist/primitives/link-button.js +56 -0
- package/dist/primitives/link-button.js.map +7 -0
- package/dist/primitives/popover.js +1 -1
- package/dist/primitives/popover.js.map +1 -1
- package/dist/primitives/social-button.js +61 -0
- package/dist/primitives/social-button.js.map +7 -0
- package/dist/primitives/tabs.js +1 -1
- package/dist/primitives/tabs.js.map +1 -1
- package/dist/primitives/tag.js +45 -0
- package/dist/primitives/tag.js.map +7 -0
- package/dist/primitives/tooltip.js +1 -1
- package/dist/primitives/tooltip.js.map +1 -1
- package/package.json +3 -3
- package/src/backend/AppShell.tsx +25 -28
- package/src/backend/ContextHelp.tsx +1 -1
- package/src/backend/CrudForm.tsx +12 -15
- package/src/backend/DataTable.tsx +9 -10
- package/src/backend/FilterBar.tsx +6 -5
- package/src/backend/FilterOverlay.tsx +10 -10
- package/src/backend/FlashMessages.tsx +1 -1
- package/src/backend/JsonBuilder.tsx +6 -6
- package/src/backend/NextStepCallout.tsx +1 -1
- package/src/backend/PerspectiveSidebar.tsx +2 -2
- package/src/backend/ProfileDropdown.tsx +1 -1
- package/src/backend/RowActions.tsx +1 -1
- package/src/backend/UserMenu.tsx +2 -2
- package/src/backend/WebhookSetupGuide.tsx +11 -11
- package/src/backend/charts/KpiCard.tsx +3 -3
- package/src/backend/columns/ColumnChooserPanel.tsx +1 -1
- package/src/backend/custom-fields/FieldDefinitionsEditor.tsx +3 -3
- package/src/backend/dashboard/DashboardScreen.tsx +1 -1
- package/src/backend/date-range/DateRangeSelect.tsx +1 -1
- package/src/backend/date-range/InlineDateRangeSelect.tsx +1 -1
- package/src/backend/detail/AccessDeniedMessage.tsx +1 -1
- package/src/backend/detail/ActivitiesSection.tsx +5 -5
- package/src/backend/detail/AddressEditor.tsx +3 -3
- package/src/backend/detail/AddressTiles.tsx +3 -3
- package/src/backend/detail/AttachmentMetadataDialog.tsx +1 -1
- package/src/backend/detail/CustomDataSection.tsx +1 -1
- package/src/backend/detail/InlineEditors.tsx +5 -5
- package/src/backend/detail/NotesSection.tsx +6 -6
- package/src/backend/detail/TagsSection.tsx +1 -1
- package/src/backend/devtools/UmesDevToolsPanel.tsx +6 -6
- package/src/backend/devtools/components/ConflictWarnings.tsx +4 -4
- package/src/backend/devtools/components/EnricherTiming.tsx +2 -2
- package/src/backend/devtools/components/EventFlow.tsx +5 -5
- package/src/backend/devtools/components/ExtensionPointList.tsx +3 -3
- package/src/backend/devtools/components/InterceptorActivity.tsx +6 -6
- package/src/backend/forms/ActionsDropdown.tsx +1 -1
- package/src/backend/forms/FormActionButtons.tsx +4 -5
- package/src/backend/indexes/PartialIndexBanner.tsx +8 -8
- package/src/backend/inputs/ComboboxInput.tsx +1 -1
- package/src/backend/inputs/DatePicker.tsx +3 -3
- package/src/backend/inputs/DateTimePicker.tsx +3 -3
- package/src/backend/inputs/EventSelect.tsx +1 -1
- package/src/backend/inputs/LookupSelect.tsx +1 -1
- package/src/backend/inputs/SwitchableMarkdownInput.tsx +1 -1
- package/src/backend/inputs/TagsInput.tsx +2 -2
- package/src/backend/inputs/TimeInput.tsx +1 -1
- package/src/backend/inputs/TimePicker.tsx +3 -3
- package/src/backend/messages/MessageObjectDetail.tsx +1 -1
- package/src/backend/messages/MessageObjectPreview.tsx +1 -1
- package/src/backend/messages/message-compose-form-groups.tsx +3 -3
- package/src/backend/notifications/NotificationCountBadge.tsx +1 -1
- package/src/backend/notifications/NotificationPanel.tsx +3 -3
- package/src/backend/progress/ProgressTopBar.tsx +4 -4
- package/src/backend/schedule/ScheduleAgenda.tsx +1 -1
- package/src/backend/schedule/ScheduleCalendar.tsx +1 -1
- package/src/backend/schedule/ScheduleGrid.tsx +1 -1
- package/src/backend/version-history/VersionHistoryPanel.tsx +4 -4
- package/src/frontend/AuthFooter.tsx +1 -1
- package/src/frontend/LanguageSwitcher.tsx +1 -1
- package/src/frontend/Layout.tsx +2 -2
- package/src/index.ts +6 -1
- package/src/portal/PortalShell.tsx +15 -15
- package/src/portal/components/PortalCard.tsx +2 -2
- package/src/portal/components/PortalNotificationPanel.tsx +18 -18
- package/src/portal/components/PortalPageHeader.tsx +1 -1
- package/src/primitives/avatar.tsx +22 -0
- package/src/primitives/badge.tsx +1 -1
- package/src/primitives/button.tsx +12 -5
- package/src/primitives/calendar.tsx +1 -1
- package/src/primitives/checkbox-field.tsx +85 -0
- package/src/primitives/checkbox.tsx +44 -18
- package/src/primitives/dialog.tsx +4 -4
- package/src/primitives/fancy-button.tsx +89 -0
- package/src/primitives/icon-button.tsx +19 -2
- package/src/primitives/kbd.tsx +38 -0
- package/src/primitives/link-button.tsx +55 -0
- package/src/primitives/popover.tsx +1 -1
- package/src/primitives/social-button.tsx +80 -0
- package/src/primitives/tabs.tsx +1 -1
- package/src/primitives/tag.tsx +66 -0
- package/src/primitives/tooltip.tsx +1 -1
package/.turbo/turbo-build.log
CHANGED
package/AGENTS.md
CHANGED
|
@@ -1,125 +1,213 @@
|
|
|
1
1
|
# UI Package - Agent Guidelines
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
UI usage patterns based on customers, sales, and staff modules. Use these defaults when building new UI in `packages/ui` or consuming from other modules.
|
|
4
|
+
|
|
5
|
+
> **DS reference:** [`.ai/ds-rules.md`](../../.ai/ds-rules.md) — color tokens, typography, spacing, decision trees. **Component reference (variants/sizes/props/examples/MUST rules):** [`.ai/ui-components.md`](../../.ai/ui-components.md).
|
|
4
6
|
|
|
5
7
|
## Reference Modules
|
|
6
8
|
|
|
7
|
-
- Customers: `packages/core/src/modules/customers/backend/customers/people/create/page.tsx`,
|
|
8
|
-
- Sales: `packages/core/src/modules/sales/components/documents/SalesDocumentsTable.tsx`,
|
|
9
|
-
- Staff
|
|
9
|
+
- Customers: `packages/core/src/modules/customers/backend/customers/people/create/page.tsx`, `…/people/page.tsx`, `…/components/detail/TaskForm.tsx`
|
|
10
|
+
- Sales: `packages/core/src/modules/sales/components/documents/SalesDocumentsTable.tsx`, `…/PaymentsSection.tsx`, `…/SalesDocumentForm.tsx`
|
|
11
|
+
- Staff: `packages/core/src/modules/auth/backend/users/page.tsx`, `…/users/create/page.tsx`, `…/roles/create/page.tsx`
|
|
12
|
+
|
|
13
|
+
## Component quick reference
|
|
14
|
+
|
|
15
|
+
When you need… use this. Details (variants, sizes, props, MUST rules) live in [`.ai/ui-components.md`](../../.ai/ui-components.md).
|
|
16
|
+
|
|
17
|
+
| Need | Component | Import |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| Button with text label (with or without icon) | `Button` | `@open-mercato/ui/primitives/button` |
|
|
20
|
+
| Icon-only button | `IconButton` | `@open-mercato/ui/primitives/icon-button` |
|
|
21
|
+
| Inline link styled as button | `LinkButton` | `@open-mercato/ui/primitives/link-button` |
|
|
22
|
+
| OAuth/sign-in button (brand-styled) | `SocialButton` | `@open-mercato/ui/primitives/social-button` |
|
|
23
|
+
| Marketing CTA with brand gradient | `FancyButton` | `@open-mercato/ui/primitives/fancy-button` |
|
|
24
|
+
| Checkbox primitive (with indeterminate) | `Checkbox` | `@open-mercato/ui/primitives/checkbox` |
|
|
25
|
+
| Checkbox with label + description | `CheckboxField` | `@open-mercato/ui/primitives/checkbox-field` |
|
|
26
|
+
| User / entity avatar | `Avatar`, `AvatarStack` | `@open-mercato/ui/primitives/avatar` |
|
|
27
|
+
| Keyboard shortcut keys | `Kbd`, `KbdShortcut` | `@open-mercato/ui/primitives/kbd` |
|
|
28
|
+
| Entity tag pill | `Tag` (with `TagMap`) | `@open-mercato/ui/primitives/tag` |
|
|
29
|
+
| Wrap a `<Link>` as button | `Button asChild` / `IconButton asChild` | — |
|
|
30
|
+
|
|
31
|
+
## Critical MUST rules (top of mind)
|
|
32
|
+
|
|
33
|
+
1. **NEVER use raw `<button>` or `<input type="checkbox">`** — always use the primitives. Native checkboxes get `accent-color: var(--accent-indigo)` as a safety net for legacy code, but new code MUST use `Checkbox`.
|
|
34
|
+
2. **Always pass `type="button"` explicitly** on non-submit `Button`/`IconButton` — HTML defaults to `submit`.
|
|
35
|
+
3. **Same-row buttons MUST share `size`.** Mixing `sm` (h-8) + `default`/`icon` (h-9) is a regression. Standardized rows: DataTable toolbar = `default`/`icon` h-9, FormActionButtons = `default` h-9.
|
|
36
|
+
4. **NEVER raw `<Link>` styled as a button** — wrap with `<Button asChild>` to inherit size + radius.
|
|
37
|
+
5. **`<Button className="h-9">` is an anti-pattern** — redundant with default size, hides contract from grep.
|
|
38
|
+
6. **`Checkbox` checked color is `--accent-indigo` (NOT `--primary`)** — matches Figma and distinguishes selection from primary actions.
|
|
10
39
|
|
|
11
|
-
##
|
|
40
|
+
## CrudForm Guidelines
|
|
12
41
|
|
|
13
|
-
|
|
42
|
+
- Use `CrudForm` as the default for create/edit flows and dialog forms.
|
|
43
|
+
- If a backend page cannot use `CrudForm`, use `useGuardedMutation` from `@open-mercato/ui/backend/injection/useGuardedMutation` for every write (`POST`/`PUT`/`PATCH`/`DELETE`).
|
|
44
|
+
- Always call writes through `runMutation({ operation, context, mutationPayload })` so global injection modules (e.g. record-lock conflict handling) can run `onBeforeSave`/`onAfterSave`, apply scoped headers, and receive errors consistently.
|
|
45
|
+
- Use manual `useInjectionSpotEvents(GLOBAL_MUTATION_INJECTION_SPOT_ID)` only when `useGuardedMutation` is insufficient.
|
|
46
|
+
- Keep `CrudForm` reusable — extract shared field/group builders and submit handlers into module-level helpers.
|
|
47
|
+
- Drive validation with Zod and surface field errors via `createCrudFormError`.
|
|
48
|
+
- With `CrudForm` + Zod, validation messages may be i18n keys (`CrudForm` translates them).
|
|
49
|
+
- If you validate outside `CrudForm` or manually map `safeParse(...).error.issues`, you MUST translate `issue.message` before passing to `createCrudFormError`.
|
|
50
|
+
- Keep `fields` and `groups` in memoized helpers.
|
|
51
|
+
- Pass `entityIds` when custom fields are involved.
|
|
52
|
+
- Use `createCrud`/`updateCrud`/`deleteCrud` for submit actions and call `flash()` for success/failure messaging.
|
|
14
53
|
|
|
15
|
-
|
|
54
|
+
## UI Interaction
|
|
55
|
+
- Every new dialog must support `Cmd/Ctrl + Enter` as a primary action shortcut and `Escape` to cancel, mirroring the shared UX patterns used across modules.
|
|
56
|
+
- Default to `CrudForm` for new forms and `DataTable` for tables displaying information unless a different component is explicitly required.
|
|
57
|
+
- Use the `EventSelect` component from `@open-mercato/ui/backend/inputs/EventSelect` for event selection. It fetches declared events via the `/api/events` endpoint.
|
|
58
|
+
- Never use `window.confirm` — use the shared `ConfirmDialog` and `useConfirmDialog` from `@open-mercato/ui/backend/confirm-dialog` for confirmation flows.
|
|
59
|
+
- New CRUD forms should use `CrudForm` wired to CRUD factory/commands APIs and be shared between create/edit flows.
|
|
60
|
+
- Prefer reusing components from the shared `packages/ui` package before introducing new UI primitives.
|
|
61
|
+
- For new `DataTable` columns, set `meta.truncate` and `meta.maxWidth` in the column config when you need specific truncation behavior; only rely on defaults when those are not set.
|
|
62
|
+
- When you create new UI check reusable components before creating UI from scratch (see [`.ai/specs/implemented/SPEC-001-2026-01-21-ui-reusable-components.md`](.ai/specs/implemented/SPEC-001-2026-01-21-ui-reusable-components.md))
|
|
63
|
+
- For form/detail page headers and footers, use `FormHeader` and `FormFooter` from `@open-mercato/ui/backend/forms`. `FormHeader` supports two modes: `edit` (compact, used automatically by CrudForm) and `detail` (large title with entity type label, status badge, Actions dropdown). Delete/Cancel/Save are always standalone buttons; additional context actions (Convert, Send, etc.) go into the `menuActions` array rendered as an "Actions" dropdown. See [SPEC-016](.ai/specs/implemented/SPEC-016-2026-02-03-form-headers-footers.md) for full API.
|
|
16
64
|
|
|
17
|
-
|
|
18
|
-
|----------|-----------|---------|
|
|
19
|
-
| Button with text label (with or without icon) | `Button` | Save, Cancel, Apply filters |
|
|
20
|
-
| Icon-only button (no visible text) | `IconButton` | Close ✕, Settings ⚙, Trash 🗑 |
|
|
21
|
-
| Button wrapping a `<Link>` | `IconButton asChild` or `Button asChild` | `<IconButton asChild><Link href="...">...</Link></IconButton>` |
|
|
65
|
+
## Avatar
|
|
22
66
|
|
|
23
|
-
|
|
67
|
+
`Avatar` displays a user or entity with a photo or auto-generated initials. `AvatarStack` overlaps multiple avatars with an overflow indicator.
|
|
68
|
+
|
|
69
|
+
### Import
|
|
24
70
|
|
|
25
71
|
```typescript
|
|
26
|
-
import {
|
|
27
|
-
import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
72
|
+
import { Avatar, AvatarStack } from '@open-mercato/ui/primitives/avatar'
|
|
28
73
|
```
|
|
29
74
|
|
|
30
|
-
###
|
|
75
|
+
### Sizes
|
|
31
76
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
77
|
+
| Size | px | Use case |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `sm` | 24px | Table rows, AvatarStack, inline lists |
|
|
80
|
+
| `default` | 32px | Default — sidebar, comments, activity feed |
|
|
81
|
+
| `md` | 40px | Section headers, assignee cards |
|
|
82
|
+
| `lg` | 80px | Profile / detail page header |
|
|
37
83
|
|
|
38
|
-
###
|
|
84
|
+
### Usage
|
|
39
85
|
|
|
40
|
-
|
|
86
|
+
```tsx
|
|
87
|
+
// Photo
|
|
88
|
+
<Avatar src="/avatars/jan.jpg" name="Jan Kowalski" size="md" />
|
|
89
|
+
|
|
90
|
+
// Initials (auto-generated from name)
|
|
91
|
+
<Avatar name="Jan Kowalski" /> // → "JK"
|
|
92
|
+
<Avatar name="Copperleaf Design" /> // → "CD"
|
|
93
|
+
|
|
94
|
+
// Stack with overflow
|
|
95
|
+
<AvatarStack max={3}>
|
|
96
|
+
<Avatar name="Jan Kowalski" size="sm" />
|
|
97
|
+
<Avatar name="Oliwia Z." size="sm" />
|
|
98
|
+
<Avatar name="Anna Nowak" size="sm" />
|
|
99
|
+
<Avatar name="Sarah Mitchell" size="sm" />
|
|
100
|
+
</AvatarStack>
|
|
101
|
+
// renders: JK · OZ · AN · +1
|
|
102
|
+
```
|
|
41
103
|
|
|
42
|
-
|
|
104
|
+
### MUST rules
|
|
43
105
|
|
|
44
|
-
|
|
106
|
+
- NEVER render `<div className="rounded-full bg-muted ...">` for avatars — use `Avatar`
|
|
107
|
+
- `size="sm"` uses `text-[9px]` — DS exception for tiny initials (same as notification badge count)
|
|
108
|
+
- `ring-2 ring-background` is built-in — provides the border needed for `AvatarStack` overlap
|
|
109
|
+
- For unknown users or empty states: render `<Avatar />` (shows blank muted circle)
|
|
45
110
|
|
|
46
|
-
|
|
111
|
+
---
|
|
47
112
|
|
|
48
|
-
|
|
113
|
+
## Kbd
|
|
114
|
+
|
|
115
|
+
`Kbd` renders a keyboard key. `KbdShortcut` renders a full shortcut sequence (`⌘ + Enter`).
|
|
116
|
+
|
|
117
|
+
Use in dialog footers, tooltips, and empty states to communicate keyboard affordances required by our UX rules (every dialog MUST support `Cmd/Ctrl+Enter` submit and `Escape` cancel).
|
|
118
|
+
|
|
119
|
+
### Import
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { Kbd, KbdShortcut } from '@open-mercato/ui/primitives/kbd'
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Usage
|
|
49
126
|
|
|
50
127
|
```tsx
|
|
51
|
-
//
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
<
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
type="button"
|
|
64
|
-
variant="ghost"
|
|
65
|
-
size="sm"
|
|
66
|
-
className={cn(
|
|
67
|
-
'h-auto rounded-none border-b-2 px-0 py-1 hover:bg-transparent',
|
|
68
|
-
isActive ? 'border-primary text-foreground' : 'border-transparent text-muted-foreground'
|
|
69
|
-
)}
|
|
70
|
-
>
|
|
71
|
-
{label}
|
|
72
|
-
</Button>
|
|
73
|
-
|
|
74
|
-
// Dropdown menu item
|
|
75
|
-
<Button variant="ghost" size="sm" type="button" className="w-full justify-start" role="menuitem">
|
|
76
|
-
<Icon className="size-4" /> {label}
|
|
77
|
-
</Button>
|
|
78
|
-
|
|
79
|
-
// Compact toolbar button (rich text editor)
|
|
80
|
-
<Button variant="ghost" size="sm" type="button" className="h-auto px-2 py-0.5 text-xs">
|
|
81
|
-
Bold
|
|
82
|
-
</Button>
|
|
83
|
-
|
|
84
|
-
// Collapsible section header
|
|
85
|
-
<Button variant="muted" type="button" className="w-full justify-between" onClick={toggle}>
|
|
86
|
-
<span>{sectionLabel}</span>
|
|
87
|
-
<ChevronDown className={cn('size-4 transition-transform', open && 'rotate-180')} />
|
|
88
|
-
</Button>
|
|
89
|
-
|
|
90
|
-
// Link-styled icon button (wrapping Next.js Link)
|
|
91
|
-
<IconButton asChild variant="ghost" size="sm">
|
|
92
|
-
<Link href="/backend/settings">
|
|
93
|
-
<Settings className="size-4" />
|
|
94
|
-
</Link>
|
|
95
|
-
</IconButton>
|
|
128
|
+
// Single key
|
|
129
|
+
<Kbd>Esc</Kbd>
|
|
130
|
+
<Kbd>⌘</Kbd>
|
|
131
|
+
|
|
132
|
+
// Shortcut sequence
|
|
133
|
+
<KbdShortcut keys={['⌘', 'Enter']} /> // renders: ⌘ + Enter
|
|
134
|
+
<KbdShortcut keys={['Ctrl', 'S']} />
|
|
135
|
+
|
|
136
|
+
// In a dialog footer hint
|
|
137
|
+
<span className="text-xs text-muted-foreground">
|
|
138
|
+
Press <KbdShortcut keys={['⌘', 'Enter']} /> to save or <Kbd>Esc</Kbd> to cancel
|
|
139
|
+
</span>
|
|
96
140
|
```
|
|
97
141
|
|
|
98
|
-
|
|
142
|
+
### MUST rules
|
|
99
143
|
|
|
100
|
-
-
|
|
101
|
-
-
|
|
102
|
-
- Always call writes through `runMutation({ operation, context, mutationPayload })` so global injection modules (for example record-lock conflict handling) can run `onBeforeSave`/`onAfterSave`, apply scoped request headers, and receive mutation errors consistently.
|
|
103
|
-
- Use manual `useInjectionSpotEvents(GLOBAL_MUTATION_INJECTION_SPOT_ID)` wiring only when you need behavior that `useGuardedMutation` does not support.
|
|
104
|
-
- Keep `CrudForm` implementations reusable: extract shared field/group builders and submit handlers into module-level helpers when multiple pages or dialogs need the same shape.
|
|
105
|
-
- Drive validation with a Zod schema and surface field errors via `createCrudFormError`.
|
|
106
|
-
- When using `CrudForm` with Zod, validation messages may be i18n keys because `CrudForm` translates them before display.
|
|
107
|
-
- If you validate outside `CrudForm` or manually map `safeParse(...).error.issues`, you MUST translate `issue.message` before passing it to `createCrudFormError` or rendering it in the UI.
|
|
108
|
-
- Keep `fields` and `groups` in memoized helpers (see customers person form config).
|
|
109
|
-
- Pass `entityIds` when custom fields are involved so form helpers load correct custom-field sets.
|
|
110
|
-
- Use `createCrud`/`updateCrud`/`deleteCrud` for submit actions and call `flash()` for success or failure messaging.
|
|
111
|
-
- For multi-step submit flows, keep the form submit handler focused and move secondary operations (like extra address writes) into isolated helpers with per-item error handling.
|
|
144
|
+
- NEVER use raw `<span>` or `<code>` to display keyboard keys — use `Kbd`
|
|
145
|
+
- Platform-specific keys (`⌘` vs `Ctrl`): detect with `navigator.platform` or use `Ctrl/⌘` text when cross-platform
|
|
112
146
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Tag
|
|
150
|
+
|
|
151
|
+
`Tag` is a static pill element representing a user-applied label on an entity (e.g. "Customer", "Hot", "Renewal"). Use it for entity tags — NOT for system status display (use `StatusBadge` for that).
|
|
152
|
+
|
|
153
|
+
### Tag vs StatusBadge
|
|
154
|
+
|
|
155
|
+
| | `Tag` | `StatusBadge` |
|
|
156
|
+
|---|---|---|
|
|
157
|
+
| Purpose | User-applied label / category | System status (active, pending, failed…) |
|
|
158
|
+
| Shape | `rounded-full` pill | `rounded-full` pill |
|
|
159
|
+
| Dot | optional (`dot` prop) | optional (`dot` prop) |
|
|
160
|
+
| `brand` variant | ✅ (violet — for custom views/renewal tags) | ❌ |
|
|
161
|
+
|
|
162
|
+
### Import
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
import { Tag } from '@open-mercato/ui/primitives/tag'
|
|
166
|
+
import type { TagMap } from '@open-mercato/ui/primitives/tag'
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Variants
|
|
170
|
+
|
|
171
|
+
| Variant | Token | Example use |
|
|
172
|
+
|---|---|---|
|
|
173
|
+
| `default` | `border-border bg-background text-muted-foreground` | Generic / inactive tag |
|
|
174
|
+
| `success` | `status-success-*` | Customer, Shipped, Active |
|
|
175
|
+
| `warning` | `status-warning-*` | Renewal, At risk |
|
|
176
|
+
| `error` | `status-error-*` | Hot, Overdue, Blocked |
|
|
177
|
+
| `info` | `status-info-*` | Pending, In review |
|
|
178
|
+
| `neutral` | `status-neutral-*` | Archived, Draft |
|
|
179
|
+
| `brand` | `brand-violet/10` bg, `brand-violet/30` border, `text-brand-violet` | Custom views, Perspectives |
|
|
180
|
+
|
|
181
|
+
### Usage
|
|
182
|
+
|
|
183
|
+
```tsx
|
|
184
|
+
<Tag variant="success" dot>Customer</Tag>
|
|
185
|
+
<Tag variant="error" dot>Hot</Tag>
|
|
186
|
+
<Tag variant="brand" dot>Renewal Q1 2026</Tag>
|
|
187
|
+
<Tag variant="neutral">Inactive</Tag>
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### TagMap helper
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
import type { TagMap } from '@open-mercato/ui/primitives/tag'
|
|
194
|
+
|
|
195
|
+
const leadTagMap: TagMap<'customer' | 'hot' | 'inactive' | 'renewal'> = {
|
|
196
|
+
customer: 'success',
|
|
197
|
+
hot: 'error',
|
|
198
|
+
inactive: 'neutral',
|
|
199
|
+
renewal: 'brand',
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
<Tag variant={leadTagMap[tag.type]} dot>{tag.label}</Tag>
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### MUST rules
|
|
206
|
+
|
|
207
|
+
- NEVER hardcode colors on `Tag` — use variants only
|
|
208
|
+
- Use `dot` for tags that represent a status-like category (Customer, Hot); omit for purely descriptive labels
|
|
209
|
+
- For "Manage tags" / add-tag affordances: use a `Button variant="ghost"` or dashed outline — NOT `Tag`
|
|
210
|
+
- `brand` variant is for user-saved views and renewal/custom category tags only (see brand color rules in root AGENTS.md)
|
|
123
211
|
|
|
124
212
|
## DataTable Guidelines
|
|
125
213
|
|
|
@@ -129,10 +217,10 @@ import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
|
129
217
|
- Populate `columns` with explicit renderers and set `meta.truncate`/`meta.maxWidth` where truncation is needed.
|
|
130
218
|
- For filters, use `FilterBar`/`FilterOverlay` with async option loaders; keep `pageSize` at or below 100.
|
|
131
219
|
- Support exports using `buildCrudExportUrl` and pass `exportOptions` to `DataTable`.
|
|
132
|
-
- Use `RowActions` for per-row actions
|
|
220
|
+
- Use `RowActions` for per-row actions; navigate via `onRowClick` or action links.
|
|
133
221
|
- Keep table state (paging, sorting, filters, search) in component state and reload on scope changes.
|
|
134
|
-
- Keep `extensionTableId` stable and deterministic
|
|
135
|
-
- Render injected row actions and bulk actions through `RowActions`/bulk
|
|
222
|
+
- Keep `extensionTableId` stable and deterministic.
|
|
223
|
+
- Render injected row actions and bulk actions through `RowActions`/bulk handlers so they follow the same guard and i18n behavior as built-ins.
|
|
136
224
|
|
|
137
225
|
## CrudForm Field Injection (UMES Phase G)
|
|
138
226
|
|
|
@@ -142,37 +230,37 @@ import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
|
142
230
|
|
|
143
231
|
## Menu Injection (UMES Phase A/B)
|
|
144
232
|
|
|
145
|
-
- Use `useInjectedMenuItems(surfaceId)`
|
|
233
|
+
- Use `useInjectedMenuItems(surfaceId)` for chrome surfaces (`menu:sidebar:*`, `menu:topbar:*`).
|
|
146
234
|
- Merge built-in and injected items with `mergeMenuItems(builtIn, injected)` to preserve deterministic placement.
|
|
147
|
-
- For relative positioning,
|
|
148
|
-
- Treat injected labels as i18n-first: prefer `labelKey` (with human fallback `label`) and `groupLabelKey
|
|
149
|
-
- Add stable attributes (`data-menu-item-id="<id>"`) when rendering merged items so integration tests can assert injected entries
|
|
150
|
-
- When filtering menu items by `item.features` or route `requireFeatures`, MUST use the shared wildcard-aware matcher from `@open-mercato/shared/lib/auth/featureMatch
|
|
235
|
+
- For relative positioning, use `InjectionPosition` + `relativeTo` IDs; if `relativeTo` is missing, insertion falls back to append.
|
|
236
|
+
- Treat injected labels as i18n-first: prefer `labelKey` (with human fallback `label`) and `groupLabelKey`.
|
|
237
|
+
- Add stable attributes (`data-menu-item-id="<id>"`) when rendering merged items so integration tests can assert injected entries.
|
|
238
|
+
- When filtering menu items by `item.features` or route `requireFeatures`, MUST use the shared wildcard-aware matcher from `@open-mercato/shared/lib/auth/featureMatch` — `Set.has(...)`/`includes(...)` miss `module.*` grants.
|
|
151
239
|
|
|
152
240
|
## Loading, Empty, and Error States
|
|
153
241
|
|
|
154
242
|
- For list/detail data loading, use `LoadingMessage` and `ErrorMessage` from `@open-mercato/ui/backend/detail`.
|
|
155
243
|
- For record-backed backend detail/edit pages, treat `notFound` as a dedicated page state, separate from generic `error`.
|
|
156
|
-
- When a record is missing, return early with a page-level
|
|
157
|
-
-
|
|
158
|
-
- Use `TabEmptyState` when a section is empty but otherwise healthy
|
|
159
|
-
- Keep loading flags local to the section
|
|
244
|
+
- When a record is missing, return early with a page-level `ErrorMessage` and a clear recovery action ("Back to list"); do not render `CrudForm`, detail sections, tabs, or record actions.
|
|
245
|
+
- Don't use ad hoc centered `<div>` error markup when shared backend detail primitives can express the state.
|
|
246
|
+
- Use `TabEmptyState` when a section is empty but otherwise healthy.
|
|
247
|
+
- Keep loading flags local to the section; reset errors before each load.
|
|
160
248
|
|
|
161
249
|
## Flash Messages
|
|
162
250
|
|
|
163
251
|
- Use `flash(message, 'success' | 'error')` from `@open-mercato/ui/backend/FlashMessages` for user feedback after CRUD operations.
|
|
164
|
-
- Prefer specific translation keys
|
|
165
|
-
- For non-blocking errors in side effects (
|
|
252
|
+
- Prefer specific translation keys; keep message copy in module locale files.
|
|
253
|
+
- For non-blocking errors in side effects (e.g. creating secondary records), show a flash error and let the main flow complete.
|
|
166
254
|
|
|
167
255
|
## Notifications
|
|
168
256
|
|
|
169
257
|
- Define notification types in `src/modules/<module>/notifications.ts` and client renderers in `notifications.client.ts`.
|
|
170
258
|
- Define reactive notification handlers in `src/modules/<module>/notifications.handlers.ts` when notifications should trigger automatic side-effects.
|
|
171
259
|
- Renderers live in `widgets/notifications/` and should use `useT()` for copy.
|
|
172
|
-
- Use
|
|
173
|
-
- Prefer notification creation in commands or subscribers
|
|
260
|
+
- Use shared action labels where possible (e.g. `notifications.actions.dismiss`).
|
|
261
|
+
- Prefer notification creation in commands or subscribers; keep UI renderers lightweight.
|
|
174
262
|
- For component-scoped reactions, use `useNotificationEffect(notificationType, effect)` instead of module-specific polling loops.
|
|
175
|
-
- When gating notification handlers
|
|
263
|
+
- When gating notification handlers by `features`, MUST use the shared wildcard-aware matcher.
|
|
176
264
|
|
|
177
265
|
## Component Reuse
|
|
178
266
|
|
|
@@ -184,7 +272,7 @@ import { IconButton } from '@open-mercato/ui/primitives/icon-button'
|
|
|
184
272
|
|
|
185
273
|
## Component Replacement (UMES Phase H)
|
|
186
274
|
|
|
187
|
-
- When a host surface is replacement-aware, resolve implementations via `useRegisteredComponent(handle, Fallback)` instead of hardcoded
|
|
275
|
+
- When a host surface is replacement-aware, resolve implementations via `useRegisteredComponent(handle, Fallback)` instead of hardcoded references.
|
|
188
276
|
- Prefer additive override modes (`wrapper`, `props`) before full `replace`; reserve `replace` for cases where compatibility is preserved.
|
|
189
277
|
- Keep handle IDs stable and document them when introducing new replacement-aware surfaces.
|
|
190
278
|
|
|
@@ -204,7 +292,7 @@ The portal extensibility system lets app modules build customer-facing pages tha
|
|
|
204
292
|
|
|
205
293
|
### Portal Shell (`packages/ui/src/portal/PortalShell.tsx`)
|
|
206
294
|
|
|
207
|
-
Shared layout with header, nav (built-in + injected), main
|
|
295
|
+
Shared layout with header, nav (built-in + injected), main, footer. Supports event bridge and component replacement handles.
|
|
208
296
|
|
|
209
297
|
```tsx
|
|
210
298
|
import { PortalShell } from '@open-mercato/ui/portal/PortalShell'
|
|
@@ -224,8 +312,8 @@ function MyPage({ orgSlug }) {
|
|
|
224
312
|
|
|
225
313
|
| Spot ID | Purpose |
|
|
226
314
|
|---------|---------|
|
|
227
|
-
| `menu:portal:sidebar:main` | Main portal navigation
|
|
228
|
-
| `menu:portal:sidebar:account` | Account/settings navigation
|
|
315
|
+
| `menu:portal:sidebar:main` | Main portal navigation |
|
|
316
|
+
| `menu:portal:sidebar:account` | Account/settings navigation |
|
|
229
317
|
| `menu:portal:header:actions` | Header action buttons |
|
|
230
318
|
| `menu:portal:user-dropdown` | User dropdown menu items |
|
|
231
319
|
|
|
@@ -288,10 +376,7 @@ Reference: see `packages/core/src/modules/portal/frontend/[orgSlug]/portal/{dash
|
|
|
288
376
|
|
|
289
377
|
### Declarative Customer Role Features in setup.ts
|
|
290
378
|
|
|
291
|
-
Modules can declare features to be merged into customer role ACLs:
|
|
292
|
-
|
|
293
379
|
```typescript
|
|
294
|
-
// setup.ts
|
|
295
380
|
export const setup: ModuleSetupConfig = {
|
|
296
381
|
defaultCustomerRoleFeatures: {
|
|
297
382
|
buyer: ['portal.orders.view', 'portal.orders.create'],
|
|
@@ -305,12 +390,10 @@ export const setup: ModuleSetupConfig = {
|
|
|
305
390
|
Events with `portalBroadcast: true` are streamed to authenticated portal users via `/api/customer_accounts/portal/events/stream`.
|
|
306
391
|
|
|
307
392
|
```typescript
|
|
308
|
-
// events.ts
|
|
309
393
|
const events = [
|
|
310
394
|
{ id: 'sales.order.status_changed', label: 'Order Status Changed', portalBroadcast: true },
|
|
311
395
|
] as const
|
|
312
396
|
|
|
313
|
-
// In portal component
|
|
314
397
|
import { usePortalAppEvent } from '@open-mercato/ui/portal/hooks/usePortalAppEvent'
|
|
315
398
|
usePortalAppEvent('sales.order.status_changed', (event) => { refetch() })
|
|
316
399
|
```
|
package/dist/backend/AppShell.js
CHANGED
|
@@ -7,6 +7,7 @@ import Image from "next/image";
|
|
|
7
7
|
import { ChevronUp, ChevronDown } from "lucide-react";
|
|
8
8
|
import { Button } from "../primitives/button.js";
|
|
9
9
|
import { IconButton } from "../primitives/icon-button.js";
|
|
10
|
+
import { Checkbox } from "../primitives/checkbox.js";
|
|
10
11
|
import { FlashMessages } from "./FlashMessages.js";
|
|
11
12
|
import { QueryProvider } from "../theme/QueryProvider.js";
|
|
12
13
|
import { usePathname, useSearchParams } from "next/navigation";
|
|
@@ -564,7 +565,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
564
565
|
setSelectedRoleIds((prev) => prev.includes(roleId) ? prev.filter((id) => id !== roleId) : [...prev, roleId]);
|
|
565
566
|
}, []);
|
|
566
567
|
const asideWidth = effectiveCollapsed ? "72px" : expandedSidebarWidth;
|
|
567
|
-
const asideClassesBase = `border-r bg-background/
|
|
568
|
+
const asideClassesBase = `border-r bg-background/80 py-4 min-h-svh`;
|
|
568
569
|
React.useEffect(() => {
|
|
569
570
|
try {
|
|
570
571
|
localStorage.setItem("om:sidebarCollapsed", collapsed ? "1" : "0");
|
|
@@ -703,18 +704,18 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
703
704
|
] }) }) : null,
|
|
704
705
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col gap-3 pr-1", children: [
|
|
705
706
|
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
706
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/
|
|
707
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" }),
|
|
707
708
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2 pl-1", children: [
|
|
708
709
|
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" }),
|
|
709
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/
|
|
710
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/
|
|
710
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" }),
|
|
711
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" })
|
|
711
712
|
] })
|
|
712
713
|
] }),
|
|
713
714
|
/* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
|
|
714
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/
|
|
715
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" }),
|
|
715
716
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2 pl-1", children: [
|
|
716
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/
|
|
717
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/
|
|
717
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" }),
|
|
718
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 rounded bg-muted/50" })
|
|
718
719
|
] })
|
|
719
720
|
] })
|
|
720
721
|
] })
|
|
@@ -778,12 +779,10 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
778
779
|
/* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-muted-foreground", children: placeholder }),
|
|
779
780
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
780
781
|
/* @__PURE__ */ jsx(
|
|
781
|
-
|
|
782
|
+
Checkbox,
|
|
782
783
|
{
|
|
783
|
-
type: "checkbox",
|
|
784
|
-
className: "h-4 w-4 accent-foreground",
|
|
785
784
|
checked: !hidden,
|
|
786
|
-
|
|
785
|
+
onCheckedChange: (next) => setItemHidden(itemKey, next !== true),
|
|
787
786
|
disabled: savingPreferences,
|
|
788
787
|
"aria-label": t("appShell.sidebarCustomizationShowItem"),
|
|
789
788
|
title: t("appShell.sidebarCustomizationShowItem")
|
|
@@ -796,7 +795,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
796
795
|
onChange: (event) => setItemLabel(itemKey, event.target.value),
|
|
797
796
|
placeholder,
|
|
798
797
|
disabled: savingPreferences,
|
|
799
|
-
className: "h-8 flex-1 rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-
|
|
798
|
+
className: "h-8 flex-1 rounded border bg-background px-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
800
799
|
}
|
|
801
800
|
)
|
|
802
801
|
] }),
|
|
@@ -807,7 +806,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
807
806
|
);
|
|
808
807
|
});
|
|
809
808
|
};
|
|
810
|
-
const customizationEditor = customizing ? customDraft ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded border border-dashed bg-muted/
|
|
809
|
+
const customizationEditor = customizing ? customDraft ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3 rounded border border-dashed bg-muted/30 p-3", children: [
|
|
811
810
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
|
|
812
811
|
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: t("appShell.sidebarCustomizationHeading") }),
|
|
813
812
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
@@ -844,7 +843,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
844
843
|
] })
|
|
845
844
|
] }),
|
|
846
845
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("appShell.sidebarCustomizationHint", { locale: localeLabel }) }),
|
|
847
|
-
canApplyToRoles ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded border bg-background/
|
|
846
|
+
canApplyToRoles ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 rounded border bg-background/80 p-3 shadow-sm", children: [
|
|
848
847
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
849
848
|
/* @__PURE__ */ jsx("div", { className: "text-sm font-semibold", children: t("appShell.sidebarApplyToRolesTitle") }),
|
|
850
849
|
/* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("appShell.sidebarApplyToRolesDescription") })
|
|
@@ -852,14 +851,12 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
852
851
|
availableRoleTargets.length > 0 ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: availableRoleTargets.map((role) => {
|
|
853
852
|
const checked = selectedRoleIds.includes(role.id);
|
|
854
853
|
const willClear = role.hasPreference && !checked;
|
|
855
|
-
return /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 rounded border bg-background px-2 py-1 text-sm shadow-sm", children: [
|
|
854
|
+
return /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 rounded-md border bg-background px-2 py-1 text-sm shadow-sm cursor-pointer", children: [
|
|
856
855
|
/* @__PURE__ */ jsx(
|
|
857
|
-
|
|
856
|
+
Checkbox,
|
|
858
857
|
{
|
|
859
|
-
type: "checkbox",
|
|
860
|
-
className: "h-4 w-4 accent-foreground",
|
|
861
858
|
checked,
|
|
862
|
-
|
|
859
|
+
onCheckedChange: () => toggleRoleSelection(role.id),
|
|
863
860
|
disabled: savingPreferences
|
|
864
861
|
}
|
|
865
862
|
),
|
|
@@ -886,7 +883,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
886
883
|
onChange: (event) => setGroupLabel(groupId, event.target.value),
|
|
887
884
|
placeholder,
|
|
888
885
|
disabled: savingPreferences,
|
|
889
|
-
className: "mt-1 h-8 w-full rounded border bg-background px-2 text-sm shadow-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-
|
|
886
|
+
className: "mt-1 h-8 w-full rounded border bg-background px-2 text-sm shadow-sm focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:opacity-50"
|
|
890
887
|
}
|
|
891
888
|
)
|
|
892
889
|
] }),
|
|
@@ -920,7 +917,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
920
917
|
/* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2", children: renderEditableItems(baseGroup.items, currentGroup.items) })
|
|
921
918
|
] }, groupId);
|
|
922
919
|
}) })
|
|
923
|
-
] }) : /* @__PURE__ */ jsx("div", { className: "rounded border border-dashed bg-muted/
|
|
920
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "rounded border border-dashed bg-muted/30 p-3 text-sm text-muted-foreground", children: t("appShell.sidebarCustomizationLoading") }) : null;
|
|
924
921
|
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col min-h-full gap-3", children: [
|
|
925
922
|
!hideHeader && /* @__PURE__ */ jsx("div", { className: `flex items-center ${compact ? "justify-center" : "justify-between"} mb-2`, children: /* @__PURE__ */ jsxs(Link, { href: "/backend", className: "flex items-center gap-2", "aria-label": t("appShell.goToDashboard"), children: [
|
|
926
923
|
/* @__PURE__ */ jsx(Image, { src: logo?.src ?? "/open-mercato.svg", alt: logo?.alt ?? resolvedProductName, width: 32, height: 32, className: "rounded m-4" }),
|
|
@@ -1041,7 +1038,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1041
1038
|
})
|
|
1042
1039
|
] }) });
|
|
1043
1040
|
})() }),
|
|
1044
|
-
/* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 pt-4 border-t bg-background/
|
|
1041
|
+
/* @__PURE__ */ jsxs("div", { className: "sticky bottom-0 pt-4 border-t bg-background/80 backdrop-blur-sm pb-1", children: [
|
|
1045
1042
|
shouldRenderSidebarInjectionSpots ? /* @__PURE__ */ jsx(
|
|
1046
1043
|
InjectionSpot,
|
|
1047
1044
|
{
|
|
@@ -1149,7 +1146,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1149
1146
|
/* @__PURE__ */ jsxs("div", { className: `min-h-svh lg:grid ${gridColsClass}`, children: [
|
|
1150
1147
|
/* @__PURE__ */ jsx("aside", { className: `${asideClassesBase} ${effectiveCollapsed ? "px-2" : "px-3"} hidden lg:block`, style: { width: asideWidth }, children: renderSidebar(effectiveCollapsed) }),
|
|
1151
1148
|
/* @__PURE__ */ jsxs("div", { className: "flex min-h-svh flex-col min-w-0", children: [
|
|
1152
|
-
/* @__PURE__ */ jsxs("header", { className: "border-b bg-background/
|
|
1149
|
+
/* @__PURE__ */ jsxs("header", { className: "border-b bg-background/80 px-3 lg:px-4 py-2 lg:py-3 flex items-center justify-between gap-2", children: [
|
|
1153
1150
|
/* @__PURE__ */ jsx(
|
|
1154
1151
|
"div",
|
|
1155
1152
|
{
|
|
@@ -1217,8 +1214,8 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1217
1214
|
rightHeaderSlot ? rightHeaderSlot : /* @__PURE__ */ jsx("span", { className: "opacity-80", children: email || t("appShell.userFallback") })
|
|
1218
1215
|
] })
|
|
1219
1216
|
] }),
|
|
1220
|
-
/* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-
|
|
1221
|
-
/* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6", children: [
|
|
1217
|
+
/* @__PURE__ */ jsx(ProgressTopBar, { t, className: "sticky top-0 z-sticky" }),
|
|
1218
|
+
/* @__PURE__ */ jsxs("main", { className: "flex-1 p-4 lg:p-6 mx-auto w-full max-w-screen-2xl", children: [
|
|
1222
1219
|
/* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_TOP_INJECTION_SPOT_ID, context: injectionContext }),
|
|
1223
1220
|
/* @__PURE__ */ jsx(FlashMessages, {}),
|
|
1224
1221
|
/* @__PURE__ */ jsx(PartialIndexBanner, {}),
|
|
@@ -1236,7 +1233,7 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1236
1233
|
children,
|
|
1237
1234
|
/* @__PURE__ */ jsx(InjectionSpot, { spotId: BACKEND_LAYOUT_FOOTER_INJECTION_SPOT_ID, context: injectionContext })
|
|
1238
1235
|
] }),
|
|
1239
|
-
/* @__PURE__ */ jsxs("footer", { className: "border-t bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/
|
|
1236
|
+
/* @__PURE__ */ jsxs("footer", { className: "border-t bg-background/80 backdrop-blur supports-[backdrop-filter]:bg-background/80 px-4 py-3 flex flex-wrap items-center justify-end gap-4", children: [
|
|
1240
1237
|
version ? /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: t("appShell.version", { version }) }) : null,
|
|
1241
1238
|
/* @__PURE__ */ jsxs("nav", { className: "flex items-center gap-3 text-xs text-muted-foreground", children: [
|
|
1242
1239
|
/* @__PURE__ */ jsx(Link, { href: "/terms", className: "transition hover:text-foreground", children: t("common.terms") }),
|
|
@@ -1244,8 +1241,8 @@ function AppShellBody({ productName, logo, email, groups, rightHeaderSlot, child
|
|
|
1244
1241
|
] })
|
|
1245
1242
|
] })
|
|
1246
1243
|
] }),
|
|
1247
|
-
mobileOpen && /* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed inset-0 z-
|
|
1248
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/
|
|
1244
|
+
mobileOpen && /* @__PURE__ */ jsxs("div", { className: "lg:hidden fixed inset-0 z-modal", children: [
|
|
1245
|
+
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 bg-black/20", onClick: () => setMobileOpen(false), "aria-hidden": "true" }),
|
|
1249
1246
|
/* @__PURE__ */ jsxs("aside", { className: "absolute left-0 top-0 flex h-full w-[260px] flex-col bg-background border-r overflow-hidden", children: [
|
|
1250
1247
|
/* @__PURE__ */ jsxs("div", { className: "shrink-0 p-3 pb-2 flex items-center justify-between border-b", children: [
|
|
1251
1248
|
/* @__PURE__ */ jsxs(Link, { href: "/backend", className: "flex items-center gap-2 text-sm font-semibold", onClick: () => setMobileOpen(false), "aria-label": t("appShell.goToDashboard"), children: [
|