@aquera/nile-elements 2.0.10 → 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 (104) hide show
  1. package/README.md +3 -0
  2. package/demo/app-shell.html +852 -0
  3. package/demo/nxtgen-classes.css +2 -2
  4. package/demo/nxtgen-properties.css +2 -2
  5. package/demo/nxtgen-utilities.css +2 -2
  6. package/demo/nxtgen.css +2 -2
  7. package/demo/utilities.css +2 -2
  8. package/demo/variables.css +2 -2
  9. package/dist/index.cjs.js +1 -1
  10. package/dist/index.esm.js +1 -1
  11. package/dist/index.js +579 -296
  12. package/dist/internal/app-shell-utility-control-styles.cjs.js +2 -0
  13. package/dist/internal/app-shell-utility-control-styles.cjs.js.map +1 -0
  14. package/dist/internal/app-shell-utility-control-styles.esm.js +16 -0
  15. package/dist/nile-app-shell/index.cjs.js +1 -1
  16. package/dist/nile-app-shell/index.esm.js +1 -1
  17. package/dist/nile-app-shell/nile-app-shell.cjs.js +1 -1
  18. package/dist/nile-app-shell/nile-app-shell.cjs.js.map +1 -1
  19. package/dist/nile-app-shell/nile-app-shell.css.cjs.js +1 -1
  20. package/dist/nile-app-shell/nile-app-shell.css.cjs.js.map +1 -1
  21. package/dist/nile-app-shell/nile-app-shell.css.esm.js +270 -92
  22. package/dist/nile-app-shell/nile-app-shell.esm.js +61 -10
  23. package/dist/nile-app-shell/shell-controller.cjs.js.map +1 -1
  24. package/dist/nile-app-shell-main/nile-app-shell-main.cjs.js.map +1 -1
  25. package/dist/nile-app-shell-nav-toggle/index.cjs.js +1 -1
  26. package/dist/nile-app-shell-nav-toggle/index.esm.js +1 -1
  27. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js +1 -1
  28. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.cjs.js.map +1 -1
  29. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js +1 -1
  30. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.cjs.js.map +1 -1
  31. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.esm.js +7 -3
  32. package/dist/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.esm.js +3 -3
  33. package/dist/nile-app-shell-panel/index.cjs.js +1 -1
  34. package/dist/nile-app-shell-panel/index.esm.js +1 -1
  35. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js +1 -1
  36. package/dist/nile-app-shell-panel/nile-app-shell-panel.cjs.js.map +1 -1
  37. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js +1 -1
  38. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.cjs.js.map +1 -1
  39. package/dist/nile-app-shell-panel/nile-app-shell-panel.css.esm.js +29 -7
  40. package/dist/nile-app-shell-panel/nile-app-shell-panel.esm.js +18 -5
  41. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.cjs.js.map +1 -1
  42. package/dist/nile-app-shell-side-nav/nile-app-shell-side-nav.css.cjs.js.map +1 -1
  43. package/dist/src/internal/app-shell-utility-control-styles.d.ts +12 -0
  44. package/dist/src/internal/app-shell-utility-control-styles.js +29 -0
  45. package/dist/src/internal/app-shell-utility-control-styles.js.map +1 -0
  46. package/dist/src/nile-app-shell/nile-app-shell-regions.test.d.ts +3 -0
  47. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js +206 -0
  48. package/dist/src/nile-app-shell/nile-app-shell-regions.test.js.map +1 -1
  49. package/dist/src/nile-app-shell/nile-app-shell.css.d.ts +1 -9
  50. package/dist/src/nile-app-shell/nile-app-shell.css.js +270 -99
  51. package/dist/src/nile-app-shell/nile-app-shell.css.js.map +1 -1
  52. package/dist/src/nile-app-shell/nile-app-shell.d.ts +90 -122
  53. package/dist/src/nile-app-shell/nile-app-shell.js +224 -143
  54. package/dist/src/nile-app-shell/nile-app-shell.js.map +1 -1
  55. package/dist/src/nile-app-shell/nile-app-shell.test.d.ts +3 -0
  56. package/dist/src/nile-app-shell/nile-app-shell.test.js +898 -29
  57. package/dist/src/nile-app-shell/nile-app-shell.test.js.map +1 -1
  58. package/dist/src/nile-app-shell/shell-controller.d.ts +1 -8
  59. package/dist/src/nile-app-shell/shell-controller.js +2 -14
  60. package/dist/src/nile-app-shell/shell-controller.js.map +1 -1
  61. package/dist/src/nile-app-shell-main/nile-app-shell-main.d.ts +2 -6
  62. package/dist/src/nile-app-shell-main/nile-app-shell-main.js +2 -6
  63. package/dist/src/nile-app-shell-main/nile-app-shell-main.js.map +1 -1
  64. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.d.ts +1 -4
  65. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js +7 -5
  66. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.js.map +1 -1
  67. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.d.ts +4 -8
  68. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js +5 -6
  69. package/dist/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.js.map +1 -1
  70. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js +28 -5
  71. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.css.js.map +1 -1
  72. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.d.ts +18 -13
  73. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js +45 -16
  74. package/dist/src/nile-app-shell-panel/nile-app-shell-panel.js.map +1 -1
  75. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.d.ts +1 -4
  76. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js +1 -4
  77. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.js.map +1 -1
  78. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.d.ts +4 -12
  79. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js +4 -12
  80. package/dist/src/nile-app-shell-side-nav/nile-app-shell-side-nav.js.map +1 -1
  81. package/dist/src/version.js +1 -1
  82. package/dist/src/version.js.map +1 -1
  83. package/dist/tsconfig.tsbuildinfo +1 -1
  84. package/dist/version.cjs.js +1 -1
  85. package/dist/version.cjs.js.map +1 -1
  86. package/dist/version.esm.js +1 -1
  87. package/package.json +1 -1
  88. package/plop-templates/lit/lit.test.ts.hbs +22 -0
  89. package/plop-templates/lit/lit.ts.hbs +10 -5
  90. package/plopfile.js +12 -0
  91. package/src/internal/app-shell-utility-control-styles.ts +30 -0
  92. package/src/nile-app-shell/nile-app-shell-regions.test.ts +248 -0
  93. package/src/nile-app-shell/nile-app-shell.css.ts +270 -99
  94. package/src/nile-app-shell/nile-app-shell.test.ts +1120 -29
  95. package/src/nile-app-shell/nile-app-shell.ts +241 -148
  96. package/src/nile-app-shell/shell-controller.ts +2 -14
  97. package/src/nile-app-shell-main/nile-app-shell-main.ts +2 -6
  98. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.css.ts +7 -5
  99. package/src/nile-app-shell-nav-toggle/nile-app-shell-nav-toggle.ts +7 -11
  100. package/src/nile-app-shell-panel/nile-app-shell-panel.css.ts +28 -5
  101. package/src/nile-app-shell-panel/nile-app-shell-panel.ts +44 -16
  102. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.css.ts +1 -4
  103. package/src/nile-app-shell-side-nav/nile-app-shell-side-nav.ts +4 -12
  104. package/vscode-html-custom-data.json +114 -37
