@arcote.tech/arc-ds 0.5.2 → 0.5.6
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/package.json +4 -2
- package/src/ds/bento-card/bento-card.tsx +27 -35
- package/src/ds/bento-grid/bento-grid.tsx +23 -36
- package/src/ds/button/button.tsx +2 -0
- package/src/ds/chat/chat-input.tsx +61 -36
- package/src/ds/chat/chat-labels.tsx +89 -0
- package/src/ds/chat/chat-message.tsx +9 -18
- package/src/ds/chat/chat-tool-log.tsx +23 -4
- package/src/ds/chat/chat-tool-question.tsx +9 -9
- package/src/ds/chat/chat.tsx +18 -18
- package/src/ds/chat/question-tabs.tsx +194 -76
- package/src/ds/editable-text/editable-text.tsx +107 -0
- package/src/ds/form/fields/search-select-field.tsx +13 -5
- package/src/ds/search-select/search-select.tsx +305 -161
- package/src/ds/sidebar/sidebar.tsx +60 -0
- package/src/ds/types.ts +1 -0
- package/src/index.ts +12 -2
- package/src/layout/dynamic-slot.tsx +12 -2
- package/src/layout/layout.tsx +79 -8
|
@@ -68,6 +68,12 @@ export function DynamicSlotProvider({ children }: { children: ReactNode }) {
|
|
|
68
68
|
/**
|
|
69
69
|
* Register dynamic content into a named slot.
|
|
70
70
|
* Content appears while the component is mounted, removed on unmount.
|
|
71
|
+
*
|
|
72
|
+
* Always registers — even with `null` content — so the slot preserves a
|
|
73
|
+
* stable insertion order across re-renders. Readers filter out null entries
|
|
74
|
+
* via `useDynamicSlotContent` below, so conditional injection
|
|
75
|
+
* (`useSlotContent(slot, id, cond ? <x /> : null)`) works without shifting
|
|
76
|
+
* the position of sibling slot entries.
|
|
71
77
|
*/
|
|
72
78
|
export function useSlotContent(slotId: string, id: string, content: ReactNode) {
|
|
73
79
|
const ctx = useContext(DynamicSlotContext);
|
|
@@ -85,11 +91,15 @@ export function useSlotContent(slotId: string, id: string, content: ReactNode) {
|
|
|
85
91
|
}
|
|
86
92
|
|
|
87
93
|
/**
|
|
88
|
-
* Read dynamic slot content for a given slot ID.
|
|
94
|
+
* Read dynamic slot content for a given slot ID. Filters out null/undefined
|
|
95
|
+
* entries so consumers get only the renderable pieces.
|
|
89
96
|
*/
|
|
90
97
|
export function useDynamicSlotContent(slotId: string): ReactNode[] {
|
|
91
98
|
const ctx = useContext(DynamicSlotContext);
|
|
92
99
|
if (!ctx) return [];
|
|
93
100
|
const entries = ctx.entries.get(slotId);
|
|
94
|
-
|
|
101
|
+
if (!entries) return [];
|
|
102
|
+
return entries
|
|
103
|
+
.map((e) => e.content)
|
|
104
|
+
.filter((c) => c != null && c !== false);
|
|
95
105
|
}
|
package/src/layout/layout.tsx
CHANGED
|
@@ -47,8 +47,11 @@ export function Layout({ children }: { children?: ReactNode }) {
|
|
|
47
47
|
function SubNavSlot() {
|
|
48
48
|
const content = useDynamicSlotContent("sub-nav");
|
|
49
49
|
if (content.length === 0) return null;
|
|
50
|
+
// `relative z-30` creates an explicit stacking context at Z.BASE so any
|
|
51
|
+
// popovers inside the sub-nav (SearchSelect absolute dropdown, etc.) paint
|
|
52
|
+
// above the main content area that follows it in document flow.
|
|
50
53
|
return (
|
|
51
|
-
<div className="flex justify-center py-2">
|
|
54
|
+
<div className="relative z-30 flex justify-center py-2">
|
|
52
55
|
<Box layoutId="sub-nav" className="flex items-center gap-1 px-3 py-2">
|
|
53
56
|
{content}
|
|
54
57
|
</Box>
|
|
@@ -56,21 +59,86 @@ function SubNavSlot() {
|
|
|
56
59
|
);
|
|
57
60
|
}
|
|
58
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Tracks the desktop toolbar's actual rendered height and exposes it as a
|
|
64
|
+
* CSS variable on `<html>`. The toolbar is `position: fixed` so the layout
|
|
65
|
+
* itself can't measure it via flow — we query the data-attributed boxes
|
|
66
|
+
* (set by `DesktopToolbar`) and observe size changes.
|
|
67
|
+
*
|
|
68
|
+
* Consumers (sidebar, content spacer) read `var(--arc-toolbar-height)` to
|
|
69
|
+
* align with the toolbar without hard-coding pixel values.
|
|
70
|
+
*/
|
|
71
|
+
function useDesktopToolbarHeight() {
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
const root = document.documentElement;
|
|
74
|
+
const set = (px: number) => {
|
|
75
|
+
root.style.setProperty("--arc-toolbar-height", `${px}px`);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
let ro: ResizeObserver | null = null;
|
|
79
|
+
let cancelled = false;
|
|
80
|
+
|
|
81
|
+
const start = () => {
|
|
82
|
+
if (cancelled) return;
|
|
83
|
+
const boxes = Array.from(
|
|
84
|
+
document.querySelectorAll<HTMLElement>("[data-arc-toolbar]"),
|
|
85
|
+
);
|
|
86
|
+
if (boxes.length === 0) {
|
|
87
|
+
// Toolbar not mounted yet — try again next frame
|
|
88
|
+
requestAnimationFrame(start);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
const measure = () => {
|
|
92
|
+
const tallest = boxes.reduce(
|
|
93
|
+
(max, el) => Math.max(max, el.offsetHeight),
|
|
94
|
+
0,
|
|
95
|
+
);
|
|
96
|
+
// 16px top margin (top-4) + box height + 16px breathing room
|
|
97
|
+
set(tallest + 16 + 16);
|
|
98
|
+
};
|
|
99
|
+
measure();
|
|
100
|
+
ro = new ResizeObserver(measure);
|
|
101
|
+
for (const el of boxes) ro.observe(el);
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
start();
|
|
105
|
+
|
|
106
|
+
return () => {
|
|
107
|
+
cancelled = true;
|
|
108
|
+
ro?.disconnect();
|
|
109
|
+
root.style.removeProperty("--arc-toolbar-height");
|
|
110
|
+
};
|
|
111
|
+
}, []);
|
|
112
|
+
}
|
|
113
|
+
|
|
59
114
|
function DesktopLayout({ children }: { children?: ReactNode }) {
|
|
60
115
|
const renderSlot = useRenderSlot();
|
|
116
|
+
useDesktopToolbarHeight();
|
|
61
117
|
|
|
62
118
|
return (
|
|
63
119
|
<>
|
|
64
120
|
<DesktopToolbar />
|
|
65
|
-
|
|
121
|
+
{/* Spacer pushing main content below the fixed toolbar.
|
|
122
|
+
Height matches `--arc-toolbar-height` set by useDesktopToolbarHeight. */}
|
|
123
|
+
<div style={{ height: "var(--arc-toolbar-height, 5rem)" }} />
|
|
66
124
|
<SubNavSlot />
|
|
67
|
-
<div className="
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
125
|
+
<div className="flex w-full flex-1">
|
|
126
|
+
<div className="flex min-w-0 flex-1 justify-center">
|
|
127
|
+
<div className="flex w-full max-w-5xl gap-4 p-4">
|
|
128
|
+
{renderSlot("sidebar-left", { className: "flex w-64 shrink-0 flex-col gap-4" })}
|
|
129
|
+
<div className="min-w-0 flex-1">
|
|
130
|
+
{children}
|
|
131
|
+
{renderSlot("main-content", { className: "flex flex-col gap-4" })}
|
|
132
|
+
</div>
|
|
133
|
+
{renderSlot("sidebar-right", { className: "flex w-64 shrink-0 flex-col gap-4" })}
|
|
134
|
+
</div>
|
|
72
135
|
</div>
|
|
73
|
-
{renderSlot("
|
|
136
|
+
{renderSlot("preview-pane", {
|
|
137
|
+
className:
|
|
138
|
+
"flex w-[480px] shrink-0 flex-col gap-4 overflow-y-auto border-l p-4 " +
|
|
139
|
+
"sticky top-[var(--arc-toolbar-height,5rem)] " +
|
|
140
|
+
"max-h-[calc(100vh-var(--arc-toolbar-height,5rem))]",
|
|
141
|
+
})}
|
|
74
142
|
</div>
|
|
75
143
|
</>
|
|
76
144
|
);
|
|
@@ -98,6 +166,7 @@ function DesktopToolbar() {
|
|
|
98
166
|
return (
|
|
99
167
|
<>
|
|
100
168
|
<div
|
|
169
|
+
data-arc-toolbar="left"
|
|
101
170
|
className="fixed left-4 top-4"
|
|
102
171
|
style={{ zIndex: zIndex("workspace") }}
|
|
103
172
|
>
|
|
@@ -108,6 +177,7 @@ function DesktopToolbar() {
|
|
|
108
177
|
</div>
|
|
109
178
|
|
|
110
179
|
<div
|
|
180
|
+
data-arc-toolbar="center"
|
|
111
181
|
className="fixed left-1/2 top-4 max-w-[calc(100vw-26rem)] -translate-x-1/2"
|
|
112
182
|
style={{ zIndex: zIndex("center") }}
|
|
113
183
|
>
|
|
@@ -117,6 +187,7 @@ function DesktopToolbar() {
|
|
|
117
187
|
</div>
|
|
118
188
|
|
|
119
189
|
<div
|
|
190
|
+
data-arc-toolbar="right"
|
|
120
191
|
className="fixed right-4 top-4"
|
|
121
192
|
style={{ zIndex: zIndex("settings") }}
|
|
122
193
|
>
|