mtrl 0.2.5 → 0.2.7
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.
- package/index.ts +18 -0
- package/package.json +1 -1
- package/src/components/badge/_styles.scss +123 -115
- package/src/components/badge/api.ts +57 -59
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +65 -11
- package/src/components/badge/constants.ts +22 -12
- package/src/components/badge/features.ts +44 -40
- package/src/components/badge/types.ts +42 -30
- package/src/components/bottom-app-bar/_styles.scss +103 -0
- package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
- package/src/components/bottom-app-bar/config.ts +73 -0
- package/src/components/bottom-app-bar/index.ts +11 -0
- package/src/components/bottom-app-bar/types.ts +108 -0
- package/src/components/button/_styles.scss +0 -66
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -2
- package/src/components/button/config.ts +5 -0
- package/src/components/button/constants.ts +0 -6
- package/src/components/button/index.ts +2 -2
- package/src/components/button/types.ts +7 -7
- package/src/components/card/_styles.scss +67 -25
- package/src/components/card/api.ts +54 -3
- package/src/components/card/card.ts +25 -6
- package/src/components/card/config.ts +189 -22
- package/src/components/card/constants.ts +20 -19
- package/src/components/card/content.ts +299 -2
- package/src/components/card/features.ts +158 -4
- package/src/components/card/index.ts +31 -9
- package/src/components/card/types.ts +166 -15
- package/src/components/checkbox/_styles.scss +0 -2
- package/src/components/chip/chip.ts +1 -9
- package/src/components/chip/constants.ts +0 -10
- package/src/components/chip/index.ts +1 -1
- package/src/components/chip/types.ts +1 -4
- package/src/components/datepicker/_styles.scss +358 -0
- package/src/components/datepicker/api.ts +272 -0
- package/src/components/datepicker/config.ts +144 -0
- package/src/components/datepicker/constants.ts +98 -0
- package/src/components/datepicker/datepicker.ts +346 -0
- package/src/components/datepicker/index.ts +9 -0
- package/src/components/datepicker/render.ts +452 -0
- package/src/components/datepicker/types.ts +268 -0
- package/src/components/datepicker/utils.ts +290 -0
- package/src/components/dialog/_styles.scss +174 -128
- package/src/components/dialog/api.ts +48 -13
- package/src/components/dialog/config.ts +9 -5
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +290 -130
- package/src/components/dialog/types.ts +7 -4
- package/src/components/divider/_styles.scss +57 -0
- package/src/components/divider/config.ts +81 -0
- package/src/components/divider/divider.ts +37 -0
- package/src/components/divider/features.ts +207 -0
- package/src/components/divider/index.ts +5 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/_styles.scss +267 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +108 -0
- package/src/components/extended-fab/constants.ts +36 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +4 -0
- package/src/components/extended-fab/types.ts +287 -0
- package/src/components/fab/_styles.scss +225 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +94 -0
- package/src/components/fab/constants.ts +41 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +4 -0
- package/src/components/fab/types.ts +234 -0
- package/src/components/navigation/_styles.scss +1 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/features/items.ts +280 -0
- package/src/components/navigation/nav-item.ts +72 -23
- package/src/components/navigation/navigation.ts +54 -2
- package/src/components/navigation/types.ts +210 -188
- package/src/components/progress/_styles.scss +0 -65
- package/src/components/progress/config.ts +1 -2
- package/src/components/progress/constants.ts +0 -14
- package/src/components/progress/index.ts +1 -1
- package/src/components/progress/progress.ts +1 -4
- package/src/components/progress/types.ts +1 -4
- package/src/components/radios/_styles.scss +0 -45
- package/src/components/radios/api.ts +85 -60
- package/src/components/radios/config.ts +1 -2
- package/src/components/radios/constants.ts +0 -9
- package/src/components/radios/index.ts +1 -1
- package/src/components/radios/radio.ts +34 -11
- package/src/components/radios/radios.ts +2 -1
- package/src/components/radios/types.ts +1 -7
- package/src/components/search/_styles.scss +306 -0
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +87 -0
- package/src/components/search/constants.ts +21 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +718 -0
- package/src/components/search/features/states.ts +165 -0
- package/src/components/search/features/structure.ts +198 -0
- package/src/components/search/index.ts +10 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +163 -0
- package/src/components/segmented-button/_styles.scss +117 -0
- package/src/components/segmented-button/config.ts +67 -0
- package/src/components/segmented-button/constants.ts +42 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +155 -0
- package/src/components/segmented-button/segmented-button.ts +250 -0
- package/src/components/segmented-button/types.ts +219 -0
- package/src/components/slider/_styles.scss +221 -168
- package/src/components/slider/accessibility.md +59 -0
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -49
- package/src/components/slider/features/handlers.ts +495 -0
- package/src/components/slider/features/index.ts +1 -2
- package/src/components/slider/features/slider.ts +66 -84
- package/src/components/slider/features/states.ts +195 -0
- package/src/components/slider/features/structure.ts +141 -184
- package/src/components/slider/features/ui.ts +150 -201
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +39 -24
- package/src/components/switch/_styles.scss +0 -2
- package/src/components/tabs/_styles.scss +346 -154
- package/src/components/tabs/api.ts +178 -400
- package/src/components/tabs/config.ts +46 -52
- package/src/components/tabs/constants.ts +85 -8
- package/src/components/tabs/features.ts +403 -0
- package/src/components/tabs/index.ts +60 -3
- package/src/components/tabs/indicator.ts +285 -0
- package/src/components/tabs/responsive.ts +144 -0
- package/src/components/tabs/scroll-indicators.ts +149 -0
- package/src/components/tabs/state.ts +186 -0
- package/src/components/tabs/tab-api.ts +258 -0
- package/src/components/tabs/tab.ts +255 -0
- package/src/components/tabs/tabs.ts +50 -31
- package/src/components/tabs/types.ts +332 -128
- package/src/components/tabs/utils.ts +107 -0
- package/src/components/textfield/_styles.scss +0 -98
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/constants.ts +0 -14
- package/src/components/textfield/index.ts +2 -2
- package/src/components/textfield/textfield.ts +0 -2
- package/src/components/textfield/types.ts +1 -4
- package/src/components/timepicker/README.md +277 -0
- package/src/components/timepicker/_styles.scss +451 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +130 -0
- package/src/components/timepicker/constants.ts +138 -0
- package/src/components/timepicker/index.ts +8 -0
- package/src/components/timepicker/render.ts +613 -0
- package/src/components/timepicker/timepicker.ts +117 -0
- package/src/components/timepicker/types.ts +336 -0
- package/src/components/timepicker/utils.ts +241 -0
- package/src/components/top-app-bar/_styles.scss +225 -0
- package/src/components/top-app-bar/config.ts +83 -0
- package/src/components/top-app-bar/index.ts +11 -0
- package/src/components/top-app-bar/top-app-bar.ts +316 -0
- package/src/components/top-app-bar/types.ts +140 -0
- package/src/core/build/_ripple.scss +6 -6
- package/src/core/build/ripple.ts +72 -95
- package/src/core/compose/component.ts +1 -1
- package/src/core/compose/features/badge.ts +79 -0
- package/src/core/compose/features/icon.ts +3 -1
- package/src/core/compose/features/index.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +26 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +115 -3
- package/src/styles/themes/_autumn.scss +21 -0
- package/src/styles/themes/_base-theme.scss +61 -0
- package/src/styles/themes/_baseline.scss +58 -0
- package/src/styles/themes/_bluekhaki.scss +125 -0
- package/src/styles/themes/_brownbeige.scss +125 -0
- package/src/styles/themes/_browngreen.scss +125 -0
- package/src/styles/themes/_forest.scss +6 -0
- package/src/styles/themes/_greenbeige.scss +125 -0
- package/src/styles/themes/_material.scss +125 -0
- package/src/styles/themes/_ocean.scss +6 -0
- package/src/styles/themes/_sageivory.scss +125 -0
- package/src/styles/themes/_spring.scss +6 -0
- package/src/styles/themes/_summer.scss +5 -0
- package/src/styles/themes/_sunset.scss +5 -0
- package/src/styles/themes/_tealcaramel.scss +125 -0
- package/src/styles/themes/_winter.scss +6 -0
- package/src/components/card/actions.ts +0 -48
- package/src/components/card/header.ts +0 -88
- package/src/components/card/media.ts +0 -52
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -43
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -261
- package/src/components/slider/features/keyboard.ts +0 -112
- package/src/core/collection/adapters/mongodb.js +0 -232
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
// src/components/top-app-bar/_styles.scss
|
|
2
|
+
@use 'sass:map';
|
|
3
|
+
@use '../../styles/abstract/base' as base;
|
|
4
|
+
@use '../../styles/abstract/variables' as v;
|
|
5
|
+
@use '../../styles/abstract/functions' as f;
|
|
6
|
+
@use '../../styles/abstract/mixins' as m;
|
|
7
|
+
@use '../../styles/abstract/theme' as t;
|
|
8
|
+
|
|
9
|
+
$component: '#{base.$prefix}-top-app-bar';
|
|
10
|
+
|
|
11
|
+
.#{$component} {
|
|
12
|
+
// Core Properties
|
|
13
|
+
position: absolute;
|
|
14
|
+
top: 0;
|
|
15
|
+
left: 0;
|
|
16
|
+
right: 0;
|
|
17
|
+
z-index: f.get-z-index('fixed');
|
|
18
|
+
display: flex;
|
|
19
|
+
align-items: center;
|
|
20
|
+
width: 100%;
|
|
21
|
+
background-color: t.color('surface');
|
|
22
|
+
|
|
23
|
+
// Default type (small) - 64dp height as per specs
|
|
24
|
+
height: 64px;
|
|
25
|
+
padding: 0 16px;
|
|
26
|
+
|
|
27
|
+
// Apply transition for scroll behavior
|
|
28
|
+
transition:
|
|
29
|
+
box-shadow 0.3s ease-in-out,
|
|
30
|
+
background-color 0.3s ease-in-out,
|
|
31
|
+
height 0.3s ease-in-out;
|
|
32
|
+
|
|
33
|
+
// Container for the headline
|
|
34
|
+
&-headline {
|
|
35
|
+
flex: 1;
|
|
36
|
+
display: flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
@include m.typography('title-large');
|
|
39
|
+
color: t.color('on-surface');
|
|
40
|
+
margin: 0;
|
|
41
|
+
padding: 0;
|
|
42
|
+
white-space: nowrap;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
transition:
|
|
46
|
+
font-size 0.3s ease-in-out,
|
|
47
|
+
margin 0.3s ease-in-out,
|
|
48
|
+
padding 0.3s ease-in-out;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Leading section (usually navigation icon)
|
|
52
|
+
&-leading {
|
|
53
|
+
display: flex;
|
|
54
|
+
align-items: center;
|
|
55
|
+
margin-right: 24px;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Trailing section (usually action icons)
|
|
59
|
+
&-trailing {
|
|
60
|
+
display: flex;
|
|
61
|
+
align-items: center;
|
|
62
|
+
gap: 8px;
|
|
63
|
+
margin-left: auto;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Center-aligned top app bar
|
|
67
|
+
&--center {
|
|
68
|
+
.#{$component}-headline {
|
|
69
|
+
position: absolute;
|
|
70
|
+
left: 50%;
|
|
71
|
+
transform: translateX(-50%);
|
|
72
|
+
text-align: center;
|
|
73
|
+
flex: 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Medium top app bar - 112dp height
|
|
78
|
+
&--medium {
|
|
79
|
+
height: 112px;
|
|
80
|
+
flex-direction: column;
|
|
81
|
+
align-items: flex-start;
|
|
82
|
+
justify-content: space-between;
|
|
83
|
+
padding: 0;
|
|
84
|
+
|
|
85
|
+
.#{$component}-row {
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: center;
|
|
88
|
+
width: 100%;
|
|
89
|
+
padding: 0 16px;
|
|
90
|
+
|
|
91
|
+
&:first-child {
|
|
92
|
+
margin-top: 20px;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.#{$component}-headline {
|
|
97
|
+
margin-bottom: 24px;
|
|
98
|
+
margin-left: 16px;
|
|
99
|
+
@include m.typography('headline-small');
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Large top app bar - 152dp height
|
|
104
|
+
&--large {
|
|
105
|
+
height: 152px;
|
|
106
|
+
flex-direction: column;
|
|
107
|
+
align-items: flex-start;
|
|
108
|
+
justify-content: space-between;
|
|
109
|
+
padding: 0;
|
|
110
|
+
|
|
111
|
+
.#{$component}-row {
|
|
112
|
+
display: flex;
|
|
113
|
+
align-items: center;
|
|
114
|
+
width: 100%;
|
|
115
|
+
padding: 0 16px;
|
|
116
|
+
|
|
117
|
+
&:first-child {
|
|
118
|
+
margin-top: 20px;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.#{$component}-headline {
|
|
123
|
+
margin-bottom: 28px;
|
|
124
|
+
margin-left: 16px;
|
|
125
|
+
@include m.typography('headline-medium');
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// States for on-scroll behavior
|
|
130
|
+
&--scrolled {
|
|
131
|
+
background-color: t.color('surface-container');
|
|
132
|
+
@include m.elevation(1);
|
|
133
|
+
|
|
134
|
+
// Compress medium and large variants to small on scroll if compressible
|
|
135
|
+
&.#{$component}--medium.#{$component}--compressible,
|
|
136
|
+
&.#{$component}--large.#{$component}--compressible {
|
|
137
|
+
height: 64px;
|
|
138
|
+
flex-direction: row;
|
|
139
|
+
align-items: center;
|
|
140
|
+
padding: 0 16px;
|
|
141
|
+
|
|
142
|
+
.#{$component}-row {
|
|
143
|
+
display: none;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.#{$component}-headline {
|
|
147
|
+
margin: 0;
|
|
148
|
+
@include m.typography('title-large');
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Fix for headline that is inside row in medium/large
|
|
152
|
+
.#{$component}-row:nth-child(2) {
|
|
153
|
+
display: flex;
|
|
154
|
+
flex: 1;
|
|
155
|
+
margin: 0;
|
|
156
|
+
padding: 0;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// RTL Support
|
|
162
|
+
[dir="rtl"] & {
|
|
163
|
+
.#{$component}-leading {
|
|
164
|
+
margin-right: 0;
|
|
165
|
+
margin-left: 24px;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.#{$component}-trailing {
|
|
169
|
+
margin-left: 0;
|
|
170
|
+
margin-right: auto;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
&--medium,
|
|
174
|
+
&--large {
|
|
175
|
+
.#{$component}-headline {
|
|
176
|
+
margin-left: 0;
|
|
177
|
+
margin-right: 16px;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
&--center {
|
|
182
|
+
.#{$component}-headline {
|
|
183
|
+
left: auto;
|
|
184
|
+
right: 50%;
|
|
185
|
+
transform: translateX(50%);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Mobile screen adjustments
|
|
191
|
+
@media (max-width: map.get(v.$breakpoints, 'sm')) {
|
|
192
|
+
// Reduce side padding slightly on small screens
|
|
193
|
+
padding: 0 12px;
|
|
194
|
+
|
|
195
|
+
.#{$component}-leading {
|
|
196
|
+
margin-right: 16px;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.#{$component}-trailing {
|
|
200
|
+
gap: 4px;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Make icon buttons slightly smaller
|
|
204
|
+
.#{$component}-leading,
|
|
205
|
+
.#{$component}-trailing {
|
|
206
|
+
button {
|
|
207
|
+
padding: 8px;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Reduced motion support
|
|
213
|
+
@include m.reduced-motion {
|
|
214
|
+
transition-duration: 0.01ms;
|
|
215
|
+
|
|
216
|
+
.#{$component}-headline {
|
|
217
|
+
transition-duration: 0.01ms;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// High contrast mode support
|
|
222
|
+
@include m.high-contrast {
|
|
223
|
+
border-bottom: 1px solid currentColor;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
// src/components/top-app-bar/config.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/top-app-bar
|
|
4
|
+
* @description Configuration for top app bar component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createComponentConfig, BaseComponentConfig } from '../../core/config/component-config';
|
|
8
|
+
import { PREFIX } from '../../core/config';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Top App Bar types
|
|
12
|
+
*/
|
|
13
|
+
export type TopAppBarType = 'small' | 'medium' | 'large' | 'center';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Configuration options for top app bar
|
|
17
|
+
*/
|
|
18
|
+
export interface TopAppBarConfig extends BaseComponentConfig {
|
|
19
|
+
/**
|
|
20
|
+
* Element to use for the container
|
|
21
|
+
* @default 'header'
|
|
22
|
+
*/
|
|
23
|
+
tag?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Type of top app bar to display
|
|
27
|
+
* @default 'small'
|
|
28
|
+
*/
|
|
29
|
+
type?: TopAppBarType;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Title text to display in the app bar
|
|
33
|
+
*/
|
|
34
|
+
title?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Whether to enable scrolling behavior
|
|
38
|
+
* @default true
|
|
39
|
+
*/
|
|
40
|
+
scrollable?: boolean;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Whether to compress medium/large variants to small on scroll
|
|
44
|
+
* @default true
|
|
45
|
+
*/
|
|
46
|
+
compressible?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Scroll threshold in pixels to trigger the scrolled state
|
|
50
|
+
* @default 4
|
|
51
|
+
*/
|
|
52
|
+
scrollThreshold?: number;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Additional CSS classes to apply
|
|
56
|
+
*/
|
|
57
|
+
class?: string;
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Optional callback when scrolling changes the bar appearance
|
|
61
|
+
*/
|
|
62
|
+
onScroll?: (scrolled: boolean) => void;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Default configuration for top app bar
|
|
67
|
+
*/
|
|
68
|
+
export const defaultConfig: Partial<TopAppBarConfig> = {
|
|
69
|
+
tag: 'header',
|
|
70
|
+
type: 'small',
|
|
71
|
+
scrollable: true,
|
|
72
|
+
compressible: true,
|
|
73
|
+
scrollThreshold: 4
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Creates the configuration for a top app bar component
|
|
78
|
+
*
|
|
79
|
+
* @param {TopAppBarConfig} config - User provided configuration
|
|
80
|
+
* @returns {TopAppBarConfig} Complete configuration with defaults applied
|
|
81
|
+
*/
|
|
82
|
+
export const createConfig = (config: TopAppBarConfig = {} as TopAppBarConfig): TopAppBarConfig =>
|
|
83
|
+
createComponentConfig(defaultConfig, config, 'top-app-bar') as TopAppBarConfig;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/components/top-app-bar/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/top-app-bar
|
|
4
|
+
* @description Top app bar component for application headers
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createTopAppBar } from './top-app-bar';
|
|
8
|
+
|
|
9
|
+
export default createTopAppBar;
|
|
10
|
+
export { createTopAppBar };
|
|
11
|
+
export type { TopAppBarConfig, TopAppBarType } from './config';
|
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
// src/components/top-app-bar/top-app-bar.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/top-app-bar
|
|
4
|
+
* @description Top app bar implementation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createBase,
|
|
9
|
+
withElement,
|
|
10
|
+
withEvents,
|
|
11
|
+
withLifecycle,
|
|
12
|
+
ElementComponent,
|
|
13
|
+
BaseComponent
|
|
14
|
+
} from '../../core/compose';
|
|
15
|
+
|
|
16
|
+
import { createConfig, TopAppBarConfig, TopAppBarType } from './config';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Top app bar component interface
|
|
20
|
+
*/
|
|
21
|
+
export interface TopAppBar extends ElementComponent {
|
|
22
|
+
/**
|
|
23
|
+
* Sets the title of the top app bar
|
|
24
|
+
* @param {string} title - Title text
|
|
25
|
+
* @returns {TopAppBar} TopAppBar instance for chaining
|
|
26
|
+
*/
|
|
27
|
+
setTitle: (title: string) => TopAppBar;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Gets the current title
|
|
31
|
+
* @returns {string} Current title text
|
|
32
|
+
*/
|
|
33
|
+
getTitle: () => string;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Adds a leading navigation icon or element
|
|
37
|
+
* @param {HTMLElement} element - Element to add to the leading section
|
|
38
|
+
* @returns {TopAppBar} TopAppBar instance for chaining
|
|
39
|
+
*/
|
|
40
|
+
addLeadingElement: (element: HTMLElement) => TopAppBar;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Adds a trailing action icon or element
|
|
44
|
+
* @param {HTMLElement} element - Element to add to the trailing section
|
|
45
|
+
* @returns {TopAppBar} TopAppBar instance for chaining
|
|
46
|
+
*/
|
|
47
|
+
addTrailingElement: (element: HTMLElement) => TopAppBar;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Changes the top app bar type
|
|
51
|
+
* @param {TopAppBarType} type - New app bar type
|
|
52
|
+
* @returns {TopAppBar} TopAppBar instance for chaining
|
|
53
|
+
*/
|
|
54
|
+
setType: (type: TopAppBarType) => TopAppBar;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Manually sets the scrolled state
|
|
58
|
+
* @param {boolean} scrolled - Whether to show the scrolled state
|
|
59
|
+
* @returns {TopAppBar} TopAppBar instance for chaining
|
|
60
|
+
*/
|
|
61
|
+
setScrollState: (scrolled: boolean) => TopAppBar;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Gets the headline element
|
|
65
|
+
* @returns {HTMLElement} Headline element
|
|
66
|
+
*/
|
|
67
|
+
getHeadlineElement: () => HTMLElement;
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Gets the leading container element
|
|
71
|
+
* @returns {HTMLElement} Leading container element
|
|
72
|
+
*/
|
|
73
|
+
getLeadingContainer: () => HTMLElement;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Gets the trailing container element
|
|
77
|
+
* @returns {HTMLElement} Trailing container element
|
|
78
|
+
*/
|
|
79
|
+
getTrailingContainer: () => HTMLElement;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Creates a top app bar component
|
|
84
|
+
*
|
|
85
|
+
* @param {TopAppBarConfig} config - Configuration options
|
|
86
|
+
* @returns {TopAppBar} Top app bar component instance
|
|
87
|
+
*/
|
|
88
|
+
export const createTopAppBar = (config: TopAppBarConfig = {}): TopAppBar => {
|
|
89
|
+
// Process configuration with defaults
|
|
90
|
+
const componentConfig = createConfig(config);
|
|
91
|
+
|
|
92
|
+
// Create base component
|
|
93
|
+
const component = createBase(componentConfig);
|
|
94
|
+
|
|
95
|
+
// Create containers for the top app bar structure
|
|
96
|
+
const createContainers = () => {
|
|
97
|
+
// Leading section container
|
|
98
|
+
const leadingContainer = document.createElement('div');
|
|
99
|
+
leadingContainer.className = `${component.getClass('top-app-bar')}-leading`;
|
|
100
|
+
|
|
101
|
+
// Headline element
|
|
102
|
+
const headlineElement = document.createElement('h1');
|
|
103
|
+
headlineElement.className = `${component.getClass('top-app-bar')}-headline`;
|
|
104
|
+
if (componentConfig.title) {
|
|
105
|
+
headlineElement.textContent = componentConfig.title;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Trailing section container
|
|
109
|
+
const trailingContainer = document.createElement('div');
|
|
110
|
+
trailingContainer.className = `${component.getClass('top-app-bar')}-trailing`;
|
|
111
|
+
|
|
112
|
+
return { leadingContainer, headlineElement, trailingContainer };
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Create the main containers
|
|
116
|
+
const { leadingContainer, headlineElement, trailingContainer } = createContainers();
|
|
117
|
+
|
|
118
|
+
// Determine initial classes based on type
|
|
119
|
+
const getInitialClasses = () => {
|
|
120
|
+
const classes = [componentConfig.class];
|
|
121
|
+
|
|
122
|
+
// Add type class if not the default 'small' type
|
|
123
|
+
if (componentConfig.type !== 'small') {
|
|
124
|
+
classes.push(`${component.getClass('top-app-bar')}--${componentConfig.type}`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Add compressible class if enabled and type is medium or large
|
|
128
|
+
if (componentConfig.compressible &&
|
|
129
|
+
(componentConfig.type === 'medium' || componentConfig.type === 'large')) {
|
|
130
|
+
classes.push(`${component.getClass('top-app-bar')}--compressible`);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
return classes.filter(Boolean);
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Apply Element enhancer
|
|
137
|
+
const enhancedComponent = withElement({
|
|
138
|
+
tag: componentConfig.tag,
|
|
139
|
+
componentName: 'top-app-bar',
|
|
140
|
+
className: getInitialClasses(),
|
|
141
|
+
attrs: {
|
|
142
|
+
role: 'banner',
|
|
143
|
+
'aria-label': 'Top app bar'
|
|
144
|
+
},
|
|
145
|
+
interactive: true
|
|
146
|
+
})(component);
|
|
147
|
+
|
|
148
|
+
// Apply events enhancer for component events
|
|
149
|
+
const withEventsComponent = withEvents()(enhancedComponent);
|
|
150
|
+
|
|
151
|
+
// Apply lifecycle enhancer for cleanup
|
|
152
|
+
const withLifecycleComponent = withLifecycle()(withEventsComponent);
|
|
153
|
+
|
|
154
|
+
// Create the DOM structure based on the type
|
|
155
|
+
const setupDomStructure = () => {
|
|
156
|
+
if (componentConfig.type === 'medium' || componentConfig.type === 'large') {
|
|
157
|
+
// For medium and large, create rows
|
|
158
|
+
const topRow = document.createElement('div');
|
|
159
|
+
topRow.className = `${component.getClass('top-app-bar')}-row`;
|
|
160
|
+
topRow.appendChild(leadingContainer);
|
|
161
|
+
topRow.appendChild(trailingContainer);
|
|
162
|
+
|
|
163
|
+
const bottomRow = document.createElement('div');
|
|
164
|
+
bottomRow.className = `${component.getClass('top-app-bar')}-row`;
|
|
165
|
+
bottomRow.appendChild(headlineElement);
|
|
166
|
+
|
|
167
|
+
withLifecycleComponent.element.appendChild(topRow);
|
|
168
|
+
withLifecycleComponent.element.appendChild(bottomRow);
|
|
169
|
+
} else {
|
|
170
|
+
// For small and center-aligned
|
|
171
|
+
withLifecycleComponent.element.appendChild(leadingContainer);
|
|
172
|
+
withLifecycleComponent.element.appendChild(headlineElement);
|
|
173
|
+
withLifecycleComponent.element.appendChild(trailingContainer);
|
|
174
|
+
}
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
// Set up the initial DOM structure
|
|
178
|
+
setupDomStructure();
|
|
179
|
+
|
|
180
|
+
// Track scrolled state
|
|
181
|
+
let isScrolled = false;
|
|
182
|
+
|
|
183
|
+
// Handle scrolling behavior if enabled
|
|
184
|
+
if (componentConfig.scrollable) {
|
|
185
|
+
const handleScroll = () => {
|
|
186
|
+
const shouldBeScrolled = window.scrollY > componentConfig.scrollThreshold;
|
|
187
|
+
|
|
188
|
+
if (isScrolled !== shouldBeScrolled) {
|
|
189
|
+
isScrolled = shouldBeScrolled;
|
|
190
|
+
|
|
191
|
+
// Toggle scrolled class
|
|
192
|
+
if (isScrolled) {
|
|
193
|
+
withLifecycleComponent.element.classList.add(`${component.getClass('top-app-bar')}--scrolled`);
|
|
194
|
+
} else {
|
|
195
|
+
withLifecycleComponent.element.classList.remove(`${component.getClass('top-app-bar')}--scrolled`);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Call the onScroll callback if provided
|
|
199
|
+
componentConfig.onScroll?.(isScrolled);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Add scroll event listener
|
|
204
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
205
|
+
|
|
206
|
+
// Initial check
|
|
207
|
+
handleScroll();
|
|
208
|
+
|
|
209
|
+
// Clean up event listener on destroy
|
|
210
|
+
const originalDestroy = withLifecycleComponent.lifecycle.destroy;
|
|
211
|
+
withLifecycleComponent.lifecycle.destroy = () => {
|
|
212
|
+
window.removeEventListener('scroll', handleScroll);
|
|
213
|
+
originalDestroy();
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Reorganize the DOM for the given type
|
|
218
|
+
const reorganizeDom = (type: TopAppBarType) => {
|
|
219
|
+
// Clear existing content
|
|
220
|
+
while (withLifecycleComponent.element.firstChild) {
|
|
221
|
+
withLifecycleComponent.element.removeChild(withLifecycleComponent.element.firstChild);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Update component type class
|
|
225
|
+
['small', 'medium', 'large', 'center'].forEach(t => {
|
|
226
|
+
withLifecycleComponent.element.classList.remove(`${component.getClass('top-app-bar')}--${t}`);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
if (type !== 'small') {
|
|
230
|
+
withLifecycleComponent.element.classList.add(`${component.getClass('top-app-bar')}--${type}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Update compressible class
|
|
234
|
+
withLifecycleComponent.element.classList.toggle(
|
|
235
|
+
`${component.getClass('top-app-bar')}--compressible`,
|
|
236
|
+
componentConfig.compressible && (type === 'medium' || type === 'large')
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
// Rebuild the DOM structure
|
|
240
|
+
if (type === 'medium' || type === 'large') {
|
|
241
|
+
// For medium and large, create rows
|
|
242
|
+
const topRow = document.createElement('div');
|
|
243
|
+
topRow.className = `${component.getClass('top-app-bar')}-row`;
|
|
244
|
+
topRow.appendChild(leadingContainer);
|
|
245
|
+
topRow.appendChild(trailingContainer);
|
|
246
|
+
|
|
247
|
+
const bottomRow = document.createElement('div');
|
|
248
|
+
bottomRow.className = `${component.getClass('top-app-bar')}-row`;
|
|
249
|
+
bottomRow.appendChild(headlineElement);
|
|
250
|
+
|
|
251
|
+
withLifecycleComponent.element.appendChild(topRow);
|
|
252
|
+
withLifecycleComponent.element.appendChild(bottomRow);
|
|
253
|
+
} else {
|
|
254
|
+
// For small and center-aligned
|
|
255
|
+
withLifecycleComponent.element.appendChild(leadingContainer);
|
|
256
|
+
withLifecycleComponent.element.appendChild(headlineElement);
|
|
257
|
+
withLifecycleComponent.element.appendChild(trailingContainer);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// Create the top app bar interface
|
|
262
|
+
const topAppBar: TopAppBar = {
|
|
263
|
+
...withLifecycleComponent,
|
|
264
|
+
|
|
265
|
+
setTitle(title: string) {
|
|
266
|
+
headlineElement.textContent = title;
|
|
267
|
+
return this;
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
getTitle() {
|
|
271
|
+
return headlineElement.textContent || '';
|
|
272
|
+
},
|
|
273
|
+
|
|
274
|
+
addLeadingElement(element: HTMLElement) {
|
|
275
|
+
leadingContainer.appendChild(element);
|
|
276
|
+
return this;
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
addTrailingElement(element: HTMLElement) {
|
|
280
|
+
trailingContainer.appendChild(element);
|
|
281
|
+
return this;
|
|
282
|
+
},
|
|
283
|
+
|
|
284
|
+
setType(type: TopAppBarType) {
|
|
285
|
+
componentConfig.type = type;
|
|
286
|
+
reorganizeDom(type);
|
|
287
|
+
return this;
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
setScrollState(scrolled: boolean) {
|
|
291
|
+
isScrolled = scrolled;
|
|
292
|
+
|
|
293
|
+
if (scrolled) {
|
|
294
|
+
this.element.classList.add(`${component.getClass('top-app-bar')}--scrolled`);
|
|
295
|
+
} else {
|
|
296
|
+
this.element.classList.remove(`${component.getClass('top-app-bar')}--scrolled`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return this;
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
getHeadlineElement() {
|
|
303
|
+
return headlineElement;
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
getLeadingContainer() {
|
|
307
|
+
return leadingContainer;
|
|
308
|
+
},
|
|
309
|
+
|
|
310
|
+
getTrailingContainer() {
|
|
311
|
+
return trailingContainer;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
return topAppBar;
|
|
316
|
+
};
|