kmaterialize 2.3.3-1.0.14 → 2.3.3-1.0.18

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.
@@ -0,0 +1,137 @@
1
+ // @implement /Users/kzarshenas/Sites/RodeoFx/rodeo_toolkit_2/app/Front/style/scss/enhancement/airdatepicker_materializecss.scss
2
+ // Remaps air-datepicker's own --adp-* custom properties onto MD3 tokens so
3
+ // its calendar popup auto-themes with the rest of kmaterialize, instead of
4
+ // theming the popup's own markup directly. Requires the consumer to
5
+ // separately import air-datepicker's own base CSS (`air-datepicker/air-datepicker.css`) -
6
+ // kmaterialize can't reach into an optional peer's package at build time
7
+ // without turning it into a hard dependency, so this partial only overrides
8
+ // the custom properties that stylesheet defines.
9
+ //
10
+ // This is the richer Rodeo toolkit variant of the port (rounded corners,
11
+ // circular day cells, weekend/other-month readability fixes, a two-line
12
+ // nav title) rather than CrazyPHP's plainer original.
13
+ input[data-date-picker='air-datepicker'][readonly] {
14
+ caret-color: transparent;
15
+ cursor: pointer;
16
+ }
17
+
18
+ .input-field:has(input[data-date-picker='air-datepicker']) {
19
+ > label,
20
+ .prefix,
21
+ .suffix {
22
+ z-index: 1;
23
+ cursor: pointer;
24
+ }
25
+ }
26
+
27
+ .air-datepicker {
28
+ --adp-z-index: 100001; // above modals/SweetAlert-style overlays
29
+ --adp-transition-duration: 0.11s;
30
+
31
+ --adp-font-family: 'Roboto', sans-serif;
32
+ --adp-background-color: var(--md-sys-color-surface);
33
+ --adp-color: var(--md-sys-color-on-surface);
34
+ --adp-color-secondary: var(--md-sys-color-on-surface-variant);
35
+ --adp-border-color: var(--md-sys-color-outline-variant);
36
+ --adp-border-color-inner: var(--md-sys-color-outline-variant);
37
+
38
+ --adp-color-current-date: var(--md-sys-color-primary);
39
+
40
+ --adp-background-color-hover: var(--md-sys-color-surface-variant);
41
+ --adp-background-color-active: var(--md-sys-color-surface-variant);
42
+
43
+ --adp-cell-background-color-selected: var(--md-sys-color-primary);
44
+ --adp-cell-background-color-selected-hover: var(--md-sys-color-primary-container);
45
+ --adp-cell-border-color-in-range: var(--md-sys-color-primary);
46
+ --adp-cell-background-color-in-range: var(--md-sys-color-primary-container);
47
+ --adp-cell-background-color-in-range-hover: var(--md-sys-color-primary-container);
48
+ --adp-color-other-month: var(--md-sys-color-outline);
49
+ --adp-color-other-month-hover: var(--md-sys-color-on-surface-variant);
50
+
51
+ --adp-btn-color: var(--md-sys-color-primary);
52
+ --adp-btn-color-hover: var(--md-sys-color-on-primary);
53
+ --adp-btn-background-color-hover: var(--md-sys-color-primary-container);
54
+ --adp-btn-background-color-active: var(--md-sys-color-primary-container);
55
+
56
+ --adp-time-track-color: var(--md-sys-color-outline-variant);
57
+ --adp-time-track-color-hover: var(--md-sys-color-primary);
58
+ --adp-time-day-period-color: var(--md-sys-color-on-surface-variant);
59
+
60
+ // Shape - rounded popup, circular day cells, generous padding, matching
61
+ // the rest of kmaterialize's MD3 shape language (pill toolbars, rounded
62
+ // cards) rather than air-datepicker's own square default.
63
+ --adp-border-radius: 24px;
64
+ --adp-cell-border-radius: 50%;
65
+ --adp-padding: 16px;
66
+
67
+ --adp-color-disabled: var(--md-sys-color-outline);
68
+ --adp-color-disabled-in-range: var(--md-sys-color-on-surface-variant);
69
+
70
+ .air-datepicker-nav {
71
+ height: auto;
72
+ min-height: 32px;
73
+ padding: 0px;
74
+ box-shadow: initial;
75
+ background-color: inherit;
76
+ color: var(--adp-nav-color-secondary) !important;
77
+
78
+ .air-datepicker-nav--title {
79
+ display: flex;
80
+ flex-direction: column;
81
+ align-items: center;
82
+ font-size: 14px;
83
+ line-height: 1.25;
84
+
85
+ i {
86
+ font-size: 14px;
87
+ }
88
+
89
+ // A second line under the month/year, for context (e.g. what range
90
+ // is about to apply) - opt-in, air-datepicker doesn't emit this
91
+ // itself, a consumer's own `dateFormatter` can inject it.
92
+ small {
93
+ font-size: 10.5px;
94
+ font-weight: 500;
95
+ letter-spacing: 0.03em;
96
+ font-variant-numeric: tabular-nums;
97
+ color: var(--adp-color-secondary);
98
+ }
99
+ }
100
+
101
+ // Material icons sit larger than the arrows they replace.
102
+ .air-datepicker-nav--action i {
103
+ font-size: 18px;
104
+ }
105
+ }
106
+
107
+ .air-datepicker-cell {
108
+ font-variant-numeric: tabular-nums;
109
+ }
110
+
111
+ // air-datepicker tags weekends and ships no style for them.
112
+ .air-datepicker-cell.-day-.-weekend- {
113
+ color: var(--md-sys-color-outline);
114
+ }
115
+
116
+ .air-datepicker-body--day-name.-weekend- {
117
+ opacity: 0.7;
118
+ }
119
+
120
+ // Inside a selected range a weekend has to stay readable.
121
+ .air-datepicker-cell.-day-.-weekend-.-in-range-,
122
+ .air-datepicker-cell.-day-.-weekend-.-selected- {
123
+ color: inherit;
124
+ }
125
+
126
+ // The view holds still when a greyed (other-month) day is picked, so the
127
+ // selection can land off-month - without this it reads as nothing
128
+ // selected.
129
+ .air-datepicker-cell.-day-.-other-month-.-selected- {
130
+ color: var(--md-sys-color-on-primary);
131
+ background: var(--md-sys-color-primary);
132
+ }
133
+
134
+ i.air-datepicker--pointer {
135
+ display: none;
136
+ }
137
+ }
@@ -0,0 +1,192 @@
1
+ import type AirDatepickerCtor from 'air-datepicker';
2
+ import type { AirDatepickerOptions } from 'air-datepicker';
3
+ import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
4
+ import { loadPeer } from '../../src/peer-loader';
5
+
6
+ export interface AirDatepickerFieldOptions extends BaseOptions {
7
+ locale?: unknown;
8
+ }
9
+
10
+ const _defaults: AirDatepickerFieldOptions = {};
11
+ let activeAirDatepickerField: AirDatepickerField | undefined;
12
+
13
+ // @implement /Users/kzarshenas/Sites/CrazyProject/CrazyPHP/src/Front/Library/Utility/Form/Date.ts
14
+ // Enhances `<input type="text" data-type="date" data-date-picker="air-datepicker">`
15
+ // with the air-datepicker calendar/range picker - deliberately separate from
16
+ // kmaterialize's own native `components/datepicker/` (a different, already
17
+ // existing class-based component); this one is opted into per-input via the
18
+ // `data-date-picker` attribute so it never collides with `.datepicker`
19
+ // elements. The `multiple` attribute switches the picker into range mode,
20
+ // `data-date-format`/`data-date-lang` (only "fr-FR" ported so far) mirror
21
+ // CrazyPHP's own dataset convention.
22
+ //
23
+ // air-datepicker is an optional peerDependency, loaded on demand via
24
+ // peer-loader. Its own base stylesheet (air-datepicker/air-datepicker.css)
25
+ // must be imported separately by the consumer - see the docs page.
26
+ export class AirDatepickerField extends Component<AirDatepickerFieldOptions> {
27
+ declare el: HTMLInputElement;
28
+ picker: AirDatepickerCtor | undefined;
29
+ ready: Promise<void>;
30
+ private _triggers: HTMLElement[];
31
+ private _handleOutsideClick = (e: MouseEvent) => {
32
+ const calendarEl = document.querySelector('.air-datepicker');
33
+ const clickedTrigger = this._triggers.some(trigger => trigger.contains(e.target as Node));
34
+ if (
35
+ calendarEl instanceof HTMLElement &&
36
+ !calendarEl.contains(e.target as Node) &&
37
+ !this.el.contains(e.target as Node) &&
38
+ !clickedTrigger
39
+ ) {
40
+ this.picker?.hide();
41
+ }
42
+ };
43
+
44
+ constructor(el: HTMLInputElement, options: Partial<AirDatepickerFieldOptions>) {
45
+ super(el, options, AirDatepickerField);
46
+ (this.el as any).M_AirDatepickerField = this;
47
+
48
+ this.options = {
49
+ ...AirDatepickerField.defaults,
50
+ ...options
51
+ };
52
+
53
+ const labels = Array.from(document.querySelectorAll('label')).filter(
54
+ label => label.htmlFor === this.el.id
55
+ );
56
+ const icons = Array.from(
57
+ this.el.parentElement?.querySelectorAll<HTMLElement>('.prefix, .suffix') ?? []
58
+ );
59
+ this._triggers = [...labels, ...icons];
60
+ this._triggers.forEach(trigger => trigger.addEventListener('click', this._handleTriggerClick));
61
+ this.ready = this._setup();
62
+ }
63
+
64
+ static get defaults(): AirDatepickerFieldOptions {
65
+ return _defaults;
66
+ }
67
+
68
+ static init(el: HTMLInputElement, options?: Partial<AirDatepickerFieldOptions>): AirDatepickerField;
69
+ static init(
70
+ els: InitElements<HTMLInputElement | MElement>,
71
+ options?: Partial<AirDatepickerFieldOptions>
72
+ ): AirDatepickerField[];
73
+ static init(
74
+ els: HTMLInputElement | InitElements<HTMLInputElement | MElement>,
75
+ options: Partial<AirDatepickerFieldOptions> = {}
76
+ ): AirDatepickerField | AirDatepickerField[] {
77
+ return super.init(els, options, AirDatepickerField);
78
+ }
79
+
80
+ static getInstance(el: HTMLInputElement): AirDatepickerField {
81
+ return (el as any).M_AirDatepickerField;
82
+ }
83
+
84
+ destroy() {
85
+ document.removeEventListener('click', this._handleOutsideClick);
86
+ this._triggers.forEach(trigger => trigger.removeEventListener('click', this._handleTriggerClick));
87
+ this.picker?.destroy();
88
+ if (activeAirDatepickerField === this) activeAirDatepickerField = undefined;
89
+ (this.el as any).M_AirDatepickerField = undefined;
90
+ }
91
+
92
+ private _handleTriggerClick = (event: MouseEvent) => {
93
+ event.preventDefault();
94
+ event.stopPropagation();
95
+ void this.ready.then(() => this.picker?.show());
96
+ };
97
+
98
+ async _setup(): Promise<void> {
99
+ const AirDatepicker = await loadPeer<typeof AirDatepickerCtor>(
100
+ {
101
+ specifier: 'air-datepicker',
102
+ globalName: 'AirDatepicker',
103
+ feature: 'Date input (air-datepicker) enhancement',
104
+ cdnHint:
105
+ '<link rel="stylesheet" href="path/to/air-datepicker.css">\n' +
106
+ ' <script src="path/to/air-datepicker.js"></script>\n' +
107
+ ' (self-hosted - copy from node_modules/air-datepicker/, or a CDN of your choice)'
108
+ },
109
+ () => import('air-datepicker')
110
+ );
111
+
112
+ const dataset = this.el.dataset;
113
+ const builtInLocale = dataset.dateLang === 'fr-FR'
114
+ ? (await import('air-datepicker/locale/fr')).default
115
+ : (await import('air-datepicker/locale/en')).default;
116
+ const pickerOptions: AirDatepickerOptions = {
117
+ dateFormat: dataset.dateFormat || 'yyyy-MM-dd',
118
+ autoClose: dataset.dateAutoClose !== 'false',
119
+ locale: (this.options.locale ?? builtInLocale) as AirDatepickerOptions['locale'],
120
+ onShow: isAnimationComplete => {
121
+ if (isAnimationComplete) return;
122
+ if (activeAirDatepickerField && activeAirDatepickerField !== this) {
123
+ activeAirDatepickerField.picker?.hide();
124
+ }
125
+ activeAirDatepickerField = this;
126
+ },
127
+ onHide: isAnimationComplete => {
128
+ if (isAnimationComplete && activeAirDatepickerField === this) {
129
+ activeAirDatepickerField = undefined;
130
+ }
131
+ }
132
+ };
133
+
134
+ let multiple = false;
135
+ if (this.el.multiple || dataset.dateRange === 'true') {
136
+ pickerOptions.range = true;
137
+ pickerOptions.multipleDatesSeparator = ' - ';
138
+ multiple = true;
139
+ }
140
+
141
+ const initialValue = this.el.getAttribute('value')?.trim();
142
+ if (initialValue) {
143
+ pickerOptions.selectedDates = multiple
144
+ ? initialValue.split(' - ').map(value => value.trim())
145
+ : [initialValue];
146
+ }
147
+
148
+ if (dataset.dateTimepicker === 'true') {
149
+ pickerOptions.timepicker = true;
150
+ if (dataset.dateTimeFormat) pickerOptions.timeFormat = dataset.dateTimeFormat;
151
+ }
152
+
153
+ const supportedViews = ['days', 'months', 'years'];
154
+ if (supportedViews.includes(dataset.dateView ?? '')) {
155
+ pickerOptions.view = dataset.dateView as AirDatepickerOptions['view'];
156
+ }
157
+ if (supportedViews.includes(dataset.dateMinView ?? '')) {
158
+ pickerOptions.minView = dataset.dateMinView as AirDatepickerOptions['minView'];
159
+ }
160
+
161
+ if (dataset.dateMobile === 'true') pickerOptions.isMobile = true;
162
+ if (dataset.datePosition) {
163
+ pickerOptions.position = dataset.datePosition as AirDatepickerOptions['position'];
164
+ }
165
+ if (dataset.dateButtons) {
166
+ const supportedButtons = ['today', 'clear'];
167
+ pickerOptions.buttons = dataset.dateButtons
168
+ .split(',')
169
+ .map(button => button.trim())
170
+ .filter(button => supportedButtons.includes(button)) as AirDatepickerOptions['buttons'];
171
+ }
172
+
173
+ if (this.el.hasAttribute('min') && this.el.getAttribute('min')) {
174
+ pickerOptions.minDate = new Date(this.el.getAttribute('min') as string);
175
+ }
176
+ if (this.el.hasAttribute('max') && this.el.getAttribute('max')) {
177
+ pickerOptions.maxDate = new Date(this.el.getAttribute('max') as string);
178
+ }
179
+ if (!this.el.required && !pickerOptions.buttons) {
180
+ pickerOptions.buttons = ['clear'];
181
+ }
182
+
183
+ pickerOptions.onSelect = ({ date }: any) => {
184
+ if (multiple && Array.isArray(date) && date.length === 1) return;
185
+ this.el.dispatchEvent(new Event('input', { bubbles: true }));
186
+ this.el.dispatchEvent(new Event('change', { bubbles: true }));
187
+ };
188
+
189
+ this.picker = new AirDatepicker(this.el, pickerOptions);
190
+ document.addEventListener('click', this._handleOutsideClick);
191
+ }
192
+ }
@@ -142,10 +142,46 @@
142
142
  }
