@reactzero/combo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/LICENSE +21 -0
- package/README.md +188 -0
- package/dist/Combo-Cx3kSkop.cjs +2 -0
- package/dist/Combo-Cx3kSkop.cjs.map +1 -0
- package/dist/Combo-qs6_L512.js +439 -0
- package/dist/Combo-qs6_L512.js.map +1 -0
- package/dist/components/Combo.d.ts +71 -0
- package/dist/components/Combo.d.ts.map +1 -0
- package/dist/components/LiveRegion.d.ts +7 -0
- package/dist/components/LiveRegion.d.ts.map +1 -0
- package/dist/components/Portal.d.ts +8 -0
- package/dist/components/Portal.d.ts.map +1 -0
- package/dist/components/slots/CheckboxItem.d.ts +38 -0
- package/dist/components/slots/CheckboxItem.d.ts.map +1 -0
- package/dist/components/slots/CustomItem.d.ts +35 -0
- package/dist/components/slots/CustomItem.d.ts.map +1 -0
- package/dist/components/slots/FooterActions.d.ts +42 -0
- package/dist/components/slots/FooterActions.d.ts.map +1 -0
- package/dist/components/slots/GroupSeparator.d.ts +30 -0
- package/dist/components/slots/GroupSeparator.d.ts.map +1 -0
- package/dist/components/slots/index.d.ts +9 -0
- package/dist/components/slots/index.d.ts.map +1 -0
- package/dist/components/tabs/TabbedCombo.d.ts +45 -0
- package/dist/components/tabs/TabbedCombo.d.ts.map +1 -0
- package/dist/components/tabs/index.d.ts +3 -0
- package/dist/components/tabs/index.d.ts.map +1 -0
- package/dist/core/announce.d.ts +10 -0
- package/dist/core/announce.d.ts.map +1 -0
- package/dist/core/ids.d.ts +13 -0
- package/dist/core/ids.d.ts.map +1 -0
- package/dist/core/keyboard.d.ts +13 -0
- package/dist/core/keyboard.d.ts.map +1 -0
- package/dist/core/scroll.d.ts +5 -0
- package/dist/core/scroll.d.ts.map +1 -0
- package/dist/core/stateMachine.d.ts +32 -0
- package/dist/core/stateMachine.d.ts.map +1 -0
- package/dist/core/utils.d.ts +26 -0
- package/dist/core/utils.d.ts.map +1 -0
- package/dist/defaults-iFGq2Q-7.cjs +2 -0
- package/dist/defaults-iFGq2Q-7.cjs.map +1 -0
- package/dist/defaults-rhC5DFTg.js +53 -0
- package/dist/defaults-rhC5DFTg.js.map +1 -0
- package/dist/entries/hook-bare.d.ts +4 -0
- package/dist/entries/hook-bare.d.ts.map +1 -0
- package/dist/entries/hook.d.ts +4 -0
- package/dist/entries/hook.d.ts.map +1 -0
- package/dist/entries/icons.d.ts +4 -0
- package/dist/entries/icons.d.ts.map +1 -0
- package/dist/entries/index.d.ts +9 -0
- package/dist/entries/index.d.ts.map +1 -0
- package/dist/entries/position.d.ts +3 -0
- package/dist/entries/position.d.ts.map +1 -0
- package/dist/entries/slots.d.ts +9 -0
- package/dist/entries/slots.d.ts.map +1 -0
- package/dist/entries/tabs.d.ts +4 -0
- package/dist/entries/tabs.d.ts.map +1 -0
- package/dist/hook-bare.cjs +2 -0
- package/dist/hook-bare.cjs.map +1 -0
- package/dist/hook-bare.js +9 -0
- package/dist/hook-bare.js.map +1 -0
- package/dist/hook.cjs +2 -0
- package/dist/hook.cjs.map +1 -0
- package/dist/hook.js +11 -0
- package/dist/hook.js.map +1 -0
- package/dist/hooks/useCombo.d.ts +3 -0
- package/dist/hooks/useCombo.d.ts.map +1 -0
- package/dist/hooks/usePosition.d.ts +16 -0
- package/dist/hooks/usePosition.d.ts.map +1 -0
- package/dist/icons/defaults.d.ts +16 -0
- package/dist/icons/defaults.d.ts.map +1 -0
- package/dist/icons/icons.d.ts +30 -0
- package/dist/icons/icons.d.ts.map +1 -0
- package/dist/icons-Ch1Q5AhF.js +40 -0
- package/dist/icons-Ch1Q5AhF.js.map +1 -0
- package/dist/icons-vzkEacAb.cjs +2 -0
- package/dist/icons-vzkEacAb.cjs.map +1 -0
- package/dist/icons.cjs +2 -0
- package/dist/icons.cjs.map +1 -0
- package/dist/icons.js +20 -0
- package/dist/icons.js.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/position.cjs +2 -0
- package/dist/position.cjs.map +1 -0
- package/dist/position.js +5 -0
- package/dist/position.js.map +1 -0
- package/dist/slots.cjs +2 -0
- package/dist/slots.cjs.map +1 -0
- package/dist/slots.js +92 -0
- package/dist/slots.js.map +1 -0
- package/dist/style.css +1 -0
- package/dist/styles/base.css +205 -0
- package/dist/styles/checkbox.css +36 -0
- package/dist/styles/chips.css +71 -0
- package/dist/styles/custom-item.css +64 -0
- package/dist/styles/footer.css +73 -0
- package/dist/styles/groups.css +23 -0
- package/dist/styles/meta.css +30 -0
- package/dist/styles/radio.css +36 -0
- package/dist/styles/select.css +35 -0
- package/dist/styles/states.css +22 -0
- package/dist/tabs.cjs +2 -0
- package/dist/tabs.cjs.map +1 -0
- package/dist/tabs.js +132 -0
- package/dist/tabs.js.map +1 -0
- package/dist/themes/dark.css +96 -0
- package/dist/themes/default.css +126 -0
- package/dist/themes/high-contrast.css +98 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/useCombo-D_vriwVz.cjs +2 -0
- package/dist/useCombo-D_vriwVz.cjs.map +1 -0
- package/dist/useCombo-gPeBdkRf.js +887 -0
- package/dist/useCombo-gPeBdkRf.js.map +1 -0
- package/dist/usePosition-6GfutqGX.cjs +2 -0
- package/dist/usePosition-6GfutqGX.cjs.map +1 -0
- package/dist/usePosition-DVw8IlwA.js +36 -0
- package/dist/usePosition-DVw8IlwA.js.map +1 -0
- package/package.json +219 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* Multi-select chip trigger */
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
|
|
5
|
+
.rzero-combo-trigger--multi {
|
|
6
|
+
flex-wrap: nowrap;
|
|
7
|
+
gap: 4px;
|
|
8
|
+
padding: var(--rzero-combo-input-padding-multi, 4px 8px);
|
|
9
|
+
align-items: flex-start;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
.rzero-combo-trigger-content {
|
|
13
|
+
display: flex;
|
|
14
|
+
flex-wrap: wrap;
|
|
15
|
+
gap: 4px;
|
|
16
|
+
align-items: center;
|
|
17
|
+
flex: 1 1 0%;
|
|
18
|
+
min-width: 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rzero-combo-trigger-content input {
|
|
22
|
+
flex: 1 1 60px;
|
|
23
|
+
min-width: 60px;
|
|
24
|
+
padding: 2px 4px;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.rzero-combo-trigger-actions {
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
gap: 2px;
|
|
31
|
+
flex-shrink: 0;
|
|
32
|
+
padding-top: 2px;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.rzero-combo-chip {
|
|
36
|
+
display: inline-flex;
|
|
37
|
+
align-items: center;
|
|
38
|
+
gap: 2px;
|
|
39
|
+
padding: var(--rzero-combo-chip-padding, 2px 6px);
|
|
40
|
+
background: var(--rzero-combo-chip-bg, #e5e7eb);
|
|
41
|
+
color: var(--rzero-combo-chip-color, #374151);
|
|
42
|
+
border-radius: var(--rzero-combo-chip-radius, 4px);
|
|
43
|
+
font-size: var(--rzero-combo-chip-font-size, 13px);
|
|
44
|
+
line-height: 1.4;
|
|
45
|
+
max-width: 150px;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.rzero-combo-chip-label {
|
|
49
|
+
overflow: hidden;
|
|
50
|
+
text-overflow: ellipsis;
|
|
51
|
+
white-space: nowrap;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.rzero-combo-chip-remove {
|
|
55
|
+
display: inline-flex;
|
|
56
|
+
align-items: center;
|
|
57
|
+
justify-content: center;
|
|
58
|
+
background: none;
|
|
59
|
+
border: none;
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
padding: 0;
|
|
62
|
+
margin-left: 2px;
|
|
63
|
+
color: var(--rzero-combo-chip-remove-color, #6b7280);
|
|
64
|
+
border-radius: 2px;
|
|
65
|
+
flex-shrink: 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.rzero-combo-chip-remove:hover {
|
|
69
|
+
color: var(--rzero-combo-chip-remove-hover-color, #111827);
|
|
70
|
+
background: var(--rzero-combo-chip-remove-hover-bg, rgba(0,0,0,0.08));
|
|
71
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* Custom item layout — multi-line with icon, title, description, meta */
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
|
|
5
|
+
.rzero-combo-custom-item {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: flex-start;
|
|
8
|
+
gap: 10px;
|
|
9
|
+
padding: 4px 0;
|
|
10
|
+
width: 100%;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.rzero-combo-custom-item-icon {
|
|
14
|
+
flex-shrink: 0;
|
|
15
|
+
width: var(--rzero-combo-custom-item-icon-size, 32px);
|
|
16
|
+
height: var(--rzero-combo-custom-item-icon-size, 32px);
|
|
17
|
+
display: flex;
|
|
18
|
+
align-items: center;
|
|
19
|
+
justify-content: center;
|
|
20
|
+
border-radius: var(--rzero-combo-custom-item-icon-radius, 6px);
|
|
21
|
+
overflow: hidden;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.rzero-combo-custom-item-content {
|
|
25
|
+
flex: 1;
|
|
26
|
+
min-width: 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.rzero-combo-custom-item-title {
|
|
30
|
+
font-weight: 500;
|
|
31
|
+
color: inherit;
|
|
32
|
+
line-height: 1.3;
|
|
33
|
+
overflow: hidden;
|
|
34
|
+
text-overflow: ellipsis;
|
|
35
|
+
white-space: nowrap;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.rzero-combo-custom-item-description {
|
|
39
|
+
font-size: var(--rzero-combo-item-description-font-size, 12px);
|
|
40
|
+
color: var(--rzero-combo-item-description-color, #6b7280);
|
|
41
|
+
line-height: 1.3;
|
|
42
|
+
margin-top: 1px;
|
|
43
|
+
overflow: hidden;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.rzero-combo-custom-item-meta {
|
|
49
|
+
flex-shrink: 0;
|
|
50
|
+
font-size: var(--rzero-combo-item-meta-font-size, 12px);
|
|
51
|
+
color: var(--rzero-combo-item-meta-color, #9ca3af);
|
|
52
|
+
align-self: center;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
.rzero-combo-custom-item-badge {
|
|
56
|
+
display: inline-flex;
|
|
57
|
+
align-items: center;
|
|
58
|
+
padding: 1px 6px;
|
|
59
|
+
border-radius: var(--rzero-combo-badge-radius, 10px);
|
|
60
|
+
background: var(--rzero-combo-badge-bg, #e5e7eb);
|
|
61
|
+
color: var(--rzero-combo-badge-color, #374151);
|
|
62
|
+
font-size: var(--rzero-combo-badge-font-size, 11px);
|
|
63
|
+
font-weight: 500;
|
|
64
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/* Footer */
|
|
2
|
+
.rzero-combo-footer {
|
|
3
|
+
border-top: var(--rzero-combo-footer-border-top, 1px solid #e5e7eb);
|
|
4
|
+
padding: var(--rzero-combo-footer-padding, 8px 12px);
|
|
5
|
+
font-size: var(--rzero-combo-input-font-size, 14px);
|
|
6
|
+
color: var(--rzero-combo-footer-color, #6b7280);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Footer actions layout */
|
|
10
|
+
.rzero-combo-footer-actions {
|
|
11
|
+
display: flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: space-between;
|
|
14
|
+
gap: 8px;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
.rzero-combo-footer-actions-buttons {
|
|
18
|
+
display: flex;
|
|
19
|
+
gap: 6px;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.rzero-combo-footer-actions button {
|
|
23
|
+
padding: 4px 12px;
|
|
24
|
+
border: var(--rzero-combo-footer-action-border, 1px solid #d1d5db);
|
|
25
|
+
border-radius: var(--rzero-combo-footer-action-border-radius, 4px);
|
|
26
|
+
background: var(--rzero-combo-footer-action-bg, transparent);
|
|
27
|
+
color: var(--rzero-combo-footer-action-color, #374151);
|
|
28
|
+
font-size: var(--rzero-combo-footer-action-font-size, 13px);
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
transition: background 150ms ease;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.rzero-combo-footer-actions button:hover {
|
|
34
|
+
background: var(--rzero-combo-footer-action-hover-bg, #f3f4f6);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.rzero-combo-footer-actions button[data-variant="primary"] {
|
|
38
|
+
background: var(--rzero-combo-footer-action-primary-bg, #3b82f6);
|
|
39
|
+
color: var(--rzero-combo-footer-action-primary-color, #ffffff);
|
|
40
|
+
border-color: var(--rzero-combo-footer-action-primary-bg, #3b82f6);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
.rzero-combo-footer-actions button[data-variant="primary"]:hover {
|
|
44
|
+
background: var(--rzero-combo-footer-action-primary-hover-bg, #2563eb);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.rzero-combo-footer-actions button[data-variant="ghost"] {
|
|
48
|
+
border-color: transparent;
|
|
49
|
+
background: transparent;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.rzero-combo-footer-actions button[data-variant="ghost"]:hover {
|
|
53
|
+
background: var(--rzero-combo-footer-action-hover-bg, #f3f4f6);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.rzero-combo-footer-actions button:disabled {
|
|
57
|
+
opacity: 0.5;
|
|
58
|
+
cursor: not-allowed;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/* Footer note */
|
|
62
|
+
.rzero-combo-footer-note {
|
|
63
|
+
font-size: var(--rzero-combo-footer-note-font-size, 12px);
|
|
64
|
+
color: var(--rzero-combo-footer-note-color, #9ca3af);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Separator between groups */
|
|
68
|
+
.rzero-combo-separator {
|
|
69
|
+
height: 1px;
|
|
70
|
+
background: var(--rzero-combo-separator-color, #e5e7eb);
|
|
71
|
+
margin: 4px 0;
|
|
72
|
+
border: none;
|
|
73
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/* Sticky group headers */
|
|
2
|
+
.rzero-combo-group-header[data-sticky] {
|
|
3
|
+
position: sticky;
|
|
4
|
+
top: 0;
|
|
5
|
+
background: var(--rzero-combo-popover-bg, #ffffff);
|
|
6
|
+
z-index: 1;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/* Group header with count badge */
|
|
10
|
+
.rzero-combo-group-header-count {
|
|
11
|
+
display: inline-flex;
|
|
12
|
+
align-items: center;
|
|
13
|
+
justify-content: center;
|
|
14
|
+
min-width: 18px;
|
|
15
|
+
height: 18px;
|
|
16
|
+
padding: 0 5px;
|
|
17
|
+
border-radius: 9px;
|
|
18
|
+
background: var(--rzero-combo-group-header-count-bg, #e5e7eb);
|
|
19
|
+
color: var(--rzero-combo-group-header-count-color, #6b7280);
|
|
20
|
+
font-size: 10px;
|
|
21
|
+
font-weight: 600;
|
|
22
|
+
margin-left: 6px;
|
|
23
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* Meta row: hint text, error text, selection count */
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
|
|
5
|
+
.rzero-combo-meta {
|
|
6
|
+
display: flex;
|
|
7
|
+
align-items: baseline;
|
|
8
|
+
justify-content: space-between;
|
|
9
|
+
gap: 8px;
|
|
10
|
+
margin-top: var(--rzero-combo-meta-margin-top, 4px);
|
|
11
|
+
font-size: var(--rzero-combo-meta-font-size, 12px);
|
|
12
|
+
line-height: 1.4;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.rzero-combo-hint {
|
|
16
|
+
color: var(--rzero-combo-hint-color, #6b7280);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.rzero-combo-error-text {
|
|
20
|
+
color: var(--rzero-combo-error-text-color, #dc2626);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.rzero-combo-selection-count {
|
|
24
|
+
color: var(--rzero-combo-selection-count-color, #9ca3af);
|
|
25
|
+
white-space: nowrap;
|
|
26
|
+
margin-left: auto;
|
|
27
|
+
}
|
|
28
|
+
.rzero-combo-selection-count[data-warning] {
|
|
29
|
+
color: var(--rzero-combo-selection-count-warning-color, #f59e0b);
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/* ------------------------------------------------------------------ */
|
|
2
|
+
/* Radio variant — pure CSS via ::before pseudo-element */
|
|
3
|
+
/* ------------------------------------------------------------------ */
|
|
4
|
+
|
|
5
|
+
.rzero-combo-item[data-variant="radio"] {
|
|
6
|
+
gap: 8px;
|
|
7
|
+
justify-content: flex-start;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.rzero-combo-item[data-variant="radio"]::before {
|
|
11
|
+
content: '';
|
|
12
|
+
flex-shrink: 0;
|
|
13
|
+
width: var(--rzero-combo-radio-size, 16px);
|
|
14
|
+
height: var(--rzero-combo-radio-size, 16px);
|
|
15
|
+
border: 2px solid var(--rzero-combo-radio-border, #d1d5db);
|
|
16
|
+
border-radius: 50%;
|
|
17
|
+
background: var(--rzero-combo-radio-bg, #ffffff);
|
|
18
|
+
transition: background 150ms ease, border-color 150ms ease;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rzero-combo-item[data-variant="radio"][data-selected]::before {
|
|
22
|
+
border-color: var(--rzero-combo-radio-checked-border, #3b82f6);
|
|
23
|
+
background: radial-gradient(
|
|
24
|
+
circle,
|
|
25
|
+
var(--rzero-combo-radio-checked-dot, #3b82f6) 35%,
|
|
26
|
+
transparent 36%
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.rzero-combo-item[data-variant="radio"][data-highlighted]::before {
|
|
31
|
+
border-color: var(--rzero-combo-radio-hover-border, #9ca3af);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.rzero-combo-item[data-variant="radio"][data-selected][data-highlighted]::before {
|
|
35
|
+
border-color: var(--rzero-combo-radio-checked-border, #3b82f6);
|
|
36
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/* Select variant trigger */
|
|
2
|
+
.rzero-combo-select-trigger {
|
|
3
|
+
display: flex;
|
|
4
|
+
align-items: center;
|
|
5
|
+
justify-content: space-between;
|
|
6
|
+
gap: 8px;
|
|
7
|
+
width: 100%;
|
|
8
|
+
border: var(--rzero-combo-input-border, 1px solid #d1d5db);
|
|
9
|
+
border-radius: var(--rzero-combo-input-border-radius, 6px);
|
|
10
|
+
background: var(--rzero-combo-input-bg, #ffffff);
|
|
11
|
+
padding: var(--rzero-combo-input-padding, 8px 12px);
|
|
12
|
+
min-height: var(--rzero-combo-input-height, 40px);
|
|
13
|
+
font-size: var(--rzero-combo-input-font-size, 14px);
|
|
14
|
+
color: var(--rzero-combo-input-color, #111827);
|
|
15
|
+
cursor: pointer;
|
|
16
|
+
transition: box-shadow 150ms ease, border-color 150ms ease;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.rzero-combo-select-trigger:focus-visible {
|
|
20
|
+
box-shadow: var(--rzero-combo-input-focus-ring, 0 0 0 2px #3b82f6);
|
|
21
|
+
outline: none;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.rzero-combo-select-trigger:disabled {
|
|
25
|
+
opacity: 0.5;
|
|
26
|
+
cursor: not-allowed;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.rzero-combo-select-value {
|
|
30
|
+
flex: 1;
|
|
31
|
+
text-align: left;
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
text-overflow: ellipsis;
|
|
34
|
+
white-space: nowrap;
|
|
35
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/* Empty / loading states */
|
|
2
|
+
.rzero-combo-empty,
|
|
3
|
+
.rzero-combo-loading {
|
|
4
|
+
display: flex;
|
|
5
|
+
align-items: center;
|
|
6
|
+
justify-content: center;
|
|
7
|
+
min-height: var(--rzero-combo-item-height, 36px);
|
|
8
|
+
padding: var(--rzero-combo-item-padding, 0 12px);
|
|
9
|
+
color: var(--rzero-combo-icon-muted-color, #9ca3af);
|
|
10
|
+
font-size: var(--rzero-combo-input-font-size, 14px);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/* Error state */
|
|
14
|
+
.rzero-combo-error {
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
min-height: var(--rzero-combo-item-height, 36px);
|
|
19
|
+
padding: var(--rzero-combo-item-padding, 0 12px);
|
|
20
|
+
color: var(--rzero-combo-error-color, #dc2626);
|
|
21
|
+
font-size: var(--rzero-combo-input-font-size, 14px);
|
|
22
|
+
}
|
package/dist/tabs.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const r=require("react/jsx-runtime"),d=require("react"),k=require("./Combo-Cx3kSkop.cjs");function w(v){const{tabs:a,defaultActiveTab:f,activeTab:l,onTabChange:m,renderTabLabel:g,renderFooter:h,...e}=v,c=d.useId(),T=d.useRef(null),[y,p]=d.useState(()=>f??a[0]?.id??""),o=l!==void 0?l:y,u=d.useCallback(t=>{l===void 0&&p(t),m?.(t)},[l,m]),I=d.useMemo(()=>a.find(n=>n.id===o)?.items??[],[a,o]),C=d.useCallback(t=>{const n=a.filter(x=>!x.disabled),s=n.findIndex(x=>x.id===o);if(s===-1)return;let i=-1;if(t.key==="ArrowRight")i=(s+1)%n.length;else if(t.key==="ArrowLeft")i=(s-1+n.length)%n.length;else if(t.key==="Home")i=0;else if(t.key==="End")i=n.length-1;else return;t.preventDefault();const b=n[i];u(b.id),T.current?.querySelector(`[data-tab-id="${b.id}"]`)?.focus()},[a,o,u]),j=d.useCallback(t=>{if(!t.ctrlKey&&!t.metaKey)return;const n=a.filter(b=>!b.disabled),s=n.findIndex(b=>b.id===o);if(s===-1)return;let i=-1;if(t.key==="ArrowRight")i=(s+1)%n.length;else if(t.key==="ArrowLeft")i=(s-1+n.length)%n.length;else return;t.preventDefault(),u(n[i].id)},[a,o,u]);return r.jsx(k.Combo,{...e,items:I,renderFooter:h,onInputKeyDown:j,renderListHeader:()=>r.jsx(A,{tabs:a,activeTabId:o,baseId:c,tabListRef:T,onTabChange:u,onKeyDown:C,renderTabLabel:g})})}function A({tabs:v,activeTabId:a,baseId:f,tabListRef:l,onTabChange:m,onKeyDown:g,renderTabLabel:h}){return r.jsx("div",{ref:l,className:"rzero-combo-tabs",role:"tablist","aria-label":"Item categories",onMouseDown:e=>e.preventDefault(),children:v.map(e=>{const c=e.id===a;return r.jsx("button",{type:"button",role:"tab",id:`${f}-tab-${e.id}`,"aria-selected":c,"aria-controls":`${f}-panel-${e.id}`,"aria-disabled":e.disabled||void 0,"data-tab-id":e.id,"data-active":c||void 0,"data-disabled":e.disabled||void 0,className:"rzero-combo-tab",tabIndex:c?0:-1,onClick:()=>{e.disabled||m(e.id)},onKeyDown:g,children:h?h(e,c):r.jsxs(r.Fragment,{children:[e.icon&&r.jsx("span",{className:"rzero-combo-tab-icon","aria-hidden":"true",children:e.icon}),r.jsx("span",{children:e.label}),e.badge!==void 0&&r.jsx("span",{className:"rzero-combo-tab-badge",children:e.badge})]})},e.id)})})}exports.TabbedCombo=w;
|
|
2
|
+
//# sourceMappingURL=tabs.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.cjs","sources":["../src/components/tabs/TabbedCombo.tsx"],"sourcesContent":["import { useState, useCallback, useRef, useMemo, useId } from 'react';\nimport { Combo } from '../Combo';\nimport type { ComboProps } from '../Combo';\nimport type { ReactNode, KeyboardEvent, RefObject } from 'react';\n\nexport interface TabConfig<T> {\n /** Unique tab identifier */\n id: string;\n /** Tab display label */\n label: string;\n /** Items belonging to this tab */\n items: T[];\n /** Optional icon for the tab */\n icon?: ReactNode;\n /** Optional badge (e.g. count) */\n badge?: ReactNode;\n /** Disable this tab */\n disabled?: boolean;\n}\n\nexport interface TabbedComboProps<T>\n extends Omit<ComboProps<T>, 'items' | 'groups'> {\n /** Tab configuration with per-tab items */\n tabs: TabConfig<T>[];\n /** Default active tab ID (uncontrolled) */\n defaultActiveTab?: string;\n /** Active tab ID (controlled) */\n activeTab?: string;\n /** Callback when active tab changes */\n onTabChange?: (tabId: string) => void;\n /** Custom tab label renderer */\n renderTabLabel?: (tab: TabConfig<T>, isActive: boolean) => ReactNode;\n}\n\n/**\n * Combo with a tab strip inside the popover for categorized content.\n * Each tab filters to its own set of items.\n *\n * @example\n * ```tsx\n * <TabbedCombo\n * tabs={[\n * { id: 'fruits', label: 'Fruits', items: fruits },\n * { id: 'vegetables', label: 'Vegetables', items: vegetables },\n * ]}\n * placeholder=\"Search food...\"\n * />\n * ```\n */\nexport function TabbedCombo<T>(props: TabbedComboProps<T>) {\n const {\n tabs,\n defaultActiveTab,\n activeTab: controlledActiveTab,\n onTabChange,\n renderTabLabel,\n renderFooter,\n ...comboProps\n } = props;\n\n const baseId = useId();\n const tabListRef = useRef<HTMLDivElement>(null);\n\n // Active tab state (controlled or uncontrolled)\n const [internalActiveTab, setInternalActiveTab] = useState(\n () => defaultActiveTab ?? tabs[0]?.id ?? '',\n );\n\n const activeTabId =\n controlledActiveTab !== undefined ? controlledActiveTab : internalActiveTab;\n\n const handleTabChange = useCallback(\n (tabId: string) => {\n if (controlledActiveTab === undefined) {\n setInternalActiveTab(tabId);\n }\n onTabChange?.(tabId);\n },\n [controlledActiveTab, onTabChange],\n );\n\n // Get items for the active tab\n const activeItems = useMemo(() => {\n const tab = tabs.find((t) => t.id === activeTabId);\n return tab?.items ?? [];\n }, [tabs, activeTabId]);\n\n // Tab keyboard navigation (Left/Right arrow)\n const handleTabKeyDown = useCallback(\n (e: KeyboardEvent) => {\n const enabledTabs = tabs.filter((t) => !t.disabled);\n const currentIdx = enabledTabs.findIndex((t) => t.id === activeTabId);\n if (currentIdx === -1) return;\n\n let nextIdx = -1;\n if (e.key === 'ArrowRight') {\n nextIdx = (currentIdx + 1) % enabledTabs.length;\n } else if (e.key === 'ArrowLeft') {\n nextIdx = (currentIdx - 1 + enabledTabs.length) % enabledTabs.length;\n } else if (e.key === 'Home') {\n nextIdx = 0;\n } else if (e.key === 'End') {\n nextIdx = enabledTabs.length - 1;\n } else {\n return;\n }\n\n e.preventDefault();\n const nextTab = enabledTabs[nextIdx];\n handleTabChange(nextTab.id);\n\n // Focus the activated tab button\n const tabEl = tabListRef.current?.querySelector(\n `[data-tab-id=\"${nextTab.id}\"]`,\n ) as HTMLButtonElement | null;\n tabEl?.focus();\n },\n [tabs, activeTabId, handleTabChange],\n );\n\n // Ctrl+Arrow from the input switches tabs without losing focus\n const handleInputKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (!e.ctrlKey && !e.metaKey) return;\n\n const enabledTabs = tabs.filter((t) => !t.disabled);\n const currentIdx = enabledTabs.findIndex((t) => t.id === activeTabId);\n if (currentIdx === -1) return;\n\n let nextIdx = -1;\n if (e.key === 'ArrowRight') {\n nextIdx = (currentIdx + 1) % enabledTabs.length;\n } else if (e.key === 'ArrowLeft') {\n nextIdx = (currentIdx - 1 + enabledTabs.length) % enabledTabs.length;\n } else {\n return;\n }\n\n e.preventDefault();\n handleTabChange(enabledTabs[nextIdx].id);\n },\n [tabs, activeTabId, handleTabChange],\n );\n\n return (\n <Combo<T>\n {...comboProps}\n items={activeItems}\n renderFooter={renderFooter}\n onInputKeyDown={handleInputKeyDown}\n renderListHeader={() => (\n <TabStrip\n tabs={tabs}\n activeTabId={activeTabId}\n baseId={baseId}\n tabListRef={tabListRef}\n onTabChange={handleTabChange}\n onKeyDown={handleTabKeyDown}\n renderTabLabel={renderTabLabel}\n />\n )}\n />\n );\n}\n\n// Internal TabStrip component\nfunction TabStrip<T>({\n tabs,\n activeTabId,\n baseId,\n tabListRef,\n onTabChange,\n onKeyDown,\n renderTabLabel,\n}: {\n tabs: TabConfig<T>[];\n activeTabId: string;\n baseId: string;\n tabListRef: RefObject<HTMLDivElement | null>;\n onTabChange: (tabId: string) => void;\n onKeyDown: (e: KeyboardEvent) => void;\n renderTabLabel?: (tab: TabConfig<T>, isActive: boolean) => ReactNode;\n}) {\n return (\n <div\n ref={tabListRef as RefObject<HTMLDivElement>}\n className=\"rzero-combo-tabs\"\n role=\"tablist\"\n aria-label=\"Item categories\"\n onMouseDown={(e) => e.preventDefault()}\n >\n {tabs.map((tab) => {\n const isActive = tab.id === activeTabId;\n return (\n <button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n id={`${baseId}-tab-${tab.id}`}\n aria-selected={isActive}\n aria-controls={`${baseId}-panel-${tab.id}`}\n aria-disabled={tab.disabled || undefined}\n data-tab-id={tab.id}\n data-active={isActive || undefined}\n data-disabled={tab.disabled || undefined}\n className=\"rzero-combo-tab\"\n tabIndex={isActive ? 0 : -1}\n onClick={() => {\n if (tab.disabled) return;\n onTabChange(tab.id);\n }}\n onKeyDown={onKeyDown}\n >\n {renderTabLabel ? (\n renderTabLabel(tab, isActive)\n ) : (\n <>\n {tab.icon && (\n <span className=\"rzero-combo-tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n </span>\n )}\n <span>{tab.label}</span>\n {tab.badge !== undefined && (\n <span className=\"rzero-combo-tab-badge\">{tab.badge}</span>\n )}\n </>\n )}\n </button>\n );\n })}\n </div>\n );\n}\n"],"names":["TabbedCombo","props","tabs","defaultActiveTab","controlledActiveTab","onTabChange","renderTabLabel","renderFooter","comboProps","baseId","useId","tabListRef","useRef","internalActiveTab","setInternalActiveTab","useState","activeTabId","handleTabChange","useCallback","tabId","activeItems","useMemo","t","handleTabKeyDown","e","enabledTabs","currentIdx","nextIdx","nextTab","handleInputKeyDown","jsx","Combo","TabStrip","onKeyDown","tab","isActive","jsxs","Fragment"],"mappings":"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"}
|
package/dist/tabs.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { jsx as d, jsxs as k, Fragment as C } from "react/jsx-runtime";
|
|
2
|
+
import { useId as D, useRef as K, useState as $, useCallback as p, useMemo as z } from "react";
|
|
3
|
+
import { C as L } from "./Combo-qs6_L512.js";
|
|
4
|
+
function F(h) {
|
|
5
|
+
const {
|
|
6
|
+
tabs: i,
|
|
7
|
+
defaultActiveTab: u,
|
|
8
|
+
activeTab: s,
|
|
9
|
+
onTabChange: f,
|
|
10
|
+
renderTabLabel: v,
|
|
11
|
+
renderFooter: m,
|
|
12
|
+
...e
|
|
13
|
+
} = h, l = D(), T = K(null), [y, x] = $(
|
|
14
|
+
() => u ?? i[0]?.id ?? ""
|
|
15
|
+
), a = s !== void 0 ? s : y, b = p(
|
|
16
|
+
(t) => {
|
|
17
|
+
s === void 0 && x(t), f?.(t);
|
|
18
|
+
},
|
|
19
|
+
[s, f]
|
|
20
|
+
), I = z(() => i.find((n) => n.id === a)?.items ?? [], [i, a]), w = p(
|
|
21
|
+
(t) => {
|
|
22
|
+
const n = i.filter((g) => !g.disabled), o = n.findIndex((g) => g.id === a);
|
|
23
|
+
if (o === -1) return;
|
|
24
|
+
let r = -1;
|
|
25
|
+
if (t.key === "ArrowRight")
|
|
26
|
+
r = (o + 1) % n.length;
|
|
27
|
+
else if (t.key === "ArrowLeft")
|
|
28
|
+
r = (o - 1 + n.length) % n.length;
|
|
29
|
+
else if (t.key === "Home")
|
|
30
|
+
r = 0;
|
|
31
|
+
else if (t.key === "End")
|
|
32
|
+
r = n.length - 1;
|
|
33
|
+
else
|
|
34
|
+
return;
|
|
35
|
+
t.preventDefault();
|
|
36
|
+
const c = n[r];
|
|
37
|
+
b(c.id), T.current?.querySelector(
|
|
38
|
+
`[data-tab-id="${c.id}"]`
|
|
39
|
+
)?.focus();
|
|
40
|
+
},
|
|
41
|
+
[i, a, b]
|
|
42
|
+
), A = p(
|
|
43
|
+
(t) => {
|
|
44
|
+
if (!t.ctrlKey && !t.metaKey) return;
|
|
45
|
+
const n = i.filter((c) => !c.disabled), o = n.findIndex((c) => c.id === a);
|
|
46
|
+
if (o === -1) return;
|
|
47
|
+
let r = -1;
|
|
48
|
+
if (t.key === "ArrowRight")
|
|
49
|
+
r = (o + 1) % n.length;
|
|
50
|
+
else if (t.key === "ArrowLeft")
|
|
51
|
+
r = (o - 1 + n.length) % n.length;
|
|
52
|
+
else
|
|
53
|
+
return;
|
|
54
|
+
t.preventDefault(), b(n[r].id);
|
|
55
|
+
},
|
|
56
|
+
[i, a, b]
|
|
57
|
+
);
|
|
58
|
+
return /* @__PURE__ */ d(
|
|
59
|
+
L,
|
|
60
|
+
{
|
|
61
|
+
...e,
|
|
62
|
+
items: I,
|
|
63
|
+
renderFooter: m,
|
|
64
|
+
onInputKeyDown: A,
|
|
65
|
+
renderListHeader: () => /* @__PURE__ */ d(
|
|
66
|
+
N,
|
|
67
|
+
{
|
|
68
|
+
tabs: i,
|
|
69
|
+
activeTabId: a,
|
|
70
|
+
baseId: l,
|
|
71
|
+
tabListRef: T,
|
|
72
|
+
onTabChange: b,
|
|
73
|
+
onKeyDown: w,
|
|
74
|
+
renderTabLabel: v
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
function N({
|
|
81
|
+
tabs: h,
|
|
82
|
+
activeTabId: i,
|
|
83
|
+
baseId: u,
|
|
84
|
+
tabListRef: s,
|
|
85
|
+
onTabChange: f,
|
|
86
|
+
onKeyDown: v,
|
|
87
|
+
renderTabLabel: m
|
|
88
|
+
}) {
|
|
89
|
+
return /* @__PURE__ */ d(
|
|
90
|
+
"div",
|
|
91
|
+
{
|
|
92
|
+
ref: s,
|
|
93
|
+
className: "rzero-combo-tabs",
|
|
94
|
+
role: "tablist",
|
|
95
|
+
"aria-label": "Item categories",
|
|
96
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
97
|
+
children: h.map((e) => {
|
|
98
|
+
const l = e.id === i;
|
|
99
|
+
return /* @__PURE__ */ d(
|
|
100
|
+
"button",
|
|
101
|
+
{
|
|
102
|
+
type: "button",
|
|
103
|
+
role: "tab",
|
|
104
|
+
id: `${u}-tab-${e.id}`,
|
|
105
|
+
"aria-selected": l,
|
|
106
|
+
"aria-controls": `${u}-panel-${e.id}`,
|
|
107
|
+
"aria-disabled": e.disabled || void 0,
|
|
108
|
+
"data-tab-id": e.id,
|
|
109
|
+
"data-active": l || void 0,
|
|
110
|
+
"data-disabled": e.disabled || void 0,
|
|
111
|
+
className: "rzero-combo-tab",
|
|
112
|
+
tabIndex: l ? 0 : -1,
|
|
113
|
+
onClick: () => {
|
|
114
|
+
e.disabled || f(e.id);
|
|
115
|
+
},
|
|
116
|
+
onKeyDown: v,
|
|
117
|
+
children: m ? m(e, l) : /* @__PURE__ */ k(C, { children: [
|
|
118
|
+
e.icon && /* @__PURE__ */ d("span", { className: "rzero-combo-tab-icon", "aria-hidden": "true", children: e.icon }),
|
|
119
|
+
/* @__PURE__ */ d("span", { children: e.label }),
|
|
120
|
+
e.badge !== void 0 && /* @__PURE__ */ d("span", { className: "rzero-combo-tab-badge", children: e.badge })
|
|
121
|
+
] })
|
|
122
|
+
},
|
|
123
|
+
e.id
|
|
124
|
+
);
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
F as TabbedCombo
|
|
131
|
+
};
|
|
132
|
+
//# sourceMappingURL=tabs.js.map
|
package/dist/tabs.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tabs.js","sources":["../src/components/tabs/TabbedCombo.tsx"],"sourcesContent":["import { useState, useCallback, useRef, useMemo, useId } from 'react';\nimport { Combo } from '../Combo';\nimport type { ComboProps } from '../Combo';\nimport type { ReactNode, KeyboardEvent, RefObject } from 'react';\n\nexport interface TabConfig<T> {\n /** Unique tab identifier */\n id: string;\n /** Tab display label */\n label: string;\n /** Items belonging to this tab */\n items: T[];\n /** Optional icon for the tab */\n icon?: ReactNode;\n /** Optional badge (e.g. count) */\n badge?: ReactNode;\n /** Disable this tab */\n disabled?: boolean;\n}\n\nexport interface TabbedComboProps<T>\n extends Omit<ComboProps<T>, 'items' | 'groups'> {\n /** Tab configuration with per-tab items */\n tabs: TabConfig<T>[];\n /** Default active tab ID (uncontrolled) */\n defaultActiveTab?: string;\n /** Active tab ID (controlled) */\n activeTab?: string;\n /** Callback when active tab changes */\n onTabChange?: (tabId: string) => void;\n /** Custom tab label renderer */\n renderTabLabel?: (tab: TabConfig<T>, isActive: boolean) => ReactNode;\n}\n\n/**\n * Combo with a tab strip inside the popover for categorized content.\n * Each tab filters to its own set of items.\n *\n * @example\n * ```tsx\n * <TabbedCombo\n * tabs={[\n * { id: 'fruits', label: 'Fruits', items: fruits },\n * { id: 'vegetables', label: 'Vegetables', items: vegetables },\n * ]}\n * placeholder=\"Search food...\"\n * />\n * ```\n */\nexport function TabbedCombo<T>(props: TabbedComboProps<T>) {\n const {\n tabs,\n defaultActiveTab,\n activeTab: controlledActiveTab,\n onTabChange,\n renderTabLabel,\n renderFooter,\n ...comboProps\n } = props;\n\n const baseId = useId();\n const tabListRef = useRef<HTMLDivElement>(null);\n\n // Active tab state (controlled or uncontrolled)\n const [internalActiveTab, setInternalActiveTab] = useState(\n () => defaultActiveTab ?? tabs[0]?.id ?? '',\n );\n\n const activeTabId =\n controlledActiveTab !== undefined ? controlledActiveTab : internalActiveTab;\n\n const handleTabChange = useCallback(\n (tabId: string) => {\n if (controlledActiveTab === undefined) {\n setInternalActiveTab(tabId);\n }\n onTabChange?.(tabId);\n },\n [controlledActiveTab, onTabChange],\n );\n\n // Get items for the active tab\n const activeItems = useMemo(() => {\n const tab = tabs.find((t) => t.id === activeTabId);\n return tab?.items ?? [];\n }, [tabs, activeTabId]);\n\n // Tab keyboard navigation (Left/Right arrow)\n const handleTabKeyDown = useCallback(\n (e: KeyboardEvent) => {\n const enabledTabs = tabs.filter((t) => !t.disabled);\n const currentIdx = enabledTabs.findIndex((t) => t.id === activeTabId);\n if (currentIdx === -1) return;\n\n let nextIdx = -1;\n if (e.key === 'ArrowRight') {\n nextIdx = (currentIdx + 1) % enabledTabs.length;\n } else if (e.key === 'ArrowLeft') {\n nextIdx = (currentIdx - 1 + enabledTabs.length) % enabledTabs.length;\n } else if (e.key === 'Home') {\n nextIdx = 0;\n } else if (e.key === 'End') {\n nextIdx = enabledTabs.length - 1;\n } else {\n return;\n }\n\n e.preventDefault();\n const nextTab = enabledTabs[nextIdx];\n handleTabChange(nextTab.id);\n\n // Focus the activated tab button\n const tabEl = tabListRef.current?.querySelector(\n `[data-tab-id=\"${nextTab.id}\"]`,\n ) as HTMLButtonElement | null;\n tabEl?.focus();\n },\n [tabs, activeTabId, handleTabChange],\n );\n\n // Ctrl+Arrow from the input switches tabs without losing focus\n const handleInputKeyDown = useCallback(\n (e: KeyboardEvent) => {\n if (!e.ctrlKey && !e.metaKey) return;\n\n const enabledTabs = tabs.filter((t) => !t.disabled);\n const currentIdx = enabledTabs.findIndex((t) => t.id === activeTabId);\n if (currentIdx === -1) return;\n\n let nextIdx = -1;\n if (e.key === 'ArrowRight') {\n nextIdx = (currentIdx + 1) % enabledTabs.length;\n } else if (e.key === 'ArrowLeft') {\n nextIdx = (currentIdx - 1 + enabledTabs.length) % enabledTabs.length;\n } else {\n return;\n }\n\n e.preventDefault();\n handleTabChange(enabledTabs[nextIdx].id);\n },\n [tabs, activeTabId, handleTabChange],\n );\n\n return (\n <Combo<T>\n {...comboProps}\n items={activeItems}\n renderFooter={renderFooter}\n onInputKeyDown={handleInputKeyDown}\n renderListHeader={() => (\n <TabStrip\n tabs={tabs}\n activeTabId={activeTabId}\n baseId={baseId}\n tabListRef={tabListRef}\n onTabChange={handleTabChange}\n onKeyDown={handleTabKeyDown}\n renderTabLabel={renderTabLabel}\n />\n )}\n />\n );\n}\n\n// Internal TabStrip component\nfunction TabStrip<T>({\n tabs,\n activeTabId,\n baseId,\n tabListRef,\n onTabChange,\n onKeyDown,\n renderTabLabel,\n}: {\n tabs: TabConfig<T>[];\n activeTabId: string;\n baseId: string;\n tabListRef: RefObject<HTMLDivElement | null>;\n onTabChange: (tabId: string) => void;\n onKeyDown: (e: KeyboardEvent) => void;\n renderTabLabel?: (tab: TabConfig<T>, isActive: boolean) => ReactNode;\n}) {\n return (\n <div\n ref={tabListRef as RefObject<HTMLDivElement>}\n className=\"rzero-combo-tabs\"\n role=\"tablist\"\n aria-label=\"Item categories\"\n onMouseDown={(e) => e.preventDefault()}\n >\n {tabs.map((tab) => {\n const isActive = tab.id === activeTabId;\n return (\n <button\n key={tab.id}\n type=\"button\"\n role=\"tab\"\n id={`${baseId}-tab-${tab.id}`}\n aria-selected={isActive}\n aria-controls={`${baseId}-panel-${tab.id}`}\n aria-disabled={tab.disabled || undefined}\n data-tab-id={tab.id}\n data-active={isActive || undefined}\n data-disabled={tab.disabled || undefined}\n className=\"rzero-combo-tab\"\n tabIndex={isActive ? 0 : -1}\n onClick={() => {\n if (tab.disabled) return;\n onTabChange(tab.id);\n }}\n onKeyDown={onKeyDown}\n >\n {renderTabLabel ? (\n renderTabLabel(tab, isActive)\n ) : (\n <>\n {tab.icon && (\n <span className=\"rzero-combo-tab-icon\" aria-hidden=\"true\">\n {tab.icon}\n </span>\n )}\n <span>{tab.label}</span>\n {tab.badge !== undefined && (\n <span className=\"rzero-combo-tab-badge\">{tab.badge}</span>\n )}\n </>\n )}\n </button>\n );\n })}\n </div>\n );\n}\n"],"names":["TabbedCombo","props","tabs","defaultActiveTab","controlledActiveTab","onTabChange","renderTabLabel","renderFooter","comboProps","baseId","useId","tabListRef","useRef","internalActiveTab","setInternalActiveTab","useState","activeTabId","handleTabChange","useCallback","tabId","activeItems","useMemo","t","handleTabKeyDown","e","enabledTabs","currentIdx","nextIdx","nextTab","handleInputKeyDown","jsx","Combo","TabStrip","onKeyDown","tab","isActive","jsxs","Fragment"],"mappings":"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"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
[data-rzero-theme="dark"] {
|
|
2
|
+
/* Input */
|
|
3
|
+
--rzero-combo-input-bg: #1f2937;
|
|
4
|
+
--rzero-combo-input-border: 1px solid #374151;
|
|
5
|
+
--rzero-combo-input-color: #f9fafb;
|
|
6
|
+
--rzero-combo-input-hover-border: #60a5fa;
|
|
7
|
+
--rzero-combo-input-focus-ring: 0 0 0 2px #60a5fa;
|
|
8
|
+
|
|
9
|
+
/* Label & hint */
|
|
10
|
+
--rzero-combo-label-color: #e2e8f0;
|
|
11
|
+
--rzero-combo-hint-color: #94a3b8;
|
|
12
|
+
|
|
13
|
+
/* Popover */
|
|
14
|
+
--rzero-combo-popover-bg: #1f2937;
|
|
15
|
+
--rzero-combo-popover-border: 1px solid #374151;
|
|
16
|
+
--rzero-combo-popover-shadow: 0 8px 24px rgba(0, 0, 0, 0.4);
|
|
17
|
+
|
|
18
|
+
/* Items */
|
|
19
|
+
--rzero-combo-item-color: #e5e7eb;
|
|
20
|
+
--rzero-combo-item-hover-bg: #374151;
|
|
21
|
+
--rzero-combo-item-selected-bg: #1e3a5f;
|
|
22
|
+
--rzero-combo-item-selected-color: #93c5fd;
|
|
23
|
+
--rzero-combo-item-highlighted-bg: #1e3a5f;
|
|
24
|
+
--rzero-combo-item-description-color: #94a3b8;
|
|
25
|
+
--rzero-combo-item-meta-color: #64748b;
|
|
26
|
+
|
|
27
|
+
/* Group headers */
|
|
28
|
+
--rzero-combo-group-header-color: #94a3b8;
|
|
29
|
+
--rzero-combo-group-header-count-bg: #374151;
|
|
30
|
+
--rzero-combo-group-header-count-color: #94a3b8;
|
|
31
|
+
|
|
32
|
+
/* Icons */
|
|
33
|
+
--rzero-combo-icon-muted-color: #64748b;
|
|
34
|
+
|
|
35
|
+
/* Chips */
|
|
36
|
+
--rzero-combo-chip-bg: #312e81;
|
|
37
|
+
--rzero-combo-chip-color: #c7d2fe;
|
|
38
|
+
--rzero-combo-chip-remove-color: #94a3b8;
|
|
39
|
+
--rzero-combo-chip-remove-hover-color: #f1f5f9;
|
|
40
|
+
--rzero-combo-chip-remove-hover-bg: rgba(255, 255, 255, 0.1);
|
|
41
|
+
|
|
42
|
+
/* Error states */
|
|
43
|
+
--rzero-combo-error-border: #f87171;
|
|
44
|
+
--rzero-combo-error-color: #f87171;
|
|
45
|
+
--rzero-combo-error-text-color: #fca5a5;
|
|
46
|
+
--rzero-combo-error-focus-ring: 0 0 0 2px #f87171;
|
|
47
|
+
|
|
48
|
+
/* Selection count */
|
|
49
|
+
--rzero-combo-selection-count-color: #94a3b8;
|
|
50
|
+
--rzero-combo-selection-count-warning-color: #fbbf24;
|
|
51
|
+
|
|
52
|
+
/* Checkbox variant */
|
|
53
|
+
--rzero-combo-checkbox-border: #4b5563;
|
|
54
|
+
--rzero-combo-checkbox-bg: #374151;
|
|
55
|
+
--rzero-combo-checkbox-checked-bg: #60a5fa;
|
|
56
|
+
--rzero-combo-checkbox-checked-border: #60a5fa;
|
|
57
|
+
--rzero-combo-checkbox-hover-border: #94a3b8;
|
|
58
|
+
|
|
59
|
+
/* Radio variant */
|
|
60
|
+
--rzero-combo-radio-border: #4b5563;
|
|
61
|
+
--rzero-combo-radio-bg: #374151;
|
|
62
|
+
--rzero-combo-radio-checked-border: #60a5fa;
|
|
63
|
+
--rzero-combo-radio-checked-dot: #60a5fa;
|
|
64
|
+
--rzero-combo-radio-hover-border: #94a3b8;
|
|
65
|
+
|
|
66
|
+
/* Badges */
|
|
67
|
+
--rzero-combo-badge-bg: #374151;
|
|
68
|
+
--rzero-combo-badge-color: #d1d5db;
|
|
69
|
+
|
|
70
|
+
/* Footer */
|
|
71
|
+
--rzero-combo-footer-border-top: 1px solid #374151;
|
|
72
|
+
--rzero-combo-footer-color: #94a3b8;
|
|
73
|
+
--rzero-combo-footer-note-color: #64748b;
|
|
74
|
+
--rzero-combo-footer-action-border: 1px solid #4b5563;
|
|
75
|
+
--rzero-combo-footer-action-color: #e5e7eb;
|
|
76
|
+
--rzero-combo-footer-action-hover-bg: #374151;
|
|
77
|
+
--rzero-combo-footer-action-primary-bg: #60a5fa;
|
|
78
|
+
--rzero-combo-footer-action-primary-color: #111827;
|
|
79
|
+
--rzero-combo-footer-action-primary-hover-bg: #3b82f6;
|
|
80
|
+
|
|
81
|
+
/* Separator */
|
|
82
|
+
--rzero-combo-separator-color: #374151;
|
|
83
|
+
|
|
84
|
+
/* Tabs */
|
|
85
|
+
--rzero-combo-tab-border-color: #374151;
|
|
86
|
+
--rzero-combo-tab-color: #94a3b8;
|
|
87
|
+
--rzero-combo-tab-hover-bg: #374151;
|
|
88
|
+
--rzero-combo-tab-hover-color: #e5e7eb;
|
|
89
|
+
--rzero-combo-tab-focus-ring: #60a5fa;
|
|
90
|
+
--rzero-combo-tab-active-color: #f9fafb;
|
|
91
|
+
--rzero-combo-tab-active-border-color: #60a5fa;
|
|
92
|
+
--rzero-combo-tab-badge-bg: #374151;
|
|
93
|
+
--rzero-combo-tab-badge-color: #94a3b8;
|
|
94
|
+
--rzero-combo-tab-badge-active-bg: #1e3a5f;
|
|
95
|
+
--rzero-combo-tab-badge-active-color: #93c5fd;
|
|
96
|
+
}
|