mtrl 0.2.6 → 0.2.8
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/demo/build.ts +349 -0
- package/demo/index.html +110 -0
- package/demo/main.js +448 -0
- package/demo/styles.css +239 -0
- package/index.ts +18 -0
- package/package.json +14 -3
- package/server.ts +86 -0
- package/src/components/badge/api.ts +70 -63
- package/src/components/badge/badge.ts +16 -2
- package/src/components/badge/config.ts +66 -13
- package/src/components/badge/features.ts +51 -42
- package/src/components/badge/index.ts +27 -2
- package/src/components/badge/types.ts +62 -30
- package/src/components/bottom-app-bar/bottom-app-bar.ts +154 -0
- package/src/components/bottom-app-bar/config.ts +29 -0
- package/src/components/bottom-app-bar/index.ts +17 -0
- package/src/components/bottom-app-bar/types.ts +114 -0
- package/src/components/button/api.ts +5 -0
- package/src/components/button/button.ts +0 -1
- package/src/components/button/config.ts +6 -2
- package/src/components/button/index.ts +10 -2
- package/src/components/button/types.ts +20 -2
- package/src/components/card/card.ts +13 -25
- package/src/components/card/config.ts +83 -30
- package/src/components/card/content.ts +8 -10
- package/src/components/card/features.ts +4 -3
- package/src/components/card/index.ts +29 -2
- package/src/components/card/types.ts +33 -22
- package/src/components/checkbox/config.ts +3 -4
- package/src/components/checkbox/index.ts +1 -2
- package/src/components/checkbox/types.ts +12 -3
- package/src/components/chip/api.ts +170 -221
- package/src/components/chip/chip.ts +34 -302
- package/src/components/chip/config.ts +1 -2
- package/src/components/chip/index.ts +10 -2
- package/src/components/chip/types.ts +224 -35
- package/src/components/datepicker/api.ts +265 -0
- package/src/components/datepicker/config.ts +141 -0
- package/src/components/datepicker/datepicker.ts +341 -0
- package/src/components/datepicker/index.ts +12 -0
- package/src/components/datepicker/render.ts +450 -0
- package/src/components/datepicker/types.ts +397 -0
- package/src/components/datepicker/utils.ts +289 -0
- package/src/components/dialog/api.ts +55 -21
- package/src/components/dialog/config.ts +12 -9
- package/src/components/dialog/dialog.ts +6 -3
- package/src/components/dialog/features.ts +345 -151
- package/src/components/dialog/index.ts +38 -8
- package/src/components/dialog/types.ts +40 -14
- 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 +9 -0
- package/src/components/divider/types.ts +55 -0
- package/src/components/extended-fab/api.ts +141 -0
- package/src/components/extended-fab/config.ts +112 -0
- package/src/components/extended-fab/extended-fab.ts +125 -0
- package/src/components/extended-fab/index.ts +9 -0
- package/src/components/extended-fab/types.ts +304 -0
- package/src/components/fab/api.ts +97 -0
- package/src/components/fab/config.ts +93 -0
- package/src/components/fab/fab.ts +67 -0
- package/src/components/fab/index.ts +9 -0
- package/src/components/fab/types.ts +251 -0
- package/src/components/list/config.ts +4 -5
- package/src/components/list/features.ts +6 -7
- package/src/components/list/index.ts +7 -9
- package/src/components/list/list-item.ts +12 -13
- package/src/components/list/types.ts +50 -5
- package/src/components/list/utils.ts +30 -3
- package/src/components/menu/features/items-manager.ts +9 -9
- package/src/components/menu/features/positioning.ts +7 -7
- package/src/components/menu/features/visibility.ts +7 -7
- package/src/components/menu/index.ts +7 -9
- package/src/components/menu/menu-item.ts +6 -6
- package/src/components/menu/menu.ts +22 -0
- package/src/components/menu/types.ts +29 -10
- package/src/components/menu/utils.ts +67 -0
- package/src/components/navigation/api.ts +78 -50
- package/src/components/navigation/config.ts +22 -10
- package/src/components/navigation/features/items.ts +284 -0
- package/src/components/navigation/index.ts +0 -6
- package/src/components/navigation/nav-item.ts +70 -33
- package/src/components/navigation/navigation.ts +53 -3
- package/src/components/navigation/types.ts +117 -70
- package/src/components/progress/api.ts +2 -3
- package/src/components/progress/config.ts +2 -3
- package/src/components/progress/index.ts +0 -1
- package/src/components/progress/progress.ts +1 -2
- package/src/components/progress/types.ts +186 -33
- package/src/components/radios/config.ts +1 -1
- package/src/components/radios/index.ts +0 -1
- package/src/components/radios/types.ts +0 -7
- package/src/components/search/api.ts +203 -0
- package/src/components/search/config.ts +86 -0
- package/src/components/search/features/index.ts +4 -0
- package/src/components/search/features/search.ts +717 -0
- package/src/components/search/features/states.ts +169 -0
- package/src/components/search/features/structure.ts +197 -0
- package/src/components/search/index.ts +7 -0
- package/src/components/search/search.ts +52 -0
- package/src/components/search/types.ts +175 -0
- package/src/components/segmented-button/config.ts +80 -0
- package/src/components/segmented-button/index.ts +4 -0
- package/src/components/segmented-button/segment.ts +154 -0
- package/src/components/segmented-button/segmented-button.ts +249 -0
- package/src/components/segmented-button/types.ts +254 -0
- package/src/components/slider/accessibility.md +5 -5
- package/src/components/slider/api.ts +41 -120
- package/src/components/slider/config.ts +51 -47
- 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 +136 -206
- package/src/components/slider/features/ui.ts +145 -206
- package/src/components/slider/index.ts +2 -11
- package/src/components/slider/slider.ts +9 -12
- package/src/components/slider/types.ts +67 -26
- package/src/components/snackbar/config.ts +2 -3
- package/src/components/snackbar/constants.ts +0 -32
- package/src/components/snackbar/index.ts +0 -1
- package/src/components/snackbar/position.ts +9 -1
- package/src/components/snackbar/types.ts +122 -46
- package/src/components/switch/config.ts +2 -3
- package/src/components/switch/index.ts +0 -1
- package/src/components/switch/types.ts +3 -2
- package/src/components/tabs/config.ts +3 -4
- package/src/components/tabs/features.ts +4 -2
- package/src/components/tabs/index.ts +0 -15
- package/src/components/tabs/indicator.ts +73 -13
- package/src/components/tabs/tab-api.ts +12 -4
- package/src/components/tabs/tab.ts +18 -6
- package/src/components/tabs/types.ts +23 -5
- package/src/components/textfield/config.ts +2 -3
- package/src/components/textfield/index.ts +0 -1
- package/src/components/textfield/types.ts +17 -3
- package/src/components/timepicker/README.md +277 -0
- package/src/components/timepicker/api.ts +632 -0
- package/src/components/timepicker/clockdial.ts +482 -0
- package/src/components/timepicker/config.ts +228 -0
- package/src/components/timepicker/index.ts +3 -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/tooltip/api.ts +1 -1
- package/src/components/tooltip/config.ts +27 -6
- package/src/components/tooltip/index.ts +0 -1
- package/src/components/tooltip/types.ts +13 -3
- 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/features/icon.ts +3 -1
- package/src/core/compose/features/ripple.ts +4 -1
- package/src/core/compose/features/textlabel.ts +23 -2
- package/src/core/dom/create.ts +5 -0
- package/src/index.ts +9 -0
- package/src/styles/abstract/_theme.scss +9 -1
- package/src/styles/components/_badge.scss +182 -0
- package/src/styles/components/_bottom-app-bar.scss +103 -0
- package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -10
- package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -2
- package/src/styles/components/_datepicker.scss +358 -0
- package/src/styles/components/_dialog.scss +259 -0
- package/src/styles/components/_divider.scss +57 -0
- package/src/styles/components/_extended-fab.scss +267 -0
- package/src/styles/components/_fab.scss +225 -0
- package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +1 -0
- package/src/styles/components/_search.scss +306 -0
- package/src/styles/components/_segmented-button.scss +117 -0
- package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +83 -24
- package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -2
- package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +95 -33
- package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +70 -67
- package/src/styles/components/_timepicker.scss +451 -0
- package/src/styles/components/_top-app-bar.scss +225 -0
- package/src/styles/main.scss +98 -49
- 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/badge/_styles.scss +0 -174
- package/src/components/badge/constants.ts +0 -30
- package/src/components/button/constants.ts +0 -11
- package/src/components/card/constants.ts +0 -84
- package/src/components/dialog/_styles.scss +0 -213
- package/src/components/dialog/constants.ts +0 -32
- package/src/components/menu/constants.ts +0 -154
- package/src/components/navigation/constants.ts +0 -200
- package/src/components/navigation/features/items.js +0 -192
- package/src/components/progress/constants.ts +0 -29
- package/src/components/slider/features/appearance.ts +0 -94
- package/src/components/slider/features/disabled.ts +0 -68
- package/src/components/slider/features/events.ts +0 -164
- package/src/components/slider/features/interactions.ts +0 -396
- package/src/components/slider/features/keyboard.ts +0 -233
- package/src/components/switch/constants.ts +0 -80
- package/src/components/tabs/constants.ts +0 -89
- package/src/core/collection/adapters/mongodb.js +0 -232
- /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
- /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
- /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
- /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
- /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
- /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
- /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
- /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
- /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
- /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
- /package/src/styles/utilities/{_color.scss → _colors.scss} +0 -0
|
@@ -8,7 +8,7 @@ export interface TabIndicatorConfig {
|
|
|
8
8
|
/** Height of the indicator in pixels */
|
|
9
9
|
height?: number;
|
|
10
10
|
/** Width strategy - fixed size or dynamic based on tab width */
|
|
11
|
-
widthStrategy?: 'fixed' | 'dynamic' | 'content';
|
|
11
|
+
widthStrategy?: 'fixed' | 'dynamic' | 'content' | 'auto';
|
|
12
12
|
/** Fixed width in pixels when using fixed strategy */
|
|
13
13
|
fixedWidth?: number;
|
|
14
14
|
/** Animation duration in milliseconds */
|
|
@@ -21,6 +21,8 @@ export interface TabIndicatorConfig {
|
|
|
21
21
|
prefix?: string;
|
|
22
22
|
/** Custom color for the indicator */
|
|
23
23
|
color?: string;
|
|
24
|
+
/** Tabs variant (primary or secondary) */
|
|
25
|
+
variant?: string;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/**
|
|
@@ -47,12 +49,13 @@ export interface TabIndicator {
|
|
|
47
49
|
* Default configuration for tab indicator
|
|
48
50
|
*/
|
|
49
51
|
const DEFAULT_CONFIG: TabIndicatorConfig = {
|
|
50
|
-
widthStrategy: '
|
|
52
|
+
widthStrategy: 'auto', // Changed to 'auto' to match variant behavior
|
|
51
53
|
fixedWidth: 40,
|
|
52
54
|
animationDuration: 250,
|
|
53
55
|
animationTiming: 'cubic-bezier(0.4, 0, 0.2, 1)',
|
|
54
56
|
visible: true,
|
|
55
|
-
prefix: 'mtrl'
|
|
57
|
+
prefix: 'mtrl',
|
|
58
|
+
variant: 'primary'
|
|
56
59
|
};
|
|
57
60
|
|
|
58
61
|
/**
|
|
@@ -81,17 +84,38 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
|
|
|
81
84
|
let currentTab: TabComponent | null = null;
|
|
82
85
|
|
|
83
86
|
/**
|
|
84
|
-
* Calculates indicator width based on strategy
|
|
87
|
+
* Calculates indicator width based on strategy and variant
|
|
85
88
|
* @param tab - Target tab
|
|
86
89
|
* @returns Width in pixels
|
|
87
90
|
*/
|
|
88
91
|
const calculateWidth = (tab: TabComponent): number => {
|
|
92
|
+
// Use auto strategy to determine based on variant
|
|
93
|
+
if (mergedConfig.widthStrategy === 'auto') {
|
|
94
|
+
// For secondary tabs, use full tab width
|
|
95
|
+
if (mergedConfig.variant === 'secondary') {
|
|
96
|
+
return tab.element.offsetWidth;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// For primary tabs (default), use text label width
|
|
100
|
+
const textElement = tab.element.querySelector(`.${prefix}-tab-text`) ||
|
|
101
|
+
tab.element.querySelector(`.${prefix}-button-text`);
|
|
102
|
+
|
|
103
|
+
if (textElement) {
|
|
104
|
+
return textElement.clientWidth;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Fallback to dynamic if text element not found
|
|
108
|
+
return Math.max(tab.element.offsetWidth / 2, 30);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Handle other strategies when explicitly set
|
|
89
112
|
switch (mergedConfig.widthStrategy) {
|
|
90
113
|
case 'dynamic':
|
|
91
114
|
return Math.max(tab.element.offsetWidth / 2, 30);
|
|
92
115
|
case 'content':
|
|
93
116
|
// Try to match content width
|
|
94
|
-
const text = tab.element.querySelector(`.${prefix}-button-text`)
|
|
117
|
+
const text = tab.element.querySelector(`.${prefix}-button-text`) ||
|
|
118
|
+
tab.element.querySelector(`.${prefix}-tab-text`);
|
|
95
119
|
if (text) {
|
|
96
120
|
return Math.max(text.clientWidth, 30);
|
|
97
121
|
}
|
|
@@ -126,6 +150,46 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
|
|
|
126
150
|
};
|
|
127
151
|
};
|
|
128
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Calculates indicator position based on variant and width
|
|
155
|
+
* @param tab - Target tab
|
|
156
|
+
* @param indicatorWidth - Width of the indicator
|
|
157
|
+
* @returns {Object} Position information
|
|
158
|
+
*/
|
|
159
|
+
const calculatePosition = (tab: TabComponent, indicatorWidth: number): { left: number } => {
|
|
160
|
+
const { left, width: tabWidth } = getTabPosition(tab.element);
|
|
161
|
+
|
|
162
|
+
// For primary tabs with text label width, center under the text
|
|
163
|
+
if (mergedConfig.variant !== 'secondary' &&
|
|
164
|
+
(mergedConfig.widthStrategy === 'auto' || mergedConfig.widthStrategy === 'content')) {
|
|
165
|
+
const textElement = tab.element.querySelector(`.${prefix}-tab-text`) ||
|
|
166
|
+
tab.element.querySelector(`.${prefix}-button-text`);
|
|
167
|
+
|
|
168
|
+
if (textElement) {
|
|
169
|
+
// Get text element position relative to tab
|
|
170
|
+
const textRect = textElement.getBoundingClientRect();
|
|
171
|
+
const tabRect = tab.element.getBoundingClientRect();
|
|
172
|
+
const textLeft = textRect.left - tabRect.left;
|
|
173
|
+
|
|
174
|
+
// Center indicator under text
|
|
175
|
+
return {
|
|
176
|
+
left: left + textLeft
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// For secondary tabs or when no text element found
|
|
182
|
+
// For centered indicators, center in the tab
|
|
183
|
+
if (indicatorWidth < tabWidth) {
|
|
184
|
+
return {
|
|
185
|
+
left: left + ((tabWidth - indicatorWidth) / 2)
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// For full-width indicators
|
|
190
|
+
return { left };
|
|
191
|
+
};
|
|
192
|
+
|
|
129
193
|
/**
|
|
130
194
|
* Moves indicator to specified tab
|
|
131
195
|
* @param tab - Target tab
|
|
@@ -140,15 +204,11 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
|
|
|
140
204
|
// Store current tab for later updates
|
|
141
205
|
currentTab = tab;
|
|
142
206
|
|
|
143
|
-
// Calculate indicator width
|
|
207
|
+
// Calculate indicator width based on strategy and variant
|
|
144
208
|
const width = calculateWidth(tab);
|
|
145
209
|
|
|
146
|
-
//
|
|
147
|
-
const { left
|
|
148
|
-
|
|
149
|
-
// Calculate position to center indicator on tab
|
|
150
|
-
const tabCenter = left + (tabWidth / 2);
|
|
151
|
-
const indicatorLeft = tabCenter - (width / 2);
|
|
210
|
+
// Calculate position based on width and variant
|
|
211
|
+
const { left } = calculatePosition(tab, width);
|
|
152
212
|
|
|
153
213
|
// Apply position immediately if requested
|
|
154
214
|
if (immediate) {
|
|
@@ -160,7 +220,7 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
|
|
|
160
220
|
|
|
161
221
|
// Update position and width
|
|
162
222
|
element.style.width = `${width}px`;
|
|
163
|
-
element.style.transform = `translateX(${
|
|
223
|
+
element.style.transform = `translateX(${left}px)`;
|
|
164
224
|
|
|
165
225
|
// Restore transition after immediate update
|
|
166
226
|
if (immediate) {
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
// src/components/tabs/tab-api.ts
|
|
2
2
|
import { TabComponent } from './types';
|
|
3
|
-
import { TAB_STATES, TAB_LAYOUT } from './constants';
|
|
4
3
|
import { BadgeComponent } from '../badge/types';
|
|
5
4
|
import createBadge from '../badge';
|
|
6
5
|
|
|
6
|
+
const TAB_LAYOUT = {
|
|
7
|
+
/** Icon-only tab layout */
|
|
8
|
+
ICON_ONLY: 'icon-only',
|
|
9
|
+
/** Text-only tab layout */
|
|
10
|
+
TEXT_ONLY: 'text-only',
|
|
11
|
+
/** Icon and text layout */
|
|
12
|
+
ICON_AND_TEXT: 'icon-and-text'
|
|
13
|
+
}
|
|
14
|
+
|
|
7
15
|
/**
|
|
8
16
|
* API options for a Tab component
|
|
9
17
|
*/
|
|
@@ -82,7 +90,7 @@ export const withTabAPI = ({ disabled, lifecycle, button }: ApiOptions) =>
|
|
|
82
90
|
* Activates the tab
|
|
83
91
|
*/
|
|
84
92
|
activate() {
|
|
85
|
-
component.element.classList.add(`${component.getClass('tab')}
|
|
93
|
+
component.element.classList.add(`${component.getClass('tab')}--active`);
|
|
86
94
|
component.element.setAttribute('aria-selected', 'true');
|
|
87
95
|
return this;
|
|
88
96
|
},
|
|
@@ -91,7 +99,7 @@ export const withTabAPI = ({ disabled, lifecycle, button }: ApiOptions) =>
|
|
|
91
99
|
* Deactivates the tab
|
|
92
100
|
*/
|
|
93
101
|
deactivate() {
|
|
94
|
-
component.element.classList.remove(`${component.getClass('tab')}
|
|
102
|
+
component.element.classList.remove(`${component.getClass('tab')}--active`);
|
|
95
103
|
component.element.setAttribute('aria-selected', 'false');
|
|
96
104
|
return this;
|
|
97
105
|
},
|
|
@@ -100,7 +108,7 @@ export const withTabAPI = ({ disabled, lifecycle, button }: ApiOptions) =>
|
|
|
100
108
|
* Checks if the tab is active
|
|
101
109
|
*/
|
|
102
110
|
isActive() {
|
|
103
|
-
return component.element.classList.contains(`${component.getClass('tab')}
|
|
111
|
+
return component.element.classList.contains(`${component.getClass('tab')}--active`);
|
|
104
112
|
},
|
|
105
113
|
|
|
106
114
|
/**
|
|
@@ -8,6 +8,18 @@ import { createTabConfig } from './config';
|
|
|
8
8
|
import createButton from '../button';
|
|
9
9
|
import createBadge from '../badge';
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Tab layout types
|
|
13
|
+
*/
|
|
14
|
+
export const TAB_LAYOUT = {
|
|
15
|
+
/** Icon-only tab layout */
|
|
16
|
+
ICON_ONLY: 'icon-only',
|
|
17
|
+
/** Text-only tab layout */
|
|
18
|
+
TEXT_ONLY: 'text-only',
|
|
19
|
+
/** Icon and text layout */
|
|
20
|
+
ICON_AND_TEXT: 'icon-and-text'
|
|
21
|
+
};
|
|
22
|
+
|
|
11
23
|
/**
|
|
12
24
|
* Creates a new Tab component following MD3 guidelines
|
|
13
25
|
* @param {TabConfig} config - Tab configuration object
|
|
@@ -49,7 +61,7 @@ export const createTab = (config: TabConfig = {}): TabComponent => {
|
|
|
49
61
|
// Set up tab accessibility attributes
|
|
50
62
|
baseComponent.element.setAttribute('role', 'tab');
|
|
51
63
|
baseComponent.element.setAttribute('aria-selected',
|
|
52
|
-
baseConfig.state ===
|
|
64
|
+
baseConfig.state === 'active' ? 'true' : 'false');
|
|
53
65
|
|
|
54
66
|
// For better accessibility
|
|
55
67
|
if (baseConfig.value) {
|
|
@@ -58,8 +70,8 @@ export const createTab = (config: TabConfig = {}): TabComponent => {
|
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
// Add active state if specified in config
|
|
61
|
-
if (baseConfig.state ===
|
|
62
|
-
baseComponent.element.classList.add(`${baseComponent.getClass('tab')}
|
|
73
|
+
if (baseConfig.state === 'active') {
|
|
74
|
+
baseComponent.element.classList.add(`${baseComponent.getClass('tab')}--active`);
|
|
63
75
|
}
|
|
64
76
|
|
|
65
77
|
// Forward button events to our component
|
|
@@ -95,7 +107,7 @@ export const createTab = (config: TabConfig = {}): TabComponent => {
|
|
|
95
107
|
},
|
|
96
108
|
|
|
97
109
|
activate() {
|
|
98
|
-
this.element.classList.add(`${this.getClass('tab')}
|
|
110
|
+
this.element.classList.add(`${this.getClass('tab')}--active`);
|
|
99
111
|
this.element.setAttribute('aria-selected', 'true');
|
|
100
112
|
|
|
101
113
|
// Dispatch event for screen readers
|
|
@@ -109,13 +121,13 @@ export const createTab = (config: TabConfig = {}): TabComponent => {
|
|
|
109
121
|
},
|
|
110
122
|
|
|
111
123
|
deactivate() {
|
|
112
|
-
this.element.classList.remove(`${this.getClass('tab')}
|
|
124
|
+
this.element.classList.remove(`${this.getClass('tab')}--active`);
|
|
113
125
|
this.element.setAttribute('aria-selected', 'false');
|
|
114
126
|
return this;
|
|
115
127
|
},
|
|
116
128
|
|
|
117
129
|
isActive() {
|
|
118
|
-
return this.element.classList.contains(`${this.getClass('tab')}
|
|
130
|
+
return this.element.classList.contains(`${this.getClass('tab')}--active`);
|
|
119
131
|
},
|
|
120
132
|
|
|
121
133
|
enable() {
|
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
// src/components/tabs/types.ts
|
|
2
|
-
import { TABS_VARIANTS, TAB_STATES, TAB_LAYOUT } from './constants';
|
|
3
2
|
import { BadgeComponent } from '../badge/types';
|
|
4
3
|
import { TabIndicator } from './indicator';
|
|
5
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Available tabs variants
|
|
7
|
+
*/
|
|
8
|
+
export type TabsVariant = 'primary' | 'secondary';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Available tabs states
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export type TabStates = 'active' | 'inactive' | 'disabled';
|
|
15
|
+
|
|
6
16
|
/**
|
|
7
17
|
* Configuration for the tab indicator
|
|
8
18
|
* @category Components
|
|
@@ -10,8 +20,14 @@ import { TabIndicator } from './indicator';
|
|
|
10
20
|
export interface IndicatorConfig {
|
|
11
21
|
/** Height of the indicator in pixels */
|
|
12
22
|
height?: number;
|
|
13
|
-
/**
|
|
14
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Width strategy for the indicator
|
|
25
|
+
* - 'fixed': Uses a fixed width defined by fixedWidth
|
|
26
|
+
* - 'dynamic': Uses half the tab width
|
|
27
|
+
* - 'content': Uses the text content width
|
|
28
|
+
* - 'auto': Adapts based on variant (primary: text width, secondary: full tab width)
|
|
29
|
+
*/
|
|
30
|
+
widthStrategy?: 'fixed' | 'dynamic' | 'content' | 'auto';
|
|
15
31
|
/** Fixed width in pixels (when using fixed strategy) */
|
|
16
32
|
fixedWidth?: number;
|
|
17
33
|
/** Animation duration in milliseconds */
|
|
@@ -20,6 +36,8 @@ export interface IndicatorConfig {
|
|
|
20
36
|
animationTiming?: string;
|
|
21
37
|
/** Custom color for the indicator */
|
|
22
38
|
color?: string;
|
|
39
|
+
/** Tab variant (primary or secondary) */
|
|
40
|
+
variant?: string;
|
|
23
41
|
}
|
|
24
42
|
|
|
25
43
|
/**
|
|
@@ -47,7 +65,7 @@ export interface TabConfig {
|
|
|
47
65
|
* Tab state that determines if it's the active destination
|
|
48
66
|
* @default 'inactive'
|
|
49
67
|
*/
|
|
50
|
-
state?:
|
|
68
|
+
state?: TabStates | string;
|
|
51
69
|
|
|
52
70
|
/**
|
|
53
71
|
* Whether the tab is initially disabled
|
|
@@ -146,7 +164,7 @@ export interface TabsConfig {
|
|
|
146
164
|
* Tabs variant (primary or secondary)
|
|
147
165
|
* @default 'primary'
|
|
148
166
|
*/
|
|
149
|
-
variant?:
|
|
167
|
+
variant?: TabsVariant | string;
|
|
150
168
|
|
|
151
169
|
/**
|
|
152
170
|
* Initial tabs to create
|
|
@@ -5,14 +5,13 @@ import {
|
|
|
5
5
|
BaseComponentConfig
|
|
6
6
|
} from '../../core/config/component-config';
|
|
7
7
|
import { TextfieldConfig, BaseComponent, ApiOptions } from './types';
|
|
8
|
-
import { TEXTFIELD_VARIANTS, TEXTFIELD_TYPES } from './constants';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Default configuration for the Textfield component
|
|
12
11
|
*/
|
|
13
12
|
export const defaultConfig: TextfieldConfig = {
|
|
14
|
-
type:
|
|
15
|
-
variant:
|
|
13
|
+
type: 'text',
|
|
14
|
+
variant: 'filled'
|
|
16
15
|
};
|
|
17
16
|
|
|
18
17
|
/**
|
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
// src/components/textfield/types.ts
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Available Textfield variants
|
|
5
|
+
*/
|
|
6
|
+
export type TextfieldVariant = 'filled' | 'outlined';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Available Textfield states
|
|
10
|
+
*/
|
|
11
|
+
export type TextfieldStates = 'active' | 'inactive' | 'disabled';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Available Textfield types
|
|
15
|
+
*/
|
|
16
|
+
export type TextfieldTypes = 'text' | 'password' | 'email' | 'number' | 'tel' | 'url' | 'search' | 'multiline';
|
|
3
17
|
|
|
4
18
|
/**
|
|
5
19
|
* Configuration interface for the Textfield component
|
|
6
20
|
*/
|
|
7
21
|
export interface TextfieldConfig {
|
|
8
22
|
/** Input type (text, password, email, etc.) */
|
|
9
|
-
type?:
|
|
23
|
+
type?: TextfieldTypes | string;
|
|
10
24
|
|
|
11
25
|
/** Visual variant (filled, outlined) */
|
|
12
|
-
variant?:
|
|
26
|
+
variant?: TextfieldVariant | string;
|
|
13
27
|
|
|
14
28
|
/** Input name attribute */
|
|
15
29
|
name?: string;
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
# TimePicker Component
|
|
2
|
+
|
|
3
|
+
The `TimePicker` component provides a user-friendly interface for selecting a time. It supports both dial-based
|
|
4
|
+
and keyboard input selection methods, 12-hour and 24-hour formats, and customizable settings.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- **Two Selection Methods**: Choose between a dial interface or keyboard input
|
|
9
|
+
- **Time Format Support**: 12-hour (AM/PM) or 24-hour format
|
|
10
|
+
- **Flexible Configuration**: Customize title, action buttons, and constraints
|
|
11
|
+
- **Responsive Layout**: Adapts between vertical and horizontal layouts based on device orientation
|
|
12
|
+
- **Accessibility**: Keyboard navigation and screen reader support
|
|
13
|
+
- **Full TypeScript Support**: Comprehensive type definitions for better development experience
|
|
14
|
+
|
|
15
|
+
## Basic Usage
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import { createTimePicker } from 'mtrl';
|
|
19
|
+
|
|
20
|
+
// Create a basic time picker
|
|
21
|
+
const timePicker = createTimePicker({
|
|
22
|
+
title: 'Select time',
|
|
23
|
+
value: '14:30', // Initial time (2:30 PM)
|
|
24
|
+
onConfirm: (time) => {
|
|
25
|
+
console.log('Selected time:', time);
|
|
26
|
+
// Update UI with selected time
|
|
27
|
+
document.getElementById('time-input').value = time;
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Create an input to trigger the time picker
|
|
32
|
+
const timeInput = document.createElement('input');
|
|
33
|
+
timeInput.id = 'time-input';
|
|
34
|
+
timeInput.type = 'text';
|
|
35
|
+
timeInput.readOnly = true;
|
|
36
|
+
timeInput.value = '14:30';
|
|
37
|
+
timeInput.addEventListener('click', () => {
|
|
38
|
+
timePicker.open();
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Add input to the DOM
|
|
42
|
+
document.body.appendChild(timeInput);
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration Options
|
|
46
|
+
|
|
47
|
+
The `createTimePicker` function accepts a configuration object with the following properties:
|
|
48
|
+
|
|
49
|
+
| Property | Type | Default | Description |
|
|
50
|
+
|----------|------|---------|-------------|
|
|
51
|
+
| `value` | `string` | Current time | Initial time value in 24-hour format (HH:MM or HH:MM:SS) |
|
|
52
|
+
| `type` | `TIME_PICKER_TYPE` | `DIAL` | Type of time picker to display (DIAL or INPUT) |
|
|
53
|
+
| `format` | `TIME_FORMAT` | `AMPM` | Time format to use (AMPM for 12-hour or MILITARY for 24-hour) |
|
|
54
|
+
| `orientation` | `TIME_PICKER_ORIENTATION` | `VERTICAL` | Layout orientation (VERTICAL or HORIZONTAL) |
|
|
55
|
+
| `title` | `string` | `undefined` | Title text for the time picker |
|
|
56
|
+
| `showSeconds` | `boolean` | `false` | Whether to show seconds selector |
|
|
57
|
+
| `class` | `string` | `undefined` | Additional CSS classes to add to the time picker |
|
|
58
|
+
| `closeOnSelect` | `boolean` | `true` | Whether to close the picker when time is selected |
|
|
59
|
+
| `minTime` | `string` | `undefined` | Minimum selectable time in 24-hour format (HH:MM) |
|
|
60
|
+
| `maxTime` | `string` | `undefined` | Maximum selectable time in 24-hour format (HH:MM) |
|
|
61
|
+
| `minuteStep` | `number` | `1` | Step interval for minute selection |
|
|
62
|
+
| `secondStep` | `number` | `1` | Step interval for second selection |
|
|
63
|
+
| `cancelText` | `string` | `'Cancel'` | Custom text for cancel button |
|
|
64
|
+
| `confirmText` | `string` | `'OK'` | Custom text for confirm button |
|
|
65
|
+
| `isOpen` | `boolean` | `false` | Whether the time picker is initially visible |
|
|
66
|
+
| `container` | `string \| HTMLElement` | `document.body` | CSS selector or element to append the time picker to |
|
|
67
|
+
| `clockIcon` | `string` | Default SVG | Custom icon for the clock button |
|
|
68
|
+
| `keyboardIcon` | `string` | Default SVG | Custom icon for the keyboard button |
|
|
69
|
+
| `onChange` | `function` | `undefined` | Callback when time is changed |
|
|
70
|
+
| `onOpen` | `function` | `undefined` | Callback when time picker is opened |
|
|
71
|
+
| `onClose` | `function` | `undefined` | Callback when time picker is closed |
|
|
72
|
+
| `onConfirm` | `function` | `undefined` | Callback when time is confirmed |
|
|
73
|
+
| `onCancel` | `function` | `undefined` | Callback when time picker is canceled |
|
|
74
|
+
|
|
75
|
+
## API Methods
|
|
76
|
+
|
|
77
|
+
Once created, the TimePicker instance provides the following methods:
|
|
78
|
+
|
|
79
|
+
### Open, Close, and Toggle
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
// Open the time picker
|
|
83
|
+
timePicker.open();
|
|
84
|
+
|
|
85
|
+
// Close the time picker
|
|
86
|
+
timePicker.close();
|
|
87
|
+
|
|
88
|
+
// Toggle the time picker open/closed state
|
|
89
|
+
timePicker.toggle();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Get and Set Value
|
|
93
|
+
|
|
94
|
+
```javascript
|
|
95
|
+
// Get the current time value as a string (HH:MM or HH:MM:SS)
|
|
96
|
+
const timeString = timePicker.getValue();
|
|
97
|
+
|
|
98
|
+
// Get the current time as an object with hours, minutes, seconds, and period
|
|
99
|
+
const timeObject = timePicker.getTimeObject();
|
|
100
|
+
|
|
101
|
+
// Set the time value
|
|
102
|
+
timePicker.setValue('15:45');
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Change Configuration
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// Switch between dial and input types
|
|
109
|
+
timePicker.setType(TIME_PICKER_TYPE.INPUT);
|
|
110
|
+
|
|
111
|
+
// Switch between 12-hour and 24-hour formats
|
|
112
|
+
timePicker.setFormat(TIME_FORMAT.MILITARY);
|
|
113
|
+
|
|
114
|
+
// Change the orientation
|
|
115
|
+
timePicker.setOrientation(TIME_PICKER_ORIENTATION.HORIZONTAL);
|
|
116
|
+
|
|
117
|
+
// Update the title
|
|
118
|
+
timePicker.setTitle('Choose departure time');
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Event Handling
|
|
122
|
+
|
|
123
|
+
```javascript
|
|
124
|
+
// Listen for time changes
|
|
125
|
+
timePicker.on('change', (time) => {
|
|
126
|
+
console.log('Time changed:', time);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// Listen for time picker opening
|
|
130
|
+
timePicker.on('open', () => {
|
|
131
|
+
console.log('Time picker opened');
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Remove an event listener
|
|
135
|
+
timePicker.off('change', myHandler);
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Cleanup
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
// Destroy the time picker and clean up resources
|
|
142
|
+
timePicker.destroy();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## Advanced Examples
|
|
146
|
+
|
|
147
|
+
### 24-Hour Time Picker with Seconds
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
import { createTimePicker, TIME_FORMAT } from 'mtrl';
|
|
151
|
+
|
|
152
|
+
const timePicker = createTimePicker({
|
|
153
|
+
title: 'Select time',
|
|
154
|
+
value: '18:30:45',
|
|
155
|
+
format: TIME_FORMAT.MILITARY,
|
|
156
|
+
showSeconds: true,
|
|
157
|
+
onConfirm: (time) => {
|
|
158
|
+
console.log('Selected time with seconds:', time);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Time Range Selection
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
import { createTimePicker } from 'mtrl';
|
|
167
|
+
|
|
168
|
+
// Start time picker
|
|
169
|
+
const startTimePicker = createTimePicker({
|
|
170
|
+
title: 'Select start time',
|
|
171
|
+
value: '09:00',
|
|
172
|
+
minTime: '08:00',
|
|
173
|
+
maxTime: '16:00',
|
|
174
|
+
onConfirm: (time) => {
|
|
175
|
+
startTimeInput.value = time;
|
|
176
|
+
|
|
177
|
+
// Update end time constraints
|
|
178
|
+
const [hours, minutes] = time.split(':');
|
|
179
|
+
const startDate = new Date();
|
|
180
|
+
startDate.setHours(parseInt(hours, 10), parseInt(minutes, 10));
|
|
181
|
+
|
|
182
|
+
// Add minimum 30 minutes to start time
|
|
183
|
+
const minEndDate = new Date(startDate);
|
|
184
|
+
minEndDate.setMinutes(minEndDate.getMinutes() + 30);
|
|
185
|
+
|
|
186
|
+
endTimePicker.minTime = `${minEndDate.getHours()}:${minEndDate.getMinutes()}`;
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// End time picker
|
|
191
|
+
const endTimePicker = createTimePicker({
|
|
192
|
+
title: 'Select end time',
|
|
193
|
+
value: '17:00',
|
|
194
|
+
minTime: '09:30', // Initial minimum (30 min after default start)
|
|
195
|
+
onConfirm: (time) => {
|
|
196
|
+
endTimeInput.value = time;
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Accessibility
|
|
202
|
+
|
|
203
|
+
The TimePicker component is designed with accessibility in mind:
|
|
204
|
+
|
|
205
|
+
- Keyboard navigation for all controls
|
|
206
|
+
- ARIA attributes for screen reader support
|
|
207
|
+
- Large touch targets for better mobile accessibility
|
|
208
|
+
- High contrast colors and clear visual indicators
|
|
209
|
+
- Support for reducing motion based on user preferences
|
|
210
|
+
|
|
211
|
+
## Customization
|
|
212
|
+
|
|
213
|
+
### Custom Styling
|
|
214
|
+
|
|
215
|
+
You can customize the appearance of the TimePicker by overriding the CSS variables and classes:
|
|
216
|
+
|
|
217
|
+
```css
|
|
218
|
+
/* Custom time picker styling */
|
|
219
|
+
.mtrl-time-picker-dialog {
|
|
220
|
+
--mtrl-primary: #6200ee;
|
|
221
|
+
--mtrl-on-primary: #ffffff;
|
|
222
|
+
--mtrl-surface-container-high: #f5f5f5;
|
|
223
|
+
--mtrl-on-surface: #1d1d1d;
|
|
224
|
+
|
|
225
|
+
/* Add custom shadows */
|
|
226
|
+
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/* Custom dial styling */
|
|
230
|
+
.mtrl-time-picker-dial-face {
|
|
231
|
+
background-color: rgba(98, 0, 238, 0.05);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* Custom buttons */
|
|
235
|
+
.mtrl-time-picker-confirm {
|
|
236
|
+
font-weight: bold;
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Custom Icons
|
|
241
|
+
|
|
242
|
+
You can also provide custom icons for the clock and keyboard buttons:
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
const timePicker = createTimePicker({
|
|
246
|
+
// Other configuration...
|
|
247
|
+
clockIcon: '<svg>...</svg>', // Custom clock icon
|
|
248
|
+
keyboardIcon: '<svg>...</svg>' // Custom keyboard icon
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
## Browser Support
|
|
253
|
+
|
|
254
|
+
The TimePicker component supports all modern browsers:
|
|
255
|
+
|
|
256
|
+
- Chrome (latest)
|
|
257
|
+
- Firefox (latest)
|
|
258
|
+
- Safari (latest)
|
|
259
|
+
- Edge (latest)
|
|
260
|
+
|
|
261
|
+
## TypeScript Support
|
|
262
|
+
|
|
263
|
+
The `TimePicker` component includes comprehensive TypeScript definitions:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
import { createTimePicker, TIME_PICKER_TYPE, TIME_FORMAT, TimePickerConfig } from 'mtrl';
|
|
267
|
+
|
|
268
|
+
// Configuration with TypeScript
|
|
269
|
+
const config: TimePickerConfig = {
|
|
270
|
+
title: 'Select time',
|
|
271
|
+
value: '14:30',
|
|
272
|
+
type: TIME_PICKER_TYPE.DIAL,
|
|
273
|
+
format: TIME_FORMAT.AMPM
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const timePicker = createTimePicker(config);
|
|
277
|
+
```
|