143
143
 
144
144
  .navbar .brand-logo.center {
145
+ position: absolute;
145
146
  left: 50%;
146
147
  transform: translateX(-50%);
147
148
  }
148
149
 
150
+ /* Collision-safe centered app bar: the left link region and the generated
151
+ right spacer share the available width, keeping the logo truly centered
152
+ without taking it out of the flex layout. */
153
+ .navbar.appbar-bordered .nav-wrapper.nav-wrapper-centered::after {
154
+ content: '';
155
+ order: 3;
156
+ flex: 1 1 0;
157
+ min-width: 0;
158
+ }
159
+
160
+ .navbar.appbar-bordered .nav-wrapper.nav-wrapper-centered > ul {
161
+ order: 1;
162
+ flex: 1 1 0;
163
+ min-width: 0;
164
+ overflow-x: auto;
165
+ overflow-y: hidden;
166
+ scrollbar-width: none;
167
+ }
168
+
169
+ .navbar.appbar-bordered .nav-wrapper.nav-wrapper-centered > ul::-webkit-scrollbar {
170
+ display: none;
171
+ }
172
+
173
+ .navbar.appbar-bordered .nav-wrapper.nav-wrapper-centered > ul > li > a {
174
+ white-space: nowrap;
175
+ }
176
+
177
+ .navbar.appbar-bordered .nav-wrapper.nav-wrapper-centered > .brand-logo.center {
178
+ position: static;
179
+ order: 2;
180
+ flex: 0 0 auto;
181
+ left: auto;
182
+ transform: none;
183
+ }
184
+
149
185
  .navbar .brand-logo.right {
150
186
  right: 0.5rem;
151
187
  padding: 0;
@@ -168,11 +204,258 @@
168
204
  margin-right: 15px;
169
205
  }