@@ -6,29 +6,32 @@
6
6
  */
7
7
 
8
8
  import { css } from 'lit';
9
+ import { utilityControlStyles } from '../internal/app-shell-utility-control-styles';
9
10
 
10
- /**
11
- * AppShell CSS.
12
- *
13
- * One grid, two columns, one flex row. The grid owns the banner row, the
14
- * optional shell-wide top bar, the side-nav track and the content column. The
15
- * content column is a flex row holding the content container, the optional
16
- * aside and — when docked — the AI panel. Nothing else in an app sets these
17
- * boxes.
18
- */
11
+ /** AppShell CSS: a grid of banner, top bar, side-nav track and content column, whose flex row holds the container, aside and docked AI panel. */
19
12
  export const styles = css`
20
13
  :host {
21
- /* Geometry. Values come from design tokens wherever one exists; the bare
22
- pixels are shell geometry with no matching token. */
23
- --nile-app-shell-side-nav-width: 260px;
24
- --nile-app-shell-side-nav-rail-width: 64px;
25
- --nile-app-shell-gutter: var(--nile-spacing-md, var(--ng-spacing-md));
14
+ /* Geometry: design tokens where one exists, bare pixels where none does. */
15
+ /* The nav track mirrors nile-side-bar's own width expressions (enterprise 240/70, NxtGen 296/68); keep in step with nile-side-bar.css.ts. */
16
+ --nile-app-shell-side-nav-width: var(
17
+ --nile-width-240px,
18
+ var(--ng-spacing-296, 240px)
19
+ );
20
+ --nile-app-shell-side-nav-rail-width: var(
21
+ --nile-width-70px,
22
+ var(--ng-spacing-68, 70px)
23
+ );
24
+ --nile-app-shell-gutter: var(--nile-spacing-xs, var(--ng-spacing-xs));
26
25
  --nile-app-shell-content-padding: var(
27
26
  --nile-spacing-3xl,
28
27
  var(--ng-spacing-3xl)
29
28
  );
30
- --nile-app-shell-top-bar-height: 56px;
31
- --nile-app-shell-tabs-height: 48px;
29
+ --nile-app-shell-top-bar-height: 68px;
30
+ /* The panel header keeps its own height, distinct from the page header. */
31
+ --nile-app-shell-panel-header-height: 56px;
32
+ --nile-app-shell-tabs-height: 33px;
33
+ /* The leading app icon is its own 20px size, not the 32px utility box. */
34
+ --nile-app-shell-app-icon-size: 20px;
32
35
  --nile-app-shell-panel-width: 480px;
33
36
  --nile-app-shell-panel-min-width: 360px;
34
37
  --nile-app-shell-aside-width: 320px;
@@ -36,9 +39,10 @@ export const styles = css`
36
39
  --nile-app-shell-content-min-width: 640px;
