@vertesia/ui 0.78.0 → 0.79.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 (212) hide show
  1. package/lib/esm/core/components/SelectList.js +18 -13
  2. package/lib/esm/core/components/SelectList.js.map +1 -1
  3. package/lib/esm/core/components/SidePanel.js +1 -1
  4. package/lib/esm/core/components/SidePanel.js.map +1 -1
  5. package/lib/esm/core/components/shadcn/filters/filterBar.js +39 -12
  6. package/lib/esm/core/components/shadcn/filters/filterBar.js.map +1 -1
  7. package/lib/esm/core/components/shadcn/index.js +1 -0
  8. package/lib/esm/core/components/shadcn/index.js.map +1 -1
  9. package/lib/esm/core/components/shadcn/resizeable.js +15 -0
  10. package/lib/esm/core/components/shadcn/resizeable.js.map +1 -0
  11. package/lib/esm/core/components/shadcn/tabs.js +11 -6
  12. package/lib/esm/core/components/shadcn/tabs.js.map +1 -1
  13. package/lib/esm/core/components/table/index.js +1 -1
  14. package/lib/esm/core/components/table/index.js.map +1 -1
  15. package/lib/esm/features/facets/CollectionsFacetsNav.js +66 -0
  16. package/lib/esm/features/facets/CollectionsFacetsNav.js.map +1 -0
  17. package/lib/esm/features/facets/DocumentsFacetsNav.js +19 -7
  18. package/lib/esm/features/facets/DocumentsFacetsNav.js.map +1 -1
  19. package/lib/esm/features/facets/EnvironmentFacet.js +1 -1
  20. package/lib/esm/features/facets/EnvironmentFacet.js.map +1 -1
  21. package/lib/esm/features/facets/InteractionsFacetsNav.js +82 -0
  22. package/lib/esm/features/facets/InteractionsFacetsNav.js.map +1 -0
  23. package/lib/esm/features/facets/PromptsFacetsNav.js +80 -0
  24. package/lib/esm/features/facets/PromptsFacetsNav.js.map +1 -0
  25. package/lib/esm/features/facets/RunsFacetsNav.js +28 -6
  26. package/lib/esm/features/facets/RunsFacetsNav.js.map +1 -1
  27. package/lib/esm/features/facets/WorkflowExecutionsFacetsNav.js +7 -5
  28. package/lib/esm/features/facets/WorkflowExecutionsFacetsNav.js.map +1 -1
  29. package/lib/esm/features/facets/index.js +10 -8
  30. package/lib/esm/features/facets/index.js.map +1 -1
  31. package/lib/esm/features/facets/utils/SearchInterface.js +2 -0
  32. package/lib/esm/features/facets/utils/SearchInterface.js.map +1 -0
  33. package/lib/esm/features/facets/utils/StringFacet.js.map +1 -0
  34. package/lib/esm/features/facets/utils/StringListFacet.js.map +1 -0
  35. package/lib/esm/features/facets/utils/TypeFacet.js.map +1 -0
  36. package/lib/esm/features/facets/utils/VEnvironmentFacet.js.map +1 -0
  37. package/lib/esm/features/facets/utils/VInteractionFacet.js.map +1 -0
  38. package/lib/esm/features/facets/utils/VStringFacet.js.map +1 -0
  39. package/lib/esm/features/facets/{VTypeFacet.js → utils/VTypeFacet.js} +5 -3
  40. package/lib/esm/features/facets/utils/VTypeFacet.js.map +1 -0
  41. package/lib/esm/features/facets/{VUserFacet.js → utils/VUserFacet.js} +1 -1
  42. package/lib/esm/features/facets/utils/VUserFacet.js.map +1 -0
  43. package/lib/esm/features/facets/utils/utils.js.map +1 -0
  44. package/lib/esm/features/store/collections/EditCollectionView.js +14 -1
  45. package/lib/esm/features/store/collections/EditCollectionView.js.map +1 -1
  46. package/lib/esm/features/store/collections/SelectCollection.js +47 -18
  47. package/lib/esm/features/store/collections/SelectCollection.js.map +1 -1
  48. package/lib/esm/features/store/objects/DocumentSearchResults.js +9 -5
  49. package/lib/esm/features/store/objects/DocumentSearchResults.js.map +1 -1
  50. package/lib/esm/features/store/objects/components/ContentOverview.js +172 -78
  51. package/lib/esm/features/store/objects/components/ContentOverview.js.map +1 -1
  52. package/lib/esm/features/store/objects/components/DocumentIcon.js +6 -0
  53. package/lib/esm/features/store/objects/components/DocumentIcon.js.map +1 -1
  54. package/lib/esm/features/store/objects/layout/documentLayout.js +3 -4
  55. package/lib/esm/features/store/objects/layout/documentLayout.js.map +1 -1
  56. package/lib/esm/features/store/objects/selection/actions/AddToCollectionAction.js +2 -2
  57. package/lib/esm/features/store/objects/selection/actions/AddToCollectionAction.js.map +1 -1
  58. package/lib/esm/features/store/objects/upload/DocumentUploadModal.js +1 -1
  59. package/lib/esm/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  60. package/lib/esm/features/store/types/ObjectSchemaEditor.js +1 -1
  61. package/lib/esm/features/store/types/ObjectSchemaEditor.js.map +1 -1
  62. package/lib/esm/features/user/UserInfo.js +33 -1
  63. package/lib/esm/features/user/UserInfo.js.map +1 -1
  64. package/lib/esm/shell/login/UserInfo.js +1 -1
  65. package/lib/esm/shell/login/UserInfo.js.map +1 -1
  66. package/lib/esm/widgets/schema-editor/index.js +0 -1
  67. package/lib/esm/widgets/schema-editor/index.js.map +1 -1
  68. package/lib/tsconfig.tsbuildinfo +1 -1
  69. package/lib/types/core/components/SelectList.d.ts +2 -1
  70. package/lib/types/core/components/SelectList.d.ts.map +1 -1
  71. package/lib/types/core/components/SidePanel.d.ts.map +1 -1
  72. package/lib/types/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
  73. package/lib/types/core/components/shadcn/index.d.ts +1 -0
  74. package/lib/types/core/components/shadcn/index.d.ts.map +1 -1
  75. package/lib/types/core/components/shadcn/resizeable.d.ts +9 -0
  76. package/lib/types/core/components/shadcn/resizeable.d.ts.map +1 -0
  77. package/lib/types/core/components/shadcn/tabs.d.ts +2 -1
  78. package/lib/types/core/components/shadcn/tabs.d.ts.map +1 -1
  79. package/lib/types/features/facets/CollectionsFacetsNav.d.ts +14 -0
  80. package/lib/types/features/facets/CollectionsFacetsNav.d.ts.map +1 -0
  81. package/lib/types/features/facets/DocumentsFacetsNav.d.ts +1 -1
  82. package/lib/types/features/facets/DocumentsFacetsNav.d.ts.map +1 -1
  83. package/lib/types/features/facets/InteractionsFacetsNav.d.ts +13 -0
  84. package/lib/types/features/facets/InteractionsFacetsNav.d.ts.map +1 -0
  85. package/lib/types/features/facets/PromptsFacetsNav.d.ts +15 -0
  86. package/lib/types/features/facets/PromptsFacetsNav.d.ts.map +1 -0
  87. package/lib/types/features/facets/RunsFacetsNav.d.ts +1 -1
  88. package/lib/types/features/facets/RunsFacetsNav.d.ts.map +1 -1
  89. package/lib/types/features/facets/WorkflowExecutionsFacetsNav.d.ts +1 -1
  90. package/lib/types/features/facets/WorkflowExecutionsFacetsNav.d.ts.map +1 -1
  91. package/lib/types/features/facets/index.d.ts +10 -8
  92. package/lib/types/features/facets/index.d.ts.map +1 -1
  93. package/lib/types/features/facets/{VFacetsNav.d.ts → utils/SearchInterface.d.ts} +1 -8
  94. package/lib/types/features/facets/utils/SearchInterface.d.ts.map +1 -0
  95. package/lib/types/features/facets/utils/StringFacet.d.ts.map +1 -0
  96. package/lib/types/features/facets/utils/StringListFacet.d.ts.map +1 -0
  97. package/lib/types/features/facets/utils/TypeFacet.d.ts.map +1 -0
  98. package/lib/types/features/facets/utils/VEnvironmentFacet.d.ts.map +1 -0
  99. package/lib/types/features/facets/utils/VInteractionFacet.d.ts.map +1 -0
  100. package/lib/types/features/facets/utils/VStringFacet.d.ts.map +1 -0
  101. package/lib/types/features/facets/utils/VTypeFacet.d.ts.map +1 -0
  102. package/lib/types/features/facets/utils/VUserFacet.d.ts.map +1 -0
  103. package/lib/types/features/facets/utils/utils.d.ts.map +1 -0
  104. package/lib/types/features/store/collections/EditCollectionView.d.ts.map +1 -1
  105. package/lib/types/features/store/collections/SelectCollection.d.ts +10 -8
  106. package/lib/types/features/store/collections/SelectCollection.d.ts.map +1 -1
  107. package/lib/types/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
  108. package/lib/types/features/store/objects/components/ContentOverview.d.ts.map +1 -1
  109. package/lib/types/features/store/objects/components/DocumentIcon.d.ts +4 -0
  110. package/lib/types/features/store/objects/components/DocumentIcon.d.ts.map +1 -1
  111. package/lib/types/features/store/objects/layout/documentLayout.d.ts.map +1 -1
  112. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts +1 -3
  113. package/lib/types/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  114. package/lib/types/features/store/objects/upload/DocumentUploadModal.d.ts.map +1 -1
  115. package/lib/types/features/user/UserInfo.d.ts +12 -1
  116. package/lib/types/features/user/UserInfo.d.ts.map +1 -1
  117. package/lib/types/widgets/schema-editor/index.d.ts +0 -1
  118. package/lib/types/widgets/schema-editor/index.d.ts.map +1 -1
  119. package/lib/vertesia-ui-core.js +1 -1
  120. package/lib/vertesia-ui-core.js.map +1 -1
  121. package/lib/vertesia-ui-features.js +1 -1
  122. package/lib/vertesia-ui-features.js.map +1 -1
  123. package/lib/vertesia-ui-shell.js +1 -1
  124. package/lib/vertesia-ui-shell.js.map +1 -1
  125. package/lib/vertesia-ui-widgets.js +1 -1
  126. package/lib/vertesia-ui-widgets.js.map +1 -1
  127. package/package.json +5 -4
  128. package/src/core/components/SelectList.tsx +11 -1
  129. package/src/core/components/SidePanel.tsx +13 -10
  130. package/src/core/components/shadcn/filters/filterBar.tsx +46 -20
  131. package/src/core/components/shadcn/index.ts +1 -0
  132. package/src/core/components/shadcn/resizeable.tsx +54 -0
  133. package/src/core/components/shadcn/tabs.tsx +16 -6
  134. package/src/core/components/table/index.tsx +1 -1
  135. package/src/features/facets/CollectionsFacetsNav.tsx +94 -0
  136. package/src/features/facets/DocumentsFacetsNav.tsx +22 -11
  137. package/src/features/facets/EnvironmentFacet.tsx +1 -1
  138. package/src/features/facets/InteractionsFacetsNav.tsx +111 -0
  139. package/src/features/facets/PromptsFacetsNav.tsx +110 -0
  140. package/src/features/facets/RunsFacetsNav.tsx +40 -9
  141. package/src/features/facets/WorkflowExecutionsFacetsNav.tsx +10 -8
  142. package/src/features/facets/index.ts +11 -9
  143. package/src/features/facets/utils/SearchInterface.tsx +8 -0
  144. package/src/features/facets/{VTypeFacet.tsx → utils/VTypeFacet.tsx} +6 -3
  145. package/src/features/facets/{VUserFacet.tsx → utils/VUserFacet.tsx} +1 -1
  146. package/src/features/store/collections/EditCollectionView.tsx +14 -1
  147. package/src/features/store/collections/SelectCollection.tsx +160 -31
  148. package/src/features/store/objects/DocumentSearchResults.tsx +42 -37
  149. package/src/features/store/objects/components/ContentOverview.tsx +432 -261
  150. package/src/features/store/objects/components/DocumentIcon.tsx +31 -1
  151. package/src/features/store/objects/layout/documentLayout.tsx +3 -7
  152. package/src/features/store/objects/selection/actions/AddToCollectionAction.tsx +15 -8
  153. package/src/features/store/objects/upload/DocumentUploadModal.tsx +5 -6
  154. package/src/features/store/types/ObjectSchemaEditor.tsx +1 -1
  155. package/src/features/user/UserInfo.tsx +66 -3
  156. package/src/shell/login/UserInfo.tsx +1 -1
  157. package/src/widgets/schema-editor/index.ts +0 -1
  158. package/lib/esm/features/facets/FacetsNav.js +0 -8
  159. package/lib/esm/features/facets/FacetsNav.js.map +0 -1
  160. package/lib/esm/features/facets/StringFacet.js.map +0 -1
  161. package/lib/esm/features/facets/StringListFacet.js.map +0 -1
  162. package/lib/esm/features/facets/TypeFacet.js.map +0 -1
  163. package/lib/esm/features/facets/VEnvironmentFacet.js.map +0 -1
  164. package/lib/esm/features/facets/VFacetsNav.js +0 -48
  165. package/lib/esm/features/facets/VFacetsNav.js.map +0 -1
  166. package/lib/esm/features/facets/VInteractionFacet.js.map +0 -1
  167. package/lib/esm/features/facets/VStringFacet.js.map +0 -1
  168. package/lib/esm/features/facets/VTypeFacet.js.map +0 -1
  169. package/lib/esm/features/facets/VUserFacet.js.map +0 -1
  170. package/lib/esm/features/facets/utils.js.map +0 -1
  171. package/lib/esm/widgets/schema-editor/JSONSchemaEditorModal.js +0 -49
  172. package/lib/esm/widgets/schema-editor/JSONSchemaEditorModal.js.map +0 -1
  173. package/lib/types/features/facets/FacetsNav.d.ts +0 -7
  174. package/lib/types/features/facets/FacetsNav.d.ts.map +0 -1
  175. package/lib/types/features/facets/StringFacet.d.ts.map +0 -1
  176. package/lib/types/features/facets/StringListFacet.d.ts.map +0 -1
  177. package/lib/types/features/facets/TypeFacet.d.ts.map +0 -1
  178. package/lib/types/features/facets/VEnvironmentFacet.d.ts.map +0 -1
  179. package/lib/types/features/facets/VFacetsNav.d.ts.map +0 -1
  180. package/lib/types/features/facets/VInteractionFacet.d.ts.map +0 -1
  181. package/lib/types/features/facets/VStringFacet.d.ts.map +0 -1
  182. package/lib/types/features/facets/VTypeFacet.d.ts.map +0 -1
  183. package/lib/types/features/facets/VUserFacet.d.ts.map +0 -1
  184. package/lib/types/features/facets/utils.d.ts.map +0 -1
  185. package/lib/types/widgets/schema-editor/JSONSchemaEditorModal.d.ts +0 -10
  186. package/lib/types/widgets/schema-editor/JSONSchemaEditorModal.d.ts.map +0 -1
  187. package/src/features/facets/FacetsNav.tsx +0 -19
  188. package/src/features/facets/VFacetsNav.tsx +0 -81
  189. package/src/widgets/schema-editor/JSONSchemaEditorModal.tsx +0 -67
  190. /package/lib/esm/features/facets/{StringFacet.js → utils/StringFacet.js} +0 -0
  191. /package/lib/esm/features/facets/{StringListFacet.js → utils/StringListFacet.js} +0 -0
  192. /package/lib/esm/features/facets/{TypeFacet.js → utils/TypeFacet.js} +0 -0
  193. /package/lib/esm/features/facets/{VEnvironmentFacet.js → utils/VEnvironmentFacet.js} +0 -0
  194. /package/lib/esm/features/facets/{VInteractionFacet.js → utils/VInteractionFacet.js} +0 -0
  195. /package/lib/esm/features/facets/{VStringFacet.js → utils/VStringFacet.js} +0 -0
  196. /package/lib/esm/features/facets/{utils.js → utils/utils.js} +0 -0
  197. /package/lib/types/features/facets/{StringFacet.d.ts → utils/StringFacet.d.ts} +0 -0
  198. /package/lib/types/features/facets/{StringListFacet.d.ts → utils/StringListFacet.d.ts} +0 -0
  199. /package/lib/types/features/facets/{TypeFacet.d.ts → utils/TypeFacet.d.ts} +0 -0
  200. /package/lib/types/features/facets/{VEnvironmentFacet.d.ts → utils/VEnvironmentFacet.d.ts} +0 -0
  201. /package/lib/types/features/facets/{VInteractionFacet.d.ts → utils/VInteractionFacet.d.ts} +0 -0
  202. /package/lib/types/features/facets/{VStringFacet.d.ts → utils/VStringFacet.d.ts} +0 -0
  203. /package/lib/types/features/facets/{VTypeFacet.d.ts → utils/VTypeFacet.d.ts} +0 -0
  204. /package/lib/types/features/facets/{VUserFacet.d.ts → utils/VUserFacet.d.ts} +0 -0
  205. /package/lib/types/features/facets/{utils.d.ts → utils/utils.d.ts} +0 -0
  206. /package/src/features/facets/{StringFacet.tsx → utils/StringFacet.tsx} +0 -0
  207. /package/src/features/facets/{StringListFacet.tsx → utils/StringListFacet.tsx} +0 -0
  208. /package/src/features/facets/{TypeFacet.tsx → utils/TypeFacet.tsx} +0 -0
  209. /package/src/features/facets/{VEnvironmentFacet.tsx → utils/VEnvironmentFacet.tsx} +0 -0
  210. /package/src/features/facets/{VInteractionFacet.tsx → utils/VInteractionFacet.tsx} +0 -0
  211. /package/src/features/facets/{VStringFacet.tsx → utils/VStringFacet.tsx} +0 -0
  212. /package/src/features/facets/{utils.tsx → utils/utils.tsx} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertesia/ui",
