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
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
// src/components/badge/features.ts
|
|
2
|
-
import { BADGE_VARIANTS, BADGE_SIZES, BADGE_COLORS, BADGE_POSITIONS } from './constants';
|
|
3
2
|
import { BadgeConfig } from './types';
|
|
3
|
+
import { formatBadgeLabel } from './config';
|
|
4
|
+
|
|
5
|
+
// Common badge variants
|
|
6
|
+
const VARIANT_SMALL = 'small';
|
|
7
|
+
const VARIANT_LARGE = 'large';
|
|
8
|
+
|
|
9
|
+
// Common badge colors
|
|
10
|
+
const COLOR_ERROR = 'error';
|
|
11
|
+
|
|
12
|
+
// Common badge positions
|
|
13
|
+
const POSITION_TOP_RIGHT = 'top-right';
|
|
4
14
|
|
|
5
15
|
/**
|
|
6
16
|
* Higher-order function that adds visibility control features to a component
|
|
@@ -64,15 +74,24 @@ export const withVisibility = () => component => {
|
|
|
64
74
|
*/
|
|
65
75
|
export const withVariant = (config: BadgeConfig) => component => {
|
|
66
76
|
// Get variant from config with fallback to default
|
|
67
|
-
const variant = config.variant ||
|
|
68
|
-
|
|
69
|
-
// Apply variant class
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
const variant = config.variant || VARIANT_LARGE;
|
|
78
|
+
|
|
79
|
+
// Apply variant class
|
|
80
|
+
component.element.classList.add(`${component.getClass('badge')}--${variant}`);
|
|
81
|
+
|
|
82
|
+
// Small badges (dot variant) don't have text
|
|
83
|
+
if (variant === VARIANT_SMALL) {
|
|
84
|
+
component.element.textContent = '';
|
|
85
|
+
component.element.setAttribute('aria-hidden', 'true');
|
|
86
|
+
} else {
|
|
87
|
+
// Add accessibility for large badges
|
|
88
|
+
component.element.setAttribute('role', 'status');
|
|
72
89
|
|
|
73
|
-
//
|
|
74
|
-
if (
|
|
75
|
-
|
|
90
|
+
// Set the label if available and variant is large
|
|
91
|
+
if (config.label !== undefined && config.label !== '') {
|
|
92
|
+
// Format the label according to max value
|
|
93
|
+
const formattedLabel = formatBadgeLabel(config.label, config.max);
|
|
94
|
+
component.element.textContent = formattedLabel;
|
|
76
95
|
}
|
|
77
96
|
}
|
|
78
97
|
|
|
@@ -86,7 +105,7 @@ export const withVariant = (config: BadgeConfig) => component => {
|
|
|
86
105
|
*/
|
|
87
106
|
export const withColor = (config: BadgeConfig) => component => {
|
|
88
107
|
// Get color from config with fallback to default
|
|
89
|
-
const color = config.color ||
|
|
108
|
+
const color = config.color || COLOR_ERROR;
|
|
90
109
|
|
|
91
110
|
// Apply color class
|
|
92
111
|
component.element.classList.add(`${component.getClass('badge')}--${color}`);
|
|
@@ -94,40 +113,22 @@ export const withColor = (config: BadgeConfig) => component => {
|
|
|
94
113
|
return component;
|
|
95
114
|
};
|
|
96
115
|
|
|
97
|
-
/**
|
|
98
|
-
* Higher-order function that adds size features to a badge component
|
|
99
|
-
* @param {BadgeConfig} config - Badge configuration
|
|
100
|
-
* @returns {Function} Component enhancer with size features
|
|
101
|
-
*/
|
|
102
|
-
export const withSize = (config: BadgeConfig) => component => {
|
|
103
|
-
// Get size from config with fallback to default
|
|
104
|
-
const size = config.size || BADGE_SIZES.MEDIUM;
|
|
105
|
-
|
|
106
|
-
// Apply size class if not medium (default)
|
|
107
|
-
if (size !== BADGE_SIZES.MEDIUM) {
|
|
108
|
-
component.element.classList.add(`${component.getClass('badge')}--${size}`);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return component;
|
|
112
|
-
};
|
|
113
|
-
|
|
114
116
|
/**
|
|
115
117
|
* Higher-order function that adds positioning features to a badge component
|
|
116
118
|
* @param {BadgeConfig} config - Badge configuration
|
|
117
119
|
* @returns {Function} Component enhancer with positioning features
|
|
118
120
|
*/
|
|
119
121
|
export const withPosition = (config: BadgeConfig) => component => {
|
|
120
|
-
// Skip for standalone badges
|
|
121
|
-
if (config.standalone) {
|
|
122
|
-
return component;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
122
|
// Get position from config with fallback to default
|
|
126
|
-
const position = config.position ||
|
|
123
|
+
const position = config.position || POSITION_TOP_RIGHT;
|
|
127
124
|
|
|
128
|
-
// Apply position class
|
|
125
|
+
// Apply position class
|
|
129
126
|
component.element.classList.add(`${component.getClass('badge')}--${position}`);
|
|
130
|
-
|
|
127
|
+
|
|
128
|
+
// If there's a target, add positioned class
|
|
129
|
+
if (config.target) {
|
|
130
|
+
component.element.classList.add(`${component.getClass('badge')}--positioned`);
|
|
131
|
+
}
|
|
131
132
|
|
|
132
133
|
return component;
|
|
133
134
|
};
|
|
@@ -138,8 +139,8 @@ export const withPosition = (config: BadgeConfig) => component => {
|
|
|
138
139
|
* @returns {Function} Component enhancer with max value features
|
|
139
140
|
*/
|
|
140
141
|
export const withMax = (config: BadgeConfig) => component => {
|
|
141
|
-
// Skip if no max is defined
|
|
142
|
-
if (config.max === undefined) {
|
|
142
|
+
// Skip if no max is defined or for small badges
|
|
143
|
+
if (config.max === undefined || config.variant === VARIANT_SMALL) {
|
|
143
144
|
return component;
|
|
144
145
|
}
|
|
145
146
|
|
|
@@ -147,9 +148,14 @@ export const withMax = (config: BadgeConfig) => component => {
|
|
|
147
148
|
component.config.max = config.max;
|
|
148
149
|
|
|
149
150
|
// Apply max formatting if needed
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
component.element.
|
|
151
|
+
if (config.label !== undefined && config.label !== '') {
|
|
152
|
+
const formattedLabel = formatBadgeLabel(config.label, config.max);
|
|
153
|
+
component.element.textContent = formattedLabel;
|
|
154
|
+
|
|
155
|
+
// Add overflow class if label was truncated
|
|
156
|
+
if (typeof config.label === 'number' && config.label > config.max) {
|
|
157
|
+
component.element.classList.add(`${component.getClass('badge')}--overflow`);
|
|
158
|
+
}
|
|
153
159
|
}
|
|
154
160
|
|
|
155
161
|
return component;
|
|
@@ -161,8 +167,8 @@ export const withMax = (config: BadgeConfig) => component => {
|
|
|
161
167
|
* @returns {Function} Component enhancer with attachment features
|
|
162
168
|
*/
|
|
163
169
|
export const withAttachment = (config: BadgeConfig) => component => {
|
|
164
|
-
// Skip
|
|
165
|
-
if (
|
|
170
|
+
// Skip if no target is provided
|
|
171
|
+
if (!config.target) {
|
|
166
172
|
return component;
|
|
167
173
|
}
|
|
168
174
|
|
|
@@ -170,6 +176,9 @@ export const withAttachment = (config: BadgeConfig) => component => {
|
|
|
170
176
|
const wrapper = document.createElement('div');
|
|
171
177
|
wrapper.classList.add(component.getClass('badge-wrapper'));
|
|
172
178
|
|
|
179
|
+
// Make sure positioning context is correct
|
|
180
|
+
wrapper.style.position = 'relative';
|
|
181
|
+
|
|
173
182
|
// Replace the target with the wrapper
|
|
174
183
|
const parent = config.target.parentNode;
|
|
175
184
|
if (parent) {
|
|
@@ -1,4 +1,29 @@
|
|
|
1
1
|
// src/components/badge/index.ts
|
|
2
2
|
export { default } from './badge';
|
|
3
|
-
export {
|
|
4
|
-
|
|
3
|
+
export { BadgeConfig, BadgeComponent, BadgeVariant, BadgeColor, BadgePosition } from './types';
|
|
4
|
+
|
|
5
|
+
// Export common badge constants for convenience and backward compatibility
|
|
6
|
+
export const BADGE_VARIANTS = {
|
|
7
|
+
SMALL: 'small',
|
|
8
|
+
LARGE: 'large'
|
|
9
|
+
} as const;
|
|
10
|
+
|
|
11
|
+
export const BADGE_COLORS = {
|
|
12
|
+
ERROR: 'error',
|
|
13
|
+
PRIMARY: 'primary',
|
|
14
|
+
SECONDARY: 'secondary',
|
|
15
|
+
TERTIARY: 'tertiary',
|
|
16
|
+
SUCCESS: 'success',
|
|
17
|
+
WARNING: 'warning',
|
|
18
|
+
INFO: 'info'
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
21
|
+
export const BADGE_POSITIONS = {
|
|
22
|
+
TOP_RIGHT: 'top-right',
|
|
23
|
+
TOP_LEFT: 'top-left',
|
|
24
|
+
BOTTOM_RIGHT: 'bottom-right',
|
|
25
|
+
BOTTOM_LEFT: 'bottom-left'
|
|
26
|
+
} as const;
|
|
27
|
+
|
|
28
|
+
// Export max characters constant
|
|
29
|
+
export const BADGE_MAX_CHARACTERS = 4;
|
|
@@ -1,43 +1,78 @@
|
|
|
1
1
|
// src/components/badge/types.ts
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Badge variant types
|
|
5
|
+
* @category Components
|
|
6
|
+
*/
|
|
7
|
+
export type BadgeVariant = 'small' | 'large';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Badge color types
|
|
11
|
+
* @category Components
|
|
12
|
+
*/
|
|
13
|
+
export type BadgeColor = 'error' | 'primary' | 'secondary' | 'tertiary' | 'success' | 'warning' | 'info';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Badge position types
|
|
17
|
+
* @category Components
|
|
18
|
+
*/
|
|
19
|
+
export type BadgePosition = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
|
|
3
20
|
|
|
4
21
|
/**
|
|
5
22
|
* Configuration interface for the Badge component
|
|
23
|
+
* Following Material Design 3 specifications
|
|
6
24
|
*/
|
|
7
25
|
export interface BadgeConfig {
|
|
8
|
-
/**
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
26
|
+
/**
|
|
27
|
+
* Badge variant (small dot or large numbered)
|
|
28
|
+
* Small badge (6dp) or Large badge (16dp height)
|
|
29
|
+
* @default 'large'
|
|
30
|
+
*/
|
|
31
|
+
variant?: BadgeVariant | string;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Badge color (error is default)
|
|
35
|
+
* @default 'error'
|
|
36
|
+
*/
|
|
37
|
+
color?: BadgeColor | string;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Badge position relative to its container
|
|
41
|
+
* @default 'top-right'
|
|
42
|
+
*/
|
|
43
|
+
position?: BadgePosition | string;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Text label inside the badge (for large badges)
|
|
47
|
+
* Up to 4 characters, with "+" for overflow
|
|
48
|
+
*/
|
|
49
|
+
label?: string | number;
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Maximum value to display (shows "{max}+" if label exceeds max)
|
|
53
|
+
* Usually 999+ for large numbers
|
|
54
|
+
*/
|
|
24
55
|
max?: number;
|
|
25
56
|
|
|
26
57
|
/** Whether the badge should be visible */
|
|
27
58
|
visible?: boolean;
|
|
28
59
|
|
|
29
|
-
/** Whether the badge should be standalone (not attached to another element) */
|
|
30
|
-
standalone?: boolean;
|
|
31
|
-
|
|
32
60
|
/** Target element to which badge will be attached */
|
|
33
61
|
target?: HTMLElement;
|
|
34
62
|
|
|
35
63
|
/** Additional CSS classes */
|
|
36
64
|
class?: string;
|
|
65
|
+
|
|
66
|
+
/** CSS class prefix */
|
|
67
|
+
prefix?: string;
|
|
68
|
+
|
|
69
|
+
/** Component name */
|
|
70
|
+
componentName?: string;
|
|
37
71
|
}
|
|
38
72
|
|
|
39
73
|
/**
|
|
40
74
|
* Badge component interface
|
|
75
|
+
* Following Material Design 3 specifications
|
|
41
76
|
*/
|
|
42
77
|
export interface BadgeComponent {
|
|
43
78
|
/** Badge element */
|
|
@@ -46,11 +81,11 @@ export interface BadgeComponent {
|
|
|
46
81
|
/** Badge wrapper element (if badge is attached to target) */
|
|
47
82
|
wrapper?: HTMLElement;
|
|
48
83
|
|
|
49
|
-
/** Sets badge text
|
|
50
|
-
|
|
84
|
+
/** Sets badge text label */
|
|
85
|
+
setLabel: (label: string | number) => BadgeComponent;
|
|
51
86
|
|
|
52
|
-
/** Gets badge text
|
|
53
|
-
|
|
87
|
+
/** Gets badge text label */
|
|
88
|
+
getLabel: () => string;
|
|
54
89
|
|
|
55
90
|
/** Shows the badge */
|
|
56
91
|
show: () => BadgeComponent;
|
|
@@ -68,16 +103,13 @@ export interface BadgeComponent {
|
|
|
68
103
|
setMax: (max: number) => BadgeComponent;
|
|
69
104
|
|
|
70
105
|
/** Sets badge color */
|
|
71
|
-
setColor: (color:
|
|
106
|
+
setColor: (color: BadgeColor | string) => BadgeComponent;
|
|
72
107
|
|
|
73
108
|
/** Sets badge variant */
|
|
74
|
-
setVariant: (variant:
|
|
75
|
-
|
|
76
|
-
/** Sets badge size */
|
|
77
|
-
setSize: (size: keyof typeof BADGE_SIZES | BADGE_SIZES) => BadgeComponent;
|
|
109
|
+
setVariant: (variant: BadgeVariant | string) => BadgeComponent;
|
|
78
110
|
|
|
79
111
|
/** Sets badge position */
|
|
80
|
-
setPosition: (position:
|
|
112
|
+
setPosition: (position: BadgePosition | string) => BadgeComponent;
|
|
81
113
|
|
|
82
114
|
/** Attaches badge to a target element */
|
|
83
115
|
attachTo: (target: HTMLElement) => BadgeComponent;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// src/components/bottom-app-bar/bottom-app-bar.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/bottom-app-bar
|
|
4
|
+
* @description Bottom 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 } from './config';
|
|
17
|
+
import { BottomAppBar, BottomAppBarConfig } from './types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a bottom app bar component
|
|
21
|
+
*
|
|
22
|
+
* @param {BottomAppBarConfig} config - Configuration options
|
|
23
|
+
* @returns {BottomAppBar} Bottom app bar component instance
|
|
24
|
+
*/
|
|
25
|
+
export const createBottomAppBar = (config: BottomAppBarConfig = {}): BottomAppBar => {
|
|
26
|
+
// Process configuration with defaults
|
|
27
|
+
const componentConfig = createConfig(config);
|
|
28
|
+
|
|
29
|
+
// Create base component
|
|
30
|
+
const component = createBase(componentConfig);
|
|
31
|
+
|
|
32
|
+
// Create actions container
|
|
33
|
+
const actionsContainer = document.createElement('div');
|
|
34
|
+
actionsContainer.className = `${component.getClass('bottom-app-bar')}-actions`;
|
|
35
|
+
|
|
36
|
+
// FAB container for proper positioning
|
|
37
|
+
const fabContainer = document.createElement('div');
|
|
38
|
+
fabContainer.className = `${component.getClass('bottom-app-bar')}-fab-container`;
|
|
39
|
+
|
|
40
|
+
// Apply Element enhancer
|
|
41
|
+
const enhancedComponent = withElement({
|
|
42
|
+
tag: componentConfig.tag,
|
|
43
|
+
componentName: 'bottom-app-bar',
|
|
44
|
+
className: [
|
|
45
|
+
componentConfig.hasFab ? `${component.getClass('bottom-app-bar')}--with-fab` : '',
|
|
46
|
+
componentConfig.fabPosition === 'center' ? `${component.getClass('bottom-app-bar')}--fab-center` : '',
|
|
47
|
+
componentConfig.class
|
|
48
|
+
],
|
|
49
|
+
attrs: {
|
|
50
|
+
role: 'toolbar',
|
|
51
|
+
'aria-label': 'Bottom app bar'
|
|
52
|
+
},
|
|
53
|
+
interactive: true
|
|
54
|
+
})(component);
|
|
55
|
+
|
|
56
|
+
// Apply events enhancer for component events
|
|
57
|
+
const withEventsComponent = withEvents()(enhancedComponent);
|
|
58
|
+
|
|
59
|
+
// Apply lifecycle enhancer for cleanup
|
|
60
|
+
const withLifecycleComponent = withLifecycle()(withEventsComponent);
|
|
61
|
+
|
|
62
|
+
// Append actions and FAB containers to the main element
|
|
63
|
+
withLifecycleComponent.element.appendChild(actionsContainer);
|
|
64
|
+
withLifecycleComponent.element.appendChild(fabContainer);
|
|
65
|
+
|
|
66
|
+
// Flag to track visibility
|
|
67
|
+
let isVisible = true;
|
|
68
|
+
|
|
69
|
+
// Previous scroll position for determining scroll direction
|
|
70
|
+
let prevScrollY = window.scrollY;
|
|
71
|
+
|
|
72
|
+
// Handle scrolling behavior if autoHide is enabled
|
|
73
|
+
if (componentConfig.autoHide) {
|
|
74
|
+
const handleScroll = () => {
|
|
75
|
+
const currentScrollY = window.scrollY;
|
|
76
|
+
|
|
77
|
+
// Determine scroll direction
|
|
78
|
+
if (currentScrollY > prevScrollY + 10) {
|
|
79
|
+
// Scrolling down - hide the bottom bar
|
|
80
|
+
if (isVisible) {
|
|
81
|
+
bottomBar.hide();
|
|
82
|
+
componentConfig.onVisibilityChange?.(false);
|
|
83
|
+
}
|
|
84
|
+
} else if (currentScrollY < prevScrollY - 10) {
|
|
85
|
+
// Scrolling up - show the bottom bar
|
|
86
|
+
if (!isVisible) {
|
|
87
|
+
bottomBar.show();
|
|
88
|
+
componentConfig.onVisibilityChange?.(true);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
prevScrollY = currentScrollY;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Add scroll event listener
|
|
96
|
+
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
97
|
+
|
|
98
|
+
// Clean up event listener on destroy
|
|
99
|
+
const originalDestroy = withLifecycleComponent.lifecycle.destroy;
|
|
100
|
+
withLifecycleComponent.lifecycle.destroy = () => {
|
|
101
|
+
window.removeEventListener('scroll', handleScroll);
|
|
102
|
+
originalDestroy();
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const bottomBar: BottomAppBar = {
|
|
107
|
+
...withLifecycleComponent,
|
|
108
|
+
|
|
109
|
+
addAction(button: HTMLElement) {
|
|
110
|
+
actionsContainer.appendChild(button);
|
|
111
|
+
return this;
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
addFab(fab: HTMLElement) {
|
|
115
|
+
// Clear existing FAB if any
|
|
116
|
+
fabContainer.innerHTML = '';
|
|
117
|
+
|
|
118
|
+
// Add the new FAB
|
|
119
|
+
fabContainer.appendChild(fab);
|
|
120
|
+
|
|
121
|
+
// Update component class to indicate it has a FAB
|
|
122
|
+
this.element.classList.add(`${component.getClass('bottom-app-bar')}--with-fab`);
|
|
123
|
+
|
|
124
|
+
return this;
|
|
125
|
+
},
|
|
126
|
+
|
|
127
|
+
show() {
|
|
128
|
+
this.element.classList.remove(`${component.getClass('bottom-app-bar')}--hidden`);
|
|
129
|
+
isVisible = true;
|
|
130
|
+
return this;
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
hide() {
|
|
134
|
+
this.element.classList.add(`${component.getClass('bottom-app-bar')}--hidden`);
|
|
135
|
+
isVisible = false;
|
|
136
|
+
return this;
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
isVisible() {
|
|
140
|
+
return isVisible;
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
getActionsContainer() {
|
|
144
|
+
return actionsContainer;
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
// Set the appropriate styles for transitions if needed
|
|
149
|
+
if (componentConfig.autoHide && componentConfig.transitionDuration) {
|
|
150
|
+
bottomBar.element.style.transition = `transform ${componentConfig.transitionDuration}ms ease-in-out`;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return bottomBar;
|
|
154
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/components/bottom-app-bar/config.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/bottom-app-bar
|
|
4
|
+
* @description Configuration for bottom app bar component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createComponentConfig, BaseComponentConfig } from '../../core/config/component-config';
|
|
8
|
+
import { PREFIX } from '../../core/config';
|
|
9
|
+
import { BottomAppBarConfig } from './types';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Default configuration for bottom app bar
|
|
13
|
+
*/
|
|
14
|
+
export const defaultConfig: Partial<BottomAppBarConfig> = {
|
|
15
|
+
tag: 'div',
|
|
16
|
+
hasFab: false,
|
|
17
|
+
fabPosition: 'end',
|
|
18
|
+
autoHide: false,
|
|
19
|
+
transitionDuration: 300
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates the configuration for a bottom app bar component
|
|
24
|
+
*
|
|
25
|
+
* @param {BottomAppBarConfig} config - User provided configuration
|
|
26
|
+
* @returns {BottomAppBarConfig} Complete configuration with defaults applied
|
|
27
|
+
*/
|
|
28
|
+
export const createConfig = (config: BottomAppBarConfig = {} as BottomAppBarConfig): BottomAppBarConfig =>
|
|
29
|
+
createComponentConfig(defaultConfig, config, 'bottom-app-bar') as BottomAppBarConfig;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// src/components/bottom-app-bar/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/bottom-app-bar
|
|
4
|
+
* @description Bottom app bar component for mobile interfaces
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createBottomAppBar } from './bottom-app-bar';
|
|
8
|
+
|
|
9
|
+
export default createBottomAppBar;
|
|
10
|
+
export { createBottomAppBar };
|
|
11
|
+
export type { BottomAppBarConfig, BottomAppBar } from './types';
|
|
12
|
+
|
|
13
|
+
// Export position constants for convenience and backward compatibility
|
|
14
|
+
export const FAB_POSITIONS = {
|
|
15
|
+
CENTER: 'center',
|
|
16
|
+
END: 'end'
|
|
17
|
+
} as const;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// src/components/bottom-app-bar/types.ts
|
|
2
|
+
/**
|
|
3
|
+
* @module components/bottom-app-bar
|
|
4
|
+
* @description Type definitions for Bottom App Bar component
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ElementComponent } from '../../core/compose';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* FAB position type for Bottom App Bar
|
|
11
|
+
* @category Components
|
|
12
|
+
*/
|
|
13
|
+
export type FabPosition = 'center' | 'end';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Configuration options for Bottom App Bar component
|
|
17
|
+
* @category Components
|
|
18
|
+
*/
|
|
19
|
+
export interface BottomAppBarConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Element to use for the container
|
|
22
|
+
* @default 'div'
|
|
23
|
+
*/
|
|
24
|
+
tag?: string;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Whether to show FAB in the bottom bar
|
|
28
|
+
* @default false
|
|
29
|
+
*/
|
|
30
|
+
hasFab?: boolean;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* FAB position in bottom bar
|
|
34
|
+
* @default 'end'
|
|
35
|
+
*/
|
|
36
|
+
fabPosition?: FabPosition;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Additional CSS classes to apply
|
|
40
|
+
*/
|
|
41
|
+
class?: string;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Whether to enable auto-hide on scroll
|
|
45
|
+
* @default false
|
|
46
|
+
*/
|
|
47
|
+
autoHide?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Transition duration for show/hide in ms
|
|
51
|
+
* @default 300
|
|
52
|
+
*/
|
|
53
|
+
transitionDuration?: number;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Optional callback when scrolling shows/hides the bar
|
|
57
|
+
*/
|
|
58
|
+
onVisibilityChange?: (visible: boolean) => void;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Component prefix for class names
|
|
62
|
+
* @default 'mtrl'
|
|
63
|
+
*/
|
|
64
|
+
prefix?: string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Component name for class generation
|
|
68
|
+
*/
|
|
69
|
+
componentName?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Bottom App Bar component interface
|
|
74
|
+
* @category Components
|
|
75
|
+
*/
|
|
76
|
+
export interface BottomAppBar extends ElementComponent {
|
|
77
|
+
/**
|
|
78
|
+
* Adds an action button to the bottom bar
|
|
79
|
+
* @param {HTMLElement} button - Button element to add
|
|
80
|
+
* @returns {BottomAppBar} BottomAppBar instance for chaining
|
|
81
|
+
*/
|
|
82
|
+
addAction: (button: HTMLElement) => BottomAppBar;
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Adds a floating action button to the bottom bar
|
|
86
|
+
* @param {HTMLElement} fab - FAB element to add
|
|
87
|
+
* @returns {BottomAppBar} BottomAppBar instance for chaining
|
|
88
|
+
*/
|
|
89
|
+
addFab: (fab: HTMLElement) => BottomAppBar;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Shows the bottom bar
|
|
93
|
+
* @returns {BottomAppBar} BottomAppBar instance for chaining
|
|
94
|
+
*/
|
|
95
|
+
show: () => BottomAppBar;
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Hides the bottom bar
|
|
99
|
+
* @returns {BottomAppBar} BottomAppBar instance for chaining
|
|
100
|
+
*/
|
|
101
|
+
hide: () => BottomAppBar;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Checks if the bottom bar is visible
|
|
105
|
+
* @returns {boolean} Whether the bottom bar is visible
|
|
106
|
+
*/
|
|
107
|
+
isVisible: () => boolean;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Get the actions container element
|
|
111
|
+
* @returns {HTMLElement} Actions container element
|
|
112
|
+
*/
|
|
113
|
+
getActionsContainer: () => HTMLElement;
|
|
114
|
+
}
|
|
@@ -73,6 +73,11 @@ export const withAPI = ({ disabled, lifecycle }: ApiOptions) =>
|
|
|
73
73
|
getIcon() {
|
|
74
74
|
return component.icon.getIcon();
|
|
75
75
|
},
|
|
76
|
+
|
|
77
|
+
setAriaLabel(label: string) {
|
|
78
|
+
component.element.setAttribute('aria-label', label);
|
|
79
|
+
return this;
|
|
80
|
+
},
|
|
76
81
|
|
|
77
82
|
destroy() {
|
|
78
83
|
lifecycle.destroy();
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
} from '../../core/compose/features';
|
|
14
14
|
import { withAPI } from './api';
|
|
15
15
|
import { ButtonConfig } from './types';
|
|
16
|
-
import { BUTTON_VARIANTS } from './constants';
|
|
17
16
|
import { createBaseConfig, getElementConfig, getApiConfig } from './config';
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -5,13 +5,12 @@ import {
|
|
|
5
5
|
BaseComponentConfig
|
|
6
6
|
} from '../../core/config/component-config';
|
|
7
7
|
import { ButtonConfig } from './types';
|
|
8
|
-
import { BUTTON_VARIANTS } from './constants';
|
|
9
8
|
|
|
10
9
|
/**
|
|
11
10
|
* Default configuration for the Button component
|
|
12
11
|
*/
|
|
13
12
|
export const defaultConfig: ButtonConfig = {
|
|
14
|
-
variant:
|
|
13
|
+
variant: 'filled',
|
|
15
14
|
type: 'button'
|
|
16
15
|
// Don't set disabled: false as default - it should be undefined by default
|
|
17
16
|
};
|
|
@@ -45,6 +44,11 @@ export const getElementConfig = (config: ButtonConfig) => {
|
|
|
45
44
|
attrs.value = config.value;
|
|
46
45
|
}
|
|
47
46
|
|
|
47
|
+
// Add aria-label attribute for accessibility
|
|
48
|
+
if (config.ariaLabel) {
|
|
49
|
+
attrs['aria-label'] = config.ariaLabel;
|
|
50
|
+
}
|
|
51
|
+
|
|
48
52
|
return createElementConfig(config, {
|
|
49
53
|
tag: 'button',
|
|
50
54
|
attrs,
|