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.
Files changed (226) hide show
  1. package/demo/build.ts +349 -0
  2. package/demo/index.html +110 -0
  3. package/demo/main.js +448 -0
  4. package/demo/styles.css +239 -0
  5. package/index.ts +18 -0
  6. package/package.json +14 -3
  7. package/server.ts +86 -0
  8. package/src/components/badge/api.ts +70 -63
  9. package/src/components/badge/badge.ts +16 -2
  10. package/src/components/badge/config.ts +66 -13
  11. package/src/components/badge/features.ts +51 -42
  12. package/src/components/badge/index.ts +27 -2
  13. package/src/components/badge/types.ts +62 -30
  14. package/src/components/bottom-app-bar/bottom-app-bar.ts +154 -0
  15. package/src/components/bottom-app-bar/config.ts +29 -0
  16. package/src/components/bottom-app-bar/index.ts +17 -0
  17. package/src/components/bottom-app-bar/types.ts +114 -0
  18. package/src/components/button/api.ts +5 -0
  19. package/src/components/button/button.ts +0 -1
  20. package/src/components/button/config.ts +6 -2
  21. package/src/components/button/index.ts +10 -2
  22. package/src/components/button/types.ts +20 -2
  23. package/src/components/card/card.ts +13 -25
  24. package/src/components/card/config.ts +83 -30
  25. package/src/components/card/content.ts +8 -10
  26. package/src/components/card/features.ts +4 -3
  27. package/src/components/card/index.ts +29 -2
  28. package/src/components/card/types.ts +33 -22
  29. package/src/components/checkbox/config.ts +3 -4
  30. package/src/components/checkbox/index.ts +1 -2
  31. package/src/components/checkbox/types.ts +12 -3
  32. package/src/components/chip/api.ts +170 -221
  33. package/src/components/chip/chip.ts +34 -302
  34. package/src/components/chip/config.ts +1 -2
  35. package/src/components/chip/index.ts +10 -2
  36. package/src/components/chip/types.ts +224 -35
  37. package/src/components/datepicker/api.ts +265 -0
  38. package/src/components/datepicker/config.ts +141 -0
  39. package/src/components/datepicker/datepicker.ts +341 -0
  40. package/src/components/datepicker/index.ts +12 -0
  41. package/src/components/datepicker/render.ts +450 -0
  42. package/src/components/datepicker/types.ts +397 -0
  43. package/src/components/datepicker/utils.ts +289 -0
  44. package/src/components/dialog/api.ts +55 -21
  45. package/src/components/dialog/config.ts +12 -9
  46. package/src/components/dialog/dialog.ts +6 -3
  47. package/src/components/dialog/features.ts +345 -151
  48. package/src/components/dialog/index.ts +38 -8
  49. package/src/components/dialog/types.ts +40 -14
  50. package/src/components/divider/config.ts +81 -0
  51. package/src/components/divider/divider.ts +37 -0
  52. package/src/components/divider/features.ts +207 -0
  53. package/src/components/divider/index.ts +9 -0
  54. package/src/components/divider/types.ts +55 -0
  55. package/src/components/extended-fab/api.ts +141 -0
  56. package/src/components/extended-fab/config.ts +112 -0
  57. package/src/components/extended-fab/extended-fab.ts +125 -0
  58. package/src/components/extended-fab/index.ts +9 -0
  59. package/src/components/extended-fab/types.ts +304 -0
  60. package/src/components/fab/api.ts +97 -0
  61. package/src/components/fab/config.ts +93 -0
  62. package/src/components/fab/fab.ts +67 -0
  63. package/src/components/fab/index.ts +9 -0
  64. package/src/components/fab/types.ts +251 -0
  65. package/src/components/list/config.ts +4 -5
  66. package/src/components/list/features.ts +6 -7
  67. package/src/components/list/index.ts +7 -9
  68. package/src/components/list/list-item.ts +12 -13
  69. package/src/components/list/types.ts +50 -5
  70. package/src/components/list/utils.ts +30 -3
  71. package/src/components/menu/features/items-manager.ts +9 -9
  72. package/src/components/menu/features/positioning.ts +7 -7
  73. package/src/components/menu/features/visibility.ts +7 -7
  74. package/src/components/menu/index.ts +7 -9
  75. package/src/components/menu/menu-item.ts +6 -6
  76. package/src/components/menu/menu.ts +22 -0
  77. package/src/components/menu/types.ts +29 -10
  78. package/src/components/menu/utils.ts +67 -0
  79. package/src/components/navigation/api.ts +78 -50
  80. package/src/components/navigation/config.ts +22 -10
  81. package/src/components/navigation/features/items.ts +284 -0
  82. package/src/components/navigation/index.ts +0 -6
  83. package/src/components/navigation/nav-item.ts +70 -33
  84. package/src/components/navigation/navigation.ts +53 -3
  85. package/src/components/navigation/types.ts +117 -70
  86. package/src/components/progress/api.ts +2 -3
  87. package/src/components/progress/config.ts +2 -3
  88. package/src/components/progress/index.ts +0 -1
  89. package/src/components/progress/progress.ts +1 -2
  90. package/src/components/progress/types.ts +186 -33
  91. package/src/components/radios/config.ts +1 -1
  92. package/src/components/radios/index.ts +0 -1
  93. package/src/components/radios/types.ts +0 -7
  94. package/src/components/search/api.ts +203 -0
  95. package/src/components/search/config.ts +86 -0
  96. package/src/components/search/features/index.ts +4 -0
  97. package/src/components/search/features/search.ts +717 -0
  98. package/src/components/search/features/states.ts +169 -0
  99. package/src/components/search/features/structure.ts +197 -0
  100. package/src/components/search/index.ts +7 -0
  101. package/src/components/search/search.ts +52 -0
  102. package/src/components/search/types.ts +175 -0
  103. package/src/components/segmented-button/config.ts +80 -0
  104. package/src/components/segmented-button/index.ts +4 -0
  105. package/src/components/segmented-button/segment.ts +154 -0
  106. package/src/components/segmented-button/segmented-button.ts +249 -0
  107. package/src/components/segmented-button/types.ts +254 -0
  108. package/src/components/slider/accessibility.md +5 -5
  109. package/src/components/slider/api.ts +41 -120
  110. package/src/components/slider/config.ts +51 -47
  111. package/src/components/slider/features/handlers.ts +495 -0
  112. package/src/components/slider/features/index.ts +1 -2
  113. package/src/components/slider/features/slider.ts +66 -84
  114. package/src/components/slider/features/states.ts +195 -0
  115. package/src/components/slider/features/structure.ts +136 -206
  116. package/src/components/slider/features/ui.ts +145 -206
  117. package/src/components/slider/index.ts +2 -11
  118. package/src/components/slider/slider.ts +9 -12
  119. package/src/components/slider/types.ts +67 -26
  120. package/src/components/snackbar/config.ts +2 -3
  121. package/src/components/snackbar/constants.ts +0 -32
  122. package/src/components/snackbar/index.ts +0 -1
  123. package/src/components/snackbar/position.ts +9 -1
  124. package/src/components/snackbar/types.ts +122 -46
  125. package/src/components/switch/config.ts +2 -3
  126. package/src/components/switch/index.ts +0 -1
  127. package/src/components/switch/types.ts +3 -2
  128. package/src/components/tabs/config.ts +3 -4
  129. package/src/components/tabs/features.ts +4 -2
  130. package/src/components/tabs/index.ts +0 -15
  131. package/src/components/tabs/indicator.ts +73 -13
  132. package/src/components/tabs/tab-api.ts +12 -4
  133. package/src/components/tabs/tab.ts +18 -6
  134. package/src/components/tabs/types.ts +23 -5
  135. package/src/components/textfield/config.ts +2 -3
  136. package/src/components/textfield/index.ts +0 -1
  137. package/src/components/textfield/types.ts +17 -3
  138. package/src/components/timepicker/README.md +277 -0
  139. package/src/components/timepicker/api.ts +632 -0
  140. package/src/components/timepicker/clockdial.ts +482 -0
  141. package/src/components/timepicker/config.ts +228 -0
  142. package/src/components/timepicker/index.ts +3 -0
  143. package/src/components/timepicker/render.ts +613 -0
  144. package/src/components/timepicker/timepicker.ts +117 -0
  145. package/src/components/timepicker/types.ts +336 -0
  146. package/src/components/timepicker/utils.ts +241 -0
  147. package/src/components/tooltip/api.ts +1 -1
  148. package/src/components/tooltip/config.ts +27 -6
  149. package/src/components/tooltip/index.ts +0 -1
  150. package/src/components/tooltip/types.ts +13 -3
  151. package/src/components/top-app-bar/config.ts +83 -0
  152. package/src/components/top-app-bar/index.ts +11 -0
  153. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  154. package/src/components/top-app-bar/types.ts +140 -0
  155. package/src/core/build/_ripple.scss +6 -6
  156. package/src/core/build/ripple.ts +72 -95
  157. package/src/core/compose/features/icon.ts +3 -1
  158. package/src/core/compose/features/ripple.ts +4 -1
  159. package/src/core/compose/features/textlabel.ts +23 -2
  160. package/src/core/dom/create.ts +5 -0
  161. package/src/index.ts +9 -0
  162. package/src/styles/abstract/_theme.scss +9 -1
  163. package/src/styles/components/_badge.scss +182 -0
  164. package/src/styles/components/_bottom-app-bar.scss +103 -0
  165. package/src/{components/button/_styles.scss → styles/components/_button.scss} +0 -10
  166. package/src/{components/checkbox/_styles.scss → styles/components/_checkbox.scss} +0 -2
  167. package/src/styles/components/_datepicker.scss +358 -0
  168. package/src/styles/components/_dialog.scss +259 -0
  169. package/src/styles/components/_divider.scss +57 -0
  170. package/src/styles/components/_extended-fab.scss +267 -0
  171. package/src/styles/components/_fab.scss +225 -0
  172. package/src/{components/navigation/_styles.scss → styles/components/_navigation.scss} +1 -0
  173. package/src/styles/components/_search.scss +306 -0
  174. package/src/styles/components/_segmented-button.scss +117 -0
  175. package/src/{components/slider/_styles.scss → styles/components/_slider.scss} +83 -24
  176. package/src/{components/switch/_styles.scss → styles/components/_switch.scss} +0 -2
  177. package/src/{components/tabs/_styles.scss → styles/components/_tabs.scss} +95 -33
  178. package/src/{components/textfield/_styles.scss → styles/components/_textfield.scss} +70 -67
  179. package/src/styles/components/_timepicker.scss +451 -0
  180. package/src/styles/components/_top-app-bar.scss +225 -0
  181. package/src/styles/main.scss +98 -49
  182. package/src/styles/themes/_autumn.scss +21 -0
  183. package/src/styles/themes/_base-theme.scss +61 -0
  184. package/src/styles/themes/_baseline.scss +58 -0
  185. package/src/styles/themes/_bluekhaki.scss +125 -0
  186. package/src/styles/themes/_brownbeige.scss +125 -0
  187. package/src/styles/themes/_browngreen.scss +125 -0
  188. package/src/styles/themes/_forest.scss +6 -0
  189. package/src/styles/themes/_greenbeige.scss +125 -0
  190. package/src/styles/themes/_material.scss +125 -0
  191. package/src/styles/themes/_ocean.scss +6 -0
  192. package/src/styles/themes/_sageivory.scss +125 -0
  193. package/src/styles/themes/_spring.scss +6 -0
  194. package/src/styles/themes/_summer.scss +5 -0
  195. package/src/styles/themes/_sunset.scss +5 -0
  196. package/src/styles/themes/_tealcaramel.scss +125 -0
  197. package/src/styles/themes/_winter.scss +6 -0
  198. package/src/components/badge/_styles.scss +0 -174
  199. package/src/components/badge/constants.ts +0 -30
  200. package/src/components/button/constants.ts +0 -11
  201. package/src/components/card/constants.ts +0 -84
  202. package/src/components/dialog/_styles.scss +0 -213
  203. package/src/components/dialog/constants.ts +0 -32
  204. package/src/components/menu/constants.ts +0 -154
  205. package/src/components/navigation/constants.ts +0 -200
  206. package/src/components/navigation/features/items.js +0 -192
  207. package/src/components/progress/constants.ts +0 -29
  208. package/src/components/slider/features/appearance.ts +0 -94
  209. package/src/components/slider/features/disabled.ts +0 -68
  210. package/src/components/slider/features/events.ts +0 -164
  211. package/src/components/slider/features/interactions.ts +0 -396
  212. package/src/components/slider/features/keyboard.ts +0 -233
  213. package/src/components/switch/constants.ts +0 -80
  214. package/src/components/tabs/constants.ts +0 -89
  215. package/src/core/collection/adapters/mongodb.js +0 -232
  216. /package/src/{components/card/_styles.scss → styles/components/_card.scss} +0 -0
  217. /package/src/{components/carousel/_styles.scss → styles/components/_carousel.scss} +0 -0
  218. /package/src/{components/chip/_styles.scss → styles/components/_chip.scss} +0 -0
  219. /package/src/{components/list/_styles.scss → styles/components/_list.scss} +0 -0
  220. /package/src/{components/menu/_styles.scss → styles/components/_menu.scss} +0 -0
  221. /package/src/{components/progress/_styles.scss → styles/components/_progress.scss} +0 -0
  222. /package/src/{components/radios/_styles.scss → styles/components/_radios.scss} +0 -0
  223. /package/src/{components/sheet/_styles.scss → styles/components/_sheet.scss} +0 -0
  224. /package/src/{components/snackbar/_styles.scss → styles/components/_snackbar.scss} +0 -0
  225. /package/src/{components/tooltip/_styles.scss → styles/components/_tooltip.scss} +0 -0
  226. /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.6",
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 { BADGE_VARIANTS, BADGE_SIZES, BADGE_COLORS, BADGE_POSITIONS } from './constants';
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
- content?: string | number;
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 content
43
- * @param {string|number} content - Content to display in the badge
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
- setContent(content: string | number) {
47
- const stringContent = String(content);
48
- component.config.content = content;
61
+ setLabel(label: string | number) {
62
+ component.config.label = label;
49
63
 
50
- // If max is defined and content is a number greater than max, show max+
51
- if (component.config.max !== undefined &&
52
- typeof content === 'number' &&
53
- content > component.config.max) {
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
- // Toggle the dot variant based on whether content is empty
62
- if (stringContent === '' || stringContent === '0') {
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 content
73
- * @returns {string} Current badge content
93
+ * Gets the badge label
94
+ * @returns {string} Current badge label
74
95
  */
75
- getContent() {
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 content exceeds max
124
- if (typeof component.config.content === 'number' &&
125
- component.config.content > max) {
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: keyof typeof BADGE_COLORS | BADGE_COLORS) {
158
+ setColor(color: BadgeColor | string) {
141
159
  // Remove existing color classes
142
- Object.values(BADGE_COLORS).forEach(colorName => {
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: keyof typeof BADGE_VARIANTS | BADGE_VARIANTS) {
174
+ setVariant(variant: BadgeVariant | string) {
157
175
  // Remove existing variant classes
158
- Object.values(BADGE_VARIANTS).forEach(variantName => {
176
+ ALL_VARIANTS.forEach(variantName => {
159
177
  component.element.classList.remove(`${component.getClass('badge')}--${variantName}`);
160
178
  });
161
179
 
162
- // Add new variant class if not standard
163
- if (variant !== BADGE_VARIANTS.STANDARD) {
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
- // Clear content if dot variant
168
- if (variant === BADGE_VARIANTS.DOT) {
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
- return this;
175
- },
176
-
177
- /**
178
- * Sets badge size
179
- * @param {string} size - Size variant to apply
180
- * @returns {BadgeComponent} Badge component instance for chaining
181
- */
182
- setSize(size: keyof typeof BADGE_SIZES | BADGE_SIZES) {
183
- // Remove existing size classes
184
- Object.values(BADGE_SIZES).forEach(sizeName => {
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: keyof typeof BADGE_POSITIONS | BADGE_POSITIONS) {
207
+ setPosition(position: BadgePosition | string) {
202
208
  // Remove existing position classes
203
- Object.values(BADGE_POSITIONS).forEach(posName => {
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
- import { BADGE_VARIANTS, BADGE_SIZES, BADGE_COLORS, BADGE_POSITIONS } from './constants';
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: BADGE_VARIANTS.STANDARD,
15
- size: BADGE_SIZES.MEDIUM,
16
- color: BADGE_COLORS.ERROR,
17
- position: BADGE_POSITIONS.TOP_RIGHT,
18
- content: '',
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
- // Convert numeric content to string if needed
41
- const content = config.content !== undefined ? String(config.content) : '';
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
- content: config.variant === BADGE_VARIANTS.DOT ? '' : content
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;