37
40
  --nile-app-shell-min-width: 768px;
38
41
  --nile-app-shell-centered-width: 824px;
42
+ /* Both tokens are 32px; the literal is the floor with no theme sheet loaded. */
39
43
  --nile-app-shell-utility-size: var(
40
44
  --nile-spacing-4xl,
41
- var(--ng-spacing-4xl)
45
+ var(--ng-spacing-4xl, 32px)
42
46
  );
43
47
  --nile-app-shell-radius: var(--nile-radius-xl, var(--ng-radius-2xl));
44
48
  --nile-app-shell-duration: var(
@@ -78,8 +82,7 @@ export const styles = css`
78
82
  height: 100dvh;
79
83
  }
80
84
 
81
- /* Effective rail state, set by the component: the user's preference, or a
82
- forced rail at narrow widths. */
85
+ /* Effective rail state: the user's preference, or a forced rail when narrow. */
83
86
  :host([rail]) {
84
87
  --_side-nav-track: var(--nile-app-shell-side-nav-rail-width);
85
88
  }
@@ -88,10 +91,19 @@ export const styles = css`
88
91
  --_side-nav-track: 0px;
89
92
  }
90
93
 
94
+ /* The Description axis: a present description adds the 8px its second line occupies. */
95
+ :host([has-description]) {
96
+ --nile-app-shell-top-bar-height: 76px;
97
+ }
98
+
91
99
  /* The top bar compresses before the content does. */
92
100
  @media (max-height: 600px) {
93
101
  :host {
94
- --nile-app-shell-top-bar-height: 48px;
102
+ --nile-app-shell-top-bar-height: 56px;
103
+ }
104
+
105
+ :host([has-description]) {
106
+ --nile-app-shell-top-bar-height: 64px;
95
107
  }
96
108
  }
97
109
 
