@fleetbase/ember-ui 0.3.22 → 0.3.23
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.
|
@@ -2,9 +2,10 @@
|
|
|
2
2
|
Layout::Header::SmartNavMenu::Dropdown
|
|
3
3
|
Phase 2: multi-column card grid with search filter.
|
|
4
4
|
|
|
5
|
-
Shortcuts are
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Shortcuts are already registered as first-class items in the universe
|
|
6
|
+
registry by menu-service.registerHeaderMenuItem(), so @items already
|
|
7
|
+
contains both parent extension items and their shortcut siblings.
|
|
8
|
+
No client-side expansion is needed.
|
|
8
9
|
|
|
9
10
|
Args:
|
|
10
11
|
@items - Array of MenuItem objects to display
|
|
@@ -15,9 +16,13 @@
|
|
|
15
16
|
@onQuickPin - Action to pin an item directly from the dropdown
|
|
16
17
|
@atPinnedLimit - Boolean: true when the bar is full (pin button hidden)
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
Click-area pattern:
|
|
20
|
+
Each card is wrapped in a `snm-dropdown-card-wrap` div (position: relative).
|
|
21
|
+
The card itself is the <LinkToExternal> (or <a> for onClick items), making
|
|
22
|
+
the entire card surface clickable. The pin button is a sibling of the card
|
|
23
|
+
inside the wrapper, absolutely positioned in the top-right corner so it
|
|
24
|
+
sits above the card link and receives its own clicks without violating the
|
|
25
|
+
no-nested-interactive rule.
|
|
21
26
|
}}
|
|
22
27
|
<div
|
|
23
28
|
class="snm-dropdown snm-dropdown--wide"
|
|
@@ -38,7 +43,7 @@
|
|
|
38
43
|
</button>
|
|
39
44
|
</div>
|
|
40
45
|
|
|
41
|
-
{{! ── Search bar
|
|
46
|
+
{{! ── Search bar ──────────────────────────────────────────────────────── }}
|
|
42
47
|
<div class="snm-dropdown-search-bar">
|
|
43
48
|
<span class="snm-dropdown-search-icon" aria-hidden="true">
|
|
44
49
|
<FaIcon @icon="magnifying-glass" @size="xs" />
|
|
@@ -76,15 +81,15 @@
|
|
|
76
81
|
{{else}}
|
|
77
82
|
{{#each this.filteredItems as |item|}}
|
|
78
83
|
{{#if item._isShortcut}}
|
|
79
|
-
{{! ── Shortcut
|
|
80
|
-
<div class="snm-dropdown-card"
|
|
81
|
-
<
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
>
|
|
84
|
+
{{! ── Shortcut card ───────────────────────────────────── }}
|
|
85
|
+
<div class="snm-dropdown-card-wrap">
|
|
86
|
+
<LinkToExternal
|
|
87
|
+
@route={{item.route}}
|
|
88
|
+
id={{concat (dasherize (or item.route item.id "sc")) "-dropdown-card"}}
|
|
89
|
+
class="snm-dropdown-card"
|
|
90
|
+
title={{item.title}}
|
|
91
|
+
>
|
|
92
|
+
<div class="snm-dropdown-card-header">
|
|
88
93
|
<span class="snm-dropdown-card-icon">
|
|
89
94
|
{{#if item.iconComponent}}
|
|
90
95
|
{{component (lazy-engine-component item.iconComponent) options=item.iconComponentOptions}}
|
|
@@ -92,43 +97,42 @@
|
|
|
92
97
|
<FaIcon @icon={{or item.icon "circle-dot"}} @prefix={{item.iconPrefix}} @size="sm" />
|
|
93
98
|
{{/if}}
|
|
94
99
|
</span>
|
|
95
|
-
{{! Title row: shortcut name + always-visible muted parent attribution }}
|
|
96
100
|
<span class="snm-dropdown-card-title-group">
|
|
97
101
|
<span class="snm-dropdown-card-title">{{item.title}}</span>
|
|
98
102
|
{{#if item._parentTitle}}
|
|
99
103
|
<span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span>
|
|
100
104
|
{{/if}}
|
|
101
105
|
</span>
|
|
102
|
-
</
|
|
103
|
-
{{#
|
|
104
|
-
<
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
{{/
|
|
106
|
+
</div>
|
|
107
|
+
{{#if item.description}}
|
|
108
|
+
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
109
|
+
{{else if item._parentTitle}}
|
|
110
|
+
<p class="snm-dropdown-card-description snm-dropdown-card-description--from"><em>from {{item._parentTitle}}</em></p>
|
|
111
|
+
{{/if}}
|
|
112
|
+
</LinkToExternal>
|
|
113
|
+
{{#unless @atPinnedLimit}}
|
|
114
|
+
<button
|
|
115
|
+
type="button"
|
|
116
|
+
class="snm-dropdown-pin-btn"
|
|
117
|
+
title="Pin to navigation bar"
|
|
118
|
+
aria-label="Pin {{item.title}} to navigation bar"
|
|
119
|
+
{{on "click" (fn @onQuickPin item)}}
|
|
120
|
+
>
|
|
121
|
+
<FaIcon @icon="thumbtack" @size="xs" />
|
|
122
|
+
</button>
|
|
123
|
+
{{/unless}}
|
|
120
124
|
</div>
|
|
121
125
|
{{else}}
|
|
122
126
|
{{! ── Primary extension card ──────────────────────────── }}
|
|
123
|
-
<div class="snm-dropdown-card"
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
>
|
|
127
|
+
<div class="snm-dropdown-card-wrap">
|
|
128
|
+
{{#if item.onClick}}
|
|
129
|
+
<a
|
|
130
|
+
href="javascript:;"
|
|
131
|
+
class="snm-dropdown-card"
|
|
132
|
+
title={{item.title}}
|
|
133
|
+
{{on "click" (fn this.handleItemClick item)}}
|
|
134
|
+
>
|
|
135
|
+
<div class="snm-dropdown-card-header">
|
|
132
136
|
<span class="snm-dropdown-card-icon">
|
|
133
137
|
{{#if item.iconComponent}}
|
|
134
138
|
{{component (lazy-engine-component item.iconComponent) options=item.iconComponentOptions}}
|
|
@@ -142,14 +146,19 @@
|
|
|
142
146
|
<span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span>
|
|
143
147
|
{{/if}}
|
|
144
148
|
</span>
|
|
145
|
-
</
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
149
|
+
</div>
|
|
150
|
+
{{#if item.description}}
|
|
151
|
+
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
152
|
+
{{/if}}
|
|
153
|
+
</a>
|
|
154
|
+
{{else}}
|
|
155
|
+
<LinkToExternal
|
|
156
|
+
@route={{item.route}}
|
|
157
|
+
id={{concat (dasherize (or item.route item.id "nav")) "-dropdown-card"}}
|
|
158
|
+
class="snm-dropdown-card"
|
|
159
|
+
title={{item.title}}
|
|
160
|
+
>
|
|
161
|
+
<div class="snm-dropdown-card-header">
|
|
153
162
|
<span class="snm-dropdown-card-icon">
|
|
154
163
|
{{#if item.iconComponent}}
|
|
155
164
|
{{component (lazy-engine-component item.iconComponent) options=item.iconComponentOptions}}
|
|
@@ -163,23 +172,23 @@
|
|
|
163
172
|
<span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span>
|
|
164
173
|
{{/if}}
|
|
165
174
|
</span>
|
|
166
|
-
</
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
class="snm-dropdown-pin-btn"
|
|
172
|
-
title="Pin to navigation bar"
|
|
173
|
-
aria-label="Pin {{item.title}} to navigation bar"
|
|
174
|
-
{{on "click" (fn @onQuickPin item)}}
|
|
175
|
-
>
|
|
176
|
-
<FaIcon @icon="thumbtack" @size="xs" />
|
|
177
|
-
</button>
|
|
178
|
-
{{/unless}}
|
|
179
|
-
</div>
|
|
180
|
-
{{#if item.description}}
|
|
181
|
-
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
175
|
+
</div>
|
|
176
|
+
{{#if item.description}}
|
|
177
|
+
<p class="snm-dropdown-card-description">{{item.description}}</p>
|
|
178
|
+
{{/if}}
|
|
179
|
+
</LinkToExternal>
|
|
182
180
|
{{/if}}
|
|
181
|
+
{{#unless @atPinnedLimit}}
|
|
182
|
+
<button
|
|
183
|
+
type="button"
|
|
184
|
+
class="snm-dropdown-pin-btn"
|
|
185
|
+
title="Pin to navigation bar"
|
|
186
|
+
aria-label="Pin {{item.title}} to navigation bar"
|
|
187
|
+
{{on "click" (fn @onQuickPin item)}}
|
|
188
|
+
>
|
|
189
|
+
<FaIcon @icon="thumbtack" @size="xs" />
|
|
190
|
+
</button>
|
|
191
|
+
{{/unless}}
|
|
183
192
|
</div>
|
|
184
193
|
{{/if}}
|
|
185
194
|
{{/each}}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import Component from '@glimmer/component';
|
|
2
2
|
import { tracked } from '@glimmer/tracking';
|
|
3
3
|
import { action } from '@ember/object';
|
|
4
|
-
import { dasherize } from '@ember/string';
|
|
5
4
|
import { isArray } from '@ember/array';
|
|
6
5
|
import { htmlSafe } from '@ember/template';
|
|
7
6
|
|
|
@@ -25,62 +24,16 @@ export default class LayoutHeaderSmartNavMenuDropdownComponent extends Component
|
|
|
25
24
|
}
|
|
26
25
|
|
|
27
26
|
/**
|
|
28
|
-
*
|
|
29
|
-
* The resulting array interleaves parent items and their shortcuts in
|
|
30
|
-
* registration order, matching the AWS Console pattern.
|
|
27
|
+
* Returns the items array as-is for filtering.
|
|
31
28
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
29
|
+
* Shortcuts are already registered as first-class items in the universe
|
|
30
|
+
* registry (with `_isShortcut: true` and `_parentTitle` set) by
|
|
31
|
+
* `menu-service.registerHeaderMenuItem()` at boot time. There is no need
|
|
32
|
+
* to expand `item.shortcuts` here — doing so would produce a duplicate card
|
|
33
|
+
* for every shortcut (one from the registry, one from the expansion).
|
|
34
34
|
*/
|
|
35
35
|
get expandedItems() {
|
|
36
|
-
|
|
37
|
-
const result = [];
|
|
38
|
-
for (const item of items) {
|
|
39
|
-
result.push(item);
|
|
40
|
-
if (isArray(item.shortcuts)) {
|
|
41
|
-
for (const sc of item.shortcuts) {
|
|
42
|
-
const scId = sc.id ?? dasherize(item.id + '-sc-' + sc.title);
|
|
43
|
-
result.push({
|
|
44
|
-
// ── Identity ────────────────────────────────────────
|
|
45
|
-
id: scId,
|
|
46
|
-
slug: sc.slug ?? scId,
|
|
47
|
-
title: sc.title,
|
|
48
|
-
text: sc.text ?? sc.title,
|
|
49
|
-
label: sc.label ?? sc.title,
|
|
50
|
-
|
|
51
|
-
// ── Routing ──────────────────────────────────────────
|
|
52
|
-
route: sc.route ?? item.route,
|
|
53
|
-
queryParams: sc.queryParams ?? {},
|
|
54
|
-
routeParams: sc.routeParams ?? [],
|
|
55
|
-
|
|
56
|
-
// ── Icons (full surface) ─────────────────────────────
|
|
57
|
-
icon: sc.icon ?? item.icon ?? 'arrow-right',
|
|
58
|
-
iconPrefix: sc.iconPrefix ?? item.iconPrefix ?? null,
|
|
59
|
-
iconSize: sc.iconSize ?? null,
|
|
60
|
-
iconClass: sc.iconClass ?? null,
|
|
61
|
-
iconComponent: sc.iconComponent ?? null,
|
|
62
|
-
iconComponentOptions: sc.iconComponentOptions ?? {},
|
|
63
|
-
|
|
64
|
-
// ── Metadata ─────────────────────────────────────────
|
|
65
|
-
description: sc.description ?? null,
|
|
66
|
-
tags: isArray(sc.tags) ? sc.tags : isArray(item.tags) ? item.tags : null,
|
|
67
|
-
|
|
68
|
-
// ── Behaviour ────────────────────────────────────────
|
|
69
|
-
onClick: sc.onClick ?? null,
|
|
70
|
-
disabled: sc.disabled ?? false,
|
|
71
|
-
|
|
72
|
-
// ── Styling ───────────────────────────────────────────
|
|
73
|
-
class: sc.class ?? null,
|
|
74
|
-
|
|
75
|
-
// ── Internal flags ────────────────────────────────────
|
|
76
|
-
_isShortcut: true,
|
|
77
|
-
_parentTitle: item.title,
|
|
78
|
-
_parentId: item.id,
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return result;
|
|
36
|
+
return this.args.items ?? [];
|
|
84
37
|
}
|
|
85
38
|
|
|
86
39
|
get filteredItems() {
|
|
@@ -169,13 +169,22 @@
|
|
|
169
169
|
|
|
170
170
|
/* ── Extension card ─────────────────────────────────────────────────────────── */
|
|
171
171
|
|
|
172
|
+
/* Wrapper that holds the card link + absolutely-positioned pin button */
|
|
173
|
+
.snm-dropdown-card-wrap {
|
|
174
|
+
position: relative;
|
|
175
|
+
}
|
|
176
|
+
|
|
172
177
|
.snm-dropdown-card {
|
|
173
178
|
@apply flex flex-col rounded-lg;
|
|
174
179
|
padding: 7px 10px;
|
|
175
180
|
background-color: #111827; /* gray-900 */
|
|
176
181
|
border: 1px solid #1f2937; /* gray-800 – darker but lighter than bg */
|
|
177
182
|
transition: border-color 0.15s ease, background-color 0.15s ease;
|
|
178
|
-
|
|
183
|
+
text-decoration: none;
|
|
184
|
+
color: inherit;
|
|
185
|
+
display: flex;
|
|
186
|
+
width: 100%;
|
|
187
|
+
box-sizing: border-box;
|
|
179
188
|
}
|
|
180
189
|
|
|
181
190
|
.snm-dropdown-card:hover {
|
|
@@ -183,36 +192,29 @@
|
|
|
183
192
|
background-color: #1a2332; /* slightly lighter than gray-900 */
|
|
184
193
|
}
|
|
185
194
|
|
|
186
|
-
/* Card header row: icon + title
|
|
195
|
+
/* Card header row: icon + title + pin button */
|
|
187
196
|
.snm-dropdown-card-header {
|
|
188
197
|
@apply flex items-center;
|
|
189
198
|
gap: 0;
|
|
190
199
|
margin-bottom: 0;
|
|
191
200
|
}
|
|
192
201
|
|
|
193
|
-
/*
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
border: none;
|
|
201
|
-
background: none;
|
|
202
|
-
padding: 0;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
.snm-dropdown-card-link:hover {
|
|
206
|
-
color: #f9fafb; /* gray-50 */
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
.snm-dropdown-card-link-wrapper {
|
|
210
|
-
@apply flex flex-1;
|
|
211
|
-
min-width: 0;
|
|
202
|
+
/* Pin button is a sibling of the card link, absolutely positioned top-right
|
|
203
|
+
so it floats above the card and receives its own clicks */
|
|
204
|
+
.snm-dropdown-pin-btn {
|
|
205
|
+
position: absolute;
|
|
206
|
+
top: 6px;
|
|
207
|
+
right: 6px;
|
|
208
|
+
z-index: 1;
|
|
212
209
|
}
|
|
213
210
|
|
|
214
|
-
|
|
215
|
-
|
|
211
|
+
/* Card-as-link: the card itself is the <LinkToExternal> or <a> */
|
|
212
|
+
a.snm-dropdown-card,
|
|
213
|
+
.snm-dropdown-card[href] {
|
|
214
|
+
display: flex;
|
|
215
|
+
text-decoration: none;
|
|
216
|
+
color: inherit;
|
|
217
|
+
cursor: pointer;
|
|
216
218
|
}
|
|
217
219
|
|
|
218
220
|
.snm-dropdown-card-icon {
|
|
@@ -250,12 +252,13 @@
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
/* Pin button on card – always visible at low opacity, brightens on hover */
|
|
253
|
-
.snm-dropdown-card .snm-dropdown-pin-btn {
|
|
254
|
-
opacity: 0
|
|
255
|
+
.snm-dropdown-card-wrap .snm-dropdown-pin-btn {
|
|
256
|
+
opacity: 0;
|
|
255
257
|
pointer-events: auto;
|
|
258
|
+
transition: opacity 0.15s ease;
|
|
256
259
|
}
|
|
257
260
|
|
|
258
|
-
.snm-dropdown-card:hover .snm-dropdown-pin-btn {
|
|
261
|
+
.snm-dropdown-card-wrap:hover .snm-dropdown-pin-btn {
|
|
259
262
|
opacity: 1;
|
|
260
263
|
}
|
|
261
264
|
|
|
@@ -787,11 +790,13 @@ body[data-theme='light'] .snm-dropdown-card:hover {
|
|
|
787
790
|
background-color: #f3f4f6; /* gray-100 */
|
|
788
791
|
}
|
|
789
792
|
|
|
790
|
-
body[data-theme='light'] .snm-dropdown-card
|
|
793
|
+
body[data-theme='light'] a.snm-dropdown-card,
|
|
794
|
+
body[data-theme='light'] .snm-dropdown-card[href] {
|
|
791
795
|
color: #374151; /* gray-700 */
|
|
792
796
|
}
|
|
793
797
|
|
|
794
|
-
body[data-theme='light'] .snm-dropdown-card
|
|
798
|
+
body[data-theme='light'] a.snm-dropdown-card:hover,
|
|
799
|
+
body[data-theme='light'] .snm-dropdown-card[href]:hover {
|
|
795
800
|
color: #111827; /* gray-900 */
|
|
796
801
|
}
|
|
797
802
|
|
|
@@ -799,7 +804,8 @@ body[data-theme='light'] .snm-dropdown-card-icon {
|
|
|
799
804
|
color: #6b7280; /* gray-500 */
|
|
800
805
|
}
|
|
801
806
|
|
|
802
|
-
body[data-theme='light'] .snm-dropdown-card
|
|
807
|
+
body[data-theme='light'] a.snm-dropdown-card:hover .snm-dropdown-card-icon,
|
|
808
|
+
body[data-theme='light'] .snm-dropdown-card[href]:hover .snm-dropdown-card-icon {
|
|
803
809
|
color: #2563eb; /* blue-600 */
|
|
804
810
|
}
|
|
805
811
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fleetbase/ember-ui",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.23",
|
|
4
4
|
"description": "Fleetbase UI provides all the interface components, helpers, services and utilities for building a Fleetbase extension into the Console.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fleetbase-ui",
|