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
package/index.ts
CHANGED
|
@@ -3,23 +3,32 @@ import {
|
|
|
3
3
|
createLayout,
|
|
4
4
|
createElement,
|
|
5
5
|
createBadge,
|
|
6
|
+
createBottomAppBar,
|
|
6
7
|
createButton,
|
|
8
|
+
createDatePicker,
|
|
9
|
+
createFab,
|
|
10
|
+
createExtendedFab,
|
|
11
|
+
createSegmentedButton,
|
|
7
12
|
createCard,
|
|
8
13
|
createCarousel,
|
|
9
14
|
createCheckbox,
|
|
10
15
|
createChip,
|
|
11
16
|
createDialog,
|
|
17
|
+
createDivider,
|
|
12
18
|
createList,
|
|
13
19
|
createMenu,
|
|
14
20
|
createNavigation,
|
|
15
21
|
createProgress,
|
|
16
22
|
createRadios,
|
|
23
|
+
createSearch,
|
|
17
24
|
createSheet,
|
|
18
25
|
createSlider,
|
|
19
26
|
createSnackbar,
|
|
20
27
|
createTabs,
|
|
21
28
|
createTextfield,
|
|
29
|
+
createTimePicker,
|
|
22
30
|
createTooltip,
|
|
31
|
+
createTopAppBar,
|
|
23
32
|
createSwitch
|
|
24
33
|
} from './src/index.js'
|
|
25
34
|
|
|
@@ -27,22 +36,31 @@ export {
|
|
|
27
36
|
createLayout,
|
|
28
37
|
createElement,
|
|
29
38
|
createBadge,
|
|
39
|
+
createBottomAppBar,
|
|
30
40
|
createButton,
|
|
41
|
+
createDatePicker,
|
|
42
|
+
createFab,
|
|
43
|
+
createExtendedFab,
|
|
44
|
+
createSegmentedButton,
|
|
31
45
|
createCard,
|
|
32
46
|
createCarousel,
|
|
33
47
|
createCheckbox,
|
|
34
48
|
createChip,
|
|
35
49
|
createDialog,
|
|
50
|
+
createDivider,
|
|
36
51
|
createList,
|
|
37
52
|
createMenu,
|
|
38
53
|
createNavigation,
|
|
39
54
|
createProgress,
|
|
40
55
|
createRadios,
|
|
56
|
+
createSearch,
|
|
41
57
|
createSheet,
|
|
42
58
|
createSlider,
|
|
43
59
|
createSnackbar,
|
|
44
60
|
createTabs,
|
|
45
61
|
createTextfield,
|
|
62
|
+
createTimePicker,
|
|
46
63
|
createTooltip,
|
|
64
|
+
createTopAppBar,
|
|
47
65
|
createSwitch
|
|
48
66
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mtrl",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "A functional TypeScript/JavaScript component library with composable architecture based on Material Design 3",
|
|
5
|
+
"author": "floor",
|
|
6
|
+
"license": "MIT License",
|
|
5
7
|
"keywords": [
|
|
6
8
|
"component",
|
|
7
9
|
"library",
|
|
@@ -13,8 +15,18 @@
|
|
|
13
15
|
"material design 3",
|
|
14
16
|
"md3"
|
|
15
17
|
],
|
|
18
|
+
"type": "module",
|
|
16
19
|
"main": "index.js",
|
|
17
20
|
"scripts": {
|
|
21
|
+
"start": "bun run server.js",
|
|
22
|
+
"dev": "bun --watch server.js",
|
|
23
|
+
"build:demo": "bun run demo/build.ts",
|
|
24
|
+
"dev:demo": "bun run demo/build.ts --watch",
|
|
25
|
+
"clean:demo": "rm -rf demo/dist",
|
|
26
|
+
"build:js": "bun build demo/main.js --outfile=demo/dist/bundle.js --format=esm",
|
|
27
|
+
"build:css": "sass src/styles/main.scss:demo/dist/styles.css --style=compressed",
|
|
28
|
+
"build": "bun run build:js && bun run build:css",
|
|
29
|
+
"build:watch": "bun build --watch demo/main.js --outfile=demo/dist/bundle.js & sass --watch src/styles/main.scss:demo/dist/styles.css",
|
|
18
30
|
"test": "bun test",
|
|
19
31
|
"test:watch": "bun test --watch",
|
|
20
32
|
"test:coverage": "bun test --coverage",
|
|
@@ -29,9 +41,8 @@
|
|
|
29
41
|
"type": "git",
|
|
30
42
|
"url": "https://github.com/floor/mtrl.git"
|
|
31
43
|
},
|
|
32
|
-
"author": "floor",
|
|
33
|
-
"license": "MIT License",
|
|
34
44
|
"devDependencies": {
|
|
45
|
+
"sass": "^1.85.1",
|
|
35
46
|
"typedoc": "^0.27.9"
|
|
36
47
|
}
|
|
37
48
|
}
|
package/server.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// server.ts - TypeScript version of the Bun server for the component library demo
|
|
2
|
+
import { serve, type ServeOptions } from "bun";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { readFileSync, existsSync } from "fs";
|
|
5
|
+
|
|
6
|
+
// Content type mapping
|
|
7
|
+
const contentTypes: Record<string, string> = {
|
|
8
|
+
".html": "text/html",
|
|
9
|
+
".js": "text/javascript",
|
|
10
|
+
".mjs": "text/javascript", // Add this for ES modules
|
|
11
|
+
".css": "text/css",
|
|
12
|
+
".json": "application/json",
|
|
13
|
+
".png": "image/png",
|
|
14
|
+
".jpg": "image/jpeg",
|
|
15
|
+
".svg": "image/svg+xml"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Define the port (hardcoded instead of using environment variable)
|
|
19
|
+
const PORT: number = 3301;
|
|
20
|
+
|
|
21
|
+
// Define demo directory path
|
|
22
|
+
const DEMO_DIR: string = "./demo";
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Serves a file from the demo directory
|
|
26
|
+
* @param path Path to the file relative to the demo directory
|
|
27
|
+
* @returns Response object with the file content or error message
|
|
28
|
+
*/
|
|
29
|
+
function serveFile(path: string): Response {
|
|
30
|
+
// Add demo directory to path
|
|
31
|
+
const filePath: string = join(DEMO_DIR, path);
|
|
32
|
+
|
|
33
|
+
// Handle index.html for directory request
|
|
34
|
+
let resolvedPath: string = filePath;
|
|
35
|
+
if (filePath.endsWith('/') || !filePath.includes('.')) {
|
|
36
|
+
resolvedPath = join(filePath, 'index.html');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Check if file exists
|
|
40
|
+
if (!existsSync(resolvedPath)) {
|
|
41
|
+
return new Response("File not found", { status: 404 });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Get file extension for content type
|
|
45
|
+
const ext: string = resolvedPath.substring(resolvedPath.lastIndexOf('.'));
|
|
46
|
+
const contentType: string = contentTypes[ext] || "application/octet-stream";
|
|
47
|
+
|
|
48
|
+
// Read and serve the file
|
|
49
|
+
try {
|
|
50
|
+
const content: Buffer = readFileSync(resolvedPath);
|
|
51
|
+
|
|
52
|
+
// For JavaScript files, ensure they're served with the correct MIME type
|
|
53
|
+
// and additionally add proper headers for modules
|
|
54
|
+
if (ext === '.js') {
|
|
55
|
+
return new Response(content, {
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "text/javascript",
|
|
58
|
+
"Cache-Control": "no-cache"
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return new Response(content, {
|
|
64
|
+
headers: { "Content-Type": contentType }
|
|
65
|
+
});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Error serving ${resolvedPath}:`, error);
|
|
68
|
+
return new Response("Server error", { status: 500 });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Server options with typings
|
|
73
|
+
const serverOptions: ServeOptions = {
|
|
74
|
+
port: PORT,
|
|
75
|
+
fetch(req: Request): Response | Promise<Response> {
|
|
76
|
+
const url = new URL(req.url);
|
|
77
|
+
const path = url.pathname === '/' ? '/' : url.pathname;
|
|
78
|
+
|
|
79
|
+
return serveFile(path);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Create and start the server
|
|
84
|
+
const server = serve(serverOptions);
|
|
85
|
+
|
|
86
|
+
console.log(`Server running at http://localhost:${PORT}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// src/components/badge/api.ts
|
|
2
|
-
import { BadgeComponent } from './types';
|
|
3
|
-
import {
|
|
2
|
+
import { BadgeComponent, BadgeColor, BadgeVariant, BadgePosition } from './types';
|
|
3
|
+
import { formatBadgeLabel } from './config';
|
|
4
4
|
|
|
5
5
|
interface ApiOptions {
|
|
6
6
|
visibility: {
|
|
@@ -19,7 +19,8 @@ interface ComponentWithElements {
|
|
|
19
19
|
wrapper?: HTMLElement;
|
|
20
20
|
config: {
|
|
21
21
|
max?: number;
|
|
22
|
-
|
|
22
|
+
label?: string | number;
|
|
23
|
+
variant?: string;
|
|
23
24
|
};
|
|
24
25
|
getClass: (name: string) => string;
|
|
25
26
|
addClass: (...classes: string[]) => any;
|
|
@@ -28,6 +29,20 @@ interface ComponentWithElements {
|
|
|
28
29
|
off: (event: string, handler: Function) => any;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
// Common variant constants for internal use
|
|
33
|
+
const VARIANT_SMALL = 'small';
|
|
34
|
+
|
|
35
|
+
// Map of all badge colors for class removal
|
|
36
|
+
const ALL_COLORS = [
|
|
37
|
+
'error', 'primary', 'secondary', 'tertiary', 'success', 'warning', 'info'
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// Map of all badge variants for class removal
|
|
41
|
+
const ALL_VARIANTS = ['small', 'large'];
|
|
42
|
+
|
|
43
|
+
// Map of all badge positions for class removal
|
|
44
|
+
const ALL_POSITIONS = ['top-right', 'top-left', 'bottom-right', 'bottom-left'];
|
|
45
|
+
|
|
31
46
|
/**
|
|
32
47
|
* Enhances a badge component with API methods
|
|
33
48
|
* @param {ApiOptions} options - API configuration options
|
|
@@ -39,27 +54,33 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
39
54
|
...component as any,
|
|
40
55
|
|
|
41
56
|
/**
|
|
42
|
-
* Sets the badge
|
|
43
|
-
* @param {string|number}
|
|
57
|
+
* Sets the badge label
|
|
58
|
+
* @param {string|number} label - Label to display in the badge
|
|
44
59
|
* @returns {BadgeComponent} Badge component instance for chaining
|
|
45
60
|
*/
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
component.config.content = content;
|
|
61
|
+
setLabel(label: string | number) {
|
|
62
|
+
component.config.label = label;
|
|
49
63
|
|
|
50
|
-
//
|
|
51
|
-
if (component.config.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
component.element.textContent = String(component.config.max);
|
|
55
|
-
component.element.classList.add(`${component.getClass('badge')}--max`);
|
|
56
|
-
} else {
|
|
57
|
-
component.element.textContent = stringContent;
|
|
58
|
-
component.element.classList.remove(`${component.getClass('badge')}--max`);
|
|
64
|
+
// Small badges (dot variant) don't have text
|
|
65
|
+
if (component.config.variant === VARIANT_SMALL) {
|
|
66
|
+
component.element.textContent = '';
|
|
67
|
+
return this;
|
|
59
68
|
}
|
|
60
69
|
|
|
61
|
-
//
|
|
62
|
-
|
|
70
|
+
// Format the label
|
|
71
|
+
const formattedLabel = formatBadgeLabel(label, component.config.max);
|
|
72
|
+
component.element.textContent = formattedLabel;
|
|
73
|
+
|
|
74
|
+
// Add overflow class if label was truncated
|
|
75
|
+
component.element.classList.remove(`${component.getClass('badge')}--overflow`);
|
|
76
|
+
if (typeof label === 'number' &&
|
|
77
|
+
component.config.max !== undefined &&
|
|
78
|
+
label > component.config.max) {
|
|
79
|
+
component.element.classList.add(`${component.getClass('badge')}--overflow`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Toggle visibility based on whether label is empty
|
|
83
|
+
if (formattedLabel === '' || formattedLabel === '0') {
|
|
63
84
|
this.hide();
|
|
64
85
|
} else {
|
|
65
86
|
this.show();
|
|
@@ -69,10 +90,10 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
69
90
|
},
|
|
70
91
|
|
|
71
92
|
/**
|
|
72
|
-
* Gets the badge
|
|
73
|
-
* @returns {string} Current badge
|
|
93
|
+
* Gets the badge label
|
|
94
|
+
* @returns {string} Current badge label
|
|
74
95
|
*/
|
|
75
|
-
|
|
96
|
+
getLabel() {
|
|
76
97
|
return component.element.textContent || '';
|
|
77
98
|
},
|
|
78
99
|
|
|
@@ -114,19 +135,16 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
114
135
|
|
|
115
136
|
/**
|
|
116
137
|
* Sets maximum value (after which badge shows max+)
|
|
138
|
+
*
|
|
117
139
|
* @param {number} max - Maximum value to display
|
|
118
140
|
* @returns {BadgeComponent} Badge component instance for chaining
|
|
119
141
|
*/
|
|
120
142
|
setMax(max: number) {
|
|
121
143
|
component.config.max = max;
|
|
122
144
|
|
|
123
|
-
// Apply max formatting if current
|
|
124
|
-
if (
|
|
125
|
-
|
|
126
|
-
component.element.textContent = String(max);
|
|
127
|
-
component.element.classList.add(`${component.getClass('badge')}--max`);
|
|
128
|
-
} else {
|
|
129
|
-
component.element.classList.remove(`${component.getClass('badge')}--max`);
|
|
145
|
+
// Apply max formatting if current label exceeds max
|
|
146
|
+
if (component.config.label !== undefined) {
|
|
147
|
+
this.setLabel(component.config.label);
|
|
130
148
|
}
|
|
131
149
|
|
|
132
150
|
return this;
|
|
@@ -137,9 +155,9 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
137
155
|
* @param {string} color - Color variant to apply
|
|
138
156
|
* @returns {BadgeComponent} Badge component instance for chaining
|
|
139
157
|
*/
|
|
140
|
-
setColor(color:
|
|
158
|
+
setColor(color: BadgeColor | string) {
|
|
141
159
|
// Remove existing color classes
|
|
142
|
-
|
|
160
|
+
ALL_COLORS.forEach(colorName => {
|
|
143
161
|
component.element.classList.remove(`${component.getClass('badge')}--${colorName}`);
|
|
144
162
|
});
|
|
145
163
|
|
|
@@ -150,44 +168,32 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
150
168
|
|
|
151
169
|
/**
|
|
152
170
|
* Sets badge variant
|
|
153
|
-
* @param {string} variant - Variant to apply
|
|
171
|
+
* @param {string} variant - Variant to apply (small or large)
|
|
154
172
|
* @returns {BadgeComponent} Badge component instance for chaining
|
|
155
173
|
*/
|
|
156
|
-
setVariant(variant:
|
|
174
|
+
setVariant(variant: BadgeVariant | string) {
|
|
157
175
|
// Remove existing variant classes
|
|
158
|
-
|
|
176
|
+
ALL_VARIANTS.forEach(variantName => {
|
|
159
177
|
component.element.classList.remove(`${component.getClass('badge')}--${variantName}`);
|
|
160
178
|
});
|
|
161
179
|
|
|
162
|
-
// Add new variant class
|
|
163
|
-
|
|
164
|
-
component.element.classList.add(`${component.getClass('badge')}--${variant}`);
|
|
165
|
-
}
|
|
180
|
+
// Add new variant class
|
|
181
|
+
component.element.classList.add(`${component.getClass('badge')}--${variant}`);
|
|
166
182
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
component.element.textContent = '';
|
|
170
|
-
} else if (component.config.content !== undefined) {
|
|
171
|
-
this.setContent(component.config.content);
|
|
172
|
-
}
|
|
183
|
+
// Update component config
|
|
184
|
+
component.config.variant = variant;
|
|
173
185
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
component.element.classList.remove(`${component.getClass('badge')}--${sizeName}`);
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
// Add new size class if not medium (default)
|
|
189
|
-
if (size !== BADGE_SIZES.MEDIUM) {
|
|
190
|
-
component.element.classList.add(`${component.getClass('badge')}--${size}`);
|
|
186
|
+
// Update accessibility attributes
|
|
187
|
+
if (variant === VARIANT_SMALL) {
|
|
188
|
+
component.element.textContent = '';
|
|
189
|
+
component.element.setAttribute('aria-hidden', 'true');
|
|
190
|
+
} else {
|
|
191
|
+
component.element.setAttribute('role', 'status');
|
|
192
|
+
|
|
193
|
+
// Restore label for large badges
|
|
194
|
+
if (component.config.label !== undefined) {
|
|
195
|
+
this.setLabel(component.config.label);
|
|
196
|
+
}
|
|
191
197
|
}
|
|
192
198
|
|
|
193
199
|
return this;
|
|
@@ -198,9 +204,9 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
198
204
|
* @param {string} position - Position variant to apply
|
|
199
205
|
* @returns {BadgeComponent} Badge component instance for chaining
|
|
200
206
|
*/
|
|
201
|
-
setPosition(position:
|
|
207
|
+
setPosition(position: BadgePosition | string) {
|
|
202
208
|
// Remove existing position classes
|
|
203
|
-
|
|
209
|
+
ALL_POSITIONS.forEach(posName => {
|
|
204
210
|
component.element.classList.remove(`${component.getClass('badge')}--${posName}`);
|
|
205
211
|
});
|
|
206
212
|
|
|
@@ -224,6 +230,7 @@ export const withAPI = ({ visibility, lifecycle }: ApiOptions) =>
|
|
|
224
230
|
// Create a new wrapper to hold the target and badge
|
|
225
231
|
const wrapper = document.createElement('div');
|
|
226
232
|
wrapper.classList.add(component.getClass('badge-wrapper'));
|
|
233
|
+
wrapper.style.position = 'relative';
|
|
227
234
|
|
|
228
235
|
// Replace the target with the wrapper
|
|
229
236
|
const parent = target.parentNode;
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
withVisibility,
|
|
10
10
|
withVariant,
|
|
11
11
|
withColor,
|
|
12
|
-
withSize,
|
|
13
12
|
withPosition,
|
|
14
13
|
withMax,
|
|
15
14
|
withAttachment
|
|
@@ -22,18 +21,33 @@ import { createBaseConfig, getElementConfig, getApiConfig } from './config';
|
|
|
22
21
|
* Creates a new Badge component
|
|
23
22
|
* @param {BadgeConfig} config - Badge configuration object
|
|
24
23
|
* @returns {BadgeComponent} Badge component instance
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* // Create a small dot badge
|
|
27
|
+
* const notificationBadge = createBadge({
|
|
28
|
+
* variant: 'small',
|
|
29
|
+
* target: document.querySelector('.icon-button')
|
|
30
|
+
* });
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Create a large badge with a count
|
|
34
|
+
* const countBadge = createBadge({
|
|
35
|
+
* variant: 'large',
|
|
36
|
+
* label: 5,
|
|
37
|
+
* target: document.querySelector('.notification-icon')
|
|
38
|
+
* });
|
|
25
39
|
*/
|
|
26
40
|
const createBadge = (config: BadgeConfig = {}): BadgeComponent => {
|
|
27
41
|
const baseConfig = createBaseConfig(config);
|
|
28
42
|
|
|
29
43
|
try {
|
|
44
|
+
// Compose the badge component from multiple feature enhancers
|
|
30
45
|
const badge = pipe(
|
|
31
46
|
createBase,
|
|
32
47
|
withEvents(),
|
|
33
48
|
withElement(getElementConfig(baseConfig)),
|
|
34
49
|
withVariant(baseConfig),
|
|
35
50
|
withColor(baseConfig),
|
|
36
|
-
withSize(baseConfig),
|
|
37
51
|
withPosition(baseConfig),
|
|
38
52
|
withMax(baseConfig),
|
|
39
53
|
withVisibility(),
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
// src/components/badge/config.ts
|
|
2
2
|
import {
|
|
3
3
|
createComponentConfig,
|
|
4
|
-
createElementConfig
|
|
5
|
-
BaseComponentConfig
|
|
4
|
+
createElementConfig
|
|
6
5
|
} from '../../core/config/component-config';
|
|
7
6
|
import { BadgeConfig } from './types';
|
|
8
|
-
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Maximum character count for badge labels
|
|
10
|
+
*/
|
|
11
|
+
export const BADGE_MAX_CHARACTERS = 4;
|
|
9
12
|
|
|
10
13
|
/**
|
|
11
14
|
* Default configuration for the Badge component
|
|
12
15
|
*/
|
|
13
16
|
export const defaultConfig: BadgeConfig = {
|
|
14
|
-
variant:
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
visible: true,
|
|
20
|
-
standalone: false
|
|
17
|
+
variant: 'large',
|
|
18
|
+
color: 'error',
|
|
19
|
+
position: 'top-right',
|
|
20
|
+
label: '',
|
|
21
|
+
visible: true
|
|
21
22
|
};
|
|
22
23
|
|
|
23
24
|
/**
|
|
@@ -37,14 +38,21 @@ export const getElementConfig = (config: BadgeConfig) => {
|
|
|
37
38
|
// Create the attributes object
|
|
38
39
|
const attrs: Record<string, any> = {};
|
|
39
40
|
|
|
40
|
-
//
|
|
41
|
-
|
|
41
|
+
// For large badges, set appropriate ARIA attributes
|
|
42
|
+
if (config.variant !== 'small') {
|
|
43
|
+
attrs.role = 'status';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Format the label if needed
|
|
47
|
+
const formattedLabel = config.variant === 'small'
|
|
48
|
+
? ''
|
|
49
|
+
: formatBadgeLabel(config.label || '', config.max);
|
|
42
50
|
|
|
43
51
|
return createElementConfig(config, {
|
|
44
52
|
tag: 'span',
|
|
45
53
|
attrs,
|
|
46
54
|
className: config.class,
|
|
47
|
-
|
|
55
|
+
text: formattedLabel // Use the formatted label
|
|
48
56
|
});
|
|
49
57
|
};
|
|
50
58
|
|
|
@@ -65,4 +73,49 @@ export const getApiConfig = (comp) => ({
|
|
|
65
73
|
}
|
|
66
74
|
});
|
|
67
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Format a label
|
|
78
|
+
* - Max 4 characters including "+" for overflow
|
|
79
|
+
* - Add "+" for numeric values exceeding max
|
|
80
|
+
*
|
|
81
|
+
* @param {string|number} label - Original label
|
|
82
|
+
* @param {number} max - Maximum value before using "+"
|
|
83
|
+
* @returns {string} Formatted label
|
|
84
|
+
*/
|
|
85
|
+
export const formatBadgeLabel = (label: string | number, max?: number): string => {
|
|
86
|
+
// Handle empty or undefined labels
|
|
87
|
+
if (label === undefined || label === null || label === '') {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let formattedLabel = String(label);
|
|
92
|
+
|
|
93
|
+
const numericLabel = Number(label);
|
|
94
|
+
|
|
95
|
+
// Apply max value formatting
|
|
96
|
+
if (max !== undefined && !isNaN(numericLabel) && numericLabel > max) {
|
|
97
|
+
formattedLabel = `${max}+`;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Ensure label doesn't exceed max characters
|
|
101
|
+
if (formattedLabel.length > BADGE_MAX_CHARACTERS) {
|
|
102
|
+
// Try to preserve as much information as possible
|
|
103
|
+
// For large numbers, use abbreviated format with "+"
|
|
104
|
+
const numericValue = Number(label);
|
|
105
|
+
if (!isNaN(numericValue)) {
|
|
106
|
+
if (numericValue >= 1000) {
|
|
107
|
+
formattedLabel = '999+';
|
|
108
|
+
} else {
|
|
109
|
+
// For numbers under 1000 but still too long, truncate
|
|
110
|
+
formattedLabel = formattedLabel.substring(0, BADGE_MAX_CHARACTERS - 1) + '+';
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
// For non-numeric values, simply truncate
|
|
114
|
+
formattedLabel = formattedLabel.substring(0, BADGE_MAX_CHARACTERS);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return formattedLabel;
|
|
119
|
+
};
|
|
120
|
+
|
|
68
121
|
export default defaultConfig;
|