mtrl 0.2.6 → 0.2.7

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 (147) hide show
  1. package/index.ts +18 -0
  2. package/package.json +1 -1
  3. package/src/components/badge/_styles.scss +117 -109
  4. package/src/components/badge/api.ts +57 -59
  5. package/src/components/badge/badge.ts +16 -2
  6. package/src/components/badge/config.ts +65 -11
  7. package/src/components/badge/constants.ts +22 -12
  8. package/src/components/badge/features.ts +44 -40
  9. package/src/components/badge/types.ts +42 -30
  10. package/src/components/bottom-app-bar/_styles.scss +103 -0
  11. package/src/components/bottom-app-bar/bottom-app-bar.ts +196 -0
  12. package/src/components/bottom-app-bar/config.ts +73 -0
  13. package/src/components/bottom-app-bar/index.ts +11 -0
  14. package/src/components/bottom-app-bar/types.ts +108 -0
  15. package/src/components/button/_styles.scss +0 -10
  16. package/src/components/button/api.ts +5 -0
  17. package/src/components/button/config.ts +5 -0
  18. package/src/components/button/types.ts +6 -0
  19. package/src/components/card/card.ts +13 -25
  20. package/src/components/card/config.ts +67 -22
  21. package/src/components/card/features.ts +3 -0
  22. package/src/components/card/types.ts +28 -0
  23. package/src/components/checkbox/_styles.scss +0 -2
  24. package/src/components/datepicker/_styles.scss +358 -0
  25. package/src/components/datepicker/api.ts +272 -0
  26. package/src/components/datepicker/config.ts +144 -0
  27. package/src/components/datepicker/constants.ts +98 -0
  28. package/src/components/datepicker/datepicker.ts +346 -0
  29. package/src/components/datepicker/index.ts +9 -0
  30. package/src/components/datepicker/render.ts +452 -0
  31. package/src/components/datepicker/types.ts +268 -0
  32. package/src/components/datepicker/utils.ts +290 -0
  33. package/src/components/dialog/_styles.scss +174 -128
  34. package/src/components/dialog/api.ts +48 -13
  35. package/src/components/dialog/config.ts +9 -5
  36. package/src/components/dialog/dialog.ts +6 -3
  37. package/src/components/dialog/features.ts +290 -130
  38. package/src/components/dialog/types.ts +7 -4
  39. package/src/components/divider/_styles.scss +57 -0
  40. package/src/components/divider/config.ts +81 -0
  41. package/src/components/divider/divider.ts +37 -0
  42. package/src/components/divider/features.ts +207 -0
  43. package/src/components/divider/index.ts +5 -0
  44. package/src/components/divider/types.ts +55 -0
  45. package/src/components/extended-fab/_styles.scss +267 -0
  46. package/src/components/extended-fab/api.ts +141 -0
  47. package/src/components/extended-fab/config.ts +108 -0
  48. package/src/components/extended-fab/constants.ts +36 -0
  49. package/src/components/extended-fab/extended-fab.ts +125 -0
  50. package/src/components/extended-fab/index.ts +4 -0
  51. package/src/components/extended-fab/types.ts +287 -0
  52. package/src/components/fab/_styles.scss +225 -0
  53. package/src/components/fab/api.ts +97 -0
  54. package/src/components/fab/config.ts +94 -0
  55. package/src/components/fab/constants.ts +41 -0
  56. package/src/components/fab/fab.ts +67 -0
  57. package/src/components/fab/index.ts +4 -0
  58. package/src/components/fab/types.ts +234 -0
  59. package/src/components/navigation/_styles.scss +1 -0
  60. package/src/components/navigation/api.ts +78 -50
  61. package/src/components/navigation/features/items.ts +280 -0
  62. package/src/components/navigation/nav-item.ts +72 -23
  63. package/src/components/navigation/navigation.ts +54 -2
  64. package/src/components/navigation/types.ts +210 -188
  65. package/src/components/search/_styles.scss +306 -0
  66. package/src/components/search/api.ts +203 -0
  67. package/src/components/search/config.ts +87 -0
  68. package/src/components/search/constants.ts +21 -0
  69. package/src/components/search/features/index.ts +4 -0
  70. package/src/components/search/features/search.ts +718 -0
  71. package/src/components/search/features/states.ts +165 -0
  72. package/src/components/search/features/structure.ts +198 -0
  73. package/src/components/search/index.ts +10 -0
  74. package/src/components/search/search.ts +52 -0
  75. package/src/components/search/types.ts +163 -0
  76. package/src/components/segmented-button/_styles.scss +117 -0
  77. package/src/components/segmented-button/config.ts +67 -0
  78. package/src/components/segmented-button/constants.ts +42 -0
  79. package/src/components/segmented-button/index.ts +4 -0
  80. package/src/components/segmented-button/segment.ts +155 -0
  81. package/src/components/segmented-button/segmented-button.ts +250 -0
  82. package/src/components/segmented-button/types.ts +219 -0
  83. package/src/components/slider/_styles.scss +83 -24
  84. package/src/components/slider/accessibility.md +5 -5
  85. package/src/components/slider/api.ts +41 -120
  86. package/src/components/slider/config.ts +51 -47
  87. package/src/components/slider/features/handlers.ts +495 -0
  88. package/src/components/slider/features/index.ts +1 -2
  89. package/src/components/slider/features/slider.ts +66 -84
  90. package/src/components/slider/features/states.ts +195 -0
  91. package/src/components/slider/features/structure.ts +136 -206
  92. package/src/components/slider/features/ui.ts +145 -206
  93. package/src/components/slider/index.ts +2 -11
  94. package/src/components/slider/slider.ts +9 -12
  95. package/src/components/slider/types.ts +39 -24
  96. package/src/components/switch/_styles.scss +0 -2
  97. package/src/components/tabs/_styles.scss +94 -32
  98. package/src/components/tabs/features.ts +4 -2
  99. package/src/components/tabs/indicator.ts +73 -13
  100. package/src/components/tabs/types.ts +10 -2
  101. package/src/components/timepicker/README.md +277 -0
  102. package/src/components/timepicker/_styles.scss +451 -0
  103. package/src/components/timepicker/api.ts +632 -0
  104. package/src/components/timepicker/clockdial.ts +482 -0
  105. package/src/components/timepicker/config.ts +130 -0
  106. package/src/components/timepicker/constants.ts +138 -0
  107. package/src/components/timepicker/index.ts +8 -0
  108. package/src/components/timepicker/render.ts +613 -0
  109. package/src/components/timepicker/timepicker.ts +117 -0
  110. package/src/components/timepicker/types.ts +336 -0
  111. package/src/components/timepicker/utils.ts +241 -0
  112. package/src/components/top-app-bar/_styles.scss +225 -0
  113. package/src/components/top-app-bar/config.ts +83 -0
  114. package/src/components/top-app-bar/index.ts +11 -0
  115. package/src/components/top-app-bar/top-app-bar.ts +316 -0
  116. package/src/components/top-app-bar/types.ts +140 -0
  117. package/src/core/build/_ripple.scss +6 -6
  118. package/src/core/build/ripple.ts +72 -95
  119. package/src/core/compose/features/icon.ts +3 -1
  120. package/src/core/compose/features/ripple.ts +4 -1
  121. package/src/core/compose/features/textlabel.ts +26 -2
  122. package/src/core/dom/create.ts +5 -0
  123. package/src/index.ts +9 -0
  124. package/src/styles/abstract/_theme.scss +9 -1
  125. package/src/styles/themes/_autumn.scss +21 -0
  126. package/src/styles/themes/_base-theme.scss +61 -0
  127. package/src/styles/themes/_baseline.scss +58 -0
  128. package/src/styles/themes/_bluekhaki.scss +125 -0
  129. package/src/styles/themes/_brownbeige.scss +125 -0
  130. package/src/styles/themes/_browngreen.scss +125 -0
  131. package/src/styles/themes/_forest.scss +6 -0
  132. package/src/styles/themes/_greenbeige.scss +125 -0
  133. package/src/styles/themes/_material.scss +125 -0
  134. package/src/styles/themes/_ocean.scss +6 -0
  135. package/src/styles/themes/_sageivory.scss +125 -0
  136. package/src/styles/themes/_spring.scss +6 -0
  137. package/src/styles/themes/_summer.scss +5 -0
  138. package/src/styles/themes/_sunset.scss +5 -0
  139. package/src/styles/themes/_tealcaramel.scss +125 -0
  140. package/src/styles/themes/_winter.scss +6 -0
  141. package/src/components/navigation/features/items.js +0 -192
  142. package/src/components/slider/features/appearance.ts +0 -94
  143. package/src/components/slider/features/disabled.ts +0 -68
  144. package/src/components/slider/features/events.ts +0 -164
  145. package/src/components/slider/features/interactions.ts +0 -396
  146. package/src/components/slider/features/keyboard.ts +0 -233
  147. package/src/core/collection/adapters/mongodb.js +0 -232