@@ -119,13 +131,12 @@ export const styles = css`
119
131
  display: none;
120
132
  }
121
133
 
122
- /*
123
- * The nav and the page share one surface, and the shell owns the track
124
- * width, so a slotted sidebar fills the track and drops its own background
125
- * and edge. Set --nile-app-shell-side-nav-bg to put a surface back.
126
- */
134
+ /* A slotted sidebar fills the shell-owned track and drops its own surface and edge; set --nile-app-shell-side-nav-bg to restore one. */
135
+ /* width is deliberately not !important: the 100% lets a raw <nav> or nile-app-shell-side-nav fill the track, while a slotted
136
+ nile-side-bar sizes itself from the same tokens the track reads (see the :host geometry block). Tests 21/22 in
137
+ nile-app-shell-regions.test.ts lock the two together; change both or neither. */
127
138
  ::slotted([slot='side-nav']) {
128
- width: 100% !important;
139
+ width: 100%;
129
140
  height: 100%;
130
141
  background: var(--nile-app-shell-side-nav-bg, transparent) !important;
131
142
  border-right: var(--nile-app-shell-side-nav-border, 0) !important;
@@ -140,19 +151,19 @@ export const styles = css`
140
151
  min-width: 0;
141
152
  gap: var(--nile-app-shell-gutter);
142
153
  padding: var(--nile-app-shell-gutter);
154
+ /* Flush against the nav track; the gutter insets the other three sides. */
155
+ padding-inline-start: 0;
143
156
  box-sizing: border-box;
144
157
  }
145
158
 
159
+ /* With no nav track there is nothing to sit flush against, so the start edge takes the gutter too. */
160
+ :host(:not([has-side-nav])) .content-section {
161
+ padding-inline-start: var(--nile-app-shell-gutter);
162
+ }
163
+
146
164
  .container {
147
165
  box-sizing: border-box;
148
- /*
149
- * The floor is the flex basis, not a min-width: a hard min-width cannot
150
- * yield, so the row overflows and :host's overflow:hidden clips the
151
- * regions after it. As a basis the container holds the floor while there
152
- * is room and gives way when there is not. The shell keeps a docked panel
153
- * off the row until it fits (see _dockRequirement), so the give-way path
154
- * is the last resort, not the usual one.
155
- */
166
+ /* The floor is the flex basis, not a min-width, so the container yields instead of overflowing the clipped row. */
156
167
  flex: 1 1 var(--nile-app-shell-content-min-width);
157
168
  display: flex;
158
169
  flex-direction: column;
@@ -160,24 +171,15 @@ export const styles = css`
160
171
  overflow: hidden;
161
172
  background: var(--nile-colors-white-base, var(--ng-colors-bg-secondary));
162
173
  border: var(--nile-border-width-1, var(--ng-border-width-1)) solid
163
- var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
174
+ var(--nile-colors-neutral-500, var(--ng-colors-border-primary));
164
175
  border-radius: var(--nile-app-shell-radius);
165
176
  box-shadow: var(--nile-box-shadow-none, var(--ng-shadow-none));
166
- /*
167
- * The container — not the viewport — is the sizing context for page
168
- * content. See the content-container note below for querying it.
169
- */
177
+ /* The container, not the viewport, is the sizing context for page content. */
170
178
  container-type: inline-size;
171
179
  container-name: nile-content;
172
180
  }
173
181
 
174
- /*
175
- * Container queries cannot cross a shadow boundary: a rule in the page's
176
- * stylesheet cannot see a container declared in here. The
177
- * content-container attribute moves the context onto the slotted page root instead, so page CSS can
178
- * write an @container nile-content query. It brings layout containment with
179
- * it, so it is opt-in.
180
- */
182
+ /* content-container moves the size query context onto the slotted page root so page CSS can query nile-content; opt-in as it brings containment. */
181
183
  :host([content-container]) slot:not([name])::slotted(*) {
182
184
  container-type: inline-size;
183
185
  container-name: nile-content;
@@ -201,12 +203,7 @@ export const styles = css`
201
203
  padding-inline: var(--nile-app-shell-content-padding);
202
204
  }
203
205
 
204
- /* Inside the container the bar carries the rule that separates it from the
205
- page body; spanning the shell it carries the container's edge instead. */
206
- .container > .top-bar {
207
- border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid
208
- var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
209
- }
206
+ /* The header bar draws no bottom rule in any variant; the only divider comes from the tab rail (see .tabs). */
210
207
 
