@knymbus/voxel-ui 1.0.15 → 1.0.19
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/.storybook/main.ts +10 -1
- package/.storybook/preview.ts +1 -1
- package/.storybook/tailwind.css +34 -0
- package/dist/chunks/{search-COxH7vvi.js → search-DSDSn6PK.js} +78 -94
- package/dist/chunks/tabs-DkmFedCI.js +182 -0
- package/dist/chunks/types-DmQVfNPh.js +21 -0
- package/dist/components/search/index.js +2 -2
- package/dist/components/search/types.d.ts +0 -20
- package/dist/components/tabs/index.js +1 -1
- package/dist/components/types.d.ts +20 -0
- package/dist/index.js +3 -3
- package/package.json +1 -1
- package/src/components/search/SearchInput.stories.tsx +4 -4
- package/src/components/search/SearchInput.tsx +6 -5
- package/src/components/search/SearchMenu.tsx +2 -1
- package/src/components/search/types.ts +0 -21
- package/src/components/tabs/TabButton.tsx +65 -23
- package/src/components/tabs/TabButtonGroup.tsx +31 -19
- package/src/components/tabs/TabPanel.tsx +33 -15
- package/src/components/tabs/TabPanelList.tsx +18 -4
- package/src/components/types.ts +22 -0
- package/src/index.css +8 -0
- package/src/index.ts +0 -1
- package/vite.config.mts +1 -2
- package/dist/assets/voxel-ui.css +0 -3
- package/dist/chunks/tabs-MaVN00hJ.js +0 -86
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
1
|
+
import { t as e } from "../../chunks/search-DSDSn6PK.js";
|
|
2
|
+
export { e as SearchInput };
|
|
@@ -20,23 +20,3 @@ export interface SearchInputProps {
|
|
|
20
20
|
showFloatPeek?: boolean;
|
|
21
21
|
peekContent?: React.ReactNode;
|
|
22
22
|
}
|
|
23
|
-
export declare const searchTokens: {
|
|
24
|
-
colors: {
|
|
25
|
-
bgInput: string;
|
|
26
|
-
border: string;
|
|
27
|
-
borderAccent: string;
|
|
28
|
-
borderAccentHover: string;
|
|
29
|
-
text: string;
|
|
30
|
-
muted: string;
|
|
31
|
-
sidebarBg: string;
|
|
32
|
-
hoverBg: string;
|
|
33
|
-
accentBgLight: string;
|
|
34
|
-
};
|
|
35
|
-
typography: {
|
|
36
|
-
sans: string;
|
|
37
|
-
mono: string;
|
|
38
|
-
};
|
|
39
|
-
transitions: {
|
|
40
|
-
smooth: string;
|
|
41
|
-
};
|
|
42
|
-
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { i as e, n as t, r as n, t as r } from "../../chunks/tabs-
|
|
1
|
+
import { i as e, n as t, r as n, t as r } from "../../chunks/tabs-DkmFedCI.js";
|
|
2
2
|
export { r as TabButton, t as TabPanel, n as TabPanelList, e as useTab };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const searchTokens: {
|
|
2
|
+
colors: {
|
|
3
|
+
bgInput: string;
|
|
4
|
+
border: string;
|
|
5
|
+
borderAccent: string;
|
|
6
|
+
borderAccentHover: string;
|
|
7
|
+
text: string;
|
|
8
|
+
muted: string;
|
|
9
|
+
sidebarBg: string;
|
|
10
|
+
hoverBg: string;
|
|
11
|
+
accentBgLight: string;
|
|
12
|
+
};
|
|
13
|
+
typography: {
|
|
14
|
+
sans: string;
|
|
15
|
+
mono: string;
|
|
16
|
+
};
|
|
17
|
+
transitions: {
|
|
18
|
+
smooth: string;
|
|
19
|
+
};
|
|
20
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { t as e } from "./chunks/resizable-ImB8dfG_.js";
|
|
2
|
-
import { i as t, n, r, t as i } from "./chunks/tabs-
|
|
2
|
+
import { i as t, n, r, t as i } from "./chunks/tabs-DkmFedCI.js";
|
|
3
3
|
import { t as a } from "./chunks/Button-BgQwvn3C.js";
|
|
4
4
|
import { n as o, t as s } from "./chunks/button-dHcpTNIG.js";
|
|
5
5
|
import { C as c, E as l, S as u, T as d, _ as f, a as p, b as m, c as h, d as g, f as _, g as v, h as y, i as b, l as x, m as S, n as C, o as w, p as T, r as E, s as D, t as O, u as k, v as A, w as j, x as M, y as N } from "./chunks/icons-BpfDVwCQ.js";
|
|
6
|
-
import {
|
|
7
|
-
export { A as Add, S as BlankDoc, a as Button, o as ButtonGroup, _ as Chat, N as ChevronDown, l as Close, x as Comment, k as DeleteChat, j as Document, v as Expand, c as Folder, C as Group, y as Minimize, f as Minus, w as More, D as OpenFolder, b as Person, g as PlusChat, h as PlusComment, T as PlusDoc, O as PlusDocBadge, E as PlusPerson, M as Refresh, e as ResizablePanel, u as Search,
|
|
6
|
+
import { t as P } from "./chunks/search-DSDSn6PK.js";
|
|
7
|
+
export { A as Add, S as BlankDoc, a as Button, o as ButtonGroup, _ as Chat, N as ChevronDown, l as Close, x as Comment, k as DeleteChat, j as Document, v as Expand, c as Folder, C as Group, y as Minimize, f as Minus, w as More, D as OpenFolder, b as Person, g as PlusChat, h as PlusComment, T as PlusDoc, O as PlusDocBadge, E as PlusPerson, M as Refresh, e as ResizablePanel, u as Search, P as SearchInput, s as SplitActionButton, i as TabButton, n as TabPanel, r as TabPanelList, d as Terminal, m as Trash, p as Truck, t as useTab };
|
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { useState } from 'react';
|
|
2
2
|
import type { Meta, StoryObj } from '@storybook/react';
|
|
3
3
|
import SearchInput from './SearchInput';
|
|
4
4
|
import { SearchResultItem } from './types';
|
|
@@ -45,10 +45,10 @@ const MultiSearchWorkspace = () => {
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
return (
|
|
48
|
-
<div
|
|
48
|
+
<div className="p-8 bg-vsc-sidebar border border-vsc-border rounded-md space-y-8 min-w-112.5 text-vsc-text" >
|
|
49
49
|
|
|
50
50
|
{/* 1. Simple Variant Bracket */}
|
|
51
|
-
<div className="space-y-1.5"
|
|
51
|
+
<div className="space-y-1.5" >
|
|
52
52
|
<p className="text-[10px] font-bold text-vsc-muted uppercase tracking-wider">1. Simple Matrix Variant with Count Log</p>
|
|
53
53
|
<SearchInput
|
|
54
54
|
variant="simple"
|
|
@@ -56,7 +56,7 @@ const MultiSearchWorkspace = () => {
|
|
|
56
56
|
onChange={setSimpleQuery}
|
|
57
57
|
resultsCount={filteredSimpleResults.length}
|
|
58
58
|
placeholder="Type track ID to evaluate records count..."
|
|
59
|
-
|
|
59
|
+
peekContent={<ResultPanel />}
|
|
60
60
|
/>
|
|
61
61
|
</div>
|
|
62
62
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
|
-
import { SearchInputProps
|
|
2
|
+
import { SearchInputProps } from './types';
|
|
3
3
|
import { Search, Close } from '../icons';
|
|
4
4
|
import { Button } from '../button/Button';
|
|
5
5
|
import SearchMenu from './SearchMenu';
|
|
6
|
+
import { searchTokens } from '../types';
|
|
6
7
|
|
|
7
8
|
export default function SearchInput({
|
|
8
9
|
variant = 'simple',
|
|
@@ -159,13 +160,13 @@ export default function SearchInput({
|
|
|
159
160
|
zIndex: 10,
|
|
160
161
|
// overflow: 'hidden',
|
|
161
162
|
transition: searchTokens.transitions.smooth,
|
|
162
|
-
height:
|
|
163
|
-
opacity:
|
|
164
|
-
transform:
|
|
163
|
+
height: hasQuery ? 'auto' : '0px',
|
|
164
|
+
opacity: hasQuery ? 1 : 0,
|
|
165
|
+
transform: hasQuery ? 'translateY(4px)' : 'translateY(-4px)',
|
|
165
166
|
pointerEvents: 'none'
|
|
166
167
|
}}
|
|
167
168
|
>
|
|
168
|
-
<div style={{ backgroundColor: searchTokens.colors.sidebarBg, border: `0px solid ${searchTokens.colors.border}`, borderTop: 'none', padding: '4px 6px', borderRadius: '
|
|
169
|
+
<div style={{ backgroundColor: searchTokens.colors.sidebarBg, border: `0px solid ${searchTokens.colors.border}`, borderTop: 'none', padding: '4px 6px', borderRadius: '6px', boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1)' }}>
|
|
169
170
|
{renderedPeek()}
|
|
170
171
|
</div>
|
|
171
172
|
</div>
|
|
@@ -25,24 +25,3 @@ export interface SearchInputProps {
|
|
|
25
25
|
peekContent?: React.ReactNode;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export const searchTokens = {
|
|
29
|
-
colors: {
|
|
30
|
-
bgInput: 'var(--color-vsc-bg-input, #f6f8fa)',
|
|
31
|
-
border: 'var(--color-vsc-border, #e4e4e7)',
|
|
32
|
-
borderAccent: 'var(--color-vsc-accent, #007acc)',
|
|
33
|
-
borderAccentHover: 'var(--color-vsc-accent-hover, #0062a3)',
|
|
34
|
-
text: 'var(--color-vsc-text, #333333)',
|
|
35
|
-
muted: 'var(--color-vsc-muted, #6a737d)',
|
|
36
|
-
sidebarBg: 'var(--color-vsc-sidebar, #f3f3f3)',
|
|
37
|
-
hoverBg: 'var(--color-vsc-hover, #e8e8e8)',
|
|
38
|
-
accentBgLight: 'rgba(0, 122, 204, 0.1)'
|
|
39
|
-
},
|
|
40
|
-
typography: {
|
|
41
|
-
/* ⚡ Pulls Tailwind's native font configuration maps from your global layout shell App context */
|
|
42
|
-
sans: 'var(--font-sans, font-sans, ui-sans-serif, system-ui, sans-serif)',
|
|
43
|
-
mono: 'var(--font-mono, font-mono, ui-monospace, SFMono-Regular, monospace)'
|
|
44
|
-
},
|
|
45
|
-
transitions: {
|
|
46
|
-
smooth: 'all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)'
|
|
47
|
-
}
|
|
48
|
-
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React from 'react';
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
2
|
import { useTab } from './useTab';
|
|
3
|
-
import { TabVariant } from './types';
|
|
3
|
+
import { TabVariant } from './types'; // Shares the typography/color tokens mapping schema
|
|
4
|
+
import { searchTokens } from '../types';
|
|
4
5
|
|
|
5
6
|
interface TabButtonProps {
|
|
6
7
|
id: string;
|
|
@@ -20,37 +21,78 @@ export default function TabButton({
|
|
|
20
21
|
endIcon
|
|
21
22
|
}: TabButtonProps) {
|
|
22
23
|
const { activeTab, changeTab } = useTab(scopeName);
|
|
24
|
+
const [isHovered, setIsHovered] = useState(false);
|
|
23
25
|
const isCurrent = activeTab === id;
|
|
24
26
|
|
|
25
|
-
const
|
|
27
|
+
const baseButtonStyles: React.CSSProperties = {
|
|
28
|
+
display: 'inline-flex',
|
|
29
|
+
alignItems: 'center',
|
|
30
|
+
justifyContent: 'center',
|
|
31
|
+
gap: '6px',
|
|
32
|
+
backgroundColor: 'transparent',
|
|
33
|
+
border: 'none',
|
|
34
|
+
outline: 'none',
|
|
35
|
+
fontFamily: searchTokens.typography.sans,
|
|
36
|
+
fontSize: '12px',
|
|
37
|
+
userSelect: 'none',
|
|
38
|
+
cursor: 'pointer',
|
|
39
|
+
height: '100%',
|
|
40
|
+
transition: 'all 150ms ease',
|
|
41
|
+
boxSizing: 'border-box',
|
|
42
|
+
position: 'relative',
|
|
43
|
+
zIndex: 10
|
|
44
|
+
};
|
|
26
45
|
|
|
27
|
-
const
|
|
28
|
-
underline:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
isCurrent ?
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
const variantStyles: Record<TabVariant, React.CSSProperties> = {
|
|
47
|
+
underline: {
|
|
48
|
+
padding: '4px 12px 8px 12px',
|
|
49
|
+
fontWeight: isCurrent ? 600 : 500,
|
|
50
|
+
color: isCurrent ? searchTokens.colors.text : (isHovered ? searchTokens.colors.text : searchTokens.colors.muted),
|
|
51
|
+
borderBottom: `2px solid ${isCurrent ? searchTokens.colors.borderAccent : 'transparent'}`
|
|
52
|
+
},
|
|
53
|
+
'sliding-underline': {
|
|
54
|
+
padding: '8px 16px',
|
|
55
|
+
fontWeight: isCurrent ? 700 : 500,
|
|
56
|
+
color: isCurrent ? searchTokens.colors.text : (isHovered ? searchTokens.colors.text : searchTokens.colors.muted)
|
|
57
|
+
},
|
|
58
|
+
pill: {
|
|
59
|
+
padding: '6px 14px',
|
|
60
|
+
fontWeight: 700,
|
|
61
|
+
borderRadius: '9999px',
|
|
62
|
+
backgroundColor: isCurrent ? searchTokens.colors.borderAccent : (isHovered ? searchTokens.colors.hoverBg : 'transparent'),
|
|
63
|
+
color: isCurrent ? '#ffffff' : (isHovered ? searchTokens.colors.text : searchTokens.colors.muted),
|
|
64
|
+
transform: isCurrent ? 'scale(1.02)' : 'scale(1)',
|
|
65
|
+
boxShadow: isCurrent ? '0 1px 3px rgba(0,0,0,0.1)' : 'none'
|
|
66
|
+
},
|
|
67
|
+
vscode: {
|
|
68
|
+
padding: '0 16px',
|
|
69
|
+
fontWeight: 500,
|
|
70
|
+
color: isCurrent ? searchTokens.colors.text : (isHovered ? searchTokens.colors.text : searchTokens.colors.muted),
|
|
71
|
+
backgroundColor: isCurrent ? searchTokens.colors.bgInput : (isHovered ? 'rgba(0,0,0,0.02)' : searchTokens.colors.sidebarBg),
|
|
72
|
+
borderRight: `1px solid ${searchTokens.colors.border}`,
|
|
73
|
+
borderTop: isCurrent ? `2px solid ${searchTokens.colors.borderAccent}` : '2px solid transparent',
|
|
74
|
+
marginTop: isCurrent ? '-1px' : '0'
|
|
75
|
+
},
|
|
76
|
+
ghost: {
|
|
77
|
+
padding: '6px 12px',
|
|
78
|
+
fontWeight: isCurrent ? 700 : 500,
|
|
79
|
+
borderRadius: '2px',
|
|
80
|
+
backgroundColor: isCurrent ? searchTokens.colors.hoverBg : (isHovered ? 'rgba(0,0,0,0.04)' : 'transparent'),
|
|
81
|
+
color: isCurrent ? searchTokens.colors.text : (isHovered ? searchTokens.colors.text : searchTokens.colors.muted)
|
|
82
|
+
}
|
|
43
83
|
};
|
|
44
84
|
|
|
45
85
|
return (
|
|
46
86
|
<button
|
|
47
87
|
data-id={id}
|
|
48
88
|
onClick={() => changeTab(id)}
|
|
49
|
-
|
|
89
|
+
onMouseEnter={() => setIsHovered(true)}
|
|
90
|
+
onMouseLeave={() => setIsHovered(false)}
|
|
91
|
+
style={{ ...baseButtonStyles, ...variantStyles[variant] }}
|
|
50
92
|
>
|
|
51
|
-
{startIcon && <span
|
|
52
|
-
<span
|
|
53
|
-
{endIcon && <span
|
|
93
|
+
{startIcon && <span style={{ display: 'inline-flex', flexShrink: 0, opacity: isCurrent || isHovered ? 1 : 0.7 }}>{startIcon}</span>}
|
|
94
|
+
<span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{children}</span>
|
|
95
|
+
{endIcon && <span style={{ display: 'inline-flex', flexShrink: 0, opacity: isCurrent || isHovered ? 1 : 0.6 }}>{endIcon}</span>}
|
|
54
96
|
</button>
|
|
55
97
|
);
|
|
56
98
|
}
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React, { useState, useEffect, useRef } from 'react';
|
|
2
2
|
import { useTab } from './useTab';
|
|
3
3
|
import { TabVariant } from './types';
|
|
4
|
-
import TabButton from './TabButton';
|
|
4
|
+
import TabButton from './TabButton';
|
|
5
|
+
import { searchTokens } from '../types';
|
|
5
6
|
|
|
6
7
|
interface TabButtonGroupProps {
|
|
7
8
|
children: React.ReactNode;
|
|
@@ -20,15 +21,10 @@ export default function TabButtonGroup({
|
|
|
20
21
|
const [lineStyles, setLineStyles] = useState<React.CSSProperties>({ left: 0, width: 0, opacity: 0 });
|
|
21
22
|
const [hoveredId, setHoveredId] = useState<string | null>(null);
|
|
22
23
|
|
|
23
|
-
// --- STRICT CHILDREN SUB-COMPONENT VALIDATION ENGINE LOOP ---
|
|
24
24
|
const validatedChildren = React.Children.map(children, (child) => {
|
|
25
25
|
if (!React.isValidElement(child)) return null;
|
|
26
|
-
|
|
27
|
-
// Verify if the child component reference maps directly to our TabButton signature
|
|
28
26
|
if (child.type !== TabButton) {
|
|
29
|
-
console.warn(
|
|
30
|
-
`[Voxel UI Validation Warning]: TabButtonGroup scope "${scopeName}" only accepts sub-children elements of type <TabButton />. Ignored invalid component node.`
|
|
31
|
-
);
|
|
27
|
+
console.warn(`[Voxel UI Warning]: TabButtonGroup only accepts children of type <TabButton />.`);
|
|
32
28
|
return null;
|
|
33
29
|
}
|
|
34
30
|
return child;
|
|
@@ -45,18 +41,27 @@ export default function TabButtonGroup({
|
|
|
45
41
|
left: `${targetElement.offsetLeft}px`,
|
|
46
42
|
width: `${targetElement.offsetWidth}px`,
|
|
47
43
|
opacity: 1,
|
|
48
|
-
backgroundColor: hoveredId && hoveredId !== activeTab ?
|
|
44
|
+
backgroundColor: hoveredId && hoveredId !== activeTab ? searchTokens.colors.text : searchTokens.colors.borderAccent
|
|
49
45
|
});
|
|
50
46
|
}
|
|
51
47
|
}, [activeTab, hoveredId, variant]);
|
|
52
48
|
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
const baseContainerStyles: React.CSSProperties = {
|
|
50
|
+
display: 'flex',
|
|
51
|
+
alignItems: 'center',
|
|
52
|
+
fontFamily: searchTokens.typography.sans,
|
|
53
|
+
userSelect: 'none',
|
|
54
|
+
boxSizing: 'border-box',
|
|
55
|
+
position: 'relative'
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const variantContainerStyles: Record<TabVariant, React.CSSProperties> = {
|
|
59
|
+
underline: { width: '100%', borderBottom: `1px solid ${searchTokens.colors.border}`, backgroundColor: searchTokens.colors.bgInput, gap: '8px', paddingBottom: '1px' },
|
|
60
|
+
'sliding-underline': { width: '100%', borderBottom: `1px solid ${searchTokens.colors.border}`, backgroundColor: searchTokens.colors.bgInput, paddingBottom: '2px' },
|
|
61
|
+
pill: { width: 'max-content', padding: '4px', backgroundColor: searchTokens.colors.sidebarBg, borderRadius: '9999px', gap: '6px' },
|
|
62
|
+
vscode: { width: '100%', height: '36px', backgroundColor: searchTokens.colors.sidebarBg, borderBottom: `1px solid ${searchTokens.colors.border}`, overflowX: 'auto', overflowY: 'hidden' },
|
|
63
|
+
ghost: { width: '100%', padding: '4px', backgroundColor: searchTokens.colors.bgInput, border: `1px solid ${searchTokens.colors.border}`, borderRadius: '4px', gap: '4px' }
|
|
64
|
+
};
|
|
60
65
|
|
|
61
66
|
return (
|
|
62
67
|
<div
|
|
@@ -66,15 +71,22 @@ export default function TabButtonGroup({
|
|
|
66
71
|
if (btn) setHoveredId(btn.getAttribute('data-id'));
|
|
67
72
|
}}
|
|
68
73
|
onMouseLeave={() => setHoveredId(null)}
|
|
69
|
-
|
|
74
|
+
style={{ ...baseContainerStyles, ...variantContainerStyles[variant] }}
|
|
70
75
|
>
|
|
71
|
-
{/* Render exclusively the type-validated sub-nodes collection array */}
|
|
72
76
|
{validatedChildren}
|
|
73
77
|
|
|
78
|
+
{/* Real-time Sliding Underline Track Line Component */}
|
|
74
79
|
{variant === 'sliding-underline' && (
|
|
75
80
|
<div
|
|
76
|
-
|
|
77
|
-
|
|
81
|
+
style={{
|
|
82
|
+
position: 'absolute',
|
|
83
|
+
bottom: 0,
|
|
84
|
+
height: '2px',
|
|
85
|
+
borderRadius: '2px 2px 0 0',
|
|
86
|
+
transition: 'all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
87
|
+
zIndex: 20,
|
|
88
|
+
...lineStyles
|
|
89
|
+
}}
|
|
78
90
|
/>
|
|
79
91
|
)}
|
|
80
92
|
</div>
|
|
@@ -10,7 +10,6 @@ interface TabPanelProps {
|
|
|
10
10
|
export default function TabPanel({ children, id, persist = false }: TabPanelProps) {
|
|
11
11
|
const { activeTabId } = useTabListContext();
|
|
12
12
|
const isCurrent = activeTabId === id;
|
|
13
|
-
|
|
14
13
|
const [hasRenderedOnce, setHasRenderedOnce] = useState<boolean>(false);
|
|
15
14
|
|
|
16
15
|
useEffect(() => {
|
|
@@ -22,21 +21,40 @@ export default function TabPanel({ children, id, persist = false }: TabPanelProp
|
|
|
22
21
|
if (!hasRenderedOnce) return null;
|
|
23
22
|
if (!persist && !isCurrent) return null;
|
|
24
23
|
|
|
24
|
+
// Render the core layout canvas frame absolutely with zero layout shifts
|
|
25
|
+
const panelInlineStyles: React.CSSProperties = {
|
|
26
|
+
position: 'absolute',
|
|
27
|
+
top: 0,
|
|
28
|
+
left: 0,
|
|
29
|
+
right: 0,
|
|
30
|
+
bottom: 0,
|
|
31
|
+
width: '100%',
|
|
32
|
+
height: '100%',
|
|
33
|
+
/*
|
|
34
|
+
⚡ FIXED VISIBILITY OVERRIDE:
|
|
35
|
+
Bypassed background color fills entirely to prevent canvas z-index clipping traps.
|
|
36
|
+
*/
|
|
37
|
+
backgroundColor: 'transparent',
|
|
38
|
+
transition: 'opacity 250ms ease-out, transform 250ms cubic-bezier(0.34, 1.56, 0.64, 1)',
|
|
39
|
+
boxSizing: 'border-box',
|
|
40
|
+
|
|
41
|
+
// Smooth opacity fading animation matrices
|
|
42
|
+
opacity: isCurrent ? 1 : 0,
|
|
43
|
+
transform: isCurrent ? 'scale(1)' : 'scale(0.995)',
|
|
44
|
+
zIndex: isCurrent ? 10 : 0,
|
|
45
|
+
pointerEvents: isCurrent ? 'auto' : 'none',
|
|
46
|
+
visibility: isCurrent ? 'visible' : 'hidden'
|
|
47
|
+
};
|
|
48
|
+
|
|
25
49
|
return (
|
|
26
|
-
<div
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
: '
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
Inner layout wrapper applies a micro-blur and fine opacity mask cross-fade
|
|
35
|
-
to ensure background elements never clip through transparent gaps during transitions.
|
|
36
|
-
*/}
|
|
37
|
-
<div className={`w-full h-full transition-opacity duration-300 ${
|
|
38
|
-
isCurrent ? 'opacity-100' : 'opacity-0'
|
|
39
|
-
}`}>
|
|
50
|
+
<div style={panelInlineStyles}>
|
|
51
|
+
<div
|
|
52
|
+
style={{
|
|
53
|
+
width: '100%',
|
|
54
|
+
height: '100%',
|
|
55
|
+
boxSizing: 'border-box'
|
|
56
|
+
}}
|
|
57
|
+
>
|
|
40
58
|
{children}
|
|
41
59
|
</div>
|
|
42
60
|
</div>
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import React, { useEffect, createContext, useContext } from 'react';
|
|
2
2
|
import { useTab } from './useTab';
|
|
3
|
+
import { searchTokens } from '../types';
|
|
3
4
|
|
|
4
5
|
interface TabPanelListProps {
|
|
5
6
|
children: React.ReactNode;
|
|
6
|
-
targetScopeName: string; // Ties this
|
|
7
|
-
defaultValue: string; // Initial
|
|
7
|
+
targetScopeName: string; // Ties this list panel block to a named useTab instance
|
|
8
|
+
defaultValue: string; // Initial focused tab on mount
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
const TabListContext = createContext<{ activeTabId: string | undefined }>({ activeTabId: undefined });
|
|
@@ -14,16 +15,29 @@ export const useTabListContext = () => useContext(TabListContext);
|
|
|
14
15
|
export default function TabPanelList({ children, targetScopeName, defaultValue }: TabPanelListProps) {
|
|
15
16
|
const { activeTab, registerDefault } = useTab(targetScopeName);
|
|
16
17
|
|
|
17
|
-
// Initialize defaultValue into
|
|
18
|
+
// Initialize defaultValue into the global Zustand store on first render execution
|
|
18
19
|
useEffect(() => {
|
|
19
20
|
registerDefault(defaultValue);
|
|
20
21
|
}, [defaultValue, registerDefault]);
|
|
21
22
|
|
|
22
23
|
const activeTabId = activeTab || defaultValue;
|
|
23
24
|
|
|
25
|
+
// Root canvas frame styling object using pure inline layout logic
|
|
26
|
+
const listContainerInlineStyles: React.CSSProperties = {
|
|
27
|
+
position: 'relative',
|
|
28
|
+
width: '100%',
|
|
29
|
+
height: '100%',
|
|
30
|
+
minHeight: 0,
|
|
31
|
+
flex: '1 1 0%',
|
|
32
|
+
overflow: 'hidden',
|
|
33
|
+
backgroundColor: searchTokens.colors.bgInput,
|
|
34
|
+
fontFamily: searchTokens.typography.sans,
|
|
35
|
+
boxSizing: 'border-box'
|
|
36
|
+
};
|
|
37
|
+
|
|
24
38
|
return (
|
|
25
39
|
<TabListContext.Provider value={{ activeTabId }}>
|
|
26
|
-
<div
|
|
40
|
+
<div style={listContainerInlineStyles}>
|
|
27
41
|
{children}
|
|
28
42
|
</div>
|
|
29
43
|
</TabListContext.Provider>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
export const searchTokens = {
|
|
3
|
+
colors: {
|
|
4
|
+
bgInput: 'var(--color-vsc-bg-input, #f6f8fa)',
|
|
5
|
+
border: 'var(--color-vsc-border, #e4e4e7)',
|
|
6
|
+
borderAccent: 'var(--color-vsc-accent, #007acc)',
|
|
7
|
+
borderAccentHover: 'var(--color-vsc-accent-hover, #0062a3)',
|
|
8
|
+
text: 'var(--color-vsc-text, #333333)',
|
|
9
|
+
muted: 'var(--color-vsc-muted, #6a737d)',
|
|
10
|
+
sidebarBg: 'var(--color-vsc-sidebar, #f3f3f3)',
|
|
11
|
+
hoverBg: 'var(--color-vsc-hover, #e8e8e8)',
|
|
12
|
+
accentBgLight: 'rgba(0, 122, 204, 0.1)'
|
|
13
|
+
},
|
|
14
|
+
typography: {
|
|
15
|
+
/* ⚡ Pulls Tailwind's native font configuration maps from your global layout shell App context */
|
|
16
|
+
sans: 'var(--font-sans, font-sans, ui-sans-serif, system-ui, sans-serif)',
|
|
17
|
+
mono: 'var(--font-mono, font-mono, ui-monospace, SFMono-Regular, monospace)'
|
|
18
|
+
},
|
|
19
|
+
transitions: {
|
|
20
|
+
smooth: 'all 200ms cubic-bezier(0.34, 1.56, 0.64, 1)'
|
|
21
|
+
}
|
|
22
|
+
};
|
package/src/index.css
CHANGED
package/src/index.ts
CHANGED
package/vite.config.mts
CHANGED
|
@@ -8,12 +8,11 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
|
|
9
9
|
import { playwright } from '@vitest/browser-playwright';
|
|
10
10
|
const dirname = typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
-
|
|
11
|
+
|
|
12
12
|
|
|
13
13
|
// More info at: https://storybook.js.org/docs/next/writing-tests/integrations/vitest-addon
|
|
14
14
|
export default defineConfig({
|
|
15
15
|
plugins: [
|
|
16
|
-
tailwindcss(),
|
|
17
16
|
react(),
|
|
18
17
|
// Auto-generates independent TypeScript type declaration files (.d.ts) matching paths
|
|
19
18
|
dts({
|
package/dist/assets/voxel-ui.css
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
/*! tailwindcss v4.3.2 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer theme{:root,:host{--voxel-font-sans:ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--voxel-font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--voxel-default-font-family:var(--voxel-font-sans);--voxel-default-mono-font-family:var(--voxel-font-mono)}}@layer base{@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--voxel-default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--voxel-default-font-feature-settings,normal);font-variation-settings:var(--voxel-default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--voxel-default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--voxel-default-mono-font-feature-settings,normal);font-variation-settings:var(--voxel-default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}.dark{--color-vsc-bg:#1e1e1e;--color-vsc-sidebar:#252526;--color-vsc-border:#3c3c3c;--color-vsc-accent:#007acc;--color-vsc-accent-hover:#0062a3;--color-vsc-hover:#2a2d2e;--color-vsc-text:#ccc;--color-vsc-muted:#8b949e;--color-vsc-bg-input:#1f1f1f}}@layer components,utilities;
|
|
3
|
-
/*$vite$:1*/
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { t as e } from "./jsx-runtime-Boo2vksn.js";
|
|
2
|
-
import { createContext as t, useContext as n, useEffect as r, useState as i } from "react";
|
|
3
|
-
import { create as a } from "zustand";
|
|
4
|
-
//#region src/components/tabs/useTab.ts
|
|
5
|
-
var o = a((e) => ({
|
|
6
|
-
activeTabs: {},
|
|
7
|
-
setActiveTab: (t, n) => e((e) => ({ activeTabs: {
|
|
8
|
-
...e.activeTabs,
|
|
9
|
-
[t]: n
|
|
10
|
-
} })),
|
|
11
|
-
initializeTab: (t, n) => e((e) => e.activeTabs[t] ? {} : { activeTabs: {
|
|
12
|
-
...e.activeTabs,
|
|
13
|
-
[t]: n
|
|
14
|
-
} })
|
|
15
|
-
}));
|
|
16
|
-
function s(e) {
|
|
17
|
-
let t = o((t) => t.activeTabs[e]), n = o((e) => e.setActiveTab), r = o((e) => e.initializeTab);
|
|
18
|
-
return {
|
|
19
|
-
activeTab: t,
|
|
20
|
-
changeTab: (t) => n(e, t),
|
|
21
|
-
registerDefault: (t) => r(e, t)
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/components/tabs/TabPanelList.tsx
|
|
26
|
-
var c = e(), l = t({ activeTabId: void 0 }), u = () => n(l);
|
|
27
|
-
function d({ children: e, targetScopeName: t, defaultValue: n }) {
|
|
28
|
-
let { activeTab: i, registerDefault: a } = s(t);
|
|
29
|
-
r(() => {
|
|
30
|
-
a(n);
|
|
31
|
-
}, [n, a]);
|
|
32
|
-
let o = i || n;
|
|
33
|
-
return /* @__PURE__ */ (0, c.jsx)(l.Provider, {
|
|
34
|
-
value: { activeTabId: o },
|
|
35
|
-
children: /* @__PURE__ */ (0, c.jsx)("div", {
|
|
36
|
-
className: "relative w-full h-full min-h-0 flex-1 overflow-hidden bg-vsc-bg",
|
|
37
|
-
children: e
|
|
38
|
-
})
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region src/components/tabs/TabPanel.tsx
|
|
43
|
-
function f({ children: e, id: t, persist: n = !1 }) {
|
|
44
|
-
let { activeTabId: a } = u(), o = a === t, [s, l] = i(!1);
|
|
45
|
-
return r(() => {
|
|
46
|
-
o && !s && l(!0);
|
|
47
|
-
}, [o, s]), !s || !n && !o ? null : /* @__PURE__ */ (0, c.jsx)("div", {
|
|
48
|
-
className: `absolute inset-0 w-full h-full bg-vsc-bg transition-all duration-300 ease-in-out ${o ? "opacity-100 scale-100 z-10 pointer-events-auto visible" : "opacity-0 scale-[0.99] z-0 pointer-events-none invisible delay-75"}`,
|
|
49
|
-
children: /* @__PURE__ */ (0, c.jsx)("div", {
|
|
50
|
-
className: `w-full h-full transition-opacity duration-300 ${o ? "opacity-100" : "opacity-0"}`,
|
|
51
|
-
children: e
|
|
52
|
-
})
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
//#endregion
|
|
56
|
-
//#region src/components/tabs/TabButton.tsx
|
|
57
|
-
function p({ id: e, scopeName: t, children: n, variant: r = "underline", startIcon: i, endIcon: a }) {
|
|
58
|
-
let { activeTab: o, changeTab: l } = s(t), u = o === e;
|
|
59
|
-
return /* @__PURE__ */ (0, c.jsxs)("button", {
|
|
60
|
-
"data-id": e,
|
|
61
|
-
onClick: () => l(e),
|
|
62
|
-
className: `flex items-center justify-center gap-1.5 focus:outline-none border-none outline-none z-10 transition-colors select-none h-full ${{
|
|
63
|
-
underline: `pb-2 pt-1 px-3 text-xs font-semibold cursor-pointer border-b-2 ${u ? "border-vsc-accent text-vsc-text" : "border-transparent text-vsc-muted hover:text-vsc-text"}`,
|
|
64
|
-
"sliding-underline": `py-2 px-4 text-xs font-semibold cursor-pointer transition-colors duration-200 ${u ? "text-vsc-text font-bold" : "text-vsc-muted hover:text-vsc-text"}`,
|
|
65
|
-
pill: `px-3.5 py-1.5 text-xs font-bold rounded-full cursor-pointer scale-100 ${u ? "bg-vsc-accent text-vsc-button-text shadow-sm" : "bg-vsc-hover/40 text-vsc-muted hover:bg-vsc-hover"}`,
|
|
66
|
-
vscode: `px-4 text-xs font-medium border-r border-vsc-border cursor-pointer ${u ? "bg-vsc-bg text-vsc-text border-t-2 border-t-vsc-accent -mt-[1px]" : "bg-vsc-sidebar text-vsc-muted hover:bg-vsc-hover/50"}`,
|
|
67
|
-
ghost: `px-3 py-1.5 text-xs font-semibold rounded cursor-pointer ${u ? "bg-vsc-hover text-vsc-text font-bold" : "text-vsc-muted hover:bg-vsc-hover/30"}`
|
|
68
|
-
}[r]}`,
|
|
69
|
-
children: [
|
|
70
|
-
i && /* @__PURE__ */ (0, c.jsx)("span", {
|
|
71
|
-
className: "shrink-0 opacity-80",
|
|
72
|
-
children: i
|
|
73
|
-
}),
|
|
74
|
-
/* @__PURE__ */ (0, c.jsx)("span", {
|
|
75
|
-
className: "truncate",
|
|
76
|
-
children: n
|
|
77
|
-
}),
|
|
78
|
-
a && /* @__PURE__ */ (0, c.jsx)("span", {
|
|
79
|
-
className: "shrink-0 opacity-70",
|
|
80
|
-
children: a
|
|
81
|
-
})
|
|
82
|
-
]
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
//#endregion
|
|
86
|
-
export { s as i, f as n, d as r, p as t };
|