@exegia/corpora-ui 0.20.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.
Files changed (123) hide show
  1. package/dist-lib/components/blocks/auth/__tests__/auth-state-atom.test.d.ts +1 -0
  2. package/dist-lib/components/blocks/auth/auth-flow-atom.d.ts +77 -0
  3. package/dist-lib/components/blocks/auth/auth-session-atom.d.ts +28 -0
  4. package/dist-lib/components/blocks/auth/auth-state-type.d.ts +71 -0
  5. package/dist-lib/components/blocks/auth/auth-state.d.ts +9 -0
  6. package/dist-lib/components/blocks/auth/use-auth-state.d.ts +52 -0
  7. package/dist-lib/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.d.ts +1 -0
  8. package/dist-lib/components/blocks/nav/sidebar/ai-sidebar-atom.d.ts +217 -0
  9. package/dist-lib/components/blocks/nav/sidebar/index.d.ts +3 -1
  10. package/dist-lib/components/blocks/nav/sidebar/sidebar-context.d.ts +3 -0
  11. package/dist-lib/components/blocks/nav/sidebar/sidebar-row.d.ts +3 -1
  12. package/dist-lib/components/blocks/nav/sidebar/type.d.ts +109 -29
  13. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar-state.d.ts +29 -0
  14. package/dist-lib/components/blocks/nav/sidebar/use-ai-sidebar.d.ts +6 -1
  15. package/dist-lib/components/blocks/nav/sidebar/utils.d.ts +1 -1
  16. package/dist-lib/components/blocks/profile/index.d.ts +7 -0
  17. package/dist-lib/components/blocks/profile/profile-card-atom.d.ts +68 -0
  18. package/dist-lib/components/blocks/profile/profile-card-block.d.ts +28 -39
  19. package/dist-lib/components/blocks/profile/type.d.ts +80 -0
  20. package/dist-lib/components/blocks/profile/use-profile-card-state.d.ts +24 -0
  21. package/dist-lib/components/blocks/profile/use-profile-card.d.ts +34 -0
  22. package/dist-lib/components/blocks/shell/__tests__/shell-fit-atom.test.d.ts +1 -0
  23. package/dist-lib/components/blocks/shell/__tests__/shell-metrics.test.d.ts +1 -0
  24. package/dist-lib/components/blocks/shell/animated-panel-provider.d.ts +1 -1
  25. package/dist-lib/components/blocks/shell/animated-panel.d.ts +1 -1
  26. package/dist-lib/components/blocks/shell/index.d.ts +4 -0
  27. package/dist-lib/components/blocks/shell/shell-fit-atom.d.ts +68 -0
  28. package/dist-lib/components/blocks/shell/shell-metrics.d.ts +54 -0
  29. package/dist-lib/components/blocks/shell/type.d.ts +115 -3
  30. package/dist-lib/components/blocks/shell/use-shell-fit-state.d.ts +25 -0
  31. package/dist-lib/components/blocks/shell/use-shell-fit.d.ts +14 -0
  32. package/dist-lib/components/blocks/shell/use-shell-panels.d.ts +12 -1
  33. package/dist-lib/components/blocks/shell/utils.d.ts +32 -0
  34. package/dist-lib/components/composed/tree/__tests__/tree-atom.test.d.ts +1 -0
  35. package/dist-lib/components/composed/tree/constants.d.ts +4 -4
  36. package/dist-lib/components/composed/tree/index.d.ts +3 -1
  37. package/dist-lib/components/composed/tree/tree-atom.d.ts +160 -0
  38. package/dist-lib/components/composed/tree/tree-node.d.ts +6 -1
  39. package/dist-lib/components/composed/tree/type.d.ts +116 -18
  40. package/dist-lib/components/composed/tree/use-tree-dnd.d.ts +18 -7
  41. package/dist-lib/components/composed/tree/use-tree-state.d.ts +25 -0
  42. package/dist-lib/components/composed/tree/use-tree.d.ts +4 -0
  43. package/dist-lib/components/composed/user-avatar.d.ts +5 -28
  44. package/dist-lib/components/user-avatar/__tests__/user-avatar.test.d.ts +1 -0
  45. package/dist-lib/components/user-avatar/component.d.ts +1 -8
  46. package/dist-lib/components/user-avatar/index.d.ts +5 -0
  47. package/dist-lib/components/user-avatar/presence-badge.d.ts +16 -0
  48. package/dist-lib/components/user-avatar/type.d.ts +45 -0
  49. package/dist-lib/components/user-avatar/use-user-avatar-state.d.ts +26 -0
  50. package/dist-lib/components/user-avatar/use-user-avatar.d.ts +32 -0
  51. package/dist-lib/components/user-avatar/user-avatar-atom.d.ts +55 -0
  52. package/dist-lib/index.d.ts +5 -2
  53. package/dist-lib/index.js +3004 -1796
  54. package/dist-lib/index.js.map +1 -1
  55. package/dist-lib/state/exegia-provider.d.ts +51 -0
  56. package/dist-lib/state/index.d.ts +4 -0
  57. package/dist-lib/state/store.d.ts +17 -0
  58. package/package.json +15 -12
  59. package/src/components/blocks/auth/__tests__/auth-state-atom.test.tsx +247 -0
  60. package/src/components/blocks/auth/auth-flow-atom.ts +238 -0
  61. package/src/components/blocks/auth/auth-session-atom.ts +82 -0
  62. package/src/components/blocks/auth/auth-state-type.ts +97 -0
  63. package/src/components/blocks/auth/auth-state.ts +52 -0
  64. package/src/components/blocks/auth/use-auth-state.ts +127 -0
  65. package/src/components/blocks/nav/sidebar/__tests__/ai-sidebar-atom.test.tsx +325 -0
  66. package/src/components/blocks/nav/sidebar/ai-sidebar-atom.ts +698 -0
  67. package/src/components/blocks/nav/sidebar/ai-sidebar.tsx +48 -42
  68. package/src/components/blocks/nav/sidebar/index.ts +53 -1
  69. package/src/components/blocks/nav/sidebar/sidebar-context.ts +15 -0
  70. package/src/components/blocks/nav/sidebar/sidebar-row.tsx +119 -50
  71. package/src/components/blocks/nav/sidebar/type.ts +125 -30
  72. package/src/components/blocks/nav/sidebar/use-ai-sidebar-state.ts +123 -0
  73. package/src/components/blocks/nav/sidebar/use-ai-sidebar.ts +301 -248
  74. package/src/components/blocks/nav/sidebar/utils.ts +1 -1
  75. package/src/components/blocks/profile/__tests__/profile-card-block.test.tsx +194 -0
  76. package/src/components/blocks/profile/index.ts +28 -0
  77. package/src/components/blocks/profile/profile-card-atom.ts +247 -0
  78. package/src/components/blocks/profile/profile-card-block.tsx +153 -74
  79. package/src/components/blocks/profile/type.ts +95 -0
  80. package/src/components/blocks/profile/use-profile-card-state.ts +67 -0
  81. package/src/components/blocks/profile/use-profile-card.ts +126 -0
  82. package/src/components/blocks/shell/__tests__/shell-fit-atom.test.tsx +360 -0
  83. package/src/components/blocks/shell/__tests__/shell-layout.test.tsx +192 -3
  84. package/src/components/blocks/shell/__tests__/shell-metrics.test.ts +108 -0
  85. package/src/components/blocks/shell/animated-panel-inset.tsx +5 -1
  86. package/src/components/blocks/shell/animated-panel-provider.tsx +77 -11
  87. package/src/components/blocks/shell/animated-panel-trigger.tsx +4 -0
  88. package/src/components/blocks/shell/animated-panel.tsx +126 -29
  89. package/src/components/blocks/shell/index.ts +18 -0
  90. package/src/components/blocks/shell/shell-fit-atom.ts +243 -0
  91. package/src/components/blocks/shell/shell-layout.tsx +55 -53
  92. package/src/components/blocks/shell/shell-metrics.ts +79 -0
  93. package/src/components/blocks/shell/type.ts +130 -3
  94. package/src/components/blocks/shell/use-shell-fit-state.ts +49 -0
  95. package/src/components/blocks/shell/use-shell-fit.ts +135 -0
  96. package/src/components/blocks/shell/use-shell-panels.ts +57 -4
  97. package/src/components/blocks/shell/utils.ts +44 -4
  98. package/src/components/composed/tree/CLAUDE.md +132 -0
  99. package/src/components/composed/tree/__tests__/tree-atom.test.tsx +217 -0
  100. package/src/components/composed/tree/__tests__/tree.test.tsx +162 -37
  101. package/src/components/composed/tree/__tests__/use-tree.test.tsx +2 -1
  102. package/src/components/composed/tree/constants.ts +4 -4
  103. package/src/components/composed/tree/index.ts +38 -0
  104. package/src/components/composed/tree/tree-atom.ts +590 -0
  105. package/src/components/composed/tree/tree-node.tsx +117 -78
  106. package/src/components/composed/tree/tree.tsx +24 -16
  107. package/src/components/composed/tree/type.ts +125 -15
  108. package/src/components/composed/tree/use-tree-dnd.ts +82 -51
  109. package/src/components/composed/tree/use-tree-state.ts +105 -0
  110. package/src/components/composed/tree/use-tree.ts +185 -184
  111. package/src/components/composed/user-avatar.tsx +11 -99
  112. package/src/components/user-avatar/__tests__/user-avatar.test.tsx +245 -0
  113. package/src/components/user-avatar/component.tsx +121 -22
  114. package/src/components/user-avatar/index.ts +19 -0
  115. package/src/components/user-avatar/presence-badge.tsx +56 -0
  116. package/src/components/user-avatar/type.ts +50 -0
  117. package/src/components/user-avatar/use-user-avatar-state.ts +60 -0
  118. package/src/components/user-avatar/use-user-avatar.ts +144 -0
  119. package/src/components/user-avatar/user-avatar-atom.ts +198 -0
  120. package/src/index.ts +34 -2
  121. package/src/state/exegia-provider.tsx +79 -0
  122. package/src/state/index.ts +4 -0
  123. package/src/state/store.ts +19 -0
