@sondor-technology/sondor-components 1.0.0-1165-initial-setup.11 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (168) hide show
  1. package/package.json +2 -3
  2. package/src/components/alert/alert.ts +100 -100
  3. package/src/components/badge/badge.ts +96 -96
  4. package/src/components/breadcrumb/breadcrumb.ts +98 -98
  5. package/src/components/button/button.ts +281 -281
  6. package/src/components/button-group/button-group.ts +67 -67
  7. package/src/components/card/card.ts +52 -52
  8. package/src/components/checkbox/checkbox.ts +238 -238
  9. package/src/components/code-block/code-block.ts +256 -256
  10. package/src/components/content-card/content-card.ts +133 -133
  11. package/src/components/divider/divider.ts +68 -68
  12. package/src/components/drawer/drawer.ts +217 -217
  13. package/src/components/field/field.ts +30 -30
  14. package/src/components/footer/footer.ts +58 -58
  15. package/src/components/form/form-step.ts +32 -32
  16. package/src/components/form/form.ts +376 -376
  17. package/src/components/grid/grid-row.ts +75 -75
  18. package/src/components/grid/grid.ts +36 -36
  19. package/src/components/icon/icon-paths.ts +124 -124
  20. package/src/components/icon/icon.ts +82 -82
  21. package/src/components/input/input.ts +248 -248
  22. package/src/components/link/link.ts +97 -97
  23. package/src/components/metadata/metadata.ts +66 -66
  24. package/src/components/modal/modal.ts +158 -158
  25. package/src/components/page-layout/page-layout.ts +704 -704
  26. package/src/components/radio/radio.ts +142 -142
  27. package/src/components/radio-group/radio-group.ts +157 -157
  28. package/src/components/router/router.ts +46 -46
  29. package/src/components/select/select.ts +161 -161
  30. package/src/components/skeleton/skeleton.ts +67 -67
  31. package/src/components/switch/switch.ts +181 -181
  32. package/src/components/table/data-table.ts +556 -556
  33. package/src/components/table/table.ts +153 -153
  34. package/src/components/tabs/tab-panel.ts +30 -30
  35. package/src/components/tabs/tab.ts +76 -76
  36. package/src/components/tabs/tabs.ts +88 -88
  37. package/src/components/textarea/textarea.ts +215 -215
  38. package/src/components/theme-toggle/theme-toggle.ts +131 -131
  39. package/src/components/vertical-nav/vertical-nav-group.ts +96 -96
  40. package/src/components/vertical-nav/vertical-nav-item.ts +49 -49
  41. package/src/styles/globals.css +58 -58
  42. package/src/styles/index.css +80 -80
  43. package/src/styles/reset.ts +50 -50
  44. package/src/styles/theme.css +58 -58
  45. package/src/styles/typography.css +25 -25
  46. package/tsconfig.json +15 -15
  47. package/README.md +0 -8
  48. package/dist/components/alert/alert.d.ts +0 -13
  49. package/dist/components/alert/alert.d.ts.map +0 -1
  50. package/dist/components/alert/alert.js +0 -105
  51. package/dist/components/badge/badge.d.ts +0 -13
  52. package/dist/components/badge/badge.d.ts.map +0 -1
  53. package/dist/components/badge/badge.js +0 -101
  54. package/dist/components/breadcrumb/breadcrumb.d.ts +0 -19
  55. package/dist/components/breadcrumb/breadcrumb.d.ts.map +0 -1
  56. package/dist/components/breadcrumb/breadcrumb.js +0 -99
  57. package/dist/components/button/button.d.ts +0 -23
  58. package/dist/components/button/button.d.ts.map +0 -1
  59. package/dist/components/button/button.js +0 -304
  60. package/dist/components/button-group/button-group.d.ts +0 -6
  61. package/dist/components/button-group/button-group.d.ts.map +0 -1
  62. package/dist/components/button-group/button-group.js +0 -74
  63. package/dist/components/card/card.d.ts +0 -11
  64. package/dist/components/card/card.d.ts.map +0 -1
  65. package/dist/components/card/card.js +0 -54
  66. package/dist/components/checkbox/checkbox.d.ts +0 -28
  67. package/dist/components/checkbox/checkbox.d.ts.map +0 -1
  68. package/dist/components/checkbox/checkbox.js +0 -247
  69. package/dist/components/code-block/code-block.d.ts +0 -20
  70. package/dist/components/code-block/code-block.d.ts.map +0 -1
  71. package/dist/components/code-block/code-block.js +0 -260
  72. package/dist/components/content-card/content-card.d.ts +0 -13
  73. package/dist/components/content-card/content-card.d.ts.map +0 -1
  74. package/dist/components/content-card/content-card.js +0 -140
  75. package/dist/components/divider/divider.d.ts +0 -16
  76. package/dist/components/divider/divider.d.ts.map +0 -1
  77. package/dist/components/divider/divider.js +0 -82
  78. package/dist/components/drawer/drawer.d.ts +0 -22
  79. package/dist/components/drawer/drawer.d.ts.map +0 -1
  80. package/dist/components/drawer/drawer.js +0 -226
  81. package/dist/components/field/field.d.ts +0 -11
  82. package/dist/components/field/field.d.ts.map +0 -1
  83. package/dist/components/field/field.js +0 -32
  84. package/dist/components/footer/footer.d.ts +0 -6
  85. package/dist/components/footer/footer.d.ts.map +0 -1
  86. package/dist/components/footer/footer.js +0 -65
  87. package/dist/components/form/form-step.d.ts +0 -13
  88. package/dist/components/form/form-step.d.ts.map +0 -1
  89. package/dist/components/form/form-step.js +0 -41
  90. package/dist/components/form/form.d.ts +0 -25
  91. package/dist/components/form/form.d.ts.map +0 -1
  92. package/dist/components/form/form.js +0 -358
  93. package/dist/components/grid/grid-row.d.ts +0 -17
  94. package/dist/components/grid/grid-row.d.ts.map +0 -1
  95. package/dist/components/grid/grid-row.js +0 -92
  96. package/dist/components/grid/grid.d.ts +0 -13
  97. package/dist/components/grid/grid.d.ts.map +0 -1
  98. package/dist/components/grid/grid.js +0 -42
  99. package/dist/components/icon/icon-paths.d.ts +0 -116
  100. package/dist/components/icon/icon-paths.d.ts.map +0 -1
  101. package/dist/components/icon/icon-paths.js +0 -121
  102. package/dist/components/icon/icon.d.ts +0 -16
  103. package/dist/components/icon/icon.d.ts.map +0 -1
  104. package/dist/components/icon/icon.js +0 -95
  105. package/dist/components/input/input.d.ts +0 -31
  106. package/dist/components/input/input.d.ts.map +0 -1
  107. package/dist/components/input/input.js +0 -268
  108. package/dist/components/link/link.d.ts +0 -16
  109. package/dist/components/link/link.d.ts.map +0 -1
  110. package/dist/components/link/link.js +0 -116
  111. package/dist/components/metadata/metadata.d.ts +0 -18
  112. package/dist/components/metadata/metadata.d.ts.map +0 -1
  113. package/dist/components/metadata/metadata.js +0 -71
  114. package/dist/components/modal/modal.d.ts +0 -24
  115. package/dist/components/modal/modal.d.ts.map +0 -1
  116. package/dist/components/modal/modal.js +0 -163
  117. package/dist/components/page-layout/page-layout.d.ts +0 -46
  118. package/dist/components/page-layout/page-layout.d.ts.map +0 -1
  119. package/dist/components/page-layout/page-layout.js +0 -697
  120. package/dist/components/radio/radio.d.ts +0 -16
  121. package/dist/components/radio/radio.d.ts.map +0 -1
  122. package/dist/components/radio/radio.js +0 -152
  123. package/dist/components/radio-group/radio-group.d.ts +0 -27
  124. package/dist/components/radio-group/radio-group.d.ts.map +0 -1
  125. package/dist/components/radio-group/radio-group.js +0 -161
  126. package/dist/components/router/router.d.ts +0 -7
  127. package/dist/components/router/router.d.ts.map +0 -1
  128. package/dist/components/router/router.js +0 -37
  129. package/dist/components/select/select.d.ts +0 -16
  130. package/dist/components/select/select.d.ts.map +0 -1
  131. package/dist/components/select/select.js +0 -172
  132. package/dist/components/skeleton/skeleton.d.ts +0 -17
  133. package/dist/components/skeleton/skeleton.d.ts.map +0 -1
  134. package/dist/components/skeleton/skeleton.js +0 -78
  135. package/dist/components/switch/switch.d.ts +0 -25
  136. package/dist/components/switch/switch.d.ts.map +0 -1
  137. package/dist/components/switch/switch.js +0 -185
  138. package/dist/components/table/data-table.d.ts +0 -72
  139. package/dist/components/table/data-table.d.ts.map +0 -1
  140. package/dist/components/table/data-table.js +0 -551
  141. package/dist/components/table/table.d.ts +0 -22
  142. package/dist/components/table/table.d.ts.map +0 -1
  143. package/dist/components/table/table.js +0 -167
  144. package/dist/components/tabs/tab-panel.d.ts +0 -8
  145. package/dist/components/tabs/tab-panel.d.ts.map +0 -1
  146. package/dist/components/tabs/tab-panel.js +0 -46
  147. package/dist/components/tabs/tab.d.ts +0 -9
  148. package/dist/components/tabs/tab.d.ts.map +0 -1
  149. package/dist/components/tabs/tab.js +0 -91
  150. package/dist/components/tabs/tabs.d.ts +0 -14
  151. package/dist/components/tabs/tabs.d.ts.map +0 -1
  152. package/dist/components/tabs/tabs.js +0 -85
  153. package/dist/components/textarea/textarea.d.ts +0 -36
  154. package/dist/components/textarea/textarea.d.ts.map +0 -1
  155. package/dist/components/textarea/textarea.js +0 -231
  156. package/dist/components/theme-toggle/theme-toggle.d.ts +0 -15
  157. package/dist/components/theme-toggle/theme-toggle.d.ts.map +0 -1
  158. package/dist/components/theme-toggle/theme-toggle.js +0 -130
  159. package/dist/components/vertical-nav/vertical-nav-group.d.ts +0 -9
  160. package/dist/components/vertical-nav/vertical-nav-group.d.ts.map +0 -1
  161. package/dist/components/vertical-nav/vertical-nav-group.js +0 -111
  162. package/dist/components/vertical-nav/vertical-nav-item.d.ts +0 -8
  163. package/dist/components/vertical-nav/vertical-nav-item.d.ts.map +0 -1
  164. package/dist/components/vertical-nav/vertical-nav-item.js +0 -65
  165. package/dist/styles/index.css +0 -171
  166. package/dist/styles/reset.d.ts +0 -2
  167. package/dist/styles/reset.d.ts.map +0 -1
  168. package/dist/styles/reset.js +0 -50