3
- "version": "0.78.0",
3
+ "version": "0.79.0",
4
4
  "description": "Vertesia UI components and and hooks",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",
@@ -56,13 +56,14 @@
56
56
  "react-dom": "^19.1.0",
57
57
  "react-error-boundary": "^6.0.0",
58
58
  "react-markdown": "^10.1.0",
59
+ "react-resizable-panels": "^3.0.6",
59
60
  "remark-gfm": "^4.0.1",
60
61
  "tailwind-merge": "^3.3.0",
61
62
  "ts-md5": "^1.3.1",
62
63
  "unist-util-visit": "^5.0.0",
63
- "@vertesia/client": "0.78.0",
64
- "@vertesia/common": "0.78.0",
65
- "@vertesia/json": "0.78.0"
64
+ "@vertesia/client": "0.79.0",
65
+ "@vertesia/json": "0.79.0",
66
+ "@vertesia/common": "0.79.0"
66
67
  },
67
68
  "devDependencies": {
68
69
  "@eslint/js": "^9.27.0",
@@ -1,6 +1,7 @@
1
1
  import { Check } from "lucide-react";
2
2
  import clsx from "clsx";
3
3
  import { ReactNode, useMemo, useState } from "react";
4
+ import { Input } from "./shadcn";
4
5
 
5
6
  const Default_Option_Style = "flex-1 px-2 py-2 hover:bg-accent nowrap";
6
7
 
@@ -28,9 +29,12 @@ export interface SelectListProps<T> {
28
29
  by?: (keyof T & string) | ((o1: T, o2: T) => boolean);
29
30
  optionLayout?: (opt: T, selected: boolean) => OptionLayout;
30
31
  noCheck?: boolean;
32
+ filterBy?: (filterValue: string) => (opt: T) => boolean;
31
33
  }
32
- export function SelectList<T>({ value, options, onChange, className, optionLayout, by, noCheck }: SelectListProps<T>) {
34
+ export function SelectList<T>({ value, options, onChange, className, optionLayout, by, noCheck, filterBy }: SelectListProps<T>) {
33
35
  const [selected, setSelected] = useState(value);
36
+ const [filterValue, setFilterValue] = useState("");
37
+
34
38
  const onSelect = (option: T) => {
35
39
  setSelected(option);
36
40
  onChange(option);
@@ -46,7 +50,13 @@ export function SelectList<T>({ value, options, onChange, className, optionLayou
46
50
  }, [by]);
47
51
  return (
48
52
  <div className={clsx("", className)}>
53
+ {filterBy && (
54
+ <Input type="text" placeholder="Filter..." value={filterValue} onChange={(value) => setFilterValue(value)} />
55
+ )}
49
56
  {options.map((option, i) => {
57
+ if (filterBy && !filterBy(filterValue)(option)) {
58
+ return null;
59
+ }
50
60
  const isSelected = selected ? optionEquals(selected, option) : false;
51
61
  let layout: OptionLayout;
52
62
  if (optionLayout) {
@@ -53,7 +53,7 @@ export function SidePanel({ isOpen, title, onClose, children, panelWidth = 768,
53
53
  onClick={onClose}
54
54
  />
55
55
  )}
56
-
56
+
57
57
  <div className="fixed inset-y-0 right-0 overflow-hidden">
58
58
  <div className="absolute inset-0 overflow-hidden">
59
59
  <div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10 sm:pl-16">
@@ -71,17 +71,20 @@ export function SidePanel({ isOpen, title, onClose, children, panelWidth = 768,
71
71
  className="absolute left-0 top-0 bottom-0 w-1 cursor-ew-resize hover:bg-indigo-500 transition-colors"
72
72
  onMouseDown={handleDragStart}
73
73
  />
74
- <div className="flex-1 flex flex-col overflow-y-scroll gap-4 bg-background py-6 shadow-xl">
75
- <div className="px-2 sm:px-4">
76
- <div className="flex items-start justify-between">
77
- <h2 className="w-full text-base font-semibold leading-6">
78
- <div className="text-2xl">{title ?? ""}</div>
79
- </h2>
80
- <div className="ml-3 flex h-7 items-center">
81
- <CloseButton onClose={onClose} />
74
+ <div className="flex-1 flex flex-col overflow-y-scroll gap-4 bg-background py-2 shadow-xl">
75
+ {title && (
76
+ <div className="px-2 sm:px-4">
77
+ <div className="flex items-start justify-between">
78
+ <h2 className="w-full text-base font-semibold leading-6">
79
+ <div className="text-2xl">{title ?? ""}</div>
80
+ </h2>
81
+ <div className="ml-3 flex h-7 items-center">
82
+ <CloseButton onClose={onClose} />
83
+ </div>
82
84
  </div>
83
85
  </div>
84
- </div>
86
+ )}
87
+
85
88
  <div className="px-2 sm:px-4">
86
89
  {children}
87
90
  </div>
@@ -29,7 +29,8 @@ interface FilterProviderProps {
29
29
  const FilterProvider = ({ filters, setFilters, filterGroups, children }: FilterProviderProps) => {
30
30
  const url = new URL(window.location.href);
31
31
  const searchParams = url.searchParams;
32
-
32
+ const [hasInitialized, setHasInitialized] = React.useState(false);
33
+
33
34
  useEffect(() => {
34
35
  try {
35
36
  const params = new URLSearchParams(searchParams.toString());
@@ -42,16 +43,22 @@ const FilterProvider = ({ filters, setFilters, filterGroups, children }: FilterP
42
43
  // Handle stringList with direct string array - always array format
43
44
  values = `[${(filter.value as string[]).map(item => encodeURIComponent(item)).join(',')}]`;
44
45
  } else if (Array.isArray(filter.value)) {
45
- if (filter.multiple || filter.value.length > 1) {
46
- // Handle multiple values - use array format
47
- values = `[${filter.value.map((item: any) => encodeURIComponent(item.value || '')).join(',')}]`;
46
+ if (filter.multiple) {
47
+ // Handle multiple filters - always use array format for multiple=true
48
+ values = `[${filter.value.map((item: any) => encodeURIComponent(item.value || item || '')).join(',')}]`;
49
+ } else if (filter.value.length > 1) {
50
+ // Handle multiple values for non-multiple filters
51
+ values = `[${filter.value.map((item: any) => encodeURIComponent(item.value || item || '')).join(',')}]`;
48
52
  } else {
49
- // Single value in array - don't use array format
53
+ // Single value in array for non-multiple filter - don't use array format
50
54
  const firstValue = filter.value[0];
51
55
  if (typeof firstValue === 'string') {
52
56
  values = encodeURIComponent(firstValue);
57
+ } else if (typeof firstValue === 'object' && firstValue?.value !== undefined) {
58
+ // Handle FilterOption object
59
+ values = encodeURIComponent(String(firstValue.value));
53
60
  } else {
54
- values = encodeURIComponent(firstValue?.value || '');
61
+ values = encodeURIComponent(String(firstValue || ''));
55
62
  }
56
63
  }
57
64
  } else {
@@ -73,14 +80,14 @@ const FilterProvider = ({ filters, setFilters, filterGroups, children }: FilterP
73
80
 
74
81
  useEffect(() => {
75
82
  const filtersParam = searchParams.get('filters');
76
- if (filtersParam) {
83
+ if (filtersParam && filterGroups.length > 0 && !hasInitialized) {
77
84
  try {
78
85
  // Parse format with array indicators: filterName:value or filterName:[value1,value2]
79
86
  const filterPairs = filtersParam.split(';');
80
87
  const parsedFilters = filterPairs.map(pair => {
81
88
  const [encodedName, valuesString] = pair.split(':');
82
89
  const name = decodeURIComponent(encodedName);
83
-
90
+
84
91
  let values: string[];
85
92
  // Check if it's an array format [value1,value2]
86
93
  if (valuesString.startsWith('[') && valuesString.endsWith(']')) {
@@ -100,8 +107,8 @@ const FilterProvider = ({ filters, setFilters, filterGroups, children }: FilterP
100
107
  filterValue = values;
101
108
  } else if (group?.type === 'text') {
102
109
  // For text, return FilterOption array (single value for text inputs)
103
- filterValue = values.length === 1 ? [{ value: values[0], label: values[0] }] :
104
- values.map(value => ({ value, label: value }));
110
+ filterValue = values.length === 1 ? [{ value: values[0], label: values[0] }] :
111
+ values.map(value => ({ value, label: value }));
105
112
  } else {
106
113
  // For other types, find options with labels
107
114
  filterValue = values.map(value => {
@@ -123,21 +130,40 @@ const FilterProvider = ({ filters, setFilters, filterGroups, children }: FilterP
123
130
  });
124
131
  }
125
132
 
126
- return {
133
+ if (group?.multiple && !valuesString.startsWith('[') && !valuesString.endsWith(']')) {
134
+ if (group.type === 'stringList') {
135
+ filterValue = values;
136
+ } else {
137
+ if (!Array.isArray(filterValue)) {
138
+ filterValue = [filterValue];
139
+ }
140
+ }
141
+ }
142
+
143
+ // Fallback: if group not found but we detected array format, assume it should be multiple
144
+ const shouldBeMultiple = group?.multiple || (!group && valuesString.startsWith('[') && valuesString.endsWith(']'));
145
+
146
+ const filter = {
127
147
  name,
128
148
  type: group?.type || 'select',
129
149
  placeholder: group?.placeholder,
130
150
  value: filterValue,
131
- multiple: group?.multiple || false
151
+ multiple: shouldBeMultiple
132
152
  };
153
+
154
+ return filter;
133
155
  });
134
156
 
135
157
  setFilters(parsedFilters);
158
+ setHasInitialized(true);
136
159
  } catch (error) {
137
- console.error("Failed to parse filters from URL:", error);
160
+ setHasInitialized(true);
138
161
  }
162
+ } else if (filterGroups.length > 0 && !hasInitialized) {
163
+ // No URL params but we have groups - mark as initialized
164
+ setHasInitialized(true);
139
165
  }
140
- }, []);
166
+ }, [filterGroups, hasInitialized]);
141
167
 
142
168
  return (
143
169
  <FilterContext.Provider value={{ filters, setFilters, filterGroups }}>
@@ -287,7 +313,7 @@ const FilterBtn = ({ className }: { className?: string }) => {
287
313
  {"Filter"}
288
314
  </Button>
289
315
  </PopoverTrigger>
290
- <PopoverContent className="w-[300px] p-0" align="start">
316
+ <PopoverContent className="w-[300px] p-0" align="start" sideOffset={4}>
291
317
  <Command>
292
318
  {
293
319
  filterGroups.find(group => group.name === selectedView)?.type === "select" && (
@@ -303,7 +329,7 @@ const FilterBtn = ({ className }: { className?: string }) => {
303
329
  />
304
330
  )
305
331
  }
306
- <CommandList className="max-h-[300px] overflow-y-auto">
332
+ <CommandList>
307
333
  <CommandGroup>
308
334
  {!selectedView ? getAvailableFilterGroups() : renderFilterOptions()}
309
335
  </CommandGroup>
@@ -316,7 +342,7 @@ const FilterBtn = ({ className }: { className?: string }) => {
316
342
 
317
343
  const FilterBar = ({ className }: { className?: string }) => {
318
344
  const { filters, setFilters, filterGroups } = React.useContext(FilterContext);
319
-
345
+
320
346
  return (
321
347
  <div className={cn(className)}>
322
348
  <Filters filters={filters} setFilters={setFilters} filterGroups={filterGroups} />
@@ -326,13 +352,13 @@ const FilterBar = ({ className }: { className?: string }) => {
326
352
 
327
353
  const FilterClear = ({ className }: { className?: string }) => {
328
354
  const { filters, setFilters } = React.useContext(FilterContext);
329
-
355
+
330
356
  const hasActiveFilters = filters.filter((filter) => filter.value?.length > 0).length > 0;
331
-
357
+
332
358
  if (!hasActiveFilters) {
333
359
  return null;
334
360
  }
335
-
361
+
336
362
  return (
337
363
  <Button
338
364
  variant="outline"
@@ -20,3 +20,4 @@ export * from './command';
20
20
  export * from './checkbox';
21
21
  export * from './heading';
22
22
  export * from './text';
23
+ export * from './resizeable';
@@ -0,0 +1,54 @@
1
+ import * as React from "react"
2
+ import { GripVerticalIcon } from "lucide-react"
3
+ import * as ResizablePrimitive from "react-resizable-panels"
4
+
5
+ import { cn } from "../libs/utils"
6
+
7
+ function ResizablePanelGroup({
8
+ className,
9
+ ...props
10
+ }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) {
11
+ return (
12
+ <ResizablePrimitive.PanelGroup
13
+ data-slot="resizable-panel-group"
14
+ className={cn(
15
+ "flex h-full w-full data-[panel-group-direction=vertical]:flex-col",
16
+ className
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function ResizablePanel({
24
+ ...props
25
+ }: React.ComponentProps<typeof ResizablePrimitive.Panel>) {
26
+ return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
27
+ }
28
+
29
+ function ResizableHandle({
30
+ withHandle,
31
+ className,
32
+ ...props
33
+ }: React.ComponentProps<typeof ResizablePrimitive.PanelResizeHandle> & {
34
+ withHandle?: boolean
35
+ }) {
36
+ return (
37
+ <ResizablePrimitive.PanelResizeHandle
38
+ data-slot="resizable-handle"
39
+ className={cn(
40
+ "bg-border focus-visible:ring-ring relative flex w-px items-center justify-center after:absolute after:inset-y-0 after:left-1/2 after:w-1 after:-translate-x-1/2 focus-visible:ring-1 focus-visible:ring-offset-1 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[panel-group-direction=vertical]:after:left-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
41
+ className
42
+ )}
43
+ {...props}
44
+ >
45
+ {withHandle && (
46
+ <div className="bg-border z-10 flex h-4 w-3 items-center justify-center rounded-xs border">
47
+ <GripVerticalIcon className="size-2.5" />
48
+ </div>
49
+ )}
50
+ </ResizablePrimitive.PanelResizeHandle>
51
+ )
52
+ }
53
+
54
+ export { ResizablePanelGroup, ResizablePanel, ResizableHandle }
@@ -12,13 +12,15 @@ const TabsContext = React.createContext<{
12
12
  setTab?: (name: string) => void;
13
13
  responsive?: boolean;
14
14
  variant?: "tabs" | "pills";
15
+ updateHash?: boolean;
15
16
  }>({
16
17
  size: undefined,
17
18
  tabs: undefined,
18
19
  current: undefined,
19
20
  setTab: undefined,
20
21
  responsive: false,
21
- variant: "tabs"
22
+ variant: "tabs",
23
+ updateHash: true
22
24
  });
23
25
 
24
26
  interface TabsProps {
@@ -31,6 +33,7 @@ interface TabsProps {
31
33
  onTabChange?: (tabName: string) => void;
32
34
  responsive?: boolean;
33
35
  variant?: "tabs" | "pills";
36
+ updateHash?: boolean;
34
37
  }
35
38
 
36
39
  const VTabs = ({
@@ -42,7 +45,8 @@ const VTabs = ({
42
45
  children,
43
46
  onTabChange,
44
47
  responsive = false,
45
- variant = "tabs"
48
+ variant = "tabs",
49
+ updateHash = true
46
50
  }: TabsProps) => {
47
51
  // Initialize value
48
52
  const [value, setValue] = React.useState(() => {
@@ -99,6 +103,12 @@ const VTabs = ({
99
103
 
100
104
  const handleValueChange = (newValue: string) => {
101
105
  setValue(newValue);
106
+
107
+ // Update the URL hash when tab changes (only if updateHash is true and not controlled by parent)
108
+ if (updateHash && !current) {
109
+ window.location.hash = newValue;
110
+ }
111
+
102
112
  if (onTabChange) {
103
113
  onTabChange(newValue);
104
114
  }
@@ -109,7 +119,7 @@ const VTabs = ({
109
119
  }, [handleValueChange]);
110
120
 
111
121
  return (
112
- <TabsContext.Provider value={{ tabs, size: fullWidth ? tabs.length : 0, current: value, setTab, responsive: responsive, variant }}>
122
+ <TabsContext.Provider value={{ tabs, size: fullWidth ? tabs.length : 0, current: value, setTab, responsive: responsive, variant, updateHash }}>
113
123
  <TabsPrimitive.Root
114
124
  defaultValue={value || tabs[0]?.name}
115
125
  value={value}
@@ -123,7 +133,7 @@ const VTabs = ({
123
133
  };
124
134
 
125
135
  const VTabsBar = ({ className }: { className?: string }) => {
126
- const { tabs, size, current, setTab, responsive, variant } = React.useContext(TabsContext);
136
+ const { tabs, size, current, setTab, responsive, variant, updateHash } = React.useContext(TabsContext);
127
137
 
128
138
  const fullWidth = size !== 0;
129
139
 
@@ -132,13 +142,13 @@ const VTabsBar = ({ className }: { className?: string }) => {
132
142
 
133
143
  const tab = tabs.find(t => t.name === tabName);
134
144
 
135
- if (tab?.href) {
145
+ if (tab?.href && updateHash) {
136
146
  window.history.pushState(null, '', tab.href);
137
147
  }
138
148
 
139
149
  setTab(tabName);
140
150
 
141
- }, [tabs, setTab]);
151
+ }, [tabs, setTab, updateHash]);
142
152
 
143
153
  if (!tabs || !setTab) {
144
154
  console.warn("TabsBar: No tabs provided or setTab not available");
@@ -23,7 +23,7 @@ export function RowSkeleton({ columns }: { columns: number }) {
23
23
  <tr className="hover:bg-muted">
24
24
  {Array(columns).fill(0).map((_, index) =>
25
25
  <td key={index}>
26
- <div className="animate-pulse rounded-xs h-5 bg-gray-200 dark:bg-gray-600"></div>
26
+ <div className="animate-pulse rounded-xs h-5 bg-muted"></div>
27
27
  </td>
28
28
  )}
29
29
  </tr>
@@ -0,0 +1,94 @@
1
+ import { Filter as BaseFilter, FilterProvider, FilterBtn, FilterBar, FilterClear, FilterGroup } from '@vertesia/ui/core';
2
+ import { useState } from 'react';
3
+ import { SearchInterface } from './utils/SearchInterface';
4
+
5
+ interface CollectionsFacetsNavProps {
6
+ facets: {
7
+ type?: any[];
8
+ dynamic?: any[];
9
+ };
10
+ search: SearchInterface;
11
+ }
12
+
13
+ // Hook to create filter groups for collections
14
+ export function useCollectionsFilterGroups(facets: CollectionsFacetsNavProps['facets']): FilterGroup[] {
15
+ void facets;
16
+ const customFilterGroups: FilterGroup[] = [];
17
+
18
+ // Add name filter as text type
19
+ const nameFilterGroup = {
20
+ name: 'name',
21
+ placeholder: 'Name',
22
+ type: 'text' as const,
23
+ multiple: false
24
+ };
25
+ customFilterGroups.push(nameFilterGroup);
26
+
27
+ return customFilterGroups;
28
+ }
29
+
30
+ // Hook to create filter change handler for collections
31
+ export function useCollectionsFilterHandler(search: SearchInterface) {
32
+ return (newFilters: BaseFilter[]) => {
33
+ if (newFilters.length === 0) {
34
+ // Clear filters without applying defaults - user wants to remove all filters
35
+ search.clearFilters(true);
36
+ return;
37
+ }
38
+
39
+ // Clear all filters first, then apply new ones
40
+ search.clearFilters(false);
41
+
42
+ newFilters.forEach(filter => {
43
+ if (filter.value && filter.value.length > 0) {
44
+ const filterName = filter.name;
45
+ let filterValue;
46
+ if (filter.type === 'stringList') {
47
+ filterValue = filter.value.map(v => typeof v === 'string' ? v : v.value);
48
+ } else if (filter.multiple) {
49
+ filterValue = Array.isArray(filter.value)
50
+ ? filter.value.map((v: any) => typeof v === 'object' && v.value ? v.value : v)
51
+ : [typeof filter.value === 'object' && (filter.value as any).value ? (filter.value as any).value : filter.value];
52
+ } else {
53
+ // Single value - don't wrap in array
54
+ filterValue = Array.isArray(filter.value) && filter.value[0] && typeof filter.value[0] === 'object'
55
+ ? (filter.value[0] as any).value
56
+ : Array.isArray(filter.value) && filter.value[0]
57
+ ? filter.value[0]
58
+ : filter.value;
59
+ }
60
+
61
+ search.query[filterName] = filterValue;
62
+ }
63
+ });
64
+
65
+ search.search();
66
+ };
67
+ }
68
+
69
+ // Component for collections filtering
70
+ export function CollectionsFacetsNav({ facets, search }: CollectionsFacetsNavProps) {
71
+ const [filters, setFilters] = useState<BaseFilter[]>([]);
72
+ const filterGroups = useCollectionsFilterGroups(facets);
73
+ const handleFilterLogic = useCollectionsFilterHandler(search);
74
+
75
+ const handleFilterChange: React.Dispatch<React.SetStateAction<BaseFilter[]>> = (value) => {
76
+ const newFilters = typeof value === 'function' ? value(filters) : value;
77
+ setFilters(newFilters);
78
+ handleFilterLogic(newFilters);
79
+ };
80
+
81
+ return (
82
+ <FilterProvider
83
+ filterGroups={filterGroups}
84
+ filters={filters}
85
+ setFilters={handleFilterChange}
86
+ >
87
+ <div className="flex gap-2 items-center">
88
+ <FilterBtn />
89
+ <FilterBar />
90
+ <FilterClear />
91
+ </div>
92
+ </FilterProvider>
93
+ );
94
+ }
@@ -1,9 +1,9 @@
1
1
  import { Filter as BaseFilter, FilterProvider, FilterBtn, FilterBar, FilterClear, FilterGroup } from '@vertesia/ui/core';
2
2
  import { useUserSession } from '@vertesia/ui/session';
3
3
  import { useState } from 'react';
4
- import { VStringFacet } from './VStringFacet';
5
- import { VTypeFacet } from './VTypeFacet';
6
- import { SearchInterface } from './VFacetsNav';
4
+ import { VStringFacet } from './utils/VStringFacet';
5
+ import { VTypeFacet } from './utils/VTypeFacet';
6
+ import { SearchInterface } from './utils/SearchInterface';
7
7
 
8
8
  interface DocumentsFacetsNavProps {
9
9
  facets: {
@@ -22,7 +22,14 @@ export function useDocumentFilterGroups(facets: DocumentsFacetsNavProps['facets'
22
22
  const customFilterGroups: FilterGroup[] = [];
23
23
 
24
24
  customFilterGroups.push({
25
- placeholder: 'Name or ID',
25
+ placeholder: 'ID',
26
+ name: 'id',
27
+ type: 'text',
28
+ options: [],
29
+ });
30
+
31
+ customFilterGroups.push({
32
+ placeholder: 'Name',
26
33
  name: 'name',
27
34
  type: 'text',
28
35
  options: [],
@@ -94,7 +101,7 @@ export function useDocumentFilterHandler(search: SearchInterface) {
94
101
  newFilters.forEach(filter => {
95
102
  if (filter.value && filter.value.length > 0) {
96
103
  const filterName = filter.name;
97
-
104
+
98
105
  let filterValue;
99
106
  if (filter.type === 'date' && filter.multiple) {
100
107
  // Handle date range filters
@@ -117,21 +124,25 @@ export function useDocumentFilterHandler(search: SearchInterface) {
117
124
  }
118
125
  }
119
126
  } else if (filter.multiple) {
120
- filterValue = Array.isArray(filter.value)
121
- ? filter.value.map((v: any) => typeof v === 'object' && v.value ? v.value : v)
122
- : [typeof filter.value === 'object' && (filter.value as any).value ? (filter.value as any).value : filter.value];
127
+ if (Array.isArray(filter.value)) {
128
+ filterValue = filter.value.map((v: any) => typeof v === 'object' && v.value ? v.value : v);
129
+ } else {
130
+ const singleValue = typeof filter.value === 'object' && (filter.value as any).value ? (filter.value as any).value : filter.value;
131
+ filterValue = [singleValue];
132
+ }
123
133
  } else {
124
134
  // Single value - don't wrap in array
125
135
  filterValue = Array.isArray(filter.value) && filter.value[0] && typeof filter.value[0] === 'object'
126
136
  ? (filter.value[0] as any).value
127
- : Array.isArray(filter.value) && filter.value[0]
137
+ : Array.isArray(filter.value) && filter.value[0]
128
138
  ? filter.value[0]
129
139
  : filter.value;
130
140
  }
131
-
141
+
132
142
  if (filterName === 'name') {
133
- search.query.search_term = filterValue;
134
143
  search.query.name = filterValue;
144
+ } else if (filterName === 'id') {
145
+ search.query.id = filterValue;
135
146
  } else {
136
147
  search.query[filterName] = filterValue;
137
148
  }
@@ -3,7 +3,7 @@ import { useEffect, useState } from 'react';
3
3
  import { FacetBucket, FacetNameBucket } from '@vertesia/common';
4
4
  import { SelectBox } from '@vertesia/ui/core';
5
5
  import { useUserSession } from '@vertesia/ui/session';
6
- import { facetOptionNameLabel } from './utils';
6
+ import { facetOptionNameLabel } from './utils/utils';
7
7
 
8
8
  interface EnvironmentFacetProps {
9
9
  search: any;