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
@@ -0,0 +1,169 @@
1
+ // src/components/search/features/states.ts
2
+ import { SearchConfig } from '../types';
3
+
4
+ const SEARCH_VARIANTS = {
5
+ BAR: 'bar',
6
+ VIEW: 'view'
7
+ }
8
+
9
+ /**
10
+ * Add state management features to search component
11
+ *
12
+ * @param config Search configuration
13
+ * @returns Component enhancer with state management features
14
+ */
15
+ export const withStates = (config: SearchConfig) => component => {
16
+ // Track initial disabled state
17
+ const isDisabled = config.disabled === true;
18
+
19
+ // Apply initial disabled state if needed
20
+ if (isDisabled) {
21
+ setTimeout(() => {
22
+ disableComponent();
23
+ }, 0);
24
+ }
25
+
26
+ /**
27
+ * Disables the component
28
+ */
29
+ function disableComponent() {
30
+ component.element.classList.add(`${component.getClass('search')}--disabled`);
31
+ component.element.setAttribute('aria-disabled', 'true');
32
+
33
+ // Disable input - important to use the disabled property not the attribute
34
+ if (component.structure?.input) {
35
+ component.structure.input.disabled = true;
36
+ }
37
+
38
+ // Ensure buttons cannot receive focus when disabled
39
+ const buttons = [
40
+ component.structure?.leadingIcon,
41
+ component.structure?.clearButton,
42
+ component.structure?.trailingIcon,
43
+ component.structure?.trailingIcon2
44
+ ].filter(Boolean);
45
+
46
+ buttons.forEach(button => {
47
+ button.tabIndex = -1;
48
+ button.setAttribute('aria-disabled', 'true');
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Enables the component
54
+ */
55
+ function enableComponent() {
56
+ component.element.classList.remove(`${component.getClass('search')}--disabled`);
57
+ component.element.setAttribute('aria-disabled', 'false');
58
+
59
+ // Enable input - important to use the disabled property not the attribute
60
+ if (component.structure?.input) {
61
+ component.structure.input.disabled = false;
62
+ }
63
+
64
+ // Re-enable buttons
65
+ const buttons = [
66
+ component.structure?.leadingIcon,
67
+ component.structure?.clearButton,
68
+ component.structure?.trailingIcon,
69
+ component.structure?.trailingIcon2
70
+ ].filter(Boolean);
71
+
72
+ buttons.forEach(button => {
73
+ button.tabIndex = 0;
74
+ button.setAttribute('aria-disabled', 'false');
75
+ });
76
+
77
+ // Clear button special case - only enable if there's text
78
+ if (component.structure?.clearButton &&
79
+ component.structure.input &&
80
+ !component.structure.input.value) {
81
+ component.structure.clearButton.tabIndex = -1;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Gets the active variant
87
+ */
88
+ function getActiveVariant() {
89
+ return Object.values(SEARCH_VARIANTS).find(variantName =>
90
+ component.element.classList.contains(`${component.getClass('search')}--${variantName}`)
91
+ ) || SEARCH_VARIANTS.BAR;
92
+ }
93
+
94
+ // Return enhanced component
95
+ return {
96
+ ...component,
97
+
98
+ // Disabled state management
99
+ disabled: {
100
+ /**
101
+ * Enables the component
102
+ * @returns Disabled manager for chaining
103
+ */
104
+ enable() {
105
+ enableComponent();
106
+ return this;
107
+ },
108
+
109
+ /**
110
+ * Disables the component
111
+ * @returns Disabled manager for chaining
112
+ */
113
+ disable() {
114
+ disableComponent();
115
+ return this;
116
+ },
117
+
118
+ /**
119
+ * Checks if component is disabled
120
+ * @returns True if disabled
121
+ */
122
+ isDisabled() {
123
+ return component.element.classList.contains(`${component.getClass('search')}--disabled`);
124
+ }
125
+ },
126
+
127
+ // Appearance management
128
+ appearance: {
129
+ /**
130
+ * Sets search variant
131
+ * @param variant Variant name
132
+ * @returns Appearance manager for chaining
133
+ */
134
+ setVariant(variant) {
135
+ const currentVariant = getActiveVariant();
136
+
137
+ // If already this variant, do nothing
138
+ if (currentVariant === variant) {
139
+ return this;
140
+ }
141
+
142
+ // Remove existing variant classes
143
+ Object.values(SEARCH_VARIANTS).forEach(variantName => {
144
+ component.element.classList.remove(`${component.getClass('search')}--${variantName}`);
145
+ });
146
+
147
+ // Add new variant class
148
+ component.element.classList.add(`${component.getClass('search')}--${variant}`);
149
+
150
+ // Toggle expanded state class
151
+ if (variant === SEARCH_VARIANTS.VIEW) {
152
+ component.element.classList.add(`${component.getClass('search')}--expanded`);
153
+ } else {
154
+ component.element.classList.remove(`${component.getClass('search')}--expanded`);
155
+ }
156
+
157
+ return this;
158
+ },
159
+
160
+ /**
161
+ * Gets search variant
162
+ * @returns Current variant name
163
+ */
164
+ getVariant() {
165
+ return getActiveVariant();
166
+ }
167
+ }
168
+ };
169
+ };
@@ -0,0 +1,197 @@
1
+ // src/components/search/features/structure.ts
2
+ import { SearchConfig } from '../types';
3
+ import { createElement } from '../../../core/dom/create';
4
+
5
+ /**
6
+ * Creates the search component DOM structure
7
+ * @param config Search configuration
8
+ * @returns Component enhancer with DOM structure
9
+ */
10
+ export const withStructure = (config: SearchConfig) => component => {
11
+ // Get initial config values
12
+ const isDisabled = config.disabled === true;
13
+ const variant = config.variant || 'bar';
14
+ const isViewMode = variant === 'view';
15
+ const placeholder = config.placeholder || 'Search';
16
+ const value = config.value || '';
17
+
18
+ // Get prefixed class names
19
+ const getClass = (className) => component.getClass(className);
20
+
21
+ // Build attributes object for the input
22
+ const inputAttrs = {
23
+ 'type': 'text',
24
+ 'placeholder': placeholder,
25
+ 'value': value,
26
+ 'aria-label': placeholder
27
+ };
28
+
29
+ // Only add disabled attribute if the input should be disabled
30
+ if (isDisabled) {
31
+ inputAttrs['disabled'] = 'disabled';
32
+ }
33
+
34
+ // Create container element
35
+ const container = createElement({
36
+ tag: 'div',
37
+ className: getClass('search-container'),
38
+ container: component.element,
39
+ attrs: {
40
+ style: `
41
+ ${config.minWidth ? `min-width: ${config.minWidth}px;` : ''}
42
+ ${config.maxWidth ? `max-width: ${config.maxWidth}px;` : ''}
43
+ ${config.fullWidth ? 'width: 100%;' : ''}
44
+ `
45
+ }
46
+ });
47
+
48
+ // Create leading icon
49
+ const leadingIcon = createElement({
50
+ tag: 'div',
51
+ className: getClass('search-leading-icon'),
52
+ container: container,
53
+ html: config.leadingIcon || '',
54
+ attrs: {
55
+ 'role': 'button',
56
+ 'tabindex': isDisabled ? '-1' : '0',
57
+ 'aria-label': 'Search'
58
+ }
59
+ });
60
+
61
+ // Create input wrapper
62
+ const inputWrapper = createElement({
63
+ tag: 'div',
64
+ className: getClass('search-input-wrapper'),
65
+ container: container
66
+ });
67
+
68
+ // Create input element with properly handled disabled state
69
+ const input = createElement({
70
+ tag: 'input',
71
+ className: getClass('search-input'),
72
+ container: inputWrapper,
73
+ attrs: inputAttrs
74
+ });
75
+
76
+ // Create clear button
77
+ const clearButton = createElement({
78
+ tag: 'div',
79
+ className: [
80
+ getClass('search-clear-button'),
81
+ value ? '' : getClass('search-clear-button--hidden')
82
+ ],
83
+ container: container,
84
+ html: '<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>',
85
+ attrs: {
86
+ 'role': 'button',
87
+ 'tabindex': isDisabled || !value ? '-1' : '0',
88
+ 'aria-label': 'Clear search'
89
+ }
90
+ });
91
+
92
+ // Create optional trailing elements
93
+ let trailingIcon, trailingIcon2, avatar;
94
+
95
+ if (config.trailingIcon) {
96
+ trailingIcon = createElement({
97
+ tag: 'div',
98
+ className: getClass('search-trailing-icon'),
99
+ container: container,
100
+ html: config.trailingIcon,
101
+ attrs: {
102
+ 'role': 'button',
103
+ 'tabindex': isDisabled ? '-1' : '0',
104
+ 'aria-label': 'Search option'
105
+ }
106
+ });
107
+ }
108
+
109
+ if (config.trailingIcon2) {
110
+ trailingIcon2 = createElement({
111
+ tag: 'div',
112
+ className: getClass('search-trailing-icon'),
113
+ container: container,
114
+ html: config.trailingIcon2,
115
+ attrs: {
116
+ 'role': 'button',
117
+ 'tabindex': isDisabled ? '-1' : '0',
118
+ 'aria-label': 'Search option'
119
+ }
120
+ });
121
+ }
122
+
123
+ if (config.avatar) {
124
+ avatar = createElement({
125
+ tag: 'div',
126
+ className: getClass('search-avatar'),
127
+ container: container,
128
+ html: config.avatar
129
+ });
130
+ }
131
+
132
+ // Create divider and suggestions container for view variant
133
+ let divider, suggestionsContainer;
134
+
135
+ if (isViewMode || config.suggestions) {
136
+ divider = createElement({
137
+ tag: 'div',
138
+ className: getClass('search-divider')
139
+ });
140
+
141
+ suggestionsContainer = createElement({
142
+ tag: 'div',
143
+ className: getClass('search-suggestions-container'),
144
+ container: component.element
145
+ });
146
+ }
147
+
148
+ // Add component base class and accessibility attributes
149
+ component.element.classList.add(component.getClass('search'));
150
+ component.element.setAttribute('role', 'search');
151
+ component.element.setAttribute('aria-disabled', isDisabled ? 'true' : 'false');
152
+
153
+ // Apply style classes
154
+ applyStyleClasses(component, config, isViewMode, isDisabled);
155
+
156
+ // Return enhanced component with structure
157
+ return {
158
+ ...component,
159
+ structure: {
160
+ container,
161
+ input,
162
+ inputWrapper,
163
+ leadingIcon,
164
+ clearButton,
165
+ trailingIcon,
166
+ trailingIcon2,
167
+ avatar,
168
+ divider,
169
+ suggestionsContainer
170
+ }
171
+ };
172
+ };
173
+
174
+ /**
175
+ * Applies style classes based on configuration
176
+ */
177
+ function applyStyleClasses(component, config, isViewMode, isDisabled) {
178
+ const baseClass = component.getClass('search');
179
+
180
+ // Apply variant class
181
+ component.element.classList.add(`${baseClass}--${config.variant || 'bar'}`);
182
+
183
+ // Apply disabled class if needed
184
+ if (isDisabled) {
185
+ component.element.classList.add(`${baseClass}--disabled`);
186
+ }
187
+
188
+ // Apply expanded class for view mode
189
+ if (isViewMode) {
190
+ component.element.classList.add(`${baseClass}--expanded`);
191
+ }
192
+
193
+ // Apply fullwidth class if needed
194
+ if (config.fullWidth) {
195
+ component.element.classList.add(`${baseClass}--fullwidth`);
196
+ }
197
+ }
@@ -0,0 +1,7 @@
1
+ // src/components/search/index.ts
2
+
3
+ // Export main component creator
4
+ export { default } from './search';
5
+
6
+ // Export types for TypeScript users
7
+ export type { SearchConfig, SearchComponent, SearchEvent } from './types';
@@ -0,0 +1,52 @@
1
+ // src/components/search/search.ts
2
+ import { pipe } from '../../core/compose/pipe';
3
+ import { createBase, withElement } from '../../core/compose/component';
4
+ import { withEvents, withLifecycle } from '../../core/compose/features';
5
+ import { withStructure, withSearch, withStates } from './features';
6
+ import { withAPI } from './api';
7
+ import { SearchConfig, SearchComponent } from './types';
8
+ import { createBaseConfig, getElementConfig, getApiConfig } from './config';
9
+
10
+ /**
11
+ * Creates a new Search component
12
+ * @param {SearchConfig} config - Search configuration object
13
+ * @returns {SearchComponent} Search component instance
14
+ */
15
+ const createSearch = (config: SearchConfig = {}): SearchComponent => {
16
+ const baseConfig = createBaseConfig(config);
17
+
18
+ try {
19
+ // Create the component with all required features
20
+ const component = pipe(
21
+ createBase,
22
+ withEvents(),
23
+ withElement(getElementConfig(baseConfig)),
24
+ withStructure(baseConfig),
25
+ withStates(baseConfig),
26
+ withSearch(baseConfig),
27
+ withLifecycle()
28
+ )(baseConfig);
29
+
30
+ // Generate the API configuration
31
+ const apiOptions = getApiConfig(component);
32
+
33
+ // Apply the API layer
34
+ const search = withAPI(apiOptions)(component);
35
+
36
+ // Register event handlers from config
37
+ if (baseConfig.on && typeof search.on === 'function') {
38
+ Object.entries(baseConfig.on).forEach(([event, handler]) => {
39
+ if (typeof handler === 'function') {
40
+ search.on(event, handler);
41
+ }
42
+ });
43
+ }
44
+
45
+ return search;
46
+ } catch (error) {
47
+ console.error('Search creation error:', error);
48
+ throw new Error(`Failed to create search: ${(error as Error).message}`);
49
+ }
50
+ };
51
+
52
+ export default createSearch;
@@ -0,0 +1,175 @@
1
+ // src/components/search/types.ts
2
+
3
+ /**
4
+ * Navigation variants for the search component
5
+ */
6
+ export type NavVariant = 'rail' | 'drawer' | 'bar' | 'modal' | 'standard';
7
+
8
+ /**
9
+ * Valid event types for search component
10
+ */
11
+ export type SearchEventType = 'focus' | 'blur' | 'input' | 'submit' | 'clear' | 'iconClick';
12
+
13
+ /**
14
+ * Configuration options for the search component
15
+ * @interface SearchConfig
16
+ */
17
+ export interface SearchConfig {
18
+ /** The variant of the search component (rail, drawer, bar, modal, or standard) */
19
+ variant?: NavVariant | string;
20
+
21
+ /** Whether the search component is disabled */
22
+ disabled?: boolean;
23
+
24
+ /** Placeholder text for the search input */
25
+ placeholder?: string;
26
+
27
+ /** Initial value for the search input */
28
+ value?: string;
29
+
30
+ /** Leading icon HTML or 'none' to remove */
31
+ leadingIcon?: string;
32
+
33
+ /** Trailing icon HTML (optional) */
34
+ trailingIcon?: string;
35
+
36
+ /** Second trailing icon HTML (optional) */
37
+ trailingIcon2?: string;
38
+
39
+ /** Avatar HTML for trailing avatar (optional) */
40
+ avatar?: string;
41
+
42
+ /** If true, will show the clear button when the input has text */
43
+ showClearButton?: boolean;
44
+
45
+ /** Callback function when a search is submitted */
46
+ onSubmit?: (value: string) => void;
47
+
48
+ /** Callback function when the input value changes */
49
+ onInput?: (value: string) => void;
50
+
51
+ /** Callback function when the clear button is clicked */
52
+ onClear?: () => void;
53
+
54
+ /** Suggestions to show when the search is focused (array of strings or objects) */
55
+ suggestions?: string[] | Array<{text: string, value?: string, icon?: string}>;
56
+
57
+ /** Whether to show dividers between suggestion groups */
58
+ showDividers?: boolean;
59
+
60
+ /** Additional CSS classes */
61
+ class?: string;
62
+
63
+ /** Maximum width of the search component in pixels */
64
+ maxWidth?: number;
65
+
66
+ /** Minimum width of the search component in pixels */
67
+ minWidth?: number;
68
+
69
+ /** Whether the search component is full-width */
70
+ fullWidth?: boolean;
71
+
72
+ /** Event handlers for search events */
73
+ on?: {
74
+ [key in SearchEventType]?: (event: SearchEvent) => void;
75
+ };
76
+ }
77
+
78
+ /**
79
+ * Search event data
80
+ * @interface SearchEvent
81
+ */
82
+ export interface SearchEvent {
83
+ /** The search component that triggered the event */
84
+ search: any;
85
+
86
+ /** Current search value */
87
+ value: string;
88
+
89
+ /** Original DOM event if available */
90
+ originalEvent: Event | null;
91
+
92
+ /** Function to prevent default behavior */
93
+ preventDefault: () => void;
94
+
95
+ /** Whether default behavior was prevented */
96
+ defaultPrevented: boolean;
97
+ }
98
+
99
+ /**
100
+ * Search component public API interface
101
+ * @interface SearchComponent
102
+ */
103
+ export interface SearchComponent {
104
+ /** The root element of the search component */
105
+ element: HTMLElement;
106
+
107
+ /** Sets the search value */
108
+ setValue: (value: string, triggerEvent?: boolean) => SearchComponent;
109
+
110
+ /** Gets the search value */
111
+ getValue: () => string;
112
+
113
+ /** Sets placeholder text */
114
+ setPlaceholder: (text: string) => SearchComponent;
115
+
116
+ /** Gets placeholder text */
117
+ getPlaceholder: () => string;
118
+
119
+ /** Sets leading icon */
120
+ setLeadingIcon: (iconHtml: string) => SearchComponent;
121
+
122
+ /** Sets trailing icon */
123
+ setTrailingIcon: (iconHtml: string) => SearchComponent;
124
+
125
+ /** Sets second trailing icon */
126
+ setTrailingIcon2: (iconHtml: string) => SearchComponent;
127
+
128
+ /** Sets avatar */
129
+ setAvatar: (avatarHtml: string) => SearchComponent;
130
+
131
+ /** Shows or hides clear button */
132
+ showClearButton: (show: boolean) => SearchComponent;
133
+
134
+ /** Updates suggestions */
135
+ setSuggestions: (suggestions: string[] | Array<{text: string, value?: string, icon?: string}>) => SearchComponent;
136
+
137
+ /** Shows or hides suggestions */
138
+ showSuggestions: (show: boolean) => SearchComponent;
139
+
140
+ /** Focuses the search input */
141
+ focus: () => SearchComponent;
142
+
143
+ /** Blurs the search input */
144
+ blur: () => SearchComponent;
145
+
146
+ /** Expands the search bar into view mode (if in bar mode) */
147
+ expand: () => SearchComponent;
148
+
149
+ /** Collapses the search view back to bar mode */
150
+ collapse: () => SearchComponent;
151
+
152
+ /** Clears the search input */
153
+ clear: () => SearchComponent;
154
+
155
+ /** Submits the search */
156
+ submit: () => SearchComponent;
157
+
158
+ /** Enables the search component */
159
+ enable: () => SearchComponent;
160
+
161
+ /** Disables the search component */
162
+ disable: () => SearchComponent;
163
+
164
+ /** Checks if search is disabled */
165
+ isDisabled: () => boolean;
166
+
167
+ /** Adds event listener */
168
+ on: (event: SearchEventType, handler: (event: SearchEvent) => void) => SearchComponent;
169
+
170
+ /** Removes event listener */
171
+ off: (event: SearchEventType, handler: (event: SearchEvent) => void) => SearchComponent;
172
+
173
+ /** Destroys the search component and cleans up resources */
174
+ destroy: () => void;
175
+ }
@@ -0,0 +1,80 @@
1
+ // src/components/segmented-button/config.ts
2
+ import { createComponentConfig } from '../../core/config/component-config';
3
+ import { SegmentedButtonConfig, SelectionMode } from './types';
4
+
5
+ export const DEFAULT_CHECKMARK_ICON = `
6
+ <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
7
+ <polyline points="20 6 9 17 4 12"></polyline>
8
+ </svg>`;
9
+
10
+ /**
11
+ * Default configuration values for segmented buttons
12
+ * @internal
13
+ */
14
+ export const DEFAULT_CONFIG = {
15
+ mode: SelectionMode.SINGLE,
16
+ ripple: true
17
+ };
18
+
19
+ /**
20
+ * Creates the base configuration for Segmented Button component
21
+ * @param {SegmentedButtonConfig} config - User provided configuration
22
+ * @returns {SegmentedButtonConfig} Complete configuration with defaults applied
23
+ * @internal
24
+ */
25
+ export const createBaseConfig = (config: SegmentedButtonConfig = {}): SegmentedButtonConfig =>
26
+ createComponentConfig(DEFAULT_CONFIG, config, 'segmented-button') as SegmentedButtonConfig;
27
+
28
+ /**
29
+ * Generates element configuration for the Segmented Button container
30
+ * @param {SegmentedButtonConfig} config - Segmented Button configuration
31
+ * @returns {Object} Element configuration object for withElement
32
+ * @internal
33
+ */
34
+ export const getContainerConfig = (config: SegmentedButtonConfig) => ({
35
+ tag: 'div',
36
+ componentName: 'segmented-button',
37
+ attrs: {
38
+ role: 'group',
39
+ 'aria-label': 'Segmented button',
40
+ 'data-mode': config.mode || SelectionMode.SINGLE
41
+ },
42
+ className: [
43
+ config.class,
44
+ config.disabled ? `${config.prefix}-segmented-button--disabled` : null
45
+ ],
46
+ interactive: true
47
+ });
48
+
49
+ /**
50
+ * Generates configuration for a segment element
51
+ * @param {Object} segment - Segment configuration
52
+ * @param {string} prefix - Component prefix
53
+ * @param {boolean} groupDisabled - Whether the entire group is disabled
54
+ * @returns {Object} Element configuration for the segment
55
+ * @internal
56
+ */
57
+ export const getSegmentConfig = (segment, prefix, groupDisabled = false) => {
58
+ const isDisabled = groupDisabled || segment.disabled;
59
+
60
+ return {
61
+ tag: 'button',
62
+ attrs: {
63
+ type: 'button',
64
+ role: 'button',
65
+ disabled: isDisabled ? true : undefined,
66
+ 'aria-pressed': segment.selected ? 'true' : 'false',
67
+ value: segment.value
68
+ },
69
+ className: [
70
+ `${prefix}-segment`,
71
+ segment.selected ? `${prefix}-segment--selected` : null,
72
+ isDisabled ? `${prefix}-segment--disabled` : null,
73
+ segment.class
74
+ ],
75
+ forwardEvents: {
76
+ click: (component) => !isDisabled
77
+ },
78
+ interactive: !isDisabled
79
+ };
80
+ };
@@ -0,0 +1,4 @@
1
+ // src/components/segmented-button/index.ts
2
+ export { default, default as createSegmentedButton } from './segmented-button';
3
+ export { SelectionMode } from './types';
4
+ export type { SegmentedButtonConfig, SegmentedButtonComponent, SegmentConfig, Segment } from './types';