kmaterialize 2.3.3-1.0.13 → 2.3.3-1.0.16

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,35 @@
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
+ }
165
+
166
+ .navbar.appbar-bordered .nav-wrapper.nav-wrapper-centered > .brand-logo.center {
167
+ position: static;
168
+ order: 2;
169
+ flex: 0 0 auto;
170
+ left: auto;
171
+ transform: none;
172
+ }
173
+
149
174
  .navbar .brand-logo.right {
150
175
  right: 0.5rem;
151
176
  padding: 0;
@@ -168,11 +193,188 @@
168
193
  margin-right: 15px;
169
194
  }
170
195
 
196
+ /* Structured MD3 app-bar content. These hooks keep branded demos and app
197
+ shells from falling back to bare, loosely spaced anchor elements. */
198
+ .navbar.appbar-bordered {
199
+ overflow: hidden;
200
+ background: var(--md-sys-color-surface-variant);
201
+ border: 1px solid var(--md-sys-color-outline-variant);
202
+ border-radius: 16px;
203
+ box-shadow: 0 1px 2px color-mix(in srgb, var(--md-sys-color-shadow) 18%, transparent);
204
+ }
205
+
206
+ /* Extended app bars may contain tabs and a halfway FAB. Keep the rounded
207
+ surface on its children, but let the FAB extend below the bar. */
208
+ .navbar.appbar-bordered.nav-extended {
209
+ overflow: visible;
210
+ }
211
+
212
+ .navbar.appbar-bordered.nav-extended .nav-wrapper {
213
+ border-radius: 15px 15px 0 0;
214
+ }
215
+
216
+ .navbar.appbar-bordered.nav-extended .nav-content {
217
+ color: var(--md-sys-color-on-surface);
218
+ background: var(--md-sys-color-surface-variant);
219
+ border-radius: 0 0 15px 15px;
220
+ }
221
+
222
+ .navbar.appbar-bordered .nav-wrapper {
223
+ gap: 12px;
224
+ padding-inline: 12px;
225
+ background: var(--md-sys-color-surface-variant);
226
+ }
227
+
228
+ .navbar .brand-logo .brand-logo-image {
229
+ box-sizing: border-box;
230
+ width: 40px;
231
+ height: 40px;
232
+ margin-right: 10px;
233
+ padding: 8px;
234
+ object-fit: contain;
235
+ background: var(--md-sys-color-primary-container);
236
+ border-radius: 12px;
237
+ }
238
+
239
+ .navbar .nav-separator {
240
+ flex: 0 0 1px;
241
+ width: 1px;
242
+ height: 32px;
243
+ margin-inline: 4px;
244
+ background: var(--md-sys-color-outline-variant);
245
+ }
246
+
247
+ .navbar .nav-context {
248
+ color: var(--md-sys-color-on-surface-variant);
249
+ font-size: 14px;
250
+ white-space: nowrap;
251
+ }
252
+
253
+ .navbar ul.nav-actions:not(.dropdown-content) {
254
+ gap: 8px;
255
+ }
256
+
257
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.nav-actions):not(.tabs) > li > a:not(.btn):not(.btn-floating) {
258
+ box-sizing: border-box;
259
+ height: 40px;
260
+ padding-inline: 12px;
261
+ line-height: 40px;
262
+ border-radius: 20px;
263
+ }
264
+
265
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.nav-actions):not(.tabs) > li > a:not(.btn):not(.btn-floating):hover {
266
+ background: color-mix(in srgb, var(--md-sys-color-on-surface) 8%, transparent);
267
+ }
268
+
269
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.nav-actions):not(.tabs) > li.active > a:not(.btn):not(.btn-floating) {
270
+ color: var(--md-sys-color-on-primary-container);
271
+ background: var(--md-sys-color-primary-container);
272
+ }
273
+
274
+ /* Text links use the same centred row as the app bar. The global navbar icon
275
+ rule intentionally sizes standalone icons to the full bar; undo that for
276
+ icons embedded beside link text. */
277
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a:not(.btn):not(.btn-floating):has(> .material-icons.left),
278
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a:not(.btn):not(.btn-floating):has(> .material-icons.right),
279
+ .navbar.appbar-bordered .dropdown-trigger:has(> .material-icons) {
280
+ display: inline-flex;
281
+ align-items: center;
282
+ gap: 8px;
283
+ min-height: 40px;
284
+ font-size: 16px;
285
+ line-height: 1;
286
+ }
287
+
288
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a > .material-icons:is(.left, .right) {
289
+ width: 24px;
290
+ height: 24px;
291
+ margin: 0;
292
+ line-height: 24px;
293
+ }
294
+
295
+ .navbar.appbar-bordered ul:not(.dropdown-content):not(.tabs) > li > a > .material-icons.right {
296
+ order: 1;
297
+ }
298
+
299
+ /* Tabs retain their component geometry and click target instead of inheriting
300
+ navigation-pill spacing. */
301
+ .navbar.appbar-bordered .tabs {
302
+ gap: 0;
303
+ background: transparent;
304
+ }
305
+
306
+ .navbar.appbar-bordered .tabs .tab > a {
307
+ height: 48px;
308
+ min-height: 48px;
309
+ padding-inline: 24px;
310
+ line-height: 48px;
311
+ border-radius: 0;
312
+ }
313
+
314
+ /* Buttons must remain legible and vertically centred in the constrained app
315
+ bar row, including legacy examples using btn-large. */
316
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > :is(.btn, .btn-large) {
317
+ box-sizing: border-box;
318
+ max-height: 48px;
319
+ margin: 0;
320
+ color: var(--md-sys-color-on-primary);
321
+ white-space: nowrap;
322
+ }
323
+
324
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > :is(.btn, .btn-large) > .material-icons {
325
+ width: 20px;
326
+ height: 20px;
327
+ margin-left: 8px;
328
+ line-height: 20px;
329
+ }
330
+
331
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > a:has(> .material-icons:only-child) {
332
+ display: grid;
333
+ place-items: center;
334
+ width: 40px;
335
+ padding: 0;
336
+ }
337
+
338
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > a:has(> .material-icons:only-child) > .material-icons {
339
+ width: 24px;
340
+ height: 24px;
341
+ line-height: 24px;
342
+ text-align: center;
343
+ }
344
+
345
+ .navbar ul.nav-actions:not(.dropdown-content) > li > :is(.btn, .btn-floating) {
346
+ margin: 0;
347
+ }
348
+
349
+ .navbar .nav-actions .btn-floating {
350
+ display: inline-grid;
351
+ place-items: center;
352
+ width: 40px;
353
+ height: 40px;
354
+ padding: 0;
355
+ border-radius: 50%;
356
+ box-shadow: none;
357
+ }
358
+
359
+ .navbar .nav-actions .btn-floating > .material-icons {
360
+ display: block;
361
+ width: 24px;
362
+ height: 24px;
363
+ line-height: 24px;
364
+ text-align: center;
365
+ }
366
+
367
+ .navbar .nav-actions .btn > .material-icons {
368
+ height: 16px;
369
+ line-height: 16px;
370
+ }
371
+
171
372
  /* Title */
172
373
  .navbar .nav-title {
173
374
  display: inline-block;
375
+ color: var(--md-sys-color-on-surface);
174
376
  font-size: 32px;
175
- padding: 28px 0;
377
+ padding: 20px 24px 28px;
176
378
  }
177
379
 
178
380
  /* Navbar Search Form */
@@ -235,6 +437,7 @@
235
437
  }
236
438
 
237
439
  .nav,
440
+ .navbar,
238
441
  .navbar .nav-wrapper i,
239
442
  nav a.sidenav-trigger,
240
443
  .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 {