@exegia/corpora-ui 0.19.0 → 0.21.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/dist-lib/components/blocks/auth/__tests__/auth-state-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
- package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
- package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
- package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
- package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
- package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.d.ts +1 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
- package/dist-lib/components/blocks/nav/sidebar/ai-sidebar.d.ts +11 -2
- package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
- package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
- package/dist-lib/components/blocks/nav/sidebar/type.d.ts +191 -29
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
- package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +27 -31
- package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +6 -1
- package/dist-lib/components/blocks/profile/index.d.ts +7 -0
- package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
- package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
- package/dist-lib/components/blocks/profile/type.d.ts +80 -0
- package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
- package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
- package/dist-lib/components/blocks/scaffold/constants.d.ts +5 -0
- package/dist-lib/components/blocks/scaffold/scaffold-canvas.d.ts +2 -0
- package/dist-lib/components/blocks/scaffold/scaffold-panel.d.ts +1 -1
- package/dist-lib/components/blocks/scaffold/scaffold-tab.d.ts +6 -1
- package/dist-lib/components/blocks/scaffold/type.d.ts +28 -0
- package/dist-lib/components/blocks/scaffold/use-panel-visibility.d.ts +19 -0
- package/dist-lib/components/blocks/scaffold/utils.d.ts +6 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
- package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
- package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
- package/dist-lib/components/blocks/shell/index.d.ts +4 -0
- package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
- package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
- package/dist-lib/components/blocks/shell/type.d.ts +115 -3
- package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
- package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
- package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
- package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
- package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/__tests__/tree.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/__tests__/use-tree.test.d.ts +1 -0
- package/dist-lib/components/composed/tree/constants.d.ts +35 -0
- package/dist-lib/components/composed/tree/index.d.ts +6 -0
- package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
- package/dist-lib/components/composed/tree/tree-context.d.ts +4 -0
- package/dist-lib/components/composed/tree/tree-node.d.ts +12 -0
- package/dist-lib/components/composed/tree/tree.d.ts +19 -0
- package/dist-lib/components/composed/tree/type.d.ts +290 -0
- package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -0
- package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
- package/dist-lib/components/composed/tree/use-tree.d.ts +24 -0
- package/dist-lib/components/composed/tree/utils.d.ts +28 -0
- package/dist-lib/components/composed/user-avatar.d.ts +5 -28
- package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
- package/dist-lib/components/user-avatar/audio-wave.d.ts +2 -0
- package/dist-lib/components/user-avatar/component.d.ts +3 -0
- package/dist-lib/components/user-avatar/fallback.d.ts +5 -0
- package/dist-lib/components/user-avatar/index.d.ts +18 -0
- package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
- package/dist-lib/components/user-avatar/type.d.ts +71 -0
- package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
- package/dist-lib/components/user-avatar/use-user-avatar.d.ts +32 -0
- package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +55 -0
- package/dist-lib/components/user-avatar/utils.d.ts +2 -0
- package/dist-lib/index.d.ts +6 -2
- package/dist-lib/index.js +3576 -1366
- package/dist-lib/index.js.map +1 -1
- package/dist-lib/state/exegia-provider.d.ts +51 -0
- package/dist-lib/state/index.d.ts +4 -0
- package/dist-lib/state/store.d.ts +17 -0
- package/package.json +16 -12
- package/src/components/beste/piece/browser-frame.tsx +9 -6
- package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
- package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
- package/src/components/blocks/auth/auth-session-atom.ts +82 -0
- package/src/components/blocks/auth/auth-state-type.ts +97 -0
- package/src/components/blocks/auth/auth-state.ts +52 -0
- package/src/components/blocks/auth/use-auth-state.ts +127 -0
- package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
- package/src/components/blocks/nav/sidebar/__tests__/use-ai-sidebar.test.tsx +310 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
- package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +112 -76
- package/src/components/blocks/nav/sidebar/index.ts +53 -1
- package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
- package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
- package/src/components/blocks/nav/sidebar/type.ts +228 -29
- package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
- package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +455 -225
- package/src/components/blocks/nav/sidebar/utils.ts +29 -1
- package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
- package/src/components/blocks/profile/index.ts +28 -0
- package/src/components/blocks/profile/profile-card-atom.ts +247 -0
- package/src/components/blocks/profile/profile-card-block.tsx +153 -74
- package/src/components/blocks/profile/type.ts +95 -0
- package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
- package/src/components/blocks/profile/use-profile-card.ts +126 -0
- package/src/components/blocks/scaffold/__tests__/scaffold.test.tsx +160 -1
- package/src/components/blocks/scaffold/constants.ts +7 -0
- package/src/components/blocks/scaffold/panel-menu-button.tsx +3 -0
- package/src/components/blocks/scaffold/scaffold-canvas.tsx +48 -5
- package/src/components/blocks/scaffold/scaffold-inspector.tsx +3 -2
- package/src/components/blocks/scaffold/scaffold-main.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-panel.tsx +41 -5
- package/src/components/blocks/scaffold/scaffold-root.tsx +18 -3
- package/src/components/blocks/scaffold/scaffold-sidebar.tsx +1 -1
- package/src/components/blocks/scaffold/scaffold-tab.tsx +45 -7
- package/src/components/blocks/scaffold/type.ts +28 -0
- package/src/components/blocks/scaffold/use-panel-visibility.ts +180 -0
- package/src/components/blocks/scaffold/utils.ts +19 -0
- package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
- package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
- package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
- package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
- package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
- package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
- package/src/components/blocks/shell/animated-panel.tsx +126 -29
- package/src/components/blocks/shell/index.ts +18 -0
- package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
- package/src/components/blocks/shell/shell-layout.tsx +55 -53
- package/src/components/blocks/shell/shell-metrics.ts +79 -0
- package/src/components/blocks/shell/type.ts +130 -3
- package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
- package/src/components/blocks/shell/use-shell-fit.ts +135 -0
- package/src/components/blocks/shell/use-shell-panels.ts +57 -4
- package/src/components/blocks/shell/utils.ts +44 -4
- package/src/components/composed/tree/CLAUDE.md +132 -0
- package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
- package/src/components/composed/tree/__tests__/tree.test.tsx +525 -0
- package/src/components/composed/tree/__tests__/use-tree.test.tsx +333 -0
- package/src/components/composed/tree/constants.ts +60 -0
- package/src/components/composed/tree/index.ts +51 -0
- package/src/components/composed/tree/tree-atom.ts +590 -0
- package/src/components/composed/tree/tree-context.ts +13 -0
- package/src/components/composed/tree/tree-node.tsx +490 -0
- package/src/components/composed/tree/tree.tsx +286 -0
- package/src/components/composed/tree/type.ts +322 -0
- package/src/components/composed/tree/use-tree-dnd.ts +179 -0
- package/src/components/composed/tree/use-tree-state.ts +105 -0
- package/src/components/composed/tree/use-tree.ts +307 -0
- package/src/components/composed/tree/utils.ts +172 -0
- package/src/components/composed/user-avatar.tsx +11 -96
- package/src/components/docs/component-preview.tsx +11 -11
- package/src/components/user-avatar/__tests__/user-avatar.test.tsx +245 -0
- package/src/components/user-avatar/audio-wave.tsx +22 -0
- package/src/components/user-avatar/component.tsx +152 -0
- package/src/components/user-avatar/fallback.tsx +26 -0
- package/src/components/user-avatar/index.ts +34 -0
- package/src/components/user-avatar/presence-badge.tsx +56 -0
- package/src/components/user-avatar/type.ts +85 -0
- package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
- package/src/components/user-avatar/use-user-avatar.ts +144 -0
- package/src/components/user-avatar/user-avatar-atom.ts +198 -0
- package/src/components/user-avatar/utils.ts +8 -0
- package/src/index.css +211 -153
- package/src/index.ts +35 -2
- package/src/state/exegia-provider.tsx +79 -0
- package/src/state/index.ts +4 -0
- package/src/state/store.ts +19 -0
|
@@ -1,57 +1,125 @@
|
|
|
1
|
-
"use client"
|
|
1
|
+
"use client"
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
3
|
+
import { useMemo } from "react"
|
|
4
|
+
import { AnimatePresence } from "motion/react"
|
|
5
|
+
import { cn } from "@/lib/utils"
|
|
6
|
+
import type {
|
|
7
|
+
AISidebarComponentProps,
|
|
8
|
+
AISidebarContextValue,
|
|
9
|
+
AISidebarController,
|
|
10
|
+
AISidebarProps,
|
|
11
|
+
AISidebarViewProps,
|
|
12
|
+
FlatResource,
|
|
13
|
+
} from "./type"
|
|
14
|
+
import { useAISidebar } from "./use-ai-sidebar"
|
|
15
|
+
import { AISidebarContext } from "./sidebar-context"
|
|
7
16
|
import { ResourceRow } from "./sidebar-row"
|
|
8
17
|
|
|
9
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Resource tree for the app rail: nested rows with drag-and-drop reorder,
|
|
20
|
+
* inline rename, per-row action menus and full keyboard navigation.
|
|
21
|
+
*
|
|
22
|
+
* Two ways to drive it. Pass the data and handlers as props and the block
|
|
23
|
+
* owns its state, or hold a `useAISidebar` controller and pass it as
|
|
24
|
+
* `controller` — the same rendering, but select, expand, rename, reorder
|
|
25
|
+
* and menu state are then callable from anywhere in your app.
|
|
26
|
+
*/
|
|
27
|
+
export function AISidebar(props: AISidebarComponentProps) {
|
|
28
|
+
// Two components rather than one: hooks may not be called conditionally,
|
|
29
|
+
// and the controller form has no options to build a fallback controller
|
|
30
|
+
// from. Nobody switches a sidebar between the two forms at runtime.
|
|
31
|
+
return props.controller ? (
|
|
32
|
+
<AISidebarView {...viewProps(props)} controller={props.controller} />
|
|
33
|
+
) : (
|
|
34
|
+
<UncontrolledAISidebar {...props} />
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** The props form: builds its own controller and renders through it. */
|
|
39
|
+
function UncontrolledAISidebar(props: AISidebarProps) {
|
|
40
|
+
const {
|
|
41
|
+
renderIcon: _icon,
|
|
42
|
+
renderMenu: _menu,
|
|
43
|
+
renderActionsTrigger: _trigger,
|
|
44
|
+
ariaLabel: _label,
|
|
45
|
+
className: _className,
|
|
46
|
+
controller: _controller,
|
|
47
|
+
...options
|
|
48
|
+
} = props
|
|
49
|
+
const controller = useAISidebar(options)
|
|
50
|
+
return <AISidebarView {...viewProps(props)} controller={controller} />
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function viewProps(props: AISidebarComponentProps): AISidebarViewProps {
|
|
54
|
+
return {
|
|
55
|
+
renderIcon: props.renderIcon,
|
|
56
|
+
renderMenu: props.renderMenu,
|
|
57
|
+
renderActionsTrigger: props.renderActionsTrigger,
|
|
58
|
+
ariaLabel: props.ariaLabel,
|
|
59
|
+
className: props.className,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Rendering only — every piece of state lives on the controller. */
|
|
64
|
+
function AISidebarView({
|
|
65
|
+
controller,
|
|
10
66
|
renderIcon,
|
|
11
67
|
renderMenu,
|
|
12
68
|
renderActionsTrigger,
|
|
13
69
|
ariaLabel = "Resources",
|
|
14
70
|
className,
|
|
15
|
-
|
|
16
|
-
}
|
|
17
|
-
const {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
71
|
+
}: AISidebarViewProps & { controller: AISidebarController }) {
|
|
72
|
+
const { dnd, hover } = controller
|
|
73
|
+
const { draggingId, dropTarget } = dnd
|
|
74
|
+
|
|
75
|
+
// Rows take the sidebar's id and the stable handlers, never the controller
|
|
76
|
+
// — so this value keeps its identity and a hover or a toggle re-renders
|
|
77
|
+
// only the rows whose own atoms changed. Deps name each function because
|
|
78
|
+
// `dnd` itself carries live drag state and changes identity with it.
|
|
79
|
+
const context = useMemo<AISidebarContextValue>(
|
|
80
|
+
() => ({
|
|
81
|
+
sidebarId: controller.sidebarId,
|
|
82
|
+
hoverLayoutId: hover.layoutId,
|
|
83
|
+
renderIcon,
|
|
84
|
+
renderMenu,
|
|
85
|
+
renderActionsTrigger,
|
|
86
|
+
setRowRef: controller.setRowRef,
|
|
87
|
+
onRowKeyDown: controller.onRowKeyDown,
|
|
88
|
+
onRowHover: hover.onRowHover,
|
|
89
|
+
closeMenu: controller.closeMenu,
|
|
90
|
+
dnd: {
|
|
91
|
+
onDrop: dnd.onDrop,
|
|
92
|
+
onRowDragStart: dnd.onRowDragStart,
|
|
93
|
+
onRowDragEnd: dnd.onRowDragEnd,
|
|
94
|
+
onRowDragOver: dnd.onRowDragOver,
|
|
95
|
+
},
|
|
96
|
+
}),
|
|
97
|
+
[
|
|
98
|
+
controller.sidebarId,
|
|
99
|
+
controller.setRowRef,
|
|
100
|
+
controller.onRowKeyDown,
|
|
101
|
+
controller.closeMenu,
|
|
102
|
+
hover.layoutId,
|
|
103
|
+
hover.onRowHover,
|
|
104
|
+
renderIcon,
|
|
105
|
+
renderMenu,
|
|
106
|
+
renderActionsTrigger,
|
|
107
|
+
dnd.onDrop,
|
|
108
|
+
dnd.onRowDragStart,
|
|
109
|
+
dnd.onRowDragEnd,
|
|
110
|
+
dnd.onRowDragOver,
|
|
111
|
+
]
|
|
112
|
+
)
|
|
45
113
|
|
|
46
114
|
return (
|
|
47
|
-
|
|
115
|
+
<AISidebarContext.Provider value={context}>
|
|
48
116
|
<div
|
|
49
117
|
role="tree"
|
|
50
118
|
aria-label={ariaLabel}
|
|
51
119
|
aria-multiselectable="false"
|
|
52
|
-
onDragOver={
|
|
53
|
-
onDrop={
|
|
54
|
-
onMouseLeave={
|
|
120
|
+
onDragOver={dnd.onRootDragOver}
|
|
121
|
+
onDrop={dnd.onDrop}
|
|
122
|
+
onMouseLeave={hover.clear}
|
|
55
123
|
className={cn(
|
|
56
124
|
"relative min-w-0 flex-col gap-0.5 [overflow-anchor:none] group-data-[state=collapsed]/sidebar:hidden",
|
|
57
125
|
draggingId && "pb-9 select-none",
|
|
@@ -59,40 +127,8 @@ export function AISidebar({
|
|
|
59
127
|
)}
|
|
60
128
|
>
|
|
61
129
|
<AnimatePresence initial={false}>
|
|
62
|
-
{flat.map((row: FlatResource) => (
|
|
63
|
-
<ResourceRow
|
|
64
|
-
key={row.item.id}
|
|
65
|
-
row={row}
|
|
66
|
-
active={selectedId === row.item.id}
|
|
67
|
-
expanded={expandedIds.has(row.item.id)}
|
|
68
|
-
focused={focusedId === row.item.id}
|
|
69
|
-
draggingId={draggingId}
|
|
70
|
-
dropTarget={dropTarget}
|
|
71
|
-
menuOpen={menuOpenId === row.item.id}
|
|
72
|
-
renaming={renamingId === row.item.id}
|
|
73
|
-
hoverActive={hoveredId !== null && !draggingId}
|
|
74
|
-
hoverPill={
|
|
75
|
-
hoveredId === row.item.id && !draggingId && !row.item.disabled
|
|
76
|
-
}
|
|
77
|
-
hoverLayoutId={hoverLayoutId}
|
|
78
|
-
onHoverChange={(hovered) => handleRowHover(row.item.id, hovered)}
|
|
79
|
-
onFocus={() => setFocusedId(row.item.id)}
|
|
80
|
-
onSelect={() => select(row.item.id)}
|
|
81
|
-
onToggle={() => toggle(row.item.id)}
|
|
82
|
-
onKeyDown={(event) => handleKeyDown(event, row)}
|
|
83
|
-
onRenameStart={() => setRenamingId(row.item.id)}
|
|
84
|
-
onRenameCancel={() => setRenamingId(null)}
|
|
85
|
-
onRenameCommit={(label) => commitRename(row, label)}
|
|
86
|
-
onMenuOpenChange={(open) => handleMenuOpenChange(row, open)}
|
|
87
|
-
onDragStart={handleRowDragStart}
|
|
88
|
-
onDragEnd={handleRowDragEnd}
|
|
89
|
-
onDragOver={handleRowDragOver}
|
|
90
|
-
onDrop={handleDrop}
|
|
91
|
-
renderIcon={renderIcon}
|
|
92
|
-
renderMenu={renderMenu}
|
|
93
|
-
renderActionsTrigger={renderActionsTrigger}
|
|
94
|
-
setRef={(node) => setRowRef(row.item.id, node)}
|
|
95
|
-
/>
|
|
130
|
+
{controller.flat.map((row: FlatResource) => (
|
|
131
|
+
<ResourceRow key={row.item.id} row={row} />
|
|
96
132
|
))}
|
|
97
133
|
</AnimatePresence>
|
|
98
134
|
|
|
@@ -107,8 +143,8 @@ export function AISidebar({
|
|
|
107
143
|
)}
|
|
108
144
|
</div>
|
|
109
145
|
<span className="sr-only" aria-live="polite">
|
|
110
|
-
{announcement}
|
|
146
|
+
{controller.announcement}
|
|
111
147
|
</span>
|
|
112
|
-
|
|
148
|
+
</AISidebarContext.Provider>
|
|
113
149
|
)
|
|
114
150
|
}
|
|
@@ -3,7 +3,59 @@ import { MarqueeLabel } from "./marquee-label.tsx"
|
|
|
3
3
|
import { ResourceRow } from "./sidebar-row.tsx"
|
|
4
4
|
import { SidebarIcon } from "./sidebar-icon.tsx"
|
|
5
5
|
export { useAISidebar, type UseAISidebarOptions } from "./use-ai-sidebar.ts"
|
|
6
|
+
export {
|
|
7
|
+
useAISidebarActions,
|
|
8
|
+
useAISidebarState,
|
|
9
|
+
} from "./use-ai-sidebar-state.ts"
|
|
6
10
|
export type * from "./type.ts"
|
|
11
|
+
// The public atom surface. `@internal` atoms (config, handlers, mount, the
|
|
12
|
+
// projections, the owned-* loop guards) stay unexported on purpose —
|
|
13
|
+
// `export *` would make every internal a breaking-change surface for
|
|
14
|
+
// consumer apps.
|
|
15
|
+
export {
|
|
16
|
+
aiSidebarActiveIdAtom,
|
|
17
|
+
aiSidebarAnnouncementAtom,
|
|
18
|
+
aiSidebarDraggingIdAtom,
|
|
19
|
+
aiSidebarDropTargetAtom,
|
|
20
|
+
aiSidebarExpandedIdsAtom,
|
|
21
|
+
aiSidebarFlatAtom,
|
|
22
|
+
aiSidebarFocusedIdAtom,
|
|
23
|
+
aiSidebarHoverActiveAtom,
|
|
24
|
+
aiSidebarHoveredIdAtom,
|
|
25
|
+
aiSidebarItemsAtom,
|
|
26
|
+
aiSidebarMenuOpenIdAtom,
|
|
27
|
+
aiSidebarMovePendingAtom,
|
|
28
|
+
aiSidebarRenamingIdAtom,
|
|
29
|
+
aiSidebarRowDraggingAtom,
|
|
30
|
+
aiSidebarRowDropAtom,
|
|
31
|
+
aiSidebarRowExpandedAtom,
|
|
32
|
+
aiSidebarRowFocusedAtom,
|
|
33
|
+
aiSidebarRowHoveredAtom,
|
|
34
|
+
aiSidebarRowMenuOpenAtom,
|
|
35
|
+
aiSidebarRowRenamingAtom,
|
|
36
|
+
aiSidebarRowSelectedAtom,
|
|
37
|
+
aiSidebarStateAtom,
|
|
38
|
+
cancelAISidebarRenameAtom,
|
|
39
|
+
clearAISidebarHoverAtom,
|
|
40
|
+
closeAISidebarMenuAtom,
|
|
41
|
+
collapseAISidebarRowAtom,
|
|
42
|
+
collapseAllAISidebarRowsAtom,
|
|
43
|
+
endAISidebarDragAtom,
|
|
44
|
+
expandAISidebarRowAtom,
|
|
45
|
+
expandAllAISidebarRowsAtom,
|
|
46
|
+
focusAISidebarRowAtom,
|
|
47
|
+
moveAISidebarRowAtom,
|
|
48
|
+
openAISidebarMenuAtom,
|
|
49
|
+
removeAISidebarInstance,
|
|
50
|
+
renameAISidebarRowAtom,
|
|
51
|
+
resetAISidebarAtom,
|
|
52
|
+
revealAISidebarRowAtom,
|
|
53
|
+
selectAISidebarRowAtom,
|
|
54
|
+
setAISidebarHoveredAtom,
|
|
55
|
+
setAISidebarItemsAtom,
|
|
56
|
+
startAISidebarRenameAtom,
|
|
57
|
+
toggleAISidebarRowAtom,
|
|
58
|
+
} from "./ai-sidebar-atom.ts"
|
|
7
59
|
|
|
8
60
|
export { AISidebar, MarqueeLabel, ResourceRow, SidebarIcon }
|
|
9
61
|
|
|
@@ -11,5 +63,5 @@ export const Sidebar = {
|
|
|
11
63
|
Wrapper: AISidebar,
|
|
12
64
|
MarqueeLabel,
|
|
13
65
|
Row: ResourceRow,
|
|
14
|
-
Icon: SidebarIcon
|
|
66
|
+
Icon: SidebarIcon,
|
|
15
67
|
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext } from "react";
|
|
4
|
+
|
|
5
|
+
import type { AISidebarContextValue } from "./type.ts";
|
|
6
|
+
|
|
7
|
+
export const AISidebarContext = createContext<AISidebarContextValue | null>(
|
|
8
|
+
null
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
export function useAISidebarContext(): AISidebarContextValue {
|
|
12
|
+
const context = useContext(AISidebarContext);
|
|
13
|
+
if (!context) throw new Error("Sidebar rows must render inside <AISidebar>.");
|
|
14
|
+
return context;
|
|
15
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
cloneElement,
|
|
5
|
+
memo,
|
|
6
|
+
useCallback,
|
|
3
7
|
useEffect,
|
|
4
8
|
useRef,
|
|
5
9
|
useState,
|
|
6
10
|
type MouseEvent,
|
|
7
11
|
} from "react"
|
|
12
|
+
import { useAtomValue, useSetAtom } from "jotai"
|
|
8
13
|
import type { ResourceRowProps } from "./type"
|
|
9
14
|
import {
|
|
10
15
|
AnimatePresence,
|
|
@@ -15,6 +20,25 @@ import {
|
|
|
15
20
|
import { ChevronRight, MoreHorizontal, Pencil } from "lucide-react"
|
|
16
21
|
import { SPRING_LAYOUT } from "@/lib/ease.ts"
|
|
17
22
|
import { cn } from "@/lib/utils.ts"
|
|
23
|
+
import {
|
|
24
|
+
aiSidebarHoverActiveAtom,
|
|
25
|
+
aiSidebarRowDraggingAtom,
|
|
26
|
+
aiSidebarRowDropAtom,
|
|
27
|
+
aiSidebarRowExpandedAtom,
|
|
28
|
+
aiSidebarRowFocusedAtom,
|
|
29
|
+
aiSidebarRowHoveredAtom,
|
|
30
|
+
aiSidebarRowMenuOpenAtom,
|
|
31
|
+
aiSidebarRowRenamingAtom,
|
|
32
|
+
aiSidebarRowSelectedAtom,
|
|
33
|
+
cancelAISidebarRenameAtom,
|
|
34
|
+
focusAISidebarRowAtom,
|
|
35
|
+
openAISidebarMenuAtom,
|
|
36
|
+
renameAISidebarRowAtom,
|
|
37
|
+
selectAISidebarRowAtom,
|
|
38
|
+
startAISidebarRenameAtom,
|
|
39
|
+
toggleAISidebarRowAtom,
|
|
40
|
+
} from "./ai-sidebar-atom.ts"
|
|
41
|
+
import { useAISidebarContext } from "./sidebar-context.ts"
|
|
18
42
|
import { canContain } from "./utils"
|
|
19
43
|
import { SidebarIcon } from "./sidebar-icon"
|
|
20
44
|
import { MarqueeLabel } from "./marquee-label"
|
|
@@ -23,7 +47,7 @@ import {
|
|
|
23
47
|
MorphPopoverContent,
|
|
24
48
|
MorphPopoverTrigger,
|
|
25
49
|
} from "@/components/motion/popover-morph.tsx"
|
|
26
|
-
import { Button } from "@/components/ui/button"
|
|
50
|
+
import { Button } from "@/components/ui/button"
|
|
27
51
|
|
|
28
52
|
// Same choreography as SharedLayoutBg: the wrapper only fades/blurs out when
|
|
29
53
|
// the pointer leaves the whole tree; a row-to-row move exits instantly so the
|
|
@@ -41,46 +65,74 @@ const reducedPillVariants: Variants = {
|
|
|
41
65
|
exit: (hoverActive: boolean) => (!hoverActive ? { opacity: 0 } : {}),
|
|
42
66
|
}
|
|
43
67
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
onFocus,
|
|
58
|
-
onKeyDown,
|
|
59
|
-
onMenuOpenChange,
|
|
60
|
-
onRenameCancel,
|
|
61
|
-
onRenameCommit,
|
|
62
|
-
onRenameStart,
|
|
63
|
-
onSelect,
|
|
64
|
-
onToggle,
|
|
65
|
-
hoverActive = false,
|
|
66
|
-
hoverPill = false,
|
|
67
|
-
hoverLayoutId,
|
|
68
|
-
onHoverChange,
|
|
69
|
-
renderIcon,
|
|
70
|
-
renderMenu,
|
|
71
|
-
renderActionsTrigger,
|
|
72
|
-
setRef,
|
|
73
|
-
}: ResourceRowProps) {
|
|
68
|
+
function ResourceRowImpl({ row }: ResourceRowProps) {
|
|
69
|
+
const {
|
|
70
|
+
sidebarId,
|
|
71
|
+
hoverLayoutId,
|
|
72
|
+
renderIcon,
|
|
73
|
+
renderMenu,
|
|
74
|
+
renderActionsTrigger,
|
|
75
|
+
setRowRef,
|
|
76
|
+
onRowKeyDown,
|
|
77
|
+
onRowHover,
|
|
78
|
+
closeMenu,
|
|
79
|
+
dnd,
|
|
80
|
+
} = useAISidebarContext()
|
|
74
81
|
const reduce = useReducedMotion() ?? false
|
|
82
|
+
|
|
83
|
+
// One subscription per field, per row. A row re-renders when its own
|
|
84
|
+
// selection, expansion, focus, rename, menu, drag or hover state changes
|
|
85
|
+
// and sits still while a sibling's does — reading the controller off
|
|
86
|
+
// context instead re-rendered every row on every change.
|
|
87
|
+
const active = useAtomValue(aiSidebarRowSelectedAtom(sidebarId, row.item.id))
|
|
88
|
+
const expanded = useAtomValue(
|
|
89
|
+
aiSidebarRowExpandedAtom(sidebarId, row.item.id)
|
|
90
|
+
)
|
|
91
|
+
const focused = useAtomValue(aiSidebarRowFocusedAtom(sidebarId, row.item.id))
|
|
92
|
+
const renaming = useAtomValue(
|
|
93
|
+
aiSidebarRowRenamingAtom(sidebarId, row.item.id)
|
|
94
|
+
)
|
|
95
|
+
const menuOpen = useAtomValue(
|
|
96
|
+
aiSidebarRowMenuOpenAtom(sidebarId, row.item.id)
|
|
97
|
+
)
|
|
98
|
+
const isDragging = useAtomValue(
|
|
99
|
+
aiSidebarRowDraggingAtom(sidebarId, row.item.id)
|
|
100
|
+
)
|
|
101
|
+
const dropPosition = useAtomValue(
|
|
102
|
+
aiSidebarRowDropAtom(sidebarId, row.item.id)
|
|
103
|
+
)
|
|
104
|
+
const hoverActive = useAtomValue(aiSidebarHoverActiveAtom(sidebarId))
|
|
105
|
+
const hoverPill =
|
|
106
|
+
useAtomValue(aiSidebarRowHoveredAtom(sidebarId, row.item.id)) &&
|
|
107
|
+
!row.item.disabled
|
|
108
|
+
|
|
109
|
+
const select = useSetAtom(selectAISidebarRowAtom(sidebarId))
|
|
110
|
+
const toggleExpanded = useSetAtom(toggleAISidebarRowAtom(sidebarId))
|
|
111
|
+
const setFocusedRow = useSetAtom(focusAISidebarRowAtom(sidebarId))
|
|
112
|
+
const startRename = useSetAtom(startAISidebarRenameAtom(sidebarId))
|
|
113
|
+
const cancelRename = useSetAtom(cancelAISidebarRenameAtom(sidebarId))
|
|
114
|
+
const rename = useSetAtom(renameAISidebarRowAtom(sidebarId))
|
|
115
|
+
const openMenu = useSetAtom(openAISidebarMenuAtom(sidebarId))
|
|
116
|
+
|
|
117
|
+
const onMenuOpenChange = useCallback(
|
|
118
|
+
(open: boolean) => {
|
|
119
|
+
if (open) openMenu(row.item.id)
|
|
120
|
+
else closeMenu()
|
|
121
|
+
},
|
|
122
|
+
[closeMenu, openMenu, row.item.id]
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
const setRef = useCallback(
|
|
126
|
+
(node: HTMLDivElement | null) => setRowRef(row.item.id, node),
|
|
127
|
+
[row.item.id, setRowRef]
|
|
128
|
+
)
|
|
129
|
+
|
|
75
130
|
const [hovered, setHovered] = useState(false)
|
|
76
131
|
const inputRef = useRef<HTMLInputElement>(null)
|
|
77
132
|
const skipRenameBlurRef = useRef(false)
|
|
78
133
|
const draggedRef = useRef(false)
|
|
79
134
|
const [draft, setDraft] = useState(row.item.label)
|
|
80
135
|
const acceptsChildren = canContain(row.item)
|
|
81
|
-
const isDragging = draggingId === row.item.id
|
|
82
|
-
const dropPosition =
|
|
83
|
-
dropTarget?.id === row.item.id ? dropTarget.position : null
|
|
84
136
|
|
|
85
137
|
useEffect(() => {
|
|
86
138
|
if (!renaming) return
|
|
@@ -96,14 +148,14 @@ export function ResourceRow({
|
|
|
96
148
|
close: () => onMenuOpenChange(false),
|
|
97
149
|
rename: () => {
|
|
98
150
|
onMenuOpenChange(false)
|
|
99
|
-
|
|
151
|
+
startRename(row.item.id)
|
|
100
152
|
},
|
|
101
153
|
}) ?? (
|
|
102
154
|
<button
|
|
103
155
|
type="button"
|
|
104
156
|
onClick={() => {
|
|
105
157
|
onMenuOpenChange(false)
|
|
106
|
-
|
|
158
|
+
startRename(row.item.id)
|
|
107
159
|
}}
|
|
108
160
|
className="flex h-8 w-full items-center gap-2 rounded-lg px-2.5 text-left text-xs text-foreground transition-colors outline-none hover:bg-muted focus-visible:bg-muted focus-visible:ring-2 focus-visible:ring-ring"
|
|
109
161
|
>
|
|
@@ -127,8 +179,8 @@ export function ResourceRow({
|
|
|
127
179
|
data-menu-open={menuOpen || undefined}
|
|
128
180
|
data-drop={dropPosition ?? undefined}
|
|
129
181
|
data-dragging={isDragging || undefined}
|
|
130
|
-
onFocus={
|
|
131
|
-
onKeyDown={
|
|
182
|
+
onFocus={() => setFocusedRow(row.item.id)}
|
|
183
|
+
onKeyDown={(event) => onRowKeyDown(event, row)}
|
|
132
184
|
onClick={(event) => {
|
|
133
185
|
if (
|
|
134
186
|
event.defaultPrevented ||
|
|
@@ -137,34 +189,34 @@ export function ResourceRow({
|
|
|
137
189
|
row.item.disabled
|
|
138
190
|
)
|
|
139
191
|
return
|
|
140
|
-
if (acceptsChildren)
|
|
141
|
-
else
|
|
192
|
+
if (acceptsChildren) toggleExpanded(row.item.id)
|
|
193
|
+
else select(row.item.id)
|
|
142
194
|
}}
|
|
143
195
|
onDoubleClick={(event) => {
|
|
144
196
|
if (acceptsChildren || row.item.disabled) return
|
|
145
197
|
event.preventDefault()
|
|
146
|
-
|
|
198
|
+
startRename(row.item.id)
|
|
147
199
|
}}
|
|
148
200
|
onMouseEnter={() => {
|
|
149
201
|
setHovered(true)
|
|
150
|
-
|
|
202
|
+
onRowHover(row.item.id, true)
|
|
151
203
|
}}
|
|
152
204
|
onMouseLeave={() => {
|
|
153
205
|
setHovered(false)
|
|
154
|
-
|
|
206
|
+
onRowHover(row.item.id, false)
|
|
155
207
|
}}
|
|
156
208
|
onDragStartCapture={(event) => {
|
|
157
209
|
draggedRef.current = true
|
|
158
|
-
|
|
210
|
+
dnd.onRowDragStart(event, row.item.id)
|
|
159
211
|
}}
|
|
160
212
|
onDragEndCapture={() => {
|
|
161
|
-
|
|
213
|
+
dnd.onRowDragEnd()
|
|
162
214
|
requestAnimationFrame(() => {
|
|
163
215
|
draggedRef.current = false
|
|
164
216
|
})
|
|
165
217
|
}}
|
|
166
|
-
onDragOver={(event) =>
|
|
167
|
-
onDrop={onDrop}
|
|
218
|
+
onDragOver={(event) => dnd.onRowDragOver(event, row)}
|
|
219
|
+
onDrop={dnd.onDrop}
|
|
168
220
|
className={cn(
|
|
169
221
|
"group/resource relative flex min-h-9 min-w-0 cursor-pointer items-center gap-2.5 rounded-md pr-3 text-sm outline-none",
|
|
170
222
|
"text-muted-foreground transition-[color,background-color,scale] duration-150 ease-smooth-out hover:text-foreground motion-reduce:transition-none",
|
|
@@ -226,17 +278,17 @@ export function ResourceRow({
|
|
|
226
278
|
onClick={(event) => event.stopPropagation()}
|
|
227
279
|
onDoubleClick={(event) => event.stopPropagation()}
|
|
228
280
|
onBlur={() => {
|
|
229
|
-
if (!skipRenameBlurRef.current)
|
|
281
|
+
if (!skipRenameBlurRef.current) rename(row.item.id, draft)
|
|
230
282
|
}}
|
|
231
283
|
onKeyDown={(event) => {
|
|
232
284
|
event.stopPropagation()
|
|
233
285
|
if (event.key === "Enter") {
|
|
234
286
|
skipRenameBlurRef.current = true
|
|
235
|
-
|
|
287
|
+
rename(row.item.id, draft)
|
|
236
288
|
}
|
|
237
289
|
if (event.key === "Escape") {
|
|
238
290
|
skipRenameBlurRef.current = true
|
|
239
|
-
|
|
291
|
+
cancelRename()
|
|
240
292
|
}
|
|
241
293
|
}}
|
|
242
294
|
className="relative h-7 min-w-0 flex-1 text-sm text-foreground outline-none focus-visible:ring-0 focus-visible:ring-ring"
|
|
@@ -307,3 +359,20 @@ export function ResourceRow({
|
|
|
307
359
|
</motion.div>
|
|
308
360
|
)
|
|
309
361
|
}
|
|
362
|
+
|
|
363
|
+
/** `flat` is rebuilt whenever expansion changes, so its `FlatResource`
|
|
364
|
+
* wrappers are fresh objects even for rows nothing happened to. The
|
|
365
|
+
* comparator looks through the wrapper at what a row actually renders from —
|
|
366
|
+
* its item (stable unless the data changed), depth and parent — so a sibling
|
|
367
|
+
* opening does not walk every row. Each row's own state reaches it through
|
|
368
|
+
* its atoms instead. */
|
|
369
|
+
function sameRow(prev: ResourceRowProps, next: ResourceRowProps): boolean {
|
|
370
|
+
return (
|
|
371
|
+
prev.row.item === next.row.item &&
|
|
372
|
+
prev.row.depth === next.row.depth &&
|
|
373
|
+
prev.row.parentId === next.row.parentId
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
export const ResourceRow = memo(ResourceRowImpl, sameRow)
|
|
378
|
+
ResourceRow.displayName = "ResourceRow"
|