@memberjunction/ng-ui-components 5.46.0 → 5.48.0
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/README.md +276 -0
- package/package.json +3 -3
- package/dist/lib/page-header/page-header.scss +0 -207
package/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# @memberjunction/ng-ui-components
|
|
2
|
+
|
|
3
|
+
Reusable, **standalone** Angular UI components for MemberJunction applications — the house replacement for Kendo UI. Every component is token-driven (`--mj-*` design tokens from `_tokens.scss`), dark-mode safe by construction, and keyboard/ARIA accessible.
|
|
4
|
+
|
|
5
|
+
**The rule for consuming apps: check here before building.** If a control, layout region, overlay, or pattern exists in this package, use it. Hand-rolling an app-prefixed equivalent (a bespoke button, chip, empty state, dialog…) creates divergence this package exists to eliminate. If what you need doesn't exist, propose it — generic components belong here; app-domain components belong in the app's shared package.
|
|
6
|
+
|
|
7
|
+
## Installation & usage
|
|
8
|
+
|
|
9
|
+
The package ships standalone components — no NgModule required (the one exception is `MJAccordionModule`, a convenience bundle). Import the symbols you use directly:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { MJButtonDirective, MJEmptyStateComponent, MJDialogComponent } from '@memberjunction/ng-ui-components';
|
|
13
|
+
|
|
14
|
+
@Component({
|
|
15
|
+
standalone: true,
|
|
16
|
+
imports: [MJButtonDirective, MJEmptyStateComponent, MJDialogComponent],
|
|
17
|
+
...
|
|
18
|
+
})
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Dependencies: `@angular/cdk` (overlays) and `angular-split` (splitter) come along as package dependencies; `@angular/common`, `@angular/core`, and `rxjs` are peers.
|
|
22
|
+
|
|
23
|
+
## Styling model — read this once
|
|
24
|
+
|
|
25
|
+
Components attach styles one of three ways, and one of them needs host cooperation:
|
|
26
|
+
|
|
27
|
+
1. **Inline `styles` in the component** (most components) — nothing to do.
|
|
28
|
+
2. **`styleUrls` compiled into the component** (`page-header`, `slide-panel`) — nothing to do.
|
|
29
|
+
3. **Global stylesheets shipped as assets** — the form controls and overlay chrome (`button`, `dialog`, `dropdown`, `combobox`, `input`, `datepicker`, `splitter`, `accordion`, `window`, `chip`, `switch`/`progress`) ship their `.scss` to `dist` and expect the **host application** to import them globally.
|
|
30
|
+
|
|
31
|
+
**If your app runs inside MJ Explorer (`explorer-app`), category 3 is already handled** — the shell imports all of them. A standalone host must add these to its global stylesheet:
|
|
32
|
+
|
|
33
|
+
```scss
|
|
34
|
+
@import '@memberjunction/ng-ui-components/dist/lib/button/button';
|
|
35
|
+
@import '@memberjunction/ng-ui-components/dist/lib/dialog/dialog';
|
|
36
|
+
@import '@memberjunction/ng-ui-components/dist/lib/dropdown/dropdown';
|
|
37
|
+
@import '@memberjunction/ng-ui-components/dist/lib/combobox/combobox';
|
|
38
|
+
@import '@memberjunction/ng-ui-components/dist/lib/input/input';
|
|
39
|
+
@import '@memberjunction/ng-ui-components/dist/lib/input/input-switch-progress';
|
|
40
|
+
@import '@memberjunction/ng-ui-components/dist/lib/input/chip';
|
|
41
|
+
@import '@memberjunction/ng-ui-components/dist/lib/datepicker/datepicker';
|
|
42
|
+
@import '@memberjunction/ng-ui-components/dist/lib/splitter/splitter';
|
|
43
|
+
@import '@memberjunction/ng-ui-components/dist/lib/accordion/accordion';
|
|
44
|
+
@import '@memberjunction/ng-ui-components/dist/lib/window/window';
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
All colors resolve through `--mj-*` semantic tokens, so light/dark theming requires zero component-level work.
|
|
48
|
+
|
|
49
|
+
## Forms integration
|
|
50
|
+
|
|
51
|
+
`mj-combobox`, `mj-dropdown`, `mj-datepicker`, `mj-numeric-input`, and `mj-switch` all implement `ControlValueAccessor` — they work with `[(ngModel)]` and reactive forms, including `setDisabledState`.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
# Component catalog
|
|
56
|
+
|
|
57
|
+
## Controls
|
|
58
|
+
|
|
59
|
+
### `button[mjButton]` / `a[mjButton]` — MJButtonDirective
|
|
60
|
+
Attribute directive that styles a **native** button or anchor. Variants: `primary | secondary (default) | outline | flat | danger | icon | success | warning`. Sizes: `sm | md (default) | lg`. Toggle mode via `[toggleable]` + `[(selected)]` (sets `aria-pressed`). Dev-mode warns when an `icon` button lacks an accessible name — pass `ariaLabel`.
|
|
61
|
+
|
|
62
|
+
```html
|
|
63
|
+
<button mjButton variant="primary" (click)="save()">Save</button>
|
|
64
|
+
<button mjButton variant="icon" ariaLabel="Remove" (click)="remove()"><i class="fa-solid fa-xmark"></i></button>
|
|
65
|
+
<button mjButton variant="flat" [toggleable]="true" [(selected)]="isActive">Toggle</button>
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `mj-combobox` — MJComboboxComponent
|
|
69
|
+
Editable text-input combobox (CDK overlay): type-to-filter, keyboard nav, optional custom values (`[AllowCustom]`). Inputs: `Data`, `TextField`, `ValueField`, `ValuePrimitive`, `Filterable` (default true), `Placeholder`, `Disabled`. Outputs: `ValueChange`, `FilterChange`. Custom item template via `<ng-template #mjComboboxItem>`.
|
|
70
|
+
|
|
71
|
+
```html
|
|
72
|
+
<mj-combobox [Data]="categories" TextField="text" ValueField="value"
|
|
73
|
+
[(ngModel)]="selected" [ValuePrimitive]="true" [AllowCustom]="true" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `mj-dropdown` — MJDropdownComponent
|
|
77
|
+
Non-editable select (CDK overlay) with optional in-panel filter (`[Filterable]`, default false) and `DefaultItem` clear option. Same `Data`/`TextField`/`ValueField`/`ValuePrimitive` contract as combobox. Custom item template via `<ng-template #mjDropdownItem>`.
|
|
78
|
+
|
|
79
|
+
```html
|
|
80
|
+
<mj-dropdown [Data]="items" TextField="name" ValueField="id"
|
|
81
|
+
[(ngModel)]="selectedId" [ValuePrimitive]="true" />
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `mj-datepicker` — MJDatepickerComponent
|
|
85
|
+
Text input + calendar popup with `Min`/`Max` range disabling and typed-input parsing. Accepts `Date | string | null` through forms; emits `ValueChange: Date | null`. (Display format is currently fixed at `MM/dd/yyyy`.)
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<mj-datepicker [(ngModel)]="dueDate" [Min]="minDate" [Max]="maxDate" Placeholder="Select a date" />
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### `mj-numeric-input` — MJNumericInputComponent
|
|
92
|
+
Native number input that clamps to `Min`/`Max`, rounds to `Decimals`, and formats on blur. Value flows through forms only (no separate output).
|
|
93
|
+
|
|
94
|
+
```html
|
|
95
|
+
<mj-numeric-input [(ngModel)]="quantity" [Min]="0" [Max]="100" [Decimals]="2" />
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### `mj-switch` — MJSwitchComponent
|
|
99
|
+
Boolean toggle with `role="switch"`, optional `OnLabel`/`OffLabel`.
|
|
100
|
+
|
|
101
|
+
```html
|
|
102
|
+
<mj-switch [(ngModel)]="isEnabled" OnLabel="On" OffLabel="Off" />
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### `[mjClickable]` — MJClickableDirective
|
|
106
|
+
Retrofits a non-semantic element (card, tile, row) into an accessible control: sets `role`, `tabindex`, `aria-label`, and makes Enter/Space fire `(click)`. Pass the accessible name as the attribute value; `role` may be `button` (default) or `link`; `testId` emits `data-testid`.
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<div class="app-card" [mjClickable]="app.Name" (click)="open(app)">…</div>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Calendar utilities
|
|
113
|
+
`calendar/calendar-utils` exports pure helpers shared with the datepicker: `BuildCalendarWeeks`, `FormatDate`, `FormatDateTime`, `GetMonthYearLabel`, `WEEK_DAYS`, `MONTH_NAMES`.
|
|
114
|
+
|
|
115
|
+
## Layout & page chrome
|
|
116
|
+
|
|
117
|
+
The `page-*` family composes into the standard page skeleton:
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<mj-page-layout>
|
|
121
|
+
<mj-page-header Title="Agents" Icon="fa-solid fa-robot" Subtitle="Manage your agents">
|
|
122
|
+
<span meta><mj-stat-badge [Count]="filtered" [Total]="total" Label="agents" /></span>
|
|
123
|
+
<div actions><button mjButton variant="primary">New</button></div>
|
|
124
|
+
<div toolbar><mj-page-search [Value]="q" (ValueChange)="q = $event" /></div>
|
|
125
|
+
</mj-page-header>
|
|
126
|
+
<mj-page-body>
|
|
127
|
+
<!-- content; scrolling and the 24px gutter are handled for you -->
|
|
128
|
+
</mj-page-body>
|
|
129
|
+
</mj-page-layout>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
And the left-nav shell variant for multi-section pages:
|
|
133
|
+
|
|
134
|
+
```html
|
|
135
|
+
<mj-page-layout>
|
|
136
|
+
<mj-page-header Title="Admin" Icon="fa-solid fa-gear" />
|
|
137
|
+
<mj-page-body [Flex]="true" [Padding]="false" Direction="row">
|
|
138
|
+
<mj-left-nav [Sections]="sections" [ActiveId]="activeId" (ItemClicked)="go($event)" />
|
|
139
|
+
<mj-left-nav-content [Loading]="loading" [Error]="err">
|
|
140
|
+
<mj-page-header-interior Subtitle="Runtime state">
|
|
141
|
+
<div actions><button mjButton size="sm">+ Add</button></div>
|
|
142
|
+
</mj-page-header-interior>
|
|
143
|
+
<mj-page-body-interior><!-- sub-page content --></mj-page-body-interior>
|
|
144
|
+
</mj-left-nav-content>
|
|
145
|
+
</mj-page-body>
|
|
146
|
+
</mj-page-layout>
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### `mj-page-layout` — outer shell
|
|
150
|
+
Flex-column, full-height, `overflow:hidden`, page background. No inputs.
|
|
151
|
+
|
|
152
|
+
### `mj-page-header` — page chrome band
|
|
153
|
+
Inputs: `Title`, `Icon`, `Subtitle`. Slots: `[meta]` (badges under the title), `[actions]` (right-aligned), `[toolbar]` (secondary row).
|
|
154
|
+
|
|
155
|
+
### `mj-page-body` — scrolling body region
|
|
156
|
+
Inputs: `Padding` (default true; false removes the gutter), `Flex` (default false), `Direction: 'row' | 'column'` (row stacks to column at ≤700px).
|
|
157
|
+
|
|
158
|
+
### `mj-page-header-interior` / `mj-page-body-interior`
|
|
159
|
+
The same pair for **sub-pages inside a left-nav shell** — card-like chrome, responsive padding. Header-interior adds `Role`/`AriaLabel` inputs and the same three slots.
|
|
160
|
+
|
|
161
|
+
### `mj-left-nav` / `mj-left-nav-content` — MJLeftNavComponent
|
|
162
|
+
Canonical left rail: sections, flat + tree items (badges, descriptions, disabled), off-canvas drawer at ≤700px. Inputs: `Sections: MJLeftNavSection[]`, `ActiveId`, `ExpandedIds`, `Width` (240), `MobileTitle`. Outputs: `ItemClicked`, `ItemToggled`. Slots: `[header]`, `[footer]`. Pair with `mj-left-nav-content`, which provides `Loading`/`Error` states and hides (not destroys) content while busy.
|
|
163
|
+
|
|
164
|
+
### `mj-tab-nav` — MJTabNavComponent
|
|
165
|
+
Data-driven tab strip. `Tabs: TabConfig[]` (`key`, `label`, `icon?`, `badge?`, `badgeVariant?: 'default'|'error'|'warning'|'success'`), `ActiveKey`, `(TabChange)` emits the key.
|
|
166
|
+
|
|
167
|
+
### `mj-page-search` — MJPageSearchComponent
|
|
168
|
+
Compact toolbar search input. `Placeholder`, `Value`, `Icon`; `(ValueChange)` on input.
|
|
169
|
+
|
|
170
|
+
### `mj-slide-panel` — MjSlidePanelComponent
|
|
171
|
+
Chrome-only slide-in panel from the right (resizable) or centered dialog (`Mode: 'slide' | 'dialog'`). Inputs include `Title`, `Visible`, `Resizable`, `MinWidthPx`, `MaxWidthRatio`, `WidthPx`, and a `CanClose` guard callback. Outputs: `Closed`, `WidthChanged`. Dialog-mode body projects into `[dialog-content]`.
|
|
172
|
+
|
|
173
|
+
### Splitter
|
|
174
|
+
Re-export of `angular-split` — use `<as-split>` / `<as-split-area>` directly:
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<as-split direction="horizontal">
|
|
178
|
+
<as-split-area [size]="30">Left</as-split-area>
|
|
179
|
+
<as-split-area [size]="70">Right</as-split-area>
|
|
180
|
+
</as-split>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### `mj-accordion-panel` — MJAccordionPanelComponent (+ directives)
|
|
184
|
+
Collapsible panel replacing Kendo panelbar/expansion panel. Inputs: `Title`, `Expanded` (+`ExpandedChange`), `Disabled`, `Variant: 'default'|'primary'|'secondary'`, `Size: 'sm'|'md'`, `Bare`, `FlushBody`, `Fill` (consume leftover height). Rich content via structural directives — `*mjAccordionTitle`, `*mjAccordionActions` (rendered outside the toggle button), and `*mjAccordionBody` (**lazy — instantiated on first expand, then kept alive**). Import `MJAccordionModule` to get the panel plus all three directives in one symbol.
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<mj-accordion-panel [Expanded]="open" (ExpandedChange)="open = $event">
|
|
188
|
+
<ng-template mjAccordionTitle><i class="fa-solid fa-code"></i> Template Editor</ng-template>
|
|
189
|
+
<ng-template mjAccordionBody><mj-code-editor /></ng-template>
|
|
190
|
+
</mj-accordion-panel>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Overlays
|
|
194
|
+
|
|
195
|
+
### `mj-dialog` — MJDialogComponent
|
|
196
|
+
Modal with backdrop, Esc/backdrop close (`Closeable`), body scroll lock. Inputs: `Visible`, `Title`, `Size: 'sm'|'md'|'lg'|'xl'|'auto'` (400/600/800/1000px), `Width`/`Height`/`MinWidth`, `Role: 'dialog'|'alertdialog'`. Output: `Close`. **`Visible` is not two-way** — set it false in your `(Close)` handler. Slots: default body, `mj-dialog-titlebar`, `mj-dialog-actions`.
|
|
197
|
+
|
|
198
|
+
```html
|
|
199
|
+
<mj-dialog [Visible]="show" Title="Confirm" Size="md" (Close)="show = false">
|
|
200
|
+
<p>Are you sure?</p>
|
|
201
|
+
<mj-dialog-actions>
|
|
202
|
+
<button mjButton variant="primary" (click)="confirm()">Yes</button>
|
|
203
|
+
<button mjButton (click)="show = false">No</button>
|
|
204
|
+
</mj-dialog-actions>
|
|
205
|
+
</mj-dialog>
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### `MJDialogService` — programmatic dialogs
|
|
209
|
+
`Open(settings): MJDialogRef` with `{ title, content: string | Type, actions: [{ text, primary?, themeColor? }], width, … }`. `ref.Result` (Observable) emits the clicked action or `undefined` on dismiss; `ref.Content.instance` exposes a mounted content component; `ref.Close(result?)`.
|
|
210
|
+
|
|
211
|
+
### `mj-confirm-dialog` — MJConfirmDialogComponent
|
|
212
|
+
Purpose-built confirmation on top of `mj-dialog`. Inputs: `[(Visible)]` (two-way), `Type: 'default'|'danger'|'warning'|'info'`, `Title`, `Message`, `DetailMessage`, `ConfirmText`, `CancelText`, `Icon`, `Processing` (spinner + blocks dismissal for async work). Outputs: `Confirmed` (**stays open — you close it when the work finishes**), `Cancelled` (auto-closes). Confirm button is leftmost, per MJ dialog convention.
|
|
213
|
+
|
|
214
|
+
### `MJConfirmService` — promise-based confirms
|
|
215
|
+
`await Confirm({ message, title?, detail?, type?, … })` → `boolean`, and `ConfirmDelete(...)` which forces danger styling and "Delete". Mounts above `mj-window` (z-index 20000).
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
private confirm = inject(MJConfirmService);
|
|
219
|
+
if (await this.confirm.ConfirmDelete({ message: 'Delete this view?', detail: 'Cannot be undone.' })) { … }
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### `mj-window` — MJWindowComponent
|
|
223
|
+
Floating, **non-modal** panel (no backdrop): `Draggable`, `Resizable` (8 handles), `State: 'default' | 'maximized'` with geometry save/restore, `Top`/`Left`/`Width`/`Height`/`MinWidth`/`MinHeight`. Outputs: `Close`, `StateChange`, `Resize`. Methods: `SetPosition`, `SetSize`, `GetWindowElement`. Slots: default body, `mj-window-titlebar`, `mj-window-actions`. `Visible` is not two-way.
|
|
224
|
+
|
|
225
|
+
### `mj-filter-popover` — MJFilterPopoverComponent
|
|
226
|
+
Filter trigger button + CDK popover on desktop, docked bottom-sheet on mobile. Inputs: `Label`, `Icon`, `ActiveCount` (badge), `ShowClearAll`. Output: `ClearAllRequested`. Project your filter UI (dropdowns, chips) as content.
|
|
227
|
+
|
|
228
|
+
## Patterns
|
|
229
|
+
|
|
230
|
+
### `mj-empty-state` — MJEmptyStateComponent
|
|
231
|
+
Centered placeholder for empty/error/no-results states. Inputs: `Variant: 'empty'|'no-results'|'success'|'warning'|'error'` (drives default icon + live-region role), `Size: 'compact'|'default'|'large'`, `Icon`, `Title`, `Message`, built-in CTA via `ActionText`/`ActionIcon`/`ActionVariant` with `(Action)`. Extra CTAs project into `[actions]`; richer body projects as default content.
|
|
232
|
+
|
|
233
|
+
```html
|
|
234
|
+
<mj-empty-state Variant="error" Title="Couldn't load permissions" [Message]="error"
|
|
235
|
+
ActionText="Try Again" ActionIcon="fa-solid fa-rotate-right" (Action)="loadData()" />
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### `mj-stat-badge` — MJStatBadgeComponent
|
|
239
|
+
Read-only stat pill, designed for the page-header `[meta]` slot. `Count`, `Total` (renders "X of Y label"), `Label`, `Icon`, `Variant: 'default'|'success'|'error'|'warning'|'running'|'info'`.
|
|
240
|
+
|
|
241
|
+
```html
|
|
242
|
+
<mj-stat-badge [Count]="filteredCount" [Total]="totalCount" Label="agents" />
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
### `mj-alert` — MJAlertComponent
|
|
246
|
+
Persistent in-flow banner (not a toast). `Variant: 'info'|'success'|'warning'|'error'`, `Size: 'sm'|'md'`, `Title`, `Message`, `Dismissible` + `(Dismissed)`, action buttons via `[actions]` slot. Correct ARIA live role per variant automatically.
|
|
247
|
+
|
|
248
|
+
### `mj-progress-bar` — MJProgressBarComponent
|
|
249
|
+
`Value` (0–100, clamped) or `Type="infinite"` for indeterminate. Styles ship in the global `input-switch-progress` sheet (see Styling model).
|
|
250
|
+
|
|
251
|
+
### `mj-view-toggle` — MJViewToggleComponent
|
|
252
|
+
Segmented control for view modes. `Options: { key, icon?, label?, title? }[]`, `ActiveKey`, `(KeyChange)`. Provide `title` for icon-only options.
|
|
253
|
+
|
|
254
|
+
### `mj-refresh-button` — MJRefreshButtonComponent
|
|
255
|
+
Canonical refresh: `Loading` spins + disables, label auto-hides on mobile. `(Clicked)` suppressed while loading/disabled.
|
|
256
|
+
|
|
257
|
+
### `mj-filter-chip` — MJFilterChipComponent
|
|
258
|
+
Clickable filter pill: `Label`, `Icon`, `Count`, `Active` (+`aria-pressed`), `(Clicked)`.
|
|
259
|
+
|
|
260
|
+
### `mj-filter-field` — MJFilterFieldComponent
|
|
261
|
+
Labeled row (icon + uppercase label) wrapping any projected widget — the escape hatch inside filter UIs.
|
|
262
|
+
|
|
263
|
+
### `mj-filter-panel` — MJFilterPanelComponent
|
|
264
|
+
Config-driven filter stack: `Fields: FilterFieldConfig[]` with `type: 'text' | 'dropdown' | 'chips'` (chips support `multi`), `[(Values)]` as a plain record, `Reset` handling, plus projected custom `mj-filter-field`s.
|
|
265
|
+
|
|
266
|
+
### `mj-applied-filters` — MJAppliedFiltersComponent
|
|
267
|
+
"Filtered by …" removable chip row. Renders nothing when `Filters` is empty, so it's safe to place unconditionally. `(Remove)`, `(ClearAll)`.
|
|
268
|
+
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
## Contributing
|
|
272
|
+
|
|
273
|
+
- **Generic components belong here; app-domain components don't.** If a component would make sense in any MJ app, add it to this package. If it encodes one app's domain (e.g. scoring bands), keep it in that app's shared package.
|
|
274
|
+
- Components must be `standalone: true`, use `--mj-*` tokens for every color (no hardcoded hexes), support both themes, and be keyboard/ARIA accessible.
|
|
275
|
+
- If a component ships a global `.scss`, register it in the `copy-assets` script in `package.json` **and** add it to the import list in this README and in `explorer-app`'s global styles.
|
|
276
|
+
- Update the catalog above when adding or changing a component's public API.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@memberjunction/ng-ui-components",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.48.0",
|
|
4
4
|
"description": "MemberJunction: Angular UI Components - reusable standalone components replacing Kendo UI dependencies.",
|
|
5
5
|
"main": "./dist/public-api.js",
|
|
6
6
|
"typings": "./dist/public-api.d.ts",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"test": "vitest run",
|
|
12
12
|
"build": "npm run clean && ngc && npm run copy-assets",
|
|
13
|
-
"copy-assets": "cpy src/lib/button/button.scss dist/lib/button --flat && cpy src/lib/dialog/dialog.scss dist/lib/dialog --flat && cpy src/lib/dropdown/dropdown.scss dist/lib/dropdown --flat && cpy src/lib/combobox/combobox.scss dist/lib/combobox --flat && cpy src/lib/input/input.scss dist/lib/input --flat && cpy src/lib/input/input-switch-progress.scss dist/lib/input --flat && cpy src/lib/datepicker/datepicker.scss dist/lib/datepicker --flat && cpy src/lib/splitter/splitter.scss dist/lib/splitter --flat && cpy src/lib/accordion/accordion.scss dist/lib/accordion --flat && cpy src/lib/window/window.scss dist/lib/window --flat && cpy src/lib/input/chip.scss dist/lib/input --flat
|
|
13
|
+
"copy-assets": "cpy src/lib/button/button.scss dist/lib/button --flat && cpy src/lib/dialog/dialog.scss dist/lib/dialog --flat && cpy src/lib/dropdown/dropdown.scss dist/lib/dropdown --flat && cpy src/lib/combobox/combobox.scss dist/lib/combobox --flat && cpy src/lib/input/input.scss dist/lib/input --flat && cpy src/lib/input/input-switch-progress.scss dist/lib/input --flat && cpy src/lib/datepicker/datepicker.scss dist/lib/datepicker --flat && cpy src/lib/splitter/splitter.scss dist/lib/splitter --flat && cpy src/lib/accordion/accordion.scss dist/lib/accordion --flat && cpy src/lib/window/window.scss dist/lib/window --flat && cpy src/lib/input/chip.scss dist/lib/input --flat",
|
|
14
14
|
"clean": "rimraf dist",
|
|
15
15
|
"test:watch": "vitest"
|
|
16
16
|
},
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@angular/compiler": "21.1.3",
|
|
25
25
|
"@angular/compiler-cli": "21.1.3",
|
|
26
|
-
"@memberjunction/ng-test-utils": "5.
|
|
26
|
+
"@memberjunction/ng-test-utils": "5.48.0",
|
|
27
27
|
"vitest": "^4.0.18"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
@@ -1,207 +0,0 @@
|
|
|
1
|
-
:host {
|
|
2
|
-
display: block;
|
|
3
|
-
/* External consumers using <mj-page-header> outside <mj-page-layout> get a
|
|
4
|
-
24px bottom margin so the header has breathing room from whatever follows.
|
|
5
|
-
This preserves backward compatibility with the published v5.x contract. */
|
|
6
|
-
margin-bottom: var(--mj-space-6);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
/* When nested inside <mj-page-layout>, the spacing between header and body is
|
|
10
|
-
owned by <mj-page-body>'s padding-top — so the header sits flush against the
|
|
11
|
-
body. This lets pages using <mj-page-body [Padding]="false"> (e.g. AI
|
|
12
|
-
Analytics flush sidebar) render truly flush against the header. */
|
|
13
|
-
:host-context(mj-page-layout) {
|
|
14
|
-
margin-bottom: 0;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
/* Slot-wrapper passthrough — any element a consumer projects with the `meta`,
|
|
18
|
-
`actions`, or `toolbar` attribute (regardless of class name) becomes a
|
|
19
|
-
transparent container so its children become direct flex children of the
|
|
20
|
-
page-header row and inherit the row's gap. Eliminates the per-page
|
|
21
|
-
`.{section}-header-meta { display: contents }` rules that pages had to
|
|
22
|
-
declare in their own CSS files. */
|
|
23
|
-
:host ::ng-deep [meta],
|
|
24
|
-
:host ::ng-deep [actions],
|
|
25
|
-
:host ::ng-deep [toolbar] {
|
|
26
|
-
display: contents;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
.mj-page-header {
|
|
30
|
-
display: flex;
|
|
31
|
-
flex-direction: column;
|
|
32
|
-
background: var(--mj-bg-surface);
|
|
33
|
-
/* Drop shadow for visual separation from the body below.
|
|
34
|
-
`position: relative; z-index: 1` lifts the header into its own stacking
|
|
35
|
-
context above <mj-page-body> (which has `position: relative` and would
|
|
36
|
-
otherwise paint over the downward shadow as a later flex sibling).
|
|
37
|
-
--mj-shadow-md is used instead of --mj-shadow-sm because the latter is
|
|
38
|
-
rgba(0,0,0,0.05) in light mode — too faint to read as separation. */
|
|
39
|
-
box-shadow: var(--mj-shadow-md);
|
|
40
|
-
position: relative;
|
|
41
|
-
z-index: 1;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
.mj-page-header-row {
|
|
45
|
-
display: flex;
|
|
46
|
-
align-items: center;
|
|
47
|
-
gap: var(--mj-space-3);
|
|
48
|
-
padding: 0 var(--mj-space-6);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/* Primary row centers the actions cluster vertically against the identity
|
|
52
|
-
column. The identity column stacks title → subtitle → (optional meta);
|
|
53
|
-
centering keeps the actions visually balanced against the whole block
|
|
54
|
-
instead of pinning them to the title baseline. When meta badges are
|
|
55
|
-
present the actions drift down a touch — acceptable trade for the more
|
|
56
|
-
common no-meta case where flex-start looks top-heavy. */
|
|
57
|
-
.mj-page-header-row--primary {
|
|
58
|
-
justify-content: space-between;
|
|
59
|
-
align-items: center;
|
|
60
|
-
gap: var(--mj-space-4);
|
|
61
|
-
padding-top: var(--mj-space-4);
|
|
62
|
-
padding-bottom: var(--mj-space-4);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
.mj-page-header-row--toolbar {
|
|
66
|
-
flex-wrap: wrap;
|
|
67
|
-
/* Control bar (search · Filter · view) is right-aligned on desktop; the
|
|
68
|
-
mobile block below resets it to flex-start. */
|
|
69
|
-
justify-content: flex-end;
|
|
70
|
-
padding-top: 0;
|
|
71
|
-
padding-bottom: var(--mj-space-4);
|
|
72
|
-
border-top: 1px solid var(--mj-border-subtle);
|
|
73
|
-
margin-top: 0;
|
|
74
|
-
padding-top: var(--mj-space-3);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
.mj-page-header-row--toolbar:empty {
|
|
78
|
-
display: none;
|
|
79
|
-
border-top: 0;
|
|
80
|
-
padding: 0;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
.mj-page-header-titlebar {
|
|
84
|
-
display: flex;
|
|
85
|
-
align-items: center;
|
|
86
|
-
gap: var(--mj-space-3);
|
|
87
|
-
flex: 1 1 auto;
|
|
88
|
-
min-width: 0;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.mj-page-header-icon {
|
|
92
|
-
flex-shrink: 0;
|
|
93
|
-
width: 40px;
|
|
94
|
-
height: 40px;
|
|
95
|
-
display: flex;
|
|
96
|
-
align-items: center;
|
|
97
|
-
justify-content: center;
|
|
98
|
-
background: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent);
|
|
99
|
-
color: var(--mj-brand-primary);
|
|
100
|
-
border-radius: var(--mj-radius-md);
|
|
101
|
-
font-size: var(--mj-text-lg);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/* Identity column — title, subtitle, meta stacked vertically. */
|
|
105
|
-
.mj-page-header-text {
|
|
106
|
-
min-width: 0;
|
|
107
|
-
flex: 1 1 auto;
|
|
108
|
-
display: flex;
|
|
109
|
-
flex-direction: column;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
.mj-page-header-title {
|
|
113
|
-
margin: 0;
|
|
114
|
-
font-size: var(--mj-text-xl);
|
|
115
|
-
font-weight: var(--mj-font-semibold);
|
|
116
|
-
color: var(--mj-text-primary);
|
|
117
|
-
line-height: 1.25;
|
|
118
|
-
white-space: nowrap;
|
|
119
|
-
overflow: hidden;
|
|
120
|
-
text-overflow: ellipsis;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
.mj-page-header-subtitle {
|
|
124
|
-
margin: var(--mj-space-1) 0 0;
|
|
125
|
-
font-size: var(--mj-text-sm);
|
|
126
|
-
color: var(--mj-text-secondary);
|
|
127
|
-
line-height: 1.4;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/* Meta lives in the identity column UNDER the subtitle — supporting info
|
|
131
|
-
for the identity, not a peer to the title. Wraps when there are many
|
|
132
|
-
badges. Matches the mj-page-header-interior pattern so both chrome
|
|
133
|
-
surfaces have one mental model. */
|
|
134
|
-
.mj-page-header-meta {
|
|
135
|
-
display: flex;
|
|
136
|
-
flex-wrap: wrap;
|
|
137
|
-
align-items: center;
|
|
138
|
-
gap: var(--mj-space-2);
|
|
139
|
-
margin-top: var(--mj-space-2);
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
.mj-page-header-actions {
|
|
143
|
-
display: flex;
|
|
144
|
-
align-items: center;
|
|
145
|
-
gap: var(--mj-space-3);
|
|
146
|
-
flex-shrink: 0;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.mj-page-header-meta:empty,
|
|
150
|
-
.mj-page-header-actions:empty {
|
|
151
|
-
display: none;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/* ── Mobile compaction (≤768px) ───────────────────────────────────────────
|
|
155
|
-
On a phone the header must not eat the viewport. We tighten padding, drop
|
|
156
|
-
the title a size, hide the decorative icon + subtitle (identity is carried
|
|
157
|
-
by the title alone), and let the actions cluster wrap tightly under the
|
|
158
|
-
title. The meta badge and toolbar stay — they're functional. */
|
|
159
|
-
@media (max-width: 768px) {
|
|
160
|
-
.mj-page-header-row {
|
|
161
|
-
padding-left: var(--mj-space-4);
|
|
162
|
-
padding-right: var(--mj-space-4);
|
|
163
|
-
gap: var(--mj-space-2);
|
|
164
|
-
}
|
|
165
|
-
.mj-page-header-row--primary {
|
|
166
|
-
/* Stays a single row on mobile: title (truncates) + the primary CTA in
|
|
167
|
-
[actions]. Filters/search/view now live in the [toolbar] control bar, so
|
|
168
|
-
the primary row no longer needs to stack. */
|
|
169
|
-
gap: var(--mj-space-2);
|
|
170
|
-
padding-top: var(--mj-space-3);
|
|
171
|
-
padding-bottom: var(--mj-space-3);
|
|
172
|
-
}
|
|
173
|
-
/* Icon + subtitle are desktop niceties; reclaim their footprint on mobile. */
|
|
174
|
-
.mj-page-header-icon,
|
|
175
|
-
.mj-page-header-subtitle {
|
|
176
|
-
display: none;
|
|
177
|
-
}
|
|
178
|
-
.mj-page-header-title {
|
|
179
|
-
font-size: var(--mj-text-lg);
|
|
180
|
-
line-height: 1.2;
|
|
181
|
-
}
|
|
182
|
-
.mj-page-header-meta {
|
|
183
|
-
margin-top: var(--mj-space-1);
|
|
184
|
-
}
|
|
185
|
-
.mj-page-header-actions {
|
|
186
|
-
flex-wrap: wrap;
|
|
187
|
-
justify-content: flex-start;
|
|
188
|
-
gap: var(--mj-space-2);
|
|
189
|
-
}
|
|
190
|
-
/* Action buttons go icon-only on mobile — consumers wrap the text in
|
|
191
|
-
<span class="action-btn-label"> so it can hide without touching mjButton. */
|
|
192
|
-
.mj-page-header-actions ::ng-deep .action-btn-label {
|
|
193
|
-
display: none;
|
|
194
|
-
}
|
|
195
|
-
.mj-page-header-row--toolbar {
|
|
196
|
-
/* Left-aligned on mobile (search grows to fill the row). */
|
|
197
|
-
justify-content: flex-start;
|
|
198
|
-
padding-top: var(--mj-space-2);
|
|
199
|
-
padding-bottom: var(--mj-space-3);
|
|
200
|
-
}
|
|
201
|
-
/* In the control bar (search · Filter · view), let search grow so the Filter
|
|
202
|
-
button + view-toggle sit at the trailing edge on one line. */
|
|
203
|
-
.mj-page-header-row--toolbar ::ng-deep mj-page-search {
|
|
204
|
-
flex: 1;
|
|
205
|
-
min-width: 0;
|
|
206
|
-
}
|
|
207
|
-
}
|