cynx-ui 1.3.6 → 1.3.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.
@@ -24,7 +24,9 @@ export default function Select({
24
24
  searchable = false,
25
25
  clearable = false,
26
26
  onClear,
27
+ size = 'md',
27
28
  style = {},
29
+ rounded = false,
28
30
  }) {
29
31
  const [open, setOpen] = useState(false);
30
32
  const [query, setQuery] = useState('');
@@ -116,14 +118,26 @@ export default function Select({
116
118
 
117
119
  const selectedValues = multi ? (Array.isArray(value) ? value : []) : (value !== undefined && value !== null ? [value] : []);
118
120
 
121
+ const heightVal = size === 'xs' ? 24 : size === 'sm' ? 28 : 32;
122
+ const paddingVal = size === 'xs' ? '0 6px' : size === 'sm' ? '0 8px' : '0 10px';
123
+ const fontSizeVal = size === 'xs' ? '11px' : size === 'sm' ? '11.5px' : '13px';
124
+
125
+ let radiusVal = rounded
126
+ ? (typeof rounded === 'string'
127
+ ? (rounded.endsWith('px') || rounded.endsWith('rem') || rounded.startsWith('var') ? rounded : `${rounded}px`)
128
+ : typeof rounded === 'number'
129
+ ? `${rounded}px`
130
+ : '6px')
131
+ : (size === 'xs' ? '4px' : size === 'sm' ? '6px' : 'var(--rmd,var(--rads))');
132
+
119
133
  return (
120
134
  <div
121
135
  ref={wrapRef}
122
136
  style={{
123
137
  position: 'relative',
124
138
  width: '100%',
139
+ boxSizing: 'border-box',
125
140
  fontFamily: 'var(--f,var(--sans))',
126
- fontSize: 'var(--fsz,13px)',
127
141
  opacity: disabled ? 0.5 : 1,
128
142
  pointerEvents: disabled ? 'none' : undefined,
129
143
  ...style,
@@ -133,19 +147,20 @@ export default function Select({
133
147
  onClick={handleOpen}
134
148
  style={{
135
149
  width: '100%',
150
+ boxSizing: 'border-box',
136
151
  display: 'flex',
137
152
  alignItems: 'center',
138
153
  justifyContent: 'space-between',
139
- gap: 8,
140
- padding: '8px 11px',
141
- height: 32,
154
+ gap: 4,
155
+ padding: paddingVal,
156
+ height: heightVal,
142
157
  background: 'var(--sf,var(--surface))',
143
- border: `1.5px solid ${open ? 'var(--accent)' : 'var(--bd2,var(--border))'}`,
144
- borderRadius: 'var(--rmd,var(--rads))',
158
+ border: `1px solid ${open ? 'var(--accent)' : 'var(--bd2,var(--border))'}`,
159
+ borderRadius: radiusVal,
145
160
  cursor: 'pointer',
146
161
  fontFamily: 'var(--f,var(--sans,inherit))',
147
- fontSize: '12.5px',
148
- lineHeight: 1,
162
+ fontSize: fontSizeVal,
163
+ lineHeight: 1.2,
149
164
  color: 'var(--t,var(--primary-text))',
150
165
  outline: 'none',
151
166
  transition: 'border-color var(--tr,.15s), box-shadow var(--tr,.15s)',
@@ -182,7 +197,7 @@ export default function Select({
182
197
  </div>
183
198
  ) : !multi && selectedValues.length > 0 ? (
184
199
  <span style={{ fontWeight: 400, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
185
- {getLabel(selectedValues[0])}
200
+ {getLabel(selectedValues[0]) || '\u00A0'}
186
201
  </span>
187
202
  ) : (
188
203
  <span style={{ color: 'var(--t3,var(--grey-3))', fontWeight: 400 }}>{placeholder}</span>
@@ -209,11 +224,11 @@ export default function Select({
209
224
  onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--danger, #ef4444)'; }}
210
225
  onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--muted, #64748b)'; }}
211
226
  >
212
- <X size={13} />
227
+ <X size={12} />
213
228
  </span>
214
229
  ) : (
215
230
  <ChevronDown
216
- size={13}
231
+ size={12}
217
232
  style={{
218
233
  flexShrink: 0,
219
234
  color: 'var(--t3,var(--grey-3))',
@@ -233,33 +248,35 @@ export default function Select({
233
248
  top: pos.top,
234
249
  left: pos.left,
235
250
  width: pos.width,
251
+ boxSizing: 'border-box',
236
252
  background: 'var(--sf,var(--surface))',
237
253
  border: '1px solid var(--bd,var(--border))',
238
- borderRadius: 'var(--rmd,var(--rads))',
254
+ borderRadius: radiusVal,
239
255
  boxShadow: 'var(--shadow,0 1px 3px rgba(20,22,41,.1))',
240
- padding: 4,
256
+ padding: 3,
241
257
  animation: 'selFadeIn .15s ease',
242
258
  overflow: 'hidden',
243
259
  }}
244
260
  >
245
261
  {searchable && (
246
- <div style={{ padding: 4 }}>
262
+ <div style={{ padding: 3 }}>
247
263
  <input
248
264
  ref={inputRef}
249
265
  type="text"
250
- placeholder="Search..."
266
+ placeholder="Поиск..."
251
267
  value={query}
252
268
  onChange={e => setQuery(e.target.value)}
253
269
  onMouseDown={e => e.stopPropagation()}
254
270
  style={{
255
271
  width: '100%',
256
- padding: '7px 10px',
257
- height: 32,
272
+ boxSizing: 'border-box',
273
+ padding: size === 'xs' ? '2px 5px' : size === 'sm' ? '3px 6px' : '7px 10px',
274
+ height: size === 'xs' ? 22 : size === 'sm' ? 26 : 32,
258
275
  background: 'var(--sf2,var(--grey-1))',
259
276
  border: '1.5px solid var(--bd2,var(--border))',
260
- borderRadius: 'var(--rmd,var(--rads))',
277
+ borderRadius: size === 'xs' ? '3px' : size === 'sm' ? '4px' : 'var(--rmd,var(--rads))',
261
278
  fontFamily: 'var(--f,var(--sans))',
262
- fontSize: '12.5px',
279
+ fontSize: size === 'xs' ? '10.5px' : size === 'sm' ? '11.5px' : '12.5px',
263
280
  lineHeight: 1,
264
281
  color: 'var(--t,var(--primary-text))',
265
282
  outline: 'none',
@@ -270,8 +287,8 @@ export default function Select({
270
287
  )}
271
288
  <div style={{ maxHeight: 200, overflowY: 'auto' }}>
272
289
  {filtered.length === 0 ? (
273
- <div style={{ padding: '14px 10px', textAlign: 'center', color: 'var(--t3,var(--grey-3))', fontSize: 11 }}>
274
- No options found
290
+ <div style={{ padding: '10px 8px', textAlign: 'center', color: 'var(--t3,var(--grey-3))', fontSize: 11 }}>
291
+ Ничего не найдено
275
292
  </div>
276
293
  ) : (
277
294
  filtered.map(opt => (
@@ -281,13 +298,14 @@ export default function Select({
281
298
  style={{
282
299
  display: 'flex',
283
300
  alignItems: 'center',
284
- gap: 8,
285
- padding: '8px 10px',
286
- borderRadius: 'var(--rmd,var(--rads))',
301
+ gap: 6,
302
+ minHeight: size === 'xs' ? 18 : size === 'sm' ? 22 : 28,
303
+ padding: size === 'xs' ? '3px 5px' : size === 'sm' ? '4.5px 7px' : '8px 10px',
304
+ borderRadius: size === 'xs' ? '3px' : size === 'sm' ? '4px' : 'var(--rmd,var(--rads))',
287
305
  cursor: opt.disabled ? 'not-allowed' : 'pointer',
288
306
  color: isSelected(opt.value) ? 'var(--accent)' : 'var(--t,var(--primary-text))',
289
307
  background: isSelected(opt.value) ? 'var(--pd,var(--accent-bg))' : undefined,
290
- fontSize: '12.5px',
308
+ fontSize: size === 'xs' ? '10.5px' : size === 'sm' ? '11.5px' : '12.5px',
291
309
  fontWeight: 400,
292
310
  transition: 'background .1s',
293
311
  userSelect: 'none',
@@ -297,8 +315,8 @@ export default function Select({
297
315
  >
298
316
  {multi && (
299
317
  <span style={{
300
- width: 15,
301
- height: 15,
318
+ width: 13,
319
+ height: 13,
302
320
  border: `1.5px solid ${isSelected(opt.value) ? 'var(--accent)' : 'var(--bd2,var(--border))'}`,
303
321
  borderRadius: 3,
304
322
  flexShrink: 0,
@@ -311,8 +329,8 @@ export default function Select({
311
329
  position: 'absolute',
312
330
  top: 1,
313
331
  left: 3.5,
314
- width: 4.5,
315
- height: 7,
332
+ width: 4,
333
+ height: 6,
316
334
  border: 'solid var(--def-btn-text,#fff)',
317
335
  borderWidth: '0 1.5px 1.5px 0',
318
336
  transform: 'rotate(45deg)',
@@ -320,11 +338,11 @@ export default function Select({
320
338
  )}
321
339
  </span>
322
340
  )}
323
- <span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>
324
- {opt.displayLabel || opt.label}
341
+ <span style={{ flex: 1, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis', minHeight: '1em' }}>
342
+ {opt.displayLabel || opt.label || '\u00A0'}
325
343
  </span>
326
344
  {!multi && isSelected(opt.value) && (
327
- <Check size={13} style={{ flexShrink: 0, color: 'var(--accent)' }} />
345
+ <Check size={12} style={{ flexShrink: 0, color: 'var(--accent)' }} />
328
346
  )}
329
347
  </div>
330
348
  ))
@@ -1,4 +1,4 @@
1
- import { useRef, useEffect } from 'react';
1
+ import { useState, useRef, useEffect } from 'react';
2
2
 
3
3
  const STYLES = `
4
4
  @keyframes cx-tabSlide {
@@ -19,10 +19,64 @@ function ensureStyles() {
19
19
 
20
20
  if (typeof document !== 'undefined') ensureStyles();
21
21
 
22
+ function ChevronIcon({ isOpen }) {
23
+ return (
24
+ <svg
25
+ width="12"
26
+ height="12"
27
+ viewBox="0 0 24 24"
28
+ fill="none"
29
+ stroke="currentColor"
30
+ strokeWidth="2.2"
31
+ strokeLinecap="round"
32
+ strokeLinejoin="round"
33
+ style={{
34
+ transition: 'transform 0.18s cubic-bezier(0.4, 0, 0.2, 1)',
35
+ transform: isOpen ? 'rotate(180deg)' : 'rotate(0deg)',
36
+ opacity: 0.75,
37
+ marginLeft: 2,
38
+ flexShrink: 0,
39
+ }}
40
+ >
41
+ <polyline points="6 9 12 15 18 9" />
42
+ </svg>
43
+ );
44
+ }
45
+
46
+ function CheckIcon() {
47
+ return (
48
+ <svg
49
+ width="13"
50
+ height="13"
51
+ viewBox="0 0 24 24"
52
+ fill="none"
53
+ stroke="var(--accent, #3b82f6)"
54
+ strokeWidth="2.5"
55
+ strokeLinecap="round"
56
+ strokeLinejoin="round"
57
+ style={{ flexShrink: 0 }}
58
+ >
59
+ <polyline points="20 6 9 17 4 12" />
60
+ </svg>
61
+ );
62
+ }
63
+
22
64
  export default function TabBar({ tabs, active, onChange, capitalize = false, padding, variant = 'underline' }) {
23
65
  const barRef = useRef(null);
24
66
  const indRef = useRef(null);
25
67
  const initDone = useRef(false);
68
+ const [openDropdownId, setOpenDropdownId] = useState(null);
69
+
70
+ // Click outside to close dropdowns
71
+ useEffect(() => {
72
+ const handleClickOutside = (e) => {
73
+ if (barRef.current && !barRef.current.contains(e.target)) {
74
+ setOpenDropdownId(null);
75
+ }
76
+ };
77
+ document.addEventListener('mousedown', handleClickOutside);
78
+ return () => document.removeEventListener('mousedown', handleClickOutside);
79
+ }, []);
26
80
 
27
81
  // Underline indicator positioning
28
82
  useEffect(() => {
@@ -31,7 +85,7 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
31
85
  const ind = indRef.current;
32
86
  if (!bar || !ind) return;
33
87
 
34
- const activeBtn = bar.querySelector(`[data-tabid="${active}"]`);
88
+ const activeBtn = bar.querySelector(`[data-tabid="${active}"]`) || bar.querySelector(`[data-active="true"]`);
35
89
  if (!activeBtn) return;
36
90
 
37
91
  const barRect = bar.getBoundingClientRect();
@@ -53,49 +107,143 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
53
107
 
54
108
  if (variant === 'pill') {
55
109
  return (
56
- <div style={{
57
- display: 'inline-flex', gap: 3,
58
- background: 'var(--grey-1, #f5f5f8)',
59
- borderRadius: 'var(--radxs, 6px)',
60
- padding: '3px',
61
- border: '1px solid var(--border-light, #ebebf0)',
62
- }}>
110
+ <div
111
+ ref={barRef}
112
+ style={{
113
+ display: 'inline-flex',
114
+ gap: 4,
115
+ background: 'var(--grey-1, #f5f5f8)',
116
+ borderRadius: 'var(--radxs, 6px)',
117
+ padding: '3px',
118
+ border: '1px solid var(--border-light, #ebebf0)',
119
+ position: 'relative',
120
+ alignItems: 'center',
121
+ flexWrap: 'wrap',
122
+ }}
123
+ >
63
124
  {tabs.map(t => {
64
- const isActive = active === t.id;
125
+ const hasItems = Array.isArray(t.items) && t.items.length > 0;
126
+ const activeSubItem = hasItems ? t.items.find(item => item.id === active) : null;
127
+ const isActive = active === t.id || !!activeSubItem;
128
+ const isOpen = openDropdownId === t.id;
129
+
130
+ const handleTabClick = () => {
131
+ if (hasItems) {
132
+ setOpenDropdownId(isOpen ? null : t.id);
133
+ } else {
134
+ setOpenDropdownId(null);
135
+ onChange(t.id);
136
+ }
137
+ };
138
+
65
139
  return (
66
- <button
67
- key={t.id}
68
- data-tabid={t.id}
69
- onClick={() => onChange(t.id)}
70
- style={{
71
- display: 'flex', alignItems: 'center', gap: 5,
72
- height: 24,
73
- padding: padding || '0 12px',
74
- border: 'none',
75
- borderRadius: 'var(--radxs, 6px)',
76
- background: isActive ? 'var(--surface,#fff)' : 'transparent',
77
- boxShadow: isActive ? 'var(--shadow, 0 1px 2px rgba(0,0,0,.06))' : 'none',
78
- fontSize: '.75rem', fontWeight: isActive ? 600 : 500,
79
- color: isActive ? 'var(--primary-text, #0f111c)' : 'var(--muted, #9b9daa)',
80
- cursor: 'pointer',
81
- transition: 'all .18s ease',
82
- fontFamily: 'inherit',
83
- whiteSpace: 'nowrap',
84
- textTransform: capitalize ? 'capitalize' : 'none',
85
- }}
86
- >
87
- {t.icon}{t.label}
88
- {t.count != null && t.count > 0 && (
89
- <span style={{
90
- background: isActive ? 'var(--accent-bg)' : 'var(--grey-2, #d7d8e0)',
91
- color: isActive ? 'var(--accent)' : 'var(--muted)',
92
- borderRadius: 10,
93
- padding: '1px 6px',
94
- fontSize: '.68rem',
95
- fontWeight: 600,
96
- }}>{t.count}</span>
140
+ <div key={t.id} style={{ position: 'relative' }}>
141
+ <button
142
+ type="button"
143
+ data-tabid={t.id}
144
+ data-active={isActive}
145
+ onClick={handleTabClick}
146
+ style={{
147
+ display: 'flex', alignItems: 'center', gap: 5,
148
+ height: 26,
149
+ padding: padding || '0 10px',
150
+ border: 'none',
151
+ borderRadius: 'var(--radxs, 6px)',
152
+ background: isActive ? 'var(--surface,#fff)' : isOpen ? 'var(--grey-2, #e5e7eb)' : 'transparent',
153
+ boxShadow: isActive ? 'var(--shadow, 0 1px 3px rgba(0,0,0,.08))' : 'none',
154
+ fontSize: '.75rem', fontWeight: isActive ? 600 : 500,
155
+ color: isActive ? 'var(--primary-text, #0f111c)' : 'var(--muted, #9b9daa)',
156
+ cursor: 'pointer',
157
+ transition: 'all .18s ease',
158
+ fontFamily: 'inherit',
159
+ whiteSpace: 'nowrap',
160
+ textTransform: capitalize ? 'capitalize' : 'none',
161
+ }}
162
+ >
163
+ {t.icon}{t.label}
164
+ {activeSubItem && (
165
+ <span style={{
166
+ fontSize: '.68rem',
167
+ fontWeight: 600,
168
+ color: 'var(--accent, #3b82f6)',
169
+ background: 'var(--accent-bg, rgba(59,130,246,0.12))',
170
+ padding: '1px 6px',
171
+ borderRadius: 4,
172
+ marginLeft: 2,
173
+ }}>
174
+ {activeSubItem.label}
175
+ </span>
176
+ )}
177
+ {hasItems && <ChevronIcon isOpen={isOpen} />}
178
+ {t.count != null && t.count > 0 && (
179
+ <span style={{
180
+ background: isActive ? 'var(--accent-bg)' : 'var(--grey-2, #d7d8e0)',
181
+ color: isActive ? 'var(--accent)' : 'var(--muted)',
182
+ borderRadius: 10,
183
+ padding: '1px 6px',
184
+ fontSize: '.68rem',
185
+ fontWeight: 600,
186
+ }}>{t.count}</span>
187
+ )}
188
+ </button>
189
+
190
+ {/* Dropdown Menu for Pill Tab */}
191
+ {isOpen && hasItems && (
192
+ <div style={{
193
+ position: 'absolute',
194
+ top: 'calc(100% + 4px)',
195
+ left: 0,
196
+ zIndex: 9999,
197
+ minWidth: 185,
198
+ background: 'var(--surface, #fff)',
199
+ border: '1px solid var(--border-light, #ebebf0)',
200
+ borderRadius: 'var(--radxs, 6px)',
201
+ boxShadow: '0 8px 24px rgba(0,0,0,0.12)',
202
+ padding: '4px',
203
+ display: 'flex',
204
+ flexDirection: 'column',
205
+ gap: 2,
206
+ }}>
207
+ {t.items.map(subItem => {
208
+ const isSubSelected = active === subItem.id;
209
+ return (
210
+ <button
211
+ key={subItem.id}
212
+ type="button"
213
+ onClick={() => {
214
+ onChange(subItem.id);
215
+ setOpenDropdownId(null);
216
+ }}
217
+ style={{
218
+ display: 'flex',
219
+ alignItems: 'center',
220
+ justifyContent: 'space-between',
221
+ padding: '6px 10px',
222
+ fontSize: '.75rem',
223
+ fontWeight: isSubSelected ? 600 : 400,
224
+ color: isSubSelected ? 'var(--accent, #3b82f6)' : 'var(--primary-text, #0f111c)',
225
+ background: isSubSelected ? 'var(--accent-bg, rgba(59,130,246,0.08))' : 'transparent',
226
+ borderRadius: 4,
227
+ border: 'none',
228
+ cursor: 'pointer',
229
+ textAlign: 'left',
230
+ transition: 'background .15s',
231
+ }}
232
+ onMouseEnter={(e) => {
233
+ if (!isSubSelected) e.currentTarget.style.background = 'var(--grey-1, #f5f5f8)';
234
+ }}
235
+ onMouseLeave={(e) => {
236
+ if (!isSubSelected) e.currentTarget.style.background = 'transparent';
237
+ }}
238
+ >
239
+ <span>{subItem.label}</span>
240
+ {isSubSelected && <CheckIcon />}
241
+ </button>
242
+ );
243
+ })}
244
+ </div>
97
245
  )}
98
- </button>
246
+ </div>
99
247
  );
100
248
  })}
101
249
  </div>
@@ -106,7 +254,7 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
106
254
  const defaultPadding = padding || '10px 16px';
107
255
 
108
256
  return (
109
- <div ref={barRef} style={{ display:'flex', position:'relative' }}>
257
+ <div ref={barRef} style={{ display:'flex', position:'relative', alignItems: 'center' }}>
110
258
  <div ref={indRef} style={{
111
259
  position:'absolute', bottom:'-1px', left:0,
112
260
  height:2, width:0,
@@ -117,39 +265,125 @@ export default function TabBar({ tabs, active, onChange, capitalize = false, pad
117
265
  }} />
118
266
 
119
267
  {tabs.map(t => {
120
- const isActive = active === t.id;
268
+ const hasItems = Array.isArray(t.items) && t.items.length > 0;
269
+ const activeSubItem = hasItems ? t.items.find(item => item.id === active) : null;
270
+ const isActive = active === t.id || !!activeSubItem;
271
+ const isOpen = openDropdownId === t.id;
272
+
273
+ const handleTabClick = () => {
274
+ if (hasItems) {
275
+ setOpenDropdownId(isOpen ? null : t.id);
276
+ } else {
277
+ setOpenDropdownId(null);
278
+ onChange(t.id);
279
+ }
280
+ };
281
+
121
282
  return (
122
- <button
123
- key={t.id}
124
- data-tabid={t.id}
125
- onClick={() => onChange(t.id)}
126
- style={{
127
- display:'flex', alignItems:'center', gap:6,
128
- padding: defaultPadding,
129
- border:'none', background:'none',
130
- fontSize:'.82rem', fontWeight:500,
131
- color: isActive ? 'var(--accent)' : 'var(--muted)',
132
- cursor:'pointer',
133
- transition:'color .18s',
134
- marginBottom:'-1px',
135
- position:'relative', zIndex:1,
136
- fontFamily:'inherit',
137
- whiteSpace:'nowrap',
138
- textTransform: capitalize ? 'capitalize' : 'none',
139
- }}
140
- >
141
- {t.icon}{t.label}
142
- {t.count != null && t.count > 0 && (
143
- <span style={{
144
- background: isActive ? 'var(--accent-bg)' : 'var(--grey-1)',
145
- color: isActive ? 'var(--accent)' : 'var(--muted)',
146
- borderRadius: 10,
147
- padding: '1px 6px',
148
- fontSize: '.68rem',
149
- transition: 'background .18s, color .18s',
150
- }}>{t.count}</span>
283
+ <div key={t.id} style={{ position: 'relative' }}>
284
+ <button
285
+ type="button"
286
+ data-tabid={t.id}
287
+ data-active={isActive}
288
+ onClick={handleTabClick}
289
+ style={{
290
+ display:'flex', alignItems:'center', gap:6,
291
+ padding: defaultPadding,
292
+ border:'none', background:'none',
293
+ fontSize:'.82rem', fontWeight:500,
294
+ color: isActive ? 'var(--accent)' : 'var(--muted)',
295
+ cursor:'pointer',
296
+ transition:'color .18s',
297
+ marginBottom:'-1px',
298
+ position:'relative', zIndex:1,
299
+ fontFamily:'inherit',
300
+ whiteSpace:'nowrap',
301
+ textTransform: capitalize ? 'capitalize' : 'none',
302
+ }}
303
+ >
304
+ {t.icon}{t.label}
305
+ {activeSubItem && (
306
+ <span style={{
307
+ fontSize: '.68rem',
308
+ fontWeight: 600,
309
+ color: 'var(--accent, #3b82f6)',
310
+ background: 'var(--accent-bg, rgba(59,130,246,0.12))',
311
+ padding: '1px 6px',
312
+ borderRadius: 4,
313
+ }}>
314
+ {activeSubItem.label}
315
+ </span>
316
+ )}
317
+ {hasItems && <ChevronIcon isOpen={isOpen} />}
318
+ {t.count != null && t.count > 0 && (
319
+ <span style={{
320
+ background: isActive ? 'var(--accent-bg)' : 'var(--grey-1)',
321
+ color: isActive ? 'var(--accent)' : 'var(--muted)',
322
+ borderRadius: 10,
323
+ padding: '1px 6px',
324
+ fontSize: '.68rem',
325
+ transition: 'background .18s, color .18s',
326
+ }}>{t.count}</span>
327
+ )}
328
+ </button>
329
+
330
+ {/* Dropdown Menu for Underline Tab */}
331
+ {isOpen && hasItems && (
332
+ <div style={{
333
+ position: 'absolute',
334
+ top: 'calc(100% + 2px)',
335
+ left: 0,
336
+ zIndex: 9999,
337
+ minWidth: 190,
338
+ background: 'var(--surface, #fff)',
339
+ border: '1px solid var(--border-light, #ebebf0)',
340
+ borderRadius: 'var(--radxs, 6px)',
341
+ boxShadow: '0 6px 20px rgba(0,0,0,0.12)',
342
+ padding: '4px',
343
+ display: 'flex',
344
+ flexDirection: 'column',
345
+ gap: 2,
346
+ }}>
347
+ {t.items.map(subItem => {
348
+ const isSubSelected = active === subItem.id;
349
+ return (
350
+ <button
351
+ key={subItem.id}
352
+ type="button"
353
+ onClick={() => {
354
+ onChange(subItem.id);
355
+ setOpenDropdownId(null);
356
+ }}
357
+ style={{
358
+ display: 'flex',
359
+ alignItems: 'center',
360
+ justify: 'space-between',
361
+ padding: '6px 10px',
362
+ fontSize: '.78rem',
363
+ fontWeight: isSubSelected ? 600 : 400,
364
+ color: isSubSelected ? 'var(--accent, #3b82f6)' : 'var(--primary-text, #0f111c)',
365
+ background: isSubSelected ? 'var(--accent-bg, rgba(59,130,246,0.08))' : 'transparent',
366
+ borderRadius: 4,
367
+ border: 'none',
368
+ cursor: 'pointer',
369
+ textAlign: 'left',
370
+ transition: 'background .15s',
371
+ }}
372
+ onMouseEnter={(e) => {
373
+ if (!isSubSelected) e.currentTarget.style.background = 'var(--grey-1, #f5f5f8)';
374
+ }}
375
+ onMouseLeave={(e) => {
376
+ if (!isSubSelected) e.currentTarget.style.background = 'transparent';
377
+ }}
378
+ >
379
+ <span>{subItem.label}</span>
380
+ {isSubSelected && <CheckIcon />}
381
+ </button>
382
+ );
383
+ })}
384
+ </div>
151
385
  )}
152
- </button>
386
+ </div>
153
387
  );
154
388
  })}
155
389
  </div>