@@ -1,9 +1,6 @@
1
1
  // src/components/slider/types.ts
2
2
  import { SLIDER_COLORS, SLIDER_SIZES, SLIDER_EVENTS } from './constants';
3
3
 
4
- /**
5
- * Configuration interface for the Slider component
6
- */
7
4
  export interface SliderConfig {
8
5
  /** Minimum value of the slider */
9
6
  min?: number;
@@ -14,7 +11,7 @@ export interface SliderConfig {
14
11
  /** Current value of the slider */
15
12
  value?: number;
16
13
 
17
- /** Secondary value for range slider (when using two thumbs) */
14
+ /** Secondary value for range slider (when using two handles) */
18
15
  secondValue?: number;
19
16
 
20
17
  /** Step size for discrete slider */
@@ -41,9 +38,21 @@ export interface SliderConfig {
41
38
  /** Whether to snap to steps while dragging (discrete slider) */
42
39
  snapToSteps?: boolean;
43
40
 
44
- /** Whether the slider is a range slider (two thumbs) */
41
+ /** Whether the slider is a range slider (two handles) */
45
42
  range?: boolean;
46
43
 
44
+ /** Label text for the slider */
45
+ label?: string;
46
+
47
+ /** Position of the label (start or end) - defaults to 'start' */
48
+ labelPosition?: 'start' | 'end';
49
+
50
+ /** Icon to display with the slider */
51
+ icon?: string;
52
+
53
+ /** Position of the icon (start or end) */
54
+ iconPosition?: 'start' | 'end';
55
+
47
56
  /** Additional CSS classes */
48
57
  class?: string;
49
58
 
@@ -53,34 +62,28 @@ export interface SliderConfig {
53
62
  };
54
63
  }
55
64
 
56
- /**
57
- * Slider event object
58
- */
59
65
  export interface SliderEvent {
60
- /** Slider component instance */
61
- slider: SliderComponent;
66
+ /** The slider component that triggered the event */
67
+ slider: any;
62
68
 
63
69
  /** Current slider value */
64
70
  value: number;
65
71
 
66
- /** Secondary slider value (for range slider) */
67
- secondValue?: number;
72
+ /** Secondary value (for range sliders) */
73
+ secondValue: number | null;
68
74
 
69
- /** Original event if applicable */
70
- originalEvent?: Event;
75
+ /** Original DOM event if available */
76
+ originalEvent: Event | null;
71
77
 
72
- /** Whether to prevent the default action */
78
+ /** Function to prevent default behavior */
73
79
  preventDefault: () => void;
74
80
 
75
- /** Whether default action was prevented */
81
+ /** Whether default behavior was prevented */
76
82
  defaultPrevented: boolean;
77
83
  }
78
84
 
79
- /**
80
- * Slider component interface
81
- */
82
85
  export interface SliderComponent {
83
- /** Slider element */
86
+ /** The root element of the slider */
84
87
  element: HTMLElement;
85
88
 
86
89
  /** Sets slider value */
@@ -95,16 +98,16 @@ export interface SliderComponent {
95
98
  /** Gets secondary slider value */
96
99
  getSecondValue: () => number | null;
97
100
 
98
- /** Sets slider minimum */
101
+ /** Sets slider minimum value */
99
102
  setMin: (min: number) => SliderComponent;
100
103
 
101
- /** Gets slider minimum */
104
+ /** Gets slider minimum value */
102
105
  getMin: () => number;
103
106
 
104
- /** Sets slider maximum */
107
+ /** Sets slider maximum value */
105
108
  setMax: (max: number) => SliderComponent;
106
109
 
107
- /** Gets slider maximum */
110
+ /** Gets slider maximum value */
108
111
  getMax: () => number;
109
112
 
110
113
  /** Sets slider step size */
@@ -140,6 +143,18 @@ export interface SliderComponent {
140
143
  /** Shows or hides current value while dragging */
141
144
  showCurrentValue: (show: boolean) => SliderComponent;
142
145
 
146
+ /** Sets label text */
147
+ setLabel: (text: string) => SliderComponent;
148
+
149
+ /** Gets label text */
150
+ getLabel: () => string;
151
+
152
+ /** Sets icon HTML */
153
+ setIcon: (iconHtml: string) => SliderComponent;
154
+
155
+ /** Gets icon HTML */
156
+ getIcon: () => string;
157
+
143
158
  /** Adds event listener */
144
159
  on: (event: keyof typeof SLIDER_EVENTS | typeof SLIDER_EVENTS[keyof typeof SLIDER_EVENTS], handler: (event: SliderEvent) => void) => SliderComponent;
145
160
 
@@ -89,8 +89,6 @@ $component: '#{base.$prefix}-switch';
89
89
 
90
90
  // Label position variants
91
91
  &--label-start {
92
- flex-direction: row-reverse;
93
-
94
92
  .#{$component}-label {
95
93
  margin-left: 0;
96
94
  margin-right: 12px;
@@ -25,11 +25,18 @@ $container: '#{base.$prefix}-tabs';
25
25
  &:has(.#{$component}--icon-and-text) {
26
26
  min-height: 64px;
27
27
  }
28
+
29
+ // Primary indicator specific styling
30
+ .#{$container}-indicator {
31
+ height: 4px;
32
+ border-radius: 3px 3px 0 0;
33
+ background-color: t.color('primary');
34
+ }
28
35
  }
29
36
 
30
37
  &--secondary {
31
38
  min-height: 48px;
32
- // Tab indicator styling
39
+ // Tab indicator styling for secondary variant
33
40
  .#{$container}-indicator {
34
41
  height: 2px;
35
42
  border-radius: 0;
@@ -63,13 +70,10 @@ $container: '#{base.$prefix}-tabs';
63
70
  background-color: t.color('outline-variant'); // MD3: Outline variant color
64
71
  }
65
72
 
66
- // Tab indicator styling
73
+ // Tab indicator styling - base styles
67
74
  &-indicator {
68
75
  position: absolute;
69
76
  bottom: 1px;
70
- height: 4px;
71
- background-color: t.color('primary');
72
- border-radius: 3px 3px 0 0;
73
77
  transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1),
74
78
  width 250ms cubic-bezier(0.4, 0, 0.2, 1);
75
79
  z-index: 1; // Ensure indicator appears above divider
@@ -122,12 +126,12 @@ $container: '#{base.$prefix}-tabs';
122
126
 
123
127
  &:focus-visible {
124
128
  outline: 2px solid t.color('primary');
125
- outline-offset: 0;
129
+ outline-offset: -2px;
126
130
  z-index: 1;
127
131
  }
128
132
 
129
133
  // Disabled state
130
- &:disabled, &[disabled], &--disabled {
134
+ &--disabled, &:disabled {
131
135
  pointer-events: none;
132
136
  opacity: 0.38;
133
137
  }
@@ -189,44 +193,102 @@ $container: '#{base.$prefix}-tabs';
189
193
  }
190
194
  }
191
195
 
192
- // Active Tab - Apply to both primary and secondary
193
- &--active {
194
- color: t.color('primary'); // MD3: Primary color
195
-
196
- .#{$component}-icon {
197
- color: t.color('primary'); // MD3: Primary color
198
- }
199
-
200
- // Hover state for active tabs
201
- &:hover:not(:disabled):not([disabled]) {
202
- background-color: t.alpha('primary', 0.08);
196
+ // ACTIVE TAB STATES - PRIMARY VARIANT
197
+ .#{$container}--primary & {
198
+ // Active Tab Base State - Apply to primary variant
199
+ &--active {
200
+ color: t.color('primary');
201
+
202
+ .#{$component}-icon {
203
+ color: t.color('primary');
204
+ }
205
+
206
+ // Hover state for active tabs
207
+ &:hover, &.#{$component}--hover {
208
+ background-color: t.alpha('primary', 0.08);
209
+ }
210
+
211
+ // Focused state for active tabs
212
+ &:focus-visible, &.#{$component}--focus {
213
+ background-color: t.alpha('primary', 0.1);
214
+ outline-color: t.color('primary');
215
+ }
216
+
217
+ // Pressed state for active tabs
218
+ &:active, &.#{$component}--pressed {
219
+ background-color: t.alpha('primary', 0.12);
220
+ }
203
221
  }
