mtrl 0.2.7 → 0.2.9

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 (190) hide show
  1. package/index.ts +2 -0
  2. package/package.json +14 -3
  3. package/src/components/badge/api.ts +23 -14
  4. package/src/components/badge/badge.ts +2 -2
  5. package/src/components/badge/config.ts +10 -11
  6. package/src/components/badge/features.ts +15 -10
  7. package/src/components/badge/index.ts +27 -2
  8. package/src/components/badge/types.ts +28 -8
  9. package/src/components/bottom-app-bar/bottom-app-bar.ts +2 -44
  10. package/src/components/bottom-app-bar/config.ts +1 -45
  11. package/src/components/bottom-app-bar/index.ts +7 -1
  12. package/src/components/bottom-app-bar/types.ts +7 -1
  13. package/src/components/button/button.ts +0 -1
  14. package/src/components/button/config.ts +1 -2
  15. package/src/components/button/index.ts +10 -2
  16. package/src/components/button/types.ts +14 -2
  17. package/src/components/card/config.ts +17 -9
  18. package/src/components/card/content.ts +8 -10
  19. package/src/components/card/features.ts +4 -6
  20. package/src/components/card/index.ts +29 -2
  21. package/src/components/card/types.ts +6 -23
  22. package/src/components/checkbox/config.ts +3 -4
  23. package/src/components/checkbox/index.ts +1 -2
  24. package/src/components/checkbox/types.ts +12 -3
  25. package/src/components/chip/api.ts +170 -221
  26. package/src/components/chip/chip.ts +34 -302
  27. package/src/components/chip/config.ts +1 -2
  28. package/src/components/chip/index.ts +10 -2
  29. package/src/components/chip/types.ts +224 -35
  30. package/src/components/datepicker/api.ts +18 -25
  31. package/src/components/datepicker/config.ts +9 -12
  32. package/src/components/datepicker/datepicker.ts +7 -12
  33. package/src/components/datepicker/index.ts +10 -7
  34. package/src/components/datepicker/render.ts +16 -18
  35. package/src/components/datepicker/types.ts +164 -35
  36. package/src/components/datepicker/utils.ts +1 -2
  37. package/src/components/dialog/api.ts +7 -8
  38. package/src/components/dialog/config.ts +3 -4
  39. package/src/components/dialog/features.ts +56 -22
  40. package/src/components/dialog/index.ts +38 -8
  41. package/src/components/dialog/types.ts +33 -10
  42. package/src/components/divider/index.ts +5 -1
  43. package/src/components/extended-fab/config.ts +6 -2
  44. package/src/components/extended-fab/index.ts +7 -2
  45. package/src/components/extended-fab/types.ts +21 -4
  46. package/src/components/fab/config.ts +3 -4
  47. package/src/components/fab/fab.ts +1 -1
  48. package/src/components/fab/index.ts +7 -2
  49. package/src/components/fab/types.ts +21 -4
  50. package/src/components/list/config.ts +4 -5
  51. package/src/components/list/features.ts +6 -7
  52. package/src/components/list/index.ts +7 -9
  53. package/src/components/list/list-item.ts +12 -13
  54. package/src/components/list/types.ts +50 -5
  55. package/src/components/list/utils.ts +30 -3
  56. package/src/components/menu/features/items-manager.ts +9 -9
  57. package/src/components/menu/features/positioning.ts +7 -7
  58. package/src/components/menu/features/visibility.ts +7 -7
  59. package/src/components/menu/index.ts +7 -9
  60. package/src/components/menu/menu-item.ts +6 -6
  61. package/src/components/menu/menu.ts +22 -0
  62. package/src/components/menu/types.ts +29 -10
  63. package/src/components/menu/utils.ts +67 -0
  64. package/src/components/navigation/api.ts +131 -96
  65. package/src/components/navigation/config.ts +22 -10
  66. package/src/components/navigation/features/controller.ts +273 -0
  67. package/src/components/navigation/features/items.ts +160 -87
  68. package/src/components/navigation/index.ts +0 -6
  69. package/src/components/navigation/nav-item.ts +12 -24
  70. package/src/components/navigation/navigation.ts +21 -8
  71. package/src/components/navigation/system-types.ts +124 -0
  72. package/src/components/navigation/system.ts +776 -0
  73. package/src/components/navigation/types.ts +228 -203
  74. package/src/components/progress/api.ts +2 -3
  75. package/src/components/progress/config.ts +2 -3
  76. package/src/components/progress/index.ts +0 -1
  77. package/src/components/progress/progress.ts +1 -2
  78. package/src/components/progress/types.ts +186 -33
  79. package/src/components/radios/config.ts +1 -1
  80. package/src/components/radios/index.ts +0 -1
  81. package/src/components/radios/types.ts +0 -7
  82. package/src/components/search/config.ts +1 -2
  83. package/src/components/search/features/search.ts +14 -15
  84. package/src/components/search/features/states.ts +5 -1
  85. package/src/components/search/features/structure.ts +3 -4
  86. package/src/components/search/index.ts +0 -3
  87. package/src/components/search/types.ts +18 -6
  88. package/src/components/segmented-button/config.ts +20 -7
  89. package/src/components/segmented-button/segment.ts +6 -7
  90. package/src/components/segmented-button/segmented-button.ts +4 -5
  91. package/src/components/segmented-button/types.ts +37 -2
  92. package/src/components/slider/config.ts +20 -2
  93. package/src/components/slider/features/controller.ts +761 -0
  94. package/src/components/slider/features/handlers.ts +18 -15
  95. package/src/components/slider/features/index.ts +3 -2
  96. package/src/components/slider/features/range.ts +104 -0
  97. package/src/components/slider/slider.ts +34 -14
  98. package/src/components/slider/structure.ts +152 -0
  99. package/src/components/slider/types.ts +34 -8
  100. package/src/components/snackbar/config.ts +2 -3
  101. package/src/components/snackbar/constants.ts +0 -32
  102. package/src/components/snackbar/index.ts +0 -1
  103. package/src/components/snackbar/position.ts +9 -1
  104. package/src/components/snackbar/types.ts +122 -46
  105. package/src/components/switch/config.ts +2 -3
  106. package/src/components/switch/index.ts +0 -1
  107. package/src/components/switch/types.ts +3 -2
  108. package/src/components/tabs/config.ts +3 -4
  109. package/src/components/tabs/index.ts +0 -15
  110. package/src/components/tabs/tab-api.ts +12 -4
  111. package/src/components/tabs/tab.ts +18 -6
  112. package/src/components/tabs/types.ts +13 -3
  113. package/src/components/textfield/api.ts +53 -0
  114. package/src/components/textfield/config.ts +2 -3
  115. package/src/components/textfield/features.ts +322 -0
  116. package/src/components/textfield/index.ts +0 -1
  117. package/src/components/textfield/textfield.ts +8 -0
  118. package/src/components/textfield/types.ts +29 -6
  119. package/src/components/timepicker/api.ts +1 -1
  120. package/src/components/timepicker/clockdial.ts +2 -5
  121. package/src/components/timepicker/config.ts +102 -4
  122. package/src/components/timepicker/index.ts +1 -6
  123. package/src/components/timepicker/render.ts +1 -1
  124. package/src/components/timepicker/timepicker.ts +1 -1
  125. package/src/components/tooltip/api.ts +1 -1
  126. package/src/components/tooltip/config.ts +27 -6
  127. package/src/components/tooltip/index.ts +0 -1
  128. package/src/components/tooltip/types.ts +13 -3
  129. package/src/core/compose/features/textinput.ts +15 -2
  130. package/src/core/compose/features/textlabel.ts +0 -3
  131. package/src/core/composition/features/dom.ts +33 -0
  132. package/src/core/composition/features/icon.ts +131 -0
  133. package/src/core/composition/features/index.ts +11 -0
  134. package/src/core/composition/features/label.ts +156 -0
  135. package/src/core/composition/features/structure.ts +22 -0
  136. package/src/core/composition/index.ts +26 -0
  137. package/src/core/index.ts +1 -1
  138. package/src/core/structure.ts +288 -0
  139. package/src/index.ts +1 -0
  140. package/src/styles/components/_navigation-mobile.scss +244 -0
  141. package/src/styles/components/_navigation-system.scss +151 -0
  142. package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +1 -1
  143. package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +314 -72
  144. package/src/styles/main.scss +98 -49
  145. package/src/components/badge/constants.ts +0 -40
  146. package/src/components/button/constants.ts +0 -11
  147. package/src/components/card/constants.ts +0 -84
  148. package/src/components/datepicker/constants.ts +0 -98
  149. package/src/components/dialog/constants.ts +0 -32
  150. package/src/components/extended-fab/constants.ts +0 -36
  151. package/src/components/fab/constants.ts +0 -41
  152. package/src/components/menu/constants.ts +0 -154
  153. package/src/components/navigation/constants.ts +0 -200
  154. package/src/components/progress/constants.ts +0 -29
  155. package/src/components/search/constants.ts +0 -21
  156. package/src/components/segmented-button/constants.ts +0 -42
  157. package/src/components/slider/features/slider.ts +0 -318
  158. package/src/components/slider/features/structure.ts +0 -181
  159. package/src/components/slider/features/ui.ts +0 -388
  160. package/src/components/switch/constants.ts +0 -80
  161. package/src/components/tabs/constants.ts +0 -89
  162. package/src/components/textfield/constants.ts +0 -100
  163. package/src/components/timepicker/constants.ts +0 -138
  164. /package/src/{components/badge/_styles.scss → styles/components/_badge.scss} +0 -0
  165. /package/src/{components/bottom-app-bar/_styles.scss → styles/components/_bottom-app-bar.scss} +0 -0
  166. /package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -0
  167. /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
  168. /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
  169. /package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -0
  170. /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
  171. /package/src/{components/datepicker/_styles.scss → styles/components/_datepicker.scss} +0 -0
  172. /package/src/{components/dialog/_styles.scss → styles/components/_dialog.scss} +0 -0
  173. /package/src/{components/divider/_styles.scss → styles/components/_divider.scss} +0 -0
  174. /package/src/{components/extended-fab/_styles.scss → styles/components/_extended-fab.scss} +0 -0
  175. /package/src/{components/fab/_styles.scss → styles/components/_fab.scss} +0 -0
  176. /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
  177. /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
  178. /package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +0 -0
  179. /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
  180. /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
  181. /package/src/{components/search/_styles.scss → styles/components/_search.scss} +0 -0
  182. /package/src/{components/segmented-button/_styles.scss → styles/components/_segmented-button.scss} +0 -0
  183. /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
  184. /package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +0 -0
  185. /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
  186. /package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -0
  187. /package/src/{components/timepicker/_styles.scss → styles/components/_timepicker.scss} +0 -0
  188. /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
  189. /package/src/{components/top-app-bar/_styles.scss → styles/components/_top-app-bar.scss} +0 -0
  190. /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
