@aquera/nile-elements 2.0.9 → 2.0.11

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.
Files changed (142) hide show
  1. package/README.md +11 -0
  2. package/demo/app-shell.html +852 -0
  3. package/demo/nxtgen-classes.css +5 -5
  4. package/demo/nxtgen-dark.css +25 -25
  5. package/demo/nxtgen-properties.css +2 -2
  6. package/demo/nxtgen-utilities.css +5 -5
  7. package/demo/nxtgen.css +5 -5
  8. package/demo/utilities.css +2 -2
  9. package/demo/variables.css +2 -2
  10. package/dist/index.cjs.js +1 -1
  11. package/dist/index.esm.js +1 -1
  12. package/dist/index.js +594 -308
  13. package/dist/internal/app-shell-utility-control-styles.cjs.js +2 -0
  14. package/dist/internal/app-shell-utility-control-styles.cjs.js.map +1 -0
  15. package/dist/internal/app-shell-utility-control-styles.esm.js +16 -0
  16. package/dist/nile-app-shell/index.cjs.js +1 -1
  17. package/dist/nile-app-shell/index.esm.js +1 -1
  18. package/dist/nile-app-shell/nile-app-shell.cjs.js +1 -1
  19. package/dist/nile-app-shell/nile-app-shell.cjs.js.map +1 -1
  20. package/dist/nile-app-shell/nile-app-shell.css.cjs.js +1 -1
  21. package/dist/nile-app-shell/nile-app-shell.css.cjs.js.map +1 -1
  22. package/dist/nile-app-shell/nile-app-shell.css.esm.js +270 -92
  23. package/dist/nile-app-shell/nile-app-shell.esm.js +61 -10
  24. package/dist/nile-app-shell/shell-controller.cjs.js.map +1 -1
  25. package/dist/nile-app-shell-main/nile-app-shell-main.cjs.js.map +1 -1
  26. package/dist/nile-app-shell-nav-toggle/index.cjs.js +1 -1
  27. package/dist/nile-app-shell-nav-toggle/index.esm.js +1 -1
  28. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js +1 -1
  29. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js.map +1 -1
  30. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js +1 -1
  31. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js.map +1 -1
  32. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.esm.js +7 -3
  33. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.esm.js +3 -3
  34. package/dist/nile-app-shell-panel/index.cjs.js +1 -1
  35. package/dist/nile-app-shell-panel/index.esm.js +1 -1
  36. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js +1 -1
  37. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js.map +1 -1
  38. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js +1 -1
  39. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js.map +1 -1
  40. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.esm.js +29 -7
  41. package/dist/nile-app-shell-panel/nile-app-shell-panel.esm.js +18 -5
  42. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.cjs.js.map +1 -1
  43. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.css.cjs.js.map +1 -1
  44. package/dist/nile-badge/nile-badge.css.cjs.js +1 -1
  45. package/dist/nile-badge/nile-badge.css.cjs.js.map +1 -1
  46. package/dist/nile-badge/nile-badge.css.esm.js +2 -2
  47. package/dist/nile-button-toggle/nile-button-toggle.css.cjs.js +1 -1
  48. package/dist/nile-button-toggle/nile-button-toggle.css.cjs.js.map +1 -1
  49. package/dist/nile-button-toggle/nile-button-toggle.css.esm.js +2 -2
  50. package/dist/nile-code-editor/nile-code-editor.cjs.js +1 -1
  51. package/dist/nile-code-editor/nile-code-editor.cjs.js.map +1 -1
  52. package/dist/nile-code-editor/nile-code-editor.esm.js +1 -1
  53. package/dist/nile-menu-item/nile-menu-item.css.cjs.js +1 -1
  54. package/dist/nile-menu-item/nile-menu-item.css.cjs.js.map +1 -1
  55. package/dist/nile-menu-item/nile-menu-item.css.esm.js +1 -1
  56. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js +1 -1
  57. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.cjs.js.map +1 -1
  58. package/dist/nile-rich-text-editor/nile-rich-text-editor.css.esm.js +5 -2
  59. package/dist/nile-toast/nile-toast.css.cjs.js +1 -1
  60. package/dist/nile-toast/nile-toast.css.cjs.js.map +1 -1
  61. package/dist/nile-toast/nile-toast.css.esm.js +5 -5
  62. package/dist/src/internal/app-shell-utility-control-styles.d.ts +12 -0
  63. package/dist/src/internal/app-shell-utility-control-styles.js +29 -0
  64. package/dist/src/internal/app-shell-utility-control-styles.js.map +1 -0
  65. package/dist/src/nile-app-shell/nile-app-shell-regions.test.d.ts +3 -0
  66. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js +206 -0
  67. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js.map +1 -1
  68. package/dist/src/nile-app-shell/nile-app-shell.css.d.ts +1 -9
  69. package/dist/src/nile-app-shell/nile-app-shell.css.js +270 -99
  70. package/dist/src/nile-app-shell/nile-app-shell.css.js.map +1 -1
  71. package/dist/src/nile-app-shell/nile-app-shell.d.ts +90 -122
  72. package/dist/src/nile-app-shell/nile-app-shell.js +224 -143
  73. package/dist/src/nile-app-shell/nile-app-shell.js.map +1 -1
  74. package/dist/src/nile-app-shell/nile-app-shell.test.d.ts +3 -0
  75. package/dist/src/nile-app-shell/nile-app-shell.test.js +898 -29
  76. package/dist/src/nile-app-shell/nile-app-shell.test.js.map +1 -1
  77. package/dist/src/nile-app-shell/shell-controller.d.ts +1 -8
  78. package/dist/src/nile-app-shell/shell-controller.js +2 -14
  79. package/dist/src/nile-app-shell/shell-controller.js.map +1 -1
  80. package/dist/src/nile-app-shell-main/nile-app-shell-main.d.ts +2 -6
  81. package/dist/src/nile-app-shell-main/nile-app-shell-main.js +2 -6
  82. package/dist/src/nile-app-shell-main/nile-app-shell-main.js.map +1 -1
  83. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.d.ts +1 -4
  84. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js +7 -5
  85. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js.map +1 -1
  86. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.d.ts +4 -8
  87. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js +5 -6
  88. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js.map +1 -1
  89. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js +28 -5
  90. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js.map +1 -1
  91. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.d.ts +18 -13
  92. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js +45 -16
  93. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js.map +1 -1
  94. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.d.ts +1 -4
  95. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js +1 -4
  96. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js.map +1 -1
  97. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.d.ts +4 -12
  98. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js +4 -12
  99. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js.map +1 -1
  100. package/dist/src/nile-badge/nile-badge.css.js +2 -2
  101. package/dist/src/nile-badge/nile-badge.css.js.map +1 -1
  102. package/dist/src/nile-button-toggle/nile-button-toggle.css.js +2 -2
  103. package/dist/src/nile-button-toggle/nile-button-toggle.css.js.map +1 -1
  104. package/dist/src/nile-code-editor/nile-code-editor.d.ts +1 -1
  105. package/dist/src/nile-code-editor/nile-code-editor.js +2 -2
  106. package/dist/src/nile-code-editor/nile-code-editor.js.map +1 -1
  107. package/dist/src/nile-menu-item/nile-menu-item.css.js +1 -1
  108. package/dist/src/nile-menu-item/nile-menu-item.css.js.map +1 -1
  109. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js +5 -2
  110. package/dist/src/nile-rich-text-editor/nile-rich-text-editor.css.js.map +1 -1
  111. package/dist/src/nile-toast/nile-toast.css.js +5 -5
  112. package/dist/src/nile-toast/nile-toast.css.js.map +1 -1
  113. package/dist/src/version.js +1 -1
  114. package/dist/src/version.js.map +1 -1
  115. package/dist/tsconfig.tsbuildinfo +1 -1
  116. package/dist/version.cjs.js +1 -1
  117. package/dist/version.cjs.js.map +1 -1
  118. package/dist/version.esm.js +1 -1
  119. package/package.json +1 -1
  120. package/plop-templates/lit/lit.test.ts.hbs +22 -0
  121. package/plop-templates/lit/lit.ts.hbs +10 -5
  122. package/plopfile.js +12 -0
  123. package/src/internal/app-shell-utility-control-styles.ts +30 -0
  124. package/src/nile-app-shell/nile-app-shell-regions.test.ts +248 -0
  125. package/src/nile-app-shell/nile-app-shell.css.ts +270 -99
  126. package/src/nile-app-shell/nile-app-shell.test.ts +1120 -29
  127. package/src/nile-app-shell/nile-app-shell.ts +241 -148
  128. package/src/nile-app-shell/shell-controller.ts +2 -14
  129. package/src/nile-app-shell-main/nile-app-shell-main.ts +2 -6
  130. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.ts +7 -5
  131. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.ts +7 -11
  132. package/src/nile-app-shell-panel/nile-app-shell-panel.css.ts +28 -5
  133. package/src/nile-app-shell-panel/nile-app-shell-panel.ts +44 -16
  134. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.ts +1 -4
  135. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.ts +4 -12
  136. package/src/nile-badge/nile-badge.css.ts +2 -2
  137. package/src/nile-button-toggle/nile-button-toggle.css.ts +2 -2
  138. package/src/nile-code-editor/nile-code-editor.ts +2 -2
  139. package/src/nile-menu-item/nile-menu-item.css.ts +1 -1
  140. package/src/nile-rich-text-editor/nile-rich-text-editor.css.ts +5 -2
  141. package/src/nile-toast/nile-toast.css.ts +5 -5
  142. package/vscode-html-custom-data.json +114 -37