204
222
 
205
- // Pressed state for active tabs
206
- &:active:not(:disabled):not([disabled]) {
207
- background-color: t.alpha('primary', 0.12);
223
+ // Inactive Tabs - Primary variant
224
+ &:not(.#{$component}--active):not(.#{$component}--disabled):not(:disabled) {
225
+ color: t.color('on-surface-variant');
226
+
227
+ // Hover state for inactive tabs
228
+ &:hover, &.#{$component}--hover {
229
+ background-color: t.alpha('on-surface-variant', 0.08);
230
+ }
231
+
232
+ // Focused state for inactive tabs
233
+ &:focus-visible, &.#{$component}--focus {
234
+ background-color: t.alpha('on-surface-variant', 0.1);
235
+ outline-color: t.color('on-surface-variant');
236
+ }
237
+
238
+ // Pressed state for inactive tabs
239
+ &:active, &.#{$component}--pressed {
240
+ background-color: t.alpha('on-surface-variant', 0.12);
241
+ }
208
242
  }
209
243
  }
210
244
 
211
- // Secondary variant overrides
245
+ // ACTIVE TAB STATES - SECONDARY VARIANT
212
246
  .#{$container}--secondary & {
247
+ // Active Tab - Secondary variant
213
248
  &--active {
214
249
  color: t.color('on-surface');
215
250
 
216
251
  .#{$component}-icon {
217
252
  color: t.color('on-surface');
218
253
  }