170
206
 
207
+ /* Structured MD3 app-bar content. These hooks keep branded demos and app
208
+ shells from falling back to bare, loosely spaced anchor elements. */
209
+ .navbar.appbar-bordered {
210
+ overflow: visible;
211
+ background: var(--md-sys-color-surface-variant);
212
+ border: 1px solid var(--md-sys-color-outline-variant);
213
+ border-radius: 0;
214
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--md-sys-color-shadow) 18%, transparent);
215
+ }
216
+
217
+ /* Layout variants. `navbar-rounded` adds the inset treatment; use
218
+ `navbar-full` for a flush, full-width rectangle. */
219
+ .navbar.appbar-bordered.navbar-rounded {
220
+ margin: 8px 0;
221
+ padding: 4px;
222
+ border-radius: 16px;
223
+ }
224
+
225
+ .navbar.appbar-bordered.navbar-full {
226
+ width: 100%;
227
+ margin: 0;
228
+ padding: 0;
229
+ border-radius: 0;
230
+ border-left: 0;
231
+ border-right: 0;
232
+ }
233
+
234
+ /* Extended app bars may contain tabs and a halfway FAB. Keep the rounded
235
+ surface on its children, but let the FAB extend below the bar. */
236
+ .navbar.appbar-bordered.nav-extended {
237
+ overflow: visible;
238
+ }
239
+
240
+ .navbar.appbar-bordered.nav-extended.navbar-rounded .nav-wrapper {
241
+ border-radius: 15px 15px 0 0;
242
+ }
243
+
244
+ .navbar.appbar-bordered.nav-extended.navbar-rounded .nav-content {
245
+ color: var(--md-sys-color-on-surface);
246
+ background: var(--md-sys-color-surface-variant);
247
+ border-radius: 0 0 15px 15px;
248
+ }
249
+
250
+ .navbar.appbar-bordered .nav-wrapper {
251
+ gap: 12px;
252
+ padding-inline: 12px;
253
+ background: var(--md-sys-color-surface-variant);
254
+ align-items: center;
255
+ min-width: 0;
256
+ overflow-x: auto;
257
+ overflow-y: hidden;
258
+ scrollbar-width: none;
259
+ }
260
+
261
+ .navbar.appbar-bordered .nav-wrapper > .brand-logo,
262
+ .navbar.appbar-bordered .nav-wrapper > ul:not(.dropdown-content),
263
+ .navbar.appbar-bordered .nav-actions > li {
264
+ align-self: center;
265
+ }
266
+
267
+ .navbar.appbar-bordered:not(.nav-extended) .nav-wrapper > .brand-logo,
268
+ .navbar.appbar-bordered:not(.nav-extended) .nav-wrapper > ul:not(.dropdown-content),
269
+ .navbar.appbar-bordered:not(.nav-extended) .nav-wrapper > ul:not(.dropdown-content) > li {
270
+ height: 40px;
271
+ }
272
+
273
+ .navbar.appbar-bordered .nav-actions > li {
274
+ display: flex;
275
+ align-items: center;
276
+ }
277
+
278
+ .navbar.appbar-bordered .nav-wrapper::-webkit-scrollbar {
279
+ display: none;
280
+ }
281
+
282
+ .navbar.appbar-bordered .nav-wrapper > ul:not(.dropdown-content) {
283
+ min-width: 0;
284
+ flex-shrink: 1;
285
+ }
286
+
287
+ .navbar.appbar-bordered.navbar-wrap .nav-wrapper {
288
+ flex-wrap: wrap;
289
+ height: auto;
290
+ row-gap: 8px;
291
+ overflow: visible;
292
+ }
293
+
294
+ .navbar.appbar-bordered.navbar-wrap .nav-wrapper > ul:not(.dropdown-content) {
295
+ flex-wrap: wrap;
296
+ white-space: normal;
297
+ }
298
+
299
+ .navbar .brand-logo .brand-logo-image {
300
+ box-sizing: border-box;
301
+ width: 40px;
302
+ height: 40px;
303
+ margin-right: 10px;
304
+ padding: 8px;
305
+ object-fit: contain;
306
+ background: var(--md-sys-color-primary-container);
307
+ border-radius: 12px;
308
+ }
309
+
310
+ .navbar .nav-separator {
311
+ flex: 0 0 1px;
312
+ width: 1px;
313
+ height: 32px;
314
+ margin-inline: 4px;
315
+ background: var(--md-sys-color-outline-variant);
316
+ }
317
+
318
+ .navbar .nav-context {
319
+ color: var(--md-sys-color-on-surface-variant);
320
+ font-size: 14px;
321
+ white-space: nowrap;
322
+ }
323
+
324
+ .navbar ul.nav-actions:not(.dropdown-content) {
325
+ gap: 8px;
326
+ }
327
+
328
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.nav-actions):not(.tabs) > li > a:not(.btn):not(.btn-floating) {
329
+ box-sizing: border-box;
330
+ height: 40px;
331
+ padding-inline: 12px;
332
+ line-height: 40px;
333
+ border-radius: 20px;
334
+ }
335
+
336
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.nav-actions):not(.tabs) > li > a:not(.btn):not(.btn-floating):hover {
337
+ background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
338
+ }
339
+
340
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.nav-actions):not(.tabs) > li.active > a:not(.btn):not(.btn-floating) {
341
+ color: var(--md-sys-color-on-primary-container);
342
+ background: var(--md-sys-color-primary-container);
343
+ }
344
+
345
+ /* Text links use the same centred row as the app bar. The global navbar icon
346
+ rule intentionally sizes standalone icons to the full bar; undo that for
347
+ icons embedded beside link text. */
348
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a:not(.btn):not(.btn-floating):has(> .material-icons.left),
349
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a:not(.btn):not(.btn-floating):has(> .material-icons.right),
350
+ .navbar.appbar-bordered .dropdown-trigger:has(> .material-icons) {
351
+ display: inline-flex;
352
+ align-items: center;
353
+ gap: 8px;
354
+ min-height: 40px;
355
+ font-size: 16px;
356
+ line-height: 1;
357
+ }
358
+
359
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a > .material-icons:is(.left, .right) {
360
+ width: 24px;
361
+ height: 24px;
362
+ margin: 0;
363
+ line-height: 24px;
364
+ }
365
+
366
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a > .material-icons.right {
367
+ order: 1;
368
+ }
369
+
370
+ /* Tabs retain their component geometry and click target instead of inheriting
371
+ navigation-pill spacing. */
372
+ .navbar.appbar-bordered .tabs {
373
+ gap: 0;
374
+ background: transparent;
375
+ }
376
+
377
+ .navbar.appbar-bordered .tabs .tab > a {
378
+ height: 48px;
379
+ min-height: 48px;
380
+ padding-inline: 24px;
381
+ line-height: 48px;
382
+ border-radius: 0;
383
+ }
384
+
385
+ /* Buttons must remain legible and vertically centred in the constrained app
386
+ bar row, including legacy examples using btn-large. */
387
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > :is(.btn, .btn-large) {
388
+ box-sizing: border-box;
389
+ max-height: 48px;
390
+ margin: 0;
391
+ color: var(--md-sys-color-on-primary);
392
+ white-space: nowrap;
393
+ display: inline-flex;
394
+ align-items: center;
395
+ justify-content: center;
396
+ gap: 8px;
397
+ }
398
+
399
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > :is(.btn, .btn-large) > .material-icons {
400
+ flex: 0 0 20px;
401
+ width: 20px;
402
+ height: 20px;
403
+ margin-left: 8px;
404
+ line-height: 20px;
405
+ }
406
+
407
+ .navbar.appbar-bordered .dropdown-trigger {
408
+ flex: 0 0 auto;
409
+ white-space: nowrap;
410
+ }
411
+
412
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > a:has(> .material-icons:only-child) {
413
+ display: grid;
414
+ place-items: center;
415
+ width: 40px;
416
+ padding: 0;
417
+ }
418
+
419
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > a:has(> .material-icons:only-child) > .material-icons {
420
+ width: 24px;
421
+ height: 24px;
422
+ line-height: 24px;
423
+ text-align: center;
424
+ }
425
+
426
+ .navbar ul.nav-actions:not(.dropdown-content) > li > :is(.btn, .btn-floating) {
427
+ margin: 0;
428
+ }
429
+
430
+ .navbar .nav-actions .btn-floating {
431
+ display: inline-grid;
432
+ place-items: center;
433
+ width: 40px;
434
+ height: 40px;
435
+ padding: 0;
436
+ border-radius: 50%;
437
+ box-shadow: none;
438
+ }
439
+
440
+ .navbar .nav-actions .btn-floating > .material-icons {
441
+ display: block;
442
+ width: 24px;
443
+ height: 24px;
444
+ line-height: 24px;
445
+ text-align: center;
446
+ }
447
+
448
+ .navbar .nav-actions .btn > .material-icons {
449
+ height: 16px;
450
+ line-height: 16px;
451
+ }
452
+
171
453
  /* Title */
172
454
  .navbar .nav-title {
173
455
  display: inline-block;
456
+ color: var(--md-sys-color-on-surface);
174
457
  font-size: 32px;
175
- padding: 28px 0;
458
+ padding: 20px 24px 28px;
176
459
  }
177
460
 
178
461
  /* Navbar Search Form */
@@ -235,6 +518,7 @@
235
518
  }
236
519
 
237
520
  .nav,
521
+ .navbar,
238
522
  .navbar .nav-wrapper i,
239
523
  nav a.sidenav-trigger,
240
524
  .navbar a.sidenav-trigger i {
@@ -51,7 +51,10 @@
51
51
 
52
52
  .btn.icon-left i, .btn.icon-right i {
53
53
  position: absolute;
54
+ top: 50%;
54
55
  font-size: var(--btn-font-size-icon);
56
+ line-height: 1;
57
+ transform: translateY(-50%);
55
58
  }
56
59
 
57
60
  .btn.icon-left i {