211
208
  .shell-top-bar {
212
209
  grid-area: top-bar;
@@ -230,6 +227,171 @@ export const styles = css`
230
227
  flex: 1 1 auto;
231
228
  }
232
229
 
230
+ /* The title block: the header's own heading, the Leading axis and the badge; renders before the top-bar slot. */
231
+ .title-area {
232
+ display: flex;
233
+ /* Top-aligned, not centred: the leading element belongs to the title's row, not the title-plus-description stack. */
234
+ align-items: flex-start;
235
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
236
+ min-width: 0;
237
+ }
238
+
239
+ /* The leading line box: exactly the page title's line height, centring whatever leading control sits in it. */
240
+ .leading {
241
+ flex: 0 0 auto;
242
+ display: inline-flex;
243
+ align-items: center;
244
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
245
+ min-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
246
+ }
247
+
248
+ .title-stack {
249
+ display: flex;
250
+ flex-direction: column;
251
+ justify-content: center;
252
+ gap: var(--nile-spacing-xxs, var(--ng-spacing-xxs));
253
+ min-width: 0;
254
+ }
255
+
256
+ .title-row {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
260
+ min-width: 0;
261
+ }
262
+
263
+ .page-title {
264
+ margin: 0;
265
+ min-width: 0;
266
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
267
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
268
+ line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
269
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));
270
+ color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
271
+ white-space: nowrap;
272
+ overflow: hidden;
273
+ text-overflow: ellipsis;
274
+ }
275
+
276
+ .description {
277
+ margin: 0;
278
+ min-width: 0;
279
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
280
+ font-size: var(--nile-type-scale-1, var(--ng-font-size-text-xs));
281
+ line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-xs));
282
+ font-weight: var(--nile-font-weight-regular, var(--ng-font-weight-regular));
283
+ color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));
284
+ white-space: nowrap;
285
+ overflow: hidden;
286
+ text-overflow: ellipsis;
287
+ }
288
+
289
+ /* The badge sits on the title row and never shrinks the title away; each surface token pairs a Nile name with its NxtGen equivalent. */
290
+ .badge {
291
+ flex: 0 0 auto;
292
+ display: inline-flex;
293
+ align-items: center;
294
+ padding-inline: var(--nile-spacing-md, var(--ng-spacing-md));
295
+ padding-block: var(--nile-spacing-xxs, var(--ng-spacing-xxs));
296
+ border: var(--nile-border-width-1, var(--ng-border-width-1)) solid
297
+ var(
298
+ --nile-colors-neutral-400,
299
+ var(--ng-componentcolors-utility-gray-200)
300
+ );
301
+ border-radius: var(--nile-radius-full, var(--ng-radius-full));
302
+ background: var(
303
+ --nile-colors-neutral-100,
304
+ var(--ng-componentcolors-utility-gray-50)
305
+ );
306
+ color: var(
307
+ --nile-colors-dark-500,
308
+ var(--ng-componentcolors-utility-gray-700)
309
+ );
310
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
311
+ font-size: var(--nile-type-scale-1, var(--ng-font-size-text-xs));
312
+ line-height: var(--nile-line-height-xsmall, var(--ng-line-height-text-xs));
313
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-medium));
314
+ white-space: nowrap;
315
+ }
316
+
317
+ /* Leading=Back: drawn as a breadcrumb label; the shell owns no history, so pressing it only emits nile-header-back. */
318
+ .back {
319
+ flex: 0 0 auto;
320
+ display: inline-flex;
321
+ align-items: center;
322
+ gap: var(--nile-spacing-xs, var(--ng-spacing-xs));
323
+ margin: 0;
324
+ padding: 0;
325
+ border: 0;
326
+ background: none;
327
+ cursor: pointer;
328
+ font-family: var(--nile-font-family-serif, var(--ng-font-family-body));
329
+ font-size: var(--nile-type-scale-3, var(--ng-font-size-text-md));
330
+ line-height: var(--nile-line-height-medium, var(--ng-line-height-text-md));
331
+ font-weight: var(--nile-font-weight-medium, var(--ng-font-weight-semibold));
332
+ color: var(--nile-colors-dark-500, var(--ng-colors-text-tertiary-600));
333
+ }
334
+
335
+ .back:hover {
336
+ color: var(--nile-colors-dark-900, var(--ng-colors-text-primary-900));
337
+ }
338
+
339
+ .crumb-divider {
340
+ flex: 0 0 auto;
341
+ display: inline-flex;
342
+ align-items: center;
343
+ color: var(--nile-colors-dark-500, var(--ng-colors-fg-quaternary-400));
344
+ }
345
+
346
+ /* Leading=AppIcon: a 20px block before the title; the page slots the mark, the shell owns the box, surface and radius. */
347
+ .app-icon {
348
+ position: relative;
349
+ flex: 0 0 auto;
350
+ display: inline-flex;
351
+ align-items: center;
352
+ justify-content: center;
353
+ width: var(--nile-app-shell-app-icon-size);
354
+ height: var(--nile-app-shell-app-icon-size);
355
+ border-radius: var(--nile-radius-sm, var(--ng-radius-sm));
356
+ overflow: hidden;
357
+ }
358
+
359
+ /* The slotted mark fills the wrapper edge to edge, clipped to the box. */
360
+ .app-icon slot::slotted(*) {
361
+ width: 100%;
362
+ height: 100%;
363
+ display: block;
364
+ object-fit: cover;
365
+ }
366
+
367
+ /* The wrapper takes no fill; the rim is a white-12% gradient drawn as a masked ::before, with literal mask fallbacks for the nxtgen-only tokens. */
368
+ .app-icon::before {
369
+ content: '';
370
+ position: absolute;
371
+ inset: 0;
372
+ border-radius: inherit;
373
+ pointer-events: none;
374
+ z-index: 1;
375
+ background: linear-gradient(
376
+ 180deg,
377
+ rgb(255 255 255 / 12%) 0%,
378
+ rgb(255 255 255 / 0%) 100%
379
+ );
380
+ mask: var(
381
+ --ng-mask-rim,
382
+ linear-gradient(#000 0 0) content-box,
383
+ linear-gradient(#000 0 0)
384
+ );
385
+ mask-composite: var(--ng-mask-composite-exclude, exclude);
386
+ padding: 1.4px;
387
+ }
388
+
389
+ :host(:not([leading='app-icon'])) .app-icon,
390
+ :host(:not([leading='back'])) .back,
391
+ :host(:not([leading='back'])) .crumb-divider {
392
+ display: none;
393
+ }
394
+
233
395
  .top-bar-actions {
234
396
  display: flex;
235
397
  align-items: center;
@@ -237,25 +399,12 @@ export const styles = css`
237
399
  margin-inline-start: auto;
238
400
  }