@@ -1,705 +1,705 @@
1
- import { LitElement, html, css, PropertyValues } from 'lit';
2
- import { customElement, property, state } from 'lit/decorators.js';
3
- import { resetStyles } from '../../styles/reset.js';
4
-
5
- export type LayoutVariant = 'app-shell' | 'content-sidebar' | 'single-column' | 'three-column';
6
-
7
- @customElement('st-page-layout')
8
- export class StPageLayout extends LitElement {
9
- /**
10
- * Defines the structural layout variant to render.
11
- */
12
- @property({ type: String, reflect: true })
13
- variant: LayoutVariant = 'single-column';
14
-
15
- /**
16
- * Maximum width for the left sidebar section (e.g., '20rem', '300px').
17
- */
18
- @property({ type: String, attribute: 'sidebar-width' })
19
- sidebarWidth = '';
20
-
21
- /**
22
- * Maximum width for the right sidebar section (e.g., '16rem', '250px').
23
- */
24
- @property({ type: String, attribute: 'right-sidebar-width' })
25
- rightSidebarWidth = '';
26
-
27
- /**
28
- * Optional maximum width for the main content area (e.g., '48rem', '800px').
29
- */
30
- @property({ type: String, attribute: 'main-max-width' })
31
- mainMaxWidth = '';
32
-
33
- /**
34
- * When true, expands the main content area to fill 100% of the available width between sidebars.
35
- */
36
- @property({ type: Boolean, attribute: 'fill-main', reflect: true })
37
- fillMain = false;
38
-
39
- @state()
40
- private _mobileMenuOpen = false;
41
-
42
- private _touchStartX = 0;
43
- private _currentTranslateX = 0;
44
- private _isDragging = false;
45
-
46
- override updated(changedProperties: PropertyValues<this>) {
47
- super.updated(changedProperties);
48
-
49
- if (changedProperties.has('sidebarWidth')) {
50
- if (this.sidebarWidth) {
51
- this.style.setProperty('--layout-sidebar-width', this.sidebarWidth);
52
- } else {
53
- this.style.removeProperty('--layout-sidebar-width');
54
- }
55
- }
56
-
57
- if (changedProperties.has('rightSidebarWidth')) {
58
- if (this.rightSidebarWidth) {
59
- this.style.setProperty('--layout-right-sidebar-width', this.rightSidebarWidth);
60
- } else {
61
- this.style.removeProperty('--layout-right-sidebar-width');
62
- }
63
- }
64
-
65
- if (changedProperties.has('mainMaxWidth')) {
66
- if (this.mainMaxWidth) {
67
- this.style.setProperty('--layout-main-max-width', this.mainMaxWidth);
68
- } else {
69
- this.style.removeProperty('--layout-main-max-width');
70
- }
71
- }
72
- }
73
-
74
- private _toggleMobileMenu = () => {
75
- this._mobileMenuOpen = !this._mobileMenuOpen;
76
- };
77
-
78
- private _closeMobileMenu = () => {
79
- this._mobileMenuOpen = false;
80
- };
81
-
82
- // Only close if an actual navigation link/item was clicked
83
- private _handleNavClick = (e: MouseEvent) => {
84
- const path = e.composedPath();
85
- const clickedLink = path.some(node => {
86
- const el = node as HTMLElement;
87
- return el.tagName === 'A' || el.tagName === 'ST-VERTICAL-NAV-ITEM';
88
- });
89
-
90
- if (clickedLink) {
91
- this._closeMobileMenu();
92
- }
93
- };
94
-
95
- private _handleTouchStart = (e: TouchEvent) => {
96
- if (window.innerWidth >= 768) return;
97
- this._touchStartX = e.touches[0].clientX;
98
- this._isDragging = true;
99
- const sidebar = e.currentTarget as HTMLElement;
100
- sidebar.style.transition = 'none';
101
- };
102
-
103
- private _handleTouchMove = (e: TouchEvent) => {
104
- if (!this._isDragging || window.innerWidth >= 768) return;
105
- const currentX = e.touches[0].clientX;
106
- const diff = currentX - this._touchStartX;
107
- const sidebar = e.currentTarget as HTMLElement;
108
-
109
- if (diff < 0) {
110
- this._currentTranslateX = diff;
111
- sidebar.style.transform = `translateX(${diff}px)`;
112
- }
113
- };
114
-
115
- private _handleTouchEnd = (e: TouchEvent) => {
116
- if (!this._isDragging || window.innerWidth >= 768) return;
117
- this._isDragging = false;
118
- const sidebar = e.currentTarget as HTMLElement;
119
- sidebar.style.transition = '';
120
-
121
- if (this._currentTranslateX < -70) {
122
- this._closeMobileMenu();
123
- sidebar.style.transform = '';
124
- } else {
125
- sidebar.style.transform = '';
126
- }
127
- this._currentTranslateX = 0;
128
- };
129
-
130
- private _resizeListener = () => {
131
- if (window.innerWidth >= 768 && this._mobileMenuOpen) {
132
- this._mobileMenuOpen = false;
133
- }
134
- };
135
-
136
- connectedCallback() {
137
- super.connectedCallback();
138
- window.addEventListener('resize', this._resizeListener);
139
- }
140
-
141
- disconnectedCallback() {
142
- super.disconnectedCallback();
143
- window.removeEventListener('resize', this._resizeListener);
144
- }
145
-
146
- static styles = [
147
- resetStyles,
148
- css`
149
- :host {
150
- display: block;
151
- box-sizing: border-box;
152
- --layout-bg-color: var(--st-color-bg, #ffffff);
153
- --layout-surface-color: var(--st-color-surface, #f8fafc);
154
- --layout-border-color: var(--st-color-border, #e2e8f0);
155
- --layout-sidebar-width: 16rem;
156
- --layout-right-sidebar-width: 14rem;
157
- --layout-main-max-width: none;
158
- --layout-nav-height: 4rem;
159
- }
160
-
161
- *, *::before, *::after {
162
- box-sizing: inherit;
163
- }
164
-
165
- /* ==========================================================
166
- Mobile Hamburger Toggle Button Styles
167
- ========================================================== */
168
- .mobile-menu-btn {
169
- display: flex;
170
- align-items: center;
171
- justify-content: center;
172
- background: transparent;
173
- border: 1px solid var(--layout-border-color);
174
- border-radius: var(--st-radius-sm, 0.375rem);
175
- width: 2.25rem;
176
- height: 2.25rem;
177
- cursor: pointer;
178
- color: var(--st-color-text);
179
- margin-right: var(--st-spacing-sm);
180
- flex-shrink: 0;
181
- }
182
-
183
- .mobile-menu-btn svg {
184
- width: 1.25rem;
185
- height: 1.25rem;
186
- stroke: currentColor;
187
- stroke-width: 2;
188
- stroke-linecap: round;
189
- stroke-linejoin: round;
190
- fill: none;
191
- }
192
-
193
- @media (min-width: 768px) {
194
- .mobile-menu-btn {
195
- display: none;
196
- }
197
- }
198
-
199
- /* ==========================================================
200
- Mobile Backdrop Overlay
201
- ========================================================== */
202
- .mobile-backdrop {
203
- position: fixed;
204
- top: 0;
205
- left: 0;
206
- width: 100vw;
207
- height: 100vh;
208
- background-color: rgba(0, 0, 0, 0.5);
209
- backdrop-filter: blur(2px);
210
- z-index: 1000;
211
- display: none;
212
- }
213
-
214
- .mobile-backdrop.open {
215
- display: block;
216
- }
217
-
218
- @media (min-width: 768px) {
219
- .mobile-backdrop {
220
- display: none !important;
221
- }
222
- }
223
-
224
- /* ==========================================================
225
- Mobile Drawer Header (Only visible < 768px)
226
- ========================================================== */
227
- .mobile-drawer-header {
228
- display: flex;
229
- align-items: center;
230
- justify-content: space-between;
231
- padding: var(--st-spacing-md) var(--st-spacing-lg);
232
- border-bottom: 1px solid var(--layout-border-color);
233
- flex-shrink: 0;
234
- }
235
-
236
- .mobile-drawer-title {
237
- font-weight: var(--st-font-weight-bold);
238
- font-size: var(--st-font-size-sm);
239
- color: var(--st-color-text);
240
- text-transform: uppercase;
241
- letter-spacing: 0.05em;
242
- }
243
-
244
- .mobile-close-btn {
245
- background: transparent;
246
- border: none;
247
- cursor: pointer;
248
- color: var(--st-color-text-muted);
249
- padding: 0.25rem;
250
- display: flex;
251
- align-items: center;
252
- justify-content: center;
253
- border-radius: var(--st-radius-sm, 0.375rem);
254
- }
255
-
256
- .mobile-close-btn:hover {
257
- color: var(--st-color-text);
258
- background-color: var(--st-color-surface-muted, #f1f5f9);
259
- }
260
-
261
- .mobile-close-btn svg {
262
- width: 1.25rem;
263
- height: 1.25rem;
264
- stroke: currentColor;
265
- stroke-width: 2;
266
- stroke-linecap: round;
267
- stroke-linejoin: round;
268
- fill: none;
269
- }
270
-
271
- @media (min-width: 768px) {
272
- .mobile-drawer-header {
273
- display: none !important;
274
- }
275
- }
276
-
277
- /* ==========================================================
278
- 1. App Shell Layout
279
- ========================================================== */
280
- .layout-app-shell {
281
- display: flex;
282
- height: 100vh;
283
- overflow: hidden;
284
- background-color: var(--layout-surface-color);
285
- }
286
-
287
- .app-shell-sidebar {
288
- display: none;
289
- border-right: 1px solid var(--layout-border-color);
290
- background: var(--layout-bg-color);
291
- }
292
-
293
- .app-shell-sidebar-inner {
294
- flex: 1;
295
- overflow-y: auto;
296
- }
297
-
298
- .app-shell-content {
299
- display: flex;
300
- flex-direction: column;
301
- flex: 1;
302
- width: 100%;
303
- }
304
-
305
- .app-shell-nav {
306
- height: var(--layout-nav-height);
307
- border-bottom: 1px solid var(--layout-border-color);
308
- background: var(--layout-bg-color);
309
- display: flex;
310
- align-items: center;
311
- padding: 0 var(--st-spacing-md);
312
- }
313
-
314
- .app-shell-main {
315
- flex: 1;
316
- overflow-y: auto;
317
- padding: var(--st-spacing-lg);
318
- }
319
-
320
- .app-shell-footer {
321
- background: var(--layout-bg-color);
322
- border-top: 1px solid var(--layout-border-color);
323
- padding: var(--st-spacing-md);
324
- }
325
-
326
- @media (min-width: 768px) {
327
- .app-shell-sidebar {
328
- display: flex;
329
- flex-direction: column;
330
- width: 100%;
331
- max-width: var(--layout-sidebar-width);
332
- flex-shrink: 0;
333
- }
334
- }
335
-
336
- /* ==========================================================
337
- 2. Content Sidebar Layout
338
- ========================================================== */
339
- .layout-content-sidebar {
340
- min-height: 100vh;
341
- display: flex;
342
- flex-direction: column;
343
- background: var(--layout-bg-color);
344
- }
345
-
346
- .content-sidebar-nav {
347
- border-bottom: 1px solid var(--layout-border-color);
348
- background: var(--layout-bg-color);
349
- }
350
-
351
- .content-sidebar-container {
352
- flex: 1;
353
- width: 100%;
354
- max-width: 72rem;
355
- margin: 0 auto;
356
- padding: var(--st-spacing-xl) var(--st-spacing-md);
357
- display: flex;
358
- flex-direction: column;
359
- gap: var(--st-spacing-xl);
360
- }
361
-
362
- .content-sidebar-main {
363
- flex: 1;
364
- max-width: 48rem;
365
- }
366
-
367
- .content-sidebar-aside {
368
- width: 100%;
369
- flex-shrink: 0;
370
- }
371
-
372
- .content-sidebar-footer {
373
- margin-top: auto;
374
- border-top: 1px solid var(--layout-border-color);
375
- background: var(--layout-surface-color);
376
- }
377
-
378
- @media (min-width: 1024px) {
379
- .content-sidebar-container {
380
- flex-direction: row;
381
- gap: 3rem;
382
- }
383
- .content-sidebar-aside {
384
- width: 100%;
385
- max-width: var(--layout-sidebar-width);
386
- }
387
- .content-sidebar-aside-inner {
388
- position: sticky;
389
- top: var(--st-spacing-lg);
390
- }
391
- }
392
-
393
- /* ==========================================================
394
- 3. Single Column Layout
395
- ========================================================== */
396
- .layout-single-column {
397
- min-height: 100vh;
398
- display: flex;
399
- flex-direction: column;
400
- background: var(--layout-bg-color);
401
- }
402
-
403
- .single-column-nav {
404
- position: sticky;
405
- top: 0;
406
- z-index: 50;
407
- width: 100%;
408
- background: var(--layout-bg-color);
409
- border-bottom: 1px solid var(--layout-border-color);
410
- }
411
-
412
- .single-column-main {
413
- flex: 1;
414
- display: flex;
415
- flex-direction: column;
416
- }
417
-
418
- .single-column-footer {
419
- margin-top: auto;
420
- }
421
-
422
- /* ==========================================================
423
- 4. Three-Column Layout (Docs Shell)
424
- ========================================================== */
425
- .layout-three-column {
426
- min-height: 100vh;
427
- display: flex;
428
- flex-direction: column;
429
- background: var(--layout-bg-color);
430
- }
431
-
432
- .three-column-nav {
433
- position: sticky;
434
- top: 0;
435
- z-index: 50;
436
- height: var(--layout-nav-height);
437
- border-bottom: 1px solid var(--layout-border-color);
438
- background: var(--layout-bg-color);
439
- display: flex;
440
- align-items: center;
441
- padding: 0 var(--st-spacing-md);
442
- }
443
-
444
- .three-column-body {
445
- flex: 1;
446
- width: 100%;
447
- max-width: 96rem;
448
- margin: 0 auto;
449
- display: flex;
450
- }
451
-
452
- :host([fill-main]) .three-column-body {
453
- max-width: 100%;
454
- }
455
-
456
- .three-column-sidebar-left {
457
- display: none;
458
- width: 100%;
459
- max-width: var(--layout-sidebar-width);
460
- flex-shrink: 0;
461
- border-right: 1px solid var(--layout-border-color);
462
- background: var(--layout-bg-color);
463
- }
464
-
465
- .three-column-sidebar-left-inner {
466
- position: sticky;
467
- top: calc(var(--layout-nav-height) + 1rem);
468
- height: calc(100vh - var(--layout-nav-height) - 2rem);
469
- overflow-y: auto;
470
- padding: var(--st-spacing-md) 0;
471
- }
472
-
473
- .three-column-main {
474
- flex: 1;
475
- min-width: 0;
476
- padding: var(--st-spacing-xl) var(--st-spacing-lg);
477
- max-width: var(--layout-main-max-width);
478
- margin-left: auto;
479
- margin-right: auto;
480
- }
481
-
482
- .three-column-sidebar-right {
483
- display: none;
484
- width: 100%;
485
- max-width: var(--layout-right-sidebar-width);
486
- flex-shrink: 0;
487
- border-left: 1px solid var(--layout-border-color);
488
- background: var(--layout-bg-color);
489
- }
490
-
491
- .three-column-sidebar-right-inner {
492
- position: sticky;
493
- top: calc(var(--layout-nav-height) + 1rem);
494
- height: calc(100vh - var(--layout-nav-height) - 2rem);
495
- overflow-y: auto;
496
- padding: var(--st-spacing-md) var(--st-spacing-sm);
497
- }
498
-
499
- .three-column-footer {
500
- border-top: 1px solid var(--layout-border-color);
501
- background: var(--layout-surface-color);
502
- padding: var(--st-spacing-lg);
503
- }
504
-
505
- @media (min-width: 768px) {
506
- .three-column-sidebar-left {
507
- display: block;
508
- }
509
- }
510
-
511
- @media (min-width: 1280px) {
512
- .three-column-sidebar-right {
513
- display: block;
514
- }
515
- }
516
-
517
- /* ==========================================================
518
- Mobile Drawer Transformation & Touch Support (< 768px)
519
- ========================================================== */
520
- @media (max-width: 767px) {
521
- .three-column-sidebar-left,
522
- .app-shell-sidebar {
523
- position: fixed;
524
- top: 0;
525
- left: 0;
526
- width: 100%;
527
- max-width: var(--layout-sidebar-width);
528
- height: 100vh;
529
- background: var(--layout-bg-color);
530
- z-index: 1001;
531
- box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
532
- transform: translateX(-100%);
533
- transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
534
- display: flex !important;
535
- flex-direction: column;
536
- touch-action: pan-y;
537
- }
538
-
539
- .three-column-sidebar-left.open,
540
- .app-shell-sidebar.open {
541
- transform: translateX(0);
542
- }
543
-
544
- .three-column-sidebar-left-inner {
545
- position: static;
546
- height: auto;
547
- flex: 1;
548
- overflow-y: auto;
549
- }
550
- }
551
- `
552
- ];
553
-
554
- render() {
555
- const backdrop = html`
556
- <div
557
- class="mobile-backdrop ${this._mobileMenuOpen ? 'open' : ''}"
558
- @click=${this._closeMobileMenu}
559
- ></div>
560
- `;
561
-
562
- const hamburgerBtn = html`
563
- <button class="mobile-menu-btn" @click=${this._toggleMobileMenu} aria-label="Open Navigation Menu">
564
- <svg viewBox="0 0 24 24"><path d="M3 12h18M3 6h18M3 18h18"></path></svg>
565
- </button>
566
- `;
567
-
568
- const drawerHeader = html`
569
- <div class="mobile-drawer-header">
570
- <span class="mobile-drawer-title">Menu</span>
571
- <button class="mobile-close-btn" @click=${this._closeMobileMenu} aria-label="Close Navigation Menu">
572
- <svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"></path></svg>
573
- </button>
574
- </div>
575
- `;
576
-
577
- if (this.variant === 'app-shell') {
578
- return html`
579
- ${backdrop}
580
- <div class="layout-app-shell">
581
- <aside
582
- class="app-shell-sidebar ${this._mobileMenuOpen ? 'open' : ''}"
583
- @click=${this._handleNavClick}
584
- @touchstart=${this._handleTouchStart}
585
- @touchmove=${this._handleTouchMove}
586
- @touchend=${this._handleTouchEnd}
587
- >
588
- ${drawerHeader}
589
- <div class="app-shell-sidebar-inner">
590
- <slot name="sidebar"></slot>
591
- </div>
592
- </aside>
593
-
594
- <div class="app-shell-content">
595
- <header class="app-shell-nav">
596
- ${hamburgerBtn}
597
- <slot name="nav"></slot>
598
- </header>
599
-
600
- <main class="app-shell-main">
601
- <slot></slot>
602
- </main>
603
-
604
- <footer class="app-shell-footer">
605
- <slot name="footer"></slot>
606
- </footer>
607
- </div>
608
- </div>
609
- `;
610
- }
611
-
612
- if (this.variant === 'content-sidebar') {
613
- return html`
614
- <div class="layout-content-sidebar">
615
- <header class="content-sidebar-nav">
616
- <div style="display: flex; align-items: center; padding: 0 var(--st-spacing-md); height: var(--layout-nav-height);">
617
- <slot name="nav"></slot>
618
- </div>
619
- </header>
620
-
621
- <div class="content-sidebar-container">
622
- <main class="content-sidebar-main">
623
- <slot></slot>
624
- </main>
625
-
626
- <aside class="content-sidebar-aside">
627
- <div class="content-sidebar-aside-inner">
628
- <slot name="aside"></slot>
629
- </div>
630
- </aside>
631
- </div>
632
-
633
- <footer class="content-sidebar-footer">
634
- <slot name="footer"></slot>
635
- </footer>
636
- </div>
637
- `;
638
- }
639
-
640
- if (this.variant === 'three-column') {
641
- return html`
642
- ${backdrop}
643
- <div class="layout-three-column">
644
- <header class="three-column-nav">
645
- ${hamburgerBtn}
646
- <slot name="nav"></slot>
647
- </header>
648
-
649
- <div class="three-column-body">
650
- <aside
651
- class="three-column-sidebar-left ${this._mobileMenuOpen ? 'open' : ''}"
652
- @click=${this._handleNavClick}
653
- @touchstart=${this._handleTouchStart}
654
- @touchmove=${this._handleTouchMove}
655
- @touchend=${this._handleTouchEnd}
656
- >
657
- ${drawerHeader}
658
- <div class="three-column-sidebar-left-inner">
659
- <slot name="sidebar-left"></slot>
660
- </div>
661
- </aside>
662
-
663
- <main class="three-column-main">
664
- <slot></slot>
665
- </main>
666
-
667
- <aside class="three-column-sidebar-right">
668
- <div class="three-column-sidebar-right-inner">
669
- <slot name="sidebar-right"></slot>
670
- </div>
671
- </aside>
672
- </div>
673
-
674
- <footer class="three-column-footer">
675
- <slot name="footer"></slot>
676
- </footer>
677
- </div>
678
- `;
679
- }
680
-
681
- return html`
682
- <div class="layout-single-column">
683
- <header class="single-column-nav">
684
- <div style="display: flex; align-items: center; padding: 0 var(--st-spacing-md); height: var(--layout-nav-height);">
685
- <slot name="nav"></slot>
686
- </div>
687
- </header>
688
-
689
- <main class="single-column-main">
690
- <slot></slot>
691
- </main>
692
-
693
- <footer class="single-column-footer">
694
- <slot name="footer"></slot>
695
- </footer>
696
- </div>
697
- `;
698
- }
699
- }
700
-
701
- declare global {
702
- interface HTMLElementTagNameMap {
703
- 'st-page-layout': StPageLayout;
704
- }
1
+ import { LitElement, html, css, PropertyValues } from 'lit';
2
+ import { customElement, property, state } from 'lit/decorators.js';
3
+ import { resetStyles } from '../../styles/reset.js';
4
+
5
+ export type LayoutVariant = 'app-shell' | 'content-sidebar' | 'single-column' | 'three-column';
6
+
7
+ @customElement('st-page-layout')
8
+ export class StPageLayout extends LitElement {
9
+ /**
10
+ * Defines the structural layout variant to render.
11
+ */
12
+ @property({ type: String, reflect: true })
13
+ variant: LayoutVariant = 'single-column';
14
+
15
+ /**
16
+ * Maximum width for the left sidebar section (e.g., '20rem', '300px').
17
+ */
18
+ @property({ type: String, attribute: 'sidebar-width' })
19
+ sidebarWidth = '';
20
+
21
+ /**
22
+ * Maximum width for the right sidebar section (e.g., '16rem', '250px').
23
+ */
24
+ @property({ type: String, attribute: 'right-sidebar-width' })
25
+ rightSidebarWidth = '';
26
+
27
+ /**
28
+ * Optional maximum width for the main content area (e.g., '48rem', '800px').
29
+ */
30
+ @property({ type: String, attribute: 'main-max-width' })
31
+ mainMaxWidth = '';
32
+
33
+ /**
34
+ * When true, expands the main content area to fill 100% of the available width between sidebars.
35
+ */
36
+ @property({ type: Boolean, attribute: 'fill-main', reflect: true })
37
+ fillMain = false;
38
+
39
+ @state()
40
+ private _mobileMenuOpen = false;
41
+
42
+ private _touchStartX = 0;
43
+ private _currentTranslateX = 0;
44
+ private _isDragging = false;
45
+
46
+ override updated(changedProperties: PropertyValues<this>) {
47
+ super.updated(changedProperties);
48
+
49
+ if (changedProperties.has('sidebarWidth')) {
50
+ if (this.sidebarWidth) {
51
+ this.style.setProperty('--layout-sidebar-width', this.sidebarWidth);
52
+ } else {
53
+ this.style.removeProperty('--layout-sidebar-width');
54
+ }
55
+ }
56
+
57
+ if (changedProperties.has('rightSidebarWidth')) {
58
+ if (this.rightSidebarWidth) {
59
+ this.style.setProperty('--layout-right-sidebar-width', this.rightSidebarWidth);
60
+ } else {
61
+ this.style.removeProperty('--layout-right-sidebar-width');
62
+ }
63
+ }
64
+
65
+ if (changedProperties.has('mainMaxWidth')) {
66
+ if (this.mainMaxWidth) {
67
+ this.style.setProperty('--layout-main-max-width', this.mainMaxWidth);
68
+ } else {
69
+ this.style.removeProperty('--layout-main-max-width');
70
+ }
71
+ }
72
+ }
73
+
74
+ private _toggleMobileMenu = () => {
75
+ this._mobileMenuOpen = !this._mobileMenuOpen;
76
+ };
77
+
78
+ private _closeMobileMenu = () => {
79
+ this._mobileMenuOpen = false;
80
+ };
81
+
82
+ // Only close if an actual navigation link/item was clicked
83
+ private _handleNavClick = (e: MouseEvent) => {
84
+ const path = e.composedPath();
85
+ const clickedLink = path.some(node => {
86
+ const el = node as HTMLElement;
87
+ return el.tagName === 'A' || el.tagName === 'ST-VERTICAL-NAV-ITEM';
88
+ });
89
+
90
+ if (clickedLink) {
91
+ this._closeMobileMenu();
92
+ }
93
+ };
94
+
95
+ private _handleTouchStart = (e: TouchEvent) => {
96
+ if (window.innerWidth >= 768) return;
97
+ this._touchStartX = e.touches[0].clientX;
98
+ this._isDragging = true;
99
+ const sidebar = e.currentTarget as HTMLElement;
100
+ sidebar.style.transition = 'none';
101
+ };
102
+
103
+ private _handleTouchMove = (e: TouchEvent) => {
104
+ if (!this._isDragging || window.innerWidth >= 768) return;
105
+ const currentX = e.touches[0].clientX;
106
+ const diff = currentX - this._touchStartX;
107
+ const sidebar = e.currentTarget as HTMLElement;
108
+
109
+ if (diff < 0) {
110
+ this._currentTranslateX = diff;
111
+ sidebar.style.transform = `translateX(${diff}px)`;
112
+ }
113
+ };
114
+
115
+ private _handleTouchEnd = (e: TouchEvent) => {
116
+ if (!this._isDragging || window.innerWidth >= 768) return;
117
+ this._isDragging = false;
118
+ const sidebar = e.currentTarget as HTMLElement;
119
+ sidebar.style.transition = '';
120
+
121
+ if (this._currentTranslateX < -70) {
122
+ this._closeMobileMenu();
123
+ sidebar.style.transform = '';
124
+ } else {
125
+ sidebar.style.transform = '';
126
+ }
127
+ this._currentTranslateX = 0;
128
+ };
129
+
130
+ private _resizeListener = () => {
131
+ if (window.innerWidth >= 768 && this._mobileMenuOpen) {
132
+ this._mobileMenuOpen = false;
133
+ }
134
+ };
135
+
136
+ connectedCallback() {
137
+ super.connectedCallback();
138
+ window.addEventListener('resize', this._resizeListener);
139
+ }
140
+
141
+ disconnectedCallback() {
142
+ super.disconnectedCallback();
143
+ window.removeEventListener('resize', this._resizeListener);
144
+ }
145
+
146
+ static styles = [
147
+ resetStyles,
148
+ css`
149
+ :host {
150
+ display: block;
151
+ box-sizing: border-box;
152
+ --layout-bg-color: var(--st-color-bg, #ffffff);
153
+ --layout-surface-color: var(--st-color-surface, #f8fafc);
154
+ --layout-border-color: var(--st-color-border, #e2e8f0);
155
+ --layout-sidebar-width: 16rem;
156
+ --layout-right-sidebar-width: 14rem;
157
+ --layout-main-max-width: none;
158
+ --layout-nav-height: 4rem;
159
+ }
160
+
161
+ *, *::before, *::after {
162
+ box-sizing: inherit;
163
+ }
164
+
165
+ /* ==========================================================
166
+ Mobile Hamburger Toggle Button Styles
167
+ ========================================================== */
168
+ .mobile-menu-btn {
169
+ display: flex;
170
+ align-items: center;
171
+ justify-content: center;
172
+ background: transparent;
173
+ border: 1px solid var(--layout-border-color);
174
+ border-radius: var(--st-radius-sm, 0.375rem);
175
+ width: 2.25rem;
176
+ height: 2.25rem;
177
+ cursor: pointer;
178
+ color: var(--st-color-text);
179
+ margin-right: var(--st-spacing-sm);
180
+ flex-shrink: 0;
181
+ }
182
+
183
+ .mobile-menu-btn svg {
184
+ width: 1.25rem;
185
+ height: 1.25rem;
186
+ stroke: currentColor;
187
+ stroke-width: 2;
188
+ stroke-linecap: round;
189
+ stroke-linejoin: round;
190
+ fill: none;
191
+ }
192
+
193
+ @media (min-width: 768px) {
194
+ .mobile-menu-btn {
195
+ display: none;
196
+ }
197
+ }
198
+
199
+ /* ==========================================================
200
+ Mobile Backdrop Overlay
201
+ ========================================================== */
202
+ .mobile-backdrop {
203
+ position: fixed;
204
+ top: 0;
205
+ left: 0;
206
+ width: 100vw;
207
+ height: 100vh;
208
+ background-color: rgba(0, 0, 0, 0.5);
209
+ backdrop-filter: blur(2px);
210
+ z-index: 1000;
211
+ display: none;
212
+ }
213
+
214
+ .mobile-backdrop.open {
215
+ display: block;
216
+ }
217
+
218
+ @media (min-width: 768px) {
219
+ .mobile-backdrop {
220
+ display: none !important;
221
+ }
222
+ }
223
+
224
+ /* ==========================================================
225
+ Mobile Drawer Header (Only visible < 768px)
226
+ ========================================================== */
227
+ .mobile-drawer-header {
228
+ display: flex;
229
+ align-items: center;
230
+ justify-content: space-between;
231
+ padding: var(--st-spacing-md) var(--st-spacing-lg);
232
+ border-bottom: 1px solid var(--layout-border-color);
233
+ flex-shrink: 0;
234
+ }
235
+
236
+ .mobile-drawer-title {
237
+ font-weight: var(--st-font-weight-bold);
238
+ font-size: var(--st-font-size-sm);
239
+ color: var(--st-color-text);
240
+ text-transform: uppercase;
241
+ letter-spacing: 0.05em;
242
+ }
243
+
244
+ .mobile-close-btn {
245
+ background: transparent;
246
+ border: none;
247
+ cursor: pointer;
248
+ color: var(--st-color-text-muted);
249
+ padding: 0.25rem;
250
+ display: flex;
251
+ align-items: center;
252
+ justify-content: center;
253
+ border-radius: var(--st-radius-sm, 0.375rem);
254
+ }
255
+
256
+ .mobile-close-btn:hover {
257
+ color: var(--st-color-text);
258
+ background-color: var(--st-color-surface-muted, #f1f5f9);
259
+ }
260
+
261
+ .mobile-close-btn svg {
262
+ width: 1.25rem;
263
+ height: 1.25rem;
264
+ stroke: currentColor;
265
+ stroke-width: 2;
266
+ stroke-linecap: round;
267
+ stroke-linejoin: round;
268
+ fill: none;
269
+ }
270
+
271
+ @media (min-width: 768px) {
272
+ .mobile-drawer-header {
273
+ display: none !important;
274
+ }
275
+ }
276
+
277
+ /* ==========================================================
278
+ 1. App Shell Layout
279
+ ========================================================== */
280
+ .layout-app-shell {
281
+ display: flex;
282
+ height: 100vh;
283
+ overflow: hidden;
284
+ background-color: var(--layout-surface-color);
285
+ }
286
+
287
+ .app-shell-sidebar {
288
+ display: none;
289
+ border-right: 1px solid var(--layout-border-color);
290
+ background: var(--layout-bg-color);
291
+ }
292
+
293
+ .app-shell-sidebar-inner {
294
+ flex: 1;
295
+ overflow-y: auto;
296
+ }
297
+
298
+ .app-shell-content {
299
+ display: flex;
300
+ flex-direction: column;
301
+ flex: 1;
302
+ width: 100%;
303
+ }
304
+
305
+ .app-shell-nav {
306
+ height: var(--layout-nav-height);
307
+ border-bottom: 1px solid var(--layout-border-color);
308
+ background: var(--layout-bg-color);
309
+ display: flex;
310
+ align-items: center;
311
+ padding: 0 var(--st-spacing-md);
312
+ }
313
+
314
+ .app-shell-main {
315
+ flex: 1;
316
+ overflow-y: auto;
317
+ padding: var(--st-spacing-lg);
318
+ }
319
+
320
+ .app-shell-footer {
321
+ background: var(--layout-bg-color);
322
+ border-top: 1px solid var(--layout-border-color);
323
+ padding: var(--st-spacing-md);
324
+ }
325
+
326
+ @media (min-width: 768px) {
327
+ .app-shell-sidebar {
328
+ display: flex;
329
+ flex-direction: column;
330
+ width: 100%;
331
+ max-width: var(--layout-sidebar-width);
332
+ flex-shrink: 0;
333
+ }
334
+ }
335
+
336
+ /* ==========================================================
337
+ 2. Content Sidebar Layout
338
+ ========================================================== */
339
+ .layout-content-sidebar {
340
+ min-height: 100vh;
341
+ display: flex;
342
+ flex-direction: column;
343
+ background: var(--layout-bg-color);
344
+ }
345
+
346
+ .content-sidebar-nav {
347
+ border-bottom: 1px solid var(--layout-border-color);
348
+ background: var(--layout-bg-color);
349
+ }
350
+
351
+ .content-sidebar-container {
352
+ flex: 1;
353
+ width: 100%;
354
+ max-width: 72rem;
355
+ margin: 0 auto;
356
+ padding: var(--st-spacing-xl) var(--st-spacing-md);
357
+ display: flex;
358
+ flex-direction: column;
359
+ gap: var(--st-spacing-xl);
360
+ }
361
+
362
+ .content-sidebar-main {
363
+ flex: 1;
364
+ max-width: 48rem;
365
+ }
366
+
367
+ .content-sidebar-aside {
368
+ width: 100%;
369
+ flex-shrink: 0;
370
+ }
371
+
372
+ .content-sidebar-footer {
373
+ margin-top: auto;
374
+ border-top: 1px solid var(--layout-border-color);
375
+ background: var(--layout-surface-color);
376
+ }
377
+
378
+ @media (min-width: 1024px) {
379
+ .content-sidebar-container {
380
+ flex-direction: row;
381
+ gap: 3rem;
382
+ }
383
+ .content-sidebar-aside {
384
+ width: 100%;
385
+ max-width: var(--layout-sidebar-width);
386
+ }
387
+ .content-sidebar-aside-inner {
388
+ position: sticky;
389
+ top: var(--st-spacing-lg);
390
+ }
391
+ }
392
+
393
+ /* ==========================================================
394
+ 3. Single Column Layout
395
+ ========================================================== */
396
+ .layout-single-column {
397
+ min-height: 100vh;
398
+ display: flex;
399
+ flex-direction: column;
400
+ background: var(--layout-bg-color);
401
+ }
402
+
403
+ .single-column-nav {
404
+ position: sticky;
405
+ top: 0;
406
+ z-index: 50;
407
+ width: 100%;
408
+ background: var(--layout-bg-color);
409
+ border-bottom: 1px solid var(--layout-border-color);
410
+ }
411
+
412
+ .single-column-main {
413
+ flex: 1;
414
+ display: flex;
415
+ flex-direction: column;
416
+ }
417
+
418
+ .single-column-footer {
419
+ margin-top: auto;
420
+ }
421
+
422
+ /* ==========================================================
423
+ 4. Three-Column Layout (Docs Shell)
424
+ ========================================================== */
425
+ .layout-three-column {
426
+ min-height: 100vh;
427
+ display: flex;
428
+ flex-direction: column;
429
+ background: var(--layout-bg-color);
430
+ }
431
+
432
+ .three-column-nav {
433
+ position: sticky;
434
+ top: 0;
435
+ z-index: 50;
436
+ height: var(--layout-nav-height);
437
+ border-bottom: 1px solid var(--layout-border-color);
438
+ background: var(--layout-bg-color);
439
+ display: flex;
440
+ align-items: center;
441
+ padding: 0 var(--st-spacing-md);
442
+ }
443
+
444
+ .three-column-body {
445
+ flex: 1;
446
+ width: 100%;
447
+ max-width: 96rem;
448
+ margin: 0 auto;
449
+ display: flex;
450
+ }
451
+
452
+ :host([fill-main]) .three-column-body {
453
+ max-width: 100%;
454
+ }
455
+
456
+ .three-column-sidebar-left {
457
+ display: none;
458
+ width: 100%;
459
+ max-width: var(--layout-sidebar-width);
460
+ flex-shrink: 0;
461
+ border-right: 1px solid var(--layout-border-color);
462
+ background: var(--layout-bg-color);
463
+ }
464
+
465
+ .three-column-sidebar-left-inner {
466
+ position: sticky;
467
+ top: calc(var(--layout-nav-height) + 1rem);
468
+ height: calc(100vh - var(--layout-nav-height) - 2rem);
469
+ overflow-y: auto;
470
+ padding: var(--st-spacing-md) 0;
471
+ }
472
+
473
+ .three-column-main {
474
+ flex: 1;
475
+ min-width: 0;
476
+ padding: var(--st-spacing-xl) var(--st-spacing-lg);
477
+ max-width: var(--layout-main-max-width);
478
+ margin-left: auto;
479
+ margin-right: auto;
480
+ }
481
+
482
+ .three-column-sidebar-right {
483
+ display: none;
484
+ width: 100%;
485
+ max-width: var(--layout-right-sidebar-width);
486
+ flex-shrink: 0;
487
+ border-left: 1px solid var(--layout-border-color);
488
+ background: var(--layout-bg-color);
489
+ }
490
+
491
+ .three-column-sidebar-right-inner {
492
+ position: sticky;
493
+ top: calc(var(--layout-nav-height) + 1rem);
494
+ height: calc(100vh - var(--layout-nav-height) - 2rem);
495
+ overflow-y: auto;
496
+ padding: var(--st-spacing-md) var(--st-spacing-sm);
497
+ }
498
+
499
+ .three-column-footer {
500
+ border-top: 1px solid var(--layout-border-color);
501
+ background: var(--layout-surface-color);
502
+ padding: var(--st-spacing-lg);
503
+ }
504
+
505
+ @media (min-width: 768px) {
506
+ .three-column-sidebar-left {
507
+ display: block;
508
+ }
509
+ }
510
+
511
+ @media (min-width: 1280px) {
512
+ .three-column-sidebar-right {
513
+ display: block;
514
+ }
515
+ }
516
+
517
+ /* ==========================================================
518
+ Mobile Drawer Transformation & Touch Support (< 768px)
519
+ ========================================================== */
520
+ @media (max-width: 767px) {
521
+ .three-column-sidebar-left,
522
+ .app-shell-sidebar {
523
+ position: fixed;
524
+ top: 0;
525
+ left: 0;
526
+ width: 100%;
527
+ max-width: var(--layout-sidebar-width);
528
+ height: 100vh;
529
+ background: var(--layout-bg-color);
530
+ z-index: 1001;
531
+ box-shadow: 4px 0 24px rgba(0, 0, 0, 0.15);
532
+ transform: translateX(-100%);
533
+ transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
534
+ display: flex !important;
535
+ flex-direction: column;
536
+ touch-action: pan-y;
537
+ }
538
+
539
+ .three-column-sidebar-left.open,
540
+ .app-shell-sidebar.open {
541
+ transform: translateX(0);
542
+ }
543
+
544
+ .three-column-sidebar-left-inner {
545
+ position: static;
546
+ height: auto;
547
+ flex: 1;
548
+ overflow-y: auto;
549
+ }
550
+ }
551
+ `
552
+ ];
553
+
554
+ render() {
555
+ const backdrop = html`
556
+ <div
557
+ class="mobile-backdrop ${this._mobileMenuOpen ? 'open' : ''}"
558
+ @click=${this._closeMobileMenu}
559
+ ></div>
560
+ `;
561
+
562
+ const hamburgerBtn = html`
563
+ <button class="mobile-menu-btn" @click=${this._toggleMobileMenu} aria-label="Open Navigation Menu">
564
+ <svg viewBox="0 0 24 24"><path d="M3 12h18M3 6h18M3 18h18"></path></svg>
565
+ </button>
566
+ `;
567
+
568
+ const drawerHeader = html`
569
+ <div class="mobile-drawer-header">
570
+ <span class="mobile-drawer-title">Menu</span>
571
+ <button class="mobile-close-btn" @click=${this._closeMobileMenu} aria-label="Close Navigation Menu">
572
+ <svg viewBox="0 0 24 24"><path d="M18 6L6 18M6 6l12 12"></path></svg>
573
+ </button>
574
+ </div>
575
+ `;
576
+
577
+ if (this.variant === 'app-shell') {
578
+ return html`
579
+ ${backdrop}
580
+ <div class="layout-app-shell">
581
+ <aside
582
+ class="app-shell-sidebar ${this._mobileMenuOpen ? 'open' : ''}"
583
+ @click=${this._handleNavClick}
584
+ @touchstart=${this._handleTouchStart}
585
+ @touchmove=${this._handleTouchMove}
586
+ @touchend=${this._handleTouchEnd}
587
+ >
588
+ ${drawerHeader}
589
+ <div class="app-shell-sidebar-inner">
590
+ <slot name="sidebar"></slot>
591
+ </div>
592
+ </aside>
593
+
594
+ <div class="app-shell-content">
595
+ <header class="app-shell-nav">
596
+ ${hamburgerBtn}
597
+ <slot name="nav"></slot>
598
+ </header>
599
+
600
+ <main class="app-shell-main">
601
+ <slot></slot>
602
+ </main>
603
+
604
+ <footer class="app-shell-footer">
605
+ <slot name="footer"></slot>
606
+ </footer>
607
+ </div>
608
+ </div>
609
+ `;
610
+ }
611
+
612
+ if (this.variant === 'content-sidebar') {
613
+ return html`
614
+ <div class="layout-content-sidebar">
615
+ <header class="content-sidebar-nav">
616
+ <div style="display: flex; align-items: center; padding: 0 var(--st-spacing-md); height: var(--layout-nav-height);">
617
+ <slot name="nav"></slot>
618
+ </div>
619
+ </header>
620
+
621
+ <div class="content-sidebar-container">
622
+ <main class="content-sidebar-main">
623
+ <slot></slot>
624
+ </main>
625
+
626
+ <aside class="content-sidebar-aside">
627
+ <div class="content-sidebar-aside-inner">
628
+ <slot name="aside"></slot>
629
+ </div>
630
+ </aside>
631
+ </div>
632
+
633
+ <footer class="content-sidebar-footer">
634
+ <slot name="footer"></slot>
635
+ </footer>
636
+ </div>
637
+ `;
638
+ }
639
+
640
+ if (this.variant === 'three-column') {
641
+ return html`
642
+ ${backdrop}
643
+ <div class="layout-three-column">
644
+ <header class="three-column-nav">
645
+ ${hamburgerBtn}
646
+ <slot name="nav"></slot>
647
+ </header>
648
+
649
+ <div class="three-column-body">
650
+ <aside
651
+ class="three-column-sidebar-left ${this._mobileMenuOpen ? 'open' : ''}"
652
+ @click=${this._handleNavClick}
653
+ @touchstart=${this._handleTouchStart}
654
+ @touchmove=${this._handleTouchMove}
655
+ @touchend=${this._handleTouchEnd}
656
+ >
657
+ ${drawerHeader}
658
+ <div class="three-column-sidebar-left-inner">
659
+ <slot name="sidebar-left"></slot>
660
+ </div>
661
+ </aside>
662
+
663
+ <main class="three-column-main">
664
+ <slot></slot>
665
+ </main>
666
+
667
+ <aside class="three-column-sidebar-right">
668
+ <div class="three-column-sidebar-right-inner">
669
+ <slot name="sidebar-right"></slot>
670
+ </div>
671
+ </aside>
672
+ </div>
673
+
674
+ <footer class="three-column-footer">
675
+ <slot name="footer"></slot>
676
+ </footer>
677
+ </div>
678
+ `;
679
+ }
680
+
681
+ return html`
682
+ <div class="layout-single-column">
683
+ <header class="single-column-nav">
684
+ <div style="display: flex; align-items: center; padding: 0 var(--st-spacing-md); height: var(--layout-nav-height);">
685
+ <slot name="nav"></slot>
686
+ </div>
687
+ </header>
688
+
689
+ <main class="single-column-main">
690
+ <slot></slot>
691
+ </main>
692
+
693
+ <footer class="single-column-footer">
694
+ <slot name="footer"></slot>
695
+ </footer>
696
+ </div>
697
+ `;
698
+ }
699
+ }
700
+
701
+ declare global {
702
+ interface HTMLElementTagNameMap {
703
+ 'st-page-layout': StPageLayout;
704
+ }
705
705
  }