|
2 | 2 | Layout::Header::SmartNavMenu::Dropdown |
3 | 3 | Phase 2: multi-column card grid with search filter. |
4 | 4 |
|
5 | | - Shortcuts are expanded as independent sibling items in the grid (AWS-style). |
6 | | - The JS getter `expandedItems` interleaves parent MenuItems and their |
7 | | - shortcut objects so each appears as its own flat card. |
| 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 | 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 | | - NOTE: Route-based cards use <LinkToExternal> with no click handler. |
19 | | - The dropdown is closed via the routeDidChange listener in smart-nav-menu.js |
20 | | - which sets isMoreOpen = false on every route transition. |
| 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 | 43 | </button> |
39 | 44 | </div> |
40 | 45 |
|
41 | | - {{! ── Search bar (own row) ────────────────────────────────────────────── }} |
| 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 | 81 | {{else}} |
77 | 82 | {{#each this.filteredItems as |item|}} |
78 | 83 | {{#if item._isShortcut}} |
79 | | - {{! ── Shortcut sibling card (AWS-style flat item) ─────── }} |
80 | | - <div class="snm-dropdown-card" role="presentation"> |
81 | | - <div class="snm-dropdown-card-header"> |
82 | | - <LinkToExternal |
83 | | - @route={{item.route}} |
84 | | - id={{concat (dasherize (or item.route item.id "sc")) "-dropdown-card"}} |
85 | | - class="snm-dropdown-card-link" |
86 | | - title={{item.title}} |
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}} |
91 | 96 | {{else}} |
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 | | - </LinkToExternal> |
103 | | - {{#unless @atPinnedLimit}} |
104 | | - <button |
105 | | - type="button" |
106 | | - class="snm-dropdown-pin-btn" |
107 | | - title="Pin to navigation bar" |
108 | | - aria-label="Pin {{item.title}} to navigation bar" |
109 | | - {{on "click" (fn @onQuickPin item)}} |
110 | | - > |
111 | | - <FaIcon @icon="thumbtack" @size="xs" /> |
112 | | - </button> |
113 | | - {{/unless}} |
114 | | - </div> |
115 | | - {{#if item.description}} |
116 | | - <p class="snm-dropdown-card-description">{{item.description}}</p> |
117 | | - {{else if item._parentTitle}} |
118 | | - <p class="snm-dropdown-card-description snm-dropdown-card-description--from"><em>from {{item._parentTitle}}</em></p> |
119 | | - {{/if}} |
| 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" role="presentation"> |
124 | | - <div class="snm-dropdown-card-header"> |
125 | | - {{#if item.onClick}} |
126 | | - <a |
127 | | - href="javascript:;" |
128 | | - class="snm-dropdown-card-link" |
129 | | - title={{item.title}} |
130 | | - {{on "click" (fn this.handleItemClick item)}} |
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 | 146 | <span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span> |
143 | 147 | {{/if}} |
144 | 148 | </span> |
145 | | - </a> |
146 | | - {{else}} |
147 | | - <LinkToExternal |
148 | | - @route={{item.route}} |
149 | | - id={{concat (dasherize (or item.route item.id "nav")) "-dropdown-card"}} |
150 | | - class="snm-dropdown-card-link" |
151 | | - title={{item.title}} |
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 | 172 | <span class="snm-dropdown-card-parent-label" aria-label="from {{item._parentTitle}}">· {{item._parentTitle}}</span> |
164 | 173 | {{/if}} |
165 | 174 | </span> |
166 | | - </LinkToExternal> |
167 | | - {{/if}} |
168 | | - {{#unless @atPinnedLimit}} |
169 | | - <button |
170 | | - type="button" |
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}} |
|
0 commit comments