239
401
 
240
- /*
241
- * An empty flex item still takes the row's gap before it, which pulls the
242
- * bar's content 16px short of the padding edge and leaves a sliver of bare
243
- * container at the end of the header. Nothing in the group, no box.
244
- */
402
+ /* Hidden when empty, so an empty group's gap does not pull the bar's content short of the padding edge. */
245
403
  :host(:not([has-top-bar-actions]):not([has-ask])) .top-bar-actions {
246
404
  display: none;
247
405
  }
248
406
 
249
- /*
250
- * The Ask button. The one control in the bar the shell renders itself: it
251
- * turns on the panel being mounted and it calls openPanel(), neither of
252
- * which slotted markup can reach.
253
- *
254
- * It is a stock <nile-button variant="primary">: the shell sets no surface,
255
- * radius, height or type of its own, so the button's appearance — and its
256
- * focus ring, hover, disabled and loading states — stays the component's.
257
- * The only rule here is the flex behaviour it needs as a top-bar item.
258
- */
407
+ /* The Ask button: a stock <nile-button variant="primary"> the shell renders itself; the only rule here is its flex behaviour as a top-bar item. */
259
408
  .ask {
260
409
  flex: 0 0 auto;
261
410
  }
@@ -278,12 +427,9 @@ export const styles = css`
278
427
  gap: var(--nile-spacing-lg, var(--ng-spacing-lg));
279
428
  }
280
429
 
281
- /*
282
- * Lands the 24px glyph box, not nile-button's 38px circle, on the
283
- * container's 24px origin: (38 - 24) / 2 = 7.
284
- */
430
+ /* Lands the 20px glyph box, not the 32px utility circle, on the 24px origin: (32 - 20) / 2 = 6. */
285
431
  .nav-toggle {
286
- margin-inline-start: -7px;
432
+ margin-inline-start: -6px;
287
433
  }
288
434
 
289
435
  .rule {
@@ -296,13 +442,14 @@ export const styles = css`
296
442
  );