254
+
255
+ // Hover state for active tabs - secondary
256
+ &:hover, &.#{$component}--hover {
257
+ background-color: t.alpha('on-surface', 0.08);
258
+ }
259
+
260
+ // Focused state for active tabs - secondary
261
+ &:focus-visible, &.#{$component}--focus {
262
+ background-color: t.alpha('on-surface', 0.1);
263
+ outline-color: t.color('on-surface');
264
+ }
265
+
266
+ // Pressed state for active tabs - secondary
267
+ &:active, &.#{$component}--pressed {
268
+ background-color: t.alpha('on-surface', 0.12);
269
+ }
270
+ }
271
+
272
+ // Inactive Tabs - Secondary variant
273
+ &:not(.#{$component}--active):not(.#{$component}--disabled):not(:disabled) {
274
+ color: t.color('on-surface-variant');
275
+
276
+ // Hover state for inactive tabs - secondary
277
+ &:hover, &.#{$component}--hover {
278
+ background-color: t.alpha('on-surface-variant', 0.08);
279
+ }
280
+
281
+ // Focused state for inactive tabs - secondary
282
+ &:focus-visible, &.#{$component}--focus {
283
+ background-color: t.alpha('on-surface-variant', 0.1);
284
+ outline-color: t.color('on-surface-variant');
285
+ }
286
+
287
+ // Pressed state for inactive tabs - secondary
288
+ &:active, &.#{$component}--pressed {
289
+ background-color: t.alpha('on-surface-variant', 0.12);
290
+ }
219
291
  }