@@ -0,0 +1,288 @@
1
+ // src/core/structure.ts
2
+ import { createElement } from './dom/create';
3
+
4
+ /**
5
+ * Type definitions for structure creation
6
+ */
7
+ export interface ComponentLike {
8
+ element: HTMLElement;
9
+ destroy?: () => void;
10
+ [key: string]: any;
11
+ }
12
+
13
+ export interface ElementDefinition {
14
+ name?: string;
15
+ creator?: Function;
16
+ options?: Record<string, any>;
17
+ children?: Record<string, ElementDefinition>;
18
+ }
19
+
20
+ export interface StructureDefinition {
21
+ element?: ElementDefinition;
22
+ [key: string]: ElementDefinition | any;
23
+ }
24
+
25
+ /**
26
+ * Structure result interface with separated structure and utility functions
27
+ */
28
+ export interface StructureResult {
29
+ // The raw structure object with all components
30
+ structure: Record<string, any>;
31
+
32
+ // Reference to the root element for convenience
33
+ element: HTMLElement | ComponentLike;
34
+
35
+ // Utility functions
36
+ get: (name: string) => any;
37
+ getAll: () => Record<string, any>;
38
+ destroy: () => void;
39
+ }
40
+
41
+ /**
42
+ * Checks if a value is a component object (has an element property)
43
+ * Uses a faster property check before the instanceof check
44
+ * @param value Value to check
45
+ * @returns True if the value is a component-like object
46
+ */
47
+ function isComponentLike(value: any): value is ComponentLike {
48
+ return value &&
49
+ typeof value === 'object' &&
50
+ 'element' in value &&
51
+ value.element instanceof HTMLElement;
52
+ }
53
+
54
+ /**
55
+ * Creates a document fragment for faster DOM operations when appending multiple children
56
+ * @returns DocumentFragment
57
+ */
58
+ function createFragment(): DocumentFragment {
59
+ return document.createDocumentFragment();
60
+ }
61
+
62
+ /**
63
+ * Creates a DOM or component structure based on a structure definition
64
+ * @param definition Structure definition object
65
+ * @param parentElement Optional parent element to attach structure to
66
+ * @returns Object containing the structure and utility functions
67
+ */
68
+ export function createStructure(
69
+ definition: StructureDefinition,
70
+ parentElement: HTMLElement | null = null
71
+ ): StructureResult {
72
+ // Use object literal instead of empty object for faster property access
73
+ const structure: Record<string, any> = Object.create(null);
74
+
75
+ // Special case for root component creation
76
+ if (definition.element && !parentElement) {
77
+ const elementDef = definition.element;
78
+ const createElementFn = elementDef.creator || createElement;
79
+ const rootComponent = createElementFn(elementDef.options);
80
+ const rootElement = isComponentLike(rootComponent) ? rootComponent.element : rootComponent;
81
+
82
+ structure.element = rootComponent;
83
+
84
+ if (elementDef.name) {
85
+ structure[elementDef.name] = rootComponent;
86
+ }
87
+
88
+ if (elementDef.children) {
89
+ // Use fragment for better performance when appending multiple children
90
+ const fragment = createFragment();
91
+ let childKeys = Object.keys(elementDef.children);
92
+
93
+ // Process all children first and collect their structures
94
+ const childStructures = new Array(childKeys.length);
95
+
96
+ for (let i = 0; i < childKeys.length; i++) {
97
+ const key = childKeys[i];
98
+ const childDef = elementDef.children[key];
99
+
100
+ // Create child structure and attach to fragment temporarily
101
+ const childResult = createStructure({ [key]: childDef }, fragment);
102
+ childStructures[i] = childResult.structure;
103
+ }
104
+
105
+ // Append fragment to root element (single DOM operation)
106
+ rootElement.appendChild(fragment);
107
+
108
+ // Now merge all child structures into the main structure
109
+ for (let i = 0; i < childStructures.length; i++) {
110
+ Object.assign(structure, childStructures[i]);
111
+ }
112
+ }
113
+
114
+ // Create and return the result object with utility functions
115
+ return createStructureResult(structure);
116
+ }
117
+
118
+ // Use fragment if we have multiple elements to append to the parent
119
+ const fragment = parentElement ? createFragment() : null;
120
+ const keys = Object.keys(definition);
121
+ const keyLength = keys.length;
122
+
123
+ // Pre-allocate arrays for better performance
124
+ const elements = new Array(keyLength);
125
+ const childStructuresToMerge = [];
126
+
127
+ // First pass: create all elements
128
+ for (let i = 0; i < keyLength; i++) {
129
+ const key = keys[i];
130
+ const def = definition[key];
131
+
132
+ // Skip if no definition
133
+ if (!def) {
134
+ elements[i] = null;
135
+ continue;
136
+ }
137
+
138
+ // Create the element
139
+ const createElementFn = def.creator || createElement;
140
+ const created = createElementFn(def.options);
141
+ elements[i] = created;
142
+
143
+ // Add to structure
144
+ structure[key] = created;
145
+
146
+ // Add element to structure with its name if different from key
147
+ if (def.name && def.name !== key) {
148
+ structure[def.name] = created;
149
+ }
150
+ }
151
+
152
+ // Second pass: handle children and append to parent
153
+ for (let i = 0; i < keyLength; i++) {
154
+ const key = keys[i];
155
+ const def = definition[key];
156
+ const created = elements[i];
157
+
158
+ if (!created || !def) continue;
159
+
160
+ // Get the actual DOM element
161
+ const element = isComponentLike(created) ? created.element : created;
162
+
163
+ // Append to fragment
164
+ if (fragment) {
165
+ fragment.appendChild(element);
166
+ }
167
+
168
+ // Process children recursively
169
+ if (def.children) {
170
+ const childResult = createStructure(def.children, element);
171
+ childStructuresToMerge.push(childResult.structure);
172
+ }
173
+ }
174
+
175
+ // Append fragment to parent (single DOM operation)
176
+ if (parentElement && fragment) {
177
+ parentElement.appendChild(fragment);
178
+ }
179
+
180
+ // Merge all child structures at once
181
+ for (let i = 0; i < childStructuresToMerge.length; i++) {
182
+ Object.assign(structure, childStructuresToMerge[i]);
183
+ }
184
+
185
+ // Create and return the result object with utility functions
186
+ return createStructureResult(structure);
187
+ }
188
+
189
+ /**
190
+ * Creates a result object with the structure and utility functions
191
+ * @param structure The raw structure object
192
+ * @returns Result object with structure and utility functions
193
+ */
194
+ function createStructureResult(structure: Record<string, any>): StructureResult {
195
+ return {
196
+ // Raw structure object
197
+ structure,
198
+
199
+ // Root element reference for convenience
200
+ element: structure.element,
201
+
202
+ /**
203
+ * Gets a component by name
204
+ * @param name Component name
205
+ * @returns Component if found, null otherwise
206
+ */
207
+ get: (name: string): any => {
208
+ return structure[name] || null;
209
+ },
210
+
211
+ /**
212
+ * Gets all components in a flattened map
213
+ * @returns Object with all components
214
+ */
215
+ getAll: (): Record<string, any> => {
216
+ return flattenStructure(structure);
217
+ },
218
+
219
+ /**
220
+ * Destroys the structure, cleaning up all components
221
+ */
222
+ destroy: (): void => {
223
+ // Clean up the root element if it exists
224
+ if (structure.element) {
225
+ // If element is a component with a destroy method, call it
226
+ if (isComponentLike(structure.element) && typeof structure.element.destroy === 'function') {
227
+ structure.element.destroy();
228
+ }
229
+ // Otherwise, if it's a DOM element or component, remove it from the DOM
230
+ else if (isComponentLike(structure.element) && structure.element.element.parentNode) {
231
+ structure.element.element.parentNode.removeChild(structure.element.element);
232
+ }
233
+ else if (structure.element instanceof HTMLElement && structure.element.parentNode) {
234
+ structure.element.parentNode.removeChild(structure.element);
235
+ }
236
+ }
237
+
238
+ // Clean up all other components in the structure
239
+ Object.keys(structure).forEach(key => {
240
+ if (key === 'element') {
241
+ return; // Already handled element
242
+ }
243
+
244
+ const item = structure[key];
245
+
246
+ // Handle component objects
247
+ if (isComponentLike(item) && typeof item.destroy === 'function') {
248
+ item.destroy();
249
+ }
250
+ // Handle DOM elements
251
+ else if (item instanceof HTMLElement && item.parentNode) {
252
+ item.parentNode.removeChild(item);
253
+ }
254
+ });
255
+ }
256
+ };
257
+ }
258
+
259
+ /**
260
+ * Flattens a nested structure into a simple object with element and component references
261
+ * Optimized version that avoids unnecessary type checks where possible
262
+ * @param structure Structure object
263
+ * @returns Flattened structure with all elements and components
264
+ */
265
+ export function flattenStructure(structure: Record<string, any>): Record<string, any> {
266
+ const flattened: Record<string, any> = Object.create(null);
267
+ const keys = Object.keys(structure);
268
+
269
+ for (let i = 0; i < keys.length; i++) {
270
+ const key = keys[i];
271
+ const value = structure[key];
272
+
273
+ // Fast path for common cases
274
+ if (value instanceof HTMLElement ||
275
+ (value && typeof value === 'object' && 'element' in value)) {
276
+ flattened[key] = value;
277
+ continue;
278
+ }
279
+
280
+ // Skip functions and other non-element/component objects
281
+ if (typeof value !== 'function' &&
282
+ (value instanceof Element || isComponentLike(value))) {
283
+ flattened[key] = value;
284
+ }
285
+ }
286
+
287
+ return flattened;
288
+ }
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ export { default as createDialog } from './components/dialog'
16
16
  export { default as createDivider } from './components/divider'