297
443
  }
298
444
 
445
+ /* ------------------------------------------------------- utility controls */
446
+
447
+ /* The shell's own icon controls (nav toggle, panel back and close): the shared utility-control recipe. */
448
+ ${utilityControlStyles}
449
+
299
450
  /* --------------------------------------------------------------- tab strip */
300
451
 
301
- /*
302
- * Hoisted out of the scroller so it never scrolls away. It sits flush under
303
- * the top bar — the header carries no bottom rule of its own, so the two read
304
- * as one block of chrome, with the single rule living at the strip's bottom.
305
- */
452
+ /* Hoisted out of the scroller so it never scrolls away; sits flush under the top bar and carries the header block's single bottom rule. */
306
453
  .tabs {
307
454
  box-sizing: border-box;
308
455
  flex: 0 0 auto;
@@ -310,6 +457,7 @@ export const styles = css`
310
457
  align-items: stretch;
311
458
  gap: var(--nile-spacing-xs, var(--ng-spacing-xs));
312
459
  min-width: 0;
460
+ /* A floor, not a fixed box: the strip is as tall as the row it holds plus its rule. */
313
461
  min-height: var(--nile-app-shell-tabs-height);
314
462
  padding-inline: var(--nile-app-shell-content-padding);
315
463
  /* The strip carries the rule for the whole header block. */
@@ -319,14 +467,32 @@ export const styles = css`
319
467
  scrollbar-width: none;
320
468
  }
321
469
 
470
+ /* The header tab's 0/4/12/4 padding composes the 32px row; the 12px bottom gives the active indicator its band, set via the group's public padding hook. */
471
+ ::slotted([slot='tabs']) {
472
+ --nile-nav-tab-padding: var(--nile-spacing-none, var(--ng-spacing-none))
473
+ var(--nile-spacing-xs, var(--ng-spacing-xs))
474
+ var(--nile-spacing-lg, var(--ng-spacing-lg))
475
+ var(--nile-spacing-xs, var(--ng-spacing-xs));
476
+ }
477
+
322
478
  .tabs::-webkit-scrollbar {
323
479
  display: none;
324
480
  }
325
481
 