220
- }
221
-
222
- // Hover state for inactive tabs
223
- &:hover:not(:disabled):not(&--active):not([disabled]) {
224
- background-color: t.alpha('on-surface-variant', 0.08);
225
- }
226
-
227
- // Pressed state for inactive tabs
228
- &:active:not(:disabled):not([disabled]):not(&--active) {
229
- background-color: t.alpha('on-surface-variant', 0.12);
230
292
  }
231
293
 
232
294
  // Ripple effect styling
@@ -282,12 +282,14 @@ export const withIndicator = <T extends IndicatorFeatureConfig>(config: T) =>
282
282
  const indicator: TabIndicator = createTabIndicator({
283
283
  prefix: config.prefix,
284
284
  // Support both new and legacy config
285
- widthStrategy: indicatorConfig.widthStrategy || config.indicatorWidthStrategy || 'fixed',
285
+ widthStrategy: indicatorConfig.widthStrategy || config.indicatorWidthStrategy || 'auto', // Changed default to 'auto'
286
286
  height: indicatorConfig.height || config.indicatorHeight || 3,
287
287
  fixedWidth: indicatorConfig.fixedWidth || 40,
288
288
  animationDuration: indicatorConfig.animationDuration || 250,
289
289
  animationTiming: indicatorConfig.animationTiming || 'cubic-bezier(0.4, 0, 0.2, 1)',
290
- color: indicatorConfig.color
290
+ color: indicatorConfig.color,
291
+ // Pass the tabs variant to the indicator
292
+ variant: config.variant || 'primary'
291
293
  });