@@ -1,39 +1,62 @@
1
1
  "use client"
2
2
 
3
3
  import * as React from "react"
4
+ import { useAtomValue, useSetAtom, useStore } from "jotai"
4
5
 
6
+ import {
7
+ endTreeDragAtom,
8
+ moveTreeNodeAtom,
9
+ treeCanMoveAtom,
10
+ treeDraggedIdAtom,
11
+ treeDropTargetAtom,
12
+ treeItemsAtom,
13
+ } from "./tree-atom"
5
14
  import type {
6
15
  TreeDndContextValue,
16
+ TreeDndHandlers,
7
17
  TreeDropPosition,
18
+ TreeInstanceId,
8
19
  TreeNode,
9
- UseTreeOptions,
10
20
  } from "./type"
11
21
  import { containsNode, findNode, locateNode } from "./utils"
12
22
 
13
- /** How far into a folder row the pointer must sit (from either edge) to
14
- * read as "inside" rather than "before"/"after". */
23
+ /** How far into a folder row the pointer must sit (from either edge) to read
24
+ * as "inside" rather than "before"/"after". */
15
25
  const EDGE_RATIO = 0.25
16
26
 
17
- /** Owns drag state for the `files` variant: which row is in flight, which
18
- * row the pointer rests on and where the drop would land. Everything is
19
- * component state — `dataTransfer` is set for interop but never read, so
20
- * the flow also works where the store is unavailable (tests, some
21
- * webviews). Without `onMove` every handler is inert. */
22
- export function useTreeDnd(
23
- items: readonly TreeNode[],
24
- onMove: UseTreeOptions["onMove"]
25
- ): TreeDndContextValue {
26
- const enabled = onMove !== undefined
27
- const [draggedId, setDraggedId] = React.useState<string | null>(null)
28
- const [dropTarget, setDropTarget] = React.useState<{
29
- id: string
30
- position: TreeDropPosition
31
- } | null>(null)
27
+ /**
28
+ * The drag handlers for the `files` variant, stable for the life of the tree.
29
+ *
30
+ * Nothing here subscribes to drag state: items, the dragged id and the drop
31
+ * target are read out of the store when a handler runs. That is what lets
32
+ * every row share one handler object without re-rendering when a drag moves —
33
+ * and it keeps `useTree` from subscribing to data it also projects from
34
+ * props, which would loop on an inline `items` array.
35
+ *
36
+ * Inert unless the tree `canMove`. `dataTransfer` is set for interop but
37
+ * never read, so the flow also works where the store is unavailable (tests,
38
+ * some webviews).
39
+ */
40
+ export function useTreeDndHandlers(treeId: TreeInstanceId): TreeDndHandlers {
41
+ const store = useStore()
42
+ const enabled = useAtomValue(treeCanMoveAtom(treeId))
43
+ const setDraggedId = useSetAtom(treeDraggedIdAtom(treeId))
44
+ const setDropTarget = useSetAtom(treeDropTargetAtom(treeId))
45
+ const move = useSetAtom(moveTreeNodeAtom(treeId))
46
+ const reset = useSetAtom(endTreeDragAtom(treeId))
32
47
 
33
- const reset = React.useCallback(() => {
34
- setDraggedId(null)
35
- setDropTarget(null)
36
- }, [])
48
+ const readItems = React.useCallback(
49
+ () => store.get(treeItemsAtom(treeId)),
50
+ [store, treeId]
51
+ )
52
+ const readDraggedId = React.useCallback(
53
+ () => store.get(treeDraggedIdAtom(treeId)),
54
+ [store, treeId]
55
+ )
56
+ const readDropTarget = React.useCallback(
57
+ () => store.get(treeDropTargetAtom(treeId)),
58
+ [store, treeId]
59
+ )
37
60
 
38
61
  const onRowDragStart = React.useCallback(
39
62
  (event: React.DragEvent, id: string) => {
@@ -45,14 +68,15 @@ export function useTreeDnd(
45
68
  event.dataTransfer.setData("text/plain", id)
46
69
  }
47
70
  },
48
- [enabled]
71
+ [enabled, setDraggedId]
49
72
  )
50
73
 
51
74
  const onRowDragOver = React.useCallback(
52
75
  (event: React.DragEvent, node: TreeNode) => {
76
+ const draggedId = readDraggedId()
53
77
  if (!enabled || draggedId === null) return
54
78
  // A node never drops onto itself or into its own subtree.
55
- if (containsNode(items, draggedId, node.id)) return
79
+ if (containsNode(readItems(), draggedId, node.id)) return
56
80
  event.preventDefault()
57
81
  event.stopPropagation()
58
82
  if (event.dataTransfer) event.dataTransfer.dropEffect = "move"
@@ -60,8 +84,8 @@ export function useTreeDnd(
60
84
  const rect = event.currentTarget.getBoundingClientRect()
61
85
  const ratio =
62
86
  rect.height > 0 ? (event.clientY - rect.top) / rect.height : 0.5
63
- // Any node carrying a children array is a folder — an empty one
64
- // still accepts drops "inside".
87
+ // Any node carrying a children array is a folder — an empty one still
88
+ // accepts drops "inside".
65
89
  const folder = node.children !== undefined
66
90
  const position: TreeDropPosition = folder
67
91
  ? ratio < EDGE_RATIO
@@ -78,33 +102,38 @@ export function useTreeDnd(
78
102
  : { id: node.id, position }
79
103
  )
80
104
  },
81
- [enabled, draggedId, items]
105
+ [enabled, readDraggedId, readItems, setDropTarget]
82
106
  )
83
107
 
84
- const onRowDragLeave = React.useCallback((event: React.DragEvent) => {
85
- // Leaving for a child of the same row still counts as hovering it.
86
- const next = event.relatedTarget as Node | null
87
- if (next && event.currentTarget.contains(next)) return
88
- setDropTarget(null)
89
- }, [])
108
+ const onRowDragLeave = React.useCallback(
109
+ (event: React.DragEvent) => {
110
+ // Leaving for a child of the same row still counts as hovering it.
111
+ const next = event.relatedTarget as Node | null
112
+ if (next && event.currentTarget.contains(next)) return
113
+ setDropTarget(null)
114
+ },
115
+ [setDropTarget]
116
+ )
90
117
 
91
118
  const onRowDrop = React.useCallback(
92
119
  (event: React.DragEvent, node: TreeNode) => {
93
120
  if (!enabled) return
94
121
  event.preventDefault()
95
122
  event.stopPropagation()
96
- const target = dropTarget
123
+ const draggedId = readDraggedId()
124
+ const target = readDropTarget()
97
125
  if (draggedId === null || target === null || target.id !== node.id) {
98
126
  reset()
99
127
  return
100
128
  }
129
+ const items = readItems()
101
130
 
102
131
  if (target.position === "inside") {
103
- // Append at the end of the folder, sized without the dragged node
104
- // (it may already live there).
132
+ // Append at the end of the folder, sized without the dragged node (it
133
+ // may already live there).
105
134
  const children = node.children ?? []
106
135
  const index = children.filter((child) => child.id !== draggedId).length
107
- onMove?.(draggedId, node.id, index)
136
+ move(draggedId, node.id, index)
108
137
  } else {
109
138
  const location = locateNode(items, node.id)
110
139
  if (location) {
@@ -115,34 +144,36 @@ export function useTreeDnd(
115
144
  ).filter((sibling) => sibling.id !== draggedId)
116
145
  const at = siblings.findIndex((sibling) => sibling.id === node.id)
117
146
  const index = target.position === "after" ? at + 1 : at
118
- onMove?.(draggedId, location.parentId, index)
147
+ move(draggedId, location.parentId, index)
119
148
  }
120
149
  }
121
150
  reset()
122
151
  },
123
- [enabled, draggedId, dropTarget, items, onMove, reset]
152
+ [enabled, move, readDraggedId, readDropTarget, readItems, reset]
124
153
  )
125
154
 
126
155
  return React.useMemo(
127
156
  () => ({
128
157
  enabled,
129
- draggedId,
130
- dropTarget,
131
158
  onRowDragStart,
132
159
  onRowDragOver,
133
160
  onRowDragLeave,
134
161
  onRowDrop,
135
162
  onRowDragEnd: reset,
136
163
  }),
137
- [
138
- enabled,
139
- draggedId,
140
- dropTarget,
141
- onRowDragStart,
142
- onRowDragOver,
143
- onRowDragLeave,
144
- onRowDrop,
145
- reset,
146
- ]
164
+ [enabled, onRowDragStart, onRowDragOver, onRowDragLeave, onRowDrop, reset]
165
+ )
166
+ }
167
+
168
+ /** The handlers plus live drag state, for `TreeController.dnd`. Rows take the
169
+ * handlers alone and read the state per node instead. */
170
+ export function useTreeDnd(treeId: TreeInstanceId): TreeDndContextValue {
171
+ const handlers = useTreeDndHandlers(treeId)
172
+ const draggedId = useAtomValue(treeDraggedIdAtom(treeId))
173
+ const dropTarget = useAtomValue(treeDropTargetAtom(treeId))
174
+
175
+ return React.useMemo(
176
+ () => ({ ...handlers, draggedId, dropTarget }),
177
+ [handlers, draggedId, dropTarget]
147
178
  )
148
179
  }
@@ -0,0 +1,105 @@
1
+ "use client"
2
+
3
+ import * as React from "react"
4
+ import { useAtomValue, useSetAtom } from "jotai"
5
+
6
+ import {
7
+ cancelTreeRenameAtom,
8
+ collapseAllTreeNodesAtom,
9
+ collapseTreeNodeAtom,
10
+ expandAllTreeNodesAtom,
11
+ expandTreeNodeAtom,
12
+ moveTreeNodeAtom,
13
+ renameTreeNodeAtom,
14
+ resetTreeAtom,
15
+ revealTreeNodeAtom,
16
+ selectTreeNodeAtom,
17
+ setTreeCollapsedAtom,
18
+ setTreeItemsAtom,
19
+ startTreeRenameAtom,
20
+ toggleTreeCollapsedAtom,
21
+ toggleTreeNodeAtom,
22
+ treeStateAtom,
23
+ } from "./tree-atom"
24
+ import type { TreeActions, TreeInstanceId, TreeState } from "./type"
25
+
26
+ /**
27
+ * Read the tree registered under `treeId` from anywhere below
28
+ * `ExegiaProvider` — no controller, no props, no provider of its own.
29
+ *
30
+ * ```tsx
31
+ * const { collapsed, activeId } = useTreeState("app-nav")
32
+ * ```
33
+ *
34
+ * This returns the whole state object, so the caller re-renders on any
35
+ * change. A component that reads one field should subscribe to that field's
36
+ * atom instead: `useAtomValue(treeCollapsedAtom("app-nav"))`.
37
+ */
38
+ export function useTreeState(treeId: TreeInstanceId): TreeState {
39
+ return useAtomValue(treeStateAtom(treeId))
40
+ }
41
+
42
+ /**
43
+ * Drive the tree registered under `treeId` from anywhere. Writes only — the
44
+ * caller never re-renders when the tree changes, so this is what a command
45
+ * palette, a keyboard shortcut or a route guard should reach for.
46
+ *
47
+ * ```tsx
48
+ * const nav = useTreeActions("app-nav")
49
+ * <Button onClick={nav.collapseAll}>Collapse all</Button>
50
+ * ```
51
+ */
52
+ export function useTreeActions(treeId: TreeInstanceId): TreeActions {
53
+ const expand = useSetAtom(expandTreeNodeAtom(treeId))
54
+ const collapse = useSetAtom(collapseTreeNodeAtom(treeId))
55
+ const toggleExpanded = useSetAtom(toggleTreeNodeAtom(treeId))
56
+ const expandAll = useSetAtom(expandAllTreeNodesAtom(treeId))
57
+ const collapseAll = useSetAtom(collapseAllTreeNodesAtom(treeId))
58
+ const reveal = useSetAtom(revealTreeNodeAtom(treeId))
59
+ const setCollapsed = useSetAtom(setTreeCollapsedAtom(treeId))
60
+ const toggleCollapsed = useSetAtom(toggleTreeCollapsedAtom(treeId))
61
+ const select = useSetAtom(selectTreeNodeAtom(treeId))
62
+ const startRename = useSetAtom(startTreeRenameAtom(treeId))
63
+ const cancelRename = useSetAtom(cancelTreeRenameAtom(treeId))
64
+ const rename = useSetAtom(renameTreeNodeAtom(treeId))
65
+ const move = useSetAtom(moveTreeNodeAtom(treeId))
66
+ const setItems = useSetAtom(setTreeItemsAtom(treeId))
67
+ const reset = useSetAtom(resetTreeAtom(treeId))
68
+
69
+ return React.useMemo(
70
+ () => ({
71
+ expand,
72
+ collapse,
73
+ toggleExpanded,
74
+ expandAll,
75
+ collapseAll,
76
+ reveal,
77
+ setCollapsed,
78
+ toggleCollapsed,
79
+ select,
80
+ startRename,
81
+ cancelRename,
82
+ rename,
83
+ move,
84
+ setItems,
85
+ reset,
86
+ }),
87
+ [
88
+ expand,
89
+ collapse,
90
+ toggleExpanded,
91
+ expandAll,
92
+ collapseAll,
93
+ reveal,
94
+ setCollapsed,
95
+ toggleCollapsed,
96
+ select,
97
+ startRename,
98
+ cancelRename,
99
+ rename,
100
+ move,
101
+ setItems,
102
+ reset,
103
+ ]
104
+ )
105
+ }