326
- :host(:not([has-tabs])) .tabs {
482
+ :host(:not([has-tabs])) .tabs,
483
+ :host([notabs]) .tabs {
327
484
  display: none;
328
485
  }
329
486
 
487
+ /* A slotted nile-nav-tab-group drops its own track (--track-width:0 on the host) and is bottom-aligned so its indicator sits on the region's single rule. */
488
+ /* Scoped to the group, not the slot, so bare tab controls are not stretched to an equal share of the strip. */
489
+ ::slotted(nile-nav-tab-group[slot='tabs']) {
490
+ --track-width: 0px;
491
+ flex: 1 1 auto;
492
+ min-width: 0;
493
+ align-self: flex-end;
494
+ }
495
+
330
496
  /* ------------------------------------------------------------------- main */
331
497
 
332
498
  .main {
@@ -336,7 +502,9 @@ export const styles = css`
336
502
  flex-direction: column;
337
503
  min-height: 0;
338
504
  overflow: auto;
505
+ /* Top and sides only: the page runs to the container's bottom edge. */
339
506
  padding: var(--nile-app-shell-content-padding);
507
+ padding-block-end: 0;
340
508
  }
341
509
 
342
510
  .main:focus {
@@ -354,13 +522,7 @@ export const styles = css`
354
522
 
355
523
  /* Centred when short, scrolls from the top when tall. */
356
524
  :host([content-align='centered']) .main-inner {
357
- /*
358
- * width:100% + max-width leaves free space for the auto inline margins to
359
- * split; dropping the width would let the auto margins cancel the flex
360
- * stretch and shrink-wrap the column to its widest child. The block margins
361
- * centre it while it is short and compute to 0 once it is taller, so a tall
362
- * page scrolls from the top rather than clipping.
363
- */
525
+ /* width:100% + max-width leaves space for the auto margins to centre it while short, and scroll from the top when tall. */
364
526
  width: 100%;
365
527
  max-width: var(--nile-app-shell-centered-width);
366
528
  margin: auto;
@@ -370,8 +532,7 @@ export const styles = css`
370
532
 
371
533
  .aside {
372
534
  box-sizing: border-box;
373
- /* Gives before the page does, down to its own floor, for the same reason
374
- the panel does: the page is the last region to be asked for room. */
535
+ /* Gives before the page does, down to its own floor. */
375
536
  flex: 0 1 var(--nile-app-shell-aside-width);
376
537
  min-width: var(--nile-app-shell-aside-min-width);
377
538
  display: flex;
@@ -393,13 +554,7 @@ export const styles = css`
393
554
 
394
555
  .panel {
395
556
  box-sizing: border-box;
396
- /*
397
- * The panel pushes the page, it never covers it, so it has to be able to
398
- * give a little: full width while the row can afford it, then shrinking to
399
- * its min. Here the floor can be a real min-width — the container's floor
400
- * is a flex basis that yields all the way, so the row still cannot
401
- * overflow into :host's clip once the panel stops giving.
402
- */
557
+ /* The panel pushes the page rather than covering it: full width while the row affords it, shrinking to a real min-width floor. */
403
558
  flex: 0 1 var(--nile-app-shell-panel-width);
404
559
  width: var(--nile-app-shell-panel-width);
405
560
  min-width: var(--nile-app-shell-panel-min-width);
@@ -453,13 +608,28 @@ export const styles = css`
453
608
  flex: 0 0 auto;
454
609
  display: flex;
455
610
  align-items: center;
456
- gap: var(--nile-spacing-lg, var(--ng-spacing-lg));
457
- height: var(--nile-app-shell-top-bar-height);
458
- padding-inline: var(--nile-spacing-xl, var(--ng-spacing-xl));
611
+ gap: var(--nile-spacing-md, var(--ng-spacing-md));
612
+ height: var(--nile-app-shell-panel-header-height);
613
+ padding-inline: var(--nile-spacing-3xl, var(--ng-spacing-3xl));
459
614
  border-bottom: var(--nile-border-width-1, var(--ng-border-width-1)) solid
460
615
  var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
461
616
  }
462
617
 
618
+ /* The back control for a panel sub-view; stands where the brand mark does in the root view. */
619
+ .panel-back {
620
+ flex: 0 0 auto;
621
+ margin-inline-start: -6px;
622
+ }
623
+
624
+ /* Mirrors the back control's offset on the end edge: the 20px glyph, not the 32px circle, lands on the padding line. */
625
+ .panel-close {
626
+ margin-inline-end: -6px;
627
+ }
628
+
629
+ :host([panelback]) .panel-mark {
630
+ display: none;
631
+ }
632
+
463
633
  .panel-heading {
464
634
  display: flex;
465
635
  align-items: center;
@@ -506,7 +676,8 @@ export const styles = css`
506
676
  flex: 1 1 auto;
507
677
  min-height: 0;
508
678
  overflow: auto;
509
- padding: var(--nile-spacing-xl, var(--ng-spacing-xl));
679
+ /* 16 under the header, 24 on the sides and bottom. */
680
+ padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl));
510
681
  display: flex;
511
682
  flex-direction: column;
512
683
  gap: var(--nile-spacing-xl, var(--ng-spacing-xl));
@@ -514,7 +685,7 @@ export const styles = css`
514
685
 
515
686
  .panel-footer {
516
687
  flex: 0 0 auto;
517
- padding: var(--nile-spacing-xl, var(--ng-spacing-xl));
688
+ padding: var(--nile-spacing-xl, var(--ng-spacing-xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl)) var(--nile-spacing-3xl, var(--ng-spacing-3xl));
518
689
  border-top: var(--nile-border-width-1, var(--ng-border-width-1)) solid
519
690
  var(--nile-colors-neutral-400, var(--ng-colors-border-secondary));
520
691
  }