292
294
 
293
295
  // Find the scroll container and add the indicator to it
@@ -8,7 +8,7 @@ export interface TabIndicatorConfig {
8
8
  /** Height of the indicator in pixels */
9
9
  height?: number;
10
10
  /** Width strategy - fixed size or dynamic based on tab width */
11
- widthStrategy?: 'fixed' | 'dynamic' | 'content';
11
+ widthStrategy?: 'fixed' | 'dynamic' | 'content' | 'auto';
12
12
  /** Fixed width in pixels when using fixed strategy */
13
13
  fixedWidth?: number;
14
14
  /** Animation duration in milliseconds */
@@ -21,6 +21,8 @@ export interface TabIndicatorConfig {
21
21
  prefix?: string;
22
22
  /** Custom color for the indicator */
23
23
  color?: string;
24
+ /** Tabs variant (primary or secondary) */
25
+ variant?: string;
24
26
  }
25
27
 
26
28
  /**
@@ -47,12 +49,13 @@ export interface TabIndicator {
47
49
  * Default configuration for tab indicator
48
50
  */
49
51
  const DEFAULT_CONFIG: TabIndicatorConfig = {
50
- widthStrategy: 'fixed',
52
+ widthStrategy: 'auto', // Changed to 'auto' to match variant behavior
51
53
  fixedWidth: 40,
52
54
  animationDuration: 250,
53
55
  animationTiming: 'cubic-bezier(0.4, 0, 0.2, 1)',
54
56
  visible: true,
55
- prefix: 'mtrl'
57
+ prefix: 'mtrl',
58
+ variant: 'primary'
56
59
  };
57
60
 
58
61
  /**
@@ -81,17 +84,38 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
81
84
  let currentTab: TabComponent | null = null;
82
85
 
83
86
  /**
84
- * Calculates indicator width based on strategy
87
+ * Calculates indicator width based on strategy and variant
85
88
  * @param tab - Target tab
86
89
  * @returns Width in pixels
87
90
  */
88
91
  const calculateWidth = (tab: TabComponent): number => {
92
+ // Use auto strategy to determine based on variant
93
+ if (mergedConfig.widthStrategy === 'auto') {
94
+ // For secondary tabs, use full tab width
95
+ if (mergedConfig.variant === 'secondary') {
96
+ return tab.element.offsetWidth;
97
+ }
98
+
99
+ // For primary tabs (default), use text label width
100
+ const textElement = tab.element.querySelector(`.${prefix}-tab-text`) ||
101
+ tab.element.querySelector(`.${prefix}-button-text`);
102
+
103
+ if (textElement) {
104
+ return textElement.clientWidth;
105
+ }
106
+
107
+ // Fallback to dynamic if text element not found
108
+ return Math.max(tab.element.offsetWidth / 2, 30);
109
+ }
110
+
111
+ // Handle other strategies when explicitly set
89
112
  switch (mergedConfig.widthStrategy) {
90
113
  case 'dynamic':
91
114
  return Math.max(tab.element.offsetWidth / 2, 30);
92
115
  case 'content':
93
116
  // Try to match content width
94
- const text = tab.element.querySelector(`.${prefix}-button-text`);
117
+ const text = tab.element.querySelector(`.${prefix}-button-text`) ||
118
+ tab.element.querySelector(`.${prefix}-tab-text`);
95
119
  if (text) {
96
120
  return Math.max(text.clientWidth, 30);
97
121
  }
@@ -126,6 +150,46 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
126
150
  };
127
151
  };
128
152
 
153
+ /**
154
+ * Calculates indicator position based on variant and width
155
+ * @param tab - Target tab
156
+ * @param indicatorWidth - Width of the indicator
157
+ * @returns {Object} Position information
158
+ */
159
+ const calculatePosition = (tab: TabComponent, indicatorWidth: number): { left: number } => {
160
+ const { left, width: tabWidth } = getTabPosition(tab.element);
161
+
162
+ // For primary tabs with text label width, center under the text
163
+ if (mergedConfig.variant !== 'secondary' &&
164
+ (mergedConfig.widthStrategy === 'auto' || mergedConfig.widthStrategy === 'content')) {
165
+ const textElement = tab.element.querySelector(`.${prefix}-tab-text`) ||
166
+ tab.element.querySelector(`.${prefix}-button-text`);
167
+
168
+ if (textElement) {
169
+ // Get text element position relative to tab
170
+ const textRect = textElement.getBoundingClientRect();
171
+ const tabRect = tab.element.getBoundingClientRect();
172
+ const textLeft = textRect.left - tabRect.left;
173
+
174
+ // Center indicator under text
175
+ return {
176
+ left: left + textLeft
177
+ };
178
+ }
179
+ }
180
+
181
+ // For secondary tabs or when no text element found
182
+ // For centered indicators, center in the tab
183
+ if (indicatorWidth < tabWidth) {
184
+ return {
185
+ left: left + ((tabWidth - indicatorWidth) / 2)
186
+ };
187
+ }
188
+
189
+ // For full-width indicators
190
+ return { left };
191
+ };
192
+
129
193
  /**
130
194
  * Moves indicator to specified tab
131
195
  * @param tab - Target tab
@@ -140,15 +204,11 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
140
204
  // Store current tab for later updates
141
205
  currentTab = tab;
142
206
 
143
- // Calculate indicator width
207
+ // Calculate indicator width based on strategy and variant
144
208
  const width = calculateWidth(tab);
145
209
 
146
- // Get tab position directly from DOM
147
- const { left, width: tabWidth } = getTabPosition(tab.element);
148
-
149
- // Calculate position to center indicator on tab
150
- const tabCenter = left + (tabWidth / 2);
151
- const indicatorLeft = tabCenter - (width / 2);
210
+ // Calculate position based on width and variant
211
+ const { left } = calculatePosition(tab, width);
152
212
 
153
213
  // Apply position immediately if requested
154
214
  if (immediate) {
@@ -160,7 +220,7 @@ export const createTabIndicator = (config: TabIndicatorConfig = {}): TabIndicato
160
220
 
161
221
  // Update position and width
162
222
  element.style.width = `${width}px`;
163
- element.style.transform = `translateX(${indicatorLeft}px)`;
223
+ element.style.transform = `translateX(${left}px)`;
164
224
 
165
225
  // Restore transition after immediate update
166
226
  if (immediate) {
@@ -10,8 +10,14 @@ import { TabIndicator } from './indicator';
10
10
  export interface IndicatorConfig {
11
11
  /** Height of the indicator in pixels */
12
12
  height?: number;
13
- /** Width strategy for the indicator */
14
- widthStrategy?: 'fixed' | 'dynamic' | 'content';
13
+ /**
14
+ * Width strategy for the indicator
15
+ * - 'fixed': Uses a fixed width defined by fixedWidth
16
+ * - 'dynamic': Uses half the tab width
17
+ * - 'content': Uses the text content width
18
+ * - 'auto': Adapts based on variant (primary: text width, secondary: full tab width)
19
+ */
20
+ widthStrategy?: 'fixed' | 'dynamic' | 'content' | 'auto';
15
21
  /** Fixed width in pixels (when using fixed strategy) */
16
22
  fixedWidth?: number;
17
23
  /** Animation duration in milliseconds */
@@ -20,6 +26,8 @@ export interface IndicatorConfig {
20
26
  animationTiming?: string;
21
27
  /** Custom color for the indicator */
22
28
  color?: string;
29
+ /** Tab variant (primary or secondary) */
30
+ variant?: string;
23
31
  }
24
32
 
25
33
  /**