17
17
  export { default as createMenu } from './components/menu'
18
18
  export { default as createNavigation } from './components/navigation'
19
+ export { default as createNavigationSystem } from './components/navigation'
19
20
  export { default as createProgress } from './components/progress'
20
21
  export { default as createRadios } from './components/radios'
21
22
  export { default as createSearch } from './components/search'
@@ -0,0 +1,244 @@
1
+ // src/components/navigation/_mobile.scss
2
+ @use '../../styles/abstract/variables' as v;
3
+ @use '../../styles/abstract/mixins' as m;
4
+ @use '../../styles/abstract/base' as base;
5
+ @use '../../styles/abstract/theme' as t;
6
+
7
+ $prefix: base.$prefix;
8
+
9
+ // Mobile navigation overlay
10
+ .#{$prefix}-nav-overlay {
11
+ position: fixed;
12
+ top: 0;
13
+ left: 0;
14
+ right: 0;
15
+ bottom: 0;
16
+ background-color: t.alpha('shadow', 0.5);
17
+ z-index: v.z-index('modal') - 1; // Just below modal level but above most content
18
+ opacity: 0;
19
+ visibility: hidden;
20
+ transition: opacity 0.3s v.motion('easing-standard'), visibility 0.3s v.motion('easing-standard');
21
+
22
+ // Add backdrop blur for a modern effect where supported
23
+ @supports (backdrop-filter: blur(4px)) {
24
+ backdrop-filter: blur(4px);
25
+ background-color: t.alpha('shadow', 0.4);
26
+ }
27
+
28
+ &.active {
29
+ opacity: 1;
30
+ visibility: visible;
31
+ }
32
+ }
33
+
34
+ // Close button for mobile drawer
35
+ .#{$prefix}-nav-close-btn {
36
+ position: absolute;
37
+ top: 12px;
38
+ right: 12px;
39
+ width: 40px;
40
+ height: 40px;
41
+ border-radius: 50%;
42
+ background-color: transparent;
43
+ border: none;
44
+ cursor: pointer;
45
+ display: none; // Hidden by default, shown on mobile
46
+ align-items: center;
47
+ justify-content: center;
48
+ color: t.color('on-surface');
49
+ z-index: 1;
50
+ -webkit-tap-highlight-color: transparent; // Removes default mobile tap highlight
51
+
52
+ &:hover {
53
+ background-color: t.alpha('on-surface', 0.05);
54
+ }
55
+
56
+ // Touch feedback state
57
+ &.active {
58
+ background-color: t.alpha('on-surface', 0.12);
59
+ }
60
+
61
+ &:focus-visible {
62
+ outline: 2px solid t.color('primary');
63
+ outline-offset: 2px;
64
+ }
65
+
66
+ svg {
67
+ width: 24px;
68
+ height: 24px;
69
+ stroke: currentColor;
70
+ }
71
+
72
+ // Ensure WCAG-compliant touch target size
73
+ @media (pointer: coarse) {
74
+ min-width: 48px;
75
+ min-height: 48px;
76
+ }
77
+ }
78
+
79
+ // Body class to prevent scrolling when drawer is open
80
+ .#{$prefix}-body-drawer-open {
81
+ overflow: hidden;
82
+
83
+ // On iOS we need to handle the body position differently
84
+ @supports (-webkit-overflow-scrolling: touch) {
85
+ position: fixed;
86
+ width: 100%;
87
+ height: 100%;
88
+ }
89
+ }
90
+
91
+ // Responsive behavior for navigation system - apply to small screens or touch devices
92
+ @media (max-width: 960px), (pointer: coarse) {
93
+ // Rail navigation
94
+ .#{$prefix}-nav--rail {
95
+ width: 56px;
96
+ z-index: v.z-index('modal') - 1;
97
+ border-right: 1px solid t.color('outline-variant');
98
+
99
+ // Hide labels on mobile
100
+ .#{$prefix}-nav-label {
101
+ display: none;
102
+ }
103
+
104
+ // Center align items
105
+ .#{$prefix}-nav-item {
106
+ justify-content: center;
107
+ height: 56px;
108
+ -webkit-tap-highlight-color: transparent; // Remove default mobile highlight
109
+
110
+ // Increase touch targets for better accessibility
111
+ @media (pointer: coarse) {
112
+ min-height: 48px;
113
+ padding: 12px 8px;
114
+ }
115
+
116
+ // Touch-friendly active state
117
+ &:active {
118
+ background-color: t.alpha('on-surface', 0.12);
119
+ }
120
+ }
121
+
122
+ // Larger icons on mobile
123
+ .#{$prefix}-nav-icon {
124
+ font-size: 1.25rem;
125
+
126
+ // Ensure icon touch target meets WCAG requirements
127
+ @media (pointer: coarse) {
128
+ min-width: 44px;
129
+ min-height: 44px;
130
+ }
131
+ }
132
+ }
133
+
134
+ // Drawer navigation
135
+ .#{$prefix}-nav--drawer {
136
+ position: fixed;
137
+ top: 0;
138
+ bottom: 0;
139
+ left: 0;
140
+ width: 280px;
141
+ max-width: 85vw;
142
+ z-index: v.z-index('modal');
143
+ background-color: t.color('surface');
144
+ box-shadow: v.elevation('level-3');
145
+ transform: translateX(-100%);
146
+ transition: transform 0.3s v.motion('easing-standard');
147
+ overflow-y: auto;
148
+ -webkit-overflow-scrolling: touch; // Smooth scrolling on iOS
149
+
150
+ &:not(.#{$prefix}-nav--hidden) {
151
+ transform: translateX(0);
152
+ }
153
+
154
+ // Optimize drawer items for touch
155
+ .#{$prefix}-nav-item {
156
+ -webkit-tap-highlight-color: transparent;
157
+
158
+ @media (pointer: coarse) {
159
+ min-height: 48px;
160
+ padding: 12px 16px;
161
+ }
162
+
163
+ // Touch feedback
164
+ &:active {
165
+ background-color: t.alpha('on-surface', 0.12);
166
+ }
167
+ }
168
+
169
+ // Support RTL languages
170
+ @include m.rtl {
171
+ left: auto;
172
+ right: 0;
173
+ transform: translateX(100%);
174
+
175
+ &:not(.#{$prefix}-nav--hidden) {
176
+ transform: translateX(0);
177
+ }
178
+ }
179
+ }
180
+ }
181
+
182
+ // Desktop behavior
183
+ @media (min-width: 961px) {
184
+ .#{$prefix}-nav--drawer {
185
+ // For desktop, transition by width instead of transform
186
+ transition: width 0.3s v.motion('easing-standard'), opacity 0.3s v.motion('easing-standard');
187
+ }
188
+
189
+ .#{$prefix}-nav-close-btn {
190
+ display: none !important; // Always hidden on desktop
191
+ }
192
+
193
+ // Hide overlay on desktop
194
+ .#{$prefix}-nav-overlay.active {
195
+ opacity: 0;
196
+ visibility: hidden;
197
+ }
198
+ }
199
+
200
+ // Safe area insets for notched devices like iPhone X and newer
201
+ @supports (padding-bottom: env(safe-area-inset-bottom)) {
202
+ .#{$prefix}-nav--drawer {
203
+ padding-top: env(safe-area-inset-top);
204
+ padding-bottom: env(safe-area-inset-bottom);
205
+ }
206
+
207
+ .#{$prefix}-nav--rail {
208
+ padding-top: env(safe-area-inset-top);
209
+ padding-bottom: env(safe-area-inset-bottom);
210
+ }
211
+
212
+ .#{$prefix}-nav--bar {
213
+ // For bottom navigation bars
214
+ &.#{$prefix}-nav--bottom {
215
+ padding-bottom: env(safe-area-inset-bottom);
216
+ }
217
+ }
218
+ }
219
+
220
+ // Gesture hints for touch interfaces
221
+ @media (pointer: coarse) {
222
+ // Add visual affordance for swipe gesture
223
+ .#{$prefix}-nav--drawer:not(.#{$prefix}-nav--hidden) {
224
+ &::after {
225
+ content: '';
226
+ position: absolute;
227
+ top: 50%;
228
+ right: 0;
229
+ width: 4px;
230
+ height: 40px;
231
+ border-radius: 4px 0 0 4px;
232
+ background-color: t.alpha('on-surface', 0.1);
233
+ transform: translateY(-50%);
234
+ opacity: 0.5;
235
+ // Hide after interaction to avoid cognitive load
236
+ animation: fadeOutAfterDelay 4s forwards;
237
+ }
238
+ }
239
+
240
+ @keyframes fadeOutAfterDelay {
241
+ 0%, 50% { opacity: 0.5; }
242
+ 100% { opacity: 0; }
243
+ }
244
+ }
@@ -0,0 +1,151 @@
1
+ // src/components/navigation/system.scss
2
+
3
+ // Navigation system specific styles
4
+ // These complement the existing navigation component styles
5
+
6
+ $nav-animation-duration: 0.2s;
7
+ $nav-animation-function: cubic-bezier(0.4, 0, 0.2, 1);
8
+
9
+ // Drawer animation states
10
+ .mtrl-nav--drawer {
11
+ transition: transform $nav-animation-duration $nav-animation-function,
12
+ opacity $nav-animation-duration $nav-animation-function;
13
+
14
+ // Hidden state (transform off-screen)
15
+ &.mtrl-nav--hidden {
16
+ transform: translateX(-100%);
17
+ opacity: 0;
18
+ pointer-events: none;
19
+ }
20
+
21
+ // Right positioned drawer
22
+ &.mtrl-nav--right {
23
+ &.mtrl-nav--hidden {
24
+ transform: translateX(100%);
25
+ }
26
+ }
27
+
28
+ // Animation classes
29
+ &.mtrl-nav--animate-in {
30
+ animation: nav-drawer-in $nav-animation-duration $nav-animation-function;
31
+ }
32
+
33
+ &.mtrl-nav--animate-out {
34
+ animation: nav-drawer-out $nav-animation-duration $nav-animation-function;
35
+ }
36
+ }
37
+
38
+ // Navigation system container (optional - for wrapping rail + drawer)
39
+ .mtrl-nav-system {
40
+ display: flex;
41
+ position: relative;
42
+
43
+ // Rail is fixed width
44
+ .mtrl-nav--rail {
45
+ flex: 0 0 auto;
46
+ }
47
+
48
+ // Drawer expands
49
+ .mtrl-nav--drawer {
50
+ flex: 1 0 auto;
51
+ }
52
+ }
53
+
54
+ // Drawer entry animation
55
+ @keyframes nav-drawer-in {
56
+ 0% {
57
+ transform: translateX(-20px);
58
+ opacity: 0;
59
+ }
60
+ 100% {
61
+ transform: translateX(0);
62
+ opacity: 1;
63
+ }
64
+ }
65
+
66
+ // Drawer exit animation
67
+ @keyframes nav-drawer-out {
68
+ 0% {
69
+ transform: translateX(0);
70
+ opacity: 1;
71
+ }
72
+ 100% {
73
+ transform: translateX(-20px);
74
+ opacity: 0;
75
+ }
76
+ }
77
+
78
+ // Right-side drawer animations
79
+ .mtrl-nav--drawer.mtrl-nav--right {
80
+ &.mtrl-nav--animate-in {
81
+ animation: nav-drawer-right-in $nav-animation-duration $nav-animation-function;
82
+ }
83
+
84
+ &.mtrl-nav--animate-out {
85
+ animation: nav-drawer-right-out $nav-animation-duration $nav-animation-function;
86
+ }
87
+ }
88
+
89
+ @keyframes nav-drawer-right-in {
90
+ 0% {
91
+ transform: translateX(20px);
92
+ opacity: 0;
93
+ }
94
+ 100% {
95
+ transform: translateX(0);
96
+ opacity: 1;
97
+ }
98
+ }
99
+
100
+ @keyframes nav-drawer-right-out {
101
+ 0% {
102
+ transform: translateX(0);
103
+ opacity: 1;
104
+ }
105
+ 100% {
106
+ transform: translateX(20px);
107
+ opacity: 0;
108
+ }
109
+ }
110
+
111
+ // Mobile optimizations
112
+ @media (max-width: 960px) {
113
+ .mtrl-nav-system {
114
+ flex-direction: column;
115
+
116
+ .mtrl-nav--rail {
117
+ // Mobile rail can be top or bottom
118
+ &.mtrl-nav--top, &.mtrl-nav--bottom {
119
+ width: 100%;
120
+ }
121
+ }
122
+
123
+ .mtrl-nav--drawer {
124
+ // Mobile drawer takes full width
125
+ width: 100%;
126
+ max-width: 100%;
127
+
128
+ &.mtrl-nav--hidden {
129
+ transform: translateY(-100%);
130
+ }
131
+
132
+ &.mtrl-nav--bottom {
133
+ &.mtrl-nav--hidden {
134
+ transform: translateY(100%);
135
+ }
136
+ }
137
+ }
138
+ }
139
+ }
140
+
141
+ // Focus and accessibility enhancements
142
+ .mtrl-nav-system {
143
+ [role="navigation"] {
144
+ &:focus-within {
145
+ outline: 2px solid var(--mtrl-primary-color, #6200ee);
146
+ outline-offset: -2px;
147
+ }
148
+ }
149
+ }
150
+
151
+ @use 'navigation-mobile'
@@ -90,7 +90,7 @@ $container: '#{base.$prefix}-tabs';
90
90
  max-width: 360px;
91
91
  padding: 0 16px;
92
92
  border: none;
93
- border-radius: 5px;
93
+ border-radius: 5px 5px 0 0;
94
94
  background-color: transparent;
95
95
  color: t.color('on-surface-variant');
96
96
  cursor: pointer;