@@ -0,0 +1,852 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en-GB">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <title>App shell demo</title>
6
+ <link rel="stylesheet" href="" id="stylesheet" />
7
+ <script>
8
+ // nxtgen is the theme the design system ships; it is the only sheet that
9
+ // defines the --ng-* tokens this page's own CSS is written against.
10
+ // ?style=variables.css still loads the legacy --nile-* sheet.
11
+ const params = new URLSearchParams(window.location.search);
12
+ const style = params.get('style') || 'nxtgen.css';
13
+ document.getElementById('stylesheet').href = style;
14
+ </script>
15
+
16
+ <!-- <link rel="stylesheet" href="variables.css" id="stylesheet" /> -->
17
+ <link rel="stylesheet" href="index.css" />
18
+
19
+ <style>
20
+ /* index.css centres a small demo in a flex body; the shell IS the page. */
21
+ body {
22
+ display: block;
23
+ height: 100%;
24
+ margin: 0;
25
+ }
26
+
27
+ html,
28
+ body {
29
+ height: 100%;
30
+ }
31
+
32
+ /*
33
+ * The view swap below drives `hidden`, and an explicit `display` in a
34
+ * class or inline style outranks the UA's `[hidden] { display: none }`.
35
+ */
36
+ [hidden] {
37
+ display: none !important;
38
+ }
39
+
40
+ /*
41
+ * A control slotted into the panel's `actions` lives in this page's tree,
42
+ * not the panel's shadow tree, so the panel cannot reach its `base` part.
43
+ * This is the recipe that matches it to the panel's own 32px controls.
44
+ */
45
+ nile-app-shell-panel [slot='actions']::part(base) {
46
+ width: 32px;
47
+ height: 32px;
48
+ padding: 0;
49
+ background: transparent;
50
+ border-color: transparent;
51
+ }
52
+
53
+ nile-app-shell-panel [slot='actions']:hover::part(base) {
54
+ background: var(--ng-colors-bg-primary-hover);
55
+ }
56
+
57
+ .demo-nav {
58
+ display: flex;
59
+ flex-direction: column;
60
+ gap: 4px;
61
+ padding: 8px 12px;
62
+ }
63
+
64
+ .demo-nav a {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 10px;
68
+ height: 40px;
69
+ padding-inline: 10px;
70
+ border-radius: 8px;
71
+ color: var(--ng-colors-text-tertiary-600);
72
+ text-decoration: none;
73
+ font-size: 14px;
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ }
77
+
78
+ .demo-nav a[aria-current='page'] {
79
+ background: var(--ng-colors-bg-primary);
80
+ color: var(--ng-colors-text-primary-900);
81
+ font-weight: 600;
82
+ }
83
+
84
+ /*
85
+ * The rail, from page CSS with no subscription: the side-nav mirrors the
86
+ * shell's effective rail onto its own `collapsed` attribute.
87
+ */
88
+ nile-app-shell-side-nav[collapsed] .demo-label,
89
+ nile-app-shell-side-nav[collapsed] .demo-brand-name {
90
+ display: none;
91
+ }
92
+
93
+ nile-app-shell-side-nav[collapsed] .demo-nav a {
94
+ justify-content: center;
95
+ padding-inline: 0;
96
+ }
97
+
98
+ .demo-brand {
99
+ display: flex;
100
+ align-items: center;
101
+ gap: 10px;
102
+ height: 40px;
103
+ padding: 0 10px;
104
+ margin: 8px 12px 0;
105
+ font-weight: 600;
106
+ font-size: 15px;
107
+ white-space: nowrap;
108
+ overflow: hidden;
109
+ }
110
+
111
+ nile-app-shell-side-nav[collapsed] .demo-brand {
112
+ justify-content: center;
113
+ padding: 0;
114
+ }
115
+
116
+ .demo-thread {
117
+ display: flex;
118
+ flex-direction: column;
119
+ gap: 12px;
120
+ font-size: 14px;
121
+ color: var(--ng-colors-text-tertiary-600);
122
+ }
123
+
124
+ .demo-chat-row {
125
+ display: flex;
126
+ flex-direction: column;
127
+ gap: 2px;
128
+ padding: 10px 12px;
129
+ border: 1px solid var(--ng-colors-border-secondary);
130
+ border-radius: 10px;
131
+ background: none;
132
+ text-align: start;
133
+ cursor: pointer;
134
+ font: inherit;
135
+ }
136
+
137
+ .demo-chat-row:hover {
138
+ background: var(--ng-colors-bg-primary-hover);
139
+ }
140
+
141
+ .demo-chat-row b {
142
+ font-size: 14px;
143
+ color: var(--ng-colors-text-primary-900);
144
+ }
145
+
146
+ .demo-chat-row span {
147
+ font-size: 12px;
148
+ color: var(--ng-colors-text-tertiary-600);
149
+ }
150
+
151
+ .demo-group-label {
152
+ font-size: 12px;
153
+ font-weight: 600;
154
+ letter-spacing: 0.04em;
155
+ text-transform: uppercase;
156
+ color: var(--ng-colors-text-tertiary-600);
157
+ }
158
+
159
+ /* ----------------------------------------------- header variant gallery
160
+ *
161
+ * The AC-7 comparison surface: the header's three Figma axes crossed out
162
+ * in full, twelve cards that differ ONLY by those axes, grouped under
163
+ * the height each pair of axes is meant to produce. Everything here is
164
+ * prefixed vg- so it cannot collide with the reference demo above.
165
+ */
166
+ .vg {
167
+ box-sizing: border-box;
168
+ padding: 24px 24px 96px;
169
+ font-family: var(--ng-font-family-body);
170
+ color: var(--ng-colors-text-primary-900);
171
+ }
172
+
173
+ .vg-intro {
174
+ max-width: 78ch;
175
+ margin: 0 0 24px;
176
+ font-size: 14px;
177
+ line-height: 20px;
178
+ color: var(--ng-colors-text-tertiary-600);
179
+ }
180
+
181
+ .vg-intro h1 {
182
+ margin: 0 0 8px;
183
+ font-size: 20px;
184
+ line-height: 28px;
185
+ color: var(--ng-colors-text-primary-900);
186
+ }
187
+
188
+ .vg-intro code {
189
+ font-size: 13px;
190
+ color: var(--ng-colors-text-primary-900);
191
+ }
192
+
193
+ .vg-section {
194
+ margin: 0 0 32px;
195
+ }
196
+
197
+ .vg-section-head {
198
+ display: flex;
199
+ align-items: baseline;
200
+ gap: 12px;
201
+ margin: 0 0 12px;
202
+ padding-bottom: 8px;
203
+ border-bottom: 1px solid var(--ng-colors-border-secondary);
204
+ }
205
+
206
+ .vg-section-head h2 {
207
+ margin: 0;
208
+ font-size: 15px;
209
+ line-height: 22px;
210
+ }
211
+
212
+ .vg-section-head span {
213
+ font-size: 13px;
214
+ color: var(--ng-colors-text-tertiary-600);
215
+ }
216
+
217
+ .vg-card {
218
+ margin: 0 0 16px;
219
+ }
220
+
221
+ .vg-caption {
222
+ display: flex;
223
+ align-items: center;
224
+ flex-wrap: wrap;
225
+ gap: 8px;
226
+ margin: 0 0 6px;
227
+ font-size: 13px;
228
+ line-height: 18px;
229
+ }
230
+
231
+ .vg-id {
232
+ font-weight: 600;
233
+ font-variant-numeric: tabular-nums;
234
+ }
235
+
236
+ .vg-axes {
237
+ color: var(--ng-colors-text-tertiary-600);
238
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
239
+ font-size: 12px;
240
+ }
241
+
242
+ /* A measurement, not decoration: green only when the card's own header
243
+ block measures the height its axes are specified to produce. */
244
+ .vg-chip {
245
+ display: inline-flex;
246
+ align-items: center;
247
+ gap: 6px;
248
+ padding: 1px 8px;
249
+ border: 1px solid var(--ng-colors-border-secondary);
250
+ border-radius: 999px;
251
+ font-size: 12px;
252
+ font-variant-numeric: tabular-nums;
253
+ white-space: nowrap;
254
+ }
255
+
256
+ .vg-chip[data-state='ok'] {
257
+ border-color: #75b389;
258
+ background: #eef7f1;
259
+ color: #1f5c38;
260
+ }
261
+
262
+ .vg-chip[data-state='off'] {
263
+ border-color: #d08a8a;
264
+ background: #fbeeee;
265
+ color: #7d2020;
266
+ }
267
+
268
+ .vg-chip[data-state='note'] {
269
+ border-color: #d8b268;
270
+ background: #fdf6e7;
271
+ color: #6b4c12;
272
+ }
273
+
274
+ /* The card frame gives each shell the height it needs to lay out: the
275
+ shell sizes to its parent unless it is full-height. */
276
+ .vg-frame {
277
+ height: 220px;
278
+ border: 1px solid var(--ng-colors-border-secondary);
279
+ border-radius: 12px;
280
+ overflow: hidden;
281
+ background: var(--ng-colors-bg-primary);
282
+ }
283
+
284
+ .vg-nav {
285
+ display: flex;
286
+ flex-direction: column;
287
+ gap: 4px;
288
+ padding: 12px;
289
+ font-size: 14px;
290
+ }
291
+
292
+ .vg-nav span {
293
+ display: flex;
294
+ align-items: center;
295
+ height: 36px;
296
+ padding-inline: 10px;
297
+ border-radius: 8px;
298
+ color: var(--ng-colors-text-tertiary-600);
299
+ white-space: nowrap;
300
+ overflow: hidden;
301
+ }
302
+
303
+ .vg-nav span:first-child {
304
+ background: var(--ng-colors-bg-primary);
305
+ color: var(--ng-colors-text-primary-900);
306
+ font-weight: 600;
307
+ }
308
+
309
+ .vg-body {
310
+ font-size: 13px;
311
+ line-height: 18px;
312
+ color: var(--ng-colors-text-tertiary-600);
313
+ }
314
+
315
+ /* Bottom-right so it never sits over a header being compared. */
316
+ .vg-switch {
317
+ position: fixed;
318
+ right: 16px;
319
+ bottom: 16px;
320
+ z-index: 10;
321
+ padding: 8px 14px;
322
+ border: 1px solid var(--ng-colors-border-secondary);
323
+ border-radius: 999px;
324
+ background: var(--ng-colors-bg-primary);
325
+ box-shadow: 0 2px 8px rgb(0 0 0 / 12%);
326
+ font: inherit;
327
+ font-size: 13px;
328
+ cursor: pointer;
329
+ }
330
+
331
+ .vg-switch:hover {
332
+ background: var(--ng-colors-bg-primary-hover);
333
+ }
334
+ </style>
335
+ <script type="module" src="index.js"></script>
336
+ <!-- nile-elements renders nile-glyph but does not register it. -->
337
+ <script type="module">
338
+ import '@aquera/nile-glyph';
339
+ </script>
340
+ </head>
341
+ <body>
342
+ <!--
343
+ Two views, switched by ?view= and the pill bottom-right:
344
+ (default) the reference shell, one realistic page
345
+ ?view=variants the AC-7 comparison surface, all 12 header variants
346
+ -->
347
+ <div id="vgShellView" style="height: 100%">
348
+ <nile-app-shell id="shell" full-height persist-key="demo-shell">
349
+ <nile-app-shell-side-nav>
350
+ <div slot="header" class="demo-brand">
351
+ <nile-glyph
352
+ name="ng-layers-2"
353
+ size="20"
354
+ method="stroke"
355
+ color="currentColor"
356
+ ></nile-glyph>
357
+ <span class="demo-brand-name">Aquera</span>
358
+ </div>
359
+ <nav class="demo-nav">
360
+ <a href="#" aria-current="page">
361
+ <nile-glyph
362
+ name="ng-home-line"
363
+ size="20"
364
+ method="stroke"
365
+ color="currentColor"
366
+ ></nile-glyph>
367
+ <span class="demo-label">Workforce overview</span>
368
+ </a>
369
+ <a href="#">
370
+ <nile-glyph
371
+ name="ng-plug"
372
+ size="20"
373
+ method="stroke"
374
+ color="currentColor"
375
+ ></nile-glyph>
376
+ <span class="demo-label">Integrations</span>
377
+ </a>
378
+ <a href="#">
379
+ <nile-glyph
380
+ name="ng-inbox"
381
+ size="20"
382
+ method="stroke"
383
+ color="currentColor"
384
+ ></nile-glyph>
385
+ <span class="demo-label">Requests</span>
386
+ </a>
387
+ <a href="#">
388
+ <nile-glyph
389
+ name="ng-settings-01"
390
+ size="20"
391
+ method="stroke"
392
+ color="currentColor"
393
+ ></nile-glyph>
394
+ <span class="demo-label">Settings</span>
395
+ </a>
396
+ </nav>
397
+ </nile-app-shell-side-nav>
398
+
399
+ <h1 slot="top-bar" style="margin: 0; font-size: 16px">
400
+ Workforce overview
401
+ </h1>
402
+
403
+ <nile-button
404
+ slot="top-bar-actions"
405
+ variant="tertiary"
406
+ id="tabs-toggle"
407
+ >
408
+ Hide tabs
409
+ </nile-button>
410
+
411
+ <!--
412
+ The module's second level: a nile-nav-tab-group in the shell's `tabs`
413
+ slot, hoisted out of the page scroller so it never scrolls away. Toggle
414
+ the shell's `noTabs` to switch the whole strip off.
415
+ -->
416
+ <nile-nav-tab-group slot="tabs" id="views">
417
+ <nile-nav-tab slot="nav" panel="apps" active>
418
+ Connected applications
419
+ <nile-badge slot="suffix" variant="gray">94</nile-badge>
420
+ </nile-nav-tab>
421
+ <nile-nav-tab slot="nav" panel="approvals">
422
+ Approvals
423
+ <nile-badge slot="suffix" variant="gray">3</nile-badge>
424
+ </nile-nav-tab>
425
+ </nile-nav-tab-group>
426
+
427
+ <nile-app-shell-main>
428
+ <div id="panel-apps">
429
+ <p style="color: var(--ng-colors-text-tertiary-600)">
430
+ Page body for <b>Connected applications</b>. Press the Ask button in
431
+ the top bar, then the clock in the panel header to open chat
432
+ history — the back control takes the brand mark's place, and
433
+ pressing it emits <code>nile-back</code>.
434
+ </p>
435
+ </div>
436
+ <div id="panel-approvals" hidden>
437
+ <p style="color: var(--ng-colors-text-tertiary-600)">
438
+ Page body for <b>Approvals</b>. The tab strip lives in the shell's
439
+ <code>tabs</code> slot, so switching views never moves the chrome.
440
+ </p>
441
+ </div>
442
+ </nile-app-shell-main>
443
+
444
+ <nile-app-shell-panel id="ask" label="Ask Aquera">
445
+ <div id="thread" class="demo-thread">
446
+ <p>How many contractors roll off this quarter?</p>
447
+ </div>
448
+
449
+ <div id="historyView" hidden style="display: contents">
450
+ <span class="demo-group-label">Today</span>
451
+ <button class="demo-chat-row" type="button">
452
+ <b>Contractor roll-off, Q3</b>
453
+ <span>4 messages</span>
454
+ </button>
455
+ <button class="demo-chat-row" type="button">
456
+ <b>Okta group drift</b>
457
+ <span>11 messages</span>
458
+ </button>
459
+ <span class="demo-group-label">Yesterday</span>
460
+ <button class="demo-chat-row" type="button">
461
+ <b>Right-size Zoom licences</b>
462
+ <span>6 messages</span>
463
+ </button>
464
+ </div>
465
+
466
+ <nile-button
467
+ slot="actions"
468
+ variant="ghost"
469
+ circle
470
+ id="history-toggle"
471
+ title="Chat history"
472
+ aria-label="Chat history"
473
+ >
474
+ <nile-glyph
475
+ name="ng-clock-refresh"
476
+ size="20"
477
+ method="stroke"
478
+ color="currentColor"
479
+ ></nile-glyph>
480
+ </nile-button>
481
+
482
+ <div slot="footer">
483
+ <nile-input placeholder="Ask about this page…"></nile-input>
484
+ </div>
485
+ </nile-app-shell-panel>
486
+ </nile-app-shell>
487
+ </div>
488
+
489
+ <!--
490
+ The header variant gallery. The cards are generated from the axis matrix
491
+ rather than written out twelve times, so the only thing that can differ
492
+ between them is the axes themselves — which is the whole point of a
493
+ parity comparison.
494
+ -->
495
+ <div id="vgVariantsView" class="vg" hidden>
496
+ <div class="vg-intro">
497
+ <h1>App Shell header — 12 variants</h1>
498
+ <p>
499
+ The three Figma axes crossed out in full:
500
+ <code>Tabs</code> &times; <code>Description</code> &times;
501
+ <code>Leading</code>. Cards are grouped by the height their axes are
502
+ specified to produce, so each section heading is one of the four
503
+ heights in SPEC-app-shell-header D-2. The chip on each card is its
504
+ <em>measured</em> header block — bar top to strip bottom — so a
505
+ variant that does not land on its number says so here rather than in
506
+ devtools.
507
+ </p>
508
+ <p>
509
+ <code>pageTitle</code> and <code>badge</code> are on every card:
510
+ neither is one of the three axes, so holding them constant is what
511
+ makes the axes comparable. Tabbed cards carry a second chip for the
512
+ inner tab row (32px) and the strip that holds it (33px) — two
513
+ separate numbers, both confirmed against the Figma nodes. See AC-6.
514
+ </p>
515
+ <p>
516
+ Heights below 600px of viewport height are compressed by design
517
+ (<code>@media (max-height: 600px)</code> in
518
+ <code>nile-app-shell.css.ts</code>), so compare in a window taller
519
+ than that. The chips say when the window is too short.
520
+ </p>
521
+ <button class="vg-switch" type="button" id="vgRemeasure" style="right: 200px">
522
+ Re-measure
523
+ </button>
524
+ </div>
525
+ <div id="vgGrid"></div>
526
+ </div>
527
+
528
+ <button class="vg-switch" type="button" id="vgSwitch"></button>
529
+
530
+ <script type="module">
531
+ /*
532
+ * The panel owns the chrome, the page owns the view stack: `back` draws
533
+ * the control, `nile-back` says it was pressed, and this decides what
534
+ * going back means.
535
+ */
536
+ const shell = document.getElementById('shell');
537
+ const ask = document.getElementById('ask');
538
+ const thread = document.getElementById('thread');
539
+ const historyView = document.getElementById('historyView');
540
+ const views = document.getElementById('views');
541
+
542
+ document.getElementById('history-toggle').addEventListener('click', () => {
543
+ ask.label = 'Chat history';
544
+ ask.back = true;
545
+ thread.hidden = true;
546
+ historyView.hidden = false;
547
+ });
548
+
549
+ ask.addEventListener('nile-back', () => {
550
+ ask.label = 'Ask Aquera';
551
+ ask.back = false;
552
+ historyView.hidden = true;
553
+ thread.hidden = false;
554
+ });
555
+
556
+ /*
557
+ * The strip is the module's second level; the page owns which view it
558
+ * shows. `detail.value` is the tab's `panel`.
559
+ */
560
+ views.addEventListener('nile-tab-change', event => {
561
+ const active = event.detail.value;
562
+ document.getElementById('panel-apps').hidden = active !== 'apps';
563
+ document.getElementById('panel-approvals').hidden = active !== 'approvals';
564
+ });
565
+
566
+ // `noTabs` hides the strip without emptying the slot.
567
+ const tabsToggle = document.getElementById('tabs-toggle');
568
+ tabsToggle.addEventListener('click', () => {
569
+ shell.noTabs = !shell.noTabs;
570
+ tabsToggle.textContent = shell.noTabs ? 'Show tabs' : 'Hide tabs';
571
+ });
572
+
573
+ // Opening a thread replaces the transcript and returns to the chat view.
574
+ historyView.addEventListener('click', event => {
575
+ const row = event.target.closest('.demo-chat-row');
576
+ if (!row) return;
577
+ ask.label = row.querySelector('b').textContent;
578
+ ask.back = false;
579
+ historyView.hidden = true;
580
+ thread.hidden = false;
581
+ });
582
+ </script>
583
+
584
+ <!--
585
+ The header variant gallery: view switching, card generation and the
586
+ measurement readout. SPEC-app-shell-header AC-5 / AC-6 / AC-7.
587
+ -->
588
+ <script type="module">
589
+ const params = new URLSearchParams(window.location.search);
590
+ const showVariants = params.get('view') === 'variants';
591
+
592
+ const vgShellView = document.getElementById('vgShellView');
593
+ const vgVariantsView = document.getElementById('vgVariantsView');
594
+ const vgSwitch = document.getElementById('vgSwitch');
595
+ const vgGrid = document.getElementById('vgGrid');
596
+ const vgRemeasure = document.getElementById('vgRemeasure');
597
+
598
+ vgShellView.hidden = showVariants;
599
+ vgVariantsView.hidden = !showVariants;
600
+ vgSwitch.textContent = showVariants
601
+ ? 'Reference shell demo'
602
+ : 'Header variants (12)';
603
+ vgSwitch.addEventListener('click', () => {
604
+ // Keeps ?style= so a variant sweep can be repeated on either theme.
605
+ params.set('view', showVariants ? 'shell' : 'variants');
606
+ window.location.search = params.toString();
607
+ });
608
+
609
+ /*
610
+ * The axis matrix, in the order the four heights come in. Tabs and
611
+ * Description each buy height; Leading buys none — which is the claim
612
+ * the three cards inside each section are there to show.
613
+ */
614
+ const SECTIONS = [
615
+ { tabs: false, description: false, height: 68 },
616
+ { tabs: false, description: true, height: 76 },
617
+ { tabs: true, description: false, height: 101 },
618
+ { tabs: true, description: true, height: 109 },
619
+ ];
620
+
621
+ const LEADINGS = [
622
+ { value: 'none', label: 'Leading=None' },
623
+ { value: 'back', label: 'Leading=Back' },
624
+ { value: 'app-icon', label: 'Leading=AppIcon' },
625
+ ];
626
+
627
+ const DESCRIPTION = 'Manage the applications connected to this tenant';
628
+
629
+ /*
630
+ * Both numbers are confirmed against the Figma nodes (2026-09-22). The
631
+ * strip is 33px — the variant frames are 101px with tabs and 68px
632
+ * without. The row inside it is 32px, composed by the tab node's own
633
+ * 0 / 4 / 12 / 4 padding around a 20px label line box: 0 + 20 + 12.
634
+ */
635
+ const STRIP_HEIGHT = 33;
636
+ const ROW_HEIGHT = 32;
637
+
638
+ const card = (n, section, leading) => {
639
+ const axes = `tabs=${section.tabs ? 'on' : 'off'} · description=${
640
+ section.description ? 'on' : 'off'
641
+ } · leading=${leading.value}`;
642
+
643
+ return `
644
+ <div class="vg-card">
645
+ <p class="vg-caption">
646
+ <span class="vg-id">${String(n).padStart(2, '0')}</span>
647
+ <span>${leading.label}</span>
648
+ <span class="vg-axes">${axes}</span>
649
+ <span class="vg-chip" data-role="block">measuring…</span>
650
+ ${
651
+ section.tabs
652
+ ? '<span class="vg-chip" data-role="row">measuring…</span>'
653
+ : ''
654
+ }
655
+ ${
656
+ leading.value === 'none'
657
+ ? ''
658
+ : '<span class="vg-chip" data-role="lead">measuring…</span>'
659
+ }
660
+ </p>
661
+ <div class="vg-frame">
662
+ <nile-app-shell
663
+ pageTitle="Connected applications"
664
+ badge="94 apps"
665
+ leading="${leading.value}"
666
+ ${section.description ? `description="${DESCRIPTION}"` : ''}
667
+ ${leading.value === 'back' ? 'backLabel="Integrations"' : ''}
668
+ no-panel-shortcut
669
+ >
670
+ <nav slot="side-nav" class="vg-nav">
671
+ <span>Integrations</span>
672
+ <span>Requests</span>
673
+ </nav>
674
+ ${
675
+ leading.value === 'app-icon'
676
+ ? `<nile-glyph
677
+ slot="app-icon"
678
+ name="ng-layers-2"
679
+ size="20"
680
+ method="stroke"
681
+ color="currentColor"
682
+ ></nile-glyph>`
683
+ : ''
684
+ }
685
+ ${
686
+ section.tabs
687
+ ? `<nile-nav-tab-group slot="tabs">
688
+ <nile-nav-tab slot="nav" panel="apps" active>
689
+ Connected applications
690
+ </nile-nav-tab>
691
+ <nile-nav-tab slot="nav" panel="approvals">
692
+ Approvals
693
+ </nile-nav-tab>
694
+ </nile-nav-tab-group>`
695
+ : ''
696
+ }
697
+ <nile-button slot="top-bar-actions" variant="tertiary">
698
+ Add application
699
+ </nile-button>
700
+ <div class="vg-body">
701
+ Page body. The header above is the whole subject of this
702
+ card; the axes are named in the caption.
703
+ </div>
704
+ <div slot="panel">
705
+ Assistant panel, slotted so the header draws its Ask control.
706
+ </div>
707
+ </nile-app-shell>
708
+ </div>
709
+ </div>`;
710
+ };
711
+
712
+ /** Every card, with the height its axes are specified to produce. */
713
+ const cards = [];
714
+
715
+ const build = () => {
716
+ let n = 0;
717
+ vgGrid.innerHTML = SECTIONS.map(section => {
718
+ const body = LEADINGS.map(leading => card(++n, section, leading)).join(
719
+ ''
720
+ );
721
+ return `
722
+ <section class="vg-section">
723
+ <div class="vg-section-head">
724
+ <h2>
725
+ Tabs ${section.tabs ? 'On' : 'Off'} · Description
726
+ ${section.description ? 'On' : 'Off'}
727
+ </h2>
728
+ <span>specified header block: ${section.height}px</span>
729
+ </div>
730
+ ${body}
731
+ </section>`;
732
+ }).join('');
733
+
734
+ vgGrid.querySelectorAll('.vg-card').forEach((el, i) => {
735
+ const section = SECTIONS[Math.floor(i / LEADINGS.length)];
736
+ cards.push({
737
+ shell: el.querySelector('nile-app-shell'),
738
+ expected: section.height,
739
+ tabs: section.tabs,
740
+ blockChip: el.querySelector('[data-role="block"]'),
741
+ rowChip: el.querySelector('[data-role="row"]'),
742
+ leadChip: el.querySelector('[data-role="lead"]'),
743
+ });
744
+ });
745
+ };
746
+
747
+ const px = value => `${Math.round(value * 10) / 10}px`;
748
+
749
+ const measure = () => {
750
+ // The bar compresses under 600px of viewport height, by design.
751
+ const short = window.innerHeight <= 600;
752
+
753
+ cards.forEach(entry => {
754
+ const root = entry.shell.shadowRoot;
755
+ if (!root) return;
756
+
757
+ const bar = root.querySelector('.container > .top-bar');
758
+ const strip = root.querySelector('.tabs');
759
+ const barBox = bar.getBoundingClientRect();
760
+ const block = entry.shell.tabsVisible
761
+ ? strip.getBoundingClientRect().bottom - barBox.top
762
+ : barBox.height;
763
+
764
+ if (short) {
765
+ entry.blockChip.dataset.state = 'note';
766
+ entry.blockChip.textContent = `${px(
767
+ block
768
+ )} — window under 600px tall, heights compress by design`;
769
+ } else {
770
+ const ok = Math.abs(block - entry.expected) < 0.5;
771
+ entry.blockChip.dataset.state = ok ? 'ok' : 'off';
772
+ entry.blockChip.textContent = ok
773
+ ? `header block ${px(block)}`
774
+ : `header block ${px(block)} — expected ${entry.expected}px`;
775
+ }
776
+
777
+ /*
778
+ * The leading element belongs to the title's row, so it sits 2px
779
+ * below the title's top edge — (24px line box - 20px control) / 2 —
780
+ * whether or not there is a description under it. The app icon is
781
+ * 20px, its own size rather than the 32px utility control box.
782
+ */
783
+ if (entry.leadChip) {
784
+ const title = root
785
+ .querySelector('[part~="page-title"]')
786
+ .getBoundingClientRect();
787
+ const icon = root.querySelector('[part~="app-icon"]');
788
+ /*
789
+ * For leading=back the 20px element is the ARROW, not the control:
790
+ * the control is the title's own 24px line box, so its label sits
791
+ * level with the page title (+0) while the arrow centres in it
792
+ * (+2). Measuring the control box would report +0 and look wrong.
793
+ */
794
+ const measured = icon
795
+ ? icon
796
+ : root.querySelector('[part~="back"]').querySelector('nile-glyph');
797
+ const box = measured.getBoundingClientRect();
798
+ const offset = Math.round(box.top - title.top);
799
+ const sized = Math.round(box.width);
800
+ const ok = offset === 2 && sized === 20;
801
+
802
+ entry.leadChip.dataset.state = ok ? 'ok' : 'off';
803
+ entry.leadChip.textContent =
804
+ `${icon ? 'app icon' : 'back arrow'} ${sized}×${Math.round(
805
+ box.height
806
+ )}` +
807
+ ` · ${offset >= 0 ? '+' : ''}${offset}px from title top` +
808
+ (ok ? '' : ' — expected 20px at +2px');
809
+ }
810
+
811
+ if (!entry.tabs || !entry.rowChip) return;
812
+
813
+ /*
814
+ * Two different numbers, and AC-6 asks for both. The strip is a
815
+ * min-height floor, so it reads 33px whenever the row fits inside
816
+ * it — which is exactly why the row is measured and reported
817
+ * separately rather than inferred from the strip.
818
+ */
819
+ const stripBox = strip.getBoundingClientRect();
820
+ const group = entry.shell.querySelector('nile-nav-tab-group');
821
+ const rowBox = group.getBoundingClientRect();
822
+ const slack = rowBox.top - stripBox.top;
823
+ const stripOk = Math.abs(stripBox.height - STRIP_HEIGHT) < 0.5;
824
+ const rowOk = Math.abs(rowBox.height - ROW_HEIGHT) < 0.5;
825
+
826
+ entry.rowChip.dataset.state = stripOk && rowOk ? 'ok' : 'off';
827
+ entry.rowChip.textContent =
828
+ `strip ${px(stripBox.height)} · row ${px(rowBox.height)}` +
829
+ (rowOk ? '' : ` — expected ${ROW_HEIGHT}px`) +
830
+ ` · slack above ${px(slack)}`;
831
+ });
832
+ };
833
+
834
+ if (showVariants) {
835
+ build();
836
+ // The shells have to be upgraded and laid out before a box means
837
+ // anything, and the nav track animates up from 0 on first paint.
838
+ await customElements.whenDefined('nile-app-shell');
839
+ await Promise.all(cards.map(entry => entry.shell.updateComplete));
840
+ requestAnimationFrame(() => requestAnimationFrame(measure));
841
+
842
+ vgRemeasure.addEventListener('click', measure);
843
+
844
+ let pending;
845
+ window.addEventListener('resize', () => {
846
+ window.clearTimeout(pending);
847
+ pending = window.setTimeout(measure, 150);
848
+ });
849
+ }
850
+ </script>
851
+ </body